[Scummvm-git-logs] scummvm master -> 1ac7de437ed3d361ca315a4502583d5d1c74aba7

bluegr noreply at scummvm.org
Wed Oct 30 16:10:40 UTC 2024


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

Summary:
0b2bfb202d AGS: Common: implemented CopyFile (and RenameFile)
076e8ea4c2 AGS: Engine: in SetSaveGameDirectory use utility FileCopy() function
c0cc23c570 AGS: Engine: remake ScriptGUI and ScriptDialog arrays into std::vectors
5acc31c4ed AGS: Engine: remake LipSync arrays into std::vectors
86f8ab5bef AGS: Engine: use usetc instead of manual check of get_uformat()
cd093dbf79 AGS: Allegro: Allegro: added utf8 validation to unicode functions
e8248f3a46 AGS: Engine: mark transition screenshot screen as opaque texture
78e1e5f1d0 AGS: Common: in SpriteFile test if indexed format has less size than regular
27a3b5e3e2 AGS: Engine: refactored FloatToInt(), use std functions
c1ebccadbf AGS: Engine: in HandleToAddressAndManager zero the returned ptrs on error
2cfe391a12 AGS: Engine: Revert default opaque flag in screen transitions
f2ddfc90f5 AGS: Engine: ccInstance: cleaned up old commented code
91b699106f AGS: Engine: ccInstance: minor parameter adjustments
96ddb49fd0 AGS: Engine: ccInstance: hide DumpInstruction under DEBUG_CC_EXEC flag
650c93b7d0 AGS: Engine: ccInstance: have stack-related asserts under DEBUG_CC_EXEC
03214f54eb AGS: Engine: ccInstance: move stackdata_ptr for each stack op, be consistent
b1e4aba613 AGS: Engine: ccInstance: tiny optimizations in SCMD_MEMREAD/WRITE/INITPTR
4f32ca32cb AGS: Engine: ccInstance: refactored to alloc arg values per opcode
a0a739d497 AGS: Engine: another refactor for RuntimeScriptValue::Read/WriteValue funcs
57d7544f08 AGS: Engine: ccInstance: hid couple more frequent asserts under DEBUG
1040f79782 AGS: Engine: be able to define DEBUG_CC_EXEC explicitly
f5c0b75160 AGS: Engine: ccInstance: replaced fixups for loop with a switch
4dc7347a4f AGS: Engine: ccInstance: only do fixups for ops/args where expected
0bf9768151 AGS: Engine: ccInstance ScriptCommandInfo is const
69327ac20b AGS: Engine: fixed stackptr increment on SCMD_ADD
66560d9717 AGS: Engine: ccInstance: added ASSERT_STACK_UNWINDED for both cases of Run()
9b56d15ef7 AGS: Engine: fixed stack unwind assert was hiding other errors
4895a10b82 AGS: Engine: ccInstance: fixed MEMWRITEPTR may leave undefined value
afa54e0526 AGS: Engine: simplify IAGSEngine::GetManagedObjectAddressByKey()
3a5ccd4cba AGS: Engine: moved dynamic object management functions to a separate unit
34fd9c07d5 AGS: Fix missing function type
11f50581a3 AGS: Don't use uintptr_t
34e15c2e57 AGS: Engine: simplifications for ManagedPool
0f44b1946c AGS: Engine: refactored CCDynamicArray for clarity and easier extension
5e9e3e8915 AGS: Engine: bring DynArray and UserObject's unserialization to uniformity
43ff49b1ac AGS: Engine: CCDynamicArray and ScriptUserObject inherit AGSCCDynamicObject
d5bf4fee8e AGS: Engine: tidied use of Character::animating and flags
8ddf1c837d AGS: Engine: tidied use of RoomObject::cycling and flags
f75a45d38d AGS: Engine: separated RoomObject's internal ANIM flags, fix ONCERESET arg
c4f25a16ed AGS: Engine: ensure that all AnimateCharacter variants lead to the same impl
96257b87cb AGS: Engine: ensure that all AnimateObject variants lead to the same impl
d0e3493ab9 AGS: Engine: tidy Button_Animate* functions a little
e0aab081f2 AGS: Engine: picked out view-loop-frame Animate params check, fixup sframe
af1c822a15 AGS: Engine: picked out few bitmap conv functions to BitmapHelper
32433b1368 AGS: Engine: fixed error check and messages in ValidateViewAnimParams()
1467791290 AGS: Engine: tidy code a bit in script_runtime.cpp
09039ca805 AGS: Engine: brought multivariant script api functions to naming consistency
5fe0694e56 AGS: Engine: added explicit functions for Room.GetDrawingSurface api
ad168dbee8 AGS: Engine: added explicit functions for Speech api and GetAudioClipCount()
a90e5b7dd7 AGS: Engine: added missing exclusive variadic api impls for plugins
08bd16b555 AGS: Engine: replaced more if/else with switches in RuntimeScriptValue
8a69992c18 AGS: Support zlib deflate for sprites
13a7a97abd AGS: Common: in WFNFont::ReadFromFile replaced C arrays with std::vector
233ec786d2 AGS: Common: WFNRenderer supports UTF-8
15c6da02ca AGS: Engine: don't do the stupid pointer reassignment when loading a save
1ca1fe14c0 AGS: Common: store CharacterInfos in std::vector
101e5ae147 AGS: Common: store WordDictionary in std::unique_ptr
e7710a9076 AGS: Marked a first 3.6.1 build
33474b4c4a AGS: Engine: factored out ManagedObjectPool::Add()
393aed7dd2 AGS: Engine: removed AGS_INLINE macro, as it's useless
1eb11a29bc AGS: Engine: in construct_*_gfx use direct obj/char reference
ddadaa75c7 AGS: Engine: moved room object scaling update from draw to main game update
ca93d22ddd AGS: Engine: moved character scaling update from draw to main game update
cfbd6c1637 AGS: Engine: fixed character scale not updating at the fully blocking state
4a0efaf0ce AGS: Common: added few comments to the common constants
69ea55b721 AGS: Engine: fixes to construct_object_gfx()
a23bc88912 AGS: Engine: character and room object share function updating sprite scale
b107fa7e75 AGS: Fix a couple includes after move to common
1f335d8364 AGS: Engine: character and room object share function constructing gfx
582a5ba422 AGS: Engine: pass ObjTexture directly into obj gfx generating funcs
49fb8efa68 AGS: Engine: character and room object share function preparing texture
54154bfd35 AGS: Common: fixed FlagToFlag() and CharFlagsToObjFlags()
dc693a5cb4 AGS: Engine: fixed get_local_tint() was inverting meaning of "use tint" arg
32c2511523 AGS: Engine: a bool cast fix in construct_object_gfx()
b580078759 AGS: Engine: implemented "after fade-out" room callback, and global event
10813d021f AGS: Update script API version
12be21d0c6 AGS: Engine: implement eEventGameSaved
af96330c5a AGS: Updated build version (3.6.1.1)
b5617e2fc2 AGS: Engine: fixed a call to get_local_tint()
2de9c546b5 AGS: Engine: store DoOnceOnly tokens in a unordered_set
572bdbfe74 AGS: Script API: implemented Game.ResetDoOnceOnly()
072a9ee824 AGS: Engine: simplified GameSetupStruct and GameState reset in unload_game()
4019f9305b AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances
90e338e875 AGS: Common: hide Dialog::optionscripts under OBSOLETE
909fa718b9 AGS: Engine: store RoomStatuses in std::unique_ptrs
99cbbaacac AGS: Engine: removed obscure hack from draw_button_background()
ac85f7fc52 AGS: Engine: unified API assertions for view, loop and frame
0f14a257de AGS: Engine: tidied adjust_x/y_for_guis and skip offscreen GUIs
959d894606 AGS: Engine: fixed first Sierra speechline not accounting for disabled gui
b1e7617964 AGS: Engine: store game's ccInstances as unique_ptrs instead
253187f4e2 Revert "AGS: Engine: store game's ccInstances as unique_ptrs instead"
8153a59121 Revert "AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances"
6b2af4cced AGS: Engine: picked out plugin's save / restore data event runs
5bb1770019 AGS: Engine: fixed IAGSEngine::GetFontType() not using proper font test
3d981f1159 AGS: SpriteCache: separated Remove() and Dispose() methods for convenience
90f94f74c0 AGS: Engine: reduced spriteset's [] operator use where possible
9225909b77 AGS: Engine: disable vsync in the "infinite fps" mode
24e8507116 AGS: Engine: support RTL in Button and ListBox
c05a1c4134 AGS: Engine: apply text direction based on guicontrol flags and game version
848d2b4ae5 AGS: Engine: support RTL in TextBox
db4637ed38 AGS: Editor: moved GUIListBox.Translated property to GUIControl parent
1c32a8538c AGS: Engine: force translate Buttons and Labels in pre-3.6.1 games
3d1e23b2a1 AGS: Engine: remember overlay index and try it before looking again
3cfeee9c0a AGS: Engine: support RTL in DrawString
f2a5f68e8c AGS: Engine: use string wrap to make transtree.find a tiny bit faster
0f5d6a1f5e AGS: Engine: store plugin infos in std::vector
df0cfa9ba4 AGS: Engine: in BitmapToVideoMem() replaced char* ptrs with uint8_t*
d8d4a77724 AGS: Engine: hide "screenover" vector behind interface functions
f9087ab378 AGS: Engine: optimized ScreenOverlays storage (vector variant)
9cd644115a AGS: Engine: fixed overlay read and write in a save: don't save empty slots
a010fddc55 AGS: Engine: safety checks for invalid id when reading overlays
aedaa4ce88 AGS: Engine: fixes for overlay id comparisons
0bb33fcfc5 AGS: Engine: made sure game res inited in InitGameState()
7ba6cf903c AGS: Engine: Removed redundant code from engine init
178826a9d8 AGS: Engine: init few more things along the GameState struct
5c5b7fb026 AGS: Engine: force std message box for debug info
72a89a8bab AGS: Engine: proper GameState initialization
a0f50c8cc9 AGS: Engine: fix declaration
92d702c4ab AGS: Engine: Fix DoOnceOnly not finding any tokens
ee1f1053d7 AGS: Updated build version (3.6.1.2)
9f9eaa1e34 AGS: Common: implemented ResourceCache as an abstract class
65237c4ae8 AGS: Engine: in GraphicsDriver made alpha & opaque params consistent
6c8b82523d AGS: Engine: let configure texture cache size
34a00ea83b AGS: Fix typos: loose -> lose
a05c63e4df AGS: Engine: fix cache config and read legacy values too
a3d471074f AGS: Engine: Engine: query gfxdriver for texture mem, and optionally restrict cache
e572c4333c AGS: Engine: print cache sizes to log
6c517c2dbd AGS: Engine: corrected few File::Open and File::Test calls
5edf71bad9 AGS: Implemented AppendPath
710f053d6b AGS: Updated build version (3.6.1.3)
a10d4f5201 AGS: Common: added optional srcName field to Stream, to help with debugging
0f7b25e4a4 AGS: Engine: when resolving script file, use filepath saved in Stream
951a8ee7e0 AGS: Engine: ScriptFileHandle stores Stream in unique_ptr
887aea0229 AGS: Engine: fixed incorrect ScalingFar condition in update_object_scale()
8af54c7f21 AGS: Script API: implemented File.ResolvePath()
7e3aac02bf AGS: Plugin API: added ResolveFilePath()
4ba37782f4 AGS: Script API: implemented File_GetPath
3076969479 AGS: Updated build version (3.6.1.4)
455caa2f84 AGS: Engine: some simplification for IAGSEngine::PlaySoundChannel()
0f437fb44a AGS: Engine: simplify free_dynamic_sprite(), also don't quit on bad index
48f18177e7 AGS: Engine: picked out SetObjectFrameSimple that does not play frame sound
dd8c13c1f0 AGS: Engine: replaced global evblock* vars with a local ObjectEvent argument
51cf1f1568 AGS: Engine: tidied code in RunObjectInteraction functions
26167025c4 AGS: Engine: fixed GUI::TransformTextForDrawing() using translate flag wrong
720b7a53ec AGS: Engine: object interaction events take obj pointer and mode as params
f35fe9b988 AGS: Script API: added Object.AnimationVolume, like in Character
2ae4a1ebd8 AGS: Engine: some refactor + apply volume for character face animations
82a656691f AGS: Updated build version (3.6.1.5)
f62f270423 AGS: Engine: use enum for Text script callback types
f866475b7f AGS: Engine: REALLY apply character's anim volume to portrait animation
551c493648 AGS: Updated build version (3.6.1.6)
9333d70466 AGS: Common: check (de)compression result in SpriteFile, + tidy deflate func
bd1b5df278 AGS: Updated build version (3.6.1.7)
d7613f9653 AGS: Marked few more game script variables as readonly
b318d39e0b AGS: Engine: tiny cleanup in ReadDynamicSprites()
f22600c021 AGS: Updated build version (3.6.1.8)
5b66b860af AGS: Engine: separate functions for getting game's set and real fps
9c633e250e AGS: Engine: cut out DebugConsole implementation, as unusable
84f7493959 AGS: Engine: support scaling character sprite offsets after char's own zoom
d03da04675 AGS: Common: hide GUIButton's image properties behind the get/set methods
bd1bf59315 AGS: Common: hide GUIObject's size behind the get/set methods
0da5748fa0 AGS: Fix FPS calculation
22cb981566 AGS: Engine: rewrote MoveList to avoid packed shorts
56c790c838 AGS: Engine: tidy do_movelist_move() and MoveList further
f1a9a35758 AGS: Engine: a new fix for character stalling before finishing movement
19496a8e24 AGS: Engine: fixed walking remainer duplicates speed
99ca176e58 AGS: Engine: support changing walking speed during walk
2dc8b67350 AGS: Engine: in MoveList store steps as fractional values, for recalculation
dadd1500ef AGS: Engine: save MoveLists as a separate component, store onpart fixed-pt
7d2b747cae AGS: Engine: smooth transition when ordering to walk while walking
56b3fd3299 AGS: Engine: converted MoveList::onpart to float
f0d0caa6d1 AGS: Engine: when fast-forwarding, skip blocking Say instantly
45f011ecd8 AGS; Engine: store MoveList final move as members, and serialize
9021cd0f04 AGS: Engine: another small fix in do_movelist_move()
4799ff06d7 AGS: Engine: fixed silly typo in dataver condition in Character_SetSpeed()
50b34a65c2 AGS: Engine: bit more refactor of movelist update, picked "move done" check
e3e6b75af5 AGS: Engine: reverted serializing movelist remainer, recalc on restore
4ef7d8af48 AGS: Updated build version (3.6.1.9)
9c7e414387 AGS: Engine: another small fix in do_movelist_move()
b52fe376d1 AGS: Common: inlined several getters in GUIMain and GUIObject
14702fcc88 AGS: Common: use GUIButton::UpdateCurrentImage() consistently
528a0a333f AGS: Engine: consistently reset "mouse-over" when GUI interact state changes
f327ebdf1b AGS: Engine: replaced literal save version values with enum constants
c66a1b5c98 AGS: Engine: fixed MoveList not getting cleared up on reuse, kept remainer
1ee7c47af2 AGS: Engine: fixed a condition in movelist_handle_remainer()
dfaa12c25a AGS: Engine: disable auto SetRestartPoint at startup
813801779c AGS: Engine: Added new functions for API scripts registration
cec77078f6 AGS: Convert several files to new API registering
20706c475f AGS: Engine: Complete move to new API registering
27ce106dac AGS: Engine: added a template ScFnRegister ctor for easier direct function
935247b450 AGS: Engine: reformatted Camera & Viewport api's registration
795dacac07 AGS: Engine: inherit StaticObject managers from ICCDynamicObject
5cb2873a83 AGS: Remove leftover files
87caf7fd93 AGS: Engine: tidy CCStaticArray
70dd8cb1f8 AGS: Engine: in RuntimeScriptValue store generic ptr as void*
54b8b1d873 AGS: Engine: changed script object data pointers from char* to void*
ac0422354a AGS: Engine: renamed ICCDynamicObject to IScriptObject
f13f7cfac2 AGS: Engine: rem the distinction between static/dynamic objects in script VM
3bf6572826 AGS: Engine: implemented proper manager for GameState script struct
dd43bf35d6 AGS: Engine: implemented proper manager for ScriptMouse script struct
16cb64b080 AGS: Engine: implemented proper manager for ScriptSystem script struct
cd3a4d8287 AGS: Engine: implemented proper manager for CCCharacter script struct
63f08f13a0 AGS: Engine: fixed data type in ScriptUserObject
e6c2b159a7 AGS: Engine: small fixes in ManagedObjectPool
1847279274 AGS: Engine: small code clarification in walk_character() + more comments
771872e338 AGS: Common: do not store temp load data in GameSetupStruct
c00522563a AGS: Engine: store ScriptUserObject in memory similar to DynamicArrays
60c4c2d484 AGS: Fix ResetDoOnceOnly registration
09ffee9ed7 AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances (take two)
815041ec6a AGS: Engine: store game's ccInstances as unique_ptrs instead
961b06447f AGS: Engine: use internal interface alias for plugin's object readers
ee82074a47 AGS: Engine: further tidy up around agsplugin.h/cpp
1527b38a5c AGS: Engine: SpriteCache uses proper callbacks instead of hardcoded externs
2fbeea1840 AGS: SpriteCache: SetSprite() disposes old sprite in the same slot itself
7edb641ff2 AGS: SpriteCache: store bitmaps in unique_ptr, tidy storage code a little
ee6cf385c9 AGS: SpriteCache: corrected handling of "remapped" sprites
e81c85c917 AGS: Engine: removed wrong cast to (char*)
59b44ac496 AGS: Remove accidental duplication in ResourceCache
b498b14ecc AGS: Engine: save global draw method args in DrawState struct
e407658475 AGS: Engine: in hw draw mode always assume walk-behinds are sprites
ffe86114e4 AGS: Complete "query gfxdriver for texture mem" commit
df504348ed AGS: Script API: Restore leftover bindings
dcd7be5b2d AGS: Common: fixed sprInfo init in SpriteCache::SetSprite()
57464f3dce AGS: Engine: Complete "object interaction events take obj pointer and mode as params" commit
d4cc4a8808 AGS: Engine: proper handling of Hotspot WalkOn/MouseOver event args
d7c2c0a490 AGS: ScriptAPI: add Object.AnimationVolume bindings
20fd74c064 AGS: Marked few more game script variables as readonly
455b9609d0 AGS: Script API: implemented Type.GetByName() for all applicable types
c6e115e697 AGS: Script API: added readonly ScriptName property to all applicable types
d48716b711 AGS: Engine: fixed ScriptUserObject de-serialization saves garbage into Size
d83adaac04 AGS: Engine: warn of write into readonly script variable, instead of error
80dcc60a78 AGS: ScriptAPI: added formatting support to a few text displaying functions
24df476bfa AGS: Engine: added some const modifiers in dynamic object methods
964b85b632 AGS: Add RoomOptions comments
dd0195163f AGS: Engine: removed excessive header guidialoginternaldefs.h
73e50455ab AGS: Engine: precache only player walk view
6c5c0f17f3 AGS: Engine: use templates in gfxdriverbase getpixel functions
941df46f90 AGS: Engine: refactor video to mem using templates
9e85642f30 AGS: Engine: in btvm functions with alpha compile time
27b34d2bf0 AGS: Engine: usingLinearFiltring move to compile time
2fd65e0c5a AGS: Engine: more const stuff in BitmapToVideoMem functions
67b9b8f6d3 AGS: Engine: fixed save ver constants for Characters and RoomStates
ed76e769c2 AGS: Updated build version (3.6.1.10)
63b36e594a AGS: Engine: in BitmapToVideoMemOpaque() cut out "HasAlpha" branch
4e41e60781 AGS: Engine: store ScriptString in memory similarly to dynamic array
f641445c70 Common: add AGS_PLATFORM_WINDOWS_MINGW
db73bb6b68 AGS: Moved "antialias" config option to "graphics" section
47c095545b AGS: Common: don't use AlignedStream, hardcode padding in read/write funcs
aaa8ccb38b AGS: Common: hardcode alignment padding in Interaction structs
bfed6e06e5 AGS: Engine: separate CharacterInfo read/write for saves, no padding
eca9b3d239 AGS: Engine: hardcode alignment padding in RoomStruct's legacy save
498a3cb270 AGS: Engine: hardcode alignment padding in GameState's legacy save
8b4e30fa35 AGS: Engine: hardcode alignment padding in remaining legacy save parts
8964231e73 AGS: Common: completely removed AlignedStream and ProxyStream classes
796796e8f3 AGS: Engine: cleanup some mentions of AlignedStream
51de20d796 AGS; Engine: drop support of pre-3.5.0-alpha movelist format version
76ebe97d9e AGS: Updated build version (3.6.1.11)
be382a0a0a AGS: Engine: separated BitmapToVideoMemLinearImpl for convenience
3995f634e9 AGS: Common: fixed pushed textual button not redrawn after mouse up
6789d24414 AGS: Fix improper string length calculation in string functions
e710697044 AGS: Engine: tidy script runner remove unused variables
032d4e9919 AGS: Engine: in script cc_instance used const when possible
5549e5c0c3 AGS: Engine: cc_instance adjust for loop
4cdfd7ebba AGS: Engine: in cc_instance, use static_cast where possible
960bccaf86 AGS: Engine: removed my_strncpy() and remade check_strlen() w/o global var
bdb34c01cc AGS: Engine: unrestricted scriptname and name in CharacterInfo
478b46a340 AGS: Engine: unrestricted name in InventoryItemInfo
98e59c7a93 AGS: Engine: unrestricted script name in MouseCursor
103ee6cb87 AGS: Engine: unrestricted script name an filename in ScriptAudioClip
8d6ba11854 AGS: Engine: add unrestricted Character name to saves
ead8bd6d57 AGS: Common: added game data extension for unrestricted object names
e158764267 AGS: Engine: unrestricted game name and save folder name
8dc4dc57c5 AGS: Support extensions offset written into game data
47b1ac9edc AGS: Engine: fixed CharacterInfo export to script, after adding new fields
419e2d7372 AGS: Engine: simplify display_at, and only call it for blocking messagebox
bb55a804e0 AGS: Engine: picked voice-over token parsing to a function
e82e1708ab AGS: Engine: fix log of uninitialized value in gfx driver base
d59eeaf4a6 AGS: Common: improved Stream::HasErrors(), renamed to GetError()
b4f9385a3f AGS: Engine: fixed BufferedStream::Seek return value
eefccd3067 AGS: Common: explicit LockSprite/UnlockSprite methods in SpriteCache
d904cac0c5 AGS: Script API: implemented Game.PrecacheSprite() and PrecacheView()
bc3fb837c1 AGS: Engine: added diagnostic measurements to PrecacheSprite, PrecacheView
d94fde8d3f AGS: Common: in AssetManager replace the libs sorter struct to function ptr
e487c2e657 AGS: Common: reverted an unintended function argument rename
d0fe6fd80e AGS: Engine: remade writing of non-fixed Character name in game saves
41b5b9edbd AGS: Engine: sync Character's legacy name field in old Str* script functions
54d22dff15 AGS: Engine: fixed AGSPlatformDriver::GetDiskFreeSpaceMB() return type
9972dcee53 AGS: Removed unused variables in precache_view
96db95c255 AGS: Engine: fix some minor warnings
ae669f262a AGS: Common: replaced few instances of non-portable "long" type
25e4c703d7 AGS: Common: fixed BufferedStream::WriteByte return value
dc16de544e AGS: Common: Stream::Seek() returns new position
fd5e6dee1e AGS: Engine: don't limit filename length in PlayMP3File()
31d95ec1e3 AGS: Engine: fixed pointer math in check_scstrcapacity, commit_scstr_update
b05753b217 AGS: Common: DataStreamSection, restricts another stream to a range
50e11cf086 AGS: Engine: basic stream range safeguard for plugin serialization
5b793dbd38 AGS: Engine: new plugins save format, stores name & data size per plugin
e12a4ab45d AGS: Engine: raise SavegameVersion for 3.6.1
5de58ed646 AGS: Updated build version (3.6.1.15)
fae4d1180e AGS: AGSflashlight: added missing AGS_PluginV2() export
33697a821a AGS: FIx missing line setting coldepth
38115f48b1 AGS: Engine: fixed Dissolve transition
2f85972fce AGS: Common: fix typo leads out of range memcpy
496afb9a35 AGS: Common: fixed Seek return value for stream sections
dd6ca00d42 AGS: Engine: fixed NumPad not handled properly
c02a141063 AGS: Engine: support IsKeyPress for some of the NumPad keys
d60cccd3e0 AGS: Updated build version (3.6.1.17 RC2)
87c0bcdf7f AGS: Engine: fixed CharacterInfo breaking script compatibility again
3e276ee58c AGS: Engine: assert text_window_ds before using
ee72f2a6f5 AGS: Common: Interactions ensure no self assignment
f3db5683ed AGS: Common: spritefile remove unused palette variable
f2f4e9420d AGS: Engine: assert parserInput is not null
aa45beee50 AGS: Common: ensure Close and Seek from BufferedStream
bcbdb4714b AGS: Common: pass by reference in scaling and geometry
9fc395f227 AGS: add DisplayMessageBox()
0ce4c45489 AGS: Engine: mention "--no-message-box" in help for all platforms
8b7b0df59c AGS: Engine: fixed MAX_WALK_AREAS value
97e847ff50 AGS: Engine: replaced ReadByte with ReadInt8 where makes sense
5d6d2bd7a7 AGS: Common: fixed GameDataExtPreloader causing assertions to trigger
822842d038 AGS: Engine, Common: refactor guard using set_our_eip
00de3853ed AGS: Engine: fix backwards compatible Overlay z-sorting in creation order
38ea062044 AGS: Engine: just amended comments to IScriptObject
2f8634abdc AGS: Engine: add allocWidth/allocHeight parameters
de40de36fb AGS: Engine: change return type of GetDirectPtr() to void*
6c209a2a98 AGS: Engine: use uintptr_t in couple of places in ccInstance::Run()
43c37eabf6 AGS: Engine: define special DRAWENTRY values in gfxdriver as uintptr_t
7b81ba52d6 AGS: Fix "Engine: fixed speech stuck in "post state" under some conditions" commit
b2e96c46aa AGS: Updated build version (3.6.1.20 RC5)
8d468642cb AGS: Engine: implement add_waypoint_direct(), fix AddWaypoint's resolution
3cf3531d4f AGS: Engine: fixed Software render crashes if rendering in game_start script
c3b5eac297 AGS: Engine: fixed IAGSEngine::SimulateMouseClick()
d585831bde AGS: Use getAt instead of braces in string access
49af109669 AGS: Engine: implemented a method of skipping sprite batches on screenshot
58dfedf323 AGS: Engine: hide mouse cursor and engine overlay when fading-out
00fc3c38c3 AGS: Engine: narrow down sprite batch filtering for fade-out
2199dd1555 AGS: Engine: check failure of adding a sprite
567b5bb54b AGS: Engine: in DynamicSprite.CreateFrom* functions fixup bad sprite size
184a45ad02 AGS: Engine: fix warning text in DrawImage to mention any "destination"
73526c8f1c AGS: Engine: add SPF_OBJECTOWNED flag for dyn sprites owned by Overlays
7a0876b6b0 AGS: Engine: in CopyScreenIntoBitmap support src rect for legacy letterbox
1f2ef9ce4a AGS: Engine: fixed Crossfade and Dissolve transitions in letterbox mode
9a766e1a35 AGS: Engine: fixed extra frame drawn before Crossfade or Dissolve fadein
258f9fa795 AGS: Engine: fixed mouse cursor texture broken after RunAGSGame()
e6ea49d9d3 AGS: Engine: fixed restoring letterboxed viewport after loading an old save
209ec5a953 AGS: Engine: hotfix room objects not visible if FadeIn called in "room load"
5d58384af2 AGS: remove "supersampling" setting, as not entirely supported
26cf741f8a AGS: Engine: fixed camera may fail to restore its position in the room
77c508662f AGS: Fixed "Engine: hotfix potential exception if cc_error is called during quit()"
afde8d4a9d AGS: Updated build version (3.6.1.22), mark "stable" 3.6.1 release
91ea7cb099 AGS: Don't attempt to access endtimeoffs/frame vectors for lines with no phonemes
794318b9db AGS: Common: fixed item crosshair colors read with mistake
8e53e97a26 AGS: Fix type/add cast in main_game_file
5ce015027f AGS: Add const_casts, comment out unneeded bindings
08a2d9cc9e AGS: Add casts in cc_instance
1bce308ff0 AGS: Add casts in ags_plugin
0da3289047 AGS: Add casts in cc_character
eac87601c4 AGS: Common: fix couple of GUI functions that may fail when restoring a save
3078b207d3 AGS: Engine: refactor around add_dynamic_sprite()
1d70b396d2 AGS: Engine: store dynamic surfaces in unique_ptrs
3d6f4c62f7 AGS: Common: fixed stupid mistake in SpriteCache::InitNullSprite()
1464a7e306 AGS: Engine: for software mode fixed removed overlay cache not invalidated
8d7cca3acf AGS: Engine: fixed read_savedgame_screenshot() in case there's no screenshot
2d2755f491 AGS; Engine: changed ScreenOverlay to store owned images as dynamic sprites
dc30f4e2a8 AGS: Engine: Overlay uses ObjTexture similar to object and chars
6833358204 AGS: Common: made SpriteCache::GetFreeIndex() much faster
f6e4d415a5 AGS: Common: implemented a sprite placeholder in SpriteCache
7af9888de2 AGS: Engine: rewrote dynamic sprite change notification method
efc70d1e1d AGS: Engine: mark portrait sprite updated, as it's now a dynamic sprite
6dfd66be65 AGS: Engine: fixed Overlay bitmap order read from legacy saves
1025254465 AGS: Fix implicit warning
7314dfc22e AGS: Fix Screenoverlay private initializer error
55d81cb237 AGS: Engine: safeguard Overlay.Create in case of spritenum out of range
38d23b5159 AGS: Engine: fixed spritemodified array not init after RunAGSGame()
81130f543a AGS: Engine: fixed software renderer closing with textual overlay on screen
92809e3114 AGS: Engine: rewrote dynamic sprite to texture notification mechanism
a5831a36a3 AGS: Engine: only alloc sprite notifiers for dynamic sprites + add comment
a5223d249b AGS: Updated build version (3.6.1.23 P1)
6efd1cc41e AGS: Common: calculate real TTF glyphs extent, fix fonts cut from the top
a8b50fed68 AGS: Common: print loaded font metrics to log
fcde33269f AGS: Engine: fixed auto outline for the fonts with abnormal extents
b9879cb9c4 AGS: Engine: made FPS overlay react on font changes and abnormal fonts
c8ddd881ae AGS: Engine: hotfix buffer usage when printing room debug info
6f20bee752 AGS: Engine: hotfix buffer size check in InputBox()
7f1ad613d5 AGS: Engine: added comments around remaining uses of STD_BUFFER_SIZE
340a7f2098 AGS: Engine: fixed InputBox draws input string beyond textbox borders
bb0ff49ee4 AGS: Engine: fixed wouttextxy_AutoOutline inconsistent int param types
f2b5bb80a1 AGS: Common: when calculating TTF's extents, use first 128 glyphs
3b52b26652 AGS: Common: when calculating TTF font's extent just use FT_Face->bbox
56ec78d7b8 AGS: Engine: fixed ScPl_System_Log() had wrong argument list (copy-paste?)
4308801cae AGS: Engine: in GetDiskFreeSpaceMB() ensure we check the save dir's disk
1c5ca014ca Engine: allow scene render in "load room" event if transition instant
6814c8dbe2 AGS: Engine: support loading 3.5.0 saves with mismatching "Room States" ver
0c4d929d6d AGS: Engine: hotfix certain platforms to not save "warnings.log" in cwd
4ffe9e6803 AGS: Updated build version (3.6.1.25 P3)
6a2d740403 AGS: Engine: fix setting Viewport and Camera sizes in game_start
187cf953d2 AGS: Script API: added missing OPT_* constants for Get/SetGameOption
455aaef9f0 AGS: Updated build version (3.6.1.26 P4)
f314926323 AGS: Engine: fixed GetRegionAtScreen to return region[0] for off coordinates
d05eeb5dc2 AGS: Engine: get rid of the room object sorting hack, use same rules for all
fdab95b34b AGS: Engine: gui mouse handles accept mouse pos as arguments
a9dc8d7cbf AGS: Engine: in ASCII mode in TextBox don't try printing unsupported chars
90863afb9b AGS: Engine: fixed ConvertUtf8ToAscii to restore old locale properly
6f25625b91 AGS: Engine: simplify DrawSpriteWithTransparency()
a7a995571b AGS: Engine: reorganized code in DrawingSurface_DrawImageImpl() a little
c5b8f70d65 AGS: Engine: clamp return value of get_walkable_area_at_location()
99a6c04112 AGS: Engine: fixed DrawSpriteWithTransparency to keep mask pixels on conv
81757a1121 AGS: Engine: fixed text-based lipsync inconsistent frame delay
74b261a1c7 AGS: Engine: fixed walkbehind sprites not recreated after walkbehinds change
3e57cad5de AGS: Engine: fixed CopyScreenIntoBitmap not applying filter when resizing
ec9f0dcb51 AGS: Engine: fixed a comment with obsolete info and link to the manual
1fad505ba5 AGS: Add old_keyhandle flag to ags_simulate_keypress
8e915febde AGS: Engine: fixed callstack not reported in case of script api errors
6ee1a4978f AGS: Engine: expanded error messages for character being in wrong room
8a0cc8e3f9 AGS: Engine: fixed ReadGameData() to read GUID before logging
f0c92b6156 AGS: Updated build version (3.6.1.29 P7)
a19929bd30 AGS: Fix operand typo
9778105b8f AGS: Add missing override keyword
cf3fcb8073 AGS: Move DrawFPS to globals
8d90ee9c2c AGS: Move valid_handles to globals
5275aaaeec AGS: Move room_statuses to globals
281d798b84 AGS: Move globalDynamicStruct to globals
dcf9cfd880 AGS: Move drawstate to globals
543dff99a6 AGS: Expand info in cc_instance error logging macros
37f8378cd6 AGS: Avoid unserializing plugins with zero datasize
1ac7de437e AGS: Fix flipped parameters in ArcTan2 plugin function


Commit: 0b2bfb202dad60a84c1282b8015c16e33f98d989
    https://github.com/scummvm/scummvm/commit/0b2bfb202dad60a84c1282b8015c16e33f98d989
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: implemented CopyFile (and RenameFile)

Both methods are restricted to the savegame directory.
Reimplemented from upstream 1b4764ae08a4bb302de662efb9563a87644c03b3

Changed paths:
    engines/ags/shared/util/file.cpp
    engines/ags/shared/util/file.h


diff --git a/engines/ags/shared/util/file.cpp b/engines/ags/shared/util/file.cpp
index d0dad2e9c75..02e0dfcd830 100644
--- a/engines/ags/shared/util/file.cpp
+++ b/engines/ags/shared/util/file.cpp
@@ -89,6 +89,34 @@ bool File::DeleteFile(const String &filename) {
 	return g_system->getSavefileManager()->removeSavefile(file);
 }
 
+bool File::RenameFile(const String &old_name, const String &new_name) {
+	// Only allow renaming files in the savegame folder
+	if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || new_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
+		warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), new_name.GetCStr());
+		return false;
+	}
+	Common::String file_old(old_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	Common::String file_new(new_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	return g_system->getSavefileManager()->renameSavefile(file_old, file_new);
+}
+
+bool File::CopyFile(const String &src_path, const String &dst_path, bool overwrite) {
+	// Only allow copying files to the savegame folder
+	// In theory it should be possible to copy any file to to save folder, but
+	// let's restrict only to files that are already in the save folder for now
+	if (src_path.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || dst_path.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
+		warning("Cannot copy file %s to %s. Source and destination files must be in the savegame directory", src_path.GetCStr(), dst_path.GetCStr());
+		return false;
+	}
+	if (ags_file_exists(dst_path.GetCStr()) && !overwrite) {
+		warning("Cannot copy file %s to %s. File exists", src_path.GetCStr(), dst_path.GetCStr());
+		return false;
+	}
+	Common::String file_src(src_path.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	Common::String file_dest(dst_path.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	return g_system->getSavefileManager()->copySavefile(file_src, file_dest);
+}
+
 bool File::GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode) {
 	// We do not test for 'b' and 't' here, because text mode reading/writing should be done with
 	// the use of ITextReader and ITextWriter implementations.
diff --git a/engines/ags/shared/util/file.h b/engines/ags/shared/util/file.h
index 2fe86547518..ba0cf6993e3 100644
--- a/engines/ags/shared/util/file.h
+++ b/engines/ags/shared/util/file.h
@@ -68,6 +68,10 @@ bool        TestWriteFile(const String &filename);
 bool        TestCreateFile(const String &filename);
 // Deletes existing file; returns TRUE if was able to delete one
 bool        DeleteFile(const String &filename);
+// Renames existing file to the new name; returns TRUE on success
+bool		RenameFile(const String &old_name, const String &new_name);
+// Copies a file from src_path to dst_path; returns TRUE on success
+bool		CopyFile(const String &src_path, const String &dst_path, bool overwrite);
 
 // Sets FileOpenMode and FileWorkMode values corresponding to C-style file open mode string
 bool        GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode);


Commit: 076e8ea4c2b4d4c8bbd06454d095998a56330859
    https://github.com/scummvm/scummvm/commit/076e8ea4c2b4d4c8bbd06454d095998a56330859
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in SetSaveGameDirectory use utility FileCopy() function

>From upstream cf6754cca84f6c1870a6b7315052b876cd5c8392

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 5cf2d6e7dca..9b1f52646c0 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -269,19 +269,10 @@ static bool SetSaveGameDirectory(const FSLocation &fsdir) {
 		return false;
 
 	// copy the Restart Game file, if applicable
-	String restartGamePath = Path::ConcatPaths(_G(saveGameDirectory), get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER));
-	Stream *restartGameFile = File::OpenFileRead(restartGamePath);
-	if (restartGameFile != nullptr) {
-		long fileSize = restartGameFile->GetLength();
-		char *mbuffer = (char *)malloc(fileSize);
-		restartGameFile->Read(mbuffer, fileSize);
-		delete restartGameFile;
-
-		restartGamePath = Path::ConcatPaths(newSaveGameDir, get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER));
-		restartGameFile = File::CreateFile(restartGamePath);
-		restartGameFile->Write(mbuffer, fileSize);
-		delete restartGameFile;
-		free(mbuffer);
+	String old_restart_path = Path::ConcatPaths(_G(saveGameDirectory), get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER));
+	if (File::IsFile(old_restart_path)) {
+		String new_restart_path = Path::ConcatPaths(newSaveGameDir, get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER));
+		File::CopyFile(old_restart_path, new_restart_path, true);
 	}
 
 	_G(saveGameDirectory) = newSaveGameDir;


Commit: c0cc23c570f7819c55cfdd19b73e5f5d705e6989
    https://github.com/scummvm/scummvm/commit/c0cc23c570f7819c55cfdd19b73e5f5d705e6989
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: remake ScriptGUI and ScriptDialog arrays into std::vectors

>From upstream bc44a84892128250ebb08576e4640d0a2858d436

Changed paths:
    engines/ags/engine/ac/dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_gui.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/dialog_options_rendering.cpp b/engines/ags/engine/ac/dialog_options_rendering.cpp
index e1010054051..410d8b138cc 100644
--- a/engines/ags/engine/ac/dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dialog_options_rendering.cpp
@@ -109,7 +109,7 @@ void DialogOptionsRendering_SetParserTextboxWidth(ScriptDialogOptionsRendering *
 }
 
 ScriptDialog *DialogOptionsRendering_GetDialogToRender(ScriptDialogOptionsRendering *dlgOptRender) {
-	return &_G(scrDialog)[dlgOptRender->dialogID];
+	return &_GP(scrDialog)[dlgOptRender->dialogID];
 }
 
 ScriptDrawingSurface *DialogOptionsRendering_GetSurface(ScriptDialogOptionsRendering *dlgOptRender) {
@@ -122,7 +122,7 @@ int DialogOptionsRendering_GetActiveOptionID(ScriptDialogOptionsRendering *dlgOp
 }
 
 void DialogOptionsRendering_SetActiveOptionID(ScriptDialogOptionsRendering *dlgOptRender, int activeOptionID) {
-	int optionCount = _G(dialog)[_G(scrDialog)[dlgOptRender->dialogID].id].numoptions;
+	int optionCount = _G(dialog)[_GP(scrDialog)[dlgOptRender->dialogID].id].numoptions;
 	if ((activeOptionID < 0) || (activeOptionID > optionCount))
 		quitprintf("DialogOptionsRenderingInfo.ActiveOptionID: invalid ID specified for this dialog (specified %d, valid range: 1..%d)", activeOptionID, optionCount);
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index 0d5bc23e4f2..7af15eb9e58 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -48,7 +48,7 @@ void CCDialog::Serialize(const char *address, Stream *out) {
 
 void CCDialog::Unserialize(int index, Stream *in, size_t data_sz) {
 	int num = in->ReadInt32();
-	ccRegisterUnserializedObject(index, &_G(scrDialog)[num], this);
+	ccRegisterUnserializedObject(index, &_GP(scrDialog)[num], this);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index b05b0dfa412..363beda50e0 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -46,7 +46,7 @@ void CCGUI::Serialize(const char *address, Stream *out) {
 
 void CCGUI::Unserialize(int index, Stream *in, size_t data_sz) {
 	int num = in->ReadInt32();
-	ccRegisterUnserializedObject(index, &_G(scrGui)[num], this);
+	ccRegisterUnserializedObject(index, &_GP(scrGui)[num], this);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 9b1f52646c0..204b0a3eb04 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -423,12 +423,10 @@ void unload_game() {
 			free(dlg.optionscripts);
 	}
 	_G(dialog).clear();
-	delete[] _G(scrDialog);
-	_G(scrDialog) = nullptr;
+	_GP(scrDialog).clear();
 
 	_GP(guis).clear();
-	delete[] _G(scrGui);
-	_G(scrGui) = nullptr;
+	_GP(scrGui).clear();
 
 	remove_screen_overlay(-1);
 
diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp
index 01806e0006a..6c142ed47de 100644
--- a/engines/ags/engine/ac/global_gui.cpp
+++ b/engines/ags/engine/ac/global_gui.cpp
@@ -118,7 +118,7 @@ void SetGUIPosition(int ifn, int xx, int yy) {
 	if ((ifn < 0) || (ifn >= _GP(game).numgui))
 		quit("!SetGUIPosition: invalid GUI number");
 
-	GUI_SetPosition(&_G(scrGui)[ifn], xx, yy);
+	GUI_SetPosition(&_GP(scrGui)[ifn], xx, yy);
 }
 
 void SetGUIObjectSize(int ifn, int objn, int newwid, int newhit) {
@@ -135,21 +135,21 @@ void SetGUISize(int ifn, int widd, int hitt) {
 	if ((ifn < 0) || (ifn >= _GP(game).numgui))
 		quit("!SetGUISize: invalid GUI number");
 
-	GUI_SetSize(&_G(scrGui)[ifn], widd, hitt);
+	GUI_SetSize(&_GP(scrGui)[ifn], widd, hitt);
 }
 
 void SetGUIZOrder(int guin, int z) {
 	if ((guin < 0) || (guin >= _GP(game).numgui))
 		quit("!SetGUIZOrder: invalid GUI number");
 
-	GUI_SetZOrder(&_G(scrGui)[guin], z);
+	GUI_SetZOrder(&_GP(scrGui)[guin], z);
 }
 
 void SetGUIClickable(int guin, int clickable) {
 	if ((guin < 0) || (guin >= _GP(game).numgui))
 		quit("!SetGUIClickable: invalid GUI number");
 
-	GUI_SetClickable(&_G(scrGui)[guin], clickable);
+	GUI_SetClickable(&_GP(scrGui)[guin], clickable);
 }
 
 // pass trans=0 for fully solid, trans=100 for fully transparent
@@ -157,14 +157,14 @@ void SetGUITransparency(int ifn, int trans) {
 	if ((ifn < 0) | (ifn >= _GP(game).numgui))
 		quit("!SetGUITransparency: invalid GUI number");
 
-	GUI_SetTransparency(&_G(scrGui)[ifn], trans);
+	GUI_SetTransparency(&_GP(scrGui)[ifn], trans);
 }
 
 void CentreGUI(int ifn) {
 	if ((ifn < 0) | (ifn >= _GP(game).numgui))
 		quit("!CentreGUI: invalid GUI number");
 
-	GUI_Centre(&_G(scrGui)[ifn]);
+	GUI_Centre(&_GP(scrGui)[ifn]);
 }
 
 int GetTextWidth(const char *text, int fontnum) {
@@ -201,7 +201,7 @@ void SetGUIBackgroundPic(int guin, int slotn) {
 	if ((guin < 0) | (guin >= _GP(game).numgui))
 		quit("!SetGUIBackgroundPic: invalid GUI number");
 
-	GUI_SetBackgroundGraphic(&_G(scrGui)[guin], slotn);
+	GUI_SetBackgroundGraphic(&_GP(scrGui)[guin], slotn);
 }
 
 void DisableInterface() {
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index ac499732fd2..d5d3fbbb35a 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -65,7 +65,7 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 ScriptGUI *GUI_AsTextWindow(ScriptGUI *tehgui) { // Internally both GUI and TextWindow are implemented by same class
-	return _GP(guis)[tehgui->id].IsTextWindow() ? &_G(scrGui)[tehgui->id] : nullptr;
+	return _GP(guis)[tehgui->id].IsTextWindow() ? &_GP(scrGui)[tehgui->id] : nullptr;
 }
 
 int GUI_GetPopupStyle(ScriptGUI *tehgui) {
@@ -269,7 +269,7 @@ ScriptGUI *GetGUIAtLocation(int xx, int yy) {
 	int guiid = GetGUIAt(xx, yy);
 	if (guiid < 0)
 		return nullptr;
-	return &_G(scrGui)[guiid];
+	return &_GP(scrGui)[guiid];
 }
 
 void GUI_Click(ScriptGUI *scgui, int mbut) {
@@ -306,7 +306,7 @@ void remove_popup_interface(int ifacenum) {
 void process_interface_click(int ifce, int btn, int mbut) {
 	if (btn < 0) {
 		// click on GUI background
-		RuntimeScriptValue params[]{ RuntimeScriptValue().SetDynamicObject(&_G(scrGui)[ifce], &_GP(ccDynamicGUI)),
+		RuntimeScriptValue params[]{ RuntimeScriptValue().SetDynamicObject(&_GP(scrGui)[ifce], &_GP(ccDynamicGUI)),
 					RuntimeScriptValue().SetInt32(mbut) };
 		QueueScriptFunction(kScInstGame, _GP(guis)[ifce].OnClickHandler.GetCStr(), 2, params);
 		return;
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index fe5ebd0a961..d6410af2c84 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -104,7 +104,7 @@ int GUIControl_GetID(GUIObject *guio) {
 }
 
 ScriptGUI *GUIControl_GetOwningGUI(GUIObject *guio) {
-	return &_G(scrGui)[guio->ParentId];
+	return &_GP(scrGui)[guio->ParentId];
 }
 
 GUIButton *GUIControl_GetAsButton(GUIObject *guio) {
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index b3d32b94c88..83f4e838b7a 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -129,14 +129,14 @@ void InitAndRegisterCharacters(GameSetupStruct &game) {
 
 // Initializes dialog and registers them in the script system
 void InitAndRegisterDialogs(GameSetupStruct &game) {
-	_G(scrDialog) = new ScriptDialog[game.numdialog];
+	_GP(scrDialog).resize(MAX(1, game.numdialog)); // ensure at least 1 element, we must register buffer
 	for (int i = 0; i < game.numdialog; ++i) {
-		_G(scrDialog)[i].id = i;
-		_G(scrDialog)[i].reserved = 0;
-		ccRegisterManagedObject(&_G(scrDialog)[i], &_GP(ccDynamicDialog));
+		_GP(scrDialog)[i].id = i;
+		_GP(scrDialog)[i].reserved = 0;
+		ccRegisterManagedObject(&_GP(scrDialog)[i], &_GP(ccDynamicDialog));
 
 		if (!game.dialogScriptNames[i].IsEmpty())
-			ccAddExternalDynamicObject(game.dialogScriptNames[i], &_G(scrDialog)[i], &_GP(ccDynamicDialog));
+			ccAddExternalDynamicObject(game.dialogScriptNames[i], &_GP(scrDialog)[i], &_GP(ccDynamicDialog));
 	}
 }
 
@@ -152,9 +152,9 @@ void InitAndRegisterDialogOptions() {
 
 // Initializes gui and registers them in the script system
 HError InitAndRegisterGUI(GameSetupStruct &game) {
-	_G(scrGui) = new ScriptGUI[game.numgui];
+	_GP(scrGui).resize(MAX(1, game.numgui)); // ensure at least 1 element, we must register buffer
 	for (int i = 0; i < game.numgui; ++i) {
-		_G(scrGui)[i].id = -1;
+		_GP(scrGui)[i].id = -1;
 	}
 
 	for (int i = 0; i < game.numgui; ++i) {
@@ -164,9 +164,9 @@ HError InitAndRegisterGUI(GameSetupStruct &game) {
 			return err;
 		// export all the GUI's controls
 		export_gui_controls(i);
-		_G(scrGui)[i].id = i;
-		ccAddExternalDynamicObject(_GP(guis)[i].Name, &_G(scrGui)[i], &_GP(ccDynamicGUI));
-		ccRegisterManagedObject(&_G(scrGui)[i], &_GP(ccDynamicGUI));
+		_GP(scrGui)[i].id = i;
+		ccAddExternalDynamicObject(_GP(guis)[i].Name, &_GP(scrGui)[i], &_GP(ccDynamicGUI));
+		ccRegisterManagedObject(&_GP(scrGui)[i], &_GP(ccDynamicGUI));
 	}
 	return HError::None();
 }
@@ -220,11 +220,11 @@ void RegisterStaticArrays(GameSetupStruct &game) {
 
 	ccAddExternalStaticArray("character", &game.chars[0], &_GP(StaticCharacterArray));
 	ccAddExternalStaticArray("object", &_G(scrObj)[0], &_GP(StaticObjectArray));
-	ccAddExternalStaticArray("gui", &_G(scrGui)[0], &_GP(StaticGUIArray));
+	ccAddExternalStaticArray("gui", &_GP(scrGui)[0], &_GP(StaticGUIArray));
 	ccAddExternalStaticArray("hotspot", &_G(scrHotspot)[0], &_GP(StaticHotspotArray));
 	ccAddExternalStaticArray("region", &_G(scrRegion)[0], &_GP(StaticRegionArray));
 	ccAddExternalStaticArray("inventory", &_G(scrInv)[0], &_GP(StaticInventoryArray));
-	ccAddExternalStaticArray("dialog", &_G(scrDialog)[0], &_GP(StaticDialogArray));
+	ccAddExternalStaticArray("dialog", &_GP(scrDialog)[0], &_GP(StaticDialogArray));
 }
 
 // Initializes various game entities and registers them in the script system
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index ef02672d80d..71b01ba9bd5 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -66,7 +66,9 @@
 #include "ags/engine/ac/dynobj/cc_serializer.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
 #include "ags/engine/ac/dynobj/script_audio_channel.h"
+#include "ags/engine/ac/dynobj/script_dialog.h"
 #include "ags/engine/ac/dynobj/script_dialog_options_rendering.h"
+#include "ags/engine/ac/dynobj/script_gui.h"
 #include "ags/engine/ac/dynobj/script_hotspot.h"
 #include "ags/engine/ac/dynobj/script_inv_item.h"
 #include "ags/engine/ac/dynobj/script_object.h"
@@ -236,10 +238,12 @@ Globals::Globals() {
 	_thisroom = new AGS::Shared::RoomStruct();
 	_troom = new RoomStatus();
 	_usetup = new GameSetup();
+	_scrGui = new std::vector<ScriptGUI>();
 	_scrObj = new ScriptObject[MAX_ROOM_OBJECTS];
 	_scrHotspot = new ScriptHotspot[MAX_ROOM_HOTSPOTS];
 	_scrRegion = new ScriptRegion[MAX_ROOM_REGIONS];
 	_scrInv = new ScriptInvItem[MAX_INV];
+	_scrDialog = new std::vector<ScriptDialog>();
 	_charcache = new std::vector<ObjectCache>();
 	_objcache = new ObjectCache[MAX_ROOM_OBJECTS];
 	_screenovercache = new std::vector<Point>();
@@ -499,10 +503,12 @@ Globals::~Globals() {
 	delete _thisroom;
 	delete _troom;
 	delete _usetup;
+	delete _scrGui;
 	delete[] _scrObj;
 	delete[] _scrHotspot;
 	delete[] _scrRegion;
 	delete[] _scrInv;
+	delete _scrDialog;
 	delete _charcache;
 	delete[] _objcache;
 	delete _screenovercache;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index c276c504e46..5bb25fcc775 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -754,18 +754,13 @@ public:
 	CCAudioClip *_ccDynamicAudioClip;
 	CCAudioChannel *_ccDynamicAudio;
 	ScriptString *_myScriptStringImpl;
-
-	// TODO: IMPORTANT!!
-	// we cannot simply replace these arrays with vectors, or other C++ containers,
-	// until we implement safe management of such containers in script exports
-	// system. Notably we would need an alternate to StaticArray class to track
-	// access to their elements.
 	ScriptObject *_scrObj;
-	ScriptGUI *_scrGui = nullptr;
+	std::vector<ScriptGUI> *_scrGui;
 	ScriptHotspot *_scrHotspot;
 	ScriptRegion *_scrRegion;
 	ScriptInvItem *_scrInv;
-	ScriptDialog *_scrDialog = nullptr;
+	std::vector<ScriptDialog> *_scrDialog;
+
 	std::vector<ViewStruct> *_views;
 	// Cached character and object states, used to determine
 	// whether these require texture update


Commit: 5acc31c4ed0f74f0a2645bc2162b350fd2629a61
    https://github.com/scummvm/scummvm/commit/5acc31c4ed0f74f0a2645bc2162b350fd2629a61
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: remake LipSync arrays into std::vectors

>From upstream b4f937a5c9900c0b05ef6ae2bc6051e970c2889a

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_audio.cpp
    engines/ags/engine/ac/lip_sync.h
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 204b0a3eb04..f4620ca4abb 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -406,17 +406,10 @@ void unload_game() {
 	_GP(charextra).clear();
 	_GP(mls).clear();
 	_GP(views).clear();
-	// Free lipsync
-	if (_G(splipsync) != nullptr) {
-		for (int i = 0; i < _G(numLipLines); ++i) {
-			free(_G(splipsync)[i].endtimeoffs);
-			free(_G(splipsync)[i].frame);
-		}
-		free(_G(splipsync));
-		_G(splipsync) = nullptr;
-		_G(numLipLines) = 0;
-		_G(curLipLine) = -1;
-	}
+
+	_GP(splipsync).clear();
+	_G(numLipLines) = 0;
+	_G(curLipLine) = -1;
 
 	for (auto &dlg : _G(dialog)) {
 		if (dlg.optionscripts != nullptr)
diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp
index fbdd1cde77f..f2fee97ec47 100644
--- a/engines/ags/engine/ac/global_audio.cpp
+++ b/engines/ags/engine/ac/global_audio.cpp
@@ -569,7 +569,7 @@ bool play_voice_speech(int charid, int sndid) {
 	_G(curLipLine) = -1;  // See if we have voice lip sync for this line
 	_G(curLipLinePhoneme) = -1;
 	for (ii = 0; ii < _G(numLipLines); ii++) {
-		if (voice_file.CompareNoCase(_G(splipsync)[ii].filename) == 0) {
+		if (voice_file.CompareNoCase(_GP(splipsync)[ii].filename) == 0) {
 			_G(curLipLine) = ii;
 			break;
 		}
diff --git a/engines/ags/engine/ac/lip_sync.h b/engines/ags/engine/ac/lip_sync.h
index eaf5c636cb3..ba847339efe 100644
--- a/engines/ags/engine/ac/lip_sync.h
+++ b/engines/ags/engine/ac/lip_sync.h
@@ -26,8 +26,8 @@ namespace AGS3 {
 
 struct SpeechLipSyncLine {
 	char  filename[14];
-	int32_t *endtimeoffs;
-	short *frame;
+	std::vector<int> endtimeoffs;
+	std::vector<short> frame;
 	short numPhonemes;
 };
 
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 83f4e838b7a..9bac7a9c206 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -296,14 +296,14 @@ void LoadLipsyncData() {
 		Debug::Printf(kDbgMsg_Info, "Unknown speech lip sync format (%d).\nLip sync disabled.", lipsync_fmt);
 	} else {
 		_G(numLipLines) = speechsync->ReadInt32();
-		_G(splipsync) = (SpeechLipSyncLine *)malloc(sizeof(SpeechLipSyncLine) * _G(numLipLines));
+		_GP(splipsync).resize(_G(numLipLines));
 		for (int ee = 0; ee < _G(numLipLines); ee++) {
-			_G(splipsync)[ee].numPhonemes = speechsync->ReadInt16();
-			speechsync->Read(_G(splipsync)[ee].filename, 14);
-			_G(splipsync)[ee].endtimeoffs = (int32_t *)malloc(_G(splipsync)[ee].numPhonemes * sizeof(int));
-			speechsync->ReadArrayOfInt32(_G(splipsync)[ee].endtimeoffs, _G(splipsync)[ee].numPhonemes);
-			_G(splipsync)[ee].frame = (short *)malloc(_G(splipsync)[ee].numPhonemes * sizeof(short));
-			speechsync->ReadArrayOfInt16(_G(splipsync)[ee].frame, _G(splipsync)[ee].numPhonemes);
+			_GP(splipsync)[ee].numPhonemes = speechsync->ReadInt16();
+			speechsync->Read(_GP(splipsync)[ee].filename, 14);
+			_GP(splipsync)[ee].endtimeoffs.resize(_GP(splipsync)[ee].numPhonemes);
+			speechsync->ReadArrayOfInt32(&_GP(splipsync)[ee].endtimeoffs.front(), _GP(splipsync)[ee].numPhonemes);
+			_GP(splipsync)[ee].frame.resize(_GP(splipsync)[ee].numPhonemes);
+			speechsync->ReadArrayOfInt16(&_GP(splipsync)[ee].frame.front(), _GP(splipsync)[ee].numPhonemes);
 		}
 	}
 	Debug::Printf(kDbgMsg_Info, "Lipsync data found and loaded");
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index c0b9f6f819d..a1e3b667d19 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -313,16 +313,16 @@ void update_sierra_speech() {
 
 		if (_G(curLipLine) >= 0) {
 			// check voice lip sync
-			if (_G(curLipLinePhoneme) >= _G(splipsync)[_G(curLipLine)].numPhonemes) {
+			if (_G(curLipLinePhoneme) >= _GP(splipsync)[_G(curLipLine)].numPhonemes) {
 				// the lip-sync has finished, so just stay idle
 			} else {
-				while ((_G(curLipLinePhoneme) < _G(splipsync)[_G(curLipLine)].numPhonemes) &&
-				        ((_G(curLipLinePhoneme) < 0) || (voice_pos_ms >= _G(splipsync)[_G(curLipLine)].endtimeoffs[_G(curLipLinePhoneme)]))) {
+				while ((_G(curLipLinePhoneme) < _GP(splipsync)[_G(curLipLine)].numPhonemes) &&
+				        ((_G(curLipLinePhoneme) < 0) || (voice_pos_ms >= _GP(splipsync)[_G(curLipLine)].endtimeoffs[_G(curLipLinePhoneme)]))) {
 					_G(curLipLinePhoneme)++;
-					if (_G(curLipLinePhoneme) >= _G(splipsync)[_G(curLipLine)].numPhonemes)
+					if (_G(curLipLinePhoneme) >= _GP(splipsync)[_G(curLipLine)].numPhonemes)
 						_G(facetalkframe) = _GP(game).default_lipsync_frame;
 					else
-						_G(facetalkframe) = _G(splipsync)[_G(curLipLine)].frame[_G(curLipLinePhoneme)];
+						_G(facetalkframe) = _GP(splipsync)[_G(curLipLine)].frame[_G(curLipLinePhoneme)];
 
 					if (_G(facetalkframe) >= _GP(views)[_G(facetalkview)].loops[_G(facetalkloop)].numFrames)
 						_G(facetalkframe) = 0;
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 71b01ba9bd5..bab3cbebdae 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -45,6 +45,7 @@
 #include "ags/engine/ac/event.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/engine/ac/game_state.h"
+#include "ags/engine/ac/lip_sync.h"
 #include "ags/engine/ac/mouse.h"
 #include "ags/engine/ac/move_list.h"
 #include "ags/engine/ac/room_status.h"
@@ -150,6 +151,9 @@ Globals::Globals() {
 	// cc_common globals
 	_ccError = new ScriptError();
 
+	// character.cpp globals
+	_splipsync = new std::vector<SpeechLipSyncLine>();
+
 	// csc_dialog.cpp globals
 	_vobjs = new NewControl *[MAXCONTROLS];
 	_oswi = new OnScreenWindow[MAXSCREENWINDOWS];
@@ -428,6 +432,9 @@ Globals::~Globals() {
 	// cc_common.cpp globals
 	delete _ccError;
 
+	// character.cpp globals
+	delete _splipsync;
+
 	// cscdialog.cpp globals
 	delete[] _vobjs;
 	delete[] _oswi;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 5bb25fcc775..d307e4c5736 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -462,7 +462,7 @@ public:
 	int _loops_per_character = 0, _text_lips_offset = 0, _char_speaking = -1;
 	int _char_thinking = -1;
 	const char *_text_lips_text = nullptr;
-	SpeechLipSyncLine *_splipsync = nullptr;
+	std::vector<SpeechLipSyncLine> *_splipsync;
 	int _numLipLines = 0, _curLipLine = -1, _curLipLinePhoneme = 0;
 
 	/**@}*/


Commit: 86f8ab5bef411b553c9e04aaf813955a7b9ad498
    https://github.com/scummvm/scummvm/commit/86f8ab5bef411b553c9e04aaf813955a7b9ad498
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use usetc instead of manual check of get_uformat()

>From upstream 3f109d2aa2195656d6ae04a751a0b58c71acd198

Changed paths:
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/script/script_api.cpp


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 6386c34abfa..c24b431eaf8 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -61,12 +61,8 @@ const char *String_Append(const char *thisString, const char *extrabit) {
 }
 
 const char *String_AppendChar(const char *thisString, int extraOne) {
-	size_t chw = 1;
-	char chr[Utf8::UtfSz + 1]{};
-	if (get_uformat() == U_UTF8)
-		chw = Utf8::SetChar(extraOne, chr, sizeof(chr));
-	else
-		chr[0] = extraOne;
+	char chr[5]{};
+	size_t chw = usetc(chr, extraOne);
 	size_t ln = strlen(thisString) + chw + 1;
 	char *buffer = (char *)malloc(ln);
 	Common::sprintf_s(buffer, ln, "%s%s", thisString, chr);
@@ -82,12 +78,8 @@ const char *String_ReplaceCharAt(const char *thisString, int index, int newChar)
 	int uchar = ugetc(thisString + off);
 	size_t remain_sz = strlen(thisString + off);
 	size_t old_sz = ucwidth(uchar);
-	size_t new_chw = 1;
-	char new_chr[Utf8::UtfSz + 1]{};
-	if (get_uformat() == U_UTF8)
-		new_chw = Utf8::SetChar(newChar, new_chr, sizeof(new_chr));
-	else
-		new_chr[0] = newChar;
+	char new_chr[5]{};
+	size_t new_chw = usetc(new_chr, newChar);
 	size_t total_sz = off + remain_sz + new_chw - old_sz + 1;
 	char *buffer = (char *)malloc(total_sz);
 	memcpy(buffer, thisString, off);
diff --git a/engines/ags/engine/script/script_api.cpp b/engines/ags/engine/script/script_api.cpp
index b5ba77d715d..16e442fe9cc 100644
--- a/engines/ags/engine/script/script_api.cpp
+++ b/engines/ags/engine/script/script_api.cpp
@@ -29,7 +29,7 @@
 
 namespace AGS3 {
 
-namespace Math = AGS::Shared::Math;
+using namespace AGS::Shared;
 
 enum FormatParseResult {
 	kFormatParseNone,
@@ -188,11 +188,8 @@ const char *ScriptSprintf(char *buffer, size_t buf_length, const char *format,
 				case kFormatParseArgCharacter:
 				{
 					int chr = GetArgInt(sc_args, varg_ptr, arg_idx);
-					char cbuf[Utf8::UtfSz + 1]{};
-					if (get_uformat() == U_UTF8)
-						Utf8::SetChar(chr, cbuf, Utf8::UtfSz);
-					else
-						cbuf[0] = chr;
+					char cbuf[5]{};
+					usetc(cbuf, chr);
 					snprintf_res = snprintf(out_ptr, avail_outbuf + 1, "%s", cbuf);
 					break;
 				}


Commit: cd093dbf79072b9f209c809c4f862a637179635c
    https://github.com/scummvm/scummvm/commit/cd093dbf79072b9f209c809c4f862a637179635c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Allegro: Allegro: added utf8 validation to unicode functions

>From upstream 0e89c1e03a5114afb416c7020a9a2d8d9308e7e1 and
a6fef605bf0cf1b04d8ff2a9f94070b479ba2a2a

Changed paths:
    engines/ags/lib/allegro/unicode.cpp


diff --git a/engines/ags/lib/allegro/unicode.cpp b/engines/ags/lib/allegro/unicode.cpp
index a1acc6c49de..99730518ba4 100644
--- a/engines/ags/lib/allegro/unicode.cpp
+++ b/engines/ags/lib/allegro/unicode.cpp
@@ -98,6 +98,12 @@ size_t ustrsize(const char *s) {
 	return strlen(s);
 }
 
+static utf8_validate(int c) {
+	if (c < 0 || c > 0x10FFFF || (0xD800 <= c && c <= 0xDFFF))
+		return 0xFFFD;
+	return c;
+}
+
 int utf8_getc(const char *s) {
 	int c = *((const unsigned char *)(s++));
 	int n, t;
@@ -119,7 +125,7 @@ int utf8_getc(const char *s) {
 		}
 	}
 
-	return c;
+	return utf8_validate(c);
 }
 
 int utf8_getx(char **s) {
@@ -145,12 +151,13 @@ int utf8_getx(char **s) {
 		}
 	}
 
-	return c;
+	return utf8_validate(c);
 }
 
 int utf8_setc(char *s, int c) {
 	int size, bits, b, i;
 
+	c = utf8_validate(c);
 	if (c < 128) {
 		*s = c;
 		return 1;
@@ -197,6 +204,7 @@ int utf8_width(const char *s) {
 int utf8_cwidth(int c) {
 	int size, bits, b;
 
+	c = utf8_validate(c);
 	if (c < 128)
 		return 1;
 
@@ -216,7 +224,7 @@ int utf8_cwidth(int c) {
 }
 
 int utf8_isok(int c) {
-	return true;
+	return utf8_validate(c) == c;
 }
 
 


Commit: e8248f3a46d176c253ba3196c81a4c5a8d0a2271
    https://github.com/scummvm/scummvm/commit/e8248f3a46d176c253ba3196c81a4c5a8d0a2271
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: mark transition screenshot screen as opaque texture

This is a minor perf optimization.
+ added couple of TODO comments.
>From upstream 33a7a29d35f53bf0d389eb69f85bbb68bd172dd3

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/screen.cpp
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 6d376be7894..10c1f482963 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -798,7 +798,8 @@ Engine::IDriverDependantBitmap* recycle_ddb_sprite(Engine::IDriverDependantBitma
 	return _G(gfxDriver)->GetSharedDDB(sprite_id, source, has_alpha, opaque);
 }
 
-void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool opaque = false) {
+// FIXME: make has_alpha and opaque properties of ObjTexture?!
+static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool opaque = false) {
 	Bitmap *use_bmp = obj.Bmp.get() ? obj.Bmp.get() : _GP(spriteset)[obj.SpriteID];
 	obj.Ddb = recycle_ddb_sprite(obj.Ddb, obj.SpriteID, use_bmp, has_alpha, opaque);
 }
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 3ed4891ab56..51c4dc0196f 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -97,8 +97,7 @@ IDriverDependantBitmap *prepare_screen_for_transition_in() {
 		delete _G(saved_viewport_bitmap);
 		_G(saved_viewport_bitmap) = clippedBuffer;
 	}
-	IDriverDependantBitmap *ddb = _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false);
-	return ddb;
+	return _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false, true);
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 9e3b61296f4..3655f5a0a11 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -149,6 +149,10 @@ public:
 	virtual void DestroyDDB(IDriverDependantBitmap *bitmap) = 0;
 
 	// Get shared texture from cache, or create from bitmap and assign ID
+	// FIXME: opaque should be either texture data's flag, - in which case same sprite_id
+	// will be either opaque or not opaque, - or DDB's flag, but in that case it cannot
+	// be applied to the shared texture data. Currently it's possible to share same
+	// texture data, but update it with different "opaque" values, which breaks logic.
 	virtual IDriverDependantBitmap *GetSharedDDB(uint32_t sprite_id,
 		Shared::Bitmap *bitmap = nullptr, bool hasAlpha = true, bool opaque = false) = 0;
 	virtual void UpdateSharedDDB(uint32_t sprite_id, Shared::Bitmap *bitmap = nullptr, bool hasAlpha = true, bool opaque = false) = 0;


Commit: 78e1e5f1d029ce803ae34ead648ef5c363ae92e7
    https://github.com/scummvm/scummvm/commit/78e1e5f1d029ce803ae34ead648ef5c363ae92e7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: in SpriteFile test if indexed format has less size than regular

This is rather a formality, as in practice opposite case would happen quite rarely.
>From upstream d372b78008c40207173f3b546cae53ecf8466e6f

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 7eebb937660..086fb2b383b 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -615,19 +615,25 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	int w = image->GetWidth();
 	int h = image->GetHeight();
 	ImBufferCPtr im_data(image->GetData(), w * h * bpp, bpp);
+
 	// (Optional) Handle storage options
 	std::vector<uint8_t> indexed_buf;
 	uint32_t palette[256];
 	uint32_t pal_count = 0;
 	SpriteFormat sformat = kSprFmt_Undefined;
-	SpriteCompression compress = kSprCompress_None;
+
 	if ((_storeFlags & kSprStore_OptimizeForSize) != 0 && (image->GetBPP() > 1)) { // Try to store this sprite as an indexed bitmap
-		if (CreateIndexedBitmap(image, indexed_buf, palette, pal_count) && pal_count > 0) {
-			sformat = PaletteFormatForBPP(image->GetBPP());
+		uint32_t gen_pal_count;
+		if (CreateIndexedBitmap(image, indexed_buf, palette, gen_pal_count) && gen_pal_count > 0) { // Test the resulting size, and switch if the paletted image is less
+			if (im_data.Size > (indexed_buf.size() + gen_pal_count * image->GetBPP())) {
 			im_data = ImBufferCPtr(&indexed_buf[0], indexed_buf.size(), 1);
+			sformat = PaletteFormatForBPP(image->GetBPP());
+			pal_count = gen_pal_count;
+			}
 		}
 	}
 	// (Optional) Compress the image data into the temp buffer
+	SpriteCompression compress = kSprCompress_None;
 	if (_compress != kSprCompress_None) {
 		compress = _compress;
 		VectorStream mems(_membuf, kStream_Write);
@@ -641,6 +647,7 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 		// mark to write as a plain byte array
 		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
 	}
+
 	// Write the final data
 	SpriteDatHeader hdr(bpp, sformat, pal_count, compress, w, h);
 	WriteSpriteData(hdr, im_data.Buf, im_data.Size, im_data.BPP, palette);


Commit: 27a3b5e3e26dc549fbef4bfd23db14c8492ff9f5
    https://github.com/scummvm/scummvm/commit/27a3b5e3e26dc549fbef4bfd23db14c8492ff9f5
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: refactored FloatToInt(), use std functions

>From upstream c2edc969a848bceae6dac252c2ca2557054f6ba2

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


diff --git a/engines/ags/engine/ac/math.cpp b/engines/ags/engine/ac/math.cpp
index ca59325958a..b25177f0838 100644
--- a/engines/ags/engine/ac/math.cpp
+++ b/engines/ags/engine/ac/math.cpp
@@ -32,25 +32,15 @@
 namespace AGS3 {
 
 int FloatToInt(float value, int roundDirection) {
-	if (value >= 0.0) {
-		if (roundDirection == eRoundDown)
-			return static_cast<int>(value);
-		else if (roundDirection == eRoundNearest)
-			return static_cast<int>(value + 0.5);
-		else if (roundDirection == eRoundUp)
-			return static_cast<int>(value + 0.999999);
-		else
-			error("!FloatToInt: invalid round direction");
-	} else {
-		// negative number
-		if (roundDirection == eRoundUp)
-			return static_cast<int>(value); // this just truncates
-		else if (roundDirection == eRoundNearest)
-			return static_cast<int>(value - 0.5);
-		else if (roundDirection == eRoundDown)
-			return static_cast<int>(value - 0.999999);
-		else
-			error("!FloatToInt: invalid round direction");
+	switch (roundDirection) {
+	case eRoundDown:
+		return static_cast<int>(floor(value));
+	case eRoundUp:
+		return static_cast<int>(ceil(value));
+	case eRoundNearest:
+		return static_cast<int>(round(value));
+	default:
+		quit("!FloatToInt: invalid round direction");
 	}
 	return 0;
 }


Commit: c1ebccadbfe93e38ce5861d0c17e8564f0b9ac2c
    https://github.com/scummvm/scummvm/commit/c1ebccadbfe93e38ce5861d0c17e8564f0b9ac2c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in HandleToAddressAndManager zero the returned ptrs on error

This helps diagnosing the errors when the engine or plugin tries to retrieve
an object by a non-registered handle.
(as it's easier to understand the null pointer error rather than accessing
a random address)
>From upstream e2a2b4885029160a974fe1f6c4fb0abf6ca03aaa

Changed paths:
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp


diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 170095459ea..6e4857ca296 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -128,14 +128,12 @@ const char *ManagedObjectPool::HandleToAddress(int32_t handle) {
 
 // this function is called often (whenever a pointer is used)
 ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager) {
-	if (handle < 0 || (size_t)handle >= objects.size()) {
+	if ((handle < 0 || (size_t)handle >= objects.size()) || !objects[handle].isUsed()) {
+		object = nullptr;
+		manager = nullptr;
 		return kScValUndefined;
 	}
 	auto &o = objects[handle];
-	if (!o.isUsed()) {
-		return kScValUndefined;
-	}
-
 	object = const_cast<char *>(o.addr);  // WARNING: This strips the const from the char* pointer.
 	manager = o.callback;
 	return o.obj_type;


Commit: 2cfe391a1292b7f124c0aa37b720f32104c3b49f
    https://github.com/scummvm/scummvm/commit/2cfe391a1292b7f124c0aa37b720f32104c3b49f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Revert default opaque flag in screen transitions

MM Deluxe uses transparency in screen transitions, so it should be preserved

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 51c4dc0196f..08a2b4ded16 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -97,7 +97,7 @@ IDriverDependantBitmap *prepare_screen_for_transition_in() {
 		delete _G(saved_viewport_bitmap);
 		_G(saved_viewport_bitmap) = clippedBuffer;
 	}
-	return _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false, true);
+	return _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false, false);
 }
 
 //=============================================================================


Commit: f2ddfc90f57159c816773a7d82c57c918aa6e25e
    https://github.com/scummvm/scummvm/commit/f2ddfc90f57159c816773a7d82c57c918aa6e25e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: cleaned up old commented code

This was an early attempt to pick out few operations into separate functions,
but IIRC this caused additional slowdown. Maybe was not done in an optimal way.
In any case, it's better to remove the old commented code, as there may be slight
differences between that and current inlined code now.
They may be recreated if necessary for another refactor. Just need to be aware of
potential performance issues, and do tests.
>From upstream 961569db426af1c09c7d9da38f5f4e4a0e09b499

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index fb2f41c4b88..cd340093203 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -460,16 +460,14 @@ int ccInstance::Run(int32_t curpc) {
 	//const auto timeout_abort = std::chrono::milliseconds(_G(timeoutAbortMs));
 	_lastAliveTs = AGS_Clock::now();
 
+	/* Main bytecode execution loop */
+	//=====================================================================
 	while ((flags & INSTF_ABORTED) == 0) {
-		if (_G(abort_engine))
-			return -1;
-
-		/*
-		if (!codeInst->ReadOperation(codeOp, pc))
-		{
-		    return -1;
-		}
-		*/
+		// WARNING: a time-critical code ahead;
+		// trying to pick some of the code out to separate function(s)
+		// may lead to a performance loss in script-heavy games.
+		// always compare execution speed before applying any major changes!
+		//
 		/* ReadOperation */
 		//=====================================================================
 		codeOp.Instruction.Code         = codeInst->code[pc];
@@ -491,15 +489,9 @@ int ccInstance::Run(int32_t curpc) {
 		for (int i = 0; i < codeOp.ArgCount; ++i, ++pc_at) {
 			char fixup = codeInst->code_fixups[pc_at];
 			if (fixup > 0) {
-				// could be relative pointer or import address
-				/*
-				if (!FixupArgument(code[pc], fixup, codeOp.Args[i]))
-				{
-				    return -1;
-				}
-				*/
 				/* FixupArgument */
 				//=====================================================================
+				// could be relative pointer or import address
 				switch (fixup) {
 				case FIXUP_GLOBALDATA: {
 					ScriptVariable *gl_var = (ScriptVariable *)codeInst->code[pc_at];
@@ -1729,89 +1721,6 @@ bool ccInstance::ResolveImportFixups(const ccScript *scri) {
 	return true;
 }
 
-/*
-bool ccInstance::ReadOperation(ScriptOperation &op, int32_t at_pc)
-{
-    op.Instruction.Code         = code[at_pc];
-    op.Instruction.InstanceId   = (op.Instruction.Code >> INSTANCE_ID_SHIFT) & INSTANCE_ID_MASK;
-    op.Instruction.Code        &= INSTANCE_ID_REMOVEMASK; // now this is pure instruction code
-
-    int want_args = (*g_commands)[op.Instruction.Code].ArgCount;
-    if (at_pc + want_args >= codesize)
-    {
-        cc_error("unexpected end of code data at %d", at_pc + want_args);
-        return false;
-    }
-    op.ArgCount = want_args;
-
-    at_pc++;
-    for (int i = 0; i < op.ArgCount; ++i, ++at_pc)
-    {
-        char fixup = code_fixups[at_pc];
-        if (fixup > 0)
-        {
-            // could be relative pointer or import address
-            if (!FixupArgument(code[at_pc], fixup, op.Args[i]))
-            {
-                return false;
-            }
-        }
-        else
-        {
-            // should be a numeric literal (int32 or float)
-            op.Args[i].SetInt32( (int32_t)code[at_pc] );
-        }
-    }
-
-    return true;
-}
-*/
-/*
-bool ccInstance::FixupArgument(intptr_t code_value, char fixup_type, RuntimeScriptValue &argument)
-{
-    switch (fixup_type)
-    {
-    case FIXUP_GLOBALDATA:
-        {
-            ScriptVariable *gl_var = (ScriptVariable*)code_value;
-            argument.SetGlobalVar(&gl_var->RValue);
-        }
-        break;
-    case FIXUP_FUNCTION:
-        // originally commented -- CHECKME: could this be used in very old versions of AGS?
-        //      code[fixup] += (long)&code[0];
-        // This is a program counter value, presumably will be used as SCMD_CALL argument
-        argument.SetInt32((int32_t)code_value);
-        break;
-    case FIXUP_STRING:
-        argument.SetStringLiteral(&strings[0] + code_value);
-        break;
-    case FIXUP_IMPORT:
-        {
-            const ScriptImport *import = _GP(simp).getByIndex((int32_t)code_value);
-            if (import)
-            {
-                argument = import->Value;
-            }
-            else
-            {
-                cc_error("cannot resolve import, key = %ld", code_value);
-                return false;
-            }
-        }
-        break;
-    case FIXUP_STACK:
-        argument = GetStackPtrOffsetFw((int32_t)code_value);
-        break;
-    default:
-        cc_error("internal fixup type error: %d", fixup_type);
-        return false;
-    }
-    return true;
-}
-*/
-//-----------------------------------------------------------------------------
-
 void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) {
 	// Write value to the stack tail and advance stack ptr
 	registers[SREG_SP].WriteValue(rval);
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 732ee5c6593..909158e7528 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -193,12 +193,9 @@ private:
 	bool    AddGlobalVar(const ScriptVariable &glvar);
 	ScriptVariable *FindGlobalVar(int32_t var_addr);
 	bool    CreateRuntimeCodeFixups(const ccScript *scri);
-	//bool    ReadOperation(ScriptOperation &op, int32_t at_pc);
 
 	// Begin executing script starting from the given bytecode index
 	int     Run(int32_t curpc);
-	// Runtime fixups
-	//bool    FixupArgument(intptr_t code_value, char fixup_type, RuntimeScriptValue &argument);
 
 	// Stack processing
 	// Push writes new value and increments stack ptr;


Commit: 91b699106fccf0badf1bb96f8ac49f12bc7b1f0e
    https://github.com/scummvm/scummvm/commit/91b699106fccf0badf1bb96f8ac49f12bc7b1f0e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: minor parameter adjustments

from upstream d9284d8c1a265f0c4b329c27dde2997f465375d4

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index cd340093203..201a795a4fa 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -307,7 +307,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 		return -1; // TODO: correct error value
 	}
 
-	if ((numargs >= 20) || (numargs < 0)) {
+	if ((numargs >= MAX_FUNCTION_PARAMS) || (numargs < 0)) {
 		cc_error("too many arguments to function");
 		return -3;
 	}
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 909158e7528..54e7a723193 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -38,9 +38,11 @@ using namespace AGS;
 #define INSTF_ABORTED       2
 #define INSTF_FREE          4
 #define INSTF_RUNNING       8   // set by main code to confirm script isn't stuck
-#define CC_STACK_SIZE       250
-#define CC_STACK_DATA_SIZE  (1000 * sizeof(int32_t))
-#define MAX_CALL_STACK      100
+
+#define CC_STACK_SIZE		256
+#define CC_STACK_DATA_SIZE	(1024 * sizeof(int32_t))
+#define MAX_CALL_STACK		128
+#define MAX_FUNCTION_PARAMS	20
 
 // 256 because we use 8 bits to hold instance number
 #define MAX_LOADED_INSTANCES 256


Commit: 96ddb49fd0b21874b0d5b50a46e25f32fd4d63d0
    https://github.com/scummvm/scummvm/commit/96ddb49fd0b21874b0d5b50a46e25f32fd4d63d0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: hide DumpInstruction under DEBUG_CC_EXEC flag

from upstream 9735b097abdb58a2d17d58cbe6b45e3b66c9cba8

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 201a795a4fa..69d727e696d 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -447,10 +447,12 @@ int ccInstance::Run(int32_t curpc) {
 	thisbase[0] = 0;
 	funcstart[0] = pc;
 	ccInstance *codeInst = runningInst;
-	bool write_debug_dump = ccGetOption(SCOPT_DEBUGRUN) ||
-		(gDebugLevel > 0 && DebugMan.isDebugChannelEnabled(::AGS::kDebugScript));
 	ScriptOperation codeOp;
 	FunctionCallStack func_callstack;
+#if DEBUG_CC_EXEC
+	const bool dump_opcodes = ccGetOption(SCOPT_DEBUGRUN) ||
+							  (gDebugLevel > 0 && DebugMan.isDebugChannelEnabled(::AGS::kDebugScript));
+#endif
 	int loopIterationCheckDisabled = 0;
 	unsigned loopIterations = 0u;      // any loop iterations (needed for timeout test)
 	unsigned loopCheckIterations = 0u; // loop iterations accumulated only if check is enabled
@@ -546,9 +548,11 @@ int ccInstance::Run(int32_t curpc) {
 		const char *direct_ptr1;
 		const char *direct_ptr2;
 
-		if (write_debug_dump) {
+#if (DEBUG_CC_EXEC)
+		if (dump_opcodes) {
 			DumpInstruction(codeOp);
 		}
+#endif
 
 		switch (codeOp.Instruction.Code) {
 		case SCMD_LINENUM:
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 54e7a723193..b4b8eec5eed 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -51,6 +51,10 @@ using namespace AGS;
 #define INSTANCE_ID_MASK  0x00000000000000ffLL
 #define INSTANCE_ID_REMOVEMASK 0x0000000000ffffffLL
 
+// Script executor debugging flag:
+// enables mistake checks, but slows things down!
+#define DEBUG_CC_EXEC (AGS_PLATFORM_DEBUG)
+
 struct ScriptInstruction {
 	ScriptInstruction() {
 		Code = 0;


Commit: 650c93b7d07abf8ae1ab0be89a786ae855e9da4a
    https://github.com/scummvm/scummvm/commit/650c93b7d07abf8ae1ab0be89a786ae855e9da4a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: have stack-related asserts under DEBUG_CC_EXEC

from upstream e16b94226fd1a67799f032c42ea3ff0d382ba423

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 69d727e696d..ee1afd137ed 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -284,13 +284,58 @@ void ccInstance::AbortAndDestroy() {
 	flags |= INSTF_FREE;
 }
 
-#define ASSERT_STACK_SPACE_AVAILABLE(N) \
-	if (registers[SREG_SP].RValue + N - &stack[0] >= CC_STACK_SIZE) \
+// ASSERT_CC_OP tests for the internal function call return value and
+// returns failure on error
+#if (DEBUG_CC_EXEC)
+
+#define CC_ERROR_IF(COND, ERROR) \
+	if (COND) \
+	{ \
+		cc_error(ERROR); \
+		return; \
+	}
+
+#define CC_ERROR_IF_RET(COND, ERROR, T) \
+	if (COND) \
+	{ \
+		cc_error(ERROR); \
+		return T(); \
+	}
+
+#define ASSERT_CC_ERROR() \
+	if (cc_has_error()) \
 	{ \
-		cc_error("stack overflow"); \
 		return -1; \
 	}
 
+#else
+
+#define CC_ERROR_IF(COND, ERROR)
+#define CC_ERROR_IF_RET(COND, ERROR, T)
+#define ASSERT_CC_ERROR()
+
+#endif // DEBUG_CC_EXEC
+
+
+// Two stack assertions that are always enabled:
+// ASSERT_STACK_SPACE_AVAILABLE tests that we do not exceed stack limit
+#define ASSERT_STACK_SPACE_AVAILABLE(N_VALS, N_BYTES) \
+	if ((registers[SREG_SP].RValue + N_VALS - &stack[0]) >= CC_STACK_SIZE || \
+		(stackdata_ptr + N_BYTES - stackdata) >= CC_STACK_DATA_SIZE) \
+	{ \
+		cc_error("stack overflow, attempted grow by %d bytes", N_BYTES); \
+		return -1; \
+	}
+
+// ASSERT_STACK_SPACE_BYTES tests that we do not exceed stack limit
+// if we are going to add N_BYTES bytes to stack
+#define ASSERT_STACK_SPACE_BYTES(N_BYTES) ASSERT_STACK_SPACE_AVAILABLE(1, N_BYTES)
+
+// ASSERT_STACK_SPACE_VALS tests that we do not exceed stack limit
+// if we are going to add N_VALS values, sizeof(int32) each
+#define ASSERT_STACK_SPACE_VALS(N_VALS) ASSERT_STACK_SPACE_AVAILABLE(N_VALS, sizeof(int32_t) * N_VALS)
+
+// ASSERT_STACK_SIZE tests that we do not unwind stack past its beginning
 #define ASSERT_STACK_SIZE(N) \
 	if (registers[SREG_SP].RValue - N < &stack[0]) \
 	{ \
@@ -367,7 +412,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	registers[SREG_SP].SetStackPtr(&stack[0]);
 	stackdata_ptr = stackdata;
 	// NOTE: Pushing parameters to stack in reverse order
-	ASSERT_STACK_SPACE_AVAILABLE(numargs + 1 /* return address */)
+	ASSERT_STACK_SPACE_VALS(numargs + 1 /* return address */);
 		for (int i = numargs - 1; i >= 0; --i) {
 			PushValueToStack(params[i]);
 		}
@@ -566,15 +611,15 @@ int ccInstance::Run(int32_t curpc) {
 			if (arg1.IValue == SREG_SP) {
 				// Only allocate new data if current stack entry is invalid;
 				// in some cases this may be advancing over value that was written by MEMWRITE*
-				ASSERT_STACK_SPACE_AVAILABLE(1);
+				// FIXME: this is weird, do this in a uniform way (always same operation),
+				// and don't rely on stack entries being valid/invalid beyond the stack ptr.
+				ASSERT_STACK_SPACE_AVAILABLE(1, arg2.IValue);
 				if (reg1.RValue->IsValid()) {
 					// TODO: perhaps should add a flag here to ensure this happens only after MEMWRITE-ing to stack
 					registers[SREG_SP].RValue++;
 				} else {
 					PushDataToStack(arg2.IValue);
-					if (cc_has_error()) {
-						return -1;
-					}
+					ASSERT_CC_ERROR();
 				}
 			} else {
 				reg1.IValue += arg2.IValue;
@@ -587,15 +632,14 @@ int ccInstance::Run(int32_t curpc) {
 				// quote JJS:
 				// // AGS 2.x games also perform relative stack access by copying SREG_SP to SREG_MAR
 				// // and then subtracting from that.
+				// FIXME: try to do this in uniform way, call same func, save result in reg1
 				if (arg1.IValue == SREG_SP) {
 					PopDataFromStack(arg2.IValue);
 				} else {
 					// This is practically LOADSPOFFS
 					reg1 = GetStackPtrOffsetRw(arg2.IValue);
 				}
-				if (cc_has_error()) {
-					return -1;
-				}
+				ASSERT_CC_ERROR();
 			} else {
 				reg1.IValue -= arg2.IValue;
 			}
@@ -654,9 +698,7 @@ int ccInstance::Run(int32_t curpc) {
 			break;
 		case SCMD_LOADSPOFFS:
 			registers[SREG_MAR] = GetStackPtrOffsetRw(arg1.IValue);
-			if (cc_has_error()) {
-				return -1;
-			}
+			ASSERT_CC_ERROR();
 			break;
 
 		// 64 bit: Force 32 bit math
@@ -731,7 +773,7 @@ int ccInstance::Run(int32_t curpc) {
 
 			PUSH_CALL_STACK;
 
-			ASSERT_STACK_SPACE_AVAILABLE(1);
+			ASSERT_STACK_SPACE_VALS(1);
 			PushValueToStack(RuntimeScriptValue().SetInt32(pc + codeOp.ArgCount + 1));
 
 			if (thisbase[curnest] == 0)
@@ -776,7 +818,7 @@ int ccInstance::Run(int32_t curpc) {
 			break;
 		case SCMD_PUSHREG:
 			// Push reg[arg1] value to the stack
-			ASSERT_STACK_SPACE_AVAILABLE(1);
+			ASSERT_STACK_SPACE_VALS(1);
 			PushValueToStack(reg1);
 			break;
 		case SCMD_POPREG:
@@ -838,8 +880,6 @@ int ccInstance::Run(int32_t curpc) {
 		// 64 bit: Handles are always 32 bit values. They are not C pointer.
 
 		case SCMD_MEMREADPTR: {
-			cc_clear_error();
-
 			int32_t handle = registers[SREG_MAR].ReadInt32();
 			void *object;
 			ICCDynamicObject *manager;
@@ -849,10 +889,7 @@ int ccInstance::Run(int32_t curpc) {
 			} else {
 				reg1.SetDynamicObject(object, manager);
 			}
-
-			// if error occurred, cc_error will have been set
-			if (cc_has_error())
-				return -1;
+			ASSERT_CC_ERROR();
 			break;
 		}
 		case SCMD_MEMWRITEPTR: {
@@ -958,7 +995,7 @@ int ccInstance::Run(int32_t curpc) {
 			if (num_args_to_func < 0) {
 				num_args_to_func = func_callstack.Count;
 			}
-			ASSERT_STACK_SPACE_AVAILABLE(num_args_to_func + 1 /* return address */);
+			ASSERT_STACK_SPACE_VALS(num_args_to_func + 1 /* return address */);
 			for (const RuntimeScriptValue *prval = func_callstack.GetHead() + num_args_to_func;
 			        prval > func_callstack.GetHead(); --prval) {
 				PushValueToStack(*prval);
@@ -967,9 +1004,7 @@ int ccInstance::Run(int32_t curpc) {
 			// 0, so that the cc_run_code returns
 			RuntimeScriptValue oldstack = registers[SREG_SP];
 			PushValueToStack(RuntimeScriptValue().SetInt32(0));
-			if (cc_has_error()) {
-				return -1;
-			}
+			ASSERT_CC_ERROR();
 
 			int oldpc = pc;
 			ccInstance *wasRunning = runningInst;
@@ -1182,13 +1217,7 @@ int ccInstance::Run(int32_t curpc) {
 			// Check if we are zeroing at stack tail
 			if (registers[SREG_MAR] == registers[SREG_SP]) {
 				// creating a local variable -- check the stack to ensure no mem overrun
-				int currentStackSize = registers[SREG_SP].RValue - &stack[0];
-				int currentDataSize = stackdata_ptr - stackdata;
-				if (currentStackSize + 1 >= CC_STACK_SIZE ||
-				        currentDataSize + arg1.IValue >= (int32_t)CC_STACK_DATA_SIZE) {
-					cc_error("stack overflow, attempted grow to %d bytes", currentDataSize + arg1.IValue);
-					return -1;
-				}
+				ASSERT_STACK_SPACE_BYTES(arg1.IValue);
 				// NOTE: according to compiler's logic, this is always followed
 				// by SCMD_ADD, and that is where the data is "allocated", here we
 				// just clean the place.
@@ -1732,10 +1761,7 @@ void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) {
 }
 
 void ccInstance::PushDataToStack(int32_t num_bytes) {
-	if (registers[SREG_SP].RValue->IsValid()) {
-		cc_error("internal error: valid data beyond stack ptr");
-		return;
-	}
+	CC_ERROR_IF(registers[SREG_SP].RValue->IsValid(), "internal error: valid data beyond stack ptr");
 	// Zero memory, assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr
 	memset(stackdata_ptr, 0, num_bytes);
 	registers[SREG_SP].RValue->SetData(stackdata_ptr, num_bytes);
@@ -1748,9 +1774,10 @@ RuntimeScriptValue ccInstance::PopValueFromStack() {
 	registers[SREG_SP].RValue--;
 	RuntimeScriptValue rval = *registers[SREG_SP].RValue;
 	if (rval.Type == kScValData) {
+		// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
 		stackdata_ptr -= rval.Size;
 	}
-	registers[SREG_SP].RValue->Invalidate();
+	registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
 	return rval;
 }
 
@@ -1759,9 +1786,10 @@ void ccInstance::PopValuesFromStack(int32_t num_entries = 1) {
 		// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 		registers[SREG_SP].RValue--;
 		if (registers[SREG_SP].RValue->Type == kScValData) {
+			// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
 			stackdata_ptr -= registers[SREG_SP].RValue->Size;
 		}
-		registers[SREG_SP].RValue->Invalidate();
+		registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
 	}
 }
 
@@ -1773,36 +1801,26 @@ void ccInstance::PopDataFromStack(int32_t num_bytes) {
 		// remember popped bytes count
 		total_pop += registers[SREG_SP].RValue->Size;
 		if (registers[SREG_SP].RValue->Type == kScValData) {
+			// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
 			stackdata_ptr -= registers[SREG_SP].RValue->Size;
 		}
-		registers[SREG_SP].RValue->Invalidate();
-	}
-	if (total_pop < num_bytes) {
-		cc_error("stack underflow");
-	} else if (total_pop > num_bytes) {
-		cc_error("stack pointer points inside local variable after pop, stack corrupted?");
+		registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
 	}
+	CC_ERROR_IF(total_pop < num_bytes, "stack underflow");
+	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop, stack corrupted?");
 }
 
 RuntimeScriptValue ccInstance::GetStackPtrOffsetFw(int32_t fw_offset) {
 	int32_t total_off = 0;
 	RuntimeScriptValue *stack_entry = &stack[0];
 	while (total_off < fw_offset && stack_entry - &stack[0] < CC_STACK_SIZE) {
-		if (stack_entry->Size > 0) {
-			total_off += stack_entry->Size;
-		}
 		stack_entry++;
+		total_off += stack_entry->Size;
 	}
-	if (total_off < fw_offset) {
-		cc_error("accessing address beyond stack's tail");
-		return RuntimeScriptValue();
-	}
+	CC_ERROR_IF_RET(total_off < fw_offset, "accessing address beyond stack's tail", RuntimeScriptValue);
+	CC_ERROR_IF_RET(total_off > fw_offset, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
-	if (total_off > fw_offset) {
-		// Forward offset should always set ptr at the beginning of stack entry
-		cc_error("stack offset forward: trying to access stack data inside stack entry, stack corrupted?");
-	}
 	return stack_ptr;
 }
 
@@ -1813,20 +1831,15 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
 		stack_entry--;
 		total_off += stack_entry->Size;
 	}
-	if (total_off < rw_offset) {
-		cc_error("accessing address before stack's head");
-		return RuntimeScriptValue();
-	}
+	CC_ERROR_IF_RET(total_off < rw_offset, "accessing address before stack's head", RuntimeScriptValue);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
-	if (total_off > rw_offset) {
-		// Could be accessing array element, so state error only if stack entry does not refer to data array
-		if (stack_entry->Type == kScValData) {
-			stack_ptr.IValue += total_off - rw_offset;
-		} else {
-			cc_error("stack offset backward: trying to access stack data inside stack entry, stack corrupted?");
-		}
+	// Could be accessing array element, so state error only if stack entry does not refer to data array
+	// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
+	if (stack_entry->Type == kScValData) {
+		stack_ptr.IValue += total_off - rw_offset;
 	}
+	CC_ERROR_IF_RET((total_off > rw_offset) && (stack_entry->Type != kScValData), "stack offset backward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue)
 	return stack_ptr;
 }
 
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index c0d898a034a..5b79e9d337d 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -128,12 +128,7 @@ public:
 	}
 
 	inline RuntimeScriptValue &Invalidate() {
-		Type = kScValUndefined;
-		methodName.clear();
-		IValue = 0;
-		Ptr = nullptr;
-		MgrPtr = nullptr;
-		Size = 0;
+		*this = RuntimeScriptValue();
 		return *this;
 	}
 	inline RuntimeScriptValue &SetUInt8(uint8_t val) {


Commit: 03214f54ebbe538df7b7c79715a20e2d0fd10886
    https://github.com/scummvm/scummvm/commit/03214f54ebbe538df7b7c79715a20e2d0fd10886
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: move stackdata_ptr for each stack op, be consistent

Stack stores either "values" or arrays. Values are always stored as int32:
these are ints, floats, managed handles, also "pointers".
Although we don't store things like pointers in the stack buffer for real,
we store them as stack "entries" as RuntimeScriptValues.
But we keep stack pointer consistent by moving along the buffer,
to avoid unnecessary data type tests. Basically, the stack buffer will be
holding 32-bit placeholders.
>From upstream 338afc27ece7ff4030101887c9f00ab4f24ca3ca

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index ee1afd137ed..70c3d1d6256 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -611,12 +611,14 @@ int ccInstance::Run(int32_t curpc) {
 			if (arg1.IValue == SREG_SP) {
 				// Only allocate new data if current stack entry is invalid;
 				// in some cases this may be advancing over value that was written by MEMWRITE*
-				// FIXME: this is weird, do this in a uniform way (always same operation),
+				// FIXME: this is bad, but seemed to be the way to separate PushValue and PushData
+				// find if it's possible to do this in a uniform way (always same operation),
 				// and don't rely on stack entries being valid/invalid beyond the stack ptr.
 				ASSERT_STACK_SPACE_AVAILABLE(1, arg2.IValue);
 				if (reg1.RValue->IsValid()) {
 					// TODO: perhaps should add a flag here to ensure this happens only after MEMWRITE-ing to stack
 					registers[SREG_SP].RValue++;
+					stackdata_ptr += sizeof(int32_t); // formality, to keep data ptr consistent
 				} else {
 					PushDataToStack(arg2.IValue);
 					ASSERT_CC_ERROR();
@@ -1221,7 +1223,6 @@ int ccInstance::Run(int32_t curpc) {
 				// NOTE: according to compiler's logic, this is always followed
 				// by SCMD_ADD, and that is where the data is "allocated", here we
 				// just clean the place.
-				// CHECKME -- since we zero memory in PushDataToStack anyway, this is not needed at all?
 				memset(stackdata_ptr, 0, arg1.IValue);
 			} else {
 				cc_error("internal error: stack tail address expected on SCMD_ZEROMEMORY instruction, reg[MAR] type is %d",
@@ -1757,13 +1758,14 @@ bool ccInstance::ResolveImportFixups(const ccScript *scri) {
 void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) {
 	// Write value to the stack tail and advance stack ptr
 	registers[SREG_SP].WriteValue(rval);
+	stackdata_ptr += sizeof(int32_t); // formality, to keep data ptr consistent
 	registers[SREG_SP].RValue++;
 }
 
 void ccInstance::PushDataToStack(int32_t num_bytes) {
 	CC_ERROR_IF(registers[SREG_SP].RValue->IsValid(), "internal error: valid data beyond stack ptr");
-	// Zero memory, assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr
-	memset(stackdata_ptr, 0, num_bytes);
+	// Assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr
+	// NOTE: memory is zeroed by SCMD_ZEROMEMORY
 	registers[SREG_SP].RValue->SetData(stackdata_ptr, num_bytes);
 	stackdata_ptr += num_bytes;
 	registers[SREG_SP].RValue++;
@@ -1772,12 +1774,9 @@ void ccInstance::PushDataToStack(int32_t num_bytes) {
 RuntimeScriptValue ccInstance::PopValueFromStack() {
 	// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 	registers[SREG_SP].RValue--;
-	RuntimeScriptValue rval = *registers[SREG_SP].RValue;
-	if (rval.Type == kScValData) {
-		// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
-		stackdata_ptr -= rval.Size;
-	}
-	registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
+	RuntimeScriptValue rval = *registers[SREG_SP].RValue; // save before invalidating
+	stackdata_ptr -= sizeof(int32_t); // formality, to keep data ptr consistent
+	registers[SREG_SP].RValue->Invalidate(); // FIXME: bad, this is used to separate PushValue and PushData
 	return rval;
 }
 
@@ -1785,11 +1784,8 @@ void ccInstance::PopValuesFromStack(int32_t num_entries = 1) {
 	for (int i = 0; i < num_entries; ++i) {
 		// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 		registers[SREG_SP].RValue--;
-		if (registers[SREG_SP].RValue->Type == kScValData) {
-			// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
-			stackdata_ptr -= registers[SREG_SP].RValue->Size;
-		}
-		registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
+		stackdata_ptr -= sizeof(int32_t); // formality, to keep data ptr consistent
+		registers[SREG_SP].RValue->Invalidate(); // FIXME: bad, this is used to separate PushValue and PushData
 	}
 }
 
@@ -1798,13 +1794,10 @@ void ccInstance::PopDataFromStack(int32_t num_bytes) {
 	while (total_pop < num_bytes && registers[SREG_SP].RValue > &stack[0]) {
 		// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 		registers[SREG_SP].RValue--;
+		stackdata_ptr -= registers[SREG_SP].RValue->Size;
 		// remember popped bytes count
 		total_pop += registers[SREG_SP].RValue->Size;
-		if (registers[SREG_SP].RValue->Type == kScValData) {
-			// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
-			stackdata_ptr -= registers[SREG_SP].RValue->Size;
-		}
-		registers[SREG_SP].RValue->Invalidate(); // FIXME: don't do this, but this is used in some conditions
+		registers[SREG_SP].RValue->Invalidate(); // FIXME: bad, this is used to separate PushValue and PushData
 	}
 	CC_ERROR_IF(total_pop < num_bytes, "stack underflow");
 	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop, stack corrupted?");
@@ -1834,11 +1827,8 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
 	CC_ERROR_IF_RET(total_off < rw_offset, "accessing address before stack's head", RuntimeScriptValue);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
+	stack_ptr.IValue += total_off - rw_offset; // possibly offset to the mid-array
 	// Could be accessing array element, so state error only if stack entry does not refer to data array
-	// FIXME: refactor and add/sub stackdata_ptr always, avoid condition
-	if (stack_entry->Type == kScValData) {
-		stack_ptr.IValue += total_off - rw_offset;
-	}
 	CC_ERROR_IF_RET((total_off > rw_offset) && (stack_entry->Type != kScValData), "stack offset backward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue)
 	return stack_ptr;
 }


Commit: b1e4aba613e395ae33be65aed3deecc08663a288
    https://github.com/scummvm/scummvm/commit/b1e4aba613e395ae33be65aed3deecc08663a288
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: tiny optimizations in SCMD_MEMREAD/WRITE/INITPTR

>From upstream eb0b0020cf4168862a7ddb01af24684486691804

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 70c3d1d6256..5c5a00bc580 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -295,7 +295,14 @@ void ccInstance::AbortAndDestroy() {
 		return; \
 	}
 
-#define CC_ERROR_IF_RET(COND, ERROR, T) \
+#define CC_ERROR_IF_RETCODE(COND, ERROR) \
+	if (COND) \
+	{ \
+		cc_error(ERROR); \
+		return -1; \
+	}
+
+#define CC_ERROR_IF_RETVAL(COND, ERROR, T) \
 	if (COND) \
 	{ \
 		cc_error(ERROR); \
@@ -311,7 +318,8 @@ void ccInstance::AbortAndDestroy() {
 #else
 
 #define CC_ERROR_IF(COND, ERROR)
-#define CC_ERROR_IF_RET(COND, ERROR, T)
+#define CC_ERROR_IF_RETCODE(COND, ERROR)
+#define CC_ERROR_IF_RETVAL(COND, ERROR, T)
 #define ASSERT_CC_ERROR()
 
 #endif // DEBUG_CC_EXEC
@@ -883,35 +891,37 @@ int ccInstance::Run(int32_t curpc) {
 
 		case SCMD_MEMREADPTR: {
 			int32_t handle = registers[SREG_MAR].ReadInt32();
+			// FIXME: make pool return a ready RuntimeScriptValue with these set?
+			// or another struct, which may be assigned to RSV
 			void *object;
 			ICCDynamicObject *manager;
 			ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(handle, object, manager);
-			if (obj_type == kScValPluginObject) {
-				reg1.SetPluginObject(object, manager);
-			} else {
-				reg1.SetDynamicObject(object, manager);
-			}
+			reg1.SetDynamicObject(obj_type, object, manager);
 			ASSERT_CC_ERROR();
 			break;
 		}
 		case SCMD_MEMWRITEPTR: {
 
 			int32_t handle = registers[SREG_MAR].ReadInt32();
-			const char *address = nullptr;
-
-			if (reg1.Type == kScValStaticArray && reg1.StcArr->GetDynamicManager()) {
-				address = (const char *)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue);
-			} else if (reg1.Type == kScValDynamicObject ||
-			           reg1.Type == kScValPluginObject) {
+			const char *address;
+			switch (reg1.Type) {
+			case kScValStaticArray:
+				CC_ERROR_IF_RETCODE(!reg1.StcArr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
+				address = reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue);
+				break;
+			case kScValDynamicObject:
+			case kScValPluginObject:
 				address = reg1.Ptr;
-			} else if (reg1.Type == kScValPluginArg) {
-				// TODO: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
+				break;
+			case kScValPluginArg:
+				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
 				address = Int32ToPtr<char>(reg1.IValue);
-			}
-			// There's one possible case when the reg1 is 0, which means writing nullptr
-			else if (!reg1.IsNull()) {
-				cc_error("internal error: MEMWRITEPTR argument is not dynamic object");
-				return -1;
+				break;
+			default:
+				// There's one possible case when the reg1 is 0, which means writing nullptr
+				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: MEMWRITEPTR argument is not a dynamic object");
+				address = nullptr;
+				break;
 			}
 
 			int32_t newHandle = ccGetObjectHandleFromAddress(address);
@@ -926,22 +936,28 @@ int ccInstance::Run(int32_t curpc) {
 			break;
 		}
 		case SCMD_MEMINITPTR: {
-			const char *address = nullptr;
+			const char *address;
 
-			if (reg1.Type == kScValStaticArray && reg1.StcArr->GetDynamicManager()) {
+			switch (reg1.Type) {
+			case kScValStaticArray:
+				CC_ERROR_IF_RETCODE(!reg1.StcArr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
 				address = (const char *)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue);
-			} else if (reg1.Type == kScValDynamicObject ||
-			           reg1.Type == kScValPluginObject) {
+				break;
+			case kScValDynamicObject:
+			case kScValPluginObject:
 				address = reg1.Ptr;
-			} else if (reg1.Type == kScValPluginArg) {
-				// TODO: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
+				break;
+			case kScValPluginArg:
+				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
 				address = Int32ToPtr<char>(reg1.IValue);
+				break;
+			default:
+				// There's one possible case when the reg1 is 0, which means writing nullptr
+				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
+				address = nullptr;
+				break;
 			}
-			// There's one possible case when the reg1 is 0, which means writing nullptr
-			else if (!reg1.IsNull()) {
-				cc_error("internal error: SCMD_MEMINITPTR argument is not dynamic object");
-				return -1;
-			}
+
 			// like memwriteptr, but doesn't attempt to free the old one
 			int32_t newHandle = ccGetObjectHandleFromAddress(address);
 			if (newHandle == -1)
@@ -1810,8 +1826,8 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetFw(int32_t fw_offset) {
 		stack_entry++;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RET(total_off < fw_offset, "accessing address beyond stack's tail", RuntimeScriptValue);
-	CC_ERROR_IF_RET(total_off > fw_offset, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue);
+	CC_ERROR_IF_RETVAL(total_off < fw_offset, "accessing address beyond stack's tail", RuntimeScriptValue);
+	CC_ERROR_IF_RETVAL(total_off > fw_offset, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	return stack_ptr;
@@ -1824,12 +1840,12 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
 		stack_entry--;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RET(total_off < rw_offset, "accessing address before stack's head", RuntimeScriptValue);
+	CC_ERROR_IF_RETVAL(total_off < rw_offset, "accessing address before stack's head", RuntimeScriptValue);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	stack_ptr.IValue += total_off - rw_offset; // possibly offset to the mid-array
 	// Could be accessing array element, so state error only if stack entry does not refer to data array
-	CC_ERROR_IF_RET((total_off > rw_offset) && (stack_entry->Type != kScValData), "stack offset backward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue)
+	CC_ERROR_IF_RETVAL((total_off > rw_offset) && (stack_entry->Type != kScValData), "stack offset backward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue)
 	return stack_ptr;
 }
 
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 5b79e9d337d..3ed987a5ecb 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -255,6 +255,14 @@ public:
 		Size = 4;
 		return *this;
 	}
+	inline RuntimeScriptValue &SetDynamicObject(ScriptValueType type, void *object, ICCDynamicObject *manager) {
+		Type = type;
+		IValue = 0;
+		Ptr = (char *)object;
+		DynMgr = manager;
+		Size = 4;
+		return *this;
+	}
 	inline RuntimeScriptValue &SetStaticFunction(ScriptAPIFunction *pfn) {
 		Type = kScValStaticFunction;
 		methodName.clear();


Commit: 4f32ca32cbd412adb2172590e158fa61d8b9b76a
    https://github.com/scummvm/scummvm/commit/4f32ca32cbd412adb2172590e158fa61d8b9b76a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: refactored to alloc arg values per opcode

Although this means more code lines, it reduces redundant variable
initialization and assertions, which may lead to slightly optimized execution.
In addition, this change makes argument read explicit, under each individual opcode.
>From upstream 777cd4a4effd1c1785582e847f9fcdadef5fe7e0

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 5c5a00bc580..12c7e83139b 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -589,18 +589,6 @@ int ccInstance::Run(int32_t curpc) {
 		/* End ReadOperation */
 		//=====================================================================
 
-		// save the arguments for quick access
-		RuntimeScriptValue &arg1 = codeOp.Args[0];
-		RuntimeScriptValue &arg2 = codeOp.Args[1];
-		RuntimeScriptValue &arg3 = codeOp.Args[2];
-		RuntimeScriptValue &reg1 =
-		    registers[arg1.IValue >= 0 && arg1.IValue < CC_NUM_REGISTERS ? arg1.IValue : 0];
-		RuntimeScriptValue &reg2 =
-		    registers[arg2.IValue >= 0 && arg2.IValue < CC_NUM_REGISTERS ? arg2.IValue : 0];
-
-		const char *direct_ptr1;
-		const char *direct_ptr2;
-
 #if (DEBUG_CC_EXEC)
 		if (dump_opcodes) {
 			DumpInstruction(codeOp);
@@ -609,33 +597,40 @@ int ccInstance::Run(int32_t curpc) {
 
 		switch (codeOp.Instruction.Code) {
 		case SCMD_LINENUM:
-			line_number = arg1.IValue;
-			_G(currentline) = arg1.IValue;
+			line_number = codeOp.Arg1i();
+			_G(currentline) = line_number;
 			if (_G(new_line_hook))
 				_G(new_line_hook)(this, _G(currentline));
 			break;
-		case SCMD_ADD:
+		case SCMD_ADD: {
+			const auto arg_reg = codeOp.Arg1i();
+			const auto arg_lit = codeOp.Arg2i();
+			auto &reg1 = registers[arg_reg];
 			// If the register is SREG_SP, we are allocating new variable on the stack
-			if (arg1.IValue == SREG_SP) {
+			if (arg_reg == SREG_SP) {
 				// Only allocate new data if current stack entry is invalid;
 				// in some cases this may be advancing over value that was written by MEMWRITE*
 				// FIXME: this is bad, but seemed to be the way to separate PushValue and PushData
 				// find if it's possible to do this in a uniform way (always same operation),
 				// and don't rely on stack entries being valid/invalid beyond the stack ptr.
-				ASSERT_STACK_SPACE_AVAILABLE(1, arg2.IValue);
+				ASSERT_STACK_SPACE_AVAILABLE(1, arg_lit);
 				if (reg1.RValue->IsValid()) {
 					// TODO: perhaps should add a flag here to ensure this happens only after MEMWRITE-ing to stack
 					registers[SREG_SP].RValue++;
 					stackdata_ptr += sizeof(int32_t); // formality, to keep data ptr consistent
 				} else {
-					PushDataToStack(arg2.IValue);
+					PushDataToStack(arg_lit);
 					ASSERT_CC_ERROR();
 				}
 			} else {
-				reg1.IValue += arg2.IValue;
+				reg1.IValue += arg_lit;
 			}
 			break;
-		case SCMD_SUB:
+		}
+		case SCMD_SUB: {
+			const auto arg_reg = codeOp.Arg1i();
+			const auto arg_lit = codeOp.Arg2i();
+			auto &reg1 = registers[arg_reg];
 			if (reg1.Type == kScValStackPtr) {
 				// If this is SREG_SP, this is stack pop, which frees local variables;
 				// Other than SREG_SP this may be AGS 2.x method to offset stack in SREG_MAR;
@@ -643,43 +638,50 @@ int ccInstance::Run(int32_t curpc) {
 				// // AGS 2.x games also perform relative stack access by copying SREG_SP to SREG_MAR
 				// // and then subtracting from that.
 				// FIXME: try to do this in uniform way, call same func, save result in reg1
-				if (arg1.IValue == SREG_SP) {
-					PopDataFromStack(arg2.IValue);
+				if (arg_reg == SREG_SP) {
+					PopDataFromStack(arg_lit);
 				} else {
 					// This is practically LOADSPOFFS
-					reg1 = GetStackPtrOffsetRw(arg2.IValue);
+					reg1 = GetStackPtrOffsetRw(arg_lit);
 				}
 				ASSERT_CC_ERROR();
 			} else {
-				reg1.IValue -= arg2.IValue;
+				reg1.IValue -= arg_lit;
 			}
 			break;
-		case SCMD_REGTOREG:
+		}
+		case SCMD_REGTOREG: {
+			const auto &reg1 = registers[codeOp.Arg1i()];
+			auto &reg2 = registers[codeOp.Arg2i()];
 			reg2 = reg1;
 			break;
-		case SCMD_WRITELIT:
+		}
+		case SCMD_WRITELIT: {
 			// Take the data address from reg[MAR] and copy there arg1 bytes from arg2 address
 			//
 			// NOTE: since it reads directly from arg2 (which originally was
 			// long, or rather int32 due x32 build), written value may normally
 			// be only up to 4 bytes large;
 			// I guess that's an obsolete way to do WRITE, WRITEW and WRITEB
-			switch (arg1.IValue) {
+			const auto arg_size = codeOp.Arg1i();
+			const auto &arg_value = codeOp.Arg2();
+			switch (arg_size) {
 			case sizeof(char):
-				registers[SREG_MAR].WriteByte(arg2.IValue);
+				registers[SREG_MAR].WriteByte(arg_value.IValue);
 				break;
 			case sizeof(int16_t):
-				registers[SREG_MAR].WriteInt16(arg2.IValue);
+				registers[SREG_MAR].WriteInt16(arg_value.IValue);
 				break;
 			case sizeof(int32_t):
 				// We do not know if this is math integer or some pointer, etc
-				registers[SREG_MAR].WriteValue(arg2);
+				registers[SREG_MAR].WriteValue(arg_value);
 				break;
 			default:
-				warning("unexpected data size for WRITELIT op: %d", arg1.IValue);
+				warning("unexpected data size for WRITELIT op: %d", arg_size);
 				break;
 			}
 			break;
+		}
 		case SCMD_RET: {
 			if (loopIterationCheckDisabled > 0)
 				loopIterationCheckDisabled--;
@@ -695,85 +697,143 @@ int ccInstance::Run(int32_t curpc) {
 			POP_CALL_STACK;
 			continue; // continue so that the PC doesn't get overwritten
 		}
-		case SCMD_LITTOREG:
-			reg1 = arg2;
+		case SCMD_LITTOREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &arg_value = codeOp.Arg2();
+			reg1 = arg_value;
 			break;
-		case SCMD_MEMREAD:
+		}
+		case SCMD_MEMREAD: {
 			// Take the data address from reg[MAR] and copy int32_t to reg[arg1]
+			auto &reg1 = registers[codeOp.Arg1i()];
 			reg1 = registers[SREG_MAR].ReadValue();
 			break;
-		case SCMD_MEMWRITE:
+		}
+		case SCMD_MEMWRITE: {
 			// Take the data address from reg[MAR] and copy there int32_t from reg[arg1]
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			registers[SREG_MAR].WriteValue(reg1);
 			break;
-		case SCMD_LOADSPOFFS:
-			registers[SREG_MAR] = GetStackPtrOffsetRw(arg1.IValue);
+		}
+		case SCMD_LOADSPOFFS: {
+			const auto arg_off = codeOp.Arg1i();
+			registers[SREG_MAR] = GetStackPtrOffsetRw(arg_off);
 			ASSERT_CC_ERROR();
 			break;
-
-		// 64 bit: Force 32 bit math
-		case SCMD_MULREG:
+		}
+		case SCMD_MULREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue * reg2.IValue);
 			break;
-		case SCMD_DIVREG:
+		}
+		case SCMD_DIVREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			if (reg2.IValue == 0) {
 				cc_error("!Integer divide by zero");
 				return -1;
 			}
 			reg1.SetInt32(reg1.IValue / reg2.IValue);
 			break;
-		case SCMD_ADDREG:
+		}
+		case SCMD_ADDREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			// This may be pointer arithmetics, in which case IValue stores offset from base pointer
 			reg1.IValue += reg2.IValue;
 			break;
-		case SCMD_SUBREG:
+		}
+		case SCMD_SUBREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			// This may be pointer arithmetics, in which case IValue stores offset from base pointer
 			reg1.IValue -= reg2.IValue;
 			break;
-		case SCMD_BITAND:
+		}
+		case SCMD_BITAND: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue & reg2.IValue);
 			break;
-		case SCMD_BITOR:
+		}
+		case SCMD_BITOR: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue | reg2.IValue);
 			break;
-		case SCMD_ISEQUAL:
+		}
+		case SCMD_ISEQUAL: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1 == reg2);
 			break;
-		case SCMD_NOTEQUAL:
+		}
+		case SCMD_NOTEQUAL: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1 != reg2);
 			break;
-		case SCMD_GREATER:
+		}
+		case SCMD_GREATER: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue > reg2.IValue);
 			break;
-		case SCMD_LESSTHAN:
+		}
+		case SCMD_LESSTHAN: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue < reg2.IValue);
 			break;
-		case SCMD_GTE:
+		}
+		case SCMD_GTE: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue >= reg2.IValue);
 			break;
-		case SCMD_LTE:
+		}
+		case SCMD_LTE: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue <= reg2.IValue);
 			break;
-		case SCMD_AND:
+		}
+		case SCMD_AND: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue && reg2.IValue);
 			break;
-		case SCMD_OR:
+		}
+		case SCMD_OR: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32AsBool(reg1.IValue || reg2.IValue);
 			break;
-		case SCMD_XORREG:
+		}
+		case SCMD_XORREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue ^ reg2.IValue);
 			break;
-		case SCMD_MODREG:
+		}
+		case SCMD_MODREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			if (reg2.IValue == 0) {
 				cc_error("!Integer divide by zero");
 				return -1;
 			}
 			reg1.SetInt32(reg1.IValue % reg2.IValue);
 			break;
-		case SCMD_NOTREG:
+		}
+		case SCMD_NOTREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1 = !(reg1);
 			break;
-		case SCMD_CALL:
+		}
+		case SCMD_CALL: {
 			// Call another function within same script, just save PC
 			// and continue from there
 			if (curnest >= MAXNEST - 1) {
@@ -786,6 +846,7 @@ int ccInstance::Run(int32_t curpc) {
 			ASSERT_STACK_SPACE_VALS(1);
 			PushValueToStack(RuntimeScriptValue().SetInt32(pc + codeOp.ArgCount + 1));
 
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			if (thisbase[curnest] == 0)
 				pc = reg1.IValue;
 			else {
@@ -802,44 +863,62 @@ int ccInstance::Run(int32_t curpc) {
 			thisbase[curnest] = 0;
 			funcstart[curnest] = pc;
 			continue; // continue so that the PC doesn't get overwritten
-		case SCMD_MEMREADB:
+		}
+		case SCMD_MEMREADB: {
 			// Take the data address from reg[MAR] and copy byte to reg[arg1]
+			auto &reg1 = registers[codeOp.Arg1i()];
 			reg1.SetUInt8(registers[SREG_MAR].ReadByte());
 			break;
-		case SCMD_MEMREADW:
+		}
+		case SCMD_MEMREADW: {
 			// Take the data address from reg[MAR] and copy int16_t to reg[arg1]
+			auto &reg1 = registers[codeOp.Arg1i()];
 			reg1.SetInt16(registers[SREG_MAR].ReadInt16());
 			break;
-		case SCMD_MEMWRITEB:
+		}
+		case SCMD_MEMWRITEB: {
 			// Take the data address from reg[MAR] and copy there byte from reg[arg1]
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			registers[SREG_MAR].WriteByte(reg1.IValue);
 			break;
-		case SCMD_MEMWRITEW:
+		}
+		case SCMD_MEMWRITEW: {
 			// Take the data address from reg[MAR] and copy there int16_t from reg[arg1]
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			registers[SREG_MAR].WriteInt16(reg1.IValue);
 			break;
-		case SCMD_JZ:
+		}
+		case SCMD_JZ: {
+			const auto arg_lit = codeOp.Arg1i();
 			if (registers[SREG_AX].IsNull())
-				pc += arg1.IValue;
+				pc += arg_lit;
 			break;
-		case SCMD_JNZ:
+		}
+		case SCMD_JNZ: {
+			const auto arg_lit = codeOp.Arg1i();
 			if (!registers[SREG_AX].IsNull())
-				pc += arg1.IValue;
+				pc += arg_lit;
 			break;
-		case SCMD_PUSHREG:
+		}
+		case SCMD_PUSHREG: {
 			// Push reg[arg1] value to the stack
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			ASSERT_STACK_SPACE_VALS(1);
 			PushValueToStack(reg1);
 			break;
-		case SCMD_POPREG:
+		}
+		case SCMD_POPREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
 			ASSERT_STACK_SIZE(1);
 			reg1 = PopValueFromStack();
 			break;
-		case SCMD_JMP:
-			pc += arg1.IValue;
+		}
+		case SCMD_JMP: {
+			const auto arg_lit = codeOp.Arg1i();
+			pc += arg_lit;
 
 			// Make sure it's not stuck in a While loop
-			if (arg1.IValue < 0) {
+			if (arg_lit < 0) {
 				++loopIterations;
 				if (flags & INSTF_RUNNING) {
 					// was notified still running, don't do anything
@@ -858,17 +937,25 @@ int ccInstance::Run(int32_t curpc) {
 				}
 			}
 			break;
-		case SCMD_MUL:
-			reg1.IValue *= arg2.IValue;
+		}
+		case SCMD_MUL: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_lit = codeOp.Arg2i();
+			reg1.IValue *= arg_lit;
 			break;
-		case SCMD_CHECKBOUNDS:
+		}
+		case SCMD_CHECKBOUNDS: {
+			const auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_lit = codeOp.Arg2i();
 			if ((reg1.IValue < 0) ||
-			        (reg1.IValue >= arg2.IValue)) {
-				cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue, arg2.IValue - 1);
+				(reg1.IValue >= arg_lit)) {
+				cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue, arg_lit - 1);
 				return -1;
 			}
 			break;
+		}
 		case SCMD_DYNAMICBOUNDS: {
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			// TODO: test reg[MAR] type here;
 			// That might be dynamic object, but also a non-managed dynamic array, "allocated"
 			// on global or local memspace (buffer)
@@ -890,6 +977,7 @@ int ccInstance::Run(int32_t curpc) {
 		// 64 bit: Handles are always 32 bit values. They are not C pointer.
 
 		case SCMD_MEMREADPTR: {
+			auto &reg1 = registers[codeOp.Arg1i()];
 			int32_t handle = registers[SREG_MAR].ReadInt32();
 			// FIXME: make pool return a ready RuntimeScriptValue with these set?
 			// or another struct, which may be assigned to RSV
@@ -901,7 +989,7 @@ int ccInstance::Run(int32_t curpc) {
 			break;
 		}
 		case SCMD_MEMWRITEPTR: {
-
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			int32_t handle = registers[SREG_MAR].ReadInt32();
 			const char *address;
 			switch (reg1.Type) {
@@ -937,6 +1025,7 @@ int ccInstance::Run(int32_t curpc) {
 		}
 		case SCMD_MEMINITPTR: {
 			const char *address;
+			const auto &reg1 = registers[codeOp.Arg1i()];
 
 			switch (reg1.Type) {
 			case kScValStaticArray:
@@ -993,19 +1082,24 @@ int ccInstance::Run(int32_t curpc) {
 				return -1;
 			}
 			break;
-		case SCMD_CHECKNULLREG:
+		case SCMD_CHECKNULLREG: {
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			if (reg1.IsNull()) {
 				cc_error("!Null string referenced");
 				return -1;
 			}
 			break;
-		case SCMD_NUMFUNCARGS:
-			num_args_to_func = arg1.IValue;
+		}
+		case SCMD_NUMFUNCARGS: {
+			const auto arg_lit = codeOp.Arg1i();
+			num_args_to_func = arg_lit;
 			break;
+		}
 		case SCMD_CALLAS: {
 			PUSH_CALL_STACK;
 
 			// Call to a function in another script
+			const auto &reg1 = registers[codeOp.Arg1i()];
 
 			// If there are nested CALLAS calls, the stack might
 			// contain 2 calls worth of parameters, so only
@@ -1060,6 +1154,8 @@ int ccInstance::Run(int32_t curpc) {
 		}
 		case SCMD_CALLEXT: {
 			// Call to a real 'C' code function
+			const auto &reg1 = registers[codeOp.Arg1i()];
+
 			was_just_callas = -1;
 			if (num_args_to_func < 0) {
 				num_args_to_func = func_callstack.Count;
@@ -1124,19 +1220,24 @@ int ccInstance::Run(int32_t curpc) {
 			num_args_to_func = -1;
 			break;
 		}
-		case SCMD_PUSHREAL:
+		case SCMD_PUSHREAL: {
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			PushToFuncCallStack(func_callstack, reg1);
 			break;
-		case SCMD_SUBREALSTACK:
-			PopFromFuncCallStack(func_callstack, arg1.IValue);
+		}
+		case SCMD_SUBREALSTACK: {
+			const auto arg_lit = codeOp.Arg1i();
+			PopFromFuncCallStack(func_callstack, arg_lit);
 			if (was_just_callas >= 0) {
-				ASSERT_STACK_SIZE(arg1.IValue);
-				PopValuesFromStack(arg1.IValue);
+				ASSERT_STACK_SIZE(arg_lit);
+				PopValuesFromStack(arg_lit);
 				was_just_callas = -1;
 			}
 			break;
-		case SCMD_CALLOBJ:
+		}
+		case SCMD_CALLOBJ: {
 			// set the OP register
+			const auto &reg1 = registers[codeOp.Arg1i()];
 			if (reg1.IsNull()) {
 				cc_error("!Null pointer referenced");
 				return -1;
@@ -1168,113 +1269,170 @@ int ccInstance::Run(int32_t curpc) {
 			}
 			next_call_needs_object = 1;
 			break;
-		case SCMD_SHIFTLEFT:
+		}
+		case SCMD_SHIFTLEFT: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue << reg2.IValue);
 			break;
-		case SCMD_SHIFTRIGHT:
+		}
+		case SCMD_SHIFTRIGHT: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetInt32(reg1.IValue >> reg2.IValue);
 			break;
-		case SCMD_THISBASE:
-			thisbase[curnest] = arg1.IValue;
+		}
+		case SCMD_THISBASE: {
+			const auto arg_lit = codeOp.Arg1i();
+			thisbase[curnest] = arg_lit;
 			break;
+		}
 		case SCMD_NEWARRAY: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_elsize = codeOp.Arg2i();
+			const auto arg_managed = codeOp.Arg3().GetAsBool();
 			int numElements = reg1.IValue;
 			if (numElements < 1) {
 				cc_error("invalid size for dynamic array; requested: %d, range: 1..%d", numElements, INT32_MAX);
 				return -1;
 			}
-			DynObjectRef ref = _GP(globalDynamicArray).Create(numElements, arg2.IValue, arg3.GetAsBool());
+			DynObjectRef ref = _GP(globalDynamicArray).Create(numElements, arg_elsize, arg_managed);
 			reg1.SetDynamicObject(ref.second, &_GP(globalDynamicArray));
 			break;
 		}
 		case SCMD_NEWUSEROBJECT: {
-			const int32_t size = arg2.IValue;
-			if (size < 0) {
-				cc_error("Invalid size for user object; requested: %d (or %d), range: 0..%d", (uint32_t)size, size, INT_MAX);
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_size = codeOp.Arg2i();
+			if (arg_size < 0) {
+				cc_error("Invalid size for user object; requested: %d (or %d), range: 0..%d", arg_size, arg_size, INT_MAX);
 				return -1;
 			}
-			ScriptUserObject *suo = ScriptUserObject::CreateManaged(size);
+			ScriptUserObject *suo = ScriptUserObject::CreateManaged(arg_size);
 			reg1.SetDynamicObject(suo, suo);
 			break;
 		}
-		case SCMD_FADD:
-			reg1.SetFloat(reg1.FValue + arg2.IValue); // arg2 was used as int here originally
+		case SCMD_FADD: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_lit = codeOp.Arg2i();
+			reg1.SetFloat(reg1.FValue + arg_lit); // arg2 was used as int here originally
 			break;
-		case SCMD_FSUB:
-			reg1.SetFloat(reg1.FValue - arg2.IValue); // arg2 was used as int here originally
+		}
+		case SCMD_FSUB: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto arg_lit = codeOp.Arg2i();
+			reg1.SetFloat(reg1.FValue - arg_lit); // arg2 was used as int here originally
 			break;
-		case SCMD_FMULREG:
+		}
+		case SCMD_FMULREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloat(reg1.FValue * reg2.FValue);
 			break;
-		case SCMD_FDIVREG:
+		}
+		case SCMD_FDIVREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			if (reg2.FValue == 0.0) {
 				cc_error("!Floating point divide by zero");
 				return -1;
 			}
 			reg1.SetFloat(reg1.FValue / reg2.FValue);
 			break;
-		case SCMD_FADDREG:
+		}
+		case SCMD_FADDREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloat(reg1.FValue + reg2.FValue);
 			break;
-		case SCMD_FSUBREG:
+		}
+		case SCMD_FSUBREG: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloat(reg1.FValue - reg2.FValue);
 			break;
-		case SCMD_FGREATER:
+		}
+		case SCMD_FGREATER: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloatAsBool(reg1.FValue > reg2.FValue);
 			break;
-		case SCMD_FLESSTHAN:
+		}
+		case SCMD_FLESSTHAN: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloatAsBool(reg1.FValue < reg2.FValue);
 			break;
-		case SCMD_FGTE:
+		}
+		case SCMD_FGTE: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloatAsBool(reg1.FValue >= reg2.FValue);
 			break;
-		case SCMD_FLTE:
+		}
+		case SCMD_FLTE: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1.SetFloatAsBool(reg1.FValue <= reg2.FValue);
 			break;
-		case SCMD_ZEROMEMORY:
+		}
+		case SCMD_ZEROMEMORY: {
+			const auto arg_size = codeOp.Arg1i();
 			// Check if we are zeroing at stack tail
 			if (registers[SREG_MAR] == registers[SREG_SP]) {
 				// creating a local variable -- check the stack to ensure no mem overrun
-				ASSERT_STACK_SPACE_BYTES(arg1.IValue);
+				ASSERT_STACK_SPACE_BYTES(arg_size);
 				// NOTE: according to compiler's logic, this is always followed
 				// by SCMD_ADD, and that is where the data is "allocated", here we
 				// just clean the place.
-				memset(stackdata_ptr, 0, arg1.IValue);
+				memset(stackdata_ptr, 0, arg_size);
 			} else {
 				cc_error("internal error: stack tail address expected on SCMD_ZEROMEMORY instruction, reg[MAR] type is %d",
 				         registers[SREG_MAR].Type);
 				return -1;
 			}
 			break;
-		case SCMD_CREATESTRING:
+		}
+		case SCMD_CREATESTRING: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			// FIXME: provide a dummy impl to avoid this?
+			// why arrays can be created using global mgr and strings not?
 			if (_G(stringClassImpl) == nullptr) {
 				cc_error("No string class implementation set, but opcode was used");
 				return -1;
+			} else {
+				const char *ptr = (const char *)reg1.GetDirectPtr();
+				reg1.SetDynamicObject(
+					_G(stringClassImpl)->CreateString(ptr).second,
+					&_GP(myScriptStringImpl));
 			}
-			direct_ptr1 = (const char *)reg1.GetDirectPtr();
-			reg1.SetDynamicObject(
-			    _G(stringClassImpl)->CreateString(direct_ptr1).second,
-			    &_GP(myScriptStringImpl));
 			break;
-		case SCMD_STRINGSEQUAL:
+		}
+		case SCMD_STRINGSEQUAL: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			if ((reg1.IsNull()) || (reg2.IsNull())) {
 				cc_error("!Null pointer referenced");
 				return -1;
+			} else {
+				const char *ptr1 = (const char *)reg1.GetDirectPtr();
+				const char *ptr2 = (const char *)reg2.GetDirectPtr();
+				reg1.SetInt32AsBool(strcmp(ptr1, ptr2) == 0);
 			}
-			direct_ptr1 = (const char *)reg1.GetDirectPtr();
-			direct_ptr2 = (const char *)reg2.GetDirectPtr();
-			reg1.SetInt32AsBool(strcmp(direct_ptr1, direct_ptr2) == 0);
-
 			break;
-		case SCMD_STRINGSNOTEQ:
+		}
+		case SCMD_STRINGSNOTEQ: {
+			auto &reg1 = registers[codeOp.Arg1i()];
+			const auto &reg2 = registers[codeOp.Arg2i()];
 			if ((reg1.IsNull()) || (reg2.IsNull())) {
 				cc_error("!Null pointer referenced");
 				return -1;
+			} else {
+				const char *ptr1 = (const char *)reg1.GetDirectPtr();
+				const char *ptr2 = (const char *)reg2.GetDirectPtr();
+				reg1.SetInt32AsBool(strcmp(ptr1, ptr2) != 0);
 			}
-			direct_ptr1 = (const char *)reg1.GetDirectPtr();
-			direct_ptr2 = (const char *)reg2.GetDirectPtr();
-			reg1.SetInt32AsBool(strcmp(direct_ptr1, direct_ptr2) != 0);
 			break;
+		}
 		case SCMD_LOOPCHECKOFF:
 			if (loopIterationCheckDisabled == 0)
 				loopIterationCheckDisabled++;
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index b4b8eec5eed..1bfc5791bdd 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -56,23 +56,27 @@ using namespace AGS;
 #define DEBUG_CC_EXEC (AGS_PLATFORM_DEBUG)
 
 struct ScriptInstruction {
-	ScriptInstruction() {
-		Code = 0;
-		InstanceId = 0;
-	}
+	ScriptInstruction() = default;
+	ScriptInstruction(int code, int instid) : Code(code), InstanceId(instid) {}
 
-	int32_t Code;
-	int32_t InstanceId;
+	int32_t Code = 0;
+	int32_t InstanceId = 0;
 };
 
 struct ScriptOperation {
-	ScriptOperation() {
-		ArgCount = 0;
-	}
-
 	ScriptInstruction   Instruction;
 	RuntimeScriptValue  Args[MAX_SCMD_ARGS];
-	int                 ArgCount;
+	int                 ArgCount = 0;
+
+	// Helper functions for clarity of intent:
+	// returns argN, 1-based
+	inline const RuntimeScriptValue &Arg1() const { return Args[0]; }
+	inline const RuntimeScriptValue &Arg2() const { return Args[1]; }
+	inline const RuntimeScriptValue &Arg3() const { return Args[2]; }
+	// returns argN as a integer literal
+	inline int Arg1i() const { return Args[0].IValue; }
+	inline int Arg2i() const { return Args[1].IValue; }
+	inline int Arg3i() const { return Args[2].IValue; }
 };
 
 struct ScriptVariable {


Commit: a0a739d49717d043751ba35d2d450afef07a03be
    https://github.com/scummvm/scummvm/commit/a0a739d49717d043751ba35d2d450afef07a03be
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: another refactor for RuntimeScriptValue::Read/WriteValue funcs

from upstream f4115e36ad769b060443170ae8199002161561e7

Changed paths:
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h


diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index 7554530693a..d103b72f82c 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -158,43 +158,6 @@ bool RuntimeScriptValue::WriteInt32(int32_t val) {
 	return true;
 }
 
-// Notice, that there are only two valid cases when a pointer may be written:
-// when the destination is a stack entry or global variable of free type
-// (not kScValData type).
-// In any other case, only the numeric value (integer/float) will be written.
-bool RuntimeScriptValue::WriteValue(const RuntimeScriptValue &rval) {
-	if (this->Type == kScValStackPtr) {
-		if (RValue->Type == kScValData) {
-			*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue) = rval.IValue;
-		} else {
-			// NOTE: we cannot just WriteValue here because when an integer
-			// is pushed to the stack, script assumes that it is always 4
-			// bytes and uses that size when calculating offsets to local
-			// variables;
-			// Therefore if pushed value is of integer type, we should rather
-			// act as WriteInt32 (for int8, int16 and int32).
-			if (rval.Type == kScValInteger) {
-				RValue->SetInt32(rval.IValue);
-			} else {
-				*RValue = rval;
-			}
-		}
-	} else if (this->Type == kScValGlobalVar) {
-		if (RValue->Type == kScValData) {
-			Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, rval.IValue);
-		} else {
-			*RValue = rval;
-		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
-		this->StcMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
-	} else if (this->Type == kScValDynamicObject) {
-		this->DynMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
-	} else {
-		*((int32_t *)this->GetPtrWithOffset()) = rval.IValue;
-	}
-	return true;
-}
-
 RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
 	if (Type == kScValGlobalVar || Type == kScValStackPtr) {
 		int ival = IValue;
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 3ed987a5ecb..76dcdfc337a 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -320,39 +320,91 @@ public:
 	// as 32-bit value here. There should be a solution to distinct these cases and
 	// store value differently, otherwise it won't work for 64-bit build.
 	inline RuntimeScriptValue ReadValue() const {
-		RuntimeScriptValue rval;
 		switch (this->Type) {
 		case kScValStackPtr: {
-			if (RValue->Type == kScValData) {
-				rval.SetInt32(*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue));
-			} else {
-				rval = *RValue;
+			// FIXME: join the kScValStackPtr with kScValData using some flag?
+			switch (RValue->Type) {
+			case kScValData:
+				// read from the stack memory buffer
+				return RuntimeScriptValue().SetInt32(*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue));
+			default:
+				// return the stack entry itself
+				return *RValue;
 			}
 		}
-		break;
 		case kScValGlobalVar: {
-			if (RValue->Type == kScValData) {
-				rval.SetInt32(AGS::Shared::Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue));
-			} else {
-				rval = *RValue;
+			// FIXME: join the kScValGlobalVar with kScValData using some flag?
+			switch (RValue->Type) {
+			case kScValData:
+				// read from the global memory buffer
+				return RuntimeScriptValue().SetInt32(AGS::Shared::Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue));
+			default:
+				// return the gvar entry itself
+				return *RValue;
 			}
 		}
-		break;
+		case kScValStaticObject:
+		case kScValStaticArray:
+			return RuntimeScriptValue().SetInt32(this->StcMgr->ReadInt32(this->Ptr, this->IValue));
+		case kScValDynamicObject:
+			return RuntimeScriptValue().SetInt32(this->DynMgr->ReadInt32(this->Ptr, this->IValue));
+		default:
+			return RuntimeScriptValue().SetInt32(*(int32_t *)this->GetPtrWithOffset());
+		}
+	}
+
+	// Notice, that there are only two valid cases when a pointer may be written:
+	// when the destination is a stack entry or global variable of free type
+	// (not kScValData type).
+	// In any other case, only the numeric value (integer/float) will be written.
+	inline void WriteValue(const RuntimeScriptValue &rval) {
+		switch (this->Type) {
+		case kScValStackPtr: {
+			// FIXME: join the kScValStackPtr with kScValData using some flag?
+			switch (RValue->Type) {
+			case kScValData:
+				// write into the stack memory buffer
+				*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue) = rval.IValue;
+				break;
+			default:
+				// write into the stack entry
+				*RValue = rval;
+				// On stack we assume each item has at least 4 bytes (with exception
+				// of arrays - kScValData). This is why we fixup the size in case
+				// the assigned value is less (char, int16).
+				RValue->Size = 4;
+				break;
+			}
+			break;
+		}
+		case kScValGlobalVar: {
+			// FIXME: join the kScValGlobalVar with kScValData using some flag?
+			switch (RValue->Type) {
+			case kScValData:
+				// write into the global memory buffer
+				AGS::Shared::Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, rval.IValue);
+				break;
+			default:
+				// write into the gvar entry
+				*RValue = rval;
+				break;
+			}
+			break;
+		}
 		case kScValStaticObject:
 		case kScValStaticArray: {
-			rval.SetInt32(this->StcMgr->ReadInt32(this->Ptr, this->IValue));
+			this->StcMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
+			break;
 		}
-		break;
 		case kScValDynamicObject: {
-			rval.SetInt32(this->DynMgr->ReadInt32(this->Ptr, this->IValue));
+			this->DynMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
+			break;
 		}
-		break;
 		default: {
-			// 64 bit: Memory reads are still 32 bit
-			rval.SetInt32(*(int32_t *)this->GetPtrWithOffset());
+			*((int32_t *)this->GetPtrWithOffset()) = rval.IValue;
+			break;
 		}
 		}
-		return rval;
 	}
 
 	Plugins::PluginMethod pluginMethod() const {
@@ -368,7 +420,6 @@ public:
 	bool        WriteByte(uint8_t val);
 	bool        WriteInt16(int16_t val);
 	bool        WriteInt32(int32_t val);
-	bool        WriteValue(const RuntimeScriptValue &rval);
 
 	// Convert to most simple pointer type by resolving RValue ptrs and applying offsets;
 	// non pointer types are left unmodified


Commit: 57d7544f082ece71463cd04fd0de8847f3def9f2
    https://github.com/scummvm/scummvm/commit/57d7544f082ece71463cd04fd0de8847f3def9f2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: hid couple more frequent asserts under DEBUG

>From upstream f52c96c64d0508addd440a7060ea5e939ebc6a67

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 12c7e83139b..a3ca5c64f2c 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -288,24 +288,24 @@ void ccInstance::AbortAndDestroy() {
 // returns failure on error
 #if (DEBUG_CC_EXEC)
 
-#define CC_ERROR_IF(COND, ERROR) \
+#define CC_ERROR_IF(COND, ERROR, ...) \
 	if (COND) \
 	{ \
-		cc_error(ERROR); \
+		cc_error(ERROR, ##__VA_ARGS__); \
 		return; \
 	}
 
-#define CC_ERROR_IF_RETCODE(COND, ERROR) \
+#define CC_ERROR_IF_RETCODE(COND, ERROR, ...) \
 	if (COND) \
 	{ \
-		cc_error(ERROR); \
+		cc_error(ERROR, ##__VA_ARGS__); \
 		return -1; \
 	}
 
-#define CC_ERROR_IF_RETVAL(COND, ERROR, T) \
+#define CC_ERROR_IF_RETVAL(COND, T, ERROR, ...) \
 	if (COND) \
 	{ \
-		cc_error(ERROR); \
+		cc_error(ERROR, ##__VA_ARGS__); \
 		return T(); \
 	}
 
@@ -317,9 +317,9 @@ void ccInstance::AbortAndDestroy() {
 
 #else
 
-#define CC_ERROR_IF(COND, ERROR)
-#define CC_ERROR_IF_RETCODE(COND, ERROR)
-#define CC_ERROR_IF_RETVAL(COND, ERROR, T)
+#define CC_ERROR_IF(COND, ERROR, ...)
+#define CC_ERROR_IF_RETCODE(COND, ERROR, ...)
+#define CC_ERROR_IF_RETVAL(COND, T, ERROR, ...)
 #define ASSERT_CC_ERROR()
 
 #endif // DEBUG_CC_EXEC
@@ -503,7 +503,7 @@ int ccInstance::Run(int32_t curpc) {
 	ScriptOperation codeOp;
 	FunctionCallStack func_callstack;
 #if DEBUG_CC_EXEC
-	const bool dump_opcodes = ccGetOption(SCOPT_DEBUGRUN) ||
+	const bool dump_opcodes = (ccGetOption(SCOPT_DEBUGRUN) != 0) ||
 							  (gDebugLevel > 0 && DebugMan.isDebugChannelEnabled(::AGS::kDebugScript));
 #endif
 	int loopIterationCheckDisabled = 0;
@@ -529,16 +529,13 @@ int ccInstance::Run(int32_t curpc) {
 		codeOp.Instruction.InstanceId   = (codeOp.Instruction.Code >> INSTANCE_ID_SHIFT) & INSTANCE_ID_MASK;
 		codeOp.Instruction.Code        &= INSTANCE_ID_REMOVEMASK; // now this is pure instruction code
 
-		if (codeOp.Instruction.Code < 0 || codeOp.Instruction.Code >= CC_NUM_SCCMDS) {
-			cc_error("invalid instruction %d found in code stream", codeOp.Instruction.Code);
-			return -1;
-		}
+		CC_ERROR_IF_RETCODE((codeOp.Instruction.Code < 0 || codeOp.Instruction.Code >= CC_NUM_SCCMDS),
+							"invalid instruction %d found in code stream", codeOp.Instruction.Code);
 
-		codeOp.ArgCount = (*g_commands)[codeOp.Instruction.Code].ArgCount;
-		if (pc + codeOp.ArgCount >= codeInst->codesize) {
-			cc_error("unexpected end of code data (%d; %d)", pc + codeOp.ArgCount, codeInst->codesize);
-			return -1;
-		}
+		codeOp.ArgCount = (*g_commands)[codeOp.Instruction.Code].ArgCount & 0x3;
+
+		CC_ERROR_IF_RETCODE(pc + codeOp.ArgCount >= codeInst->codesize,
+							"unexpected end of code data (%d; %d)", pc + codeOp.ArgCount, codeInst->codesize);
 
 		int pc_at = pc + 1;
 		for (int i = 0; i < codeOp.ArgCount; ++i, ++pc_at) {
@@ -1984,8 +1981,8 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetFw(int32_t fw_offset) {
 		stack_entry++;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RETVAL(total_off < fw_offset, "accessing address beyond stack's tail", RuntimeScriptValue);
-	CC_ERROR_IF_RETVAL(total_off > fw_offset, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue);
+	CC_ERROR_IF_RETVAL(total_off < fw_offset, RuntimeScriptValue, "accessing address beyond stack's tail");
+	CC_ERROR_IF_RETVAL(total_off > fw_offset, RuntimeScriptValue, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?");
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	return stack_ptr;
@@ -1998,12 +1995,12 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
 		stack_entry--;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RETVAL(total_off < rw_offset, "accessing address before stack's head", RuntimeScriptValue);
+	CC_ERROR_IF_RETVAL(total_off < rw_offset, RuntimeScriptValue, "accessing address before stack's head");
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	stack_ptr.IValue += total_off - rw_offset; // possibly offset to the mid-array
 	// Could be accessing array element, so state error only if stack entry does not refer to data array
-	CC_ERROR_IF_RETVAL((total_off > rw_offset) && (stack_entry->Type != kScValData), "stack offset backward: trying to access stack data inside stack entry, stack corrupted?", RuntimeScriptValue)
+	CC_ERROR_IF_RETVAL((total_off > rw_offset) && (stack_entry->Type != kScValData), RuntimeScriptValue, "stack offset backward: trying to access stack data inside stack entry, stack corrupted?")
 	return stack_ptr;
 }
 


Commit: 1040f7978290b7ba95b1bfabbb6cb5f637d555c1
    https://github.com/scummvm/scummvm/commit/1040f7978290b7ba95b1bfabbb6cb5f637d555c1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: be able to define DEBUG_CC_EXEC explicitly

>From upstream 4149d108ebf6045b823b9de96d7fe611bacfac73

Changed paths:
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 1bfc5791bdd..26fc84c8e86 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -53,7 +53,9 @@ using namespace AGS;
 
 // Script executor debugging flag:
 // enables mistake checks, but slows things down!
+#ifndef DEBUG_CC_EXEC
 #define DEBUG_CC_EXEC (AGS_PLATFORM_DEBUG)
+#endif
 
 struct ScriptInstruction {
 	ScriptInstruction() = default;


Commit: f5c0b751604001377d9a7c2dce28e811c1a215a6
    https://github.com/scummvm/scummvm/commit/f5c0b751604001377d9a7c2dce28e811c1a215a6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: replaced fixups for loop with a switch

Based on the idea by @ericoporto
This is more code lines, and looks kind of ugly, but proved to be significantly faster enough to justify this change.
Maybe there are more ways to refactor and tidy this in the future.

>From upstream 9f255eab2343896928039dbca71576718f5f7380

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index a3ca5c64f2c..a5beb58c39b 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -482,6 +482,63 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	line_number = callStackLineNumber[callStackSize];\
 	_G(currentline) = line_number
 
+// Return stack ptr at given offset from stack head;
+// Offset is in data bytes; program stack ptr is __not__ changed
+inline RuntimeScriptValue GetStackPtrOffsetFw(RuntimeScriptValue *stack, int32_t fw_offset) {
+	int32_t total_off = 0;
+	RuntimeScriptValue *stack_entry = stack;
+	while (total_off < fw_offset && (stack_entry - stack) < CC_STACK_SIZE) {
+		stack_entry++;
+		total_off += stack_entry->Size;
+	}
+	CC_ERROR_IF_RETVAL(total_off < fw_offset, RuntimeScriptValue, "accessing address beyond stack's tail");
+	CC_ERROR_IF_RETVAL(total_off > fw_offset, RuntimeScriptValue, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?");
+	RuntimeScriptValue stack_ptr;
+	stack_ptr.SetStackPtr(stack_entry);
+	return stack_ptr;
+}
+
+// Applies a runtime fixup to the given arg;
+// Fixup of type `fixup` is applied to the `code` value,
+// the result is assigned to the `arg`.
+inline bool FixupArgument(RuntimeScriptValue &arg, int fixup, uintptr_t code, RuntimeScriptValue *stack, const char *strings) {
+	// could be relative pointer or import address
+	switch (fixup) {
+	case FIXUP_GLOBALDATA: {
+		ScriptVariable *gl_var = (ScriptVariable *)code;
+		arg.SetGlobalVar(&gl_var->RValue);
+	}
+		return true;
+	case FIXUP_FUNCTION:
+		// originally commented -- CHECKME: could this be used in very old versions of AGS?
+		//      code[fixup] += (long)&code[0];
+		// This is a program counter value, presumably will be used as SCMD_CALL argument
+		arg.SetInt32((int32_t)code);
+		return true;
+	case FIXUP_STRING:
+		arg.SetStringLiteral(strings + code);
+		return true;
+	case FIXUP_IMPORT: {
+		const ScriptImport *import = _GP(simp).getByIndex(static_cast<uint32_t>(code));
+		if (import) {
+			arg = import->Value;
+		} else {
+			cc_error("cannot resolve import, key = %ld", code);
+			return false;
+		}
+	}
+		return true;
+	case FIXUP_DATADATA:
+		return false; // placeholder, fail at this as not supposed to be here
+	case FIXUP_STACK:
+		arg = GetStackPtrOffsetFw(stack, (int32_t)code);
+		return true;
+	default:
+		cc_error("internal fixup type error: %d", fixup);
+		return false;
+	}
+}
+
 #define MAXNEST 50  // number of recursive function calls allowed
 int ccInstance::Run(int32_t curpc) {
 	pc = curpc;
@@ -523,7 +580,7 @@ int ccInstance::Run(int32_t curpc) {
 		// may lead to a performance loss in script-heavy games.
 		// always compare execution speed before applying any major changes!
 		//
-		/* ReadOperation */
+		/* Read operation */
 		//=====================================================================
 		codeOp.Instruction.Code         = codeInst->code[pc];
 		codeOp.Instruction.InstanceId   = (codeOp.Instruction.Code >> INSTANCE_ID_SHIFT) & INSTANCE_ID_MASK;
@@ -537,53 +594,54 @@ int ccInstance::Run(int32_t curpc) {
 		CC_ERROR_IF_RETCODE(pc + codeOp.ArgCount >= codeInst->codesize,
 							"unexpected end of code data (%d; %d)", pc + codeOp.ArgCount, codeInst->codesize);
 
-		int pc_at = pc + 1;
-		for (int i = 0; i < codeOp.ArgCount; ++i, ++pc_at) {
-			char fixup = codeInst->code_fixups[pc_at];
-			if (fixup > 0) {
-				/* FixupArgument */
-				//=====================================================================
-				// could be relative pointer or import address
-				switch (fixup) {
-				case FIXUP_GLOBALDATA: {
-					ScriptVariable *gl_var = (ScriptVariable *)codeInst->code[pc_at];
-					codeOp.Args[i].SetGlobalVar(&gl_var->RValue);
-				}
-				break;
-				case FIXUP_FUNCTION:
-					// originally commented -- CHECKME: could this be used in very old versions of AGS?
-					//      code[fixup] += (long)&code[0];
-					// This is a program counter value, presumably will be used as SCMD_CALL argument
-					codeOp.Args[i].SetInt32((int32_t)codeInst->code[pc_at]);
-					break;
-				case FIXUP_STRING:
-					codeOp.Args[i].SetStringLiteral(&codeInst->strings[0] + codeInst->code[pc_at]);
-					break;
-				case FIXUP_IMPORT: {
-					const ScriptImport *import = _GP(simp).getByIndex(static_cast<uint32_t>(codeInst->code[pc_at]));
-					if (import) {
-						codeOp.Args[i] = import->Value;
-					} else {
-						cc_error("cannot resolve import, key = %ld", codeInst->code[pc_at]);
-						return -1;
-					}
-				}
-				break;
-				case FIXUP_STACK:
-					codeOp.Args[i] = GetStackPtrOffsetFw((int32_t)codeInst->code[pc_at]);
-					break;
-				default:
-					cc_error("internal fixup type error: %d", fixup);
+		//---------------------------------------------------------------------
+		/* Fixup arguments */
+		switch (codeOp.ArgCount) {
+		case 3: {
+			const int pc_at = pc + 3;
+			const uintptr_t code = codeInst->code[pc_at];
+			const char fixup = codeInst->code_fixups[pc_at];
+			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
+				if (!FixupArgument(codeOp.Args[2], fixup, code, this->stack, codeInst->strings))
 					return -1;
-				}
-				/* End FixupArgument */
-				//=====================================================================
-			} else {
-				// should be a numeric literal (int32 or float)
-				codeOp.Args[i].SetInt32((int32_t)codeInst->code[pc_at]);
+			} else // numeric literal
+			{      // should be a numeric literal (int32 or float)
+				codeOp.Args[2].SetInt32((int32_t)code);
+			}
+			/* fall-through */
+		}
+		case 2: {
+			const int pc_at = pc + 2;
+			const uintptr_t code = codeInst->code[pc_at];
+			const char fixup = codeInst->code_fixups[pc_at];
+			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
+				if (!FixupArgument(codeOp.Args[1], fixup, code, this->stack, codeInst->strings))
+					return -1;
+			} else // numeric literal
+			{      // should be a numeric literal (int32 or float)
+				codeOp.Args[1].SetInt32((int32_t)code);
+			}
+			/* fall-through */
+		}
+		case 1: {
+			const int pc_at = pc + 1;
+			const uintptr_t code = codeInst->code[pc_at];
+			const char fixup = codeInst->code_fixups[pc_at];
+			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
+				if (!FixupArgument(codeOp.Args[0], fixup, code, this->stack, codeInst->strings))
+					return -1;
+			} else // numeric literal
+			{      // should be a numeric literal (int32 or float)
+				codeOp.Args[0].SetInt32((int32_t)code);
 			}
+			break;
+		}
+		default:
+			break;
 		}
-		/* End ReadOperation */
+		/* End fixup arguments */
+		//---------------------------------------------------------------------
+		/* End read operation */
 		//=====================================================================
 
 #if (DEBUG_CC_EXEC)
@@ -592,6 +650,8 @@ int ccInstance::Run(int32_t curpc) {
 		}
 #endif
 
+		/* Perform operation */
+		//=====================================================================
 		switch (codeOp.Instruction.Code) {
 		case SCMD_LINENUM:
 			line_number = codeOp.Arg1i();
@@ -1438,6 +1498,8 @@ int ccInstance::Run(int32_t curpc) {
 			cc_error("instruction %d is not implemented", codeOp.Instruction.Code);
 			return -1;
 		}
+		/* End perform operation */
+		//=====================================================================
 
 		pc += codeOp.ArgCount + 1;
 	}
@@ -1974,20 +2036,6 @@ void ccInstance::PopDataFromStack(int32_t num_bytes) {
 	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop, stack corrupted?");
 }
 
-RuntimeScriptValue ccInstance::GetStackPtrOffsetFw(int32_t fw_offset) {
-	int32_t total_off = 0;
-	RuntimeScriptValue *stack_entry = &stack[0];
-	while (total_off < fw_offset && stack_entry - &stack[0] < CC_STACK_SIZE) {
-		stack_entry++;
-		total_off += stack_entry->Size;
-	}
-	CC_ERROR_IF_RETVAL(total_off < fw_offset, RuntimeScriptValue, "accessing address beyond stack's tail");
-	CC_ERROR_IF_RETVAL(total_off > fw_offset, RuntimeScriptValue, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?");
-	RuntimeScriptValue stack_ptr;
-	stack_ptr.SetStackPtr(stack_entry);
-	return stack_ptr;
-}
-
 RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
 	int32_t total_off = 0;
 	RuntimeScriptValue *stack_entry = registers[SREG_SP].RValue;
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 26fc84c8e86..f7443d8131a 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -220,9 +220,6 @@ private:
 	// helper function to pop & dump several values
 	void    PopValuesFromStack(int32_t num_entries);
 	void    PopDataFromStack(int32_t num_bytes);
-	// Return stack ptr at given offset from stack head;
-	// Offset is in data bytes; program stack ptr is __not__ changed
-	RuntimeScriptValue GetStackPtrOffsetFw(int32_t fw_offset);
 	// Return stack ptr at given offset from stack tail;
 	// Offset is in data bytes; program stack ptr is __not__ changed
 	RuntimeScriptValue GetStackPtrOffsetRw(int32_t rw_offset);


Commit: 4dc7347a4ff81ddc9b07df0b74a3647a4def2948
    https://github.com/scummvm/scummvm/commit/4dc7347a4ff81ddc9b07df0b74a3647a4def2948
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: only do fixups for ops/args where expected

This explicitly calls FixupArgument() only for the 2 instructions where a fixup may be expected to be applied to a literal arg. These are:
* SCMD_WRITELIT
* SCMD_LITTOREG

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/shared/script/cc_internal.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index a5beb58c39b..fd0f82b41d6 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -504,6 +504,8 @@ inline RuntimeScriptValue GetStackPtrOffsetFw(RuntimeScriptValue *stack, int32_t
 inline bool FixupArgument(RuntimeScriptValue &arg, int fixup, uintptr_t code, RuntimeScriptValue *stack, const char *strings) {
 	// could be relative pointer or import address
 	switch (fixup) {
+	case FIXUP_NOFIXUP:
+		return true;
 	case FIXUP_GLOBALDATA: {
 		ScriptVariable *gl_var = (ScriptVariable *)code;
 		arg.SetGlobalVar(&gl_var->RValue);
@@ -589,57 +591,27 @@ int ccInstance::Run(int32_t curpc) {
 		CC_ERROR_IF_RETCODE((codeOp.Instruction.Code < 0 || codeOp.Instruction.Code >= CC_NUM_SCCMDS),
 							"invalid instruction %d found in code stream", codeOp.Instruction.Code);
 
-		codeOp.ArgCount = (*g_commands)[codeOp.Instruction.Code].ArgCount & 0x3;
+		codeOp.ArgCount = (*g_commands)[codeOp.Instruction.Code].ArgCount;
 
 		CC_ERROR_IF_RETCODE(pc + codeOp.ArgCount >= codeInst->codesize,
 							"unexpected end of code data (%d; %d)", pc + codeOp.ArgCount, codeInst->codesize);
 
-		//---------------------------------------------------------------------
-		/* Fixup arguments */
+
+		// Read arguments; use switch as it proved to be faster than the loop
+
 		switch (codeOp.ArgCount) {
-		case 3: {
-			const int pc_at = pc + 3;
-			const uintptr_t code = codeInst->code[pc_at];
-			const char fixup = codeInst->code_fixups[pc_at];
-			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
-				if (!FixupArgument(codeOp.Args[2], fixup, code, this->stack, codeInst->strings))
-					return -1;
-			} else // numeric literal
-			{      // should be a numeric literal (int32 or float)
-				codeOp.Args[2].SetInt32((int32_t)code);
-			}
+		case 3:
+			codeOp.Args[2].SetInt32((int32_t)codeInst->code[pc + 3]);
 			/* fall-through */
-		}
-		case 2: {
-			const int pc_at = pc + 2;
-			const uintptr_t code = codeInst->code[pc_at];
-			const char fixup = codeInst->code_fixups[pc_at];
-			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
-				if (!FixupArgument(codeOp.Args[1], fixup, code, this->stack, codeInst->strings))
-					return -1;
-			} else // numeric literal
-			{      // should be a numeric literal (int32 or float)
-				codeOp.Args[1].SetInt32((int32_t)code);
-			}
+		case 2:
+			codeOp.Args[1].SetInt32((int32_t)codeInst->code[pc + 2]);
 			/* fall-through */
-		}
-		case 1: {
-			const int pc_at = pc + 1;
-			const uintptr_t code = codeInst->code[pc_at];
-			const char fixup = codeInst->code_fixups[pc_at];
-			if (fixup > 0) { // WARNING: passing our own "stack" instead of one from codeInst
-				if (!FixupArgument(codeOp.Args[0], fixup, code, this->stack, codeInst->strings))
-					return -1;
-			} else // numeric literal
-			{      // should be a numeric literal (int32 or float)
-				codeOp.Args[0].SetInt32((int32_t)code);
-			}
+		case 1:
+			codeOp.Args[0].SetInt32((int32_t)codeInst->code[pc + 1]);
 			break;
-		}
 		default:
 			break;
 		}
-		/* End fixup arguments */
 		//---------------------------------------------------------------------
 		/* End read operation */
 		//=====================================================================
@@ -721,6 +693,8 @@ int ccInstance::Run(int32_t curpc) {
 			// be only up to 4 bytes large;
 			// I guess that's an obsolete way to do WRITE, WRITEW and WRITEB
 			const auto arg_size = codeOp.Arg1i();
+			FixupArgument(codeOp.Args[1], codeInst->code_fixups[pc + 2], codeInst->code[pc + 2], this->stack, codeInst->strings);
+			ASSERT_CC_ERROR();
 			const auto &arg_value = codeOp.Arg2();
 			switch (arg_size) {
 			case sizeof(char):
@@ -756,6 +730,8 @@ int ccInstance::Run(int32_t curpc) {
 		}
 		case SCMD_LITTOREG: {
 			auto &reg1 = registers[codeOp.Arg1i()];
+			FixupArgument(codeOp.Args[1], codeInst->code_fixups[pc + 2], codeInst->code[pc + 2], this->stack, codeInst->strings);
+			ASSERT_CC_ERROR();
 			const auto &arg_value = codeOp.Arg2();
 			reg1 = arg_value;
 			break;
diff --git a/engines/ags/shared/script/cc_internal.h b/engines/ags/shared/script/cc_internal.h
index cb5e395b3c0..c2c4c355adc 100644
--- a/engines/ags/shared/script/cc_internal.h
+++ b/engines/ags/shared/script/cc_internal.h
@@ -118,6 +118,7 @@ namespace AGS3 {
 #define EXPORT_FUNCTION   1
 #define EXPORT_DATA       2
 
+#define FIXUP_NOFIXUP     0     // no-op
 #define FIXUP_GLOBALDATA  1     // code[fixup] += &globaldata[0]
 #define FIXUP_FUNCTION    2     // code[fixup] += &code[0]
 #define FIXUP_STRING      3     // code[fixup] += &strings[0]


Commit: 0bf9768151f9e2f32ec0f7374c6620d57be423fa
    https://github.com/scummvm/scummvm/commit/0bf9768151f9e2f32ec0f7374c6620d57be423fa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance ScriptCommandInfo is const

from upstream 412f6fcd606084abedca8744024c25a676c29f98

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index fd0f82b41d6..c5c230e6145 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -62,19 +62,18 @@ enum ScriptOpArgIsReg {
 };
 
 struct ScriptCommandInfo {
-	ScriptCommandInfo(int32_t code, const char *cmdname, int arg_count, ScriptOpArgIsReg arg_is_reg) {
-		Code        = code;
-		CmdName     = cmdname;
-		ArgCount    = arg_count;
-		ArgIsReg[0] = (arg_is_reg & kScOpArg1IsReg) != 0;
-		ArgIsReg[1] = (arg_is_reg & kScOpArg2IsReg) != 0;
-		ArgIsReg[2] = (arg_is_reg & kScOpArg3IsReg) != 0;
-	}
-
-	int32_t             Code;
-	const char          *CmdName;
-	int                 ArgCount;
-	bool                ArgIsReg[3];
+	ScriptCommandInfo(int32_t code, const char *cmdname, int arg_count, ScriptOpArgIsReg arg_is_reg)
+		: Code(code), CmdName(cmdname), ArgCount(arg_count), ArgIsReg{
+			(arg_is_reg & kScOpArg1IsReg) != 0,
+			(arg_is_reg & kScOpArg2IsReg) != 0,
+			(arg_is_reg & kScOpArg3IsReg) != 0
+		}
+	{}
+
+	const int32_t Code = 0;
+	const char *CmdName = nullptr;
+	const int ArgCount = 0;
+	const bool ArgIsReg[3]{};
 };
 
 struct ScriptCommands {


Commit: 69327ac20b850f2cff7550e9563b0ae80508713a
    https://github.com/scummvm/scummvm/commit/69327ac20b850f2cff7550e9563b0ae80508713a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed stackptr increment on SCMD_ADD

This fixes 338afc2
>From upstream 38a9d7dae7ad7045121e2d6efa65b54a70c5f14b

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index c5c230e6145..5be0373772e 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -330,7 +330,7 @@ void ccInstance::AbortAndDestroy() {
 	if ((registers[SREG_SP].RValue + N_VALS - &stack[0]) >= CC_STACK_SIZE || \
 		(stackdata_ptr + N_BYTES - stackdata) >= CC_STACK_DATA_SIZE) \
 	{ \
-		cc_error("stack overflow, attempted grow by %d bytes", N_BYTES); \
+		cc_error("stack overflow, attempted to grow from %d by %d bytes", (stackdata_ptr - stackdata), N_BYTES); \
 		return -1; \
 	}
 
@@ -645,7 +645,7 @@ int ccInstance::Run(int32_t curpc) {
 				if (reg1.RValue->IsValid()) {
 					// TODO: perhaps should add a flag here to ensure this happens only after MEMWRITE-ing to stack
 					registers[SREG_SP].RValue++;
-					stackdata_ptr += sizeof(int32_t); // formality, to keep data ptr consistent
+					stackdata_ptr += arg_lit; // formality, to keep data ptr consistent
 				} else {
 					PushDataToStack(arg_lit);
 					ASSERT_CC_ERROR();
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index f7443d8131a..d74d2e6f485 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -39,7 +39,9 @@ using namespace AGS;
 #define INSTF_FREE          4
 #define INSTF_RUNNING       8   // set by main code to confirm script isn't stuck
 
+// Size of stack in RuntimeScriptValues (aka distinct variables)
 #define CC_STACK_SIZE		256
+// Size of stack in bytes (raw data storage)
 #define CC_STACK_DATA_SIZE	(1024 * sizeof(int32_t))
 #define MAX_CALL_STACK		128
 #define MAX_FUNCTION_PARAMS	20


Commit: 66560d97176bc5297b4d22b9d4c96bced9957929
    https://github.com/scummvm/scummvm/commit/66560d97176bc5297b4d22b9d4c96bced9957929
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: added ASSERT_STACK_UNWINDED for both cases of Run()

from upstream 2697c95ba185cd371412da86cb47fbe39484f891

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 5be0373772e..2fb0aed0cab 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -350,6 +350,15 @@ void ccInstance::AbortAndDestroy() {
 		return -1; \
 	}
 
+// ASSERT_STACK_UNWINDED tests that the stack pointer is at the expected position
+#define ASSERT_STACK_UNWINDED(STACK_VAL, DATA_PTR) \
+	if ((registers[SREG_SP].RValue > STACK_VAL.RValue) || \
+		(stackdata_ptr > DATA_PTR)) \
+	{ \
+		cc_error("stack is not unwinded after function call, %d bytes remain", (stackdata_ptr - DATA_PTR)); \
+		return -1; \
+	}
+
 int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const RuntimeScriptValue *params) {
 	cc_clear_error();
 	_G(currentline) = 0;
@@ -420,14 +429,18 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	stackdata_ptr = stackdata;
 	// NOTE: Pushing parameters to stack in reverse order
 	ASSERT_STACK_SPACE_VALS(numargs + 1 /* return address */);
-		for (int i = numargs - 1; i >= 0; --i) {
-			PushValueToStack(params[i]);
-		}
-	PushValueToStack(RuntimeScriptValue().SetInt32(0)); // return address on stack
+	for (int i = numargs - 1; i >= 0; --i) {
+		PushValueToStack(params[i]);
+	}
+	const RuntimeScriptValue oldstack = registers[SREG_SP];
+	const char *oldstackdata = stackdata_ptr;
+	// Push placeholder for the return value (it will be popped before ret)
+	PushValueToStack(RuntimeScriptValue().SetInt32(0));
 
 	_GP(InstThreads).push_back(this); // push instance thread
 	runningInst = this;
 	int reterr = Run(startat);
+	ASSERT_STACK_UNWINDED(oldstack, oldstackdata);
 	// Cleanup before returning, even if error
 	ASSERT_STACK_SIZE(numargs);
 	PopValuesFromStack(numargs);
@@ -1145,10 +1158,10 @@ int ccInstance::Run(int32_t curpc) {
 				PushValueToStack(*prval);
 			}
 
-			// 0, so that the cc_run_code returns
-			RuntimeScriptValue oldstack = registers[SREG_SP];
+			const RuntimeScriptValue oldstack = registers[SREG_SP];
+			const char *oldstackdata = stackdata_ptr;
+			// Push placeholder for the return value (it will be popped before ret)
 			PushValueToStack(RuntimeScriptValue().SetInt32(0));
-			ASSERT_CC_ERROR();
 
 			int oldpc = pc;
 			ccInstance *wasRunning = runningInst;
@@ -1169,12 +1182,8 @@ int ccInstance::Run(int32_t curpc) {
 
 			runningInst = wasRunning;
 
-			if ((flags & INSTF_ABORTED) == 0) {
-				if (oldstack != registers[SREG_SP]) {
-					cc_error("stack corrupt after function call");
-					return -1;
-				}
-			}
+			if ((flags & INSTF_ABORTED) == 0)
+				ASSERT_STACK_UNWINDED(oldstack, oldstackdata);
 
 			next_call_needs_object = 0;
 


Commit: 9b56d15ef71fd059e371ee8da175ff4147d31f30
    https://github.com/scummvm/scummvm/commit/9b56d15ef71fd059e371ee8da175ff4147d31f30
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed stack unwind assert was hiding other errors

This complements 2697c95
from upstream ec693e5b74b437745d98bf3bcba26f891f857525

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 2fb0aed0cab..e47f74c0b29 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -432,15 +432,12 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	for (int i = numargs - 1; i >= 0; --i) {
 		PushValueToStack(params[i]);
 	}
-	const RuntimeScriptValue oldstack = registers[SREG_SP];
-	const char *oldstackdata = stackdata_ptr;
 	// Push placeholder for the return value (it will be popped before ret)
 	PushValueToStack(RuntimeScriptValue().SetInt32(0));
 
 	_GP(InstThreads).push_back(this); // push instance thread
 	runningInst = this;
 	int reterr = Run(startat);
-	ASSERT_STACK_UNWINDED(oldstack, oldstackdata);
 	// Cleanup before returning, even if error
 	ASSERT_STACK_SIZE(numargs);
 	PopValuesFromStack(numargs);
@@ -467,10 +464,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 		return 100;
 	}
 
-	if (registers[SREG_SP].RValue != &stack[0]) {
-		cc_error("stack pointer was not zero at completion of script");
-		return -5;
-	}
+	ASSERT_STACK_UNWINDED(registers[SREG_SP], stackdata);
 	return cc_has_error();
 }
 


Commit: 4895a10b8294012984b79540785b19ebbf550e72
    https://github.com/scummvm/scummvm/commit/4895a10b8294012984b79540785b19ebbf550e72
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ccInstance: fixed MEMWRITEPTR may leave undefined value

This might happen when assigning a local pointer variable, which was not cleaned either with ZEROMEMORY or MEMINITPTR.
from upstream a989f3ed0150fc0218c47b3d7ac3c655d072e78a

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index e47f74c0b29..bfa4cde9cec 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1060,6 +1060,8 @@ int ccInstance::Run(int32_t curpc) {
 				ccAddObjectReference(newHandle);
 				registers[SREG_MAR].WriteInt32(newHandle);
 			}
+			// Assign always, avoid leaving undefined value
+			registers[SREG_MAR].WriteInt32(newHandle);
 			break;
 		}
 		case SCMD_MEMINITPTR: {


Commit: afa54e052627eca0c0d9e903cf324d9af036ddd2
    https://github.com/scummvm/scummvm/commit/afa54e052627eca0c0d9e903cf324d9af036ddd2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplify IAGSEngine::GetManagedObjectAddressByKey()

from upstream 9648cc3c733681da4b8793fb9d8d96c7b0f6b8c1

Changed paths:
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 5cffbfb5fff..28867ab584d 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -682,11 +682,7 @@ void *IAGSEngine::GetManagedObjectAddressByKey(int key) {
 	void *object;
 	ICCDynamicObject *manager;
 	ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager);
-	if (obj_type == kScValPluginObject) {
-		_GP(GlobalReturnValue).SetPluginObject(object, manager);
-	} else {
-		_GP(GlobalReturnValue).SetDynamicObject(object, manager);
-	}
+	_GP(GlobalReturnValue).SetDynamicObject(obj_type, object, manager);
 	return object;
 }
 


Commit: 3a5ccd4cbac1de8a82f0ac8559a796fb5857f754
    https://github.com/scummvm/scummvm/commit/3a5ccd4cbac1de8a82f0ac8559a796fb5857f754
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: moved dynamic object management functions to a separate unit

>From upstream 1a9df937e3ac9eba0c2f5b663507a52569d635e0

Changed paths:
  A engines/ags/engine/ac/dynobj/dynobj_manager.cpp
  A engines/ags/engine/ac/dynobj/dynobj_manager.h
  R engines/ags/engine/ac/dynobj/cc_dynamic_object.cpp
  R engines/ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/date_time.cpp
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/dynamic_sprite.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/script_containers.cpp
    engines/ags/engine/ac/speech.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/main/quit.cpp
    engines/ags/engine/media/audio/audio.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/module.mk
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 2b3ae923285..a92312b3a29 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -64,6 +64,7 @@
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/engine/ac/dynobj/cc_character.h"
 #include "ags/engine/ac/dynobj/cc_inventory.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/shared/gfx/gfx_def.h"
 #include "ags/engine/media/audio/audio_system.h"
diff --git a/engines/ags/engine/ac/date_time.cpp b/engines/ags/engine/ac/date_time.cpp
index 71c530c6724..c1d3c0d6dba 100644
--- a/engines/ags/engine/ac/date_time.cpp
+++ b/engines/ags/engine/ac/date_time.cpp
@@ -20,8 +20,8 @@
  */
 
 #include "ags/engine/ac/date_time.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/engine/script/runtime_script_value.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index 5bab1292ef7..641033e645e 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -31,6 +31,7 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/walk_behind.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/gui/gui_main.h"
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 43d4aa70541..0eaa4ae69da 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -32,6 +32,7 @@
 #include "ags/engine/ac/room_object.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/system.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/gui/gui_button.h"
diff --git a/engines/ags/engine/ac/dynamic_sprite.h b/engines/ags/engine/ac/dynamic_sprite.h
index 50f646fa952..7133715fa2e 100644
--- a/engines/ags/engine/ac/dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynamic_sprite.h
@@ -51,7 +51,7 @@ ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot);
 ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height);
 
 
-void    add_dynamic_sprite(int gotSlot, Shared::Bitmap *redin, bool hasAlpha = false);
+void    add_dynamic_sprite(int gotSlot, AGS::Shared::Bitmap *redin, bool hasAlpha = false);
 void    free_dynamic_sprite(int gotSlot);
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index 796152000d2..b563a1e078b 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -22,6 +22,7 @@
 #include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
 #include "ags/engine/ac/dynobj/script_audio_channel.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/media/audio/audio_system.h"
 #include "ags/globals.h"
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index 92685a00f1f..939937acf6c 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_audio_clip.h"
 #include "ags/shared/ac/dynobj/script_audio_clip.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/shared/util/stream.h"
 
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 25974013194..eab51b5e92b 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/cc_character.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/character_info.h"
 #include "ags/engine/ac/global_character.h"
 #include "ags/shared/ac/game_setup_struct.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index 7af15eb9e58..a1e9db0c3fb 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/cc_dialog.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/dialog.h"
 #include "ags/shared/ac/dialog_topic.h"
 #include "ags/shared/ac/game_struct_defines.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 69ff8c3cc29..9842a0204ea 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -20,7 +20,8 @@
  */
 
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
-#include  "ags/globals.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
index af3ae5c66ef..57d91933d55 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
@@ -40,7 +40,6 @@ class Stream;
 } // namespace Shared
 } // namespace AGS
 
-using namespace AGS; // FIXME later
 
 // A pair of managed handle and abstract object pointer
 typedef std::pair<int32_t, void *> DynObjectRef;
@@ -105,32 +104,6 @@ struct ICCStringClass {
 	virtual DynObjectRef CreateString(const char *fromText) = 0;
 };
 
-// set the class that will be used for dynamic strings
-extern void  ccSetStringClassImpl(ICCStringClass *theClass);
-// register a memory handle for the object and allow script
-// pointers to point to it
-extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, bool plugin_object = false);
-// register a de-serialized object
-extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, bool plugin_object = false);
-// unregister a particular object
-extern int   ccUnRegisterManagedObject(const void *object);
-// remove all registered objects
-extern void  ccUnregisterAllObjects();
-// serialize all objects to disk
-extern void  ccSerializeAllObjects(Shared::Stream *out);
-// un-serialise all objects (will remove all currently registered ones)
-extern int   ccUnserializeAllObjects(Shared::Stream *in, ICCObjectReader *callback);
-// dispose the object if RefCount==0
-extern void  ccAttemptDisposeObject(int32_t handle);
-// translate between object handles and memory addresses
-extern int32_t ccGetObjectHandleFromAddress(const void *address);
-// TODO: not sure if it makes any sense whatsoever to use "const char*"
-// in these functions, might as well change to char* or just void*.
-extern const char *ccGetObjectAddressFromHandle(int32_t handle);
-
-extern int ccAddObjectReference(int32_t handle);
-extern int ccReleaseObjectReference(int32_t handle);
-
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h b/engines/ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h
deleted file mode 100644
index a2c46875beb..00000000000
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* 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 AGS_ENGINE_AC_DYNOBJ_ADDR_AND_MANAGER_H
-#define AGS_ENGINE_AC_DYNOBJ_ADDR_AND_MANAGER_H
-
-#include "ags/engine/script/runtime_script_value.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
-
-namespace AGS3 {
-
-extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(
-    int32_t handle, void *&object, ICCDynamicObject *&manager);
-
-} // namespace AGS3
-
-#endif
-
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index 363beda50e0..3ea12fd40fb 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_gui.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index 8814f9e8b4b..f5bb5f74ab5 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_gui_object.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_object.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index 5bdfe4d7828..1bc1072b995 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_hotspot.h"
 #include "ags/engine/ac/dynobj/script_hotspot.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index 94cbf05ff2c..63273408786 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_inventory.h"
 #include "ags/engine/ac/dynobj/script_inv_item.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/character_info.h"
 #include "ags/shared/util/stream.h"
 #include "ags/globals.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index 24110e8921d..93a3f540ed6 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_object.h"
 #include "ags/engine/ac/dynobj/script_object.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index 5249721e319..ee78bc1f81d 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_region.h"
 #include "ags/engine/ac/dynobj/script_region.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index 8316cd801e1..e8976fefbe1 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -23,6 +23,7 @@
 #include "ags/engine/ac/dynobj/cc_serializer.h"
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/dynobj/script_camera.h"
 #include "ags/engine/ac/dynobj/script_containers.h"
 #include "ags/engine/ac/dynobj/script_file.h"
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
similarity index 99%
rename from engines/ags/engine/ac/dynobj/cc_dynamic_object.cpp
rename to engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index a0bb167de27..f5c707364eb 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -33,6 +33,7 @@
 //
 //=============================================================================
 
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/core/platform.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_object.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
new file mode 100644
index 00000000000..7827f28ec21
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -0,0 +1,77 @@
+/* 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/>.
+ *
+ */
+
+//=============================================================================
+//
+// Dynamic object management utilities.
+// TODO: frankly, many of these functions could be factored out by a direct
+// use of ManagedPool class.
+//
+//=============================================================================
+
+#ifndef AGS_ENGINE_AC_DYNOBJ_MANAGER_H
+#define AGS_ENGINE_AC_DYNOBJ_MANAGER_H
+
+#include "ags/shared/core/types.h"
+#include "ags/engine/script/runtime_script_value.h"
+#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+
+namespace AGS3 {
+
+// Forward declaration
+namespace AGS {
+namespace Shared {
+class Stream;
+} // namespace Shared
+} // namespace AGS
+
+using namespace AGS; // FIXME later
+
+// set the class that will be used for dynamic strings
+extern void  ccSetStringClassImpl(ICCStringClass *theClass);
+// register a memory handle for the object and allow script
+// pointers to point to it
+extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, bool plugin_object = false);
+// register a de-serialized object
+extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, bool plugin_object = false);
+// unregister a particular object
+extern int   ccUnRegisterManagedObject(const void *object);
+// remove all registered objects
+extern void  ccUnregisterAllObjects();
+// serialize all objects to disk
+extern void  ccSerializeAllObjects(Shared::Stream *out);
+// un-serialise all objects (will remove all currently registered ones)
+extern int   ccUnserializeAllObjects(Shared::Stream *in, ICCObjectReader *callback);
+// dispose the object if RefCount==0
+extern void  ccAttemptDisposeObject(int32_t handle);
+// translate between object handles and memory addresses
+extern int32_t ccGetObjectHandleFromAddress(const void *address);
+// TODO: not sure if it makes any sense whatsoever to use "const char*"
+// in these functions, might as well change to char* or just void*.
+extern const char *ccGetObjectAddressFromHandle(int32_t handle);
+extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, ICCDynamicObject *&manager);
+
+extern int ccAddObjectReference(int32_t handle);
+extern int ccReleaseObjectReference(int32_t handle);
+
+} // namespace AGS3
+
+#endif
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index bda5c551f93..5213ffeee99 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_camera.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/util/bbop.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index d461d6b5ad5..3757d5ccf09 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_date_time.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index 0f40bc322a2..50cc03f2b43 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_dialog_options_rendering.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index 41b00febc2a..9c18971133f 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_dict.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index a253f3f2e68..cbebdaf0791 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_drawing_surface.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/shared/ac/common.h"
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index 12b68540432..1d0f2b8c910 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -24,15 +24,11 @@
 
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/game/room_struct.h"
+#include "ags/shared/gfx/bitmap.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
-namespace AGS {
-namespace Shared {
-class Bitmap;
-} // namespace Shared
-} // namespace AGS
-
 struct ScriptDrawingSurface final : AGSCCDynamicObject {
 	// These numbers and types are used to determine the source of this drawing surface;
 	// only one of them can be valid for this surface.
@@ -41,7 +37,7 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 	int dynamicSpriteNumber;
 	int dynamicSurfaceNumber;
 	bool isLinkedBitmapOnly;
-	Shared::Bitmap *linkedBitmapOnly;
+	AGS::Shared::Bitmap *linkedBitmapOnly;
 	int currentColour;
 	int currentColourScript;
 	int highResCoordinates;
@@ -52,8 +48,8 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 	int Dispose(const char *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
-	Shared::Bitmap *GetBitmapSurface();
-	Shared::Bitmap *StartDrawing();
+	AGS::Shared::Bitmap *GetBitmapSurface();
+	AGS::Shared::Bitmap *StartDrawing();
 	void PointToGameResolution(int *xcoord, int *ycoord);
 	void SizeToGameResolution(int *width, int *height);
 	void SizeToGameResolution(int *adjustValue);
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index db30e0d337b..e1ec5ac2d00 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_dynamic_sprite.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynamic_sprite.h"
 
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 184cfca563a..055f820b3d3 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_overlay.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/ac/common.h"
 #include "ags/shared/util/stream.h"
 #include "ags/engine/ac/overlay.h"
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index 22775977043..eaeeeb3f7e4 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "ags/engine/ac/dynobj/script_set.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
+
 namespace AGS3 {
 
 int ScriptSetBase::Dispose(const char *address, bool force) {
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index 3e6bca65948..f0baa5302e9 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -20,7 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_string.h"
-#include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/string.h"
 #include "ags/shared/util/stream.h"
 
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 97e1a70be87..119ab47283d 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -22,6 +22,7 @@
 #include "common/std/memory.h"
 #include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynobj/script_user_object.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index d270ac36a91..27076059324 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_view_frame.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index 39eff63ce19..6a2ab596009 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/script_viewport.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/util/bbop.h"
 #include "ags/shared/util/stream.h"
diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index f70eaa2e496..51a7307fd47 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -31,6 +31,7 @@
 #include "ags/engine/ac/path_helper.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index f4620ca4abb..702a0ed3d72 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -57,6 +57,7 @@
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
 #include "ags/engine/ac/dynobj/script_camera.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/shared/debugging/out.h"
diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 1c73802310c..a7c57abad5e 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -28,6 +28,7 @@
 #include "ags/engine/ac/dynobj/script_camera.h"
 #include "ags/engine/ac/dynobj/script_system.h"
 #include "ags/engine/ac/dynobj/script_viewport.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/device/mouse_w32.h"
 #include "ags/shared/game/custom_properties.h"
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index d5d3fbbb35a..57f926c5a29 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -40,6 +40,7 @@
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/dynobj/cc_gui_object.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/script/cc_instance.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/device/mouse_w32.h"
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index cfbac97cf80..d38c78354d7 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -35,6 +35,7 @@
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/screen_overlay.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/gfx/graphics_driver.h"
 #include "ags/shared/gfx/bitmap.h"
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 833ae6db21f..dbbdeff2f02 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -51,6 +51,7 @@
 #include "ags/engine/ac/walk_behind.h"
 #include "ags/engine/ac/dynobj/script_object.h"
 #include "ags/engine/ac/dynobj/script_hotspot.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/engine/script/cc_instance.h"
 #include "ags/engine/debugging/debug_log.h"
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index e5c9a22412f..37dcdb636d5 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -32,6 +32,7 @@
 #include "ags/engine/ac/dynobj/script_dict.h"
 #include "ags/engine/ac/dynobj/script_set.h"
 #include "ags/engine/ac/dynobj/script_string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/shared/util/bbop.h"
diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp
index 503e3e629b6..a462ff659a2 100644
--- a/engines/ags/engine/ac/speech.cpp
+++ b/engines/ags/engine/ac/speech.cpp
@@ -29,6 +29,7 @@
 #include "ags/engine/ac/global_audio.h"
 #include "ags/engine/ac/global_display.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index c24b431eaf8..f15b027eb43 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -29,6 +29,7 @@
 #include "ags/engine/ac/global_translation.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/dynobj/script_string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/script/runtime_script_value.h"
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 9bac7a9c206..5b788de722f 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -33,6 +33,7 @@
 #include "ags/engine/ac/move_list.h"
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/statobj/ags_static_object.h"
 #include "ags/engine/ac/statobj/static_array.h"
 #include "ags/shared/ac/view.h"
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 5a6947df95e..44f26710d9e 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -40,6 +40,7 @@
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/timer.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/device/mouse_w32.h"
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 6e808a265aa..0f9609c09a6 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -41,6 +41,7 @@
 #include "ags/shared/ac/view.h"
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/dynobj/cc_serializer.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/game/savegame_internal.h"
 #include "ags/shared/gfx/bitmap.h"
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index f196b0102f1..a8e4e19203a 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -45,6 +45,7 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/shared/ac/view.h"
 #include "ags/engine/ac/dynobj/cc_serializer.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/game/savegame.h"
 #include "ags/engine/game/savegame_components.h"
 #include "ags/engine/game/savegame_internal.h"
diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp
index 1bbaf0b8ed8..663a217a629 100644
--- a/engines/ags/engine/main/quit.cpp
+++ b/engines/ags/engine/main/quit.cpp
@@ -32,6 +32,7 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/route_finder.h"
 #include "ags/engine/ac/translation.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/ags_editor_debugger.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/debugging/debugger.h"
diff --git a/engines/ags/engine/media/audio/audio.cpp b/engines/ags/engine/media/audio/audio.cpp
index 54756291dfc..7741e2637e1 100644
--- a/engines/ags/engine/media/audio/audio.cpp
+++ b/engines/ags/engine/media/audio/audio.cpp
@@ -25,6 +25,7 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/dynobj/cc_audio_clip.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/engine/ac/audio_channel.h"
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index bfa4cde9cec..53f6ebaba22 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -23,6 +23,7 @@
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/gui/gui_defines.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/cc_instance.h"
@@ -40,7 +41,6 @@
 #include "ags/engine/ac/statobj/ags_static_object.h"
 #include "ags/engine/ac/statobj/static_array.h"
 #include "ags/engine/ac/sys_events.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h"
 #include "ags/shared/util/memory.h"
 #include "ags/shared/util/string_utils.h" // linux strnicmp definition
 #include "ags/detection.h"
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 76dcdfc337a..9bd9e1dce97 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -28,16 +28,16 @@
 #ifndef AGS_ENGINE_SCRIPT_RUNTIME_SCRIPT_VALUE_H
 #define AGS_ENGINE_SCRIPT_RUNTIME_SCRIPT_VALUE_H
 
+#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/statobj/static_object.h"
+#include "ags/engine/ac/statobj/static_array.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/shared/util/memory.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+
 #include "ags/plugins/plugin_base.h"
 
 namespace AGS3 {
 
-struct ICCStaticObject;
-struct StaticArray;
-
 enum ScriptValueType {
 	kScValUndefined,    // to detect errors
 	kScValInteger,      // as strictly 32-bit integer (for integer math)
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 936b7b59923..3238ce660e7 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -200,7 +200,6 @@ MODULE_OBJS = \
 	engine/ac/dynobj/cc_character.o \
 	engine/ac/dynobj/cc_dialog.o \
 	engine/ac/dynobj/cc_dynamic_array.o \
-	engine/ac/dynobj/cc_dynamic_object.o \
 	engine/ac/dynobj/cc_gui.o \
 	engine/ac/dynobj/cc_gui_object.o \
 	engine/ac/dynobj/cc_hotspot.o \
@@ -208,6 +207,7 @@ MODULE_OBJS = \
 	engine/ac/dynobj/cc_object.o \
 	engine/ac/dynobj/cc_region.o \
 	engine/ac/dynobj/cc_serializer.o \
+	engine/ac/dynobj/dynobj_manager.o \
 	engine/ac/dynobj/managed_object_pool.o \
 	engine/ac/dynobj/script_camera.o \
 	engine/ac/dynobj/script_date_time.o \
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 28867ab584d..3161f08702e 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -47,8 +47,8 @@
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/sys_events.h"
 #include "ags/shared/ac/sprite_cache.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h"
 #include "ags/engine/ac/dynobj/script_string.h"
+#include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/debugging/debugger.h"


Commit: 34fd9c07d5ce9d8d8631bed54db3e2e1e22a71ec
    https://github.com/scummvm/scummvm/commit/34fd9c07d5ce9d8d8631bed54db3e2e1e22a71ec
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix missing function type

Changed paths:
    engines/ags/lib/allegro/unicode.cpp


diff --git a/engines/ags/lib/allegro/unicode.cpp b/engines/ags/lib/allegro/unicode.cpp
index 99730518ba4..f1dbc508c9d 100644
--- a/engines/ags/lib/allegro/unicode.cpp
+++ b/engines/ags/lib/allegro/unicode.cpp
@@ -98,7 +98,7 @@ size_t ustrsize(const char *s) {
 	return strlen(s);
 }
 
-static utf8_validate(int c) {
+static int utf8_validate(int c) {
 	if (c < 0 || c > 0x10FFFF || (0xD800 <= c && c <= 0xDFFF))
 		return 0xFFFD;
 	return c;


Commit: 11f50581a36108d2ee20c944cecb7c9df25699dd
    https://github.com/scummvm/scummvm/commit/11f50581a36108d2ee20c944cecb7c9df25699dd
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Don't use uintptr_t

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 53f6ebaba22..ec3c4a3e5b4 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -507,7 +507,7 @@ inline RuntimeScriptValue GetStackPtrOffsetFw(RuntimeScriptValue *stack, int32_t
 // Applies a runtime fixup to the given arg;
 // Fixup of type `fixup` is applied to the `code` value,
 // the result is assigned to the `arg`.
-inline bool FixupArgument(RuntimeScriptValue &arg, int fixup, uintptr_t code, RuntimeScriptValue *stack, const char *strings) {
+inline bool FixupArgument(RuntimeScriptValue &arg, int fixup, uintptr code, RuntimeScriptValue *stack, const char *strings) {
 	// could be relative pointer or import address
 	switch (fixup) {
 	case FIXUP_NOFIXUP:


Commit: 34e15c2e5739eee0d1a2816a829977228e452c9e
    https://github.com/scummvm/scummvm/commit/34e15c2e5739eee0d1a2816a829977228e452c9e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplifications for ManagedPool

* Don't call CheckDispose() from SubRef, we may call Remove directly,
as we already know that reference count is zero.
* Don't pass a "plugin_object" flag to the object registration methods,
pass actual ScriptValueType instead. This also allows to have more subtypes if necessary.

from upstream 9b93f6f4340e8291d671245a082c1ad88a8da554

Changed paths:
    engines/ags/engine/ac/dynobj/dynobj_manager.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index f5c707364eb..6b91d72fd9d 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -54,8 +54,8 @@ void ccSetStringClassImpl(ICCStringClass *theClass) {
 
 // register a memory handle for the object and allow script
 // pointers to point to it
-int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback, bool plugin_object) {
-	int32_t handl = _GP(pool).AddObject((const char *)object, callback, plugin_object);
+int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+	int32_t handl = _GP(pool).AddObject((const char *)object, callback, obj_type);
 
 	ManagedObjectLog("Register managed object type '%s' handle=%d addr=%08X",
 	                 ((callback == NULL) ? "(unknown)" : callback->GetType()), handl, object);
@@ -64,8 +64,8 @@ int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback,
 }
 
 // register a de-serialized object
-int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *callback, bool plugin_object) {
-	return _GP(pool).AddUnserializedObject((const char *)object, callback, plugin_object, index);
+int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+	return _GP(pool).AddUnserializedObject((const char *)object, callback, obj_type, index);
 }
 
 // unregister a particular object
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index 7827f28ec21..0d10b70cf2a 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -49,9 +49,9 @@ using namespace AGS; // FIXME later
 extern void  ccSetStringClassImpl(ICCStringClass *theClass);
 // register a memory handle for the object and allow script
 // pointers to point to it
-extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, bool plugin_object = false);
+extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
 // register a de-serialized object
-extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, bool plugin_object = false);
+extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
 // unregister a particular object
 extern int   ccUnRegisterManagedObject(const void *object);
 // remove all registered objects
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 6e4857ca296..60fbd0014b1 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -39,14 +39,9 @@ const auto GARBAGE_COLLECTION_INTERVAL = 1024;
 const auto RESERVED_SIZE = 2048;
 
 int ManagedObjectPool::Remove(ManagedObject &o, bool force) {
-	if (!o.isUsed()) {
-		return 1;
-	} // already removed
-
-	bool canBeRemovedFromPool = o.callback->Dispose(o.addr, force) != 0;
-	if (!(canBeRemovedFromPool || force)) {
+	const bool can_remove = o.callback->Dispose(o.addr, force) != 0;
+	if (!(can_remove || force))
 		return 0;
-	}
 
 	available_ids.push(o.handle);
 	handleByAddress.erase(o.addr);
@@ -56,23 +51,21 @@ int ManagedObjectPool::Remove(ManagedObject &o, bool force) {
 }
 
 int32_t ManagedObjectPool::AddRef(int32_t handle) {
-	if (handle < 0 || (size_t)handle >= objects.size()) {
+	if (handle < 1 || (size_t)handle >= objects.size())
 		return 0;
-	}
+
 	auto &o = objects[handle];
-	if (!o.isUsed()) {
+	if (!o.isUsed())
 		return 0;
-	}
-
-	o.refCount += 1;
+	o.refCount++;
 	ManagedObjectLog("Line %d AddRef: handle=%d new refcount=%d", _G(currentline), o.handle, o.refCount);
 	return o.refCount;
 }
 
 int ManagedObjectPool::CheckDispose(int32_t handle) {
-	if (handle < 0 || (size_t)handle >= objects.size()) {
+	if (handle < 1 || (size_t)handle >= objects.size())
 		return 1;
-	}
+
 	auto &o = objects[handle];
 	if (!o.isUsed()) {
 		return 1;
@@ -93,10 +86,10 @@ int32_t ManagedObjectPool::SubRef(int32_t handle) {
 	}
 
 	o.refCount--;
-	auto newRefCount = o.refCount;
-	auto canBeDisposed = (o.addr != disableDisposeForObject);
-	if (canBeDisposed) {
-		CheckDispose(handle);
+	const auto newRefCount = o.refCount;
+	const auto canBeDisposed = (o.addr != disableDisposeForObject);
+	if (canBeDisposed && o.refCount <= 0) {
+		Remove(o);
 	}
 	// object could be removed at this point, don't use any values.
 	ManagedObjectLog("Line %d SubRef: handle=%d new refcount=%d canBeDisposed=%d", _G(currentline), handle, newRefCount, canBeDisposed);
@@ -116,7 +109,7 @@ int32_t ManagedObjectPool::AddressToHandle(const char *addr) {
 
 // this function is called often (whenever a pointer is used)
 const char *ManagedObjectPool::HandleToAddress(int32_t handle) {
-	if (handle < 0 || (size_t)handle >= objects.size()) {
+	if (handle < 1 || (size_t)handle >= objects.size()) {
 		return nullptr;
 	}
 	auto &o = objects[handle];
@@ -173,7 +166,7 @@ void ManagedObjectPool::RunGarbageCollection() {
 	ManagedObjectLog("Ran garbage collection");
 }
 
-int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback, bool plugin_object) {
+int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type) {
 	int32_t handle;
 
 	if (!available_ids.empty()) {
@@ -187,12 +180,9 @@ int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback
 	}
 
 	auto &o = objects[handle];
-	if (o.isUsed()) {
-		cc_error("used: %d", handle);
-		return 0;
-	}
+	assert(!o.isUsed());
 
-	o = ManagedObject(plugin_object ? kScValPluginObject : kScValDynamicObject, handle, address, callback);
+	o = ManagedObject(obj_type, handle, address, callback);
 
 	handleByAddress.insert({ address, o.handle });
 	objectCreationCounter++;
@@ -201,7 +191,7 @@ int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback
 }
 
 
-int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObject *callback, bool plugin_object, int handle) {
+int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle) {
 	if (handle < 0) {
 		cc_error("Attempt to assign invalid handle: %d", handle);
 		return 0;
@@ -211,12 +201,9 @@ int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObje
 	}
 
 	auto &o = objects[handle];
-	if (o.isUsed()) {
-		cc_error("bad save. used: %d", o.handle);
-		return 0;
-	}
+	assert(!o.isUsed());
 
-	o = ManagedObject(plugin_object ? kScValPluginObject : kScValDynamicObject, handle, address, callback);
+	o = ManagedObject(obj_type, handle, address, callback);
 
 	handleByAddress.insert({ address, o.handle });
 	ManagedObjectLog("Allocated unserialized managed object handle=%d, type=%s", o.handle, callback->GetType());
@@ -361,9 +348,7 @@ void ManagedObjectPool::reset() {
 		}
 		Remove(o, true);
 	}
-	while (!available_ids.empty()) {
-		available_ids.pop();
-	}
+	available_ids = std::queue<int32_t>();
 	nextHandle = 1;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index 83db22749cb..9c265295852 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -80,7 +80,6 @@ private:
 	std::vector<ManagedObject> objects;
 	std::unordered_map<const char *, int32_t, Pointer_Hash> handleByAddress;
 
-	void Init(int32_t theHandle, const char *theAddress, ICCDynamicObject *theCallback, ScriptValueType objType);
 	int Remove(ManagedObject &o, bool force = false);
 
 	void RunGarbageCollection();
@@ -95,8 +94,8 @@ public:
 	ScriptValueType HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager);
 	int RemoveObject(const char *address);
 	void RunGarbageCollectionIfAppropriate();
-	int AddObject(const char *address, ICCDynamicObject *callback, bool plugin_object);
-	int AddUnserializedObject(const char *address, ICCDynamicObject *callback, bool plugin_object, int handle);
+	int AddObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type);
+	int AddUnserializedObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle);
 	void WriteToDisk(Shared::Stream *out);
 	int ReadFromDisk(Shared::Stream *in, ICCObjectReader *reader);
 	void reset();
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 3161f08702e..f53caea5afb 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -649,7 +649,7 @@ void IAGSEngine::QueueGameScriptFunction(const char *name, int32 globalScript, i
 
 int IAGSEngine::RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback) {
 	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
-	return ccRegisterManagedObject(object, (ICCDynamicObject *)callback, true);
+	return ccRegisterManagedObject(object, (ICCDynamicObject *)callback, kScValPluginObject);
 }
 
 void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader) {
@@ -671,7 +671,7 @@ void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectR
 
 void IAGSEngine::RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback) {
 	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
-	ccRegisterUnserializedObject(key, object, (ICCDynamicObject *)callback, true);
+	ccRegisterUnserializedObject(key, object, (ICCDynamicObject *)callback, kScValPluginObject);
 }
 
 int IAGSEngine::GetManagedObjectKeyByAddress(const char *address) {


Commit: 0f44b1946c23d913687a75b29a4ee51c0192ebaa
    https://github.com/scummvm/scummvm/commit/0f44b1946c23d913687a75b29a4ee51c0192ebaa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: refactored CCDynamicArray for clarity and easier extension

Changed paths:
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 9842a0204ea..b4df52a3f4b 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -21,73 +21,82 @@
 
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
+#include "ags/shared/util/memory_stream.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
+using namespace AGS::Shared;
+
+const char *CCDynamicArray::TypeName = "CCDynamicArray";
+
 // return the type name of the object
 const char *CCDynamicArray::GetType() {
-	return CC_DYNAMIC_ARRAY_TYPE_NAME;
+	return TypeName;
 }
 
 int CCDynamicArray::Dispose(const char *address, bool force) {
-	address -= 8;
-
 	// If it's an array of managed objects, release their ref counts;
 	// except if this array is forcefully removed from the managed pool,
 	// in which case just ignore these.
 	if (!force) {
-		int *elementCount = (int *)const_cast<char *>(address);
-		if (elementCount[0] & ARRAY_MANAGED_TYPE_FLAG) {
-			elementCount[0] &= ~ARRAY_MANAGED_TYPE_FLAG;
-			for (int i = 0; i < elementCount[0]; i++) {
-				if (elementCount[2 + i] != 0) {
-					ccReleaseObjectReference(elementCount[2 + i]);
-				}
+		const Header &hdr = GetHeader(address);
+		bool is_managed = (hdr.ElemCount & ARRAY_MANAGED_TYPE_FLAG) != 0;
+		const uint32_t el_count = hdr.ElemCount & (~ARRAY_MANAGED_TYPE_FLAG);
+
+		if (is_managed) { // Dynamic array of managed pointers: subref them directly
+			const uint32_t *handles = reinterpret_cast<const uint32_t *>(address);
+			for (uint32_t i = 0; i < el_count; ++i) {
+				if (handles[i] > 0)
+					ccReleaseObjectReference(handles[i]);
 			}
 		}
 	}
 
-	delete[] address;
+	delete[] (address - MemHeaderSz);
 	return 1;
 }
 
-// serialize the object into BUFFER (which is BUFSIZE bytes)
-// return number of bytes used
 int CCDynamicArray::Serialize(const char *address, char *buffer, int bufsize) {
-	const int *sizeInBytes = &((const int *)address)[-1];
-	int sizeToWrite = *sizeInBytes + 8;
+	const Header &hdr = GetHeader(address);
+	int sizeToWrite = hdr.TotalSize + FileHeaderSz;
 	if (sizeToWrite > bufsize) {
 		// buffer not big enough, ask for a bigger one
 		return -sizeToWrite;
 	}
-	memcpy(buffer, address - 8, sizeToWrite);
-	return sizeToWrite;
+	MemoryStream mems(reinterpret_cast<uint8_t *>(buffer), bufsize, kStream_Write);
+	mems.WriteInt32(hdr.ElemCount);
+	mems.WriteInt32(hdr.TotalSize);
+	mems.Write(address, hdr.TotalSize); // elements
+	return static_cast<int32_t>(mems.GetPosition());
 }
 
 void CCDynamicArray::Unserialize(int index, const char *serializedData, int dataSize) {
-	char *newArray = new char[dataSize];
-	memcpy(newArray, serializedData, dataSize);
-	ccRegisterUnserializedObject(index, &newArray[8], this);
+	char *new_arr = new char[(dataSize - FileHeaderSz) + MemHeaderSz];
+	MemoryStream mems(reinterpret_cast<const uint8_t *>(serializedData), dataSize);
+	Header &hdr = reinterpret_cast<Header &>(*new_arr);
+	hdr.ElemCount = mems.ReadInt32();
+	hdr.TotalSize = mems.ReadInt32();
+	memcpy(new_arr + MemHeaderSz, serializedData + FileHeaderSz, dataSize - FileHeaderSz);
+	ccRegisterUnserializedObject(index, &new_arr[MemHeaderSz], this);
 }
 
 DynObjectRef CCDynamicArray::Create(int numElements, int elementSize, bool isManagedType) {
-	char *newArray = new char[numElements * elementSize + 8]();
-	int *sizePtr = (int *)newArray;
-	sizePtr[0] = numElements;
-	sizePtr[1] = numElements * elementSize;
-	if (isManagedType)
-		sizePtr[0] |= ARRAY_MANAGED_TYPE_FLAG;
-	void *obj_ptr = &newArray[8];
+	char *new_arr = new char[numElements * elementSize + MemHeaderSz];
+	memset(new_arr, 0, numElements * elementSize + MemHeaderSz);
+	Header &hdr = reinterpret_cast<Header &>(*new_arr);
+	hdr.ElemCount = numElements | (ARRAY_MANAGED_TYPE_FLAG * isManagedType);
+	hdr.TotalSize = elementSize * numElements;
+	void *obj_ptr = &new_arr[MemHeaderSz];
+	// TODO: investigate if it's possible to register real object ptr directly
 	int32_t handle = ccRegisterManagedObject(obj_ptr, this);
 	if (handle == 0) {
-		delete[] newArray;
+		delete[] new_arr;
 		return DynObjectRef(0, nullptr);
 	}
 	return DynObjectRef(handle, obj_ptr);
 }
 
-
 const char *CCDynamicArray::GetFieldPtr(const char *address, intptr_t offset) {
 	return address + offset;
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 259849c3986..46d2e457728 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -27,10 +27,23 @@
 
 namespace AGS3 {
 
-#define CC_DYNAMIC_ARRAY_TYPE_NAME "CCDynamicArray"
 #define ARRAY_MANAGED_TYPE_FLAG    0x80000000
 
 struct CCDynamicArray final : ICCDynamicObject {
+public:
+	static const char *TypeName;
+
+	struct Header {
+		// May contain ARRAY_MANAGED_TYPE_FLAG
+		uint32_t ElemCount = 0u;
+		// TODO: refactor and store "elem size" instead
+		uint32_t TotalSize = 0u;
+	};
+
+	inline static const Header &GetHeader(const char *address) {
+		return reinterpret_cast<const Header &>(*(address - MemHeaderSz));
+	}
+
 	// return the type name of the object
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
@@ -53,6 +66,12 @@ struct CCDynamicArray final : ICCDynamicObject {
 	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
 	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
 	void    WriteFloat(const char *address, intptr_t offset, float val) override;
+
+private:
+	// The size of the array's header in memory, prepended to the element data
+	static const size_t MemHeaderSz = sizeof(Header);
+	// The size of the serialized header
+	static const size_t FileHeaderSz = sizeof(uint32_t) * 2;
 };
 
 // Helper functions for setting up dynamic arrays.
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 60fbd0014b1..c98b2051234 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -280,7 +280,7 @@ int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) {
 					serializeBuffer.resize(numBytes);
 				}
 				in->Read(&serializeBuffer.front(), numBytes);
-				if (strcmp(typeNameBuffer, CC_DYNAMIC_ARRAY_TYPE_NAME) == 0) {
+				if (strcmp(typeNameBuffer, CCDynamicArray::TypeName) == 0) {
 					_GP(globalDynamicArray).Unserialize(i, &serializeBuffer.front(), numBytes);
 				} else {
 					reader->Unserialize(i, typeNameBuffer, &serializeBuffer.front(), numBytes);
@@ -304,7 +304,7 @@ int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) {
 				serializeBuffer.resize(numBytes);
 			}
 			in->Read(&serializeBuffer.front(), numBytes);
-			if (strcmp(typeNameBuffer, CC_DYNAMIC_ARRAY_TYPE_NAME) == 0) {
+			if (strcmp(typeNameBuffer, CCDynamicArray::TypeName) == 0) {
 				_GP(globalDynamicArray).Unserialize(handle, &serializeBuffer.front(), numBytes);
 			} else {
 				reader->Unserialize(handle, typeNameBuffer, &serializeBuffer.front(), numBytes);
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index ec3c4a3e5b4..8acb703b648 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -998,22 +998,23 @@ int ccInstance::Run(int32_t curpc) {
 			// TODO: test reg[MAR] type here;
 			// That might be dynamic object, but also a non-managed dynamic array, "allocated"
 			// on global or local memspace (buffer)
-			int32_t upperBoundInBytes = *((int32_t *)(registers[SREG_MAR].GetPtrWithOffset() - 4));
+			const char *arr_ptr = registers[SREG_MAR].GetPtrWithOffset();
+			const auto &hdr = CCDynamicArray::GetHeader(arr_ptr);
 			if ((reg1.IValue < 0) ||
-			        (reg1.IValue >= upperBoundInBytes)) {
-				int32_t upperBound = *((int32_t *)(registers[SREG_MAR].GetPtrWithOffset() - 8)) & (~ARRAY_MANAGED_TYPE_FLAG);
-				if (upperBound <= 0) {
-					cc_error("!Array has an invalid size (%d) and cannot be accessed", upperBound);
+				(static_cast<uint32_t>(reg1.IValue) >= hdr.TotalSize)) {
+				int elem_count = hdr.ElemCount & (~ARRAY_MANAGED_TYPE_FLAG);
+				if (elem_count <= 0) {
+					cc_error("!Array has an invalid size (%d) and cannot be accessed", elem_count);
 				} else {
-					int elementSize = (upperBoundInBytes / upperBound);
-					cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue / elementSize, upperBound - 1);
+					int elementSize = (hdr.TotalSize / elem_count);
+					cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue / elementSize, elem_count - 1);
 				}
 				return -1;
 			}
 			break;
 		}
 
-		// 64 bit: Handles are always 32 bit values. They are not C pointer.
+			// 64 bit: Handles are always 32 bit values. They are not C pointer.
 
 		case SCMD_MEMREADPTR: {
 			auto &reg1 = registers[codeOp.Arg1i()];


Commit: 5e9e3e8915b62cbc972c46b01059d81fad55a611
    https://github.com/scummvm/scummvm/commit/5e9e3e8915b62cbc972c46b01059d81fad55a611
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: bring DynArray and UserObject's unserialization to uniformity

* Move DynArray unserialization to the AGSDeSerializer class,
because ManagedObjectPool's purpose is a storage, not object creation operations.
* Unified DynArray and UserObject deserialization, including use of Stream instead
of copying values from the raw buffer.
* Add some comments for the future refactor considerations.

from upstream 948bf8efbe77e1ef9955c744f212bc3ba064f325

Changed paths:
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h


diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index b4df52a3f4b..eb51e578eda 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -71,13 +71,12 @@ int CCDynamicArray::Serialize(const char *address, char *buffer, int bufsize) {
 	return static_cast<int32_t>(mems.GetPosition());
 }
 
-void CCDynamicArray::Unserialize(int index, const char *serializedData, int dataSize) {
-	char *new_arr = new char[(dataSize - FileHeaderSz) + MemHeaderSz];
-	MemoryStream mems(reinterpret_cast<const uint8_t *>(serializedData), dataSize);
+void CCDynamicArray::Unserialize(int index, Stream *in, size_t data_sz) {
+	char *new_arr = new char[(data_sz - FileHeaderSz) + MemHeaderSz];
 	Header &hdr = reinterpret_cast<Header &>(*new_arr);
-	hdr.ElemCount = mems.ReadInt32();
-	hdr.TotalSize = mems.ReadInt32();
-	memcpy(new_arr + MemHeaderSz, serializedData + FileHeaderSz, dataSize - FileHeaderSz);
+	hdr.ElemCount = in->ReadInt32();
+	hdr.TotalSize = in->ReadInt32();
+	in->Read(new_arr + MemHeaderSz, data_sz - FileHeaderSz);
 	ccRegisterUnserializedObject(index, &new_arr[MemHeaderSz], this);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 46d2e457728..06aefa391c2 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -24,6 +24,7 @@
 
 #include "common/std/vector.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_object.h"   // ICCDynamicObject
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
@@ -50,7 +51,7 @@ public:
 	// serialize the object into BUFFER (which is BUFSIZE bytes)
 	// return number of bytes used
 	int Serialize(const char *address, char *buffer, int bufsize) override;
-	virtual void Unserialize(int index, const char *serializedData, int dataSize);
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 	// Create managed array object and return a pointer to the beginning of a buffer
 	DynObjectRef Create(int numElements, int elementSize, bool isManagedType);
 
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index e8976fefbe1..4a5aced3516 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -24,10 +24,11 @@
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
+#include "ags/engine/ac/dynobj/cc_dynamic_array.h"
+#include "ags/engine/ac/dynobj/script_user_object.h"
 #include "ags/engine/ac/dynobj/script_camera.h"
 #include "ags/engine/ac/dynobj/script_containers.h"
 #include "ags/engine/ac/dynobj/script_file.h"
-#include "ags/engine/ac/dynobj/script_user_object.h"
 #include "ags/engine/ac/dynobj/script_viewport.h"
 #include "ags/engine/ac/game.h"
 #include "ags/engine/debugging/debug_log.h"
@@ -52,7 +53,19 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 	size_t data_sz = static_cast<size_t>(dataSize);
 	MemoryStream mems(reinterpret_cast<const uint8_t *>(serializedData), dataSize);
 
-	if (strcmp(objectType, "GUIObject") == 0) {
+	// TODO: consider this: there are object types that are part of the
+	// script's foundation, because they are created by the bytecode ops:
+	// such as DynamicArray and UserObject. *Maybe* these should be moved
+	// to certain "base serializer" class which guarantees their restoration.
+	//
+	// TODO: should we support older save versions here (DynArray, UserObj)?
+	// might have to use older class names to distinguish save formats
+	if (strcmp(objectType, CCDynamicArray::TypeName) == 0) {
+		_GP(globalDynamicArray).Unserialize(index, &mems, data_sz);
+	} else if (strcmp(objectType, ScriptUserObject::TypeName) == 0) {
+		ScriptUserObject *suo = new ScriptUserObject();
+		suo->Unserialize(index, &mems, data_sz);
+	} else if (strcmp(objectType, "GUIObject") == 0) {
 		_GP(ccDynamicGUIObject).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Character") == 0) {
 		_GP(ccDynamicCharacter).Unserialize(index, &mems, data_sz);
@@ -105,9 +118,6 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 		Viewport_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Camera2") == 0) {
 		Camera_Unserialize(index, &mems, data_sz);
-	} else if (strcmp(objectType, "UserObject") == 0) {
-		ScriptUserObject *suo = new ScriptUserObject();
-		suo->Unserialize(index, &mems, data_sz);
 	} else if (!unserialize_audio_script_object(index, objectType, &mems, data_sz)) {
 		// check if the type is read by a plugin
 		for (int ii = 0; ii < _G(numPluginReaders); ii++) {
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index c98b2051234..b45f9d5d3ec 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -21,7 +21,6 @@
 
 #include "common/std/vector.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_array.h" // globalDynamicArray, constants
 #include "ags/shared/debugging/out.h"
 #include "ags/shared/util/string_utils.h"               // fputstring, etc
 #include "ags/shared/script/cc_common.h"
@@ -280,11 +279,8 @@ int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) {
 					serializeBuffer.resize(numBytes);
 				}
 				in->Read(&serializeBuffer.front(), numBytes);
-				if (strcmp(typeNameBuffer, CCDynamicArray::TypeName) == 0) {
-					_GP(globalDynamicArray).Unserialize(i, &serializeBuffer.front(), numBytes);
-				} else {
-					reader->Unserialize(i, typeNameBuffer, &serializeBuffer.front(), numBytes);
-				}
+				// Delegate work to ICCObjectReader
+				reader->Unserialize(i, typeNameBuffer, &serializeBuffer.front(), numBytes);
 				objects[i].refCount = in->ReadInt32();
 				ManagedObjectLog("Read handle = %d", objects[i].handle);
 			}
@@ -304,11 +300,8 @@ int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) {
 				serializeBuffer.resize(numBytes);
 			}
 			in->Read(&serializeBuffer.front(), numBytes);
-			if (strcmp(typeNameBuffer, CCDynamicArray::TypeName) == 0) {
-				_GP(globalDynamicArray).Unserialize(handle, &serializeBuffer.front(), numBytes);
-			} else {
-				reader->Unserialize(handle, typeNameBuffer, &serializeBuffer.front(), numBytes);
-			}
+			// Delegate work to ICCObjectReader
+			reader->Unserialize(handle, typeNameBuffer, &serializeBuffer.front(), numBytes);
 			objects[handle].refCount = in->ReadInt32();
 			ManagedObjectLog("Read handle = %d", objects[i].handle);
 		}
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index 9c265295852..1b153a6c709 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -81,7 +81,6 @@ private:
 	std::unordered_map<const char *, int32_t, Pointer_Hash> handleByAddress;
 
 	int Remove(ManagedObject &o, bool force = false);
-
 	void RunGarbageCollection();
 
 public:
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 119ab47283d..e365bb549ec 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -28,9 +28,11 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+const char *ScriptUserObject::TypeName = "UserObject";
+
 // return the type name of the object
 const char *ScriptUserObject::GetType() {
-	return "UserObject";
+	return TypeName;
 }
 
 ScriptUserObject::ScriptUserObject()
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index 1454bfe358c..a5fedcb501f 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -28,12 +28,15 @@
 #ifndef AGS_ENGINE_DYNOBJ__SCRIPTUSERSTRUCT_H
 #define AGS_ENGINE_DYNOBJ__SCRIPTUSERSTRUCT_H
 
-#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
 struct ScriptUserObject final : ICCDynamicObject {
 public:
+	static const char *TypeName;
+
 	ScriptUserObject();
 
 protected:


Commit: 43ff49b1ac77e77030256bcfcd8225637bb95788
    https://github.com/scummvm/scummvm/commit/43ff49b1ac77e77030256bcfcd8225637bb95788
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: CCDynamicArray and ScriptUserObject inherit AGSCCDynamicObject

This is primarily for removing duplicating Read/Write method implementations
for CCDynamicArray, and making these two inherit common Serialize() and
CalcSerializeSize() methods.

I had to add "address" parameter to CalcSerializeSize(), which resulted in
small inessential changes around all the other dynamic manager classes.

>From upstream 7e0dff2c8486ba24c87199a66e9aee4be1659834

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_channel.h
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.h
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.h
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui.h
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.h
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.h
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.h
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_object.h
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_region.h
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_camera.h
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_date_time.h
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_dict.h
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_overlay.h
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_set.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.h
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/dynobj/script_viewport.h


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index 47dc7e0b6ad..2cd4a19bb6c 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -38,7 +38,7 @@ int AGSCCDynamicObject::Dispose(const char *address, bool force) {
 int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize) {
 	// If the required space is larger than the provided buffer,
 	// then return negated required space, notifying the caller that a larger buffer is necessary
-	size_t req_size = CalcSerializeSize();
+	size_t req_size = CalcSerializeSize(address);
 	if (bufsize < 0 || req_size > static_cast<size_t>(bufsize))
 		return -(static_cast<int32_t>(req_size));
 
diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index bd3d4626589..32c9afce03a 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -19,6 +19,21 @@
  *
  */
 
+//=============================================================================
+//
+// The common implementation for ICCDynamicObject interface.
+// Intended to be used as a parent class for majority of the
+// dynamic object managers.
+//
+// Basic implementation of:
+// * Serialization from a raw buffer; provides a virtual function that
+//   accepts Stream, to be implemented in children instead.
+// * Provides Unserialize interface that accepts Stream.
+// * Data Read/Write methods that treat the contents of the object as
+//   a raw byte buffer.
+//
+//=============================================================================
+
 #ifndef AGS_ENGINE_AC_DYNOBJ_CCDYNAMIC_OBJECT_H
 #define AGS_ENGINE_AC_DYNOBJ_CCDYNAMIC_OBJECT_H
 
@@ -56,7 +71,7 @@ public:
 protected:
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	virtual size_t CalcSerializeSize() = 0;
+	virtual size_t CalcSerializeSize(const char *address) = 0;
 	// Write object data into the provided stream
 	virtual void Serialize(const char *address, AGS::Shared::Stream *out) = 0;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index b563a1e078b..8918a4bd10c 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -34,7 +34,7 @@ const char *CCAudioChannel::GetType() {
 	return "AudioChannel";
 }
 
-size_t CCAudioChannel::CalcSerializeSize() {
+size_t CCAudioChannel::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.h b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
index 9e9f0911a1d..1aae5ee3abe 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
@@ -31,7 +31,7 @@ struct CCAudioChannel final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index 939937acf6c..3098b9230d2 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -33,7 +33,7 @@ const char *CCAudioClip::GetType() {
 	return "AudioClip";
 }
 
-size_t CCAudioClip::CalcSerializeSize() {
+size_t CCAudioClip::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.h b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
index e9a367a11b8..4afadee80a5 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
@@ -31,7 +31,7 @@ struct CCAudioClip final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index eab51b5e92b..3f7864e7870 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -39,7 +39,7 @@ const char *CCCharacter::GetType() {
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-size_t CCCharacter::CalcSerializeSize() {
+size_t CCCharacter::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index 96ee1607b68..d25e3052f98 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -36,7 +36,7 @@ struct CCCharacter final : AGSCCDynamicObject {
 	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index a1e9db0c3fb..4bdd8281a4a 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -36,7 +36,7 @@ const char *CCDialog::GetType() {
 	return "Dialog";
 }
 
-size_t CCDialog::CalcSerializeSize() {
+size_t CCDialog::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h
index f4556ecc1da..eaaf00e19e5 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.h
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.h
@@ -34,7 +34,7 @@ struct CCDialog final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index eb51e578eda..3d939ca0382 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -57,18 +57,16 @@ int CCDynamicArray::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-int CCDynamicArray::Serialize(const char *address, char *buffer, int bufsize) {
+size_t CCDynamicArray::CalcSerializeSize(const char *address) {
 	const Header &hdr = GetHeader(address);
-	int sizeToWrite = hdr.TotalSize + FileHeaderSz;
-	if (sizeToWrite > bufsize) {
-		// buffer not big enough, ask for a bigger one
-		return -sizeToWrite;
-	}
-	MemoryStream mems(reinterpret_cast<uint8_t *>(buffer), bufsize, kStream_Write);
-	mems.WriteInt32(hdr.ElemCount);
-	mems.WriteInt32(hdr.TotalSize);
-	mems.Write(address, hdr.TotalSize); // elements
-	return static_cast<int32_t>(mems.GetPosition());
+	return hdr.TotalSize + FileHeaderSz;
+}
+
+void CCDynamicArray::Serialize(const char *address, AGS::Shared::Stream *out) {
+	const Header &hdr = GetHeader(address);
+	out->WriteInt32(hdr.ElemCount);
+	out->WriteInt32(hdr.TotalSize);
+	out->Write(address, hdr.TotalSize); // elements
 }
 
 void CCDynamicArray::Unserialize(int index, Stream *in, size_t data_sz) {
@@ -96,50 +94,6 @@ DynObjectRef CCDynamicArray::Create(int numElements, int elementSize, bool isMan
 	return DynObjectRef(handle, obj_ptr);
 }
 
-const char *CCDynamicArray::GetFieldPtr(const char *address, intptr_t offset) {
-	return address + offset;
-}
-
-void CCDynamicArray::Read(const char *address, intptr_t offset, void *dest, int size) {
-	memcpy(dest, address + offset, size);
-}
-
-uint8_t CCDynamicArray::ReadInt8(const char *address, intptr_t offset) {
-	return *(const uint8_t *)(address + offset);
-}
-
-int16_t CCDynamicArray::ReadInt16(const char *address, intptr_t offset) {
-	return *(const int16_t *)(address + offset);
-}
-
-int32_t CCDynamicArray::ReadInt32(const char *address, intptr_t offset) {
-	return *(const int32_t *)(address + offset);
-}
-
-float CCDynamicArray::ReadFloat(const char *address, intptr_t offset) {
-	return *(const float *)(address + offset);
-}
-
-void CCDynamicArray::Write(const char *address, intptr_t offset, void *src, int size) {
-	memcpy((void *)(const_cast<char *>(address) + offset), src, size);
-}
-
-void CCDynamicArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-	*(uint8_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void CCDynamicArray::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	*(int16_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void CCDynamicArray::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	*(int32_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void CCDynamicArray::WriteFloat(const char *address, intptr_t offset, float val) {
-	*(float *)(const_cast<char *>(address) + offset) = val;
-}
-
 DynObjectRef DynamicArrayHelpers::CreateStringArray(const std::vector<const char *> items) {
 	// NOTE: we need element size of "handle" for array of managed pointers
 	DynObjectRef arr = _GP(globalDynamicArray).Create(items.size(), sizeof(int32_t), true);
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 06aefa391c2..c5cc63ae409 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -22,15 +22,15 @@
 #ifndef AGS_ENGINE_AC_DYNOBJ_CC_DYNAMICARRAY_H
 #define AGS_ENGINE_AC_DYNOBJ_CC_DYNAMICARRAY_H
 
-#include "common/std/vector.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"   // ICCDynamicObject
+#include "ags/lib/std/vector.h"
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
 #define ARRAY_MANAGED_TYPE_FLAG    0x80000000
 
-struct CCDynamicArray final : ICCDynamicObject {
+struct CCDynamicArray final : AGSCCDynamicObject {
 public:
 	static const char *TypeName;
 
@@ -48,31 +48,21 @@ public:
 	// return the type name of the object
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 	// Create managed array object and return a pointer to the beginning of a buffer
 	DynObjectRef Create(int numElements, int elementSize, bool isManagedType);
 
-	// Legacy support for reading and writing object values by their relative offset
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
-
 private:
 	// The size of the array's header in memory, prepended to the element data
 	static const size_t MemHeaderSz = sizeof(Header);
 	// The size of the serialized header
 	static const size_t FileHeaderSz = sizeof(uint32_t) * 2;
+
+	// Savegame serialization
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize(const char *address) override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 // Helper functions for setting up dynamic arrays.
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index 3ea12fd40fb..d7035573d28 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -34,7 +34,7 @@ const char *CCGUI::GetType() {
 	return "GUI";
 }
 
-size_t CCGUI::CalcSerializeSize() {
+size_t CCGUI::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h
index fc6c02e7b2c..24bceda2133 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui.h
@@ -34,7 +34,7 @@ struct CCGUI final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index f5bb5f74ab5..c0d1f1c90bf 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -36,7 +36,7 @@ const char *CCGUIObject::GetType() {
 	return "GUIObject";
 }
 
-size_t CCGUIObject::CalcSerializeSize() {
+size_t CCGUIObject::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t) * 2;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.h b/engines/ags/engine/ac/dynobj/cc_gui_object.h
index c27ec74193b..daec1cec26d 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.h
@@ -34,7 +34,7 @@ struct CCGUIObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index 1bc1072b995..75279b3747b 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -36,7 +36,7 @@ const char *CCHotspot::GetType() {
 	return "Hotspot";
 }
 
-size_t CCHotspot::CalcSerializeSize() {
+size_t CCHotspot::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h
index b6314dac312..a8d3fccd452 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.h
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h
@@ -34,7 +34,7 @@ struct CCHotspot final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index 63273408786..41e80685b65 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -35,7 +35,7 @@ const char *CCInventory::GetType() {
 	return "Inventory";
 }
 
-size_t CCInventory::CalcSerializeSize() {
+size_t CCInventory::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h
index d4cc8a3e939..961e1a76332 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.h
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.h
@@ -34,7 +34,7 @@ struct CCInventory final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index 93a3f540ed6..0fc40b7e090 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -36,7 +36,7 @@ const char *CCObject::GetType() {
 	return "Object";
 }
 
-size_t CCObject::CalcSerializeSize() {
+size_t CCObject::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h
index 84d4e3547ba..7d3335ec022 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_object.h
@@ -34,7 +34,7 @@ struct CCObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index ee78bc1f81d..6850ba26912 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -36,7 +36,7 @@ const char *CCRegion::GetType() {
 	return "Region";
 }
 
-size_t CCRegion::CalcSerializeSize() {
+size_t CCRegion::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h
index bdc89b341f6..6d9f64c7e18 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.h
+++ b/engines/ags/engine/ac/dynobj/cc_region.h
@@ -34,7 +34,7 @@ struct CCRegion final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index 5213ffeee99..32647b59098 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -44,7 +44,7 @@ int ScriptCamera::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-size_t ScriptCamera::CalcSerializeSize() {
+size_t ScriptCamera::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_camera.h b/engines/ags/engine/ac/dynobj/script_camera.h
index 555e580122f..557fa867b20 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.h
+++ b/engines/ags/engine/ac/dynobj/script_camera.h
@@ -48,7 +48,7 @@ public:
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index 3757d5ccf09..27eadd63cb3 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -37,7 +37,7 @@ const char *ScriptDateTime::GetType() {
 	return "DateTime";
 }
 
-size_t ScriptDateTime::CalcSerializeSize() {
+size_t ScriptDateTime::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t) * 7;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.h b/engines/ags/engine/ac/dynobj/script_date_time.h
index 1394348b2bc..e4ca7a750e3 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.h
+++ b/engines/ags/engine/ac/dynobj/script_date_time.h
@@ -39,7 +39,7 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index 50cc03f2b43..51a279ac288 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -32,7 +32,7 @@ const char *ScriptDialogOptionsRendering::GetType() {
 	return "DialogOptionsRendering";
 }
 
-size_t ScriptDialogOptionsRendering::CalcSerializeSize() {
+size_t ScriptDialogOptionsRendering::CalcSerializeSize(const char * /*address*/) {
 	return 0;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
index 5f6ca19b4e7..9006b476db5 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
@@ -49,7 +49,7 @@ struct ScriptDialogOptionsRendering final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index 9c18971133f..efc5dfecde5 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -34,6 +34,10 @@ const char *ScriptDictBase::GetType() {
 	return "StringDictionary";
 }
 
+size_t ScriptDictBase::CalcSerializeSize(const char * /*address*/) {
+	return CalcContainerSize();
+}
+
 void ScriptDictBase::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
diff --git a/engines/ags/engine/ac/dynobj/script_dict.h b/engines/ags/engine/ac/dynobj/script_dict.h
index f200610741d..7e6b3e15a4d 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.h
+++ b/engines/ags/engine/ac/dynobj/script_dict.h
@@ -64,10 +64,13 @@ public:
 	virtual void GetKeys(std::vector<const char *> &buf) const = 0;
 	virtual void GetValues(std::vector<const char *> &buf) const = 0;
 protected:
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
 private:
+	virtual size_t CalcContainerSize() = 0;
 	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
 	virtual void UnserializeContainer(AGS::Shared::Stream *in) = 0;
 };
@@ -140,7 +143,7 @@ private:
 	}
 	void DeleteItem(ConstIterator /*it*/) { /* do nothing */ }
 
-	size_t CalcSerializeSize() override {
+	size_t CalcContainerSize() override {
 		// 2 class properties + item count
 		size_t total_sz = sizeof(int32_t) * 3;
 		// (int32 + string buffer) per item
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index cbebdaf0791..55069c648ed 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -75,7 +75,7 @@ const char *ScriptDrawingSurface::GetType() {
 	return "DrawingSurface";
 }
 
-size_t ScriptDrawingSurface::CalcSerializeSize() {
+size_t ScriptDrawingSurface::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t) * 9;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index 1d0f2b8c910..8feef052766 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -61,7 +61,7 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index e1ec5ac2d00..4d32b4d91d6 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -41,7 +41,7 @@ const char *ScriptDynamicSprite::GetType() {
 	return "DynamicSprite";
 }
 
-size_t ScriptDynamicSprite::CalcSerializeSize() {
+size_t ScriptDynamicSprite::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
index 319075f1b34..b4b7a4f8ebc 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
@@ -38,7 +38,7 @@ struct ScriptDynamicSprite final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 055f820b3d3..7ae75e3e9f7 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -57,7 +57,7 @@ const char *ScriptOverlay::GetType() {
 	return "Overlay";
 }
 
-size_t ScriptOverlay::CalcSerializeSize() {
+size_t ScriptOverlay::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t) * 4;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.h b/engines/ags/engine/ac/dynobj/script_overlay.h
index 5b29b3ccdea..1a40d4bcbd5 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.h
+++ b/engines/ags/engine/ac/dynobj/script_overlay.h
@@ -37,7 +37,7 @@ struct ScriptOverlay final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index eaeeeb3f7e4..479de145300 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -35,6 +35,10 @@ const char *ScriptSetBase::GetType() {
 	return "StringSet";
 }
 
+size_t ScriptSetBase::CalcSerializeSize(const char * /*address*/) {
+	return CalcContainerSize();
+}
+
 void ScriptSetBase::Serialize(const char *address, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
diff --git a/engines/ags/engine/ac/dynobj/script_set.h b/engines/ags/engine/ac/dynobj/script_set.h
index 2e6bd512b66..31e62a7d7b6 100644
--- a/engines/ags/engine/ac/dynobj/script_set.h
+++ b/engines/ags/engine/ac/dynobj/script_set.h
@@ -62,10 +62,13 @@ public:
 	virtual void GetItems(std::vector<const char *> &buf) const = 0;
 
 protected:
+	// Calculate and return required space for serialization, in bytes
+	virtual size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
 private:
+	virtual size_t CalcContainerSize() = 0;
 	virtual void SerializeContainer(AGS::Shared::Stream *out) = 0;
 	virtual void UnserializeContainer(AGS::Shared::Stream *in) = 0;
 };
@@ -117,7 +120,7 @@ private:
 	}
 	void DeleteItem(ConstIterator /*it*/) { /* do nothing */ }
 
-	size_t CalcSerializeSize() override {
+	size_t CalcContainerSize() override {
 		// 2 class properties + item count
 		size_t total_sz = sizeof(int32_t) * 3;
 		// (int32 + string buffer) per item
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index f0baa5302e9..8c66ba8fde3 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -46,7 +46,7 @@ const char *ScriptString::GetType() {
 	return "String";
 }
 
-size_t ScriptString::CalcSerializeSize() {
+size_t ScriptString::CalcSerializeSize(const char * /*address*/) {
 	return _len + 1 + sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index 4d6863d474a..3840d48815c 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -42,7 +42,7 @@ struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index e365bb549ec..e500a1894e6 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -35,11 +35,6 @@ const char *ScriptUserObject::GetType() {
 	return TypeName;
 }
 
-ScriptUserObject::ScriptUserObject()
-	: _size(0)
-	, _data(nullptr) {
-}
-
 ScriptUserObject::~ScriptUserObject() {
 	delete[] _data;
 }
@@ -72,15 +67,14 @@ int ScriptUserObject::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-int ScriptUserObject::Serialize(const char *address, char *buffer, int bufsize) {
-	if (_size > bufsize)
-		// buffer not big enough, ask for a bigger one
-		return -_size;
-
-	memcpy(buffer, _data, _size);
+size_t ScriptUserObject::CalcSerializeSize(const char * /*address*/) {
 	return _size;
 }
 
+void ScriptUserObject::Serialize(const char * /*address*/, AGS::Shared::Stream *out) {
+	out->Write(_data, _size);
+}
+
 void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
 	Create(nullptr, in, data_sz);
 	ccRegisterUnserializedObject(index, this, this);
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index a5fedcb501f..a2fdf92af53 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -28,16 +28,16 @@
 #ifndef AGS_ENGINE_DYNOBJ__SCRIPTUSERSTRUCT_H
 #define AGS_ENGINE_DYNOBJ__SCRIPTUSERSTRUCT_H
 
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
-struct ScriptUserObject final : ICCDynamicObject {
+struct ScriptUserObject final : AGSCCDynamicObject {
 public:
 	static const char *TypeName;
 
-	ScriptUserObject();
+	ScriptUserObject() = default;
 
 protected:
 	virtual ~ScriptUserObject();
@@ -49,10 +49,7 @@ public:
 	// return the type name of the object
 	const char *GetType() override;
 	int Dispose(const char *address, bool force) override;
-	// serialize the object into BUFFER (which is BUFSIZE bytes)
-	// return number of bytes used
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	// Support for reading and writing object values by their relative offset
 	const char *GetFieldPtr(const char *address, intptr_t offset) override;
@@ -74,8 +71,14 @@ private:
 	// enough. Since this interface is also a part of Plugin API, we would
 	// need more significant change to program before we could use different
 	// approach.
-	int32_t  _size;
-	char *_data;
+	int32_t _size = 0;
+	char *_data = nullptr;
+
+	// Savegame serialization
+	// Calculate and return required space for serialization, in bytes
+	size_t CalcSerializeSize(const char *address) override;
+	// Write object data into the provided stream
+	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
 
 
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index 27076059324..b7b204f8a3e 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -37,7 +37,7 @@ const char *ScriptViewFrame::GetType() {
 	return "ViewFrame";
 }
 
-size_t ScriptViewFrame::CalcSerializeSize() {
+size_t ScriptViewFrame::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t) * 3;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.h b/engines/ags/engine/ac/dynobj/script_view_frame.h
index 43e68a5aba6..690df07d175 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.h
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.h
@@ -38,7 +38,7 @@ struct ScriptViewFrame final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 };
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index 6a2ab596009..5c79d313801 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -44,7 +44,7 @@ int ScriptViewport::Dispose(const char *address, bool force) {
 	return 1;
 }
 
-size_t ScriptViewport::CalcSerializeSize() {
+size_t ScriptViewport::CalcSerializeSize(const char * /*address*/) {
 	return sizeof(int32_t);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.h b/engines/ags/engine/ac/dynobj/script_viewport.h
index 9d930a87348..c5c6f4fb3ac 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.h
+++ b/engines/ags/engine/ac/dynobj/script_viewport.h
@@ -48,7 +48,7 @@ public:
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize() override;
+	size_t CalcSerializeSize(const char *address) override;
 	// Write object data into the provided stream
 	void Serialize(const char *address, AGS::Shared::Stream *out) override;
 


Commit: d5bf4fee8e880da22f2979c121ffaa5e4b1f338f
    https://github.com/scummvm/scummvm/commit/d5bf4fee8e880da22f2979c121ffaa5e4b1f338f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidied use of Character::animating and flags

from upstream 35b5cde74c166f98be62ab9de44b0bda02b80741

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index a92312b3a29..06b5edd945c 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2054,11 +2054,9 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
 	if ((sframe < 0) || (sframe >= _GP(views)[chap->view].loops[loopn].numFrames))
 		quit("!AnimateCharacter: invalid starting frame number specified");
 	Character_StopMoving(chap);
-	chap->animating = 1;
-	if (rept) chap->animating |= CHANIM_REPEAT;
-	if (direction) chap->animating |= CHANIM_BACKWARDS;
 
-	chap->animating |= ((sppd << 8) & 0xff00);
+	chap->set_animating(rept, direction == 0, sppd);
+
 	chap->loop = loopn;
 	// reverse animation starts at the *previous frame*
 	if (direction) {
@@ -2653,10 +2651,10 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 
 			oldview = speakingChar->view;
 			oldloop = speakingChar->loop;
-			speakingChar->animating = 1 | (GetCharacterSpeechAnimationDelay(speakingChar) << 8);
-			// only repeat if speech, not thought
-			if (!isThought)
-				speakingChar->animating |= CHANIM_REPEAT;
+
+			speakingChar->set_animating(!isThought, // only repeat if speech, not thought
+										true,       // always forwards
+										GetCharacterSpeechAnimationDelay(speakingChar));
 
 			speakingChar->view = useview;
 			speakingChar->frame = 0;
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 0b116640f1e..456f8ee555d 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -309,8 +309,8 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
 				done_anim = true;
 				frame = 0;
 			} else {
-				if (!CycleViewAnim(view, loop, frame, (animating & CHANIM_BACKWARDS) == 0,
-					(animating & CHANIM_REPEAT) ? ANIM_REPEAT : ANIM_ONCE)) {
+				if (!CycleViewAnim(view, loop, frame, get_anim_forwards(),
+					(get_anim_repeat() ? ANIM_REPEAT : ANIM_ONCE))) {
 					done_anim = true; // finished animating
 					// end of idle anim
 					if (idleleft < 0) {
@@ -331,7 +331,7 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
 			if (idleleft < 0)
 				wait += idle_anim_speed;
 			else
-				wait += (animating >> 8) & 0x00ff;
+				wait += get_anim_delay();
 
 			if (frame != oldframe)
 				CheckViewFrameForCharacter(this);
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index 7c9132b3925..ca4432840ec 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -70,37 +70,40 @@ enum LegacyScriptAlignment {
 const int LegacyMusicMasterVolumeAdjustment = 60;
 const int LegacyRoomVolumeFactor = 30;
 
-// These numbers were chosen arbitrarily -- the idea is
+// Common command arguments
+// HISTORICAL NOTE: These numbers were chosen arbitrarily -- the idea is
 // to make sure that the user gets the parameters the right way round
+// Walk (pathfinding) modes
 #define ANYWHERE       304
 #define WALKABLE_AREAS 305
+// Blocking / non-blocking action
 #define BLOCKING       919
 #define IN_BACKGROUND  920
+// Direction of animation
 #define FORWARDS       1062
 #define BACKWARDS      1063
+// Stop / don't stop when changing a view
 #define STOP_MOVING    1
 #define KEEP_MOVING    0
+// Animation flow mode; internal flags, only saved in structs (not API)
+// Animates once and stops at the *last* frame
+#define ANIM_ONCE      1
+// Animates infinitely until stopped by command
+#define ANIM_REPEAT    2
+// Animates once and stops, resetting to the very first frame
+#define ANIM_ONCERESET 3
+
+// ROOM OBJECT ANIM FLAGS (INTERNAL)
+#define ANIM_BACKWARDS   10
 
 #define SCR_NO_VALUE   31998
 #define SCR_COLOR_TRANSPARENT -1
 
 
-
-#define NUM_DIGI_VOICES     16
-#define NUM_MOD_DIGI_VOICES 12
-
 #define DEBUG_CONSOLE_NUMLINES 6
 #define TXT_SCOREBAR        29
 #define MAXSCORE _GP(play).totalscore
-#define CHANIM_REPEAT    2
-#define CHANIM_BACKWARDS 4
-#define ANIM_BACKWARDS 10
-// Animates once and stops at the *last* frame
-#define ANIM_ONCE      1
-// Animates infinitely until stopped by command
-#define ANIM_REPEAT    2
-// Animates once and stops, resetting to the very first frame
-#define ANIM_ONCERESET 3
+
 #define FONT_STATUSBAR  0
 #define FONT_NORMAL     _GP(play).normal_font
 //#define FONT_SPEECHBACK 1
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 95c9c89e373..606028f89c4 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -63,6 +63,12 @@ using namespace AGS; // FIXME later
 #define UNIFORM_WALK_SPEED  0
 #define FOLLOW_ALWAYSONTOP  0x7ffe
 
+// Character's internal flags, packed in CharacterInfo::animating
+#define CHANIM_MASK         0xFF
+#define CHANIM_ON           0x01
+#define CHANIM_REPEAT       0x02
+#define CHANIM_BACKWARDS    0x04
+
 // Length of deprecated character name field, in bytes
 #define MAX_CHAR_NAME_LEN 40
 
@@ -99,7 +105,8 @@ struct CharacterInfo {
 	short pic_xoffs; // this is fixed in screen coordinates
 	short walkwaitcounter;
 	uint16_t loop, frame;
-	short walking, animating;
+	short walking;
+	short animating; // stores CHANIM_* flags in lower byte and delay in upper byte
 	short walkspeed, animspeed;
 	short inv[MAX_INV];
 	short actx, acty;
@@ -124,6 +131,24 @@ struct CharacterInfo {
 	inline bool has_explicit_tint()  const {
 		return (flags & CHF_HASTINT) != 0;
 	}
+	inline bool is_animating() const {
+		return (animating & CHANIM_ON) != 0;
+	}
+	inline bool get_anim_repeat() const {
+		return (animating & CHANIM_REPEAT) != 0;
+	}
+	inline bool get_anim_forwards() const {
+		return (animating & CHANIM_BACKWARDS) == 0;
+	}
+	inline int get_anim_delay() const {
+		return (animating >> 8) & 0xFF;
+	}
+	inline void set_animating(bool repeat, bool forwards, int delay) {
+		animating = CHANIM_ON |
+					(CHANIM_REPEAT * repeat) |
+					(CHANIM_BACKWARDS * !forwards) |
+					((delay & 0xFF) << 8);
+	}
 
 	// [IKM] 2012-06-28: I still have to pass char_index to some of those functions
 	// either because they use it to set some variables with it,


Commit: 8ddf1c837d640f727340d20e080997536d5f53f2
    https://github.com/scummvm/scummvm/commit/8ddf1c837d640f727340d20e080997536d5f53f2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidied use of RoomObject::cycling and flags

from upstream 7e3f4118a6aa133e69d2b9bb61faa4b39e98b314

Changed paths:
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_object.h
    engines/ags/engine/ac/runtime_defines.h


diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 7d3c18b38ac..5ba94d8f395 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -238,7 +238,8 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 
 	if ((direction < 0) || (direction > 1))
 		quit("!AnimateObjectEx: invalid direction");
-	if (((rept + 1) < ANIM_ONCE) || ((rept + 1) > ANIM_ONCERESET)) // will convert to 1-based repeat below
+	rept += 1; // convert to 1-based repeat (ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET)
+	if ((rept < ANIM_ONCE) || (rept > ANIM_ONCERESET))
 		quit("!AnimateObjectEx: invalid repeat value");
 
 	// reverse animation starts at the *previous frame*
@@ -256,10 +257,9 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 
 	debug_script_log("Obj %d start anim view %d loop %d, speed %d, repeat %d, frame %d", obn, _G(objs)[obn].view + 1, loopn, spdd, rept, sframe);
 
-	_G(objs)[obn].cycling = rept + 1 + (direction * 10);
+	_G(objs)[obn].set_animating(rept, direction == 0, spdd);
 	_G(objs)[obn].loop = (uint16_t)loopn;
 	_G(objs)[obn].frame = (uint16_t)sframe;
-	_G(objs)[obn].overall_speed = spdd;
 	_G(objs)[obn].wait = spdd + _GP(views)[_G(objs)[obn].view].loops[loopn].frames[_G(objs)[obn].frame].speed;
 	int pic = _GP(views)[_G(objs)[obn].view].loops[loopn].frames[_G(objs)[obn].frame].pic;
 	_G(objs)[obn].num = Math::InRangeOrDef<uint16_t>(pic, 0);
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 2ae9e6a1ac6..4afa06e5d33 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -83,7 +83,7 @@ void RoomObject::UpdateCyclingView(int ref_id) {
 		wait--; return;
 	}
 
-	if (!CycleViewAnim(view, loop, frame, cycling < ANIM_BACKWARDS, cycling % ANIM_BACKWARDS))
+	if (!CycleViewAnim(view, loop, frame, get_anim_forwards(), get_anim_repeat()))
 		cycling = 0; // finished animating
 
 	ViewFrame *vfptr = &_GP(views)[view].loops[loop].frames[frame];
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index 1692029f650..e8b87198ac2 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -42,6 +42,9 @@ class Stream;
 
 using namespace AGS; // FIXME later
 
+// RoomObject's internal values, packed in RoomObject::cycling
+#define OBJANIM_BACKWARDS 10
+
 // IMPORTANT: exposed to plugin API as AGSObject!
 // keep that in mind if extending this struct, and dont change existing fields
 // unless you plan on adjusting plugin API as well.
@@ -59,8 +62,8 @@ struct RoomObject {
 	short baseline;       // <=0 to use Y co-ordinate; >0 for specific baseline
 	uint16_t view, loop, frame; // only used to track animation - 'num' holds the current sprite
 	short wait, moving;
-	int8  cycling;        // is it currently animating?
-	int8  overall_speed;
+	int8  cycling;        // stores OBJANIM_* flags and values
+	int8  overall_speed;  // animation delay
 	int8  on;
 	int8  flags;
 	// Down to here is a part of the plugin API
@@ -81,6 +84,25 @@ struct RoomObject {
 		return (flags & OBJF_HASTINT) != 0;
 	}
 
+	inline bool is_animating() const {
+		return (cycling > 0);
+	}
+	// repeat may be ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET
+	inline int get_anim_repeat() const {
+		return (cycling % OBJANIM_BACKWARDS);
+	}
+	inline bool get_anim_forwards() const {
+		return (cycling < OBJANIM_BACKWARDS);
+	}
+	inline int get_anim_delay() const {
+		return overall_speed;
+	}
+	// repeat may be ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET
+	inline void set_animating(int repeat, bool forwards, int delay) {
+		cycling = repeat + (!forwards * OBJANIM_BACKWARDS);
+		overall_speed = delay;
+	}
+
 	void UpdateCyclingView(int ref_id);
 
 	void ReadFromSavegame(Shared::Stream *in, int save_ver);
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index ca4432840ec..d2a426841d6 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -93,9 +93,6 @@ const int LegacyRoomVolumeFactor = 30;
 // Animates once and stops, resetting to the very first frame
 #define ANIM_ONCERESET 3
 
-// ROOM OBJECT ANIM FLAGS (INTERNAL)
-#define ANIM_BACKWARDS   10
-
 #define SCR_NO_VALUE   31998
 #define SCR_COLOR_TRANSPARENT -1
 


Commit: f75a45d38d6d63eaab4ceb11bac137c19430595c
    https://github.com/scummvm/scummvm/commit/f75a45d38d6d63eaab4ceb11bac137c19430595c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: separated RoomObject's internal ANIM flags, fix ONCERESET arg

Separated internal RoomObject's storage values for ANIM_* types into OBJANIM_ constants.
Use common ANIM_* constants in shared code, such as ValidateViewAnimParams() and CycleViewAnim().
The internal storage values of objects, characters and buttons are converted from and to ANIM_ values
as necessary.

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/room_object.h
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/gui/animating_gui_button.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/common_defines.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 36389fed62c..0da4280862c 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -89,7 +89,7 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed,
 	abtn.view = view;
 	abtn.loop = loop;
 	abtn.speed = speed;
-	abtn.repeat = repeat;
+	abtn.repeat = static_cast<bool>(repeat) ? ANIM_REPEAT : ANIM_ONCE; // for now, clamp to supported modes
 	abtn.blocking = blocking;
 	abtn.direction = direction;
 	abtn.frame = sframe;
@@ -252,8 +252,7 @@ bool UpdateAnimatingButton(int bu) {
 		abtn.wait--;
 		return true;
 	}
-	if (!CycleViewAnim(abtn.view, abtn.loop, abtn.frame, !abtn.direction,
-		abtn.repeat != 0 ? ANIM_REPEAT : ANIM_ONCE))
+	if (!CycleViewAnim(abtn.view, abtn.loop, abtn.frame, !abtn.direction, abtn.repeat))
 		return false;
 	CheckViewFrame(abtn.view, abtn.loop, abtn.frame, abtn.volume);
 	abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 06b5edd945c..8a807f95ed9 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2055,7 +2055,7 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
 		quit("!AnimateCharacter: invalid starting frame number specified");
 	Character_StopMoving(chap);
 
-	chap->set_animating(rept, direction == 0, sppd);
+	chap->set_animating(rept != 0, direction == 0, sppd);
 
 	chap->loop = loopn;
 	// reverse animation starts at the *previous frame*
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 456f8ee555d..76f56e85f8a 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -309,8 +309,7 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
 				done_anim = true;
 				frame = 0;
 			} else {
-				if (!CycleViewAnim(view, loop, frame, get_anim_forwards(),
-					(get_anim_repeat() ? ANIM_REPEAT : ANIM_ONCE))) {
+				if (!CycleViewAnim(view, loop, frame, get_anim_forwards(), get_anim_repeat())) {
 					done_anim = true; // finished animating
 					// end of idle anim
 					if (idleleft < 0) {
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 5ba94d8f395..0cb8d6e6ca0 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -238,7 +238,6 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 
 	if ((direction < 0) || (direction > 1))
 		quit("!AnimateObjectEx: invalid direction");
-	rept += 1; // convert to 1-based repeat (ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET)
 	if ((rept < ANIM_ONCE) || (rept > ANIM_ONCERESET))
 		quit("!AnimateObjectEx: invalid repeat value");
 
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 1ac2ec86b82..7e7638cfee5 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -554,9 +554,9 @@ void ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int
 	else if (direction == BACKWARDS)
 		direction = 1;
 
-	if ((repeat < 0) || (repeat > 1)) {
+	if ((repeat < ANIM_ONCE) || (repeat > ANIM_ONCERESET)) {
 		debug_script_warn("%s: invalid repeat value %d, will treat as REPEAT (1).", apiname, repeat);
-		repeat = 1;
+		repeat = ANIM_REPEAT;
 	}
 	if ((blocking < 0) || (blocking > 1)) {
 		debug_script_warn("%s: invalid blocking value %d, will treat as BLOCKING (1)", apiname, blocking);
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index e8b87198ac2..f23e8285235 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -43,6 +43,13 @@ class Stream;
 using namespace AGS; // FIXME later
 
 // RoomObject's internal values, packed in RoomObject::cycling
+// Animates once and stops at the *last* frame
+#define OBJANIM_ONCE      (ANIM_ONCE + 1)
+// Animates infinitely until stopped by command
+#define OBJANIM_REPEAT    (ANIM_REPEAT + 1)
+// Animates once and stops, resetting to the very first frame
+#define OBJANIM_ONCERESET (ANIM_ONCERESET + 1)
+// Animates backwards, as opposed to forwards
 #define OBJANIM_BACKWARDS 10
 
 // IMPORTANT: exposed to plugin API as AGSObject!
@@ -87,9 +94,10 @@ struct RoomObject {
 	inline bool is_animating() const {
 		return (cycling > 0);
 	}
-	// repeat may be ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET
+	// repeat may be ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET;
+	// get_anim_repeat() converts from OBJANIM_* to ANIM_* values
 	inline int get_anim_repeat() const {
-		return (cycling % OBJANIM_BACKWARDS);
+		return (cycling % OBJANIM_BACKWARDS) - 1;
 	}
 	inline bool get_anim_forwards() const {
 		return (cycling < OBJANIM_BACKWARDS);
@@ -99,7 +107,8 @@ struct RoomObject {
 	}
 	// repeat may be ANIM_ONCE, ANIM_REPEAT, ANIM_ONCERESET
 	inline void set_animating(int repeat, bool forwards, int delay) {
-		cycling = repeat + (!forwards * OBJANIM_BACKWARDS);
+		// convert "repeat" to 1-based OBJANIM_* flag
+		cycling = (repeat + 1) + (!forwards * OBJANIM_BACKWARDS);
 		overall_speed = delay;
 	}
 
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index d2a426841d6..08639869abd 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -85,13 +85,6 @@ const int LegacyRoomVolumeFactor = 30;
 // Stop / don't stop when changing a view
 #define STOP_MOVING    1
 #define KEEP_MOVING    0
-// Animation flow mode; internal flags, only saved in structs (not API)
-// Animates once and stops at the *last* frame
-#define ANIM_ONCE      1
-// Animates infinitely until stopped by command
-#define ANIM_REPEAT    2
-// Animates once and stops, resetting to the very first frame
-#define ANIM_ONCERESET 3
 
 #define SCR_NO_VALUE   31998
 #define SCR_COLOR_TRANSPARENT -1
diff --git a/engines/ags/engine/gui/animating_gui_button.cpp b/engines/ags/engine/gui/animating_gui_button.cpp
index 042a4c66368..54534fea50b 100644
--- a/engines/ags/engine/gui/animating_gui_button.cpp
+++ b/engines/ags/engine/gui/animating_gui_button.cpp
@@ -39,7 +39,7 @@ void AnimatingGUIButton::ReadFromSavegame(Stream *in, int cmp_ver) {
 	wait = in->ReadInt16();
 
 	if (cmp_ver < kGuiSvgVersion_36020) anim_flags &= 0x1; // restrict to repeat only
-	repeat = anim_flags & 0x1;
+	repeat = (anim_flags & 0x1) ? ANIM_REPEAT : ANIM_ONCE;
 	blocking = (anim_flags >> 1) & 0x1;
 	direction = (anim_flags >> 2) & 0x1;
 
@@ -53,7 +53,7 @@ void AnimatingGUIButton::ReadFromSavegame(Stream *in, int cmp_ver) {
 
 void AnimatingGUIButton::WriteToSavegame(Stream *out) {
 	uint16_t anim_flags =
-		(repeat & 0x1) |
+		(repeat & 0x1) | // either ANIM_ONCE or ANIM_REPEAT
 		(blocking & 0x1) << 1 |
 		(direction & 0x1) << 2;
 
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 606028f89c4..01444280f56 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -134,8 +134,8 @@ struct CharacterInfo {
 	inline bool is_animating() const {
 		return (animating & CHANIM_ON) != 0;
 	}
-	inline bool get_anim_repeat() const {
-		return (animating & CHANIM_REPEAT) != 0;
+	inline int get_anim_repeat() const {
+		return (animating & CHANIM_REPEAT) ? ANIM_REPEAT : ANIM_ONCE;
 	}
 	inline bool get_anim_forwards() const {
 		return (animating & CHANIM_BACKWARDS) == 0;
diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h
index 2ec21213368..986e02d6cb9 100644
--- a/engines/ags/shared/ac/common_defines.h
+++ b/engines/ags/shared/ac/common_defines.h
@@ -122,6 +122,15 @@ namespace AGS3 {
 #define OBJF_LEGACY_LOCKED  0x40  // object position is locked in the editor (OBSOLETE since 3.5.0)
 #define OBJF_HASLIGHT       0x80  // the tint_light is valid and treated as brightness
 
+// Animation flow mode
+// NOTE: had to move to common_defines, because used by CharacterInfo
+// Animates once and stops at the *last* frame
+#define ANIM_ONCE              0
+// Animates infinitely until stopped by command
+#define ANIM_REPEAT            1
+// Animates once and stops, resetting to the very first frame
+#define ANIM_ONCERESET         2
+
 } // namespace AGS3
 
 #endif


Commit: c4f25a16ed87b77b412a126818418f1149f87af6
    https://github.com/scummvm/scummvm/commit/c4f25a16ed87b77b412a126818418f1149f87af6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ensure that all AnimateCharacter variants lead to the same impl

from upstream eb623b4557c91e353fe6fa1bbcfc109e614ceac2

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_character.h
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/script/script.cpp
    engines/ags/plugins/core/global_api.cpp
    engines/ags/plugins/core/global_api.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 8a807f95ed9..36b67fd472f 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -177,8 +177,8 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 	}
 }
 
-void Character_AnimateEx(CharacterInfo *chaa, int loop, int delay, int repeat,
-	int blocking, int direction, int sframe, int volume = 100) {
+void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat,
+	int blocking, int direction, int sframe, int volume) {
 
 	ValidateViewAnimParams("Character.Animate", repeat, blocking, direction);
 
@@ -188,8 +188,8 @@ void Character_AnimateEx(CharacterInfo *chaa, int loop, int delay, int repeat,
 		GameLoopUntilValueIsZero(&chaa->animating);
 }
 
-void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) {
-	Character_AnimateEx(chaa, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
+void Character_Animate5(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) {
+	Character_Animate(chaa, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
 }
 
 void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos) {
@@ -2861,16 +2861,16 @@ RuntimeScriptValue Sc_Character_AddWaypoint(void *self, const RuntimeScriptValue
 }
 
 // void | CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction
-RuntimeScriptValue Sc_Character_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Animate);
+RuntimeScriptValue Sc_Character_Animate5(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Animate5);
 }
 
 RuntimeScriptValue Sc_Character_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT6(CharacterInfo, Character_AnimateEx);
+	API_OBJCALL_VOID_PINT6(CharacterInfo, Character_Animate);
 }
 
 RuntimeScriptValue Sc_Character_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT7(CharacterInfo, Character_AnimateEx);
+	API_OBJCALL_VOID_PINT7(CharacterInfo, Character_Animate);
 }
 
 // void | CharacterInfo *chaa, int room, int x, int y
@@ -3587,7 +3587,7 @@ void ScPl_Character_Think(CharacterInfo *chaa, const char *texx, ...) {
 void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_api */) {
 	ccAddExternalObjectFunction("Character::AddInventory^2",            Sc_Character_AddInventory);
 	ccAddExternalObjectFunction("Character::AddWaypoint^2",             Sc_Character_AddWaypoint);
-	ccAddExternalObjectFunction("Character::Animate^5",                 Sc_Character_Animate);
+	ccAddExternalObjectFunction("Character::Animate^5",                 Sc_Character_Animate5);
 	ccAddExternalObjectFunction("Character::Animate^6",                 Sc_Character_Animate6);
 	ccAddExternalObjectFunction("Character::Animate^7",                 Sc_Character_Animate7);
 	ccAddExternalObjectFunction("Character::ChangeRoom^3",              Sc_Character_ChangeRoom);
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index 7c6c589edc3..b64fa2f91a6 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -41,7 +41,8 @@ bool	AssertCharacter(const char *apiname, int char_id);
 
 void    Character_AddInventory(CharacterInfo *chaa, ScriptInvItem *invi, int addIndex);
 void    Character_AddWaypoint(CharacterInfo *chaa, int x, int y);
-void    Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction);
+void    Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction, int sframe = 0, int volume = 100);
+void    Character_Animate5(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction);
 void    Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos);
 void    Character_ChangeRoom(CharacterInfo *chaa, int room, int x, int y);
 void    Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, int direction);
@@ -183,6 +184,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);
 // Clears up animation parameters
diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index e13e99df1c7..9d0a147636f 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -107,13 +107,13 @@ RuntimeScriptValue Sc_AnimateButton(const RuntimeScriptValue *params, int32_t pa
 }
 
 // void  (int chh, int loopn, int sppd, int rept)
-RuntimeScriptValue Sc_scAnimateCharacter(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT4(scAnimateCharacter);
+RuntimeScriptValue Sc_AnimateCharacter4(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT4(AnimateCharacter4);
 }
 
 // void (int chh, int loopn, int sppd, int rept, int direction, int blocking)
-RuntimeScriptValue Sc_AnimateCharacterEx(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT6(AnimateCharacterEx);
+RuntimeScriptValue Sc_AnimateCharacter6(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT6(AnimateCharacter6);
 }
 
 // void (int obn,int loopn,int spdd,int rept)
@@ -1895,8 +1895,8 @@ void RegisterGlobalAPI() {
 	ccAddExternalStaticFunction("AddInventory",             Sc_add_inventory);
 	ccAddExternalStaticFunction("AddInventoryToCharacter",  Sc_AddInventoryToCharacter);
 	ccAddExternalStaticFunction("AnimateButton",            Sc_AnimateButton);
-	ccAddExternalStaticFunction("AnimateCharacter",         Sc_scAnimateCharacter);
-	ccAddExternalStaticFunction("AnimateCharacterEx",       Sc_AnimateCharacterEx);
+	ccAddExternalStaticFunction("AnimateCharacter",         Sc_AnimateCharacter4);
+	ccAddExternalStaticFunction("AnimateCharacterEx",       Sc_AnimateCharacter6);
 	ccAddExternalStaticFunction("AnimateObject",            Sc_AnimateObject);
 	ccAddExternalStaticFunction("AnimateObjectEx",          Sc_AnimateObjectEx);
 	ccAddExternalStaticFunction("AreCharactersColliding",   Sc_AreCharactersColliding);
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 971db404b7b..5501b8bdbf6 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -158,34 +158,17 @@ void SetCharacterTransparency(int obn, int trans) {
 	Character_SetTransparency(&_GP(game).chars[obn], trans);
 }
 
-void scAnimateCharacter(int chh, int loopn, int sppd, int rept) {
-	if (!is_valid_character(chh))
-		quit("AnimateCharacter: invalid character");
-
-	animate_character(&_GP(game).chars[chh], loopn, sppd, rept);
+void AnimateCharacter4(int chh, int loopn, int sppd, int rept) {
+	AnimateCharacter6(chh, loopn, sppd, rept, FORWARDS, IN_BACKGROUND);
 }
 
-void AnimateCharacterEx(int chh, int loopn, int sppd, int rept, int direction, int blocking) {
-	if ((direction < 0) || (direction > 1))
-		quit("!AnimateCharacterEx: invalid direction");
+void AnimateCharacter6(int chh, int loopn, int sppd, int rept, int direction, int blocking) {
 	if (!is_valid_character(chh))
 		quit("AnimateCharacter: invalid character");
 
-	if (direction)
-		direction = BACKWARDS;
-	else
-		direction = FORWARDS;
-
-	if (blocking)
-		blocking = BLOCKING;
-	else
-		blocking = IN_BACKGROUND;
-
-	Character_Animate(&_GP(game).chars[chh], loopn, sppd, rept, blocking, direction);
-
+	Character_Animate5(&_GP(game).chars[chh], loopn, sppd, rept, blocking, direction);
 }
 
-
 void SetPlayerCharacter(int newchar) {
 	if (!is_valid_character(newchar))
 		quit("!SetPlayerCharacter: Invalid character specified");
diff --git a/engines/ags/engine/ac/global_character.h b/engines/ags/engine/ac/global_character.h
index 607ef4eb307..aabb1f9703b 100644
--- a/engines/ags/engine/ac/global_character.h
+++ b/engines/ags/engine/ac/global_character.h
@@ -37,8 +37,8 @@ int  GetCharacterHeight(int charid);
 void SetCharacterBaseline(int obn, int basel);
 // pass trans=0 for fully solid, trans=100 for fully transparent
 void SetCharacterTransparency(int obn, int trans);
-void scAnimateCharacter(int chh, int loopn, int sppd, int rept);
-void AnimateCharacterEx(int chh, int loopn, int sppd, int rept, int direction, int blocking);
+void AnimateCharacter4(int chh, int loopn, int sppd, int rept);
+void AnimateCharacter6(int chh, int loopn, int sppd, int rept, int direction, int blocking);
 void SetPlayerCharacter(int newchar);
 void FollowCharacterEx(int who, int tofollow, int distaway, int eagerness);
 void FollowCharacter(int who, int tofollow);
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 0cb8d6e6ca0..20a18aa62cf 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -276,7 +276,7 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 static void LegacyAnimateObjectImpl(int obn, int loopn, int spdd, int rept,
 									int direction = 0, int blocking = 0) {
 	if (obn >= LEGACY_ANIMATE_CHARIDBASE) {
-		scAnimateCharacter(obn - LEGACY_ANIMATE_CHARIDBASE, loopn, spdd, rept);
+		AnimateCharacter4(obn - LEGACY_ANIMATE_CHARIDBASE, loopn, spdd, rept);
 	} else {
 		AnimateObjectImpl(obn, loopn, spdd, rept, direction, blocking, 0, 100 /* full volume */);
 	}
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index ae42c2be16a..f8feaefc8c2 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -795,12 +795,12 @@ int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int
 			get_interaction_variable(nicl->Cmds[i].Data[0].Value)->Value = IPARAM2;
 			break;
 		case 34: // Run animation
-			scAnimateCharacter(IPARAM1, IPARAM2, IPARAM3, 0);
+			AnimateCharacter4(IPARAM1, IPARAM2, IPARAM3, 0);
 			GameLoopUntilValueIsZero(&_GP(game).chars[IPARAM1].animating);
 			break;
 		case 35: // Quick animation
 			SetCharacterView(IPARAM1, IPARAM2);
-			scAnimateCharacter(IPARAM1, IPARAM3, IPARAM4, 0);
+			AnimateCharacter4(IPARAM1, IPARAM3, IPARAM4, 0);
 			GameLoopUntilValueIsZero(&_GP(game).chars[IPARAM1].animating);
 			ReleaseCharacterView(IPARAM1);
 			break;
diff --git a/engines/ags/plugins/core/global_api.cpp b/engines/ags/plugins/core/global_api.cpp
index e2755bebe72..2e01f5c75ea 100644
--- a/engines/ags/plugins/core/global_api.cpp
+++ b/engines/ags/plugins/core/global_api.cpp
@@ -80,8 +80,8 @@ void GlobalAPI::AGS_EngineStartup(IAGSEngine *engine) {
 	SCRIPT_METHOD(AddInventory, GlobalAPI::add_inventory);
 	SCRIPT_METHOD(AddInventoryToCharacter, GlobalAPI::AddInventoryToCharacter);
 	SCRIPT_METHOD(AnimateButton, GlobalAPI::AnimateButton);
-	SCRIPT_METHOD(AnimateCharacter, GlobalAPI::scAnimateCharacter);
-	SCRIPT_METHOD(AnimateCharacterEx, GlobalAPI::AnimateCharacterEx);
+	SCRIPT_METHOD(AnimateCharacter, GlobalAPI::AnimateCharacter4);
+	SCRIPT_METHOD(AnimateCharacterEx, GlobalAPI::AnimateCharacter6);
 	SCRIPT_METHOD(AnimateObject, GlobalAPI::AnimateObject);
 	SCRIPT_METHOD(AnimateObjectEx, GlobalAPI::AnimateObjectEx);
 	SCRIPT_METHOD(AreCharactersColliding, GlobalAPI::AreCharactersColliding);
@@ -462,14 +462,14 @@ void GlobalAPI::AnimateButton(ScriptMethodParams &params) {
 	AGS3::AnimateButton(guin, objn, view, loop, speed, repeat);
 }
 
-void GlobalAPI::scAnimateCharacter(ScriptMethodParams &params) {
+void GlobalAPI::AnimateCharacter4(ScriptMethodParams &params) {
 	PARAMS4(int, chh, int, loopn, int, sppd, int, rept);
-	AGS3::scAnimateCharacter(chh, loopn, sppd, rept);
+	AGS3::AnimateCharacter4(chh, loopn, sppd, rept);
 }
 
-void GlobalAPI::AnimateCharacterEx(ScriptMethodParams &params) {
+void GlobalAPI::AnimateCharacter6(ScriptMethodParams &params) {
 	PARAMS6(int, chh, int, loopn, int, sppd, int, rept, int, direction, int, blocking);
-	AGS3::AnimateCharacterEx(chh, loopn, sppd, rept, direction, blocking);
+	AGS3::AnimateCharacter6(chh, loopn, sppd, rept, direction, blocking);
 }
 
 void GlobalAPI::AnimateObject(ScriptMethodParams &params) {
diff --git a/engines/ags/plugins/core/global_api.h b/engines/ags/plugins/core/global_api.h
index 4f894689cef..078d470d394 100644
--- a/engines/ags/plugins/core/global_api.h
+++ b/engines/ags/plugins/core/global_api.h
@@ -37,8 +37,8 @@ public:
 	void add_inventory(ScriptMethodParams &params);
 	void AddInventoryToCharacter(ScriptMethodParams &params);
 	void AnimateButton(ScriptMethodParams &params);
-	void scAnimateCharacter(ScriptMethodParams &params);
-	void AnimateCharacterEx(ScriptMethodParams &params);
+	void AnimateCharacter4(ScriptMethodParams &params);
+	void AnimateCharacter6(ScriptMethodParams &params);
 	void AnimateObject(ScriptMethodParams &params);
 	void AnimateObjectEx(ScriptMethodParams &params);
 	void AreCharactersColliding(ScriptMethodParams &params);


Commit: 96257b87cbb0d2a8235920fcd4ad1a002d295634
    https://github.com/scummvm/scummvm/commit/96257b87cbb0d2a8235920fcd4ad1a002d295634
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ensure that all AnimateObject variants lead to the same impl

Changed paths:
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_object.h
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h
    engines/ags/engine/script/script.cpp
    engines/ags/plugins/core/global_api.cpp
    engines/ags/plugins/core/global_api.h


diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index 9d0a147636f..a5682aad1d2 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -117,13 +117,13 @@ RuntimeScriptValue Sc_AnimateCharacter6(const RuntimeScriptValue *params, int32_
 }
 
 // void (int obn,int loopn,int spdd,int rept)
-RuntimeScriptValue Sc_AnimateObject(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT4(AnimateObject);
+RuntimeScriptValue Sc_AnimateObject4(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT4(AnimateObject4);
 }
 
 // void (int obn,int loopn,int spdd,int rept, int direction, int blocking)
-RuntimeScriptValue Sc_AnimateObjectEx(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT6(AnimateObjectEx);
+RuntimeScriptValue Sc_AnimateObject6(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT6(AnimateObject6);
 }
 
 // int (int cchar1,int cchar2)
@@ -1897,8 +1897,8 @@ void RegisterGlobalAPI() {
 	ccAddExternalStaticFunction("AnimateButton",            Sc_AnimateButton);
 	ccAddExternalStaticFunction("AnimateCharacter",         Sc_AnimateCharacter4);
 	ccAddExternalStaticFunction("AnimateCharacterEx",       Sc_AnimateCharacter6);
-	ccAddExternalStaticFunction("AnimateObject",            Sc_AnimateObject);
-	ccAddExternalStaticFunction("AnimateObjectEx",          Sc_AnimateObjectEx);
+	ccAddExternalStaticFunction("AnimateObject",            Sc_AnimateObject4);
+	ccAddExternalStaticFunction("AnimateObjectEx",          Sc_AnimateObject6);
 	ccAddExternalStaticFunction("AreCharactersColliding",   Sc_AreCharactersColliding);
 	ccAddExternalStaticFunction("AreCharObjColliding",      Sc_AreCharObjColliding);
 	ccAddExternalStaticFunction("AreObjectsColliding",      Sc_AreObjectsColliding);
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 20a18aa62cf..2b8d4818e6a 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -227,25 +227,25 @@ int GetObjectBaseline(int obn) {
 void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe, int volume) {
 	if (!is_valid_object(obn))
 		quit("!AnimateObject: invalid object number specified");
-	if (_G(objs)[obn].view == RoomObject::NoView)
+
+	RoomObject &obj = _G(objs)[obn];
+
+	if (obj.view == RoomObject::NoView)
 		quit("!AnimateObject: object has not been assigned a view");
-	if (loopn < 0 || loopn >= _GP(views)[_G(objs)[obn].view].numLoops)
+	if (loopn < 0 || loopn >= _GP(views)[obj.view].numLoops)
 		quit("!AnimateObject: invalid loop number specified");
-	if (_GP(views)[_G(objs)[obn].view].loops[loopn].numFrames < 1)
+	if (_GP(views)[obj.view].loops[loopn].numFrames < 1)
 		quit("!AnimateObject: no frames in the specified view loop");
-	if (sframe < 0 || sframe >= _GP(views)[_G(objs)[obn].view].loops[loopn].numFrames)
+	if (sframe < 0 || sframe >= _GP(views)[obj.view].loops[loopn].numFrames)
 		quit("!AnimateObject: invalid starting frame number specified");
 
-	if ((direction < 0) || (direction > 1))
-		quit("!AnimateObjectEx: invalid direction");
-	if ((rept < ANIM_ONCE) || (rept > ANIM_ONCERESET))
-		quit("!AnimateObjectEx: invalid repeat value");
+	ValidateViewAnimParams("Object.Animate", rept, blocking, direction);
 
 	// reverse animation starts at the *previous frame*
 	if (direction) {
 		sframe--;
 		if (sframe < 0)
-			sframe = _GP(views)[_G(objs)[obn].view].loops[loopn].numFrames - (-sframe);
+			sframe = _GP(views)[obj.view].loops[loopn].numFrames - (-sframe);
 	}
 
 	if (loopn > UINT16_MAX || sframe > UINT16_MAX) {
@@ -254,40 +254,39 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 		return;
 	}
 
-	debug_script_log("Obj %d start anim view %d loop %d, speed %d, repeat %d, frame %d", obn, _G(objs)[obn].view + 1, loopn, spdd, rept, sframe);
+	debug_script_log("Obj %d start anim view %d loop %d, speed %d, repeat %d, frame %d", obn, obj.view + 1, loopn, spdd, rept, sframe);
 
-	_G(objs)[obn].set_animating(rept, direction == 0, spdd);
-	_G(objs)[obn].loop = (uint16_t)loopn;
-	_G(objs)[obn].frame = (uint16_t)sframe;
-	_G(objs)[obn].wait = spdd + _GP(views)[_G(objs)[obn].view].loops[loopn].frames[_G(objs)[obn].frame].speed;
-	int pic = _GP(views)[_G(objs)[obn].view].loops[loopn].frames[_G(objs)[obn].frame].pic;
-	_G(objs)[obn].num = Math::InRangeOrDef<uint16_t>(pic, 0);
+	obj.set_animating(rept, direction == 0, spdd);
+	obj.loop = (uint16_t)loopn;
+	obj.frame = (uint16_t)sframe;
+	obj.wait = spdd + _GP(views)[obj.view].loops[loopn].frames[obj.frame].speed;
+	int pic = _GP(views)[obj.view].loops[loopn].frames[obj.frame].pic;
+	obj.num = Math::InRangeOrDef<uint16_t>(pic, 0);
 	if (pic > UINT16_MAX)
 		debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0", obn, pic, UINT16_MAX);
-	_G(objs)[obn].anim_volume = Math::Clamp(volume, 0, 100);
-	CheckViewFrame(_G(objs)[obn].view, loopn, _G(objs)[obn].frame, _G(objs)[obn].anim_volume);
+	obj.anim_volume = Math::Clamp(volume, 0, 100);
+	CheckViewFrame(obj.view, loopn, obj.frame, obj.anim_volume);
 
 	if (blocking)
-		GameLoopUntilValueIsZero(&_G(objs)[obn].cycling);
+		GameLoopUntilValueIsZero(&obj.cycling);
 }
 
 // A legacy variant of AnimateObject implementation: for pre-2.72 scripts;
 // it has a quirk: for IDs >= 100 this actually calls AnimateCharacter(ID - 100)
-static void LegacyAnimateObjectImpl(int obn, int loopn, int spdd, int rept,
-									int direction = 0, int blocking = 0) {
+static void LegacyAnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking) {
 	if (obn >= LEGACY_ANIMATE_CHARIDBASE) {
 		AnimateCharacter4(obn - LEGACY_ANIMATE_CHARIDBASE, loopn, spdd, rept);
 	} else {
-		AnimateObjectImpl(obn, loopn, spdd, rept, direction, blocking, 0, 100 /* full volume */);
+		AnimateObjectImpl(obn, loopn, spdd, rept, direction, blocking, 0 /* first frame */, 100 /* full volume */);
 	}
 }
 
-void AnimateObjectEx(int obn, int loopn, int spdd, int rept, int direction, int blocking) {
+void AnimateObject6(int obn, int loopn, int spdd, int rept, int direction, int blocking) {
 	LegacyAnimateObjectImpl(obn, loopn, spdd, rept, direction, blocking);
 }
 
-void AnimateObject(int obn, int loopn, int spdd, int rept) {
-	LegacyAnimateObjectImpl(obn, loopn, spdd, rept, 0, 0);
+void AnimateObject4(int obn, int loopn, int spdd, int rept) {
+	LegacyAnimateObjectImpl(obn, loopn, spdd, rept, 0 /* forward */, 0 /* non-blocking */);
 }
 
 void MergeObject(int obn) {
diff --git a/engines/ags/engine/ac/global_object.h b/engines/ags/engine/ac/global_object.h
index eeef60e207f..5be988bc22b 100644
--- a/engines/ags/engine/ac/global_object.h
+++ b/engines/ags/engine/ac/global_object.h
@@ -48,8 +48,8 @@ void SetObjectFrame(int obn, int viw, int lop, int fra);
 void SetObjectTransparency(int obn, int trans);
 void SetObjectBaseline(int obn, int basel);
 int  GetObjectBaseline(int obn);
-void AnimateObjectEx(int obn, int loopn, int spdd, int rept, int direction, int blocking);
-void AnimateObject(int obn, int loopn, int spdd, int rept);
+void AnimateObject6(int obn, int loopn, int spdd, int rept, int direction, int blocking);
+void AnimateObject4(int obn, int loopn, int spdd, int rept);
 void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe, int volume = 100);
 void MergeObject(int obn);
 void StopObjectMoving(int objj);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 7e7638cfee5..ec660e9ba8f 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -116,16 +116,12 @@ int Object_GetBaseline(ScriptObject *objj) {
 	return GetObjectBaseline(objj->id);
 }
 
-void Object_AnimateEx(ScriptObject *objj, int loop, int delay, int repeat,
-	int blocking, int direction, int sframe, int volume = 100) {
-
-	ValidateViewAnimParams("Object.Animate", repeat, blocking, direction);
-
+void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction, int sframe, int volume) {
 	AnimateObjectImpl(objj->id, loop, delay, repeat, direction, blocking, sframe, volume);
 }
 
-void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) {
-	Object_AnimateEx(objj, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
+void Object_Animate5(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) {
+	Object_Animate(objj, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
 }
 
 void Object_StopAnimating(ScriptObject *objj) {
@@ -641,16 +637,16 @@ bool CycleViewAnim(int view, uint16_t &o_loop, uint16_t &o_frame, bool forwards,
 //=============================================================================
 
 // void (ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction)
-RuntimeScriptValue Sc_Object_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT5(ScriptObject, Object_Animate);
+RuntimeScriptValue Sc_Object_Animate5(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT5(ScriptObject, Object_Animate5);
 }
 
 RuntimeScriptValue Sc_Object_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT6(ScriptObject, Object_AnimateEx);
+	API_OBJCALL_VOID_PINT6(ScriptObject, Object_Animate);
 }
 
 RuntimeScriptValue Sc_Object_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT7(ScriptObject, Object_AnimateEx);
+	API_OBJCALL_VOID_PINT7(ScriptObject, Object_Animate);
 }
 
 // int (ScriptObject *objj, ScriptObject *obj2)
@@ -954,7 +950,7 @@ RuntimeScriptValue Sc_Object_SetY(void *self, const RuntimeScriptValue *params,
 
 
 void RegisterObjectAPI() {
-	ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate);
+	ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate5);
 	ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_Animate6);
 	ccAddExternalObjectFunction("Object::Animate^7", Sc_Object_Animate7);
 	ccAddExternalObjectFunction("Object::IsCollidingWithObject^1", Sc_Object_IsCollidingWithObject);
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index 7423ebf93c7..9fc7d8042d8 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -57,7 +57,7 @@ void    Object_SetTransparency(ScriptObject *objj, int trans);
 int     Object_GetTransparency(ScriptObject *objj);
 void    Object_SetBaseline(ScriptObject *objj, int basel);
 int     Object_GetBaseline(ScriptObject *objj);
-void    Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction);
+void    Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction, int sframe = 0, int volume = 100);
 void    Object_StopAnimating(ScriptObject *objj);
 void    Object_MergeIntoBackground(ScriptObject *objj);
 void    Object_StopMoving(ScriptObject *objj);
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index f8feaefc8c2..86f72b59988 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -730,7 +730,7 @@ int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int
 			SetObjectView(IPARAM1, IPARAM2);
 			break;
 		case 18: // Animate Object
-			AnimateObject(IPARAM1, IPARAM2, IPARAM3, IPARAM4);
+			AnimateObject4(IPARAM1, IPARAM2, IPARAM3, IPARAM4);
 			break;
 		case 19: // Move Character
 			if (IPARAM4)
diff --git a/engines/ags/plugins/core/global_api.cpp b/engines/ags/plugins/core/global_api.cpp
index 2e01f5c75ea..df30f696ec4 100644
--- a/engines/ags/plugins/core/global_api.cpp
+++ b/engines/ags/plugins/core/global_api.cpp
@@ -82,8 +82,8 @@ void GlobalAPI::AGS_EngineStartup(IAGSEngine *engine) {
 	SCRIPT_METHOD(AnimateButton, GlobalAPI::AnimateButton);
 	SCRIPT_METHOD(AnimateCharacter, GlobalAPI::AnimateCharacter4);
 	SCRIPT_METHOD(AnimateCharacterEx, GlobalAPI::AnimateCharacter6);
-	SCRIPT_METHOD(AnimateObject, GlobalAPI::AnimateObject);
-	SCRIPT_METHOD(AnimateObjectEx, GlobalAPI::AnimateObjectEx);
+	SCRIPT_METHOD(AnimateObject, GlobalAPI::AnimateObject4);
+	SCRIPT_METHOD(AnimateObjectEx, GlobalAPI::AnimateObject6);
 	SCRIPT_METHOD(AreCharactersColliding, GlobalAPI::AreCharactersColliding);
 	SCRIPT_METHOD(AreCharObjColliding, GlobalAPI::AreCharObjColliding);
 	SCRIPT_METHOD(AreObjectsColliding, GlobalAPI::AreObjectsColliding);
@@ -472,14 +472,14 @@ void GlobalAPI::AnimateCharacter6(ScriptMethodParams &params) {
 	AGS3::AnimateCharacter6(chh, loopn, sppd, rept, direction, blocking);
 }
 
-void GlobalAPI::AnimateObject(ScriptMethodParams &params) {
+void GlobalAPI::AnimateObject4(ScriptMethodParams &params) {
 	PARAMS4(int, obn, int, loopn, int, spdd, int, rept);
-	AGS3::AnimateObject(obn, loopn, spdd, rept);
+	AGS3::AnimateObject4(obn, loopn, spdd, rept);
 }
 
-void GlobalAPI::AnimateObjectEx(ScriptMethodParams &params) {
+void GlobalAPI::AnimateObject6(ScriptMethodParams &params) {
 	PARAMS6(int, obn, int, loopn, int, spdd, int, rept, int, direction, int, blocking);
-	AGS3::AnimateObjectEx(obn, loopn, spdd, rept, direction, blocking);
+	AGS3::AnimateObject6(obn, loopn, spdd, rept, direction, blocking);
 }
 
 void GlobalAPI::AreCharactersColliding(ScriptMethodParams &params) {
diff --git a/engines/ags/plugins/core/global_api.h b/engines/ags/plugins/core/global_api.h
index 078d470d394..b8a327422f8 100644
--- a/engines/ags/plugins/core/global_api.h
+++ b/engines/ags/plugins/core/global_api.h
@@ -39,8 +39,8 @@ public:
 	void AnimateButton(ScriptMethodParams &params);
 	void AnimateCharacter4(ScriptMethodParams &params);
 	void AnimateCharacter6(ScriptMethodParams &params);
-	void AnimateObject(ScriptMethodParams &params);
-	void AnimateObjectEx(ScriptMethodParams &params);
+	void AnimateObject4(ScriptMethodParams &params);
+	void AnimateObject6(ScriptMethodParams &params);
 	void AreCharactersColliding(ScriptMethodParams &params);
 	void AreCharObjColliding(ScriptMethodParams &params);
 	void AreObjectsColliding(ScriptMethodParams &params);


Commit: d0e3493ab9698755ba6b7cb1169ce009dd04fa38
    https://github.com/scummvm/scummvm/commit/d0e3493ab9698755ba6b7cb1169ce009dd04fa38
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidy Button_Animate* functions a little

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/button.h
    engines/ags/engine/ac/global_button.cpp
    engines/ags/plugins/core/button.cpp


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 0da4280862c..03dbc44d2f6 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -55,8 +55,7 @@ void UpdateButtonState(const AnimatingGUIButton &abtn) {
 	_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
 }
 
-void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed,
-		int repeat, int blocking, int direction, int sframe, int volume = 100) {
+void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat, int blocking, int direction, int sframe, int volume) {
 	int guin = butt->ParentId;
 	int objn = butt->Id;
 
@@ -105,12 +104,12 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed,
 		GameLoopUntilButAnimEnd(guin, objn);
 }
 
-void Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat) {
-	Button_AnimateEx(butt, view, loop, speed, repeat, IN_BACKGROUND, FORWARDS, 0, 100 /* full volume */);
+void Button_Animate4(GUIButton *butt, int view, int loop, int speed, int repeat) {
+	Button_Animate(butt, view, loop, speed, repeat, IN_BACKGROUND, FORWARDS, 0, 100 /* full volume */);
 }
 
 void Button_Animate7(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
-	Button_AnimateEx(butt, view, loop, speed, repeat, blocking, direction, sframe, 100 /* full volume */);
+	Button_Animate(butt, view, loop, speed, repeat, blocking, direction, sframe, 100 /* full volume */);
 }
 
 const char *Button_GetText_New(GUIButton *butt) {
@@ -330,8 +329,8 @@ void Button_SetTextAlignment(GUIButton *butt, int align) {
 //=============================================================================
 
 // void | GUIButton *butt, int view, int loop, int speed, int repeat
-RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT4(GUIButton, Button_Animate);
+RuntimeScriptValue Sc_Button_Animate4(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT4(GUIButton, Button_Animate4);
 }
 
 RuntimeScriptValue Sc_Button_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
@@ -339,7 +338,7 @@ RuntimeScriptValue Sc_Button_Animate7(void *self, const RuntimeScriptValue *para
 }
 
 RuntimeScriptValue Sc_Button_Animate8(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT8(GUIButton, Button_AnimateEx);
+	API_OBJCALL_VOID_PINT8(GUIButton, Button_Animate);
 }
 
 // const char* | GUIButton *butt
@@ -451,7 +450,7 @@ RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *param
 }
 
 void RegisterButtonAPI() {
-	ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate);
+	ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate4);
 	ccAddExternalObjectFunction("Button::Animate^7", Sc_Button_Animate7);
 	ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_Animate8);
 	ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click);
diff --git a/engines/ags/engine/ac/button.h b/engines/ags/engine/ac/button.h
index 43fe8603fcd..6136ce21868 100644
--- a/engines/ags/engine/ac/button.h
+++ b/engines/ags/engine/ac/button.h
@@ -30,7 +30,8 @@ namespace AGS3 {
 using AGS::Shared::GUIButton;
 struct AnimatingGUIButton;
 
-void        Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat);
+void        Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe = 0, int volume = 100);
+void        Button_Animate4(GUIButton *butt, int view, int loop, int speed, int repeat);
 const char *Button_GetText_New(GUIButton *butt);
 void        Button_GetText(GUIButton *butt, char *buffer);
 void        Button_SetText(GUIButton *butt, const char *newtx);
diff --git a/engines/ags/engine/ac/global_button.cpp b/engines/ags/engine/ac/global_button.cpp
index e4a0c1b18d1..f87346a06e7 100644
--- a/engines/ags/engine/ac/global_button.cpp
+++ b/engines/ags/engine/ac/global_button.cpp
@@ -53,7 +53,7 @@ void AnimateButton(int guin, int objn, int view, int loop, int speed, int repeat
 	if (_GP(guis)[guin].GetControlType(objn) != kGUIButton)
 		quit("!AnimateButton: specified control is not a button");
 
-	Button_Animate((GUIButton *)_GP(guis)[guin].GetControl(objn), view, loop, speed, repeat);
+	Button_Animate4((GUIButton *)_GP(guis)[guin].GetControl(objn), view, loop, speed, repeat);
 }
 
 
diff --git a/engines/ags/plugins/core/button.cpp b/engines/ags/plugins/core/button.cpp
index 563bf3f4f19..4b350db530a 100644
--- a/engines/ags/plugins/core/button.cpp
+++ b/engines/ags/plugins/core/button.cpp
@@ -51,7 +51,7 @@ void Button::AGS_EngineStartup(IAGSEngine *engine) {
 
 void Button::Animate(ScriptMethodParams &params) {
 	PARAMS5(GUIButton *, butt, int, view, int, loop, int, speed, int, repeat);
-	AGS3::Button_Animate(butt, view, loop, speed, repeat);
+	AGS3::Button_Animate4(butt, view, loop, speed, repeat);
 }
 
 void Button::GetText(ScriptMethodParams &params) {


Commit: e0aab081f2fd299eec6e0d0f8fc823160e5de6e7
    https://github.com/scummvm/scummvm/commit/e0aab081f2fd299eec6e0d0f8fc823160e5de6e7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: picked out view-loop-frame Animate params check, fixup sframe

from upstream db71d928a6f290172975209d36ba7b7e22881f57

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 03dbc44d2f6..df7454405c0 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -59,13 +59,8 @@ void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat,
 	int guin = butt->ParentId;
 	int objn = butt->Id;
 
-	if ((view < 1) || (view > _GP(game).numviews))
-		quit("!AnimateButton: invalid view specified");
-	view--;
-	if ((loop < 0) || (loop >= _GP(views)[view].numLoops))
-		quit("!AnimateButton: invalid loop specified for view");
-	if (sframe < 0 || sframe >= _GP(views)[view].loops[loop].numFrames)
-		quit("!AnimateButton: invalid starting frame number specified");
+	view--; // convert to 0-based view ID
+	ValidateViewAnimVLF("Button.Animate", view, loop, sframe);
 
 	ValidateViewAnimParams("Button.Animate", repeat, blocking, direction);
 
@@ -74,12 +69,6 @@ void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat,
 	// if it's already animating, stop it
 	FindAndRemoveButtonAnimation(guin, objn);
 
-	// reverse animation starts at the *previous frame*
-	if (direction) {
-		if (--sframe < 0)
-			sframe = _GP(views)[view].loops[loop].numFrames - (-sframe);
-	}
-
 	int but_id = _GP(guis)[guin].GetControlID(objn);
 	AnimatingGUIButton abtn;
 	abtn.ongui = guin;
@@ -91,7 +80,7 @@ void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat,
 	abtn.repeat = static_cast<bool>(repeat) ? ANIM_REPEAT : ANIM_ONCE; // for now, clamp to supported modes
 	abtn.blocking = blocking;
 	abtn.direction = direction;
-	abtn.frame = sframe;
+	abtn.frame = SetFirstAnimFrame(view, loop, sframe, direction);
 	abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
 	abtn.volume = volume;
 	_GP(animbuts).push_back(abtn);
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 36b67fd472f..83e0003ade8 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -180,6 +180,7 @@ 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) {
 
+	ValidateViewAnimVLF("Character.Animate", chaa->view, loop, sframe);
 	ValidateViewAnimParams("Character.Animate", repeat, blocking, direction);
 
 	animate_character(chaa, loop, delay, repeat, 0, direction, sframe, volume);
@@ -2033,38 +2034,34 @@ void setup_player_character(int charid) {
 	}
 }
 
-void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
-	int noidleoverride, int direction, int sframe, int volume) {
-	if ((chap->view < 0) || (chap->view > _GP(game).numviews)) {
-		quitprintf("!AnimateCharacter: you need to set the view number first\n"
-			"(trying to animate '%s' using loop %d. View is currently %d).", chap->name, loopn, chap->view + 1);
-	}
-	debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
-		chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
+// 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)) {
-		// if idle view in progress for the character (and this is not the
-		// "start idle animation" animate_character call), stop the idle anim
 		Character_UnlockView(chap);
 		chap->idleleft = chap->idletime;
 	}
-	if ((loopn < 0) || (loopn >= _GP(views)[chap->view].numLoops)) {
-		quitprintf("!AnimateCharacter: invalid loop number\n"
-			"(trying to animate '%s' using loop %d. View is currently %d).", chap->name, loopn, chap->view + 1);
+
+	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"
+				   "(trying to animate '%s' using view %d (range is 1..%d) and loop %d (view has %d loops)).",
+				   chap->name, chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
 	}
-	if ((sframe < 0) || (sframe >= _GP(views)[chap->view].loops[loopn].numFrames))
-		quit("!AnimateCharacter: invalid starting frame number specified");
+	// NOTE: there's always frame 0 allocated for safety
+	sframe = std::max(0, std::min(sframe, _GP(views)[chap->view].loops[loopn].numFrames - 1));
+	debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
+					 chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
+
 	Character_StopMoving(chap);
 
 	chap->set_animating(rept != 0, direction == 0, sppd);
-
 	chap->loop = loopn;
-	// reverse animation starts at the *previous frame*
-	if (direction) {
-		sframe--;
-		if (sframe < 0)
-			sframe = _GP(views)[chap->view].loops[loopn].numFrames - (-sframe);
-	}
-	chap->frame = sframe;
+	chap->frame = SetFirstAnimFrame(chap->view, loopn, sframe, direction);
+
 	chap->wait = sppd + _GP(views)[chap->view].loops[loopn].frames[chap->frame].speed;
 	_GP(charextra)[chap->index_id].cur_anim_volume = Math::Clamp(volume, 0, 100);
 
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 2b8d4818e6a..1abfabbbef8 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -232,22 +232,10 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 
 	if (obj.view == RoomObject::NoView)
 		quit("!AnimateObject: object has not been assigned a view");
-	if (loopn < 0 || loopn >= _GP(views)[obj.view].numLoops)
-		quit("!AnimateObject: invalid loop number specified");
-	if (_GP(views)[obj.view].loops[loopn].numFrames < 1)
-		quit("!AnimateObject: no frames in the specified view loop");
-	if (sframe < 0 || sframe >= _GP(views)[obj.view].loops[loopn].numFrames)
-		quit("!AnimateObject: invalid starting frame number specified");
 
+	ValidateViewAnimVLF("Object.Animate", obj.view, loopn, sframe);
 	ValidateViewAnimParams("Object.Animate", rept, blocking, direction);
 
-	// reverse animation starts at the *previous frame*
-	if (direction) {
-		sframe--;
-		if (sframe < 0)
-			sframe = _GP(views)[obj.view].loops[loopn].numFrames - (-sframe);
-	}
-
 	if (loopn > UINT16_MAX || sframe > UINT16_MAX) {
 		debug_script_warn("Warning: object's (id %d) loop/frame (%d/%d) is outside of internal range (%d/%d), cancel animation",
 		                  obn, loopn, sframe, UINT16_MAX, UINT16_MAX);
@@ -258,7 +246,7 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 
 	obj.set_animating(rept, direction == 0, spdd);
 	obj.loop = (uint16_t)loopn;
-	obj.frame = (uint16_t)sframe;
+	obj.frame = (uint16_t)SetFirstAnimFrame(obj.view, loopn, sframe, direction);
 	obj.wait = spdd + _GP(views)[obj.view].loops[loopn].frames[obj.frame].speed;
 	int pic = _GP(views)[obj.view].loops[loopn].frames[obj.frame].pic;
 	obj.num = Math::InRangeOrDef<uint16_t>(pic, 0);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index ec660e9ba8f..cda2dc05ced 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -564,6 +564,36 @@ void ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int
 	}
 }
 
+void ValidateViewAnimVLF(const char *apiname, int view, int loop, int &sframe) {
+	if ((view < 1) || (view > _GP(game).numviews))
+		quitprintf("!%s: invalid view (range is 1..%d.", apiname, view + 1, _GP(game).numviews);
+	if (_GP(views)[view].numLoops == 0)
+		quitprintf("!%s: view %d does not have any loops.", apiname, view + 1);
+	if (loop < 0 || loop >= _GP(views)[view].numLoops)
+		quitprintf("!%s: invalid loop number for view %d (range is 0..%d).", apiname, view + 1, _GP(views)[view].numLoops);
+
+	if (_GP(views)[view].loops[loop].numFrames < 1)
+		debug_script_warn("%s: view %d loop %d does not have any frames, will use a frame placeholder.",
+						  apiname, view + 1, loop);
+	else if (sframe < 0 || sframe >= _GP(views)[view].loops[loop].numFrames)
+		debug_script_warn("%s: invalid starting frame number for view %d loop %d (range is 0..%d)",
+						  view + 1, loop, _GP(views)[view].loops[loop].numFrames);
+	// NOTE: there's always frame 0 allocated for safety
+	sframe = std::max(0, std::min(sframe, _GP(views)[view].loops[loop].numFrames - 1));
+}
+
+int SetFirstAnimFrame(int view, int loop, int sframe, int direction) {
+	if (_GP(views)[view].loops[loop].numFrames <= 1)
+		return 0;
+	// reverse animation starts at the *previous frame*
+	if (direction != 0) {
+		sframe--;
+		if (sframe < 0)
+			sframe = _GP(views)[view].loops[loop].numFrames - (-sframe);
+	}
+	return sframe;
+}
+
 // General view animation algorithm: find next loop and frame, depending on anim settings
 bool CycleViewAnim(int view, uint16_t &o_loop, uint16_t &o_frame, bool forwards, int repeat) {
 	// Allow multi-loop repeat: idk why, but original engine behavior
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index 9fc7d8042d8..04834c32298 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -107,10 +107,18 @@ int     is_pos_in_sprite(int xx, int yy, int arx, int ary, Shared::Bitmap *sprit
 // X and Y co-ordinates must be in native format
 // X and Y are ROOM coordinates
 int     check_click_on_object(int roomx, int roomy, int mood);
+
+// Shared functions that prepare or advance the view animation;
+// used by characters, room objects and buttons.
 // TODO: pick out some kind of "animation" struct
 // Tests if the standard animate parameters are in valid range, if not then clamps them and
 // reports a script warning.
-void ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int &direction);
+void    ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int &direction);
+// Tests if the view, loop, frame animate params are in valid range,
+// errors in case of out-of-range view or loop, but clamps a frame to a range.
+void    ValidateViewAnimVLF(const char *apiname, int view, int loop, int &sframe);
+// Calculates the first shown frame for a view animation, depending on parameters.
+int     SetFirstAnimFrame(int view, int loop, int sframe, int direction);
 // General view animation algorithm: find next loop and frame, depending on anim settings;
 // loop and frame values are passed by reference and will be updated;
 // returns whether the animation should continue.


Commit: af1c822a15f56e55d6f7e25272942f503faf33ed
    https://github.com/scummvm/scummvm/commit/af1c822a15f56e55d6f7e25272942f503faf33ed
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: picked out few bitmap conv functions to BitmapHelper

partially from upstream 0115cdafb1928245cf4613bda6dcebae60264b36

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/sprite.cpp
    engines/ags/engine/ac/sprite.h
    engines/ags/shared/gfx/bitmap.cpp
    engines/ags/shared/gfx/bitmap.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 10c1f482963..4f72e699f1d 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -175,7 +175,7 @@ Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap *bitmap, bool has_alpha) {
 	// (this adjustment is probably needed for DrawingSurface ops)
 	if (game_col_depth == 32 && bmp_col_depth == 32) {
 		if (has_alpha)
-			set_rgb_mask_using_alpha_channel(new_bitmap);
+			BitmapHelper::ReplaceAlphaWithRGBMask(new_bitmap);
 	}
 	// In 32-bit game hicolor bitmaps must be converted to the true color
 	else if (game_col_depth == 32 && (bmp_col_depth > 8 && bmp_col_depth <= 16)) {
diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp
index 2d851daa8b9..938b88534b2 100644
--- a/engines/ags/engine/ac/sprite.cpp
+++ b/engines/ags/engine/ac/sprite.cpp
@@ -45,21 +45,6 @@ void get_new_size_for_sprite(int ee, int ww, int hh, int &newwid, int &newhit) {
 	ctx_data_to_game_size(newwid, newhit, spinfo.IsLegacyHiRes());
 }
 
-// set any alpha-transparent pixels in the image to the appropriate
-// RGB mask value so that the blit calls work correctly
-void set_rgb_mask_using_alpha_channel(Bitmap *image) {
-	int x, y;
-
-	for (y = 0; y < image->GetHeight(); y++) {
-		unsigned int *psrc = (unsigned int *)image->GetScanLine(y);
-
-		for (x = 0; x < image->GetWidth(); x++) {
-			if ((psrc[x] & 0xff000000) == 0x00000000)
-				psrc[x] = MASK_COLOR_32;
-		}
-	}
-}
-
 // from is a 32-bit RGBA image, to is a 15/16/24-bit destination image
 Bitmap *remove_alpha_channel(Bitmap *from) {
 	const int game_cd = _GP(game).GetColorDepth();
diff --git a/engines/ags/engine/ac/sprite.h b/engines/ags/engine/ac/sprite.h
index 613c93393d0..17183b356bf 100644
--- a/engines/ags/engine/ac/sprite.h
+++ b/engines/ags/engine/ac/sprite.h
@@ -25,10 +25,8 @@
 namespace AGS3 {
 
 void get_new_size_for_sprite(int ee, int ww, int hh, int &newwid, int &newhit);
-// set any alpha-transparent pixels in the image to the appropriate
-// RGB mask value so that the ->Blit calls work correctly
-void set_rgb_mask_using_alpha_channel(Shared::Bitmap *image);
-// from is a 32-bit RGBA image, to is a 15/16/24-bit destination image
+// Converts from 32-bit RGBA image, to a 15/16/24-bit destination image,
+// replacing more than half-translucent alpha pixels with transparency mask pixels.
 Shared::Bitmap *remove_alpha_channel(Shared::Bitmap *from);
 void pre_save_sprite(Shared::Bitmap *bitmap);
 void initialize_sprite(int ee);
diff --git a/engines/ags/shared/gfx/bitmap.cpp b/engines/ags/shared/gfx/bitmap.cpp
index ec57457fdf6..8b452628fa1 100644
--- a/engines/ags/shared/gfx/bitmap.cpp
+++ b/engines/ags/shared/gfx/bitmap.cpp
@@ -102,6 +102,44 @@ Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height) {
 	return bmp;
 }
 
+void MakeOpaque(Bitmap *bmp) {
+	if (bmp->GetColorDepth() < 32)
+		return; // no alpha channel
+
+	for (int i = 0; i < bmp->GetHeight(); ++i) {
+		uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
+		uint32_t *line_end = line + bmp->GetWidth();
+		for (uint32_t *px = line; px != line_end; ++px)
+			*px = makeacol32(getr32(*px), getg32(*px), getb32(*px), 255);
+	}
+}
+
+void MakeOpaqueSkipMask(Bitmap *bmp) {
+	if (bmp->GetColorDepth() < 32)
+		return; // no alpha channel
+
+	for (int i = 0; i < bmp->GetHeight(); ++i) {
+		uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
+		uint32_t *line_end = line + bmp->GetWidth();
+		for (uint32_t *px = line; px != line_end; ++px)
+			if (*px != MASK_COLOR_32)
+				*px = makeacol32(getr32(*px), getg32(*px), getb32(*px), 255);
+	}
+}
+
+void ReplaceAlphaWithRGBMask(Bitmap *bmp) {
+	if (bmp->GetColorDepth() < 32)
+		return; // no alpha channel
+
+	for (int i = 0; i < bmp->GetHeight(); ++i) {
+		uint32_t *line = reinterpret_cast<uint32_t *>(bmp->GetScanLineForWriting(i));
+		uint32_t *line_end = line + bmp->GetWidth();
+		for (uint32_t *px = line; px != line_end; ++px)
+			if (geta32(*px) == 0)
+				*px = MASK_COLOR_32;
+	}
+}
+
 // Functor that copies the "mask color" pixels from source to dest
 template <class TPx, size_t BPP_>
 struct PixelTransCpy {
diff --git a/engines/ags/shared/gfx/bitmap.h b/engines/ags/shared/gfx/bitmap.h
index 6f606dce6dc..a8cb9598ed4 100644
--- a/engines/ags/shared/gfx/bitmap.h
+++ b/engines/ags/shared/gfx/bitmap.h
@@ -88,6 +88,13 @@ Bitmap *LoadFromFile(PACKFILE *pf);
 // Stretches bitmap to the requested size. The new bitmap will have same
 // colour depth. Returns original bitmap if no changes are necessary.
 Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height);
+// Makes the given bitmap opaque (full alpha), while keeping pixel RGB unchanged.
+void    MakeOpaque(Bitmap *bmp);
+// Makes the given bitmap opaque (full alpha), while keeping pixel RGB unchanged.
+// Skips mask color (leaves it with zero alpha).
+void    MakeOpaqueSkipMask(Bitmap *bmp);
+// Replaces fully transparent (alpha = 0) pixels with standard mask color.
+void    ReplaceAlphaWithRGBMask(Bitmap *bmp);
 // Copy transparency mask and/or alpha channel from one bitmap into another.
 // Destination and mask bitmaps must be of the same pixel format.
 // Transparency is merged, meaning that fully transparent pixels on


Commit: 32433b136835d389eeb06f938d081b443b9ee0bc
    https://github.com/scummvm/scummvm/commit/32433b136835d389eeb06f938d081b443b9ee0bc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed error check and messages in ValidateViewAnimParams()

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index df7454405c0..4bb747f9d00 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -59,7 +59,7 @@ void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat,
 	int guin = butt->ParentId;
 	int objn = butt->Id;
 
-	view--; // convert to 0-based view ID
+	view--; // convert to internal 0-based view ID
 	ValidateViewAnimVLF("Button.Animate", view, loop, sframe);
 
 	ValidateViewAnimParams("Button.Animate", repeat, blocking, direction);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index cda2dc05ced..016c9bfc801 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -565,19 +565,21 @@ void ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int
 }
 
 void ValidateViewAnimVLF(const char *apiname, int view, int loop, int &sframe) {
-	if ((view < 1) || (view > _GP(game).numviews))
-		quitprintf("!%s: invalid view (range is 1..%d.", apiname, view + 1, _GP(game).numviews);
+	// NOTE: we assume that the view is already in an internal 0-based range.
+	// but when printing an error we will use (view + 1) for compliance with the script API.
+	if ((view < 0) || (view >= _GP(game).numviews))
+		quitprintf("!%s: invalid view %d (range is 1..%d).", apiname, view + 1, _GP(game).numviews);
 	if (_GP(views)[view].numLoops == 0)
 		quitprintf("!%s: view %d does not have any loops.", apiname, view + 1);
 	if (loop < 0 || loop >= _GP(views)[view].numLoops)
-		quitprintf("!%s: invalid loop number for view %d (range is 0..%d).", apiname, view + 1, _GP(views)[view].numLoops);
+		quitprintf("!%s: invalid loop number %d for view %d (range is 0..%d).", apiname, loop, view + 1, _GP(views)[view].numLoops - 1);
 
 	if (_GP(views)[view].loops[loop].numFrames < 1)
 		debug_script_warn("%s: view %d loop %d does not have any frames, will use a frame placeholder.",
 						  apiname, view + 1, loop);
 	else if (sframe < 0 || sframe >= _GP(views)[view].loops[loop].numFrames)
-		debug_script_warn("%s: invalid starting frame number for view %d loop %d (range is 0..%d)",
-						  view + 1, loop, _GP(views)[view].loops[loop].numFrames);
+		debug_script_warn("%s: invalid starting frame number %d for view %d loop %d (range is 0..%d)",
+						  apiname, sframe, view + 1, loop, _GP(views)[view].loops[loop].numFrames - 1);
 	// NOTE: there's always frame 0 allocated for safety
 	sframe = std::max(0, std::min(sframe, _GP(views)[view].loops[loop].numFrames - 1));
 }
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index 04834c32298..bdbf906f4d7 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -116,6 +116,7 @@ int     check_click_on_object(int roomx, int roomy, int mood);
 void    ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int &direction);
 // Tests if the view, loop, frame animate params are in valid range,
 // errors in case of out-of-range view or loop, but clamps a frame to a range.
+// NOTE: assumes view is already in an internal 0-based range.
 void    ValidateViewAnimVLF(const char *apiname, int view, int loop, int &sframe);
 // Calculates the first shown frame for a view animation, depending on parameters.
 int     SetFirstAnimFrame(int view, int loop, int sframe, int direction);


Commit: 14677912905be84f4806a27a9681f500e401dae9
    https://github.com/scummvm/scummvm/commit/14677912905be84f4806a27a9681f500e401dae9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidy code a bit in script_runtime.cpp

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 8acb703b648..e0a18fb6e5a 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -169,7 +169,6 @@ void script_commands_free() {
 }
 
 const char *regnames[] = { "null", "sp", "mar", "ax", "bx", "cx", "op", "dx" };
-
 const char *fixupnames[] = { "null", "fix_gldata", "fix_func", "fix_string", "fix_import", "fix_datadata", "fix_stack" };
 
 String cc_get_callstack(int max_lines) {
@@ -1735,8 +1734,10 @@ void ccInstance::Free() {
 		_G(loadedInstances)[loadedInstanceId] = nullptr;
 
 	if ((flags & INSTF_SHAREDATA) == 0) {
-		nullfree(globaldata);
-		nullfree(code);
+		if (globaldata)
+			free(globaldata);
+		if (code)
+			free(code);
 	}
 	globalvars.reset();
 	globaldata = nullptr;
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index a2e64b3eacf..ce26c1ec650 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -19,11 +19,11 @@
  *
  */
 
+#include "ags/engine/script/script_runtime.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
 #include "ags/engine/ac/statobj/static_object.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/system_imports.h"
-#include "ags/engine/script/script_runtime.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
@@ -32,6 +32,14 @@ bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticFunction(pfn), nullptr) != UINT32_MAX;
 }
 
+bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn) {
+	return _GP(simp).add(name, RuntimeScriptValue().SetObjectFunction(pfn), nullptr) != UINT32_MAX;
+}
+
+bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer *instance) {
+	return _GP(simp_for_plugin).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
+}
+
 bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *instance) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
@@ -48,10 +56,6 @@ bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject
 	return _GP(simp).add(name, RuntimeScriptValue().SetDynamicObject(ptr, manager), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn) {
-	return _GP(simp).add(name, RuntimeScriptValue().SetObjectFunction(pfn), nullptr) != UINT32_MAX;
-}
-
 bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst) {
 	return _GP(simp).add(name, prval, inst) != UINT32_MAX;
 }
@@ -64,11 +68,6 @@ void ccRemoveAllSymbols() {
 	_GP(simp).clear();
 }
 
-void nullfree(void *data) {
-	if (data != nullptr)
-		free(data);
-}
-
 void *ccGetSymbolAddress(const String &name) {
 	const ScriptImport *import = _GP(simp).getByName(name);
 	if (import) {
@@ -77,10 +76,6 @@ void *ccGetSymbolAddress(const String &name) {
 	return nullptr;
 }
 
-bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer *instance) {
-	return _GP(simp_for_plugin).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) == 0;
-}
-
 Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name) {
 	const ScriptImport *import = _GP(simp_for_plugin).getByName(name);
 	if (import) {
@@ -110,8 +105,7 @@ void ccSetDebugHook(new_line_hook_type jibble) {
 	_G(new_line_hook) = jibble;
 }
 
-int call_function(const Plugins::PluginMethod &method,
-		const RuntimeScriptValue *object, int numparm, const RuntimeScriptValue *parms) {
+int call_function(const Plugins::PluginMethod &method, const RuntimeScriptValue *object, int numparm, const RuntimeScriptValue *parms) {
 	if (!method) {
 		cc_error("invalid method in call_function");
 		return -1;
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index bf2d81be210..e4ab17ed6a8 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -33,47 +33,46 @@ struct ICCDynamicObject;
 struct StaticArray;
 
 using AGS::Shared::String;
-using AGS::Shared::String;
-
-// ************ SCRIPT LOADING AND RUNNING FUNCTIONS ************
-
-// give the script access to a variable or function in your program
-extern bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn);
-// temporary workaround for plugins
-extern bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *sc);
-extern bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager);
-extern bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr);
-extern bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager);
-extern bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn);
-extern bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst);
-// remove the script access to a variable or function in your program
-extern void ccRemoveExternalSymbol(const String &name);
-// removes all external symbols, allowing you to start from scratch
-extern void ccRemoveAllSymbols();
 
-// get the address of an exported variable in the script
-extern void *ccGetSymbolAddress(const String &name);
+// Following functions register engine API symbols for script and plugins.
+// Calls from script is handled by specific "translator" functions, which
+// unpack script interpreter's values into the real arguments and call the
+// actual engine's function. For plugins we have to provide actual engine
+// function directly.
+bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn);
+bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn);
+bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer *sc);
+// Register a function, exported from a plugin. Requires direct function pointer only.
+bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *sc);
+// Register engine objects for script's access.
+bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager);
+bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr);
+bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager);
+// Register script own functions (defined in the linked scripts)
+bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst);
+// Remove the script access to a variable or function in your program
+void ccRemoveExternalSymbol(const String &name);
+// Remove all external symbols, allowing you to start from scratch
+void ccRemoveAllSymbols();
 
-// registering functions, compatible with old unsafe call style;
-// this is to be used solely by plugins until plugin inteface is redone
-extern bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer *instance);
-extern Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name);
+// Get the address of an exported variable in the script
+void *ccGetSymbolAddress(const String &name);
+// Get a registered symbol's direct pointer; this is used solely for plugins
+Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name);
 
 // DEBUG HOOK
 typedef void (*new_line_hook_type)(ccInstance *, int);
-extern void ccSetDebugHook(new_line_hook_type jibble);
+void ccSetDebugHook(new_line_hook_type jibble);
 
 // Set the script interpreter timeout values:
 // * sys_poll_timeout - defines the timeout (ms) at which the interpreter will run system events poll;
 // * abort_timeout - [temp disabled] defines the timeout (ms) at which the interpreter will cancel with error.
 // * abort_loops - max script loops without an engine update after which the interpreter will error;
-extern void ccSetScriptAliveTimer(unsigned sys_poll_timeout, unsigned abort_timeout, unsigned abort_loops);
+void ccSetScriptAliveTimer(unsigned sys_poll_timeout, unsigned abort_timeout, unsigned abort_loops);
 // reset the current while loop counter
-extern void ccNotifyScriptStillAlive();
+void ccNotifyScriptStillAlive();
 // for calling exported plugin functions old-style
-extern int call_function(const Plugins::PluginMethod &method,
-	const RuntimeScriptValue *obj, int numparm, const RuntimeScriptValue *parms);
-extern void nullfree(void *data); // in script/script_runtime
+int call_function(const Plugins::PluginMethod &method, const RuntimeScriptValue *object, int numparm, const RuntimeScriptValue *parms);
 
 } // namespace AGS3
 


Commit: 09039ca80568a18fa822fe343eedcf9c37e5003d
    https://github.com/scummvm/scummvm/commit/09039ca80568a18fa822fe343eedcf9c37e5003d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: brought multivariant script api functions to naming consistency

The idea is that the old variants have internal names with number
(signifying number of args), while the latest variant has a name without number.

Make sure there's a distinct function for each variant, and older vars are calling later ones in a sequence.
These calls likely to be inlined.
>From upstream ca48926a37e848a1bcc162f77cc81e63ec7a1ca0

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/drawing_surface.h
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/mouse.h
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/overlay.h
    engines/ags/engine/ac/screen.cpp
    engines/ags/engine/script/script_api.h
    engines/ags/plugins/core/drawing_surface.cpp
    engines/ags/plugins/core/mouse.cpp
    engines/ags/plugins/core/mouse.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 4bb747f9d00..e20e4911411 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -326,7 +326,7 @@ RuntimeScriptValue Sc_Button_Animate7(void *self, const RuntimeScriptValue *para
 	API_OBJCALL_VOID_PINT7(GUIButton, Button_Animate7);
 }
 
-RuntimeScriptValue Sc_Button_Animate8(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_PINT8(GUIButton, Button_Animate);
 }
 
@@ -441,7 +441,7 @@ RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *param
 void RegisterButtonAPI() {
 	ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate4);
 	ccAddExternalObjectFunction("Button::Animate^7", Sc_Button_Animate7);
-	ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_Animate8);
+	ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_Animate);
 	ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click);
 	ccAddExternalObjectFunction("Button::GetText^1", Sc_Button_GetText);
 	ccAddExternalObjectFunction("Button::SetText^1", Sc_Button_SetText);
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 83e0003ade8..0ae4156b244 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -190,7 +190,11 @@ void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat,
 }
 
 void Character_Animate5(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) {
-	Character_Animate(chaa, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
+	Character_Animate(chaa, loop, delay, repeat, blocking, direction, 0 /* first frame */, 100 /* full volume */);
+}
+
+void Character_Animate6(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction, int sframe) {
+	Character_Animate(chaa, loop, delay, repeat, blocking, direction, sframe, 100 /* full volume */);
 }
 
 void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos) {
@@ -2863,10 +2867,10 @@ RuntimeScriptValue Sc_Character_Animate5(void *self, const RuntimeScriptValue *p
 }
 
 RuntimeScriptValue Sc_Character_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT6(CharacterInfo, Character_Animate);
+	API_OBJCALL_VOID_PINT6(CharacterInfo, Character_Animate6);
 }
 
-RuntimeScriptValue Sc_Character_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Character_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_PINT7(CharacterInfo, Character_Animate);
 }
 
@@ -3586,7 +3590,7 @@ void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_
 	ccAddExternalObjectFunction("Character::AddWaypoint^2",             Sc_Character_AddWaypoint);
 	ccAddExternalObjectFunction("Character::Animate^5",                 Sc_Character_Animate5);
 	ccAddExternalObjectFunction("Character::Animate^6",                 Sc_Character_Animate6);
-	ccAddExternalObjectFunction("Character::Animate^7",                 Sc_Character_Animate7);
+	ccAddExternalObjectFunction("Character::Animate^7",                 Sc_Character_Animate);
 	ccAddExternalObjectFunction("Character::ChangeRoom^3",              Sc_Character_ChangeRoom);
 	ccAddExternalObjectFunction("Character::ChangeRoom^4",              Sc_Character_ChangeRoomSetLoop);
 	ccAddExternalObjectFunction("Character::ChangeRoomAutoPosition^2",  Sc_Character_ChangeRoomAutoPosition);
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index 641033e645e..0f06cbb2a8e 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -206,7 +206,7 @@ void DrawingSurface_DrawImageImpl(ScriptDrawingSurface *sds, Bitmap *src,
 		delete src;
 }
 
-void DrawingSurface_DrawImageEx(ScriptDrawingSurface *sds,
+void DrawingSurface_DrawImage(ScriptDrawingSurface *sds,
 		int dst_x, int dst_y, int slot, int trans,
 		int dst_width, int dst_height,
 		int src_x, int src_y, int src_width, int src_height) {
@@ -216,19 +216,19 @@ void DrawingSurface_DrawImageEx(ScriptDrawingSurface *sds,
 		src_x, src_y, src_width, src_height, slot, (_GP(game).SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) != 0);
 }
 
-void DrawingSurface_DrawImage(ScriptDrawingSurface *sds, int xx, int yy, int slot, int trans, int width, int height) {
-	DrawingSurface_DrawImageEx(sds, xx, yy, slot, trans, width, height, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE);
+void DrawingSurface_DrawImage6(ScriptDrawingSurface *sds, int xx, int yy, int slot, int trans, int width, int height) {
+	DrawingSurface_DrawImage(sds, xx, yy, slot, trans, width, height, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE);
 }
 
-void DrawingSurface_DrawSurfaceEx(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans,
+void DrawingSurface_DrawSurface(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans,
 		int dst_x, int dst_y, int dst_width, int dst_height,
 		int src_x, int src_y, int src_width, int src_height) {
 	DrawingSurface_DrawImageImpl(target, source->GetBitmapSurface(), dst_x, dst_y, trans, dst_width, dst_height,
 		src_x, src_y, src_width, src_height, -1, source->hasAlphaChannel != 0);
 }
 
-void DrawingSurface_DrawSurface(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans) {
-	DrawingSurface_DrawSurfaceEx(target, source, trans, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE);
+void DrawingSurface_DrawSurface2(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans) {
+	DrawingSurface_DrawSurface(target, source, trans, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE);
 }
 
 void DrawingSurface_SetDrawingColor(ScriptDrawingSurface *sds, int newColour) {
@@ -435,13 +435,13 @@ RuntimeScriptValue Sc_DrawingSurface_DrawCircle(void *self, const RuntimeScriptV
 }
 
 // void (ScriptDrawingSurface* sds, int xx, int yy, int slot, int trans, int width, int height)
-RuntimeScriptValue Sc_DrawingSurface_DrawImage_6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT6(ScriptDrawingSurface, DrawingSurface_DrawImage);
+RuntimeScriptValue Sc_DrawingSurface_DrawImage6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT6(ScriptDrawingSurface, DrawingSurface_DrawImage6);
 }
 
 RuntimeScriptValue Sc_DrawingSurface_DrawImage(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 10);
-	DrawingSurface_DrawImageEx((ScriptDrawingSurface *)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue,
+	DrawingSurface_DrawImage((ScriptDrawingSurface *)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue,
 		params[6].IValue, params[7].IValue, params[8].IValue, params[9].IValue);
 	return RuntimeScriptValue((int32_t)0);
 }
@@ -483,13 +483,13 @@ RuntimeScriptValue Sc_DrawingSurface_DrawStringWrapped(void *self, const Runtime
 }
 
 // void (ScriptDrawingSurface* target, ScriptDrawingSurface* source, int translev)
-RuntimeScriptValue Sc_DrawingSurface_DrawSurface_2(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_POBJ_PINT(ScriptDrawingSurface, DrawingSurface_DrawSurface, ScriptDrawingSurface);
+RuntimeScriptValue Sc_DrawingSurface_DrawSurface2(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_POBJ_PINT(ScriptDrawingSurface, DrawingSurface_DrawSurface2, ScriptDrawingSurface);
 }
 
 RuntimeScriptValue Sc_DrawingSurface_DrawSurface(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 10);
-	DrawingSurface_DrawSurfaceEx((ScriptDrawingSurface *)self, (ScriptDrawingSurface *)params[0].Ptr,
+	DrawingSurface_DrawSurface((ScriptDrawingSurface *)self, (ScriptDrawingSurface *)params[0].Ptr,
 		params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue,
 		params[6].IValue, params[7].IValue, params[8].IValue, params[9].IValue);
 	return RuntimeScriptValue((int32_t)0);
@@ -550,7 +550,7 @@ void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*com
 	ccAddExternalObjectFunction("DrawingSurface::Clear^1", Sc_DrawingSurface_Clear);
 	ccAddExternalObjectFunction("DrawingSurface::CreateCopy^0", Sc_DrawingSurface_CreateCopy);
 	ccAddExternalObjectFunction("DrawingSurface::DrawCircle^3", Sc_DrawingSurface_DrawCircle);
-	ccAddExternalObjectFunction("DrawingSurface::DrawImage^6", Sc_DrawingSurface_DrawImage_6);
+	ccAddExternalObjectFunction("DrawingSurface::DrawImage^6", Sc_DrawingSurface_DrawImage6);
 	ccAddExternalObjectFunction("DrawingSurface::DrawImage^10", Sc_DrawingSurface_DrawImage);
 	ccAddExternalObjectFunction("DrawingSurface::DrawLine^5", Sc_DrawingSurface_DrawLine);
 	ccAddExternalObjectFunction("DrawingSurface::DrawMessageWrapped^5", Sc_DrawingSurface_DrawMessageWrapped);
@@ -561,7 +561,7 @@ void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*com
 		ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped_Old);
 	else
 		ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped);
-	ccAddExternalObjectFunction("DrawingSurface::DrawSurface^2", Sc_DrawingSurface_DrawSurface_2);
+	ccAddExternalObjectFunction("DrawingSurface::DrawSurface^2", Sc_DrawingSurface_DrawSurface2);
 	ccAddExternalObjectFunction("DrawingSurface::DrawSurface^10", Sc_DrawingSurface_DrawSurface);
 	ccAddExternalObjectFunction("DrawingSurface::DrawTriangle^6", Sc_DrawingSurface_DrawTriangle);
 	ccAddExternalObjectFunction("DrawingSurface::GetPixel^2", Sc_DrawingSurface_GetPixel);
diff --git a/engines/ags/engine/ac/drawing_surface.h b/engines/ags/engine/ac/drawing_surface.h
index f38231efb2b..896437feae8 100644
--- a/engines/ags/engine/ac/drawing_surface.h
+++ b/engines/ags/engine/ac/drawing_surface.h
@@ -29,8 +29,11 @@ namespace AGS3 {
 void    DrawingSurface_Release(ScriptDrawingSurface *sds);
 // convert actual co-ordinate back to what the script is expecting
 ScriptDrawingSurface *DrawingSurface_CreateCopy(ScriptDrawingSurface *sds);
-void    DrawingSurface_DrawSurface(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int translev);
-void    DrawingSurface_DrawImage(ScriptDrawingSurface *sds, int xx, int yy, int slot, int trans, int width, int height);
+void    DrawingSurface_DrawSurface(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans,
+								   int dst_x, int dst_y, int dst_width, int dst_height,
+								   int src_x, int src_y, int src_width, int src_height);
+void    DrawingSurface_DrawSurface2(ScriptDrawingSurface *target, ScriptDrawingSurface *source, int trans);
+void    DrawingSurface_DrawImage6(ScriptDrawingSurface *sds, int xx, int yy, int slot, int trans, int width, int height);
 void    DrawingSurface_SetDrawingColor(ScriptDrawingSurface *sds, int newColour);
 int     DrawingSurface_GetDrawingColor(ScriptDrawingSurface *sds);
 void    DrawingSurface_SetUseHighResCoordinates(ScriptDrawingSurface *sds, int highRes);
diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 9279db82025..3f080046442 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -184,7 +184,7 @@ void ChangeCursorHotspot(int curs, int x, int y) {
 		set_mouse_cursor(_G(cur_cursor));
 }
 
-void Mouse_ChangeModeViewEx(int curs, int newview, int delay) {
+void Mouse_ChangeModeView(int curs, int newview, int delay) {
 	if ((curs < 0) || (curs >= _GP(game).numcursors))
 		quit("!Mouse.ChangeModeView: invalid mouse cursor");
 
@@ -202,8 +202,8 @@ void Mouse_ChangeModeViewEx(int curs, int newview, int delay) {
 		_G(mouse_delay) = 0;  // force update
 }
 
-void Mouse_ChangeModeView(int curs, int newview) {
-	Mouse_ChangeModeViewEx(curs, newview, SCR_NO_VALUE);
+void Mouse_ChangeModeView2(int curs, int newview) {
+	Mouse_ChangeModeView(curs, newview, SCR_NO_VALUE);
 }
 
 void SetNextCursor() {
@@ -459,12 +459,12 @@ RuntimeScriptValue Sc_ChangeCursorHotspot(const RuntimeScriptValue *params, int3
 }
 
 // void (int curs, int newview)
-RuntimeScriptValue Sc_Mouse_ChangeModeView_2(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT2(Mouse_ChangeModeView);
+RuntimeScriptValue Sc_Mouse_ChangeModeView2(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT2(Mouse_ChangeModeView2);
 }
 
 RuntimeScriptValue Sc_Mouse_ChangeModeView(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT3(Mouse_ChangeModeViewEx);
+	API_SCALL_VOID_PINT3(Mouse_ChangeModeView);
 }
 
 // void (int modd)
@@ -585,7 +585,7 @@ RuntimeScriptValue Sc_Mouse_SetSpeed(const RuntimeScriptValue *params, int32_t p
 void RegisterMouseAPI() {
 	ccAddExternalStaticFunction("Mouse::ChangeModeGraphic^2", Sc_ChangeCursorGraphic);
 	ccAddExternalStaticFunction("Mouse::ChangeModeHotspot^3", Sc_ChangeCursorHotspot);
-	ccAddExternalStaticFunction("Mouse::ChangeModeView^2", Sc_Mouse_ChangeModeView_2);
+	ccAddExternalStaticFunction("Mouse::ChangeModeView^2", Sc_Mouse_ChangeModeView2);
 	ccAddExternalStaticFunction("Mouse::ChangeModeView^3", Sc_Mouse_ChangeModeView);
 	ccAddExternalStaticFunction("Mouse::Click^1", Sc_Mouse_Click);
 	ccAddExternalStaticFunction("Mouse::DisableMode^1", Sc_disable_cursor_mode);
diff --git a/engines/ags/engine/ac/mouse.h b/engines/ags/engine/ac/mouse.h
index b9bd5aaaa81..4e443bcba20 100644
--- a/engines/ags/engine/ac/mouse.h
+++ b/engines/ags/engine/ac/mouse.h
@@ -29,7 +29,8 @@ namespace AGS3 {
 void Mouse_SetVisible(int isOn);
 int Mouse_GetVisible();
 int Mouse_GetModeGraphic(int curs);
-void Mouse_ChangeModeView(int curs, int newview);
+void Mouse_ChangeModeView(int curs, int newview, int delay);
+void Mouse_ChangeModeView2(int curs, int newview);
 // The Mouse:: functions are static so the script doesn't pass
 // in an object parameter
 void SetMousePosition(int newx, int newy);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 016c9bfc801..1267ba0aebc 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -121,7 +121,11 @@ void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blo
 }
 
 void Object_Animate5(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) {
-	Object_Animate(objj, loop, delay, repeat, blocking, direction, 0, 100 /* full volume */);
+	Object_Animate(objj, loop, delay, repeat, blocking, direction, 0 /* frame */, 100 /* full volume */);
+}
+
+void Object_Animate6(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction, int sframe) {
+	Object_Animate(objj, loop, delay, repeat, blocking, direction, sframe, 100 /* full volume */);
 }
 
 void Object_StopAnimating(ScriptObject *objj) {
@@ -674,10 +678,10 @@ RuntimeScriptValue Sc_Object_Animate5(void *self, const RuntimeScriptValue *para
 }
 
 RuntimeScriptValue Sc_Object_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT6(ScriptObject, Object_Animate);
+	API_OBJCALL_VOID_PINT6(ScriptObject, Object_Animate6);
 }
 
-RuntimeScriptValue Sc_Object_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Object_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_PINT7(ScriptObject, Object_Animate);
 }
 
@@ -984,7 +988,7 @@ RuntimeScriptValue Sc_Object_SetY(void *self, const RuntimeScriptValue *params,
 void RegisterObjectAPI() {
 	ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate5);
 	ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_Animate6);
-	ccAddExternalObjectFunction("Object::Animate^7", Sc_Object_Animate7);
+	ccAddExternalObjectFunction("Object::Animate^7", Sc_Object_Animate);
 	ccAddExternalObjectFunction("Object::IsCollidingWithObject^1", Sc_Object_IsCollidingWithObject);
 	ccAddExternalObjectFunction("Object::GetName^1", Sc_Object_GetName);
 	ccAddExternalObjectFunction("Object::GetProperty^1", Sc_Object_GetProperty);
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index d38c78354d7..7c24bbab1ae 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -241,17 +241,24 @@ ScreenOverlay *Overlay_CreateTextCore(bool room_layer, int x, int y, int width,
 	return _display_main(x, y, width, text, disp_type, font, -text_color, 0, allow_shrink, false, room_layer);
 }
 
-ScriptOverlay *Overlay_CreateGraphicalEx(bool room_layer, int x, int y, int slot, int transparent, bool clone) {
-	auto *over = Overlay_CreateGraphicCore(room_layer, x, y, slot, transparent != 0, clone);
+ScriptOverlay *Overlay_CreateGraphicalImpl(bool room_layer, int x, int y, int slot, bool transparent, bool clone) {
+	auto *over = Overlay_CreateGraphicCore(room_layer, x, y, slot, transparent, clone);
 	return over ? create_scriptoverlay(*over) : nullptr;
 }
 
-ScriptOverlay *Overlay_CreateGraphical(int x, int y, int slot, int transparent) {
-	auto *over = Overlay_CreateGraphicCore(false, x, y, slot, transparent != 0, true); // always clone
-	return over ? create_scriptoverlay(*over) : nullptr;
+ScriptOverlay *Overlay_CreateGraphical4(int x, int y, int slot, bool transparent) {
+	return Overlay_CreateGraphical(x, y, slot, transparent, true /* clone */);
+}
+
+ScriptOverlay *Overlay_CreateGraphical(int x, int y, int slot, bool transparent, bool clone) {
+	return Overlay_CreateGraphicalImpl(false, x, y, slot, transparent, clone);
 }
 
-ScriptOverlay *Overlay_CreateTextualEx(bool room_layer, int x, int y, int width, int font, int colour, const char *text) {
+ScriptOverlay *Overlay_CreateRoomGraphical(int x, int y, int slot, bool transparent, bool clone) {
+	return Overlay_CreateGraphicalImpl(true, x, y, slot, transparent, clone);
+}
+
+ScriptOverlay *Overlay_CreateTextualImpl(bool room_layer, int x, int y, int width, int font, int colour, const char *text) {
 	data_to_game_coords(&x, &y);
 	width = data_to_game_coord(width);
 	auto *over = Overlay_CreateTextCore(room_layer, x, y, width, font, colour, text, DISPLAYTEXT_NORMALOVERLAY, 0);
@@ -259,7 +266,11 @@ ScriptOverlay *Overlay_CreateTextualEx(bool room_layer, int x, int y, int width,
 }
 
 ScriptOverlay *Overlay_CreateTextual(int x, int y, int width, int font, int colour, const char *text) {
-	return Overlay_CreateTextualEx(false, x, y, width, font, colour, text);
+	return Overlay_CreateTextualImpl(false, x, y, width, font, colour, text);
+}
+
+ScriptOverlay *Overlay_CreateRoomTextual(int x, int y, int width, int font, int colour, const char *text) {
+	return Overlay_CreateTextualImpl(true, x, y, width, font, colour, text);
 }
 
 int Overlay_GetTransparency(ScriptOverlay *scover) {
@@ -496,39 +507,30 @@ void recreate_overlay_ddbs() {
 //=============================================================================
 
 // ScriptOverlay* (int x, int y, int slot, int transparent)
-RuntimeScriptValue Sc_Overlay_CreateGraphical(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_PARAM_COUNT(FUNCTION, 4);
-	ScriptOverlay *overlay = Overlay_CreateGraphicalEx(false, params[0].IValue, params[1].IValue, params[2].IValue,
-		params[3].IValue, true); // always clone image
-	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
+RuntimeScriptValue Sc_Overlay_CreateGraphical4(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJAUTO_PINT3_PBOOL(ScriptOverlay, Overlay_CreateGraphical4);
 }
 
-RuntimeScriptValue Sc_Overlay_CreateGraphicalRef(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_PARAM_COUNT(FUNCTION, 5);
-	ScriptOverlay *overlay = Overlay_CreateGraphicalEx(false, params[0].IValue, params[1].IValue, params[2].IValue,
-		params[3].IValue, params[4].GetAsBool());
-	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
+RuntimeScriptValue Sc_Overlay_CreateGraphical(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJAUTO_PINT3_PBOOL2(ScriptOverlay, Overlay_CreateGraphical);
 }
 
 RuntimeScriptValue Sc_Overlay_CreateRoomGraphical(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_PARAM_COUNT(FUNCTION, 5);
-	ScriptOverlay *overlay = Overlay_CreateGraphicalEx(true, params[0].IValue, params[1].IValue, params[2].IValue,
-		params[3].IValue, params[4].GetAsBool());
-	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
+	API_SCALL_OBJAUTO_PINT3_PBOOL2(ScriptOverlay, Overlay_CreateRoomGraphical);
 }
 
 // ScriptOverlay* (int x, int y, int width, int font, int colour, const char* text, ...)
 RuntimeScriptValue Sc_Overlay_CreateTextual(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(Overlay_CreateTextual, 6);
-	ScriptOverlay *overlay = Overlay_CreateTextualEx(false, params[0].IValue, params[1].IValue, params[2].IValue,
-		params[3].IValue, params[4].IValue, scsf_buffer);
+	ScriptOverlay *overlay = Overlay_CreateTextual(params[0].IValue, params[1].IValue, params[2].IValue,
+												   params[3].IValue, params[4].IValue, scsf_buffer);
 	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
 }
 
 RuntimeScriptValue Sc_Overlay_CreateRoomTextual(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(Overlay_CreateRoomTextual, 6);
-	ScriptOverlay *overlay = Overlay_CreateTextualEx(true, params[0].IValue, params[1].IValue, params[2].IValue,
-		params[3].IValue, params[4].IValue, scsf_buffer);
+	ScriptOverlay *overlay = Overlay_CreateRoomTextual(params[0].IValue, params[1].IValue, params[2].IValue,
+													   params[3].IValue, params[4].IValue, scsf_buffer);
 	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
 }
 
@@ -635,8 +637,8 @@ void ScPl_Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, c
 
 
 void RegisterOverlayAPI() {
-	ccAddExternalStaticFunction("Overlay::CreateGraphical^4", Sc_Overlay_CreateGraphical);
-	ccAddExternalStaticFunction("Overlay::CreateGraphical^5", Sc_Overlay_CreateGraphicalRef);
+	ccAddExternalStaticFunction("Overlay::CreateGraphical^4", Sc_Overlay_CreateGraphical4);
+	ccAddExternalStaticFunction("Overlay::CreateGraphical^5", Sc_Overlay_CreateGraphical);
 	ccAddExternalStaticFunction("Overlay::CreateTextual^106", Sc_Overlay_CreateTextual);
 	ccAddExternalStaticFunction("Overlay::CreateRoomGraphical^5", Sc_Overlay_CreateRoomGraphical);
 	ccAddExternalStaticFunction("Overlay::CreateRoomTextual^106", Sc_Overlay_CreateRoomTextual);
diff --git a/engines/ags/engine/ac/overlay.h b/engines/ags/engine/ac/overlay.h
index f9b3cd507d1..3046edc88a9 100644
--- a/engines/ags/engine/ac/overlay.h
+++ b/engines/ags/engine/ac/overlay.h
@@ -43,11 +43,11 @@ void Overlay_SetX(ScriptOverlay *scover, int newx);
 int  Overlay_GetY(ScriptOverlay *scover);
 void Overlay_SetY(ScriptOverlay *scover, int newy);
 int  Overlay_GetValid(ScriptOverlay *scover);
-ScriptOverlay *Overlay_CreateGraphical(int x, int y, int slot, int transparent);
+ScriptOverlay *Overlay_CreateGraphical(int x, int y, int slot, bool transparent = true, bool clone = false);
 ScriptOverlay *Overlay_CreateTextual(int x, int y, int width, int font, int colour, const char *text);
-ScreenOverlay *Overlay_CreateGraphicCore(bool room_layer, int x, int y, int slot, bool transparent, bool clone);
+ScreenOverlay *Overlay_CreateGraphicCore(bool room_layer, int x, int y, int slot, bool transparent = true, bool clone = false);
 ScreenOverlay *Overlay_CreateTextCore(bool room_layer, int x, int y, int width, int font, int text_color,
-	const char *text, int disp_type, int allow_shrink);
+									  const char *text, int disp_type, int allow_shrink);
 
 int  find_overlay_of_type(int type);
 void remove_screen_overlay(int type);
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 08a2b4ded16..582bd1970e0 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -145,6 +145,10 @@ ScriptUserObject *Screen_ScreenToRoomPoint(int scrx, int scry, bool restrict) {
 	return ScriptStructHelpers::CreatePoint(vpt.first.X, vpt.first.Y);
 }
 
+ScriptUserObject *Screen_ScreenToRoomPoint2(int scrx, int scry) {
+	return Screen_ScreenToRoomPoint(scrx, scry, true);
+}
+
 ScriptUserObject *Screen_RoomToScreenPoint(int roomx, int roomy) {
 	data_to_game_coords(&roomx, &roomy);
 	Point pt = _GP(play).RoomToScreen(roomx, roomy);
@@ -181,13 +185,11 @@ RuntimeScriptValue Sc_Screen_GetAnyViewport(const RuntimeScriptValue *params, in
 }
 
 RuntimeScriptValue Sc_Screen_ScreenToRoomPoint2(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_PARAM_COUNT(FUNCTION, 2);
-	ScriptUserObject* obj = Screen_ScreenToRoomPoint(params[0].IValue, params[1].IValue, true);
-	return RuntimeScriptValue().SetDynamicObject(obj, obj);
+	API_SCALL_OBJAUTO_PINT2(ScriptUserObject, Screen_ScreenToRoomPoint2);
 }
 
-RuntimeScriptValue Sc_Screen_ScreenToRoomPoint3(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_OBJAUTO_PINT3(ScriptUserObject, Screen_ScreenToRoomPoint);
+RuntimeScriptValue Sc_Screen_ScreenToRoomPoint(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJAUTO_PINT2_PBOOL(ScriptUserObject, Screen_ScreenToRoomPoint);
 }
 
 RuntimeScriptValue Sc_Screen_RoomToScreenPoint(const RuntimeScriptValue *params, int32_t param_count) {
@@ -203,7 +205,7 @@ void RegisterScreenAPI() {
 	ccAddExternalStaticFunction("Screen::get_ViewportCount", Sc_Screen_GetViewportCount);
 	ccAddExternalStaticFunction("Screen::geti_Viewports", Sc_Screen_GetAnyViewport);
 	ccAddExternalStaticFunction("Screen::ScreenToRoomPoint^2", Sc_Screen_ScreenToRoomPoint2);
-	ccAddExternalStaticFunction("Screen::ScreenToRoomPoint^3", Sc_Screen_ScreenToRoomPoint3);
+	ccAddExternalStaticFunction("Screen::ScreenToRoomPoint^3", Sc_Screen_ScreenToRoomPoint);
 	ccAddExternalStaticFunction("Screen::RoomToScreenPoint", Sc_Screen_RoomToScreenPoint);
 }
 
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index 33c6972722a..c756678c7ab 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -374,6 +374,21 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \
 	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
 
+#define API_SCALL_OBJAUTO_PINT2_PBOOL(RET_CLASS, FUNCTION) \
+	ASSERT_PARAM_COUNT(FUNCTION, 3); \
+	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].GetAsBool()); \
+	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+
+#define API_SCALL_OBJAUTO_PINT3_PBOOL(RET_CLASS, FUNCTION) \
+	ASSERT_PARAM_COUNT(FUNCTION, 4); \
+	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].GetAsBool()); \
+	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+
+#define API_SCALL_OBJAUTO_PINT3_PBOOL2(RET_CLASS, FUNCTION) \
+	ASSERT_PARAM_COUNT(FUNCTION, 5); \
+	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].GetAsBool(), params[4].GetAsBool()); \
+	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+
 #define API_SCALL_OBJAUTO_PBOOL2(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].GetAsBool(), params[1].GetAsBool()); \
diff --git a/engines/ags/plugins/core/drawing_surface.cpp b/engines/ags/plugins/core/drawing_surface.cpp
index be534bcfdf1..8ebb5efc35c 100644
--- a/engines/ags/plugins/core/drawing_surface.cpp
+++ b/engines/ags/plugins/core/drawing_surface.cpp
@@ -74,7 +74,7 @@ void DrawingSurface::DrawCircle(ScriptMethodParams &params) {
 
 void DrawingSurface::DrawImage(ScriptMethodParams &params) {
 	PARAMS7(ScriptDrawingSurface *, sds, int, xx, int, yy, int, slot, int, trans, int, width, int, height);
-	AGS3::DrawingSurface_DrawImage(sds, xx, yy, slot, trans, width, height);
+	AGS3::DrawingSurface_DrawImage6(sds, xx, yy, slot, trans, width, height);
 }
 
 void DrawingSurface::DrawLine(ScriptMethodParams &params) {
@@ -116,7 +116,7 @@ void DrawingSurface::DrawStringWrapped(ScriptMethodParams &params) {
 
 void DrawingSurface::DrawSurface(ScriptMethodParams &params) {
 	PARAMS3(ScriptDrawingSurface *, target, ScriptDrawingSurface *, source, int, translev);
-	AGS3::DrawingSurface_DrawSurface(target, source, translev);
+	AGS3::DrawingSurface_DrawSurface2(target, source, translev);
 }
 
 void DrawingSurface::DrawTriangle(ScriptMethodParams &params) {
diff --git a/engines/ags/plugins/core/mouse.cpp b/engines/ags/plugins/core/mouse.cpp
index 7e3f59cd36e..5d0a8ddafac 100644
--- a/engines/ags/plugins/core/mouse.cpp
+++ b/engines/ags/plugins/core/mouse.cpp
@@ -32,7 +32,7 @@ void Mouse::AGS_EngineStartup(IAGSEngine *engine) {
 
 	SCRIPT_METHOD(Mouse::ChangeModeGraphic^2, Mouse::ChangeCursorGraphic);
 	SCRIPT_METHOD(Mouse::ChangeModeHotspot^3, Mouse::ChangeCursorHotspot);
-	SCRIPT_METHOD(Mouse::ChangeModeView^2, Mouse::Mouse_ChangeModeView);
+	SCRIPT_METHOD(Mouse::ChangeModeView^2, Mouse::Mouse_ChangeModeView2);
 	SCRIPT_METHOD(Mouse::DisableMode^1, Mouse::disable_cursor_mode);
 	SCRIPT_METHOD(Mouse::EnableMode^1, Mouse::enable_cursor_mode);
 	SCRIPT_METHOD(Mouse::GetModeGraphic^1, Mouse::Mouse_GetModeGraphic);
@@ -62,9 +62,9 @@ void Mouse::ChangeCursorHotspot(ScriptMethodParams &params) {
 	AGS3::ChangeCursorHotspot(curs, x, y);
 }
 
-void Mouse::Mouse_ChangeModeView(ScriptMethodParams &params) {
+void Mouse::Mouse_ChangeModeView2(ScriptMethodParams &params) {
 	PARAMS2(int, curs, int, newview);
-	AGS3::Mouse_ChangeModeView(curs, newview);
+	AGS3::Mouse_ChangeModeView2(curs, newview);
 }
 
 void Mouse::disable_cursor_mode(ScriptMethodParams &params) {
diff --git a/engines/ags/plugins/core/mouse.h b/engines/ags/plugins/core/mouse.h
index 85c3769b7c5..593e8f2a308 100644
--- a/engines/ags/plugins/core/mouse.h
+++ b/engines/ags/plugins/core/mouse.h
@@ -36,7 +36,7 @@ public:
 
 	void ChangeCursorGraphic(ScriptMethodParams &params);
 	void ChangeCursorHotspot(ScriptMethodParams &params);
-	void Mouse_ChangeModeView(ScriptMethodParams &params);
+	void Mouse_ChangeModeView2(ScriptMethodParams &params);
 	void disable_cursor_mode(ScriptMethodParams &params);
 	void enable_cursor_mode(ScriptMethodParams &params);
 	void Mouse_GetModeGraphic(ScriptMethodParams &params);


Commit: 5fe0694e56666d235bb7d12d9abfd04beff2a332
    https://github.com/scummvm/scummvm/commit/5fe0694e56666d235bb7d12d9abfd04beff2a332
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added explicit functions for Room.GetDrawingSurface api

from upstream 035b3bc5bb208b6d93855b4d3fd207efd55f18fc

Changed paths:
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/hotspot.cpp
    engines/ags/engine/ac/region.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/room.h


diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index a5682aad1d2..958b56d1c56 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -717,15 +717,11 @@ RuntimeScriptValue Sc_GetWalkableAreaAtScreen(const RuntimeScriptValue *params,
 }
 
 RuntimeScriptValue Sc_GetDrawingSurfaceForWalkableArea(const RuntimeScriptValue *params, int32_t param_count) {
-	(void)params; (void)param_count;
-	ScriptDrawingSurface *ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaWalkable);
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj);
+	API_SCALL_OBJAUTO(ScriptDrawingSurface, GetDrawingSurfaceForWalkableArea);
 }
 
 RuntimeScriptValue Sc_GetDrawingSurfaceForWalkbehind(const RuntimeScriptValue *params, int32_t param_count) {
-	(void)params; (void)param_count;
-	ScriptDrawingSurface *ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaWalkBehind);
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj);
+	API_SCALL_OBJAUTO(ScriptDrawingSurface, GetDrawingSurfaceForWalkbehind);
 }
 
 // void (int amnt)
diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp
index 725fdc54554..24b97f47a36 100644
--- a/engines/ags/engine/ac/hotspot.cpp
+++ b/engines/ags/engine/ac/hotspot.cpp
@@ -155,9 +155,7 @@ RuntimeScriptValue Sc_GetHotspotAtScreen(const RuntimeScriptValue *params, int32
 }
 
 RuntimeScriptValue Sc_Hotspot_GetDrawingSurface(const RuntimeScriptValue *params, int32_t param_count) {
-	(void)params; (void)param_count;
-	ScriptDrawingSurface *ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaHotspot);
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj);
+	API_SCALL_OBJAUTO(ScriptDrawingSurface, Hotspot_GetDrawingSurface);
 }
 
 // void (ScriptHotspot *hss, char *buffer)
diff --git a/engines/ags/engine/ac/region.cpp b/engines/ags/engine/ac/region.cpp
index 4de875483ee..3bc33240b55 100644
--- a/engines/ags/engine/ac/region.cpp
+++ b/engines/ags/engine/ac/region.cpp
@@ -140,9 +140,7 @@ RuntimeScriptValue Sc_GetRegionAtScreen(const RuntimeScriptValue *params, int32_
 }
 
 RuntimeScriptValue Sc_Region_GetDrawingSurface(const RuntimeScriptValue *params, int32_t param_count) {
-	(void)params; (void)param_count;
-	ScriptDrawingSurface *ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaRegion);
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj);
+	API_SCALL_OBJAUTO(ScriptDrawingSurface, Region_GetDrawingSurface);
 }
 
 RuntimeScriptValue Sc_Region_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index dbbdeff2f02..4506a1c5daf 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -180,6 +180,22 @@ bool Room_Exists(int room) {
 	return _GP(AssetMgr)->DoesAssetExist(room_filename);
 }
 
+ScriptDrawingSurface *GetDrawingSurfaceForWalkableArea() {
+	return Room_GetDrawingSurfaceForMask(kRoomAreaWalkable);
+}
+
+ScriptDrawingSurface *GetDrawingSurfaceForWalkbehind() {
+	return Room_GetDrawingSurfaceForMask(kRoomAreaWalkBehind);
+}
+
+ScriptDrawingSurface *Hotspot_GetDrawingSurface() {
+	return Room_GetDrawingSurfaceForMask(kRoomAreaHotspot);
+}
+
+ScriptDrawingSurface *Region_GetDrawingSurface() {
+	return Room_GetDrawingSurfaceForMask(kRoomAreaRegion);
+}
+
 //=============================================================================
 
 // Makes sure that room background and walk-behind mask are matching room size
diff --git a/engines/ags/engine/ac/room.h b/engines/ags/engine/ac/room.h
index 58174c83e01..a7c43a29613 100644
--- a/engines/ags/engine/ac/room.h
+++ b/engines/ags/engine/ac/room.h
@@ -47,6 +47,10 @@ bool Room_SetTextProperty(const char *property, const char *value);
 const char *Room_GetMessages(int index);
 bool Room_Exists(int room);
 RuntimeScriptValue Sc_Room_GetProperty(const RuntimeScriptValue *params, int32_t param_count);
+ScriptDrawingSurface *GetDrawingSurfaceForWalkableArea();
+ScriptDrawingSurface *GetDrawingSurfaceForWalkbehind();
+ScriptDrawingSurface *Hotspot_GetDrawingSurface();
+ScriptDrawingSurface *Region_GetDrawingSurface();
 
 //=============================================================================
 


Commit: ad168dbee89bd2e8e243f07c8ae442612a421051
    https://github.com/scummvm/scummvm/commit/ad168dbee89bd2e8e243f07c8ae442612a421051
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added explicit functions for Speech api and GetAudioClipCount()

from upstream 45dcb946a818a65768dfa7f08f27b27a2dccffc2

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/speech.cpp
    engines/ags/engine/script/script_api.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 702a0ed3d72..d500bb98264 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -679,6 +679,10 @@ bool Game_ChangeSpeechVox(const char *newFilename) {
 	return true;
 }
 
+int Game_GetAudioClipCount() {
+	return _GP(game).audioClips.size();
+}
+
 ScriptAudioClip *Game_GetAudioClip(int index) {
 	if (index < 0 || (size_t)index >= _GP(game).audioClips.size())
 		return nullptr;
@@ -1681,7 +1685,7 @@ RuntimeScriptValue Sc_Game_GetViewCount(const RuntimeScriptValue *params, int32_
 }
 
 RuntimeScriptValue Sc_Game_GetAudioClipCount(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(game).audioClips.size());
+	API_SCALL_INT(Game_GetAudioClipCount);
 }
 
 RuntimeScriptValue Sc_Game_GetAudioClip(const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp
index a462ff659a2..11350380143 100644
--- a/engines/ags/engine/ac/speech.cpp
+++ b/engines/ags/engine/ac/speech.cpp
@@ -162,70 +162,152 @@ ScriptOverlay *Speech_GetPortraitOverlay() {
 	return const_cast<ScriptOverlay*>((const ScriptOverlay*)ccGetObjectAddressFromHandle(_GP(play).speech_face_schandle));
 }
 
+int Speech_GetAnimationStopTimeMargin() {
+	return _GP(play).close_mouth_speech_time;
+}
+
+void Speech_SetAnimationStopTimeMargin(int time) {
+	_GP(play).close_mouth_speech_time = time;
+}
+
+int Speech_GetCustomPortraitPlacement() {
+	return _GP(play).speech_portrait_placement;
+}
+
+void Speech_SetCustomPortraitPlacement(int placement) {
+	_GP(play).speech_portrait_placement = placement;
+}
+
+int Speech_GetDisplayPostTimeMs() {
+	return _GP(play).speech_display_post_time_ms;
+}
+
+void Speech_SetDisplayPostTimeMs(int time_ms) {
+	_GP(play).speech_display_post_time_ms = time_ms;
+}
+
+int Speech_GetGlobalSpeechAnimationDelay() {
+	return _GP(play).talkanim_speed;
+}
+
+void Speech_SetGlobalSpeechAnimationDelay(int delay) {
+	if (_GP(game).options[OPT_GLOBALTALKANIMSPD] == 0) {
+		debug_script_warn("Speech.GlobalSpeechAnimationDelay cannot be set when global speech animation speed is not enabled; set Speech.UseGlobalSpeechAnimationDelay first!");
+		return;
+	}
+	_GP(play).talkanim_speed = delay;
+}
+
+int Speech_GetPortraitXOffset() {
+	return _GP(play).speech_portrait_x;
+}
+
+void Speech_SetPortraitXOffset(int x) {
+	_GP(play).speech_portrait_x = x;
+}
+
+int Speech_GetPortraitY() {
+	return _GP(play).speech_portrait_y;
+}
+
+void Speech_SetPortraitY(int y) {
+	_GP(play).speech_portrait_y = y;
+}
+
+int Speech_GetStyle() {
+	return _GP(game).options[OPT_SPEECHTYPE];
+}
+
+int Speech_GetSkipKey() {
+	return _GP(play).skip_speech_specific_key;
+}
+
+void Speech_SetSkipKey(int key) {
+	_GP(play).skip_speech_specific_key = key;
+}
+
+int Speech_GetTextAlignment() {
+	return _GP(play).speech_text_align;
+}
+
+void Speech_SetTextAlignment_Old(int alignment) {
+	_GP(play).speech_text_align = ReadScriptAlignment(alignment);
+}
+
+void Speech_SetTextAlignment(int alignment) {
+	_GP(play).speech_text_align = (HorAlignment)alignment;
+}
+
+int Speech_GetUseGlobalSpeechAnimationDelay() {
+	return _GP(game).options[OPT_GLOBALTALKANIMSPD];
+}
+
+void Speech_SetUseGlobalSpeechAnimationDelay(int delay) {
+	_GP(game).options[OPT_GLOBALTALKANIMSPD] = delay;
+}
+
+//-----------------------------------------------------------------------------
+
 RuntimeScriptValue Sc_Speech_GetAnimationStopTimeMargin(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).close_mouth_speech_time);
+	API_SCALL_INT(Speech_GetAnimationStopTimeMargin);
 }
 
 RuntimeScriptValue Sc_Speech_SetAnimationStopTimeMargin(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).close_mouth_speech_time);
+	API_SCALL_VOID_PINT(Speech_SetAnimationStopTimeMargin);
 }
 
 RuntimeScriptValue Sc_Speech_GetCustomPortraitPlacement(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).speech_portrait_placement);
+	API_SCALL_INT(Speech_GetCustomPortraitPlacement);
 }
 
 RuntimeScriptValue Sc_Speech_SetCustomPortraitPlacement(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).speech_portrait_placement);
+	API_SCALL_VOID_PINT(Speech_SetCustomPortraitPlacement);
 }
 
 RuntimeScriptValue Sc_Speech_GetDisplayPostTimeMs(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).speech_display_post_time_ms);
+	API_SCALL_INT(Speech_GetDisplayPostTimeMs);
 }
 
 RuntimeScriptValue Sc_Speech_SetDisplayPostTimeMs(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).speech_display_post_time_ms);
+	API_SCALL_VOID_PINT(Speech_SetDisplayPostTimeMs);
 }
 
 RuntimeScriptValue Sc_Speech_GetGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).talkanim_speed);
+	API_SCALL_INT(Speech_GetGlobalSpeechAnimationDelay);
 }
 
 RuntimeScriptValue Sc_Speech_SetGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) {
-	if (_GP(game).options[OPT_GLOBALTALKANIMSPD] == 0) {
-		debug_script_warn("Speech.GlobalSpeechAnimationDelay cannot be set when global speech animation speed is not enabled; set Speech.UseGlobalSpeechAnimationDelay first!");
-		return RuntimeScriptValue();
-	}
-	API_VARSET_PINT(_GP(play).talkanim_speed);
+	API_SCALL_VOID_PINT(Speech_SetGlobalSpeechAnimationDelay);
 }
 
 RuntimeScriptValue Sc_Speech_GetPortraitXOffset(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).speech_portrait_x);
+	API_SCALL_INT(Speech_GetPortraitXOffset);
 }
 
 RuntimeScriptValue Sc_Speech_SetPortraitXOffset(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).speech_portrait_x);
+	API_SCALL_VOID_PINT(Speech_SetPortraitXOffset);
 }
 
 RuntimeScriptValue Sc_Speech_GetPortraitY(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).speech_portrait_y);
+	API_SCALL_INT(Speech_GetPortraitY);
 }
 
 RuntimeScriptValue Sc_Speech_SetPortraitY(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).speech_portrait_y);
+	API_SCALL_VOID_PINT(Speech_SetPortraitY);
 }
 
 RuntimeScriptValue Sc_Speech_GetStyle(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(game).options[OPT_SPEECHTYPE]);
+	API_SCALL_INT(Speech_GetStyle);
 }
 
 extern RuntimeScriptValue Sc_SetSpeechStyle(const RuntimeScriptValue *params, int32_t param_count);
 
 RuntimeScriptValue Sc_Speech_GetSkipKey(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).skip_speech_specific_key);
+	API_SCALL_INT(Speech_GetSkipKey);
 }
 
 RuntimeScriptValue Sc_Speech_SetSkipKey(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(play).skip_speech_specific_key);
+	API_SCALL_VOID_PINT(Speech_SetSkipKey);
 }
 
 RuntimeScriptValue Sc_Speech_GetSkipStyle(const RuntimeScriptValue *params, int32_t param_count) {
@@ -235,27 +317,23 @@ RuntimeScriptValue Sc_Speech_GetSkipStyle(const RuntimeScriptValue *params, int3
 extern RuntimeScriptValue Sc_SetSkipSpeech(const RuntimeScriptValue *params, int32_t param_count);
 
 RuntimeScriptValue Sc_Speech_GetTextAlignment(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(play).speech_text_align);
+	API_SCALL_INT(Speech_GetTextAlignment);
 }
 
 RuntimeScriptValue Sc_Speech_SetTextAlignment_Old(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_VARIABLE_VALUE(_GP(play).speech_text_align);
-	_GP(play).speech_text_align = ReadScriptAlignment(params[0].IValue);
-	return RuntimeScriptValue();
+	API_SCALL_VOID_PINT(Speech_SetTextAlignment_Old);
 }
 
 RuntimeScriptValue Sc_Speech_SetTextAlignment(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_VARIABLE_VALUE(_GP(play).speech_text_align);
-	_GP(play).speech_text_align = (HorAlignment)params[0].IValue;
-	return RuntimeScriptValue();
+	API_SCALL_VOID_PINT(Speech_SetTextAlignment);
 }
 
 RuntimeScriptValue Sc_Speech_GetUseGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARGET_INT(_GP(game).options[OPT_GLOBALTALKANIMSPD]);
+	API_SCALL_INT(Speech_GetUseGlobalSpeechAnimationDelay);
 }
 
 RuntimeScriptValue Sc_Speech_SetUseGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) {
-	API_VARSET_PINT(_GP(game).options[OPT_GLOBALTALKANIMSPD]);
+	API_SCALL_VOID_PINT(Speech_SetUseGlobalSpeechAnimationDelay);
 }
 
 RuntimeScriptValue Sc_Speech_GetVoiceMode(const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index c756678c7ab..51fe89e9ec8 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -73,18 +73,6 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
     ASSERT_SELF(METHOD); \
     ASSERT_PARAM_COUNT(METHOD, X)
 
-//-----------------------------------------------------------------------------
-// Get/set variables
-
-#define API_VARGET_INT(VARIABLE) \
-	(void)params; (void)param_count; \
-	return RuntimeScriptValue().SetInt32(VARIABLE)
-
-#define API_VARSET_PINT(VARIABLE) \
-	ASSERT_VARIABLE_VALUE(VARIABLE); \
-	VARIABLE = params[0].IValue; \
-	return RuntimeScriptValue()
-
 //-----------------------------------------------------------------------------
 // Calls to ScriptSprintf with automatic translation
 


Commit: a90e5b7dd7f088a692803982fe7f85573e402e36
    https://github.com/scummvm/scummvm/commit/a90e5b7dd7f088a692803982fe7f85573e402e36
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added missing exclusive variadic api impls for plugins

from upstream a61392135e734898b93d73db42942dc881250ef2

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/system.cpp
    engines/ags/engine/script/script_api.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 0ae4156b244..e67948c0a46 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -3569,7 +3569,7 @@ RuntimeScriptValue Sc_Character_SetZ(void *self, const RuntimeScriptValue *param
 
 //=============================================================================
 //
-// Exclusive API for Plugins
+// Exclusive variadic API implementation for Plugins
 //
 //=============================================================================
 
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index 0f06cbb2a8e..59e186e3cfd 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -542,7 +542,7 @@ RuntimeScriptValue Sc_DrawingSurface_GetWidth(void *self, const RuntimeScriptVal
 
 //=============================================================================
 //
-// Exclusive API for Plugins
+// Exclusive variadic API implementation for Plugins
 //
 //=============================================================================
 
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 7c24bbab1ae..6369ff360a5 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -625,10 +625,15 @@ RuntimeScriptValue Sc_Overlay_SetZOrder(void *self, const RuntimeScriptValue *pa
 
 //=============================================================================
 //
-// Exclusive API for Plugins
+// Exclusive variadic API implementation for Plugins
 //
 //=============================================================================
 
+ScriptOverlay *ScPl_Overlay_CreateRoomTextual(int x, int y, int width, int font, int colour, const char *text, ...) {
+	API_PLUGIN_SCRIPT_SPRINTF(text);
+	return Overlay_CreateRoomTextual(x, y, width, font, colour, scsf_buffer);
+}
+
 // void (ScriptOverlay *scover, int wii, int fontid, int clr, char*texx, ...)
 void ScPl_Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, char *texx, ...) {
 	API_PLUGIN_SCRIPT_SPRINTF(texx);
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index f15b027eb43..d97420bc25b 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -437,7 +437,7 @@ RuntimeScriptValue Sc_String_GetLength(void *self, const RuntimeScriptValue *par
 
 //=============================================================================
 //
-// Exclusive API for Plugins
+// Exclusive variadic API implementation for Plugins
 //
 //=============================================================================
 
diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index 36c3e8a6e84..84e11d31661 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -355,6 +355,16 @@ RuntimeScriptValue Sc_System_Log(const RuntimeScriptValue *params, int32_t param
 	return RuntimeScriptValue((int32_t)0);
 }
 
+//=============================================================================
+//
+// Exclusive variadic API implementation for Plugins
+//
+//=============================================================================
+
+void ScPl_System_Log(CharacterInfo *chaa, int message_type, const char *texx, ...) {
+	API_PLUGIN_SCRIPT_SPRINTF_PURE(texx);
+	Debug::Printf(kDbgGroup_Script, (MessageType)message_type, scsf_buffer);
+}
 
 void RegisterSystemAPI() {
 	ccAddExternalStaticFunction("System::get_AudioChannelCount", Sc_System_GetAudioChannelCount);
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index 51fe89e9ec8..a1374eb2d33 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -105,6 +105,13 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 	const char *scsf_buffer = ScriptVSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(FORMAT_STR), args); \
 	va_end(args)
 
+#define API_PLUGIN_SCRIPT_SPRINTF_PURE(FORMAT_STR) \
+	va_list args; \
+	va_start(args, FORMAT_STR); \
+	char ScSfBuffer[STD_BUFFER_SIZE]; \
+	const char *scsf_buffer = ScriptVSprintf(ScSfBuffer, STD_BUFFER_SIZE, FORMAT_STR, args); \
+	va_end(args)
+
 //-----------------------------------------------------------------------------
 // Calls to static functions
 //


Commit: 08bd16b55563922a8f308583e8548d5d958876e7
    https://github.com/scummvm/scummvm/commit/08bd16b55563922a8f308583e8548d5d958876e7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: replaced more if/else with switches in RuntimeScriptValue

>From upstream e51283a73c346d7738f809afd2955c0f95eaf72a

Changed paths:
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h


diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index d103b72f82c..d2bb3200ece 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -39,123 +39,152 @@ using namespace AGS::Shared;
 // TODO: use endian-agnostic method to access global vars
 
 uint8_t RuntimeScriptValue::ReadByte() const {
-	if (this->Type == kScValStackPtr || this->Type == kScValGlobalVar) {
+	switch (this->Type) {
+	case kScValStackPtr:
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			return *(uint8_t *)(RValue->GetPtrWithOffset() + this->IValue);
 		} else {
 			return static_cast<uint8_t>(RValue->IValue);
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+	case kScValStaticObject:
+	case kScValStaticArray:
 		return this->StcMgr->ReadInt8(this->Ptr, this->IValue);
-	} else if (this->Type == kScValDynamicObject) {
+	case kScValDynamicObject:
 		return this->DynMgr->ReadInt8(this->Ptr, this->IValue);
+	default:
+		return *((uint8_t *)this->GetPtrWithOffset());
 	}
-	return *((uint8_t *)this->GetPtrWithOffset());
 }
 
 int16_t RuntimeScriptValue::ReadInt16() const {
-	if (this->Type == kScValStackPtr) {
+	switch (this->Type) {
+	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
 			return *(int16_t *)(RValue->GetPtrWithOffset() + this->IValue);
 		} else {
-			return static_cast<uint16_t>(RValue->IValue);
+			return static_cast<int16_t>(RValue->IValue);
 		}
-	} else if (this->Type == kScValGlobalVar) {
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			return Memory::ReadInt16LE(RValue->GetPtrWithOffset() + this->IValue);
 		} else {
-			return static_cast<uint16_t>(RValue->IValue);
+			return static_cast<int16_t>(RValue->IValue);
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+	case kScValStaticObject:
+	case kScValStaticArray:
 		return this->StcMgr->ReadInt16(this->Ptr, this->IValue);
-	} else if (this->Type == kScValDynamicObject) {
+	case kScValDynamicObject:
 		return this->DynMgr->ReadInt16(this->Ptr, this->IValue);
+
+	default:
+		return *((int16_t *)this->GetPtrWithOffset());
 	}
-	return *((int16_t *)this->GetPtrWithOffset());
 }
 
 int32_t RuntimeScriptValue::ReadInt32() const {
-	if (this->Type == kScValStackPtr) {
+	switch (this->Type) {
+	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
 			return *(int32_t *)(RValue->GetPtrWithOffset() + this->IValue);
 		} else {
-			return RValue->IValue; // get RValue as int
+			return static_cast<int32_t>(RValue->IValue);
 		}
-	} else if (this->Type == kScValGlobalVar) {
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			return Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue);
 		} else {
-			return RValue->IValue; // get RValue as int
+			return static_cast<uint32_t>(RValue->IValue);
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+	case kScValStaticObject:
+	case kScValStaticArray:
 		return this->StcMgr->ReadInt32(this->Ptr, this->IValue);
-	} else if (this->Type == kScValDynamicObject) {
+	case kScValDynamicObject:
 		return this->DynMgr->ReadInt32(this->Ptr, this->IValue);
+	default:
+		return *((int32_t *)this->GetPtrWithOffset());
 	}
-	return *((int32_t *)this->GetPtrWithOffset());
 }
 
-bool RuntimeScriptValue::WriteByte(uint8_t val) {
-	if (this->Type == kScValStackPtr || this->Type == kScValGlobalVar) {
+void RuntimeScriptValue::WriteByte(uint8_t val) {
+	switch (this->Type) {
+	case kScValStackPtr:
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			*(uint8_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
 		} else {
 			RValue->SetUInt8(val); // set RValue as int
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+		break;
+	case kScValStaticObject:
+	case kScValStaticArray:
 		this->StcMgr->WriteInt8(this->Ptr, this->IValue, val);
-	} else if (this->Type == kScValDynamicObject) {
+		break;
+	case kScValDynamicObject:
 		this->DynMgr->WriteInt8(this->Ptr, this->IValue, val);
-	} else {
+		break;
+	default:
 		*((uint8_t *)this->GetPtrWithOffset()) = val;
+		break;
 	}
-	return true;
 }
 
-bool RuntimeScriptValue::WriteInt16(int16_t val) {
-	if (this->Type == kScValStackPtr) {
+void RuntimeScriptValue::WriteInt16(int16_t val) {
+	switch (this->Type) {
+	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
 			*(int16_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
 		} else {
 			RValue->SetInt16(val); // set RValue as int
 		}
-	} else if (this->Type == kScValGlobalVar) {
+		break;
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			Memory::WriteInt16LE(RValue->GetPtrWithOffset() + this->IValue, val);
 		} else {
 			RValue->SetInt16(val); // set RValue as int
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+		break;
+	case kScValStaticObject:
+	case kScValStaticArray:
 		this->StcMgr->WriteInt16(this->Ptr, this->IValue, val);
-	} else if (this->Type == kScValDynamicObject) {
+		break;
+	case kScValDynamicObject:
 		this->DynMgr->WriteInt16(this->Ptr, this->IValue, val);
-	} else {
+		break;
+	default:
 		*((int16_t *)this->GetPtrWithOffset()) = val;
+		break;
 	}
-	return true;
 }
 
-bool RuntimeScriptValue::WriteInt32(int32_t val) {
-	if (this->Type == kScValStackPtr) {
+void RuntimeScriptValue::WriteInt32(int32_t val) {
+	switch (this->Type) {
+	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
 			*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
 		} else {
 			RValue->SetInt32(val); // set RValue as int
 		}
-	} else if (this->Type == kScValGlobalVar) {
+		break;
+	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
 			Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, val);
 		} else {
 			RValue->SetInt32(val); // set RValue as int
 		}
-	} else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) {
+		break;
+	case kScValStaticObject:
+	case kScValStaticArray:
 		this->StcMgr->WriteInt32(this->Ptr, this->IValue, val);
-	} else if (this->Type == kScValDynamicObject) {
+		break;
+	case kScValDynamicObject:
 		this->DynMgr->WriteInt32(this->Ptr, this->IValue, val);
-	} else {
+		break;
+	default:
 		*((int32_t *)this->GetPtrWithOffset()) = val;
+		break;
 	}
-	return true;
 }
 
 RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 9bd9e1dce97..160c3514811 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -417,9 +417,9 @@ public:
 	uint8_t     ReadByte() const;
 	int16_t     ReadInt16() const;
 	int32_t     ReadInt32() const;
-	bool        WriteByte(uint8_t val);
-	bool        WriteInt16(int16_t val);
-	bool        WriteInt32(int32_t val);
+	void        WriteByte(uint8_t val);
+	void        WriteInt16(int16_t val);
+	void        WriteInt32(int32_t val);
 
 	// Convert to most simple pointer type by resolving RValue ptrs and applying offsets;
 	// non pointer types are left unmodified


Commit: 8a69992c18442aa6ae57eae7f17ea1235d99499f
    https://github.com/scummvm/scummvm/commit/8a69992c18442aa6ae57eae7f17ea1235d99499f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Support zlib deflate for sprites

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/ac/sprite_file.h
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 086fb2b383b..0c2e0887be8 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -140,7 +140,7 @@ static inline SpriteFormat PaletteFormatForBPP(int bpp) {
 	case 2: return kSprFmt_PaletteRgb565;
 	case 4: return kSprFmt_PaletteArgb8888;
 	default: return kSprFmt_Undefined;
-	}	
+	}
 }
 
 static inline uint8_t GetPaletteBPP(SpriteFormat fmt) {
@@ -149,7 +149,7 @@ static inline uint8_t GetPaletteBPP(SpriteFormat fmt) {
 	case kSprFmt_PaletteArgb8888: return 4;
 	case kSprFmt_PaletteRgb565: return 2;
 	default: return 0; // means no palette
-	}	
+	}
 }
 
 SpriteFile::SpriteFile() {
@@ -410,6 +410,8 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 			break;
 		case kSprCompress_LZW: lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
 			break;
+		case kSprCompress_Deflate: inflate_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
+			break;
 		default: assert(!"Unsupported compression type!"); break;
 		}
 		// TODO: test that not more than data_size was read!
@@ -634,7 +636,9 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	}
 	// (Optional) Compress the image data into the temp buffer
 	SpriteCompression compress = kSprCompress_None;
-	if (_compress != kSprCompress_None) {
+	if (_compress != kSprCompress_Deflate)
+		warning("TODO: Deflate not implemented, writing uncompressed BMP");
+	else if (_compress != kSprCompress_None) {
 		compress = _compress;
 		VectorStream mems(_membuf, kStream_Write);
 		switch (compress) {
@@ -642,6 +646,8 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 			break;
 		case kSprCompress_LZW: lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
 			break;
+		case kSprCompress_Deflate: deflate_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+			break;
 		default: assert(!"Unsupported compression type!"); break;
 		}
 		// mark to write as a plain byte array
diff --git a/engines/ags/shared/ac/sprite_file.h b/engines/ags/shared/ac/sprite_file.h
index 31e1122eada..d1d0bdac6bd 100644
--- a/engines/ags/shared/ac/sprite_file.h
+++ b/engines/ags/shared/ac/sprite_file.h
@@ -84,7 +84,8 @@ enum SpriteFormat {
 enum SpriteCompression {
 	kSprCompress_None = 0,
 	kSprCompress_RLE,
-	kSprCompress_LZW
+	kSprCompress_LZW,
+	kSprCompress_Deflate
 };
 
 typedef int32_t sprkey_t;
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 5013735cec6..87a8719083b 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -31,6 +31,8 @@
 #include "ags/shared/util/bbop.h"
 #endif
 
+#include "common/compression/deflate.h"
+
 namespace AGS3 {
 
 using namespace AGS::Shared;
@@ -429,4 +431,14 @@ Bitmap *load_lzw(Stream *in, int dst_bpp, RGB(*pal)[256]) {
 	return bmm;
 }
 
+void deflate_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *out) {
+	// TODO
+}
+
+void inflate_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *in, size_t in_sz) {
+	std::vector<uint8_t> in_buf(in_sz);
+	in->Read(in_buf.data(), in_sz);
+	Common::inflateZlib(data, (unsigned long)data_sz, in_buf.data(), in_sz);
+}
+
 } // namespace AGS3
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index 8172164390d..e3feee48d5e 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -51,7 +51,11 @@ void lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream
 // Saves bitmap with an optional palette compressed by LZW
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB(*pal)[256] = nullptr);
 // Loads bitmap decompressing
-Shared::Bitmap *load_lzw(Shared::Stream *in, int dst_bpp, RGB(*pal)[256] = nullptr);
+Shared::Bitmap *load_lzw(Shared::Stream *in, int dst_bpp, RGB (*pal)[256] = nullptr);
+
+// Deflate compression
+void deflate_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+void inflate_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
 
 } // namespace AGS3
 


Commit: 13a7a97abddff6be527103610192e8ce29cf8446
    https://github.com/scummvm/scummvm/commit/13a7a97abddff6be527103610192e8ce29cf8446
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: in WFNFont::ReadFromFile replaced C arrays with std::vector

from upstream 56de873ede185a4910f416e9224428bdd681e88e

Changed paths:
    engines/ags/shared/font/wfn_font.cpp


diff --git a/engines/ags/shared/font/wfn_font.cpp b/engines/ags/shared/font/wfn_font.cpp
index 72d5b9b5955..d133034656c 100644
--- a/engines/ags/shared/font/wfn_font.cpp
+++ b/engines/ags/shared/font/wfn_font.cpp
@@ -89,13 +89,16 @@ WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
 	//    referenced by many characters.
 	WFNError err = kWFNErr_NoError;
 
+	if (total_char_data == 0u || char_count == 0u)
+		return kWFNErr_NoError; // no items
+
 	// Read character data array
-	uint8_t *raw_data = new uint8_t[total_char_data];
-	in->Read(raw_data, total_char_data);
+	std::vector<uint8_t> raw_data; raw_data.resize(total_char_data);
+	in->Read(&raw_data.front(), total_char_data);
 
 	// Read offset table
-	uint16_t *offset_table = new uint16_t[char_count];
-	in->ReadArrayOfInt16((int16_t *)offset_table, char_count);
+	std::vector<uint16_t> offset_table;	offset_table.resize(char_count);
+	in->ReadArrayOfInt16(reinterpret_cast<int16_t *>(&offset_table.front()), char_count);
 
 	// Read all referenced offsets in an unsorted vector
 	std::vector<uint16_t> offs;
@@ -124,7 +127,7 @@ WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
 	_items.resize(offs.size());
 	size_t total_pixel_size = 0;
 	for (size_t i = 0; i < _items.size(); ++i) {
-		const uint8_t *p_data = raw_data + offs[i] - raw_data_offset;
+		const uint8_t *p_data = &raw_data[offs[i] - raw_data_offset];
 		init_ch.Width = Memory::ReadInt16LE(p_data);
 		init_ch.Height = Memory::ReadInt16LE(p_data + sizeof(uint16_t));
 		total_pixel_size += init_ch.GetRequiredPixelSize();
@@ -161,7 +164,7 @@ WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
 		_items[i].RestrictToBytes(src_size);
 
 		assert(pixel_it + pixel_data_size <= _pixelData.end()); // should not normally fail
-		Common::copy(raw_data + raw_off, raw_data + raw_off + src_size, pixel_it);
+		Common::copy(raw_data.begin() + raw_off, raw_data.begin() + (raw_off + src_size), pixel_it);
 		_items[i].Data = &(*pixel_it);
 		pixel_it += pixel_data_size;
 	}
@@ -187,8 +190,6 @@ WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
 		}
 	}
 
-	delete[] raw_data;
-	delete[] offset_table;
 	return err;
 }
 


Commit: 233ec786d20e249a311ccf82f9152064a902329a
    https://github.com/scummvm/scummvm/commit/233ec786d20e249a311ccf82f9152064a902329a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: WFNRenderer supports UTF-8

from upstream 49561d40b7335f8b02070dbf1272e063ee2ac099

Changed paths:
    engines/ags/shared/font/wfn_font.cpp
    engines/ags/shared/font/wfn_font.h
    engines/ags/shared/font/wfn_font_renderer.cpp


diff --git a/engines/ags/shared/font/wfn_font.cpp b/engines/ags/shared/font/wfn_font.cpp
index d133034656c..afb164937ca 100644
--- a/engines/ags/shared/font/wfn_font.cpp
+++ b/engines/ags/shared/font/wfn_font.cpp
@@ -45,7 +45,7 @@ void WFNChar::RestrictToBytes(size_t bytes) {
 		Height = static_cast<uint16_t>(bytes / GetRowByteCount());
 }
 
-const WFNChar &WFNFont::GetChar(uint8_t code) const {
+const WFNChar &WFNFont::GetChar(uint16_t code) const {
 	return code < _refs.size() ? *_refs[code] : _G(emptyChar);
 }
 
diff --git a/engines/ags/shared/font/wfn_font.h b/engines/ags/shared/font/wfn_font.h
index b8db5b37d4b..51fd579aec0 100644
--- a/engines/ags/shared/font/wfn_font.h
+++ b/engines/ags/shared/font/wfn_font.h
@@ -90,7 +90,7 @@ public:
 	}
 
 	// Get WFN character for the given code; if the character is missing, returns empty character
-	const WFNChar &GetChar(uint8_t code) const;
+	const WFNChar &GetChar(uint16_t code) const;
 
 	void Clear();
 	// Reads WFNFont object, using data_size bytes from stream; if data_size = 0,
diff --git a/engines/ags/shared/font/wfn_font_renderer.cpp b/engines/ags/shared/font/wfn_font_renderer.cpp
index 9efd3aa54cc..00e94918592 100644
--- a/engines/ags/shared/font/wfn_font_renderer.cpp
+++ b/engines/ags/shared/font/wfn_font_renderer.cpp
@@ -32,22 +32,12 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-static unsigned char GetCharCode(unsigned char wanted_code, const WFNFont *font) {
-	return wanted_code < font->GetCharCount() ? wanted_code : '?';
-}
-
 void WFNFontRenderer::AdjustYCoordinateForFont(int *ycoord, int fontNumber) {
 	// Do nothing
 }
 
 void WFNFontRenderer::EnsureTextValidForFont(char *text, int fontNumber) {
-	const WFNFont *font = _fontData[fontNumber].Font;
-	// replace any extended characters with question marks
-	for (; *text; ++text) {
-		if ((unsigned char)*text >= font->GetCharCount()) {
-			*text = '?';
-		}
-	}
+	// Do nothing
 }
 
 int WFNFontRenderer::GetTextWidth(const char *text, int fontNumber) {
@@ -55,9 +45,8 @@ int WFNFontRenderer::GetTextWidth(const char *text, int fontNumber) {
 	const FontRenderParams &params = _fontData[fontNumber].Params;
 	int text_width = 0;
 
-	for (; *text; ++text) {
-		const WFNChar &wfn_char = font->GetChar(GetCharCode(*text, font));
-		text_width += wfn_char.Width;
+	for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
+		text_width += font->GetChar(code).Width;
 	}
 	return text_width * params.SizeMultiplier;
 }
@@ -67,11 +56,9 @@ int WFNFontRenderer::GetTextHeight(const char *text, int fontNumber) {
 	const FontRenderParams &params = _fontData[fontNumber].Params;
 	int max_height = 0;
 
-	for (; *text; ++text) {
-		const WFNChar &wfn_char = font->GetChar(GetCharCode(*text, font));
-		const uint16_t height = wfn_char.Height;
-		if (height > max_height)
-			max_height = height;
+	for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
+		const uint16_t height = font->GetChar(code).Height;
+		max_height = std::max(max_height, static_cast<int>(height));
 	}
 	return max_height * params.SizeMultiplier;
 }
@@ -90,8 +77,8 @@ void WFNFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *desti
 	// NOTE: allegro's putpixel ignores clipping (optimization),
 	// so we'll have to accommodate for that ourselves
 	Rect clip = ds.GetClip();
-	for (; *text; ++text)
-		x += RenderChar(&ds, x, y, clip, font->GetChar(GetCharCode(*text, font)), params.SizeMultiplier, colour);
+	for (int code = ugetxc(&text); code; code = ugetxc(&text))
+		x += RenderChar(&ds, x, y, clip, font->GetChar(code), params.SizeMultiplier, colour);
 
 	set_our_eip(oldeip);
 }


Commit: 15c6da02caeba13b0d36486c501aee23df6b5594
    https://github.com/scummvm/scummvm/commit/15c6da02caeba13b0d36486c501aee23df6b5594
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: don't do the stupid pointer reassignment when loading a save

>From upstream e1672eb56d6ba03d116e1b62570717884c54a01f

Changed paths:
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h


diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index a8e4e19203a..7f385237430 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -459,15 +459,6 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 	restore_game_play(in, data_ver, r_data);
 	ReadMoveList_Aligned(in);
 
-	// save pointer members before reading
-	char *gswas = _GP(game).globalscript;
-	ccScript *compsc = _GP(game).compiled_script;
-	CharacterInfo *chwas = _GP(game).chars;
-	WordsDictionary *olddict = _GP(game).dict;
-	std::vector<String> mesbk(MAXGLOBALMES);
-	for (size_t i = 0; i < MAXGLOBALMES; ++i)
-		mesbk[i] = _GP(game).messages[i];
-
 	// List of game objects, used to compare with the save contents
 	struct ObjectCounts {
 		int CharacterCount = _GP(game).numcharacters;
@@ -489,7 +480,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 		!AssertGameContent(err, objwas.ViewCount, _GP(game).numviews, "Views"))
 		return err;
 
-	_GP(game).ReadFromSaveGame_v321(in, data_ver, gswas, compsc, chwas, olddict, mesbk);
+	_GP(game).ReadFromSaveGame_v321(in);
 
 	// Modified custom properties are read separately to keep existing save format
 	_GP(play).ReadCustomProperties_v340(in, data_ver);
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 6291d22f7ef..9aef70445b3 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -351,25 +351,17 @@ void GameSetupStruct::ReadAudioClips_Aligned(Shared::Stream *in, size_t count) {
 	}
 }
 
-void GameSetupStruct::ReadFromSaveGame_v321(Stream *in, GameDataVersion data_ver, char *gswas, ccScript *compsc, CharacterInfo *chwas,
-											WordsDictionary *olddict, std::vector<String> &mesbk) {
+void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
 	ReadInvInfo_Aligned(in);
 	ReadMouseCursors_Aligned(in);
 
-	if (data_ver <= kGameVersion_272) {
+	if (_G(loaded_game_file_version) <= kGameVersion_272) {
 		for (int i = 0; i < numinvitems; ++i)
 			intrInv[i]->ReadTimesRunFromSave_v321(in);
 		for (int i = 0; i < numcharacters; ++i)
 			intrChar[i]->ReadTimesRunFromSave_v321(in);
 	}
 
-	// restore pointer members
-	globalscript = gswas;
-	compiled_script = compsc;
-	chars = chwas;
-	dict = olddict;
-	for (size_t i = 0; i < MAXGLOBALMES; ++i)
-		messages[i] = mesbk[i];
 	in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1);
 	options[OPT_LIPSYNCTEXT] = in->ReadByte();
 
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 9d099139fc1..24ab4fde08f 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -159,8 +159,7 @@ struct GameSetupStruct : public GameSetupStructBase {
 	//--------------------------------------------------------------------
 
 	// Functions for reading and writing appropriate data from/to save game
-	void ReadFromSaveGame_v321(Shared::Stream *in, GameDataVersion data_ver, char *gswas, ccScript *compsc, CharacterInfo *chwas,
-							   WordsDictionary *olddict, std::vector<String> &mesbk);
+	void ReadFromSaveGame_v321(Shared::Stream *in);
 
 	void ReadFromSavegame(Shared::Stream *in);
 	void WriteForSavegame(Shared::Stream *out);


Commit: 1ca1fe14c07f368431a11cb26672aa9dbcfc203c
    https://github.com/scummvm/scummvm/commit/1ca1fe14c07f368431a11cb26672aa9dbcfc203c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: store CharacterInfos in std::vector

from upstream f4fe44fe3b51670db2a8a93c450ffa09c8b63e38

Changed paths:
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_setup_struct_base.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 9aef70445b3..620f255c4d7 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -56,7 +56,6 @@ void GameSetupStruct::Free() {
 	intrChar.clear();
 	charScripts.clear();
 	charProps.clear();
-	numcharacters = 0;
 
 	// TODO: find out if it really needs to begin with 1 here?
 	for (size_t i = 1; i < (size_t)MAX_INV; i++) {
@@ -161,6 +160,7 @@ void GameSetupStruct::WriteInvInfo_Aligned(Stream *out) {
 }
 
 HGameFileError GameSetupStruct::read_cursors(Shared::Stream *in) {
+	mcurs.resize(numcursors);
 	ReadMouseCursors_Aligned(in);
 	return HGameFileError::None();
 }
@@ -197,7 +197,6 @@ void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
 }
 
 void GameSetupStruct::ReadMouseCursors_Aligned(Stream *in) {
-	mcurs.resize(numcursors);
 	AlignedStream align_s(in, Shared::kAligned_Read);
 	for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) {
 		mcurs[iteratorCount].ReadFromFile(&align_s);
@@ -217,7 +216,7 @@ void GameSetupStruct::WriteMouseCursors_Aligned(Stream *out) {
 // Reading Part 2
 
 void GameSetupStruct::read_characters(Shared::Stream *in) {
-	chars = new CharacterInfo[numcharacters];
+	chars.resize(numcharacters);
 
 	ReadCharacters_Aligned(in, false);
 }
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index da017dc0d27..624f7e317c1 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -53,7 +53,6 @@ GameSetupStructBase::GameSetupStructBase()
 	, invhotdotsprite(0)
 	, dict(nullptr)
 	, globalscript(nullptr)
-	, chars(nullptr)
 	, compiled_script(nullptr)
 	, load_messages(nullptr)
 	, load_dictionary(false)
@@ -84,8 +83,9 @@ void GameSetupStructBase::Free() {
 	globalscript = nullptr;
 	delete compiled_script;
 	compiled_script = nullptr;
-	delete[] chars;
-	chars = nullptr;
+	chars.clear();
+
+	numcharacters = 0;
 }
 
 void GameSetupStructBase::SetDefaultResolution(GameResolutionType type) {
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 30501e96476..7e467e1aa7f 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -19,10 +19,18 @@
  *
  */
 
+//
+//=============================================================================
+//
+// GameSetupStructBase is a base class for main game data.
+//
+//=============================================================================
+
 #ifndef AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
 #define AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
 
 #include "ags/lib/allegro.h" // RGB
+#include "ags/lib/std/vector.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/ac/game_struct_defines.h"
 #include "ags/shared/util/string.h"
@@ -73,7 +81,7 @@ struct GameSetupStructBase {
 	String			  messages[MAXGLOBALMES];
 	WordsDictionary *dict;
 	char *globalscript;
-	CharacterInfo *chars;
+	std::vector<CharacterInfo> chars;
 	ccScript *compiled_script;
 
 	// TODO: refactor to not have this as struct members
@@ -87,6 +95,7 @@ struct GameSetupStructBase {
 
 	GameSetupStructBase();
 	~GameSetupStructBase();
+
 	void Free();
 	void SetDefaultResolution(GameResolutionType type);
 	void SetDefaultResolution(Size game_res);
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 76caff1d5ff..a1ed728201f 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -566,9 +566,7 @@ void UpgradeAudio(GameSetupStruct &game, LoadedGameEntities &ents, GameDataVersi
 
 // Convert character data to the current version
 void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
-	// TODO: this was done to simplify code transition; ideally we should be
-	// working with GameSetupStruct's getters and setters here
-	CharacterInfo *&chars = _GP(game).chars;
+	auto &chars = _GP(game).chars;
 	const int numcharacters = _GP(game).numcharacters;
 
 	// Fixup charakter script names for 2.x (EGO -> cEgo)


Commit: 101e5ae147d284099bb89fb03605ca5eca0508ea
    https://github.com/scummvm/scummvm/commit/101e5ae147d284099bb89fb03605ca5eca0508ea
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: store WordDictionary in std::unique_ptr

from upstream e084b20cafec94d91b347300dbc281a4a9fb19aa

Changed paths:
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_setup_struct_base.h


diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 620f255c4d7..59179a627c4 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -191,8 +191,8 @@ void GameSetupStruct::read_interaction_scripts(Shared::Stream *in, GameDataVersi
 
 void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
 	if (load_dictionary) {
-		dict = new WordsDictionary();
-		read_dictionary(dict, in);
+		dict.reset(new WordsDictionary());
+		read_dictionary(dict.get(), in);
 	}
 }
 
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 624f7e317c1..ce3c0ca1a51 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -77,8 +77,7 @@ void GameSetupStructBase::Free() {
 	}
 	delete[] load_messages;
 	load_messages = nullptr;
-	delete dict;
-	dict = nullptr;
+	dict.reset();
 	delete globalscript;
 	globalscript = nullptr;
 	delete compiled_script;
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 7e467e1aa7f..fc9999b7cbb 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -30,6 +30,7 @@
 #define AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
 
 #include "ags/lib/allegro.h" // RGB
+#include "ags/lib/std/memory.h"
 #include "ags/lib/std/vector.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/ac/game_struct_defines.h"
@@ -79,7 +80,7 @@ struct GameSetupStructBase {
 	int               invhotdotsprite;
 	int32_t           reserved[NUM_INTS_RESERVED];
 	String			  messages[MAXGLOBALMES];
-	WordsDictionary *dict;
+	std::unique_ptr<WordsDictionary> dict;
 	char *globalscript;
 	std::vector<CharacterInfo> chars;
 	ccScript *compiled_script;


Commit: e7710a907635fb205e41be512a20b6b1a8dc3c3c
    https://github.com/scummvm/scummvm/commit/e7710a907635fb205e41be512a20b6b1a8dc3c3c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Marked a first 3.6.1 build

from upstream 6ece949ee6c4dff3528953e1a195c4f72f0a7a34

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 ae20ef6411e..d53e313fc8b 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.0.58"
+#define ACI_VERSION_STR      "3.6.1.0"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.0.58
+#define ACI_VERSION_MSRC_DEF  3.6.1.0
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 33474b4c4a1d03590891a6ea1cdf0534b7540052
    https://github.com/scummvm/scummvm/commit/33474b4c4a1d03590891a6ea1cdf0534b7540052
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: factored out ManagedObjectPool::Add()

>From upstream b035f4bbd5d403941595be576379cfccf45695ff

Changed paths:
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h


diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index b45f9d5d3ec..93c3fda41e2 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -165,6 +165,18 @@ void ManagedObjectPool::RunGarbageCollection() {
 	ManagedObjectLog("Ran garbage collection");
 }
 
+int ManagedObjectPool::Add(int handle, const char *address, ICCDynamicObject *callback, ScriptValueType obj_type)
+{
+    auto &o = objects[handle];
+    assert(!o.isUsed());
+
+    o = ManagedObject(obj_type, handle, address, callback);
+
+    handleByAddress.insert({address, handle});
+    ManagedObjectLog("Allocated managed object type=%s, handle=%d, addr=%08X", callback->GetType(), handle, address);
+    return handle;
+}
+
 int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type) {
 	int32_t handle;
 
@@ -178,18 +190,10 @@ int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback
 		}
 	}
 
-	auto &o = objects[handle];
-	assert(!o.isUsed());
-
-	o = ManagedObject(obj_type, handle, address, callback);
-
-	handleByAddress.insert({ address, o.handle });
 	objectCreationCounter++;
-	ManagedObjectLog("Allocated managed object handle=%d, type=%s", handle, callback->GetType());
-	return o.handle;
+	return Add(handle, address, callback, obj_type);
 }
 
-
 int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle) {
 	if (handle < 0) {
 		cc_error("Attempt to assign invalid handle: %d", handle);
@@ -199,14 +203,7 @@ int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObje
 		objects.resize(handle + 1024, ManagedObject());
 	}
 
-	auto &o = objects[handle];
-	assert(!o.isUsed());
-
-	o = ManagedObject(obj_type, handle, address, callback);
-
-	handleByAddress.insert({ address, o.handle });
-	ManagedObjectLog("Allocated unserialized managed object handle=%d, type=%s", o.handle, callback->GetType());
-	return o.handle;
+	return Add(handle, address, callback, obj_type);
 }
 
 void ManagedObjectPool::WriteToDisk(Stream *out) {
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index 1b153a6c709..23e5a0e8661 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -80,6 +80,7 @@ private:
 	std::vector<ManagedObject> objects;
 	std::unordered_map<const char *, int32_t, Pointer_Hash> handleByAddress;
 
+	int Add(int handle, const char *address, ICCDynamicObject *callback, ScriptValueType obj_type);
 	int Remove(ManagedObject &o, bool force = false);
 	void RunGarbageCollection();
 


Commit: 393aed7dd2e0dcc20779f9212c333a6d2d6dfb37
    https://github.com/scummvm/scummvm/commit/393aed7dd2e0dcc20779f9212c333a6d2d6dfb37
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: removed AGS_INLINE macro, as it's useless

from upstream e1919aa3b9e719f437e966780a41dd297eb82940

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/room.h
    engines/ags/shared/ac/common_defines.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 4f72e699f1d..12bf8aade55 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -249,42 +249,42 @@ Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res) {
 
 // Multiplies up the number of pixels depending on the current
 // resolution, to give a relatively fixed size at any game res
-AGS_INLINE int get_fixed_pixel_size(int pixels) {
+int get_fixed_pixel_size(int pixels) {
 	return pixels * _GP(game).GetRelativeUIMult();
 }
 
-AGS_INLINE int data_to_game_coord(int coord) {
+int data_to_game_coord(int coord) {
 	return coord * _GP(game).GetDataUpscaleMult();
 }
 
-AGS_INLINE void data_to_game_coords(int *x, int *y) {
+void data_to_game_coords(int *x, int *y) {
 	const int mul = _GP(game).GetDataUpscaleMult();
 	x[0] *= mul;
 	y[0] *= mul;
 }
 
-AGS_INLINE void data_to_game_round_up(int *x, int *y) {
+void data_to_game_round_up(int *x, int *y) {
 	const int mul = _GP(game).GetDataUpscaleMult();
 	x[0] = x[0] * mul + (mul - 1);
 	y[0] = y[0] * mul + (mul - 1);
 }
 
-AGS_INLINE int game_to_data_coord(int coord) {
+int game_to_data_coord(int coord) {
 	return coord / _GP(game).GetDataUpscaleMult();
 }
 
-AGS_INLINE void game_to_data_coords(int &x, int &y) {
+void game_to_data_coords(int &x, int &y) {
 	const int mul = _GP(game).GetDataUpscaleMult();
 	x /= mul;
 	y /= mul;
 }
 
-AGS_INLINE int game_to_data_round_up(int coord) {
+int game_to_data_round_up(int coord) {
 	const int mul = _GP(game).GetDataUpscaleMult();
 	return (coord / mul) + (mul - 1);
 }
 
-AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) {
+void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) {
 	if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
 		x /= HIRES_COORD_MULTIPLIER;
 		y /= HIRES_COORD_MULTIPLIER;
@@ -294,7 +294,7 @@ AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) {
 	}
 }
 
-AGS_INLINE void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) {
+void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) {
 	if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
 		w = MAX(1, (w / HIRES_COORD_MULTIPLIER));
 		h = MAX(1, (h / HIRES_COORD_MULTIPLIER));
@@ -304,7 +304,7 @@ AGS_INLINE void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) {
 	}
 }
 
-AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx) {
+int ctx_data_to_game_size(int size, bool hires_ctx) {
 	if (hires_ctx && !_GP(game).IsLegacyHiRes())
 		return MAX(1, (size / HIRES_COORD_MULTIPLIER));
 	if (!hires_ctx && _GP(game).IsLegacyHiRes())
@@ -312,7 +312,7 @@ AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx) {
 	return size;
 }
 
-AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx) {
+int game_to_ctx_data_size(int size, bool hires_ctx) {
 	if (hires_ctx && !_GP(game).IsLegacyHiRes())
 		return size * HIRES_COORD_MULTIPLIER;
 	else if (!hires_ctx && _GP(game).IsLegacyHiRes())
@@ -320,7 +320,7 @@ AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx) {
 	return size;
 }
 
-AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y) {
+void defgame_to_finalgame_coords(int &x, int &y) {
 	// Note we support only upscale now
 	x *= _GP(game).GetScreenUpscaleMult();
 	y *= _GP(game).GetScreenUpscaleMult();
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 07f80e4f71a..049fcdd39cd 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -216,22 +216,22 @@ void setpal();
 // This conversion is done before anything else (like moving from room to
 // viewport on screen, or scaling game further in the window by the graphic
 // renderer).
-AGS_INLINE int get_fixed_pixel_size(int pixels);
+int get_fixed_pixel_size(int pixels);
 // coordinate conversion data,script ---> final game resolution
-extern AGS_INLINE int data_to_game_coord(int coord);
-extern AGS_INLINE void data_to_game_coords(int *x, int *y);
-extern AGS_INLINE void data_to_game_round_up(int *x, int *y);
+extern int data_to_game_coord(int coord);
+extern void data_to_game_coords(int *x, int *y);
+extern void data_to_game_round_up(int *x, int *y);
 // coordinate conversion final game resolution ---> data,script
-extern AGS_INLINE int game_to_data_coord(int coord);
-extern AGS_INLINE void game_to_data_coords(int &x, int &y);
-extern AGS_INLINE int game_to_data_round_up(int coord);
+extern int game_to_data_coord(int coord);
+extern void game_to_data_coords(int &x, int &y);
+extern int game_to_data_round_up(int coord);
 // convert contextual data coordinates to final game resolution
-extern AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx);
-extern AGS_INLINE void ctx_data_to_game_size(int &x, int &y, bool hires_ctx);
-extern AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx);
-extern AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx);
+extern void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx);
+extern void ctx_data_to_game_size(int &x, int &y, bool hires_ctx);
+extern int ctx_data_to_game_size(int size, bool hires_ctx);
+extern int game_to_ctx_data_size(int size, bool hires_ctx);
 // This function converts game coordinates coming from script to the actual game resolution.
-extern AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y);
+extern void defgame_to_finalgame_coords(int &x, int &y);
 
 // Creates bitmap of a format compatible with the gfxdriver;
 // if col_depth is 0, uses game's native color depth.
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 1267ba0aebc..2a34f9d1fed 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -56,7 +56,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-AGS_INLINE bool is_valid_object(int obj_id) {
+bool is_valid_object(int obj_id) {
 	return (obj_id >= 0) && (static_cast<uint32_t>(obj_id) < _G(croom)->numobj);
 }
 
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index bdbf906f4d7..ecf551ce981 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -43,7 +43,7 @@ class Bitmap;
 
 using namespace AGS; // FIXME later
 
-extern AGS_INLINE bool is_valid_object(int obj_id);
+extern bool is_valid_object(int obj_id);
 // Asserts the object ID is valid in the current room,
 // if not then prints a warning to the log; returns assertion result
 bool    AssertObject(const char *apiname, int obj_id);
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 4506a1c5daf..7193761c356 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1015,11 +1015,11 @@ void croom_ptr_clear() {
 }
 
 
-AGS_INLINE int room_to_mask_coord(int coord) {
+int room_to_mask_coord(int coord) {
 	return coord * _GP(game).GetDataUpscaleMult() / _GP(thisroom).MaskResolution;
 }
 
-AGS_INLINE int mask_to_room_coord(int coord) {
+int mask_to_room_coord(int coord) {
 	return coord * _GP(thisroom).MaskResolution / _GP(game).GetDataUpscaleMult();
 }
 
diff --git a/engines/ags/engine/ac/room.h b/engines/ags/engine/ac/room.h
index a7c43a29613..1820845b7f2 100644
--- a/engines/ags/engine/ac/room.h
+++ b/engines/ags/engine/ac/room.h
@@ -75,9 +75,9 @@ void  croom_ptr_clear();
 // between data and room coordinates.
 //
 // coordinate conversion data ---> room ---> mask
-extern AGS_INLINE int room_to_mask_coord(int coord);
+extern int room_to_mask_coord(int coord);
 // coordinate conversion mask ---> room ---> data
-extern AGS_INLINE int mask_to_room_coord(int coord);
+extern int mask_to_room_coord(int coord);
 
 struct MoveList;
 // Convert move path from room's mask resolution to room resolution
diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h
index 986e02d6cb9..ddb5ea40a03 100644
--- a/engines/ags/shared/ac/common_defines.h
+++ b/engines/ags/shared/ac/common_defines.h
@@ -102,13 +102,6 @@ namespace AGS3 {
 #define LEGACY_MAX_SPRITES_V25  6000
 #define LEGACY_MAX_SPRITES      30000
 
-#if AGS_PLATFORM_OS_WINDOWS
-#define AGS_INLINE inline
-#else
-// the linux compiler won't allow extern inline
-#define AGS_INLINE
-#endif
-
 // The game to screen coordinate conversion multiplier in hi-res type games
 #define HIRES_COORD_MULTIPLIER 2
 


Commit: 1eb11a29bcc30e4bbac659b83073508005585119
    https://github.com/scummvm/scummvm/commit/1eb11a29bcc30e4bbac659b83073508005585119
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in construct_*_gfx use direct obj/char reference

>From upstream 094e5e912fad794c1af00f31a49209cea4b54b79

Changed paths:
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_object.h
    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 76f56e85f8a..75e1a787019 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -43,20 +43,23 @@ using namespace AGS::Shared;
 
 #define Random __Rand
 
-int CharacterInfo::get_effective_y() {
+int CharacterInfo::get_effective_y() const {
 	return y - z;
 }
-int CharacterInfo::get_baseline() {
+
+int CharacterInfo::get_baseline() const {
 	if (baseline < 1)
 		return y;
 	return baseline;
 }
-int CharacterInfo::get_blocking_top() {
+
+int CharacterInfo::get_blocking_top() const {
 	if (blocking_height > 0)
 		return y - blocking_height / 2;
 	return y - 2;
 }
-int CharacterInfo::get_blocking_bottom() {
+
+int CharacterInfo::get_blocking_bottom() const {
 	// the blocking_bottom should be 1 less than the top + height
 	// since the code does <= checks on it rather than < checks
 	if (blocking_height > 0)
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 12bf8aade55..cb65b567cb9 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1170,15 +1170,17 @@ static bool scale_and_flip_sprite(int useindx, int sppic, int newwidth, int newh
 // require altering the raw bitmap itself.
 // Except if alwaysUseSoftware is set, in which case even HW renderers
 // construct the image in software mode as well.
-bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
+bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
 	bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
 
-	if (_GP(spriteset)[_G(objs)[aa].num] == nullptr)
-		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", aa, _G(objs)[aa].num);
+	/*const*/ RoomObject &obj = _G(objs)[objid];
 
-	int coldept = _GP(spriteset)[_G(objs)[aa].num]->GetColorDepth();
-	const int src_sprwidth = _GP(game).SpriteInfos[_G(objs)[aa].num].Width;
-	const int src_sprheight = _GP(game).SpriteInfos[_G(objs)[aa].num].Height;
+	if (_GP(spriteset)[obj.num] == nullptr)
+		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
+
+	int coldept = _GP(spriteset)[obj.num]->GetColorDepth();
+	const int src_sprwidth = _GP(game).SpriteInfos[obj.num].Width;
+	const int src_sprheight = _GP(game).SpriteInfos[obj.num].Height;
 	int sprwidth = src_sprwidth;
 	int sprheight = src_sprheight;
 
@@ -1187,22 +1189,22 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 	int zoom_level = 100;
 
 	// calculate the zoom level
-	if ((_G(objs)[aa].flags & OBJF_USEROOMSCALING) == 0) {
-		zoom_level = _G(objs)[aa].zoom;
+	if ((obj.flags & OBJF_USEROOMSCALING) == 0) {
+		zoom_level = obj.zoom;
 	} else {
-		int onarea = get_walkable_area_at_location(_G(objs)[aa].x, _G(objs)[aa].y);
+		int onarea = get_walkable_area_at_location(obj.x, obj.y);
 
 		if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
 			// just off the edge of an area -- use the scaling we had
 			// while on the area
-			zoom_level = _G(objs)[aa].zoom;
+			zoom_level = obj.zoom;
 		} else
-			zoom_level = get_area_scaling(onarea, _G(objs)[aa].x, _G(objs)[aa].y);
+			zoom_level = get_area_scaling(onarea, obj.x, obj.y);
 	}
 
 	if (zoom_level != 100)
-		scale_sprite_size(_G(objs)[aa].num, zoom_level, &sprwidth, &sprheight);
-	_G(objs)[aa].zoom = zoom_level;
+		scale_sprite_size(obj.num, zoom_level, &sprwidth, &sprheight);
+	obj.zoom = zoom_level;
 
 	// save width/height into parameters if requested
 	if (drawnWidth)
@@ -1210,93 +1212,96 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 	if (drawnHeight)
 		*drawnHeight = sprheight;
 
-	_G(objs)[aa].last_width = sprwidth;
-	_G(objs)[aa].last_height = sprheight;
+	obj.last_width = sprwidth;
+	obj.last_height = sprheight;
 
 	tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
 
-	if (_G(objs)[aa].flags & OBJF_HASTINT) {
+	if (obj.flags & OBJF_HASTINT) {
 		// object specific tint, use it
-		tint_red = _G(objs)[aa].tint_r;
-		tint_green = _G(objs)[aa].tint_g;
-		tint_blue = _G(objs)[aa].tint_b;
-		tint_level = _G(objs)[aa].tint_level;
-		tint_light = _G(objs)[aa].tint_light;
+		tint_red = obj.tint_r;
+		tint_green = obj.tint_g;
+		tint_blue = obj.tint_b;
+		tint_level = obj.tint_level;
+		tint_light = obj.tint_light;
 		light_level = 0;
-	} else if (_G(objs)[aa].flags & OBJF_HASLIGHT) {
-		light_level = _G(objs)[aa].tint_light;
+	} else if (obj.flags & OBJF_HASLIGHT) {
+		light_level = obj.tint_light;
 	} else {
 		// get the ambient or region tint
 		int ignoreRegionTints = 1;
-		if (_G(objs)[aa].flags & OBJF_USEREGIONTINTS)
+		if (obj.flags & OBJF_USEREGIONTINTS)
 			ignoreRegionTints = 0;
 
-		get_local_tint(_G(objs)[aa].x, _G(objs)[aa].y, ignoreRegionTints,
+		get_local_tint(obj.x, obj.y, ignoreRegionTints,
 		               &tint_level, &tint_red, &tint_green, &tint_blue,
 		               &tint_light, &light_level);
 	}
 
 	// check whether the image should be flipped
 	bool isMirrored = false;
-	if ((_G(objs)[aa].view != (uint16_t)-1) &&
-		(_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].pic == _G(objs)[aa].num) &&
-		((_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) {
+	if ((obj.view != (uint16_t)-1) &&
+		(_GP(views)[obj.view].loops[obj.loop].frames[obj.frame].pic == obj.num) &&
+		((_GP(views)[obj.view].loops[obj.loop].frames[obj.frame].flags & VFLG_FLIPSPRITE) != 0)) {
 		isMirrored = true;
 	}
 
-	const int useindx = aa; // actsps array index
+	const int useindx = objid; // actsps array index
 	auto &actsp = _GP(actsps)[useindx];
-	actsp.SpriteID = _G(objs)[aa].num; // for texture sharing
+	actsp.SpriteID = obj.num; // for texture sharing
+
 	// NOTE: we need cached bitmap if:
 	// * it's a software renderer, otherwise
 	// * the walk-behind method is DrawOverCharSprite
 	if ((hardwareAccelerated) && (_G(walkBehindMethod) != DrawOverCharSprite)) {
 		// HW acceleration
-		bool is_texture_intact = _G(objcache)[aa].sppic != _G(objs)[aa].num;
-		_G(objcache)[aa].sppic = _G(objs)[aa].num;
-		_G(objcache)[aa].tintamnt = tint_level;
-		_G(objcache)[aa].tintr = tint_red;
-		_G(objcache)[aa].tintg = tint_green;
-		_G(objcache)[aa].tintb = tint_blue;
-		_G(objcache)[aa].tintlight = tint_light;
-		_G(objcache)[aa].lightlev = light_level;
-		_G(objcache)[aa].zoom = zoom_level;
-		_G(objcache)[aa].mirrored = isMirrored;
+		ObjectCache &objsav = _G(objcache)[objid];
+		bool is_texture_intact = objsav.sppic == obj.num;
+		objsav.sppic = obj.num;
+		objsav.tintamnt = tint_level;
+		objsav.tintr = tint_red;
+		objsav.tintg = tint_green;
+		objsav.tintb = tint_blue;
+		objsav.tintlight = tint_light;
+		objsav.lightlev = light_level;
+		objsav.zoom = zoom_level;
+		objsav.mirrored = isMirrored;
 		return is_texture_intact;
 	}
 
 	//
 	// Software mode below
 	//
+	ObjectCache &objsav = _G(objcache)[objid];
 	if ((!hardwareAccelerated) && (_G(gfxDriver)->HasAcceleratedTransform())) {
 		// They want to draw it in software mode with the D3D driver, so force a redraw
-		_G(objcache)[aa].sppic = -389538;
+		objsav.sppic = -389538;
 	}
 
 	// If we have the image cached, use it
-	if ((_G(objcache)[aa].image != nullptr) &&
-	        (_G(objcache)[aa].sppic == _G(objs)[aa].num) &&
-			(_G(objcache)[aa].tintamnt == tint_level) &&
-			(_G(objcache)[aa].tintlight == tint_light) &&
-			(_G(objcache)[aa].tintr == tint_red) &&
-			(_G(objcache)[aa].tintg == tint_green) &&
-			(_G(objcache)[aa].tintb == tint_blue) &&
-			(_G(objcache)[aa].lightlev == light_level) &&
-			(_G(objcache)[aa].zoom == zoom_level) &&
-			(_G(objcache)[aa].mirrored == isMirrored)) {
+	if ((objsav.image != nullptr) &&
+	        (objsav.sppic == obj.num) &&
+			(objsav.tintamnt == tint_level) &&
+			(objsav.tintlight == tint_light) &&
+			(objsav.tintr == tint_red) &&
+			(objsav.tintg == tint_green) &&
+			(objsav.tintb == tint_blue) &&
+			(objsav.lightlev == light_level) &&
+			(objsav.zoom == zoom_level) &&
+			(objsav.mirrored == isMirrored)) {
 		// the image is the same, we can use it cached!
 		if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
 			(actsp.Bmp != nullptr))
 			return true;
 		// Check if the X & Y co-ords are the same, too -- if so, there
 		// is scope for further optimisations
-		if ((_G(objcache)[aa].x == _G(objs)[aa].x) &&
-				(_G(objcache)[aa].y == _G(objs)[aa].y) &&
+		if ((objsav.x == obj.x) &&
+				(objsav.y == obj.y) &&
 				(actsp.Bmp != nullptr) &&
 				(_G(walk_behind_baselines_changed) == 0))
 			return true;
 		recycle_bitmap(actsp.Bmp, coldept, sprwidth, sprheight);
-		actsp.Bmp->Blit(_G(objcache)[aa].image.get(), 0, 0, 0, 0, _G(objcache)[aa].image->GetWidth(), _G(objcache)[aa].image->GetHeight());
+		actsp.Bmp->Blit(objsav.image.get(), 0, 0, 0, 0, objsav.image->GetWidth(), objsav.image->GetHeight());
 		return false; // image was modified
 	}
 
@@ -1305,7 +1310,7 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 	bool actspsUsed = false;
 	if (!hardwareAccelerated) {
 		// draw the base sprite, scaled and flipped as appropriate
-		actspsUsed = scale_and_flip_sprite(useindx, _G(objs)[aa].num, sprwidth, sprheight, isMirrored);
+		actspsUsed = scale_and_flip_sprite(useindx, obj.num, sprwidth, sprheight, isMirrored);
 	}
 	if (!actspsUsed) {
 		recycle_bitmap(actsp.Bmp, coldept, src_sprwidth, src_sprheight);
@@ -1314,7 +1319,7 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 	// direct read from source bitmap, where possible
 	Bitmap *comeFrom = nullptr;
 	if (!actspsUsed)
-		comeFrom = _GP(spriteset)[_G(objs)[aa].num];
+		comeFrom = _GP(spriteset)[obj.num];
 
 	// apply tints or lightenings where appropriate, else just copy
 	// the source bitmap
@@ -1323,22 +1328,22 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 			tint_green, tint_blue, tint_light, coldept,
 			comeFrom);
 	} else if (!actspsUsed) {
-		actsp.Bmp->Blit(_GP(spriteset)[_G(objs)[aa].num], 0, 0);
+		actsp.Bmp->Blit(_GP(spriteset)[obj.num], 0, 0);
 	}
 
 	// Re-use the bitmap if it's the same size
-	recycle_bitmap(_G(objcache)[aa].image, coldept, sprwidth, sprheight);
+	recycle_bitmap(objsav.image, coldept, sprwidth, sprheight);
 	// Create the cached image and store it
-	_G(objcache)[aa].image->Blit(actsp.Bmp.get(), 0, 0);
-	_G(objcache)[aa].sppic = _G(objs)[aa].num;
-	_G(objcache)[aa].tintamnt = tint_level;
-	_G(objcache)[aa].tintr = tint_red;
-	_G(objcache)[aa].tintg = tint_green;
-	_G(objcache)[aa].tintb = tint_blue;
-	_G(objcache)[aa].tintlight = tint_light;
-	_G(objcache)[aa].lightlev = light_level;
-	_G(objcache)[aa].zoom = zoom_level;
-	_G(objcache)[aa].mirrored = isMirrored;
+	objsav.image->Blit(actsp.Bmp.get(), 0, 0);
+	objsav.sppic = obj.num;
+	objsav.tintamnt = tint_level;
+	objsav.tintr = tint_red;
+	objsav.tintg = tint_green;
+	objsav.tintb = tint_blue;
+	objsav.tintlight = tint_light;
+	objsav.lightlev = light_level;
+	objsav.zoom = zoom_level;
+	objsav.mirrored = isMirrored;
 	return false; // image was modified
 }
 
@@ -1347,27 +1352,29 @@ bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool always
 void prepare_objects_for_drawing() {
 	_G(our_eip) = 32;
 
-	for (uint32_t aa = 0; aa < _G(croom)->numobj; aa++) {
-		if (_G(objs)[aa].on != 1) continue;
+	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
+		const RoomObject &obj = _G(objs)[objid];
+		if (obj.on != 1) continue;
 		// offscreen, don't draw
-		if ((_G(objs)[aa].x >= _GP(thisroom).Width) || (_G(objs)[aa].y < 1))
+		if ((obj.x >= _GP(thisroom).Width) || (obj.y < 1))
 			continue;
 
 		int tehHeight;
-		bool actspsIntact = construct_object_gfx(aa, nullptr, &tehHeight, false);
+		bool actspsIntact = construct_object_gfx(objid, nullptr, &tehHeight, false);
 
-		const int useindx = aa; // actsps array index
+		const int useindx = objid; // actsps array index
 		auto &actsp = _GP(actsps)[useindx];
 
 		// update the cache for next time
-		_G(objcache)[aa].x = _G(objs)[aa].x;
-		_G(objcache)[aa].y = _G(objs)[aa].y;
-		int atxp = data_to_game_coord(_G(objs)[aa].x);
-		int atyp = data_to_game_coord(_G(objs)[aa].y) - tehHeight;
+		ObjectCache &objsav = _G(objcache)[objid];
+		objsav.x = obj.x;
+		objsav.y = obj.y;
+		int atxp = data_to_game_coord(obj.x);
+		int atyp = data_to_game_coord(obj.y) - tehHeight;
 
-		int usebasel = _G(objs)[aa].get_baseline();
+		int usebasel = obj.get_baseline();
 
-		if (_G(objs)[aa].flags & OBJF_NOWALKBEHINDS) {
+		if (obj.flags & OBJF_NOWALKBEHINDS) {
 			// ignore walk-behinds, do nothing
 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
 				usebasel += _GP(thisroom).Height;
@@ -1377,28 +1384,28 @@ void prepare_objects_for_drawing() {
 		}
 
 		if ((!actspsIntact) || (actsp.Ddb == nullptr)) {
-			sync_object_texture(actsp, (_GP(game).SpriteInfos[_G(objs)[aa].num].Flags & SPF_ALPHACHANNEL) != 0);
+			sync_object_texture(actsp, (_GP(game).SpriteInfos[obj.num].Flags & SPF_ALPHACHANNEL) != 0);
 		}
 
 		if (_G(gfxDriver)->HasAcceleratedTransform()) {
-			actsp.Ddb->SetFlippedLeftRight(_G(objcache)[aa].mirrored);
-			actsp.Ddb->SetStretch(_G(objs)[aa].last_width, _G(objs)[aa].last_height);
-			actsp.Ddb->SetTint(_G(objcache)[aa].tintr, _G(objcache)[aa].tintg, _G(objcache)[aa].tintb, (_G(objcache)[aa].tintamnt * 256) / 100);
+			actsp.Ddb->SetFlippedLeftRight(objsav.mirrored);
+			actsp.Ddb->SetStretch(obj.last_width, obj.last_height);
+			actsp.Ddb->SetTint(objsav.tintr, objsav.tintg, objsav.tintb, (objsav.tintamnt * 256) / 100);
 
-			if (_G(objcache)[aa].tintamnt > 0) {
-				if (_G(objcache)[aa].tintlight == 0)  // luminance of 0 -- pass 1 to enable
+			if (objsav.tintamnt > 0) {
+				if (objsav.tintlight == 0)  // luminance of 0 -- pass 1 to enable
 					actsp.Ddb->SetLightLevel(1);
-				else if (_G(objcache)[aa].tintlight < 250)
-					actsp.Ddb->SetLightLevel(_G(objcache)[aa].tintlight);
+				else if (objsav.tintlight < 250)
+					actsp.Ddb->SetLightLevel(objsav.tintlight);
 				else
 					actsp.Ddb->SetLightLevel(0);
-			} else if (_G(objcache)[aa].lightlev != 0)
-				actsp.Ddb->SetLightLevel((_G(objcache)[aa].lightlev * 25) / 10 + 256);
+			} else if (objsav.lightlev != 0)
+				actsp.Ddb->SetLightLevel((objsav.lightlev * 25) / 10 + 256);
 			else
 				actsp.Ddb->SetLightLevel(0);
 		}
 
-		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_G(objs)[aa].transparent));
+		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(obj.transparent));
 		add_to_sprite_list(actsp.Ddb, atxp, atyp, usebasel, false);
 	}
 }
@@ -1464,12 +1471,16 @@ void prepare_characters_for_drawing() {
 	_G(our_eip) = 33;
 
 	// draw characters
-	for (int aa = 0; aa < _GP(game).numcharacters; aa++) {
-		if (_GP(game).chars[aa].on == 0) continue;
-		if (_GP(game).chars[aa].room != _G(displayed_room)) continue;
-		_G(eip_guinum) = aa;
+	for (int charid = 0; charid < _GP(game).numcharacters; ++charid) {
+		/*const*/ CharacterInfo *chin = &_GP(game).chars[charid];
+		if (chin->on == 0)
+			continue;
+		if (chin->room != _G(displayed_room))
+			continue;
+		_G(eip_guinum) = charid;
+
+		/*const*/ CharacterExtras &chex = _GP(charextra)[charid];
 
-		CharacterInfo *chin = &_GP(game).chars[aa];
 		_G(our_eip) = 330;
 		// Test for valid view and loop
 		if (chin->view < 0) {
@@ -1491,34 +1502,34 @@ void prepare_characters_for_drawing() {
 			sppic = 0;  // in case it's screwed up somehow
 		_G(our_eip) = 331;
 		// sort out the stretching if required
-		onarea = get_walkable_area_at_character(aa);
+		onarea = get_walkable_area_at_character(charid);
 		_G(our_eip) = 332;
 
 		// calculates the zoom level
 		if (chin->flags & CHF_MANUALSCALING)  // character ignores scaling
-			zoom_level = _GP(charextra)[aa].zoom;
+			zoom_level = chex.zoom;
 		else if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
-			zoom_level = _GP(charextra)[aa].zoom;
+			zoom_level = chex.zoom;
 			// NOTE: room objects don't have this fix
 			if (zoom_level == 0)
 				zoom_level = 100;
 		} else
 			zoom_level = get_area_scaling(onarea, chin->x, chin->y);
 
-		_GP(charextra)[aa].zoom = zoom_level;
+		chex.zoom = zoom_level;
 
 		tint_red = tint_green = tint_blue = tint_amount = tint_light = light_level = 0;
 
 		if (chin->flags & CHF_HASTINT) {
 			// object specific tint, use it
-			tint_red = _GP(charextra)[aa].tint_r;
-			tint_green = _GP(charextra)[aa].tint_g;
-			tint_blue = _GP(charextra)[aa].tint_b;
-			tint_amount = _GP(charextra)[aa].tint_level;
-			tint_light = _GP(charextra)[aa].tint_light;
+			tint_red = chex.tint_r;
+			tint_green = chex.tint_g;
+			tint_blue = chex.tint_b;
+			tint_amount = chex.tint_level;
+			tint_light = chex.tint_light;
 			light_level = 0;
 		} else if (chin->flags & CHF_HASLIGHT) {
-			light_level = _GP(charextra)[aa].tint_light;
+			light_level = chex.tint_light;
 		} else {
 			get_local_tint(chin->x, chin->y, chin->flags & CHF_NOLIGHTING,
 			               &tint_amount, &tint_red, &tint_green, &tint_blue,
@@ -1541,33 +1552,34 @@ void prepare_characters_for_drawing() {
 
 		_G(our_eip) = 3331;
 
-		const int useindx = aa + ACTSP_OBJSOFF; // actsps array index
+		const int useindx = charid + ACTSP_OBJSOFF; // actsps array index
 		auto &actsp = _GP(actsps)[useindx];
 		actsp.SpriteID = sppic; // for texture sharing
 
+		ObjectCache &chsav = _GP(charcache)[charid];
 		// if the character was the same sprite and scaling last time,
 		// just use the cached image
-		if ((_GP(charcache)[aa].in_use) &&
-			(_GP(charcache)[aa].sppic == specialpic) &&
-			(_GP(charcache)[aa].zoom == zoom_level) &&
-			(_GP(charcache)[aa].tintr == tint_red) &&
-			(_GP(charcache)[aa].tintg == tint_green) &&
-			(_GP(charcache)[aa].tintb == tint_blue) &&
-			(_GP(charcache)[aa].tintamnt == tint_amount) &&
-			(_GP(charcache)[aa].tintlight == tint_light) &&
-			(_GP(charcache)[aa].lightlev == light_level)) {
+		if ((chsav.in_use) &&
+			(chsav.sppic == specialpic) &&
+			(chsav.zoom == zoom_level) &&
+			(chsav.tintr == tint_red) &&
+			(chsav.tintg == tint_green) &&
+			(chsav.tintb == tint_blue) &&
+			(chsav.tintamnt == tint_amount) &&
+			(chsav.tintlight == tint_light) &&
+			(chsav.lightlev == light_level)) {
 			if (_G(walkBehindMethod) == DrawOverCharSprite) {
-				recycle_bitmap(actsp.Bmp, _GP(charcache)[aa].image->GetColorDepth(), _GP(charcache)[aa].image->GetWidth(), _GP(charcache)[aa].image->GetHeight());
-				actsp.Bmp->Blit(_GP(charcache)[aa].image.get(), 0, 0);
+				recycle_bitmap(actsp.Bmp, chsav.image->GetColorDepth(), chsav.image->GetWidth(), chsav.image->GetHeight());
+				actsp.Bmp->Blit(chsav.image.get(), 0, 0);
 			} else {
 				usingCachedImage = true;
 			}
-		} else if ((_GP(charcache)[aa].in_use) &&
-			(_GP(charcache)[aa].sppic == specialpic) &&
+		} else if ((chsav.in_use) &&
+			(chsav.sppic == specialpic) &&
 			(_G(gfxDriver)->HasAcceleratedTransform())) {
 			usingCachedImage = true;
-		} else if (_GP(charcache)[aa].in_use) {
-			_GP(charcache)[aa].in_use = false;
+		} else if (chsav.in_use) {
+			chsav.in_use = false;
 		}
 
 		_G(our_eip) = 3332;
@@ -1579,13 +1591,13 @@ void prepare_characters_for_drawing() {
 			// it needs to be stretched, so calculate the new dimensions
 
 			scale_sprite_size(sppic, zoom_level, &newwidth, &newheight);
-			_GP(charextra)[aa].width = newwidth;
-			_GP(charextra)[aa].height = newheight;
+			chex.width = newwidth;
+			chex.height = newheight;
 		} else {
 			// draw at original size, so just use the sprite width and height
-			// TODO: store width and height always, that's much simpler to use for reference!
-			_GP(charextra)[aa].width = 0;
-			_GP(charextra)[aa].height = 0;
+			// TODO: store width and height always, that's much simplier to use for reference!
+			chex.width = 0;
+			chex.height = 0;
 			newwidth = src_sprwidth;
 			newheight = src_sprheight;
 		}
@@ -1598,20 +1610,20 @@ void prepare_characters_for_drawing() {
 		                 // adjust the Y positioning for the character's Z co-ord
 		                 - data_to_game_coord(chin->z);
 
-		_GP(charcache)[aa].zoom = zoom_level;
-		_GP(charcache)[aa].sppic = specialpic;
-		_GP(charcache)[aa].tintr = tint_red;
-		_GP(charcache)[aa].tintg = tint_green;
-		_GP(charcache)[aa].tintb = tint_blue;
-		_GP(charcache)[aa].tintamnt = tint_amount;
-		_GP(charcache)[aa].tintlight = tint_light;
-		_GP(charcache)[aa].lightlev = light_level;
+		chsav.zoom = zoom_level;
+		chsav.sppic = specialpic;
+		chsav.tintr = tint_red;
+		chsav.tintg = tint_green;
+		chsav.tintb = tint_blue;
+		chsav.tintamnt = tint_amount;
+		chsav.tintlight = tint_light;
+		chsav.lightlev = light_level;
 
 		// If cache needs to be re-drawn
 		// NOTE: we need cached bitmap if:
 		// * it's a software renderer, otherwise
 		// * the walk-behind method is DrawOverCharSprite
-		if (((!_G(gfxDriver)->HasAcceleratedTransform()) || (_G(walkBehindMethod) == DrawOverCharSprite)) && !_GP(charcache)[aa].in_use) {
+		if (((!_G(gfxDriver)->HasAcceleratedTransform()) || (_G(walkBehindMethod) == DrawOverCharSprite)) && !chsav.in_use) {
 			// create the base sprite in _GP(actsps)[useindx], which will
 			// be scaled and/or flipped, as appropriate
 			bool actspsUsed = false;
@@ -1642,9 +1654,9 @@ void prepare_characters_for_drawing() {
 			}
 
 			// update the character cache with the new image
-			_GP(charcache)[aa].in_use = true;
-			recycle_bitmap(_GP(charcache)[aa].image, coldept, actsp.Bmp->GetWidth(), actsp.Bmp->GetHeight());
-			_GP(charcache)[aa].image->Blit(actsp.Bmp.get(), 0, 0);
+			chsav.in_use = true;
+			recycle_bitmap(chsav.image, coldept, actsp.Bmp->GetWidth(), actsp.Bmp->GetHeight());
+			chsav.image->Blit(actsp.Bmp.get(), 0, 0);
 
 		} // end if !cache.in_use
 
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 4afa06e5d33..0204c601d41 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -56,17 +56,17 @@ RoomObject::RoomObject() {
 	blocking_width = blocking_height = 0;
 }
 
-int RoomObject::get_width() {
+int RoomObject::get_width() const {
 	if (last_width == 0)
 		return _GP(game).SpriteInfos[num].Width;
 	return last_width;
 }
-int RoomObject::get_height() {
+int RoomObject::get_height() const {
 	if (last_height == 0)
 		return _GP(game).SpriteInfos[num].Height;
 	return last_height;
 }
-int RoomObject::get_baseline() {
+int RoomObject::get_baseline() const {
 	if (baseline < 1)
 		return y;
 	return baseline;
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index f23e8285235..be7fa3acb31 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -80,9 +80,9 @@ struct RoomObject {
 
 	RoomObject();
 
-	int get_width();
-	int get_height();
-	int get_baseline();
+	int get_width() const;
+	int get_height() const;
+	int get_baseline() const;
 
 	inline bool has_explicit_light() const {
 		return (flags & OBJF_HASLIGHT) != 0;
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 01444280f56..976a376a4a4 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -114,10 +114,10 @@ struct CharacterInfo {
 	char  scrname[MAX_SCRIPT_NAME_LEN];
 	int8  on;
 
-	int get_effective_y();   // return Y - Z
-	int get_baseline();      // return baseline, or Y if not set
-	int get_blocking_top();    // return Y - BlockingHeight/2
-	int get_blocking_bottom(); // return Y + BlockingHeight/2
+	int get_effective_y() const;   // return Y - Z
+	int get_baseline() const;      // return baseline, or Y if not set
+	int get_blocking_top() const;    // return Y - BlockingHeight/2
+	int get_blocking_bottom() const; // return Y + BlockingHeight/2
 
 	// Returns effective x/y walkspeeds for this character
 	void get_effective_walkspeeds(int &walk_speed_x, int &walk_speed_y) const {


Commit: ddadaa75c775ffb34af355eadf22ac85114441b0
    https://github.com/scummvm/scummvm/commit/ddadaa75c775ffb34af355eadf22ac85114441b0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: moved room object scaling update from draw to main game update

>From upstream c5e20301a490d22631975ada3e4fba91d650039d

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/room_object.h
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index cb65b567cb9..89bfc2487f3 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1173,7 +1173,7 @@ static bool scale_and_flip_sprite(int useindx, int sppic, int newwidth, int newh
 bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
 	bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
 
-	/*const*/ RoomObject &obj = _G(objs)[objid];
+	const RoomObject &obj = _G(objs)[objid];
 
 	if (_GP(spriteset)[obj.num] == nullptr)
 		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
@@ -1186,25 +1186,6 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
-	int zoom_level = 100;
-
-	// calculate the zoom level
-	if ((obj.flags & OBJF_USEROOMSCALING) == 0) {
-		zoom_level = obj.zoom;
-	} else {
-		int onarea = get_walkable_area_at_location(obj.x, obj.y);
-
-		if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
-			// just off the edge of an area -- use the scaling we had
-			// while on the area
-			zoom_level = obj.zoom;
-		} else
-			zoom_level = get_area_scaling(onarea, obj.x, obj.y);
-	}
-
-	if (zoom_level != 100)
-		scale_sprite_size(obj.num, zoom_level, &sprwidth, &sprheight);
-	obj.zoom = zoom_level;
 
 	// save width/height into parameters if requested
 	if (drawnWidth)
@@ -1212,9 +1193,6 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 	if (drawnHeight)
 		*drawnHeight = sprheight;
 
-	obj.last_width = sprwidth;
-	obj.last_height = sprheight;
-
 	tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
 
 	if (obj.flags & OBJF_HASTINT) {
@@ -1264,7 +1242,6 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 		objsav.tintb = tint_blue;
 		objsav.tintlight = tint_light;
 		objsav.lightlev = light_level;
-		objsav.zoom = zoom_level;
 		objsav.mirrored = isMirrored;
 		return is_texture_intact;
 	}
@@ -1287,7 +1264,6 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 			(objsav.tintg == tint_green) &&
 			(objsav.tintb == tint_blue) &&
 			(objsav.lightlev == light_level) &&
-			(objsav.zoom == zoom_level) &&
 			(objsav.mirrored == isMirrored)) {
 		// the image is the same, we can use it cached!
 		if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
@@ -1342,7 +1318,6 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 	objsav.tintb = tint_blue;
 	objsav.tintlight = tint_light;
 	objsav.lightlev = light_level;
-	objsav.zoom = zoom_level;
 	objsav.mirrored = isMirrored;
 	return false; // image was modified
 }
@@ -1482,6 +1457,8 @@ void prepare_characters_for_drawing() {
 		/*const*/ CharacterExtras &chex = _GP(charextra)[charid];
 
 		_G(our_eip) = 330;
+
+		// FIXME ----- move to update
 		// Test for valid view and loop
 		if (chin->view < 0) {
 			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
@@ -1584,6 +1561,7 @@ void prepare_characters_for_drawing() {
 
 		_G(our_eip) = 3332;
 
+		// FIXME ----- move to update
 		const int src_sprwidth = _GP(game).SpriteInfos[sppic].Width;
 		const int src_sprheight = _GP(game).SpriteInfos[sppic].Height;
 
@@ -1601,14 +1579,17 @@ void prepare_characters_for_drawing() {
 			newwidth = src_sprwidth;
 			newheight = src_sprheight;
 		}
+		// FIXME ----- move to update
 
 		_G(our_eip) = 3336;
 
+		// FIXME ----- move to update
 		// Calculate the X & Y co-ordinates of where the sprite will be
 		const int atxp = (data_to_game_coord(chin->x)) - newwidth / 2;
 		const int atyp = (data_to_game_coord(chin->y) - newheight)
 		                 // adjust the Y positioning for the character's Z co-ord
 		                 - data_to_game_coord(chin->z);
+		// FIXME ----- move to update
 
 		chsav.zoom = zoom_level;
 		chsav.sppic = specialpic;
@@ -1701,8 +1682,10 @@ void prepare_characters_for_drawing() {
 
 		_G(our_eip) = 337;
 
+		// FIXME ----- move to update
 		chin->actx = atxp;
 		chin->acty = atyp;
+		// FIXME ----- move to update
 
 		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(chin->transparency));
 		add_to_sprite_list(actsp.Ddb, bgX, bgY, usebasel, false);
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index be7fa3acb31..88c91ed5e3a 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -64,7 +64,7 @@ struct RoomObject {
 	short tint_b, tint_level;
 	short tint_light;
 	short zoom;           // zoom level, either manual or from the current area
-	short last_width, last_height;   // width/height last time drawn
+	short last_width, last_height;   // width/height based on a scaled sprite
 	uint16_t num;            // sprite slot number
 	short baseline;       // <=0 to use Y co-ordinate; >0 for specific baseline
 	uint16_t view, loop, frame; // only used to track animation - 'num' holds the current sprite
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 4598a186f6f..effe4a052fb 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -50,6 +50,7 @@
 #include "ags/engine/ac/room_object.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/view_frame.h"
+#include "ags/engine/ac/walkable_area.h"
 #include "ags/engine/ac/walk_behind.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/engine/debugging/debug_log.h"
@@ -615,6 +616,33 @@ static void game_loop_update_animated_buttons() {
 	}
 }
 
+static void update_objects_scale() {
+	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
+		RoomObject &obj = _G(objs)[objid];
+		int zoom_level = 100;
+		// calculate the zoom level
+		if ((obj.flags & OBJF_USEROOMSCALING) == 0) {
+			zoom_level = obj.zoom;
+		} else {
+			int onarea = get_walkable_area_at_location(obj.x, obj.y);
+			if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
+				// not on a valid area -- use the last scaling we had while on the area
+				zoom_level = obj.zoom;
+			} else {
+				zoom_level = get_area_scaling(onarea, obj.x, obj.y);
+			}
+		}
+		int sprwidth = _GP(game).SpriteInfos[obj.num].Width;
+		int sprheight = _GP(game).SpriteInfos[obj.num].Height;
+		if (zoom_level != 100) {
+			scale_sprite_size(obj.num, zoom_level, &sprwidth, &sprheight);
+		}
+		obj.zoom = zoom_level;
+		obj.last_width = sprwidth;
+		obj.last_height = sprheight;
+	}
+}
+
 // Updates GUI reaction to the cursor position change
 // TODO: possibly may be merged with gui_on_mouse_move()
 static void update_cursor_over_gui() {
@@ -844,11 +872,14 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 
 	game_loop_do_late_script_update();
 
-	update_audio_system_on_game_loop();
-
+	// historically room object and character scaling was updated
+	// right before the drawing
+	update_objects_scale();
 	update_cursor_over_location(mwasatx, mwasaty);
 	update_cursor_view();
 
+	update_audio_system_on_game_loop();
+
 	// Only render if we are not skipping a cutscene
 	if (!_GP(play).fast_forward)
 		render_graphics(extraBitmap, extraX, extraY);


Commit: ca93d22ddd4e414707fde003fd9686bd83cf57b3
    https://github.com/scummvm/scummvm/commit/ca93d22ddd4e414707fde003fd9686bd83cf57b3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: moved character scaling update from draw to main game update

from upstream f41eb86f25d425f4c8c040089131897d20419559

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 89bfc2487f3..361b68f862b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1156,10 +1156,10 @@ static Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, std::unique_ptr
 // Returns 1 if something was drawn to actsps; returns 0 if no
 // scaling or stretching was required, in which case nothing was done.
 // Used for software render mode only.
-static bool scale_and_flip_sprite(int useindx, int sppic, int newwidth, int newheight, bool hmirror) {
+static bool scale_and_flip_sprite(int useindx, int sppic, int width, int height, bool hmirror) {
 	Bitmap *src = _GP(spriteset)[sppic];
 	Bitmap *result = transform_sprite(src, (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0,
-		_GP(actsps)[useindx].Bmp, Size(newwidth, newheight), hmirror ? kFlip_Horizontal : kFlip_None);
+		_GP(actsps)[useindx].Bmp, Size(width, height), hmirror ? kFlip_Horizontal : kFlip_None);
 	return result != src;
 }
 
@@ -1439,65 +1439,25 @@ void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light
 
 
 void prepare_characters_for_drawing() {
-	int zoom_level, newwidth, newheight, onarea, sppic;
-	int light_level, coldept;
-	int tint_red, tint_green, tint_blue, tint_amount, tint_light = 255;
-
 	_G(our_eip) = 33;
 
 	// draw characters
-	for (int charid = 0; charid < _GP(game).numcharacters; ++charid) {
-		/*const*/ CharacterInfo *chin = &_GP(game).chars[charid];
-		if (chin->on == 0)
+	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
+		const CharacterInfo &chin = _GP(game).chars[charid];
+		if (chin.on == 0)
 			continue;
-		if (chin->room != _G(displayed_room))
+		if (chin.room != _G(displayed_room))
 			continue;
 		_G(eip_guinum) = charid;
 
-		/*const*/ CharacterExtras &chex = _GP(charextra)[charid];
+		const CharacterExtras &chex = _GP(charextra)[charid];
 
 		_G(our_eip) = 330;
 
-		// FIXME ----- move to update
-		// Test for valid view and loop
-		if (chin->view < 0) {
-			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
-			           chin->name, _G(displayed_room));
-		}
-		if (chin->loop >= _GP(views)[chin->view].numLoops) {
-			quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
-					   chin->name, chin->loop, chin->view + 1);
-			continue;  // FIXME: upstream does not break here
-		}
-		// If frame is too high -- fallback to the frame 0;
-		// there's always at least 1 dummy frame at index 0
-		if (chin->frame >= _GP(views)[chin->view].loops[chin->loop].numFrames)
-			chin->frame = 0;
-
-		sppic = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
-		if (sppic < 0)
-			sppic = 0;  // in case it's screwed up somehow
-		_G(our_eip) = 331;
-		// sort out the stretching if required
-		onarea = get_walkable_area_at_character(charid);
-		_G(our_eip) = 332;
-
-		// calculates the zoom level
-		if (chin->flags & CHF_MANUALSCALING)  // character ignores scaling
-			zoom_level = chex.zoom;
-		else if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
-			zoom_level = chex.zoom;
-			// NOTE: room objects don't have this fix
-			if (zoom_level == 0)
-				zoom_level = 100;
-		} else
-			zoom_level = get_area_scaling(onarea, chin->x, chin->y);
-
-		chex.zoom = zoom_level;
-
-		tint_red = tint_green = tint_blue = tint_amount = tint_light = light_level = 0;
-
-		if (chin->flags & CHF_HASTINT) {
+		int tint_red = 0, tint_green = 0, tint_blue = 0, tint_amount = 0;
+		int tint_light = 0, light_level = 0;
+
+		if (chin.flags & CHF_HASTINT) {
 			// object specific tint, use it
 			tint_red = chex.tint_r;
 			tint_green = chex.tint_g;
@@ -1505,24 +1465,25 @@ void prepare_characters_for_drawing() {
 			tint_amount = chex.tint_level;
 			tint_light = chex.tint_light;
 			light_level = 0;
-		} else if (chin->flags & CHF_HASLIGHT) {
+		} else if (chin.flags & CHF_HASLIGHT) {
 			light_level = chex.tint_light;
 		} else {
-			get_local_tint(chin->x, chin->y, chin->flags & CHF_NOLIGHTING,
+			get_local_tint(chin.x, chin.y, chin.flags & CHF_NOLIGHTING,
 			               &tint_amount, &tint_red, &tint_green, &tint_blue,
 			               &tint_light, &light_level);
 		}
 
 		_G(our_eip) = 3330;
+		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
 		bool isMirrored = false;
 		int specialpic = sppic;
 		bool usingCachedImage = false;
 
-		coldept = _GP(spriteset)[sppic]->GetColorDepth();
+		const int coldept = _GP(spriteset)[sppic]->GetColorDepth();
 
 		// adjust the sppic if mirrored, so it doesn't accidentally
 		// cache the mirrored frame as the real one
-		if (_GP(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE) {
+		if (_GP(views)[chin.view].loops[chin.loop].frames[chin.frame].flags & VFLG_FLIPSPRITE) {
 			isMirrored = true;
 			specialpic = -sppic;
 		}
@@ -1538,7 +1499,7 @@ void prepare_characters_for_drawing() {
 		// just use the cached image
 		if ((chsav.in_use) &&
 			(chsav.sppic == specialpic) &&
-			(chsav.zoom == zoom_level) &&
+			(chsav.zoom == chex.zoom) &&
 			(chsav.tintr == tint_red) &&
 			(chsav.tintg == tint_green) &&
 			(chsav.tintb == tint_blue) &&
@@ -1559,39 +1520,7 @@ void prepare_characters_for_drawing() {
 			chsav.in_use = false;
 		}
 
-		_G(our_eip) = 3332;
-
-		// FIXME ----- move to update
-		const int src_sprwidth = _GP(game).SpriteInfos[sppic].Width;
-		const int src_sprheight = _GP(game).SpriteInfos[sppic].Height;
-
-		if (zoom_level != 100) {
-			// it needs to be stretched, so calculate the new dimensions
-
-			scale_sprite_size(sppic, zoom_level, &newwidth, &newheight);
-			chex.width = newwidth;
-			chex.height = newheight;
-		} else {
-			// draw at original size, so just use the sprite width and height
-			// TODO: store width and height always, that's much simplier to use for reference!
-			chex.width = 0;
-			chex.height = 0;
-			newwidth = src_sprwidth;
-			newheight = src_sprheight;
-		}
-		// FIXME ----- move to update
-
-		_G(our_eip) = 3336;
-
-		// FIXME ----- move to update
-		// Calculate the X & Y co-ordinates of where the sprite will be
-		const int atxp = (data_to_game_coord(chin->x)) - newwidth / 2;
-		const int atyp = (data_to_game_coord(chin->y) - newheight)
-		                 // adjust the Y positioning for the character's Z co-ord
-		                 - data_to_game_coord(chin->z);
-		// FIXME ----- move to update
-
-		chsav.zoom = zoom_level;
+		chsav.zoom = chex.zoom;
 		chsav.sppic = specialpic;
 		chsav.tintr = tint_red;
 		chsav.tintg = tint_green;
@@ -1609,11 +1538,11 @@ void prepare_characters_for_drawing() {
 			// be scaled and/or flipped, as appropriate
 			bool actspsUsed = false;
 			if (!_G(gfxDriver)->HasAcceleratedTransform()) {
-				actspsUsed = scale_and_flip_sprite(useindx, sppic, newwidth, newheight, isMirrored);
+				actspsUsed = scale_and_flip_sprite(useindx, sppic, chex.width, chex.height, isMirrored);
 			}
 			if (!actspsUsed) {
 				// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
-				recycle_bitmap(actsp.Bmp, coldept, src_sprwidth, src_sprheight);
+				recycle_bitmap(actsp.Bmp, coldept, _GP(game).SpriteInfos[sppic].Width, _GP(game).SpriteInfos[sppic].Height);
 			}
 
 			_G(our_eip) = 335;
@@ -1641,14 +1570,14 @@ void prepare_characters_for_drawing() {
 
 		} // end if !cache.in_use
 
-		int usebasel = chin->get_baseline();
+		int usebasel = chin.get_baseline();
 
 		_G(our_eip) = 336;
 
-		const int bgX = atxp + chin->pic_xoffs;
-		const int bgY = atyp + chin->pic_yoffs;
+		const int bgX = chin.actx + chin.pic_xoffs;
+		const int bgY = chin.acty + chin.pic_yoffs;
 
-		if (chin->flags & CHF_NOWALKBEHINDS) {
+		if (chin.flags & CHF_NOWALKBEHINDS) {
 			// ignore walk-behinds, do nothing
 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
 				usebasel += _GP(thisroom).Height;
@@ -1662,7 +1591,7 @@ void prepare_characters_for_drawing() {
 		}
 
 		if (_G(gfxDriver)->HasAcceleratedTransform()) {
-			actsp.Ddb->SetStretch(newwidth, newheight);
+			actsp.Ddb->SetStretch(chex.width, chex.height);
 			actsp.Ddb->SetFlippedLeftRight(isMirrored);
 			actsp.Ddb->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100);
 
@@ -1682,12 +1611,7 @@ void prepare_characters_for_drawing() {
 
 		_G(our_eip) = 337;
 
-		// FIXME ----- move to update
-		chin->actx = atxp;
-		chin->acty = atyp;
-		// FIXME ----- move to update
-
-		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(chin->transparency));
+		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(chin.transparency));
 		add_to_sprite_list(actsp.Ddb, bgX, bgY, usebasel, false);
 	}
 }
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index effe4a052fb..1fffda051e5 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -632,15 +632,79 @@ static void update_objects_scale() {
 				zoom_level = get_area_scaling(onarea, obj.x, obj.y);
 			}
 		}
+
+		if (zoom_level == 0)
+			zoom_level = 100; // safety fix
+
 		int sprwidth = _GP(game).SpriteInfos[obj.num].Width;
 		int sprheight = _GP(game).SpriteInfos[obj.num].Height;
 		if (zoom_level != 100) {
 			scale_sprite_size(obj.num, zoom_level, &sprwidth, &sprheight);
 		}
+
+		// Save calculated propertes
 		obj.zoom = zoom_level;
 		obj.last_width = sprwidth;
 		obj.last_height = sprheight;
 	}
+
+	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
+		// Test for valid view and loop
+		CharacterInfo &chin = _GP(game).chars[charid];
+		CharacterExtras &chex = _GP(charextra)[charid];
+		if (chin.view < 0) {
+			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
+					   chin.name, _G(displayed_room));
+		}
+		if (chin.loop >= _GP(views)[chin.view].numLoops) {
+			quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
+					   chin.name, chin.loop, chin.view + 1);
+		}
+		// If frame is too high -- fallback to the frame 0;
+		// there's always at least 1 dummy frame at index 0
+		if (chin.frame >= _GP(views)[chin.view].loops[chin.loop].numFrames) {
+			chin.frame = 0;
+		}
+
+		// calculate the zoom level
+		int zoom_level = 100;
+		if (chin.flags & CHF_MANUALSCALING) // character ignores scaling
+		{
+			zoom_level = chex.zoom;
+		} else {
+			const int onarea = get_walkable_area_at_character(charid);
+			if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
+				// not on a valid area -- use the last scaling we had while on the area
+				zoom_level = chex.zoom;
+			} else {
+				zoom_level = get_area_scaling(onarea, chin.x, chin.y);
+			}
+		}
+
+		if (zoom_level == 0)
+			zoom_level = 100; // safety fix
+
+		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
+		int sprwidth = _GP(game).SpriteInfos[sppic].Width;
+		int sprheight = _GP(game).SpriteInfos[sppic].Height;
+		if (zoom_level != 100) {
+			scale_sprite_size(sppic, zoom_level, &sprwidth, &sprheight);
+		}
+
+		// Calculate the X & Y co-ordinates of where the sprite will be;
+		// for the character sprite's origin is at the bottom-mid of a sprite.
+		const int atxp = (data_to_game_coord(chin.x)) - sprwidth / 2;
+		const int atyp = (data_to_game_coord(chin.y) - sprheight)
+						 // adjust the Y positioning for the character's Z co-ord
+						 - data_to_game_coord(chin.z);
+
+		// Save calculated properties
+		chex.width = sprwidth;
+		chex.height = sprheight;
+		chin.actx = atxp;
+		chin.acty = atyp;
+		chex.zoom = zoom_level;
+	}
 }
 
 // Updates GUI reaction to the cursor position change


Commit: cfbd6c16378104082c35864226a766cbbd88d158
    https://github.com/scummvm/scummvm/commit/cfbd6c16378104082c35864226a766cbbd88d158
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed character scale not updating at the fully blocking state

Also fixed disabled objects and characters updating unnecessarily.
This complements c5e2030 and f41eb86

from upstream 963f0e133189bc9aad1cecb8321a5aa370c8cfd7

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/gui/gui_dialog.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/main/game_run.h


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 1334d5c27b5..f9926afa65c 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -806,7 +806,7 @@ bool DialogOptions::Run() {
 		_GP(play).disabled_user_interface--;
 	} else {
 		update_audio_system_on_game_loop();
-		update_cursor_and_dependent();
+		UpdateCursorAndDrawables();
 		render_graphics(ddb, dirtyx, dirtyy);
 	}
 
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index cee7c97bf37..dc4e987de1e 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -306,7 +306,7 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 			sys_evt_process_pending();
 
 			update_audio_system_on_game_loop();
-			update_cursor_and_dependent();
+			UpdateCursorAndDrawables();
 			render_graphics();
 			eAGSMouseButton mbut;
 			int mwheelz;
diff --git a/engines/ags/engine/gui/gui_dialog.cpp b/engines/ags/engine/gui/gui_dialog.cpp
index b6524e5d6ea..91d731d2cff 100644
--- a/engines/ags/engine/gui/gui_dialog.cpp
+++ b/engines/ags/engine/gui/gui_dialog.cpp
@@ -81,7 +81,7 @@ void clear_gui_screen() {
 
 void refresh_gui_screen() {
 	_G(gfxDriver)->UpdateDDBFromBitmap(_G(dialogDDB), _G(windowBuffer), false);
-	update_cursor_and_dependent();
+	UpdateCursorAndDrawables();
 	render_graphics(_G(dialogDDB), _G(windowPosX), _G(windowPosY));
 }
 
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 1fffda051e5..16dfab35973 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -619,6 +619,9 @@ static void game_loop_update_animated_buttons() {
 static void update_objects_scale() {
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		RoomObject &obj = _G(objs)[objid];
+		if (obj.on == 0)
+			continue; // not enabled
+
 		int zoom_level = 100;
 		// calculate the zoom level
 		if ((obj.flags & OBJF_USEROOMSCALING) == 0) {
@@ -651,6 +654,9 @@ static void update_objects_scale() {
 	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
 		// Test for valid view and loop
 		CharacterInfo &chin = _GP(game).chars[charid];
+		if (chin.on == 0 || chin.room != _G(displayed_room))
+			continue; // not enabled, or in a different room
+
 		CharacterExtras &chex = _GP(charextra)[charid];
 		if (chin.view < 0) {
 			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
@@ -1201,12 +1207,16 @@ void RunGameUntilAborted() {
 	}
 }
 
-void update_cursor_and_dependent() {
+void UpdateCursorAndDrawables() {
 	const int mwasatx = _G(mousex), mwasaty = _G(mousey);
 	ags_domouse();
 	update_cursor_over_gui();
 	update_cursor_over_location(mwasatx, mwasaty);
 	update_cursor_view();
+	// TODO: following does not have to be called every frame while in a
+	// fully blocking state (like Display() func), refactor to only call it
+	// once the blocking state begins.
+	update_objects_scale();
 }
 
 void update_polled_stuff() {
diff --git a/engines/ags/engine/main/game_run.h b/engines/ags/engine/main/game_run.h
index 7cc247849e2..033713e209e 100644
--- a/engines/ags/engine/main/game_run.h
+++ b/engines/ags/engine/main/game_run.h
@@ -50,6 +50,11 @@ void RunGameUntilAborted();
 void UpdateGameOnce(bool checkControls = false, IDriverDependantBitmap *extraBitmap = nullptr, int extraX = 0, int extraY = 0);
 // Update minimal required game state: audio, loop counter, etc; wait for the next frame
 void UpdateGameAudioOnly();
+// Updates everything related to object views that could have changed in the midst of a
+// blocking script, cursor position and view, poll anything related to cursor position;
+// this function is useful when you don't want to update whole game, but only things
+// that are necessary for rendering the game screen.
+void UpdateCursorAndDrawables();
 // Gets current logical game FPS, this is normally a fixed number set in script;
 // in case of "maxed fps" mode this function returns real measured FPS.
 float get_current_fps();
@@ -62,9 +67,6 @@ bool run_service_mb_controls(eAGSMouseButton &mbut, int &mwheelz);
 // Polls few things (exit flag and debugger messages)
 // TODO: refactor this
 void update_polled_stuff();
-// Update cursor position and view, poll anything related to cursor position;
-// this function is useful when you don't want to update whole game, but only the cursor.
-void update_cursor_and_dependent();
 
 } // namespace AGS3
 


Commit: 4a0efaf0cedca90e2348d5c2b870e73d22083b25
    https://github.com/scummvm/scummvm/commit/4a0efaf0cedca90e2348d5c2b870e73d22083b25
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: added few comments to the common constants

from upstream a3ee12fba15a958c160bcde9ec867390fb924b8e

Changed paths:
    engines/ags/shared/ac/common_defines.h


diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h
index ddb5ea40a03..2a8f6c421a3 100644
--- a/engines/ags/shared/ac/common_defines.h
+++ b/engines/ags/shared/ac/common_defines.h
@@ -26,21 +26,14 @@
 
 namespace AGS3 {
 
+// Some arbitrary return values, should be replaced with either
+// simple boolean, or HError
 #define EXIT_NORMAL 0
 #define EXIT_CRASH  92
 #define EXIT_ERROR  93
 
-
-#if defined (OBSOLETE)
-#define NUM_MISC      20
-#define NUMOTCON      7                 // number of conditions before standing on
-#define NUM_CONDIT    (120 + NUMOTCON)
-#endif
-
-#define MAX_SCRIPT_NAME_LEN 20
-
-//const int MISC_COND = MAX_WALK_BEHINDS * 4 + NUMOTCON + MAX_ROOM_OBJECTS * 4;
-
+// Legacy (UNSUPPORTED!) interaction script constants
+//
 // NUMCONDIT : whataction[0]:  Char walks off left
 //                       [1]:  Char walks off right
 //                       [2]:  Char walks off bottom
@@ -74,6 +67,11 @@ namespace AGS3 {
 // v1.12             12  :  Play FLI/FLC animation FLIC%d.FLC or FLIC%d.FLI
 //                   13  :  Turn object on
 // v2.00             14  :  Run conversation
+#if defined(OBSOLETE)
+#define NUM_MISC      20
+#define NUMOTCON      7 // number of conditions before standing on
+#define NUM_CONDIT    (120 + NUMOTCON)
+#define MISC_COND     (MAX_WALK_BEHINDS * 4 + NUMOTCON + MAX_ROOM_OBJECTS * 4)
 #define NUMRESPONSE   14
 #define NUMCOMMANDS   15
 #define GO_TO_SCREEN  0
@@ -91,21 +89,24 @@ namespace AGS3 {
 #define PLAY_FLI      12
 #define OBJECT_ON     13
 #define RUN_DIALOG    14
+#endif
 
+// Script name length limit for some game objects
+#define MAX_SCRIPT_NAME_LEN 20
 // Number of state-saved rooms
 #define MAX_ROOMS 300
 // Some obsolete room data, likely pre-2.5
 #define MAX_LEGACY_ROOM_FLAGS 15
-
+// Old object name limit
 #define LEGACY_MAXOBJNAMELEN 30
-
+// Max number of sprites in older versions
 #define LEGACY_MAX_SPRITES_V25  6000
 #define LEGACY_MAX_SPRITES      30000
 
-// The game to screen coordinate conversion multiplier in hi-res type games
+// The game to screen coordinate conversion multiplier, was used in older high-res games
 #define HIRES_COORD_MULTIPLIER 2
 
-// object flags (currently only a char)
+// Room object flags (currently limited by a byte)
 #define OBJF_NOINTERACT        1  // not clickable
 #define OBJF_NOWALKBEHINDS     2  // ignore walk-behinds
 #define OBJF_HASTINT           4  // the tint_* members are valid


Commit: 69ea55b721f7d70b27148adac1677234a03fa42e
    https://github.com/scummvm/scummvm/commit/69ea55b721f7d70b27148adac1677234a03fa42e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixes to construct_object_gfx()

from upstream fb240801322d30eb957fbb6a9e228928a7345553

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/global_object.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 361b68f862b..e035449ee5c 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1163,14 +1163,14 @@ static bool scale_and_flip_sprite(int useindx, int sppic, int width, int height,
 	return result != src;
 }
 
-// Create the actsps[aa] image with the object drawn correctly.
+// Create the actsps[objid] image with the object drawn correctly.
 // Returns true if nothing at all has changed and actsps is still
 // intact from last time; false otherwise.
 // Hardware-accelerated renderers always return true, because they do not
 // require altering the raw bitmap itself.
 // Except if alwaysUseSoftware is set, in which case even HW renderers
 // construct the image in software mode as well.
-bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
+bool construct_object_gfx(int objid, bool alwaysUseSoftware) {
 	bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
 
 	const RoomObject &obj = _G(objs)[objid];
@@ -1178,21 +1178,12 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 	if (_GP(spriteset)[obj.num] == nullptr)
 		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
 
-	int coldept = _GP(spriteset)[obj.num]->GetColorDepth();
+	const int coldept = _GP(spriteset)[obj.num]->GetColorDepth();
 	const int src_sprwidth = _GP(game).SpriteInfos[obj.num].Width;
 	const int src_sprheight = _GP(game).SpriteInfos[obj.num].Height;
-	int sprwidth = src_sprwidth;
-	int sprheight = src_sprheight;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
-
-	// save width/height into parameters if requested
-	if (drawnWidth)
-		*drawnWidth = sprwidth;
-	if (drawnHeight)
-		*drawnHeight = sprheight;
-
 	tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
 
 	if (obj.flags & OBJF_HASTINT) {
@@ -1276,8 +1267,8 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 				(actsp.Bmp != nullptr) &&
 				(_G(walk_behind_baselines_changed) == 0))
 			return true;
-		recycle_bitmap(actsp.Bmp, coldept, sprwidth, sprheight);
-		actsp.Bmp->Blit(objsav.image.get(), 0, 0, 0, 0, objsav.image->GetWidth(), objsav.image->GetHeight());
+		recycle_bitmap(actsp.Bmp, objsav.image->GetColorDepth(), objsav.image->GetWidth(), objsav.image->GetHeight());
+		actsp.Bmp->Blit(objsav.image.get(), 0, 0);
 		return false; // image was modified
 	}
 
@@ -1286,7 +1277,7 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 	bool actspsUsed = false;
 	if (!hardwareAccelerated) {
 		// draw the base sprite, scaled and flipped as appropriate
-		actspsUsed = scale_and_flip_sprite(useindx, obj.num, sprwidth, sprheight, isMirrored);
+		actspsUsed = scale_and_flip_sprite(useindx, obj.num, obj.last_width, obj.last_height, isMirrored);
 	}
 	if (!actspsUsed) {
 		recycle_bitmap(actsp.Bmp, coldept, src_sprwidth, src_sprheight);
@@ -1307,9 +1298,8 @@ bool construct_object_gfx(int objid, int *drawnWidth, int *drawnHeight, bool alw
 		actsp.Bmp->Blit(_GP(spriteset)[obj.num], 0, 0);
 	}
 
-	// Re-use the bitmap if it's the same size
-	recycle_bitmap(objsav.image, coldept, sprwidth, sprheight);
 	// Create the cached image and store it
+	recycle_bitmap(objsav.image, actsp.Bmp->GetColorDepth(), actsp.Bmp->GetWidth(), actsp.Bmp->GetHeight());
 	objsav.image->Blit(actsp.Bmp.get(), 0, 0);
 	objsav.sppic = obj.num;
 	objsav.tintamnt = tint_level;
@@ -1334,8 +1324,7 @@ void prepare_objects_for_drawing() {
 		if ((obj.x >= _GP(thisroom).Width) || (obj.y < 1))
 			continue;
 
-		int tehHeight;
-		bool actspsIntact = construct_object_gfx(objid, nullptr, &tehHeight, false);
+		bool actspsIntact = construct_object_gfx(objid, false);
 
 		const int useindx = objid; // actsps array index
 		auto &actsp = _GP(actsps)[useindx];
@@ -1344,8 +1333,8 @@ void prepare_objects_for_drawing() {
 		ObjectCache &objsav = _G(objcache)[objid];
 		objsav.x = obj.x;
 		objsav.y = obj.y;
-		int atxp = data_to_game_coord(obj.x);
-		int atyp = data_to_game_coord(obj.y) - tehHeight;
+		const int atxp = data_to_game_coord(obj.x);
+		const int atyp = data_to_game_coord(obj.y) - obj.last_height;
 
 		int usebasel = obj.get_baseline();
 
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 049fcdd39cd..f4f6d56b820 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -188,7 +188,7 @@ void draw_game_screen_callback();
 void GfxDriverOnInitCallback(void *data);
 bool GfxDriverSpriteEvtCallback(int evt, int data);
 void putpixel_compensate(Shared::Bitmap *g, int xx, int yy, int col);
-// Create the actsps[aa] image with the object drawn correctly.
+// Create the actsps[objid] image with the object drawn correctly.
 // Returns true if nothing at all has changed and actsps is still
 // intact from last time; false otherwise.
 // Hardware-accelerated do not require altering the raw bitmap itself,
@@ -197,7 +197,7 @@ void putpixel_compensate(Shared::Bitmap *g, int xx, int yy, int col);
 // effect changes (scaling, tint, etc).
 // * alwaysUseSoftware option forces HW renderers to  construct the image
 // in software mode as well.
-bool construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware);
+bool construct_object_gfx(int objid, bool alwaysUseSoftware);
 // Returns a cached character image prepared for the render
 Shared::Bitmap *get_cached_character_image(int charid);
 // Returns a cached object image prepared for the render
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 1abfabbbef8..a1f21464076 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -279,9 +279,9 @@ void AnimateObject4(int obn, int loopn, int spdd, int rept) {
 
 void MergeObject(int obn) {
 	if (!is_valid_object(obn)) quit("!MergeObject: invalid object specified");
-	int theHeight;
 
-	construct_object_gfx(obn, nullptr, &theHeight, true);
+	// FIXME: call update object scale
+	construct_object_gfx(obn, true);
 	Bitmap *actsp = get_cached_object_image(obn);
 
 	PBitmap bg_frame = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
@@ -289,7 +289,7 @@ void MergeObject(int obn) {
 		quit("!MergeObject: unable to merge object due to color depth differences");
 
 	int xpos = data_to_game_coord(_G(objs)[obn].x);
-	int ypos = (data_to_game_coord(_G(objs)[obn].y) - theHeight);
+	int ypos = (data_to_game_coord(_G(objs)[obn].y) - _G(objs)[obn].last_height);
 
 	draw_sprite_support_alpha(bg_frame.get(), false, xpos, ypos, actsp, (_GP(game).SpriteInfos[_G(objs)[obn].num].Flags & SPF_ALPHACHANNEL) != 0);
 	invalidate_screen();


Commit: a23bc88912991a7d2c165b98f86c52eb13104910
    https://github.com/scummvm/scummvm/commit/a23bc88912991a7d2c165b98f86c52eb13104910
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: character and room object share function updating sprite scale

from upstream 54b689724c076e92e70decf9fe795ea76bec5f84

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index e67948c0a46..074ef0ae2f8 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2135,6 +2135,47 @@ CharacterInfo *GetCharacterAtRoom(int x, int y) {
 	return &_GP(game).chars[hsnum];
 }
 
+void update_character_scale(int charid) {
+	// Test for valid view and loop
+	CharacterInfo &chin = _GP(game).chars[charid];
+	if (chin.on == 0 || chin.room != _G(displayed_room))
+		return; // not enabled, or in a different room
+
+	CharacterExtras &chex = _GP(charextra)[charid];
+	if (chin.view < 0) {
+		quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
+				   chin.name, _G(displayed_room));
+	}
+	if (chin.loop >= _GP(views)[chin.view].numLoops) {
+		quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
+				   chin.name, chin.loop, chin.view + 1);
+	}
+	// If frame is too high -- fallback to the frame 0;
+	// there's always at least 1 dummy frame at index 0
+	if (chin.frame >= _GP(views)[chin.view].loops[chin.loop].numFrames) {
+		chin.frame = 0;
+	}
+
+	int zoom, scale_width, scale_height;
+	update_object_scale(zoom, scale_width, scale_height,
+						chin.x, chin.y, _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic,
+						chex.zoom, (chin.flags & CHF_MANUALSCALING) == 0);
+
+	// Calculate the X & Y co-ordinates of where the sprite will be;
+	// for the character sprite's origin is at the bottom-mid of a sprite.
+	const int atxp = (data_to_game_coord(chin.x)) - scale_width / 2;
+	const int atyp = (data_to_game_coord(chin.y) - scale_height)
+					 // adjust the Y positioning for the character's Z co-ord
+					 - data_to_game_coord(chin.z);
+
+	// Save calculated properties
+	chex.width = scale_width;
+	chex.height = scale_height;
+	chin.actx = atxp;
+	chin.acty = atyp;
+	chex.zoom = zoom;
+}
+
 int is_pos_on_character(int xx, int yy) {
 	int cc, sppic, lowestyp = 0, lowestwas = -1;
 	for (cc = 0; cc < _GP(game).numcharacters; cc++) {
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index b64fa2f91a6..448db9de154 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -212,6 +212,9 @@ void CheckViewFrameForCharacter(CharacterInfo *chi);
 int  GetCharacterFrameVolume(CharacterInfo *chi);
 Shared::Bitmap *GetCharacterImage(int charid, int *isFlipped);
 CharacterInfo *GetCharacterAtScreen(int xx, int yy);
+// Deduces room object's scale, accounting for both manual scaling and the room region effects;
+// calculates resulting sprite size.
+void update_character_scale(int charid);
 CharacterInfo *GetCharacterAtRoom(int x, int y);
 // Get character ID at the given room coordinates
 int is_pos_on_character(int xx, int yy);
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index a1f21464076..a904263be50 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -280,7 +280,7 @@ void AnimateObject4(int obn, int loopn, int spdd, int rept) {
 void MergeObject(int obn) {
 	if (!is_valid_object(obn)) quit("!MergeObject: invalid object specified");
 
-	// FIXME: call update object scale
+	update_object_scale(obn); // make sure sprite transform is up to date
 	construct_object_gfx(obn, true);
 	Bitmap *actsp = get_cached_object_image(obn);
 
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 2a34f9d1fed..57f5d259094 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -458,6 +458,47 @@ bool Object_SetTextProperty(ScriptObject *objj, const char *property, const char
 	return set_text_property(_G(croom)->objProps[objj->id], property, value);
 }
 
+void update_object_scale(int &res_zoom, int &res_width, int &res_height,
+						 int objx, int objy, int sprnum, int own_zoom, bool use_region_scaling) {
+	int zoom = own_zoom;
+	if (use_region_scaling) {
+		// Only apply area zoom if we're on a a valid area:
+		// * either area is > 0, or
+		// * area 0 has valid scaling property
+		int onarea = get_walkable_area_at_location(objx, objy);
+		if ((onarea > 0) || (_GP(thisroom).WalkAreas[0].ScalingFar > 0)) {
+			zoom = get_area_scaling(onarea, objx, objy);
+		}
+	}
+
+	if (zoom == 0)
+		zoom = 100; // safety fix
+
+	int sprwidth = _GP(game).SpriteInfos[sprnum].Width;
+	int sprheight = _GP(game).SpriteInfos[sprnum].Height;
+	if (zoom != 100) {
+		scale_sprite_size(sprnum, zoom, &sprwidth, &sprheight);
+	}
+
+	res_zoom = zoom;
+	res_width = sprwidth;
+	res_height = sprheight;
+}
+
+void update_object_scale(int objid) {
+	RoomObject &obj = _G(objs)[objid];
+	if (obj.on == 0)
+		return; // not enabled
+
+	int zoom, scale_width, scale_height;
+	update_object_scale(zoom, scale_width, scale_height,
+						obj.x, obj.y, obj.num, obj.zoom, (obj.flags & OBJF_USEROOMSCALING) != 0);
+
+	obj.zoom = zoom;
+	obj.last_width = scale_width;
+	obj.last_height = scale_height;
+}
+
 void get_object_blocking_rect(int objid, int *x1, int *y1, int *width, int *y2) {
 	RoomObject *tehobj = &_G(objs)[objid];
 	int cwidth, fromx;
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index ecf551ce981..285d218b742 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -100,6 +100,12 @@ const char *Object_GetTextProperty(ScriptObject *objj, const char *property);
 bool    Object_SetProperty(ScriptObject *objj, const char *property, int value);
 bool    Object_SetTextProperty(ScriptObject *objj, const char *property, const char *value);
 
+// Deduces room object's scale, accounting for both manual scaling and the room region effects;
+// calculates resulting sprite size.
+void    update_object_scale(int objid);
+// Deduces arbitrary object's scale, accounting for both manual scaling and the room region effects
+void    update_object_scale(int &res_zoom, int &res_width, int &res_height,
+						    int objx, int objy, int sprnum, int own_zoom, bool use_region_scaling);
 void    move_object(int objj, int tox, int toy, int spee, int ignwal);
 void    get_object_blocking_rect(int objid, int *x1, int *y1, int *width, int *y2);
 int     isposinbox(int mmx, int mmy, int lf, int tp, int rt, int bt);
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 16dfab35973..954109bf6c7 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -26,6 +26,7 @@
 #include "common/std/limits.h"
 #include "ags/engine/ac/button.h"
 #include "ags/shared/ac/common.h"
+#include "ags/engine/ac/character.h"
 #include "ags/engine/ac/character_extras.h"
 #include "ags/shared/ac/character_info.h"
 #include "ags/engine/ac/draw.h"
@@ -43,6 +44,7 @@
 #include "ags/engine/ac/hotspot.h"
 #include "ags/shared/ac/keycode.h"
 #include "ags/engine/ac/mouse.h"
+#include "ags/engine/ac/object.h"
 #include "ags/engine/ac/overlay.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/sys_events.h"
@@ -618,98 +620,10 @@ static void game_loop_update_animated_buttons() {
 
 static void update_objects_scale() {
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
-		RoomObject &obj = _G(objs)[objid];
-		if (obj.on == 0)
-			continue; // not enabled
-
-		int zoom_level = 100;
-		// calculate the zoom level
-		if ((obj.flags & OBJF_USEROOMSCALING) == 0) {
-			zoom_level = obj.zoom;
-		} else {
-			int onarea = get_walkable_area_at_location(obj.x, obj.y);
-			if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
-				// not on a valid area -- use the last scaling we had while on the area
-				zoom_level = obj.zoom;
-			} else {
-				zoom_level = get_area_scaling(onarea, obj.x, obj.y);
-			}
-		}
-
-		if (zoom_level == 0)
-			zoom_level = 100; // safety fix
-
-		int sprwidth = _GP(game).SpriteInfos[obj.num].Width;
-		int sprheight = _GP(game).SpriteInfos[obj.num].Height;
-		if (zoom_level != 100) {
-			scale_sprite_size(obj.num, zoom_level, &sprwidth, &sprheight);
-		}
-
-		// Save calculated propertes
-		obj.zoom = zoom_level;
-		obj.last_width = sprwidth;
-		obj.last_height = sprheight;
+		update_object_scale(objid);
 	}
-
 	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
-		// Test for valid view and loop
-		CharacterInfo &chin = _GP(game).chars[charid];
-		if (chin.on == 0 || chin.room != _G(displayed_room))
-			continue; // not enabled, or in a different room
-
-		CharacterExtras &chex = _GP(charextra)[charid];
-		if (chin.view < 0) {
-			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
-					   chin.name, _G(displayed_room));
-		}
-		if (chin.loop >= _GP(views)[chin.view].numLoops) {
-			quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
-					   chin.name, chin.loop, chin.view + 1);
-		}
-		// If frame is too high -- fallback to the frame 0;
-		// there's always at least 1 dummy frame at index 0
-		if (chin.frame >= _GP(views)[chin.view].loops[chin.loop].numFrames) {
-			chin.frame = 0;
-		}
-
-		// calculate the zoom level
-		int zoom_level = 100;
-		if (chin.flags & CHF_MANUALSCALING) // character ignores scaling
-		{
-			zoom_level = chex.zoom;
-		} else {
-			const int onarea = get_walkable_area_at_character(charid);
-			if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
-				// not on a valid area -- use the last scaling we had while on the area
-				zoom_level = chex.zoom;
-			} else {
-				zoom_level = get_area_scaling(onarea, chin.x, chin.y);
-			}
-		}
-
-		if (zoom_level == 0)
-			zoom_level = 100; // safety fix
-
-		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
-		int sprwidth = _GP(game).SpriteInfos[sppic].Width;
-		int sprheight = _GP(game).SpriteInfos[sppic].Height;
-		if (zoom_level != 100) {
-			scale_sprite_size(sppic, zoom_level, &sprwidth, &sprheight);
-		}
-
-		// Calculate the X & Y co-ordinates of where the sprite will be;
-		// for the character sprite's origin is at the bottom-mid of a sprite.
-		const int atxp = (data_to_game_coord(chin.x)) - sprwidth / 2;
-		const int atyp = (data_to_game_coord(chin.y) - sprheight)
-						 // adjust the Y positioning for the character's Z co-ord
-						 - data_to_game_coord(chin.z);
-
-		// Save calculated properties
-		chex.width = sprwidth;
-		chex.height = sprheight;
-		chin.actx = atxp;
-		chin.acty = atyp;
-		chex.zoom = zoom_level;
+		update_character_scale(charid);
 	}
 }
 


Commit: b107fa7e75efa95428e919da4794d8b21bcedaa7
    https://github.com/scummvm/scummvm/commit/b107fa7e75efa95428e919da4794d8b21bcedaa7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix a couple includes after move to common

Changed paths:
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/shared/ac/game_setup_struct_base.h


diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index c5cc63ae409..3e12815c7a6 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -22,7 +22,7 @@
 #ifndef AGS_ENGINE_AC_DYNOBJ_CC_DYNAMICARRAY_H
 #define AGS_ENGINE_AC_DYNOBJ_CC_DYNAMICARRAY_H
 
-#include "ags/lib/std/vector.h"
+#include "common/std/vector.h"
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/util/stream.h"
 
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index fc9999b7cbb..8075e528f95 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -30,8 +30,8 @@
 #define AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
 
 #include "ags/lib/allegro.h" // RGB
-#include "ags/lib/std/memory.h"
-#include "ags/lib/std/vector.h"
+#include "common/std/memory.h"
+#include "common/std/vector.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/ac/game_struct_defines.h"
 #include "ags/shared/util/string.h"


Commit: 1f335d83646f31eb6d6f6aa29d4b0f792c5a0790
    https://github.com/scummvm/scummvm/commit/1f335d83646f31eb6d6f6aa29d4b0f792c5a0790
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: character and room object share function constructing gfx

from upstream 878da2b324a8afd4fcc761a66158cdf5e189386e

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/plugins/ags_plugin.h
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/common_defines.h
    engines/ags/shared/util/bbop.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index e035449ee5c..2f7b457ed4e 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1170,100 +1170,102 @@ static bool scale_and_flip_sprite(int useindx, int sppic, int width, int height,
 // require altering the raw bitmap itself.
 // Except if alwaysUseSoftware is set, in which case even HW renderers
 // construct the image in software mode as well.
-bool construct_object_gfx(int objid, bool alwaysUseSoftware) {
-	bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
-
-	const RoomObject &obj = _G(objs)[objid];
-
-	if (_GP(spriteset)[obj.num] == nullptr)
-		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
-
-	const int coldept = _GP(spriteset)[obj.num]->GetColorDepth();
-	const int src_sprwidth = _GP(game).SpriteInfos[obj.num].Width;
-	const int src_sprheight = _GP(game).SpriteInfos[obj.num].Height;
+static bool construct_object_gfx(const ViewFrame *vf, int pic,
+								 const Size &scale_size,
+								 int tint_flags,            // OBJF_* flags related to using tint and light fx
+								 const ObjectCache &objsrc, // source item to acquire values from
+								 ObjectCache &objsav,       // cache item to use
+								 int actsps_index,          // actsps array item to use // FIXME: pass ObjectTexture instead?
+								 bool optimize_by_position, // allow to optimize walk-behind merging using object's pos
+								 bool force_software) {
+	const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
+	const int coldept = _GP(spriteset)[pic]->GetColorDepth();
+	const int src_sprwidth = _GP(game).SpriteInfos[pic].Width;
+	const int src_sprheight = _GP(game).SpriteInfos[pic].Height;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
 	tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
 
-	if (obj.flags & OBJF_HASTINT) {
+	if (tint_flags & OBJF_HASTINT) {
 		// object specific tint, use it
-		tint_red = obj.tint_r;
-		tint_green = obj.tint_g;
-		tint_blue = obj.tint_b;
-		tint_level = obj.tint_level;
-		tint_light = obj.tint_light;
+		tint_red = objsrc.tintr;
+		tint_green = objsrc.tintg;
+		tint_blue = objsrc.tintb;
+		tint_level = objsrc.tintamnt;
+		tint_light = objsrc.tintlight;
 		light_level = 0;
-	} else if (obj.flags & OBJF_HASLIGHT) {
-		light_level = obj.tint_light;
+	} else if (tint_flags & OBJF_HASLIGHT) {
+		light_level = objsrc.tintlight;
 	} else {
 		// get the ambient or region tint
-		int ignoreRegionTints = 1;
-		if (obj.flags & OBJF_USEREGIONTINTS)
-			ignoreRegionTints = 0;
-
-		get_local_tint(obj.x, obj.y, ignoreRegionTints,
+		get_local_tint(objsrc.x, objsrc.y, (tint_flags & OBJF_USEREGIONTINTS),
 		               &tint_level, &tint_red, &tint_green, &tint_blue,
 		               &tint_light, &light_level);
 	}
 
 	// check whether the image should be flipped
-	bool isMirrored = false;
-	if ((obj.view != (uint16_t)-1) &&
-		(_GP(views)[obj.view].loops[obj.loop].frames[obj.frame].pic == obj.num) &&
-		((_GP(views)[obj.view].loops[obj.loop].frames[obj.frame].flags & VFLG_FLIPSPRITE) != 0)) {
-		isMirrored = true;
+	bool is_mirrored = false;
+	int specialpic = pic;
+	if ( //(obj.view != (uint16_t)-1) &&
+		vf &&
+		(vf->pic == pic) &&
+		((vf->flags & VFLG_FLIPSPRITE) != 0)) {
+		is_mirrored = true;
+		specialpic = -pic;
 	}
 
-	const int useindx = objid; // actsps array index
+	const int useindx = actsps_index; // objid; // actsps array index
 	auto &actsp = _GP(actsps)[useindx];
-	actsp.SpriteID = obj.num; // for texture sharing
+	actsp.SpriteID = pic; // for texture sharing
 
 	// NOTE: we need cached bitmap if:
 	// * it's a software renderer, otherwise
 	// * the walk-behind method is DrawOverCharSprite
-	if ((hardwareAccelerated) && (_G(walkBehindMethod) != DrawOverCharSprite)) {
+	if ((use_hw_transform) && (_G(walkBehindMethod) != DrawOverCharSprite)) {
 		// HW acceleration
-		ObjectCache &objsav = _G(objcache)[objid];
-		bool is_texture_intact = objsav.sppic == obj.num;
-		objsav.sppic = obj.num;
+		const bool is_texture_intact = objsav.sppic == specialpic;
+		objsav.sppic = specialpic;
 		objsav.tintamnt = tint_level;
 		objsav.tintr = tint_red;
 		objsav.tintg = tint_green;
 		objsav.tintb = tint_blue;
 		objsav.tintlight = tint_light;
 		objsav.lightlev = light_level;
-		objsav.mirrored = isMirrored;
+		objsav.zoom = objsrc.zoom;
+		objsav.mirrored = is_mirrored;
 		return is_texture_intact;
 	}
 
 	//
 	// Software mode below
 	//
-	ObjectCache &objsav = _G(objcache)[objid];
-	if ((!hardwareAccelerated) && (_G(gfxDriver)->HasAcceleratedTransform())) {
+
+	if ((!use_hw_transform) && (_G(gfxDriver)->HasAcceleratedTransform())) {
 		// They want to draw it in software mode with the D3D driver, so force a redraw
-		objsav.sppic = -389538;
+		objsav.sppic = INT32_MIN;
 	}
 
 	// If we have the image cached, use it
 	if ((objsav.image != nullptr) &&
-	        (objsav.sppic == obj.num) &&
+	        (objsav.sppic == specialpic) &&
 			(objsav.tintamnt == tint_level) &&
 			(objsav.tintlight == tint_light) &&
 			(objsav.tintr == tint_red) &&
 			(objsav.tintg == tint_green) &&
 			(objsav.tintb == tint_blue) &&
 			(objsav.lightlev == light_level) &&
-			(objsav.mirrored == isMirrored)) {
+			(objsav.zoom == objsrc.zoom) &&
+			(objsav.mirrored == is_mirrored)) {
 		// the image is the same, we can use it cached!
 		if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
 			(actsp.Bmp != nullptr))
 			return true;
 		// Check if the X & Y co-ords are the same, too -- if so, there
 		// is scope for further optimisations
-		if ((objsav.x == obj.x) &&
-				(objsav.y == obj.y) &&
+		if (optimize_by_position &&
+			(objsav.x == objsrc.x) &&
+				(objsav.y == objsrc.y) &&
 				(actsp.Bmp != nullptr) &&
 				(_G(walk_behind_baselines_changed) == 0))
 			return true;
@@ -1274,49 +1276,76 @@ bool construct_object_gfx(int objid, bool alwaysUseSoftware) {
 
 	// Not cached, so draw the image
 
-	bool actspsUsed = false;
-	if (!hardwareAccelerated) {
+	bool actsps_used = false;
+	if (!use_hw_transform) {
 		// draw the base sprite, scaled and flipped as appropriate
-		actspsUsed = scale_and_flip_sprite(useindx, obj.num, obj.last_width, obj.last_height, isMirrored);
+		actsps_used = scale_and_flip_sprite(useindx, pic, scale_size.Width, scale_size.Height, is_mirrored);
 	}
-	if (!actspsUsed) {
+	if (!actsps_used) {
+		// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
 		recycle_bitmap(actsp.Bmp, coldept, src_sprwidth, src_sprheight);
 	}
 
-	// direct read from source bitmap, where possible
-	Bitmap *comeFrom = nullptr;
-	if (!actspsUsed)
-		comeFrom = _GP(spriteset)[obj.num];
-
 	// apply tints or lightenings where appropriate, else just copy
 	// the source bitmap
-	if (!hardwareAccelerated && ((tint_level > 0) || (light_level != 0))) {
+	if (!use_hw_transform && ((tint_level > 0) || (light_level != 0))) {
+		// direct read from source bitmap, where possible
+		Bitmap *blit_from = nullptr;
+		if (!actsps_used)
+			blit_from = _GP(spriteset)[pic];
+
 		apply_tint_or_light(useindx, light_level, tint_level, tint_red,
 			tint_green, tint_blue, tint_light, coldept,
-			comeFrom);
-	} else if (!actspsUsed) {
-		actsp.Bmp->Blit(_GP(spriteset)[obj.num], 0, 0);
+			blit_from);
+	} else if (!actsps_used) {
+		// no scaling, flipping or tinting was done, so just blit it normally
+		actsp.Bmp->Blit(_GP(spriteset)[pic], 0, 0);
 	}
 
 	// Create the cached image and store it
+	objsav.in_use = true;
 	recycle_bitmap(objsav.image, actsp.Bmp->GetColorDepth(), actsp.Bmp->GetWidth(), actsp.Bmp->GetHeight());
 	objsav.image->Blit(actsp.Bmp.get(), 0, 0);
-	objsav.sppic = obj.num;
+	objsav.sppic = specialpic;
 	objsav.tintamnt = tint_level;
 	objsav.tintr = tint_red;
 	objsav.tintg = tint_green;
 	objsav.tintb = tint_blue;
 	objsav.tintlight = tint_light;
 	objsav.lightlev = light_level;
-	objsav.mirrored = isMirrored;
+	objsav.zoom = objsrc.zoom;
+	objsav.mirrored = is_mirrored;
 	return false; // image was modified
 }
 
+bool construct_object_gfx(int objid, bool force_software) {
+	const RoomObject &obj = _G(objs)[objid];
+	if (_GP(spriteset)[obj.num] == nullptr)
+		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
+
+	ObjectCache objsrc(obj.num, obj.tint_r, obj.tint_g, obj.tint_b,
+					   obj.tint_level, obj.tint_light, 0 /* skip */, obj.zoom, false /* skip */,
+					   obj.x, obj.y);
+
+	return construct_object_gfx(
+		(obj.view != UINT16_MAX) ? &_GP(views)[obj.view].loops[obj.loop].frames[obj.frame] : nullptr,
+		obj.num,
+		Size(obj.last_width, obj.last_height),
+		obj.flags & OBJF_TINTLIGHTMASK,
+		objsrc,
+		_G(objcache)[objid],
+		objid,
+		true,
+		force_software);
+}
+
 // This is only called from draw_screen_background, but it's separated
 // to help with profiling the program
 void prepare_objects_for_drawing() {
 	_G(our_eip) = 32;
 
+	const bool hw_accel = _G(gfxDriver)->HasAcceleratedTransform();
+
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		const RoomObject &obj = _G(objs)[objid];
 		if (obj.on != 1) continue;
@@ -1351,7 +1380,7 @@ void prepare_objects_for_drawing() {
 			sync_object_texture(actsp, (_GP(game).SpriteInfos[obj.num].Flags & SPF_ALPHACHANNEL) != 0);
 		}
 
-		if (_G(gfxDriver)->HasAcceleratedTransform()) {
+		if (hw_accel) {
 			actsp.Ddb->SetFlippedLeftRight(objsav.mirrored);
 			actsp.Ddb->SetStretch(obj.last_width, obj.last_height);
 			actsp.Ddb->SetTint(objsav.tintr, objsav.tintg, objsav.tintb, (objsav.tintamnt * 256) / 100);
@@ -1424,11 +1453,35 @@ void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light
 	}
 }
 
+bool construct_char_gfx(int charid, bool force_software) {
+	// const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
+
+	const CharacterInfo &chin = _GP(game).chars[charid];
+	const CharacterExtras &chex = _GP(charextra)[charid];
+	const ViewFrame *vf = &_GP(views)[chin.view].loops[chin.loop].frames[chin.frame];
+	const int pic = vf->pic;
+	if (_GP(spriteset)[pic] == nullptr)
+		quitprintf("There was an error drawing character %d. Its current frame's sprite, %d, is invalid.", charid, pic);
 
+	ObjectCache chsrc(pic, chex.tint_r, chex.tint_g, chex.tint_b,
+					  chex.tint_level, chex.tint_light, 0 /* skip */, chex.zoom, false /* skip */,
+					  chin.x, chin.y);
 
+	return construct_object_gfx(
+		vf,
+		pic,
+		Size(chex.width, chex.height),
+		CharFlagsToObjFlags(chin.flags) & OBJF_TINTLIGHTMASK,
+		chsrc,
+		_GP(charcache)[charid],
+		charid + ACTSP_OBJSOFF, // actsps array index
+		false,                  // characters cannot optimize by pos, probably because of z coord and view offsets (?)
+		force_software);
+}
 
 void prepare_characters_for_drawing() {
 	_G(our_eip) = 33;
+	const bool hw_accel = _G(gfxDriver)->HasAcceleratedTransform();
 
 	// draw characters
 	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
@@ -1439,133 +1492,22 @@ void prepare_characters_for_drawing() {
 			continue;
 		_G(eip_guinum) = charid;
 
-		const CharacterExtras &chex = _GP(charextra)[charid];
-
-		_G(our_eip) = 330;
-
-		int tint_red = 0, tint_green = 0, tint_blue = 0, tint_amount = 0;
-		int tint_light = 0, light_level = 0;
-
-		if (chin.flags & CHF_HASTINT) {
-			// object specific tint, use it
-			tint_red = chex.tint_r;
-			tint_green = chex.tint_g;
-			tint_blue = chex.tint_b;
-			tint_amount = chex.tint_level;
-			tint_light = chex.tint_light;
-			light_level = 0;
-		} else if (chin.flags & CHF_HASLIGHT) {
-			light_level = chex.tint_light;
-		} else {
-			get_local_tint(chin.x, chin.y, chin.flags & CHF_NOLIGHTING,
-			               &tint_amount, &tint_red, &tint_green, &tint_blue,
-			               &tint_light, &light_level);
-		}
-
-		_G(our_eip) = 3330;
-		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
-		bool isMirrored = false;
-		int specialpic = sppic;
-		bool usingCachedImage = false;
-
-		const int coldept = _GP(spriteset)[sppic]->GetColorDepth();
-
-		// adjust the sppic if mirrored, so it doesn't accidentally
-		// cache the mirrored frame as the real one
-		if (_GP(views)[chin.view].loops[chin.loop].frames[chin.frame].flags & VFLG_FLIPSPRITE) {
-			isMirrored = true;
-			specialpic = -sppic;
-		}
-
-		_G(our_eip) = 3331;
+		bool usingCachedImage = construct_char_gfx(charid, false);
 
 		const int useindx = charid + ACTSP_OBJSOFF; // actsps array index
+		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
 		auto &actsp = _GP(actsps)[useindx];
-		actsp.SpriteID = sppic; // for texture sharing
+		const CharacterExtras &chex = _GP(charextra)[charid];
 
+		// update the cache for next time
 		ObjectCache &chsav = _GP(charcache)[charid];
-		// if the character was the same sprite and scaling last time,
-		// just use the cached image
-		if ((chsav.in_use) &&
-			(chsav.sppic == specialpic) &&
-			(chsav.zoom == chex.zoom) &&
-			(chsav.tintr == tint_red) &&
-			(chsav.tintg == tint_green) &&
-			(chsav.tintb == tint_blue) &&
-			(chsav.tintamnt == tint_amount) &&
-			(chsav.tintlight == tint_light) &&
-			(chsav.lightlev == light_level)) {
-			if (_G(walkBehindMethod) == DrawOverCharSprite) {
-				recycle_bitmap(actsp.Bmp, chsav.image->GetColorDepth(), chsav.image->GetWidth(), chsav.image->GetHeight());
-				actsp.Bmp->Blit(chsav.image.get(), 0, 0);
-			} else {
-				usingCachedImage = true;
-			}
-		} else if ((chsav.in_use) &&
-			(chsav.sppic == specialpic) &&
-			(_G(gfxDriver)->HasAcceleratedTransform())) {
-			usingCachedImage = true;
-		} else if (chsav.in_use) {
-			chsav.in_use = false;
-		}
-
-		chsav.zoom = chex.zoom;
-		chsav.sppic = specialpic;
-		chsav.tintr = tint_red;
-		chsav.tintg = tint_green;
-		chsav.tintb = tint_blue;
-		chsav.tintamnt = tint_amount;
-		chsav.tintlight = tint_light;
-		chsav.lightlev = light_level;
-
-		// If cache needs to be re-drawn
-		// NOTE: we need cached bitmap if:
-		// * it's a software renderer, otherwise
-		// * the walk-behind method is DrawOverCharSprite
-		if (((!_G(gfxDriver)->HasAcceleratedTransform()) || (_G(walkBehindMethod) == DrawOverCharSprite)) && !chsav.in_use) {
-			// create the base sprite in _GP(actsps)[useindx], which will
-			// be scaled and/or flipped, as appropriate
-			bool actspsUsed = false;
-			if (!_G(gfxDriver)->HasAcceleratedTransform()) {
-				actspsUsed = scale_and_flip_sprite(useindx, sppic, chex.width, chex.height, isMirrored);
-			}
-			if (!actspsUsed) {
-				// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
-				recycle_bitmap(actsp.Bmp, coldept, _GP(game).SpriteInfos[sppic].Width, _GP(game).SpriteInfos[sppic].Height);
-			}
-
-			_G(our_eip) = 335;
-
-			if (((light_level != 0) || (tint_amount != 0)) &&
-			        (!_G(gfxDriver)->HasAcceleratedTransform())) {
-				// apply the lightning or tinting
-				Bitmap *comeFrom = nullptr;
-				// if possible, direct read from the source image
-				if (!actspsUsed)
-					comeFrom = _GP(spriteset)[sppic];
-
-				apply_tint_or_light(useindx, light_level, tint_amount, tint_red,
-				                    tint_green, tint_blue, tint_light, coldept,
-				                    comeFrom);
-			} else if (!actspsUsed) {
-				// no scaling, flipping or tinting was done, so just blit it normally
-				actsp.Bmp->Blit(_GP(spriteset)[sppic], 0, 0);
-			}
-
-			// update the character cache with the new image
-			chsav.in_use = true;
-			recycle_bitmap(chsav.image, coldept, actsp.Bmp->GetWidth(), actsp.Bmp->GetHeight());
-			chsav.image->Blit(actsp.Bmp.get(), 0, 0);
-
-		} // end if !cache.in_use
-
-		int usebasel = chin.get_baseline();
 
 		_G(our_eip) = 336;
 
 		const int bgX = chin.actx + chin.pic_xoffs;
 		const int bgY = chin.acty + chin.pic_yoffs;
 
+		int usebasel = chin.get_baseline();
 		if (chin.flags & CHF_NOWALKBEHINDS) {
 			// ignore walk-behinds, do nothing
 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
@@ -1579,20 +1521,20 @@ void prepare_characters_for_drawing() {
 			sync_object_texture(actsp, (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0);
 		}
 
-		if (_G(gfxDriver)->HasAcceleratedTransform()) {
+		if (hw_accel) {
 			actsp.Ddb->SetStretch(chex.width, chex.height);
-			actsp.Ddb->SetFlippedLeftRight(isMirrored);
-			actsp.Ddb->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100);
+			actsp.Ddb->SetFlippedLeftRight(chsav.mirrored);
+			actsp.Ddb->SetTint(chsav.tintr, chsav.tintg, chsav.tintb, (chsav.tintamnt * 256) / 100);
 
-			if (tint_amount != 0) {
-				if (tint_light == 0) // tint with 0 luminance, pass as 1 instead
+			if (chsav.tintamnt != 0) {
+				if (chsav.tintlight == 0) // tint with 0 luminance, pass as 1 instead
 					actsp.Ddb->SetLightLevel(1);
-				else if (tint_light < 250)
-					actsp.Ddb->SetLightLevel(tint_light);
+				else if (chsav.tintlight < 250)
+					actsp.Ddb->SetLightLevel(chsav.tintlight);
 				else
 					actsp.Ddb->SetLightLevel(0);
-			} else if (light_level != 0)
-				actsp.Ddb->SetLightLevel((light_level * 25) / 10 + 256);
+			} else if (chsav.lightlev != 0)
+				actsp.Ddb->SetLightLevel((chsav.lightlev * 25) / 10 + 256);
 			else
 				actsp.Ddb->SetLightLevel(0);
 
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index f4f6d56b820..218e0ee7677 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -90,12 +90,18 @@ struct ObjTexture {
 // if active sprite / texture should be reconstructed
 struct ObjectCache {
 	std::unique_ptr<AGS::Shared::Bitmap> image;
-	bool  in_use = false;
+	bool  in_use = false;  // CHECKME: possibly may be removed
 	int   sppic = 0;
 	short tintr = 0, tintg = 0, tintb = 0, tintamnt = 0, tintlight = 0;
 	short lightlev = 0, zoom = 0;
 	bool  mirrored = 0;
 	int   x = 0, y = 0;
+
+	ObjectCache() = default;
+	ObjectCache(int pic_, int tintr_, int tintg_, int tintb_, int tint_amnt_, int tint_light_,
+				int light_, int zoom_, bool mirror_, int posx_, int posy_)
+		: sppic(pic_), tintr(tintr_), tintg(tintg_), tintb(tintb_), tintamnt(tint_amnt_), tintlight(tint_light_)
+		, lightlev(light_), zoom(zoom_), mirrored(mirror_), x(posx_), y(posy_) {}
 };
 
 // Converts AGS color index to the actual bitmap color using game's color depth
diff --git a/engines/ags/plugins/ags_plugin.h b/engines/ags/plugins/ags_plugin.h
index 124392978c5..62be0abee94 100644
--- a/engines/ags/plugins/ags_plugin.h
+++ b/engines/ags/plugins/ags_plugin.h
@@ -159,8 +159,8 @@ struct AGSCharacter {
 };
 
 // AGSObject.flags
-#define OBJF_NOINTERACT 1     // not clickable
-#define OBJF_NOWALKBEHINDS 2  // ignore walk-behinds
+#define OBJF_NOINTERACT 0x01     // not clickable
+#define OBJF_NOWALKBEHINDS 0x02  // ignore walk-behinds
 
 struct AGSObject {
 	int32 x = 0, y = 0;
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 976a376a4a4..dbafcd1dded 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -26,6 +26,7 @@
 #include "ags/shared/ac/common_defines.h" // constants
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/core/types.h"
+#include "ags/shared/util/bbop.h"
 
 namespace AGS3 {
 
@@ -69,6 +70,18 @@ using namespace AGS; // FIXME later
 #define CHANIM_REPEAT       0x02
 #define CHANIM_BACKWARDS    0x04
 
+// Converts character flags (CHF_*) to matching RoomObject flags (OBJF_*)
+inline int CharFlagsToObjFlags(int chflags) {
+	using namespace AGS::Shared;
+	return FlagToFlag(chflags, CHF_NOINTERACT, OBJF_NOINTERACT) |
+		   FlagToFlag(chflags, CHF_NOWALKBEHINDS, OBJF_NOWALKBEHINDS) |
+		   FlagToFlag(chflags, CHF_HASTINT, OBJF_HASTINT) |
+		   FlagToFlag(NegateFlag(chflags, CHF_NOLIGHTING), CHF_NOLIGHTING, OBJF_USEREGIONTINTS) |
+		   FlagToFlag(NegateFlag(chflags, CHF_MANUALSCALING), CHF_MANUALSCALING, OBJF_USEROOMSCALING) |
+		   FlagToFlag(NegateFlag(chflags, CHF_NOBLOCKING), CHF_NOBLOCKING, OBJF_SOLID) |
+		   FlagToFlag(chflags, CHF_HASLIGHT, OBJF_HASLIGHT);
+}
+
 // Length of deprecated character name field, in bytes
 #define MAX_CHAR_NAME_LEN 40
 
diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h
index 2a8f6c421a3..2181244565e 100644
--- a/engines/ags/shared/ac/common_defines.h
+++ b/engines/ags/shared/ac/common_defines.h
@@ -107,14 +107,15 @@ namespace AGS3 {
 #define HIRES_COORD_MULTIPLIER 2
 
 // Room object flags (currently limited by a byte)
-#define OBJF_NOINTERACT        1  // not clickable
-#define OBJF_NOWALKBEHINDS     2  // ignore walk-behinds
-#define OBJF_HASTINT           4  // the tint_* members are valid
-#define OBJF_USEREGIONTINTS    8  // obey region tints/light areas
+#define OBJF_NOINTERACT     0x01  // not clickable
+#define OBJF_NOWALKBEHINDS  0x02  // ignore walk-behinds
+#define OBJF_HASTINT        0x04  // the tint_* members are valid
+#define OBJF_USEREGIONTINTS 0x08  // obey region tints/light areas
 #define OBJF_USEROOMSCALING 0x10  // obey room scaling areas
 #define OBJF_SOLID          0x20  // blocks characters from moving
 #define OBJF_LEGACY_LOCKED  0x40  // object position is locked in the editor (OBSOLETE since 3.5.0)
 #define OBJF_HASLIGHT       0x80  // the tint_light is valid and treated as brightness
+#define OBJF_TINTLIGHTMASK (OBJF_HASTINT | OBJF_HASLIGHT | OBJF_USEREGIONTINTS)
 
 // Animation flow mode
 // NOTE: had to move to common_defines, because used by CharacterInfo
diff --git a/engines/ags/shared/util/bbop.h b/engines/ags/shared/util/bbop.h
index f24afc11831..7b4f76c237a 100644
--- a/engines/ags/shared/util/bbop.h
+++ b/engines/ags/shared/util/bbop.h
@@ -50,6 +50,28 @@ enum DataEndianess {
 	#endif
 };
 
+
+//
+// Various bit flags operations
+//
+// Negates only the given bit flags in the value
+inline int NegateFlag(int value, int flag) {
+	return value & (~flag);
+}
+// Converts a bit flag value into the lowest bit (1 or 0)
+inline int FlagToOne(int value, int flag) {
+	return ((value & flag) >> flag);
+}
+// Converts a lowest bit (1 or 0) into a bit flag value
+inline int OneToFlag(int value, int flag) {
+	return ((value & 0x1) << flag);
+}
+// Converts from one flag into another
+inline int FlagToFlag(int value, int flag1, int flag2) {
+	return OneToFlag(FlagToOne(value, flag1), flag2);
+}
+
+
 namespace BitByteOperations {
 inline int16_t SwapBytesInt16(const int16_t val) {
 	return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);


Commit: 582a5ba4227a9577840379853504ac3ce721a355
    https://github.com/scummvm/scummvm/commit/582a5ba4227a9577840379853504ac3ce721a355
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: pass ObjTexture directly into obj gfx generating funcs

>From upstream 948837e7057cf4b41fc04c5684841b8d0c920d95

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 2f7b457ed4e..99ed1b7920e 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1039,10 +1039,9 @@ void get_local_tint(int xpp, int ypp, int nolight,
 
 
 
-// Applies the specified RGB Tint or Light Level to the actsps
-// sprite indexed with actspsindex.
+// Applies the specified RGB Tint or Light Level to the ObjTexture 'actsp'.
 // Used for software render mode only.
-static void apply_tint_or_light(int actspsindex, int light_level,
+static void apply_tint_or_light(ObjTexture &actsp, int light_level,
 								int tint_amount, int tint_red, int tint_green,
 								int tint_blue, int tint_light, int coldept,
 								Bitmap *blitFrom) {
@@ -1054,7 +1053,6 @@ static void apply_tint_or_light(int actspsindex, int light_level,
 			return;
 	}
 
-	auto &actsp = _GP(actsps)[actspsindex];
 	// we can only do tint/light if the colour depths match
 	if (_GP(game).GetColorDepth() == actsp.Bmp->GetColorDepth()) {
 		std::unique_ptr<Bitmap> oldwas;
@@ -1151,15 +1149,15 @@ static Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, std::unique_ptr
 	return dst.get(); // return transformed result
 }
 
-// Draws the specified 'sppic' sprite onto _GP(actsps)[useindx] at the
+// Draws the specified 'sppic' sprite onto ObjTexture 'actsp' at the
 // specified width and height, and flips the sprite if necessary.
 // Returns 1 if something was drawn to actsps; returns 0 if no
 // scaling or stretching was required, in which case nothing was done.
 // Used for software render mode only.
-static bool scale_and_flip_sprite(int useindx, int sppic, int width, int height, bool hmirror) {
+static bool scale_and_flip_sprite(ObjTexture &actsp, int sppic, int width, int height, bool hmirror) {
 	Bitmap *src = _GP(spriteset)[sppic];
 	Bitmap *result = transform_sprite(src, (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0,
-		_GP(actsps)[useindx].Bmp, Size(width, height), hmirror ? kFlip_Horizontal : kFlip_None);
+		actsp.Bmp, Size(width, height), hmirror ? kFlip_Horizontal : kFlip_None);
 	return result != src;
 }
 
@@ -1175,7 +1173,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 								 int tint_flags,            // OBJF_* flags related to using tint and light fx
 								 const ObjectCache &objsrc, // source item to acquire values from
 								 ObjectCache &objsav,       // cache item to use
-								 int actsps_index,          // actsps array item to use // FIXME: pass ObjectTexture instead?
+								 ObjTexture &actsp,			// object texture to draw upon
 								 bool optimize_by_position, // allow to optimize walk-behind merging using object's pos
 								 bool force_software) {
 	const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
@@ -1215,8 +1213,6 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 		specialpic = -pic;
 	}
 
-	const int useindx = actsps_index; // objid; // actsps array index
-	auto &actsp = _GP(actsps)[useindx];
 	actsp.SpriteID = pic; // for texture sharing
 
 	// NOTE: we need cached bitmap if:
@@ -1279,7 +1275,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	bool actsps_used = false;
 	if (!use_hw_transform) {
 		// draw the base sprite, scaled and flipped as appropriate
-		actsps_used = scale_and_flip_sprite(useindx, pic, scale_size.Width, scale_size.Height, is_mirrored);
+		actsps_used = scale_and_flip_sprite(actsp, pic, scale_size.Width, scale_size.Height, is_mirrored);
 	}
 	if (!actsps_used) {
 		// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
@@ -1294,7 +1290,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 		if (!actsps_used)
 			blit_from = _GP(spriteset)[pic];
 
-		apply_tint_or_light(useindx, light_level, tint_level, tint_red,
+		apply_tint_or_light(actsp, light_level, tint_level, tint_red,
 			tint_green, tint_blue, tint_light, coldept,
 			blit_from);
 	} else if (!actsps_used) {
@@ -1334,7 +1330,7 @@ bool construct_object_gfx(int objid, bool force_software) {
 		obj.flags & OBJF_TINTLIGHTMASK,
 		objsrc,
 		_G(objcache)[objid],
-		objid,
+		_GP(actsps)[objid],
 		true,
 		force_software);
 }
@@ -1474,7 +1470,7 @@ bool construct_char_gfx(int charid, bool force_software) {
 		CharFlagsToObjFlags(chin.flags) & OBJF_TINTLIGHTMASK,
 		chsrc,
 		_GP(charcache)[charid],
-		charid + ACTSP_OBJSOFF, // actsps array index
+		_GP(actsps)[charid + ACTSP_OBJSOFF],
 		false,                  // characters cannot optimize by pos, probably because of z coord and view offsets (?)
 		force_software);
 }


Commit: 49fb8efa683fa8231776d1754a902e83fd2bebbc
    https://github.com/scummvm/scummvm/commit/49fb8efa683fa8231776d1754a902e83fd2bebbc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: character and room object share function preparing texture

>From upstream f1248c19ef988eb8b2c2f19f5a8b1465406b382f

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 99ed1b7920e..270b5ed3fdf 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1311,9 +1311,62 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	objsav.lightlev = light_level;
 	objsav.zoom = objsrc.zoom;
 	objsav.mirrored = is_mirrored;
+	objsav.x = objsrc.x;
+	objsav.y = objsrc.y;
 	return false; // image was modified
 }
 
+// Generate object's raw sprite bitmap, update the object's texture
+// from the sprite, add the object's texture to the draw list.
+// - atx and aty are coordinates of the top-left object's corner in the room;
+// - usebasel is object's z-order, it may be modified within the function;
+// TODO: possibly makes sense to split this function into parts later.
+void prepare_and_add_object_gfx(const ObjectCache &objsav, ObjTexture &actsp, bool actsp_modified, const Size &scale_size,
+								int atx, int aty, int &usebasel, bool use_walkbehinds, int transparency, bool hw_accel) {
+	// Handle the walk-behinds, according to the walkBehindMethod.
+	// This potentially may edit actsp's raw bitmap if actsp_modified is set.
+	if (use_walkbehinds) {
+		// Only merge sprite with the walk-behinds in software mode
+		if ((_G(walkBehindMethod) == DrawOverCharSprite) && (actsp_modified)) {
+			walkbehinds_cropout(actsp.Bmp.get(), atx, aty, usebasel);
+		}
+	} else {
+		// Ignore walk-behinds by shifting baseline to a larger value
+		// CHECKME: may this fail if WB somehow got larger than room baseline?
+		if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
+			usebasel += _GP(thisroom).Height;
+		}
+	}
+
+	// Sync object texture with the raw sprite bitmap.
+	if ((actsp.Ddb == nullptr) || (actsp_modified)) {
+		sync_object_texture(actsp, (_GP(game).SpriteInfos[actsp.SpriteID].Flags & SPF_ALPHACHANNEL) != 0);
+	}
+
+	// Now when we have a ready texture, assign texture properties
+	// (transform, effects, and so forth)
+	if (hw_accel) {
+		actsp.Ddb->SetStretch(scale_size.Width, scale_size.Height);
+		actsp.Ddb->SetFlippedLeftRight(objsav.mirrored);
+		actsp.Ddb->SetTint(objsav.tintr, objsav.tintg, objsav.tintb, (objsav.tintamnt * 256) / 100);
+
+		if (objsav.tintamnt > 0) {
+			if (objsav.tintlight == 0) // luminance of 0 -- pass 1 to enable
+				actsp.Ddb->SetLightLevel(1);
+			else if (objsav.tintlight < 250)
+				actsp.Ddb->SetLightLevel(objsav.tintlight);
+			else
+				actsp.Ddb->SetLightLevel(0);
+		} else if (objsav.lightlev != 0)
+			actsp.Ddb->SetLightLevel((objsav.lightlev * 25) / 10 + 256);
+		else
+			actsp.Ddb->SetLightLevel(0);
+	}
+
+	actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(transparency));
+}
+
+// Generates RoomObject's raw bitmap and saves in actsps; updates object cache.
 bool construct_object_gfx(int objid, bool force_software) {
 	const RoomObject &obj = _G(objs)[objid];
 	if (_GP(spriteset)[obj.num] == nullptr)
@@ -1335,8 +1388,6 @@ bool construct_object_gfx(int objid, bool force_software) {
 		force_software);
 }
 
-// This is only called from draw_screen_background, but it's separated
-// to help with profiling the program
 void prepare_objects_for_drawing() {
 	_G(our_eip) = 32;
 
@@ -1344,63 +1395,32 @@ void prepare_objects_for_drawing() {
 
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		const RoomObject &obj = _G(objs)[objid];
-		if (obj.on != 1) continue;
+		if (obj.on != 1) // WARNING: 'on' may have other values than 0 and 1 !!
+			continue;    // disabled
 		// offscreen, don't draw
 		if ((obj.x >= _GP(thisroom).Width) || (obj.y < 1))
-			continue;
-
-		bool actspsIntact = construct_object_gfx(objid, false);
-
-		const int useindx = objid; // actsps array index
-		auto &actsp = _GP(actsps)[useindx];
+			continue; // offscreen
 
-		// update the cache for next time
-		ObjectCache &objsav = _G(objcache)[objid];
-		objsav.x = obj.x;
-		objsav.y = obj.y;
-		const int atxp = data_to_game_coord(obj.x);
-		const int atyp = data_to_game_coord(obj.y) - obj.last_height;
+		_G(eip_guinum) = objid;
+		const ObjectCache &objsav = _G(objcache)[objid];
+		ObjTexture &actsp = _GP(actsps)[objid];
 
+		// Calculate sprite top-left position in the room and baseline
+		const int atx = data_to_game_coord(obj.x);
+		const int aty = data_to_game_coord(obj.y) - obj.last_height;
 		int usebasel = obj.get_baseline();
 
-		if (obj.flags & OBJF_NOWALKBEHINDS) {
-			// ignore walk-behinds, do nothing
-			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
-				usebasel += _GP(thisroom).Height;
-			}
-		} else if ((!actspsIntact) && (_G(walkBehindMethod) == DrawOverCharSprite)) {
-			walkbehinds_cropout(actsp.Bmp.get(), atxp, atyp, usebasel);
-		}
-
-		if ((!actspsIntact) || (actsp.Ddb == nullptr)) {
-			sync_object_texture(actsp, (_GP(game).SpriteInfos[obj.num].Flags & SPF_ALPHACHANNEL) != 0);
-		}
-
-		if (hw_accel) {
-			actsp.Ddb->SetFlippedLeftRight(objsav.mirrored);
-			actsp.Ddb->SetStretch(obj.last_width, obj.last_height);
-			actsp.Ddb->SetTint(objsav.tintr, objsav.tintg, objsav.tintb, (objsav.tintamnt * 256) / 100);
-
-			if (objsav.tintamnt > 0) {
-				if (objsav.tintlight == 0)  // luminance of 0 -- pass 1 to enable
-					actsp.Ddb->SetLightLevel(1);
-				else if (objsav.tintlight < 250)
-					actsp.Ddb->SetLightLevel(objsav.tintlight);
-				else
-					actsp.Ddb->SetLightLevel(0);
-			} else if (objsav.lightlev != 0)
-				actsp.Ddb->SetLightLevel((objsav.lightlev * 25) / 10 + 256);
-			else
-				actsp.Ddb->SetLightLevel(0);
-		}
-
-		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(obj.transparent));
-		add_to_sprite_list(actsp.Ddb, atxp, atyp, usebasel, false);
+		// Generate raw bitmap in ObjTexture and store parameters in ObjectCache.
+		bool actsp_modified = !construct_object_gfx(objid, false);
+		// Prepare the object texture
+		prepare_and_add_object_gfx(objsav, actsp, actsp_modified,
+								   Size(obj.last_width, obj.last_height), atx, aty, usebasel,
+								   (obj.flags & OBJF_NOWALKBEHINDS) == 0, obj.transparent, hw_accel);
+		// Finally, add the texture to the draw list
+		add_to_sprite_list(actsp.Ddb, atx, aty, usebasel, false);
 	}
 }
 
-
-
 // Draws srcimg onto destimg, tinting to the specified level
 // Totally overwrites the contents of the destination image
 void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light_level, int luminance) {
@@ -1449,6 +1469,7 @@ void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light
 	}
 }
 
+// Generates Character's raw bitmap and saves in actsps; updates character cache.
 bool construct_char_gfx(int charid, bool force_software) {
 	// const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
 
@@ -1483,63 +1504,28 @@ void prepare_characters_for_drawing() {
 	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
 		const CharacterInfo &chin = _GP(game).chars[charid];
 		if (chin.on == 0)
-			continue;
+			continue;  // disabled
 		if (chin.room != _G(displayed_room))
-			continue;
-		_G(eip_guinum) = charid;
-
-		bool usingCachedImage = construct_char_gfx(charid, false);
+			continue;  // in another room
 
-		const int useindx = charid + ACTSP_OBJSOFF; // actsps array index
-		const int sppic = _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic;
-		auto &actsp = _GP(actsps)[useindx];
+		_G(eip_guinum) = charid;
 		const CharacterExtras &chex = _GP(charextra)[charid];
+		const ObjectCache &chsav = _GP(charcache)[charid];
+		ObjTexture &actsp = _GP(actsps)[charid + ACTSP_OBJSOFF];
 
-		// update the cache for next time
-		ObjectCache &chsav = _GP(charcache)[charid];
-
-		_G(our_eip) = 336;
-
-		const int bgX = chin.actx + chin.pic_xoffs;
-		const int bgY = chin.acty + chin.pic_yoffs;
-
+		// Calculate sprite top-left position in the room and baseline
+		const int atx = chin.actx + chin.pic_xoffs;
+		const int aty = chin.acty + chin.pic_yoffs;
 		int usebasel = chin.get_baseline();
-		if (chin.flags & CHF_NOWALKBEHINDS) {
-			// ignore walk-behinds, do nothing
-			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
-				usebasel += _GP(thisroom).Height;
-			}
-		} else if (_G(walkBehindMethod) == DrawOverCharSprite) {
-			walkbehinds_cropout(actsp.Bmp.get(), bgX, bgY, usebasel);
-		}
-
-		if ((!usingCachedImage) || (actsp.Ddb == nullptr)) {
-			sync_object_texture(actsp, (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0);
-		}
-
-		if (hw_accel) {
-			actsp.Ddb->SetStretch(chex.width, chex.height);
-			actsp.Ddb->SetFlippedLeftRight(chsav.mirrored);
-			actsp.Ddb->SetTint(chsav.tintr, chsav.tintg, chsav.tintb, (chsav.tintamnt * 256) / 100);
-
-			if (chsav.tintamnt != 0) {
-				if (chsav.tintlight == 0) // tint with 0 luminance, pass as 1 instead
-					actsp.Ddb->SetLightLevel(1);
-				else if (chsav.tintlight < 250)
-					actsp.Ddb->SetLightLevel(chsav.tintlight);
-				else
-					actsp.Ddb->SetLightLevel(0);
-			} else if (chsav.lightlev != 0)
-				actsp.Ddb->SetLightLevel((chsav.lightlev * 25) / 10 + 256);
-			else
-				actsp.Ddb->SetLightLevel(0);
-
-		}
-
-		_G(our_eip) = 337;
 
-		actsp.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(chin.transparency));
-		add_to_sprite_list(actsp.Ddb, bgX, bgY, usebasel, false);
+        // Generate raw bitmap in ObjTexture and store parameters in ObjectCache.
+        bool actsp_modified = !construct_char_gfx(charid, false);
+        // Prepare the object texture
+        prepare_and_add_object_gfx(chsav, actsp, actsp_modified,
+            Size(chex.width, chex.height), atx, aty, usebasel,
+            (chin.flags & CHF_NOWALKBEHINDS) == 0, chin.transparency, hw_accel);
+        // Finally, add the texture to the draw list
+        add_to_sprite_list(actsp.Ddb, atx, aty, usebasel, false);
 	}
 }
 
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 218e0ee7677..3587f1529f8 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -201,9 +201,10 @@ void putpixel_compensate(Shared::Bitmap *g, int xx, int yy, int col);
 // so they only detect whether the sprite ID itself has changed.
 // Software renderers modify the cached bitmap whenever any visual
 // effect changes (scaling, tint, etc).
-// * alwaysUseSoftware option forces HW renderers to  construct the image
+// * force_software option forces HW renderers to  construct the image
 // in software mode as well.
-bool construct_object_gfx(int objid, bool alwaysUseSoftware);
+bool construct_object_gfx(int objid, bool force_software);
+bool construct_char_gfx(int charid, bool force_software);
 // Returns a cached character image prepared for the render
 Shared::Bitmap *get_cached_character_image(int charid);
 // Returns a cached object image prepared for the render


Commit: 54154bfd350062a6c47bc391bf412671519545fd
    https://github.com/scummvm/scummvm/commit/54154bfd350062a6c47bc391bf412671519545fd
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed FlagToFlag() and CharFlagsToObjFlags()

>From upstream bde46e4815ea92161348995f195ca294f316ccc0

Changed paths:
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/util/bbop.h


diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index dbafcd1dded..ecd68430554 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -39,6 +39,7 @@ class Stream;
 using namespace AGS; // FIXME later
 
 #define MAX_INV             301
+// Character flags
 #define CHF_MANUALSCALING   1
 #define CHF_FIXVIEW         2     // between SetCharView and ReleaseCharView
 #define CHF_NOINTERACT      4
@@ -58,6 +59,7 @@ using namespace AGS; // FIXME later
 #define CHF_MOVENOTWALK     0x10000   // engine only - do not do walk anim
 #define CHF_ANTIGLIDE       0x20000
 #define CHF_HASLIGHT        0x40000
+#define CHF_TINTLIGHTMASK   (CHF_NOLIGHTING | CHF_HASTINT | CHF_HASLIGHT)
 // Speechcol is no longer part of the flags as of v2.5
 #define OCHF_SPEECHCOL      0xff000000
 #define OCHF_SPEECHCOLSHIFT 24
@@ -76,10 +78,11 @@ inline int CharFlagsToObjFlags(int chflags) {
 	return FlagToFlag(chflags, CHF_NOINTERACT, OBJF_NOINTERACT) |
 		   FlagToFlag(chflags, CHF_NOWALKBEHINDS, OBJF_NOWALKBEHINDS) |
 		   FlagToFlag(chflags, CHF_HASTINT, OBJF_HASTINT) |
-		   FlagToFlag(NegateFlag(chflags, CHF_NOLIGHTING), CHF_NOLIGHTING, OBJF_USEREGIONTINTS) |
-		   FlagToFlag(NegateFlag(chflags, CHF_MANUALSCALING), CHF_MANUALSCALING, OBJF_USEROOMSCALING) |
-		   FlagToFlag(NegateFlag(chflags, CHF_NOBLOCKING), CHF_NOBLOCKING, OBJF_SOLID) |
-		   FlagToFlag(chflags, CHF_HASLIGHT, OBJF_HASLIGHT);
+		   FlagToFlag(chflags, CHF_HASLIGHT, OBJF_HASLIGHT) |
+		   // following flags are inverse
+		   FlagToNoFlag(chflags, CHF_NOLIGHTING, OBJF_USEREGIONTINTS) |
+		   FlagToNoFlag(chflags, CHF_MANUALSCALING, OBJF_USEROOMSCALING) |
+		   FlagToNoFlag(chflags, CHF_NOBLOCKING, OBJF_SOLID);
 }
 
 // Length of deprecated character name field, in bytes
diff --git a/engines/ags/shared/util/bbop.h b/engines/ags/shared/util/bbop.h
index 7b4f76c237a..5ed820be52c 100644
--- a/engines/ags/shared/util/bbop.h
+++ b/engines/ags/shared/util/bbop.h
@@ -54,21 +54,15 @@ enum DataEndianess {
 //
 // Various bit flags operations
 //
-// Negates only the given bit flags in the value
-inline int NegateFlag(int value, int flag) {
-	return value & (~flag);
-}
-// Converts a bit flag value into the lowest bit (1 or 0)
-inline int FlagToOne(int value, int flag) {
-	return ((value & flag) >> flag);
-}
-// Converts a lowest bit (1 or 0) into a bit flag value
-inline int OneToFlag(int value, int flag) {
-	return ((value & 0x1) << flag);
-}
-// Converts from one flag into another
+// Converts from one flag into another:
+// sets flag2 if flag1 IS set
+// TODO: find more optimal way, using bitwise ops?
 inline int FlagToFlag(int value, int flag1, int flag2) {
-	return OneToFlag(FlagToOne(value, flag1), flag2);
+	return ((value & flag1) != 0) * flag2;
+}
+// Sets flag2 if flag1 is NOT set
+inline int FlagToNoFlag(int value, int flag1, int flag2) {
+	return ((value & flag1) == 0) * flag2;
 }
 
 


Commit: dc693a5cb46481d7928ddb6f368eb9bfb6873354
    https://github.com/scummvm/scummvm/commit/dc693a5cb46481d7928ddb6f368eb9bfb6873354
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed get_local_tint() was inverting meaning of "use tint" arg

>From upstream db3749a1bceb7d2b0ad3a5abeb3655e74f2f2ab6

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 270b5ed3fdf..0eb7addd851 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -957,7 +957,7 @@ void recycle_bitmap(std::unique_ptr<Shared::Bitmap> &bimp, int coldep, int wid,
 // room regions and SetAmbientTint
 // tint_amnt will be set to 0 if there is no tint enabled
 // if this is the case, then light_lev holds the light level (0=none)
-void get_local_tint(int xpp, int ypp, int nolight,
+void get_local_tint(int xpp, int ypp, bool use_region_tint,
                     int *tint_amnt, int *tint_r, int *tint_g,
                     int *tint_b, int *tint_lit,
                     int *light_lev) {
@@ -969,7 +969,7 @@ void get_local_tint(int xpp, int ypp, int nolight,
 	int tint_blue = 0;
 	int tint_light = 255;
 
-	if (nolight == 0) {
+	if (use_region_tint) {
 
 		int onRegion = 0;
 
@@ -1205,10 +1205,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// check whether the image should be flipped
 	bool is_mirrored = false;
 	int specialpic = pic;
-	if ( //(obj.view != (uint16_t)-1) &&
-		vf &&
-		(vf->pic == pic) &&
-		((vf->flags & VFLG_FLIPSPRITE) != 0)) {
+	if (vf && (vf->pic == pic) && ((vf->flags & VFLG_FLIPSPRITE) != 0)) {
 		is_mirrored = true;
 		specialpic = -pic;
 	}


Commit: 32c25115238d58acab643110aca7b18afd202eb4
    https://github.com/scummvm/scummvm/commit/32c25115238d58acab643110aca7b18afd202eb4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: a bool cast fix in construct_object_gfx()

>From upstream a9e3a802cc34f72e17b1cad502f9ae949f7a30d7

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 0eb7addd851..08968184ce9 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1197,7 +1197,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 		light_level = objsrc.tintlight;
 	} else {
 		// get the ambient or region tint
-		get_local_tint(objsrc.x, objsrc.y, (tint_flags & OBJF_USEREGIONTINTS),
+		get_local_tint(objsrc.x, objsrc.y, (tint_flags & OBJF_USEREGIONTINTS) == 0,
 		               &tint_level, &tint_red, &tint_green, &tint_blue,
 		               &tint_light, &light_level);
 	}


Commit: b58007875987a18db02b51db6a554f165ff05323
    https://github.com/scummvm/scummvm/commit/b58007875987a18db02b51db6a554f165ff05323
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented "after fade-out" room callback, and global event

>From upstream d3057943e5f979d9386c412d62e8e2e953a623c5

Changed paths:
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/event.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/screen.cpp


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 45cbe6ec8d0..14def7f39ea 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -30,6 +30,7 @@
 #include "ags/engine/ac/gui.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/screen.h"
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/main/game_run.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
@@ -193,6 +194,7 @@ void process_event(const EventHappened *evp) {
 		if ((evp->data1 == EVB_ROOM) && (evp->data3 == EVROM_BEFOREFADEIN))
 			_G(in_enters_screen)--;
 	} else if (evp->type == EV_FADEIN) {
+		debug_script_log("Transition-in in room %d", _G(displayed_room));
 		// if they change the transition type before the fadein, make
 		// sure the screen doesn't freeze up
 		_GP(play).screen_is_faded_out = 0;
diff --git a/engines/ags/engine/ac/event.h b/engines/ags/engine/ac/event.h
index 87966341c82..c201dbb2ee5 100644
--- a/engines/ags/engine/ac/event.h
+++ b/engines/ags/engine/ac/event.h
@@ -38,6 +38,7 @@ namespace AGS3 {
 #define GE_LOSE_INV      8
 #define GE_RESTORE_GAME  9
 #define GE_ENTER_ROOM_AFTERFADE 10
+#define GE_LEAVE_ROOM_AFTERFADE 11
 
 // Game event types:
 // common script callback
@@ -80,6 +81,10 @@ namespace AGS3 {
 #define EVROM_REPEXEC      6
 // after fade-in
 #define EVROM_AFTERFADEIN  7
+// leave room (before fade-out)
+#define EVROM_LEAVE        8
+// unload room; aka after fade-out
+#define EVROM_AFTERFADEOUT 9
 // Hotspot event types:
 // player stands on hotspot
 #define EVHOT_STANDSON  0
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 7193761c356..0e8d1e87637 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -235,10 +235,15 @@ void unload_old_room() {
 	if (_G(displayed_room) < 0)
 		return;
 
-	debug_script_log("Unloading room %d", _G(displayed_room));
-
 	current_fade_out_effect();
 
+	// room unloaded callback
+	run_room_event(EVROM_AFTERFADEOUT);
+	// global room unloaded event
+	run_on_event(GE_LEAVE_ROOM_AFTERFADE, RuntimeScriptValue().SetInt32(_G(displayed_room)));
+
+	debug_script_log("Unloading room %d", _G(displayed_room));
+
 	dispose_room_drawdata();
 
 	for (uint32_t ff = 0; ff < _G(croom)->numobj; ff++)
@@ -879,7 +884,7 @@ void new_room(int newnum, CharacterInfo *forchar) {
 	_G(in_leaves_screen) = newnum;
 
 	// player leaves screen event
-	run_room_event(8);
+	run_room_event(EVROM_LEAVE);
 	// Run the global OnRoomLeave event
 	run_on_event(GE_LEAVE_ROOM, RuntimeScriptValue().SetInt32(_G(displayed_room)));
 
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 582bd1970e0..34dc58d0c2c 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -28,6 +28,7 @@
 #include "ags/engine/ac/screen.h"
 #include "ags/engine/ac/dynobj/script_viewport.h"
 #include "ags/engine/ac/dynobj/script_user_object.h"
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/plugins/ags_plugin.h"
@@ -56,6 +57,7 @@ void fadein_impl(PALETTE p, int speed) {
 }
 
 void current_fade_out_effect() {
+	debug_script_log("Transition-out in room %d", _G(displayed_room));
 	if (pl_run_plugin_hooks(AGSE_TRANSITIONOUT, 0))
 		return;
 


Commit: 10813d021f6d467075751909a5fb4340c5cca4da
    https://github.com/scummvm/scummvm/commit/10813d021f6d467075751909a5fb4340c5cca4da
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Update script API version

Partially from upstream a07f1f193cc020818c5095c535098ccf153ba14c

Changed paths:
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index ce3c0ca1a51..a2be1a8ae26 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -265,6 +265,7 @@ const char *GetScriptAPIName(ScriptAPIVersion v) {
 	case kScriptAPI_v351: return "v3.5.1";
 	case kScriptAPI_v360: return "v3.6.0-alpha";
 	case kScriptAPI_v36026: return "v3.6.0-final";
+	case kScriptAPI_v361: return "v3.6.1";
 	default: return "unknown";
 	}
 }
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 0385056c5e9..909397328ec 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -191,7 +191,8 @@ enum ScriptAPIVersion {
 	kScriptAPI_v351 = 8,
 	kScriptAPI_v360 = 3060000,
 	kScriptAPI_v36026 = 3060026,
-	kScriptAPI_Current = kScriptAPI_v36026
+	kScriptAPI_v361 = 3060100,
+	kScriptAPI_Current = kScriptAPI_v361
 };
 
 extern const char *GetScriptAPIName(ScriptAPIVersion v);


Commit: 12be21d0c60c58ec36b8b68121f14ee7fd5b74f8
    https://github.com/scummvm/scummvm/commit/12be21d0c60c58ec36b8b68121f14ee7fd5b74f8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implement eEventGameSaved

>From upstream adc8d3dd48bfeb444c179e2b416057e0fca1dc45

Changed paths:
    engines/ags/engine/ac/event.h
    engines/ags/engine/ac/game.cpp


diff --git a/engines/ags/engine/ac/event.h b/engines/ags/engine/ac/event.h
index c201dbb2ee5..dc34219eab5 100644
--- a/engines/ags/engine/ac/event.h
+++ b/engines/ags/engine/ac/event.h
@@ -39,6 +39,7 @@ namespace AGS3 {
 #define GE_RESTORE_GAME  9
 #define GE_ENTER_ROOM_AFTERFADE 10
 #define GE_LEAVE_ROOM_AFTERFADE 11
+#define GE_SAVE_GAME     12
 
 // Game event types:
 // common script callback
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index d500bb98264..4f6ace9be70 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -885,6 +885,8 @@ void save_game(int slotn, const char *descript) {
 
 	// Actual dynamic game data is saved here
 	SaveGameState(out.get());
+	// call "After Save" event callback
+	run_on_event(GE_SAVE_GAME, RuntimeScriptValue().SetInt32(slotn));
 
 	if (screenShot != nullptr) {
 		int screenShotOffset = out->GetPosition() - sizeof(RICH_GAME_MEDIA_HEADER);


Commit: af96330c5adf1748666eda5b41f8c0e7d546d62e
    https://github.com/scummvm/scummvm/commit/af96330c5adf1748666eda5b41f8c0e7d546d62e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.1)

Partially from upstream 8997730084cb76e5196af5c6307ced5a70eab4b1

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 d53e313fc8b..0d231429a90 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.0"
+#define ACI_VERSION_STR      "3.6.1.1"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.0
+#define ACI_VERSION_MSRC_DEF  3.6.1.1
 #endif
 
 #define SPECIAL_VERSION ""


Commit: b5617e2fc243aaf6a4bb6e15f2f9bb04a3aecb64
    https://github.com/scummvm/scummvm/commit/b5617e2fc243aaf6a4bb6e15f2f9bb04a3aecb64
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed a call to get_local_tint()

Was broken by a9e3a80
>From upstream 8291c23dff6cea60b7e4d2c054b6453efb36b920

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 08968184ce9..466e6863b87 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1197,7 +1197,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 		light_level = objsrc.tintlight;
 	} else {
 		// get the ambient or region tint
-		get_local_tint(objsrc.x, objsrc.y, (tint_flags & OBJF_USEREGIONTINTS) == 0,
+		get_local_tint(objsrc.x, objsrc.y, (tint_flags & OBJF_USEREGIONTINTS) != 0,
 		               &tint_level, &tint_red, &tint_green, &tint_blue,
 		               &tint_light, &light_level);
 	}


Commit: 2de9c546b51d0c8dd078e37d5185c16669efb683
    https://github.com/scummvm/scummvm/commit/2de9c546b51d0c8dd078e37d5185c16669efb683
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store DoOnceOnly tokens in a unordered_set

>From upstream c3927ec4b225778eba98d9ab0eb1f785eb3e6c03

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_internal.h
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 4f6ace9be70..e56c11d0f7f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -353,7 +353,7 @@ bool do_save_game_dialog() {
 }
 
 void free_do_once_tokens() {
-	_GP(play).do_once_tokens.resize(0);
+	_GP(play).do_once_tokens.clear();
 }
 
 
@@ -534,12 +534,10 @@ ScriptViewFrame *Game_GetViewFrame(int viewNumber, int loopNumber, int frame) {
 }
 
 int Game_DoOnceOnly(const char *token) {
-	for (int i = 0; i < (int)_GP(play).do_once_tokens.size(); i++) {
-		if (_GP(play).do_once_tokens[i] == token) {
-			return 0;
-		}
-	}
-	_GP(play).do_once_tokens.push_back(token);
+	if (_GP(play).do_once_tokens.count(String::Wrapper(token)) > 0)
+		return 0;
+	_GP(play).do_once_tokens.insert(token);
+
 	return 1;
 }
 
diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index a7c57abad5e..6dc65f2493a 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -617,11 +617,10 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 		in->ReadInt32(); // gui_draw_order
 		in->ReadInt32(); // do_once_tokens;
 	}
-	int num_do_once_tokens = in->ReadInt32();
-	do_once_tokens.resize(num_do_once_tokens);
+	r_data.DoOnceCount = static_cast<uint32_t>(in->ReadInt32());
 	if (!old_save) {
-		for (int i = 0; i < num_do_once_tokens; ++i) {
-			StrUtil::ReadString(do_once_tokens[i], in);
+		for (size_t i = 0; i < r_data.DoOnceCount; ++i) {
+			do_once_tokens.insert(StrUtil::ReadString(in));
 		}
 	}
 	text_min_display_time_ms = in->ReadInt32();
@@ -805,9 +804,9 @@ void GameState::WriteForSavegame(Shared::Stream *out) const {
 	out->WriteInt32(gamma_adjustment);
 	out->WriteInt16(temporarily_turned_off_character);
 	out->WriteInt16(inv_backwards_compatibility);
-	out->WriteInt32(do_once_tokens.size());
-	for (int i = 0; i < (int)do_once_tokens.size(); ++i) {
-		StrUtil::WriteString(do_once_tokens[i], out);
+	out->WriteInt32(static_cast<uint32_t>(do_once_tokens.size()));
+	for (const auto &token : do_once_tokens) {
+		StrUtil::WriteString(token, out);
 	}
 	out->WriteInt32(text_min_display_time_ms);
 	out->WriteInt32(ignore_user_input_after_text_timeout_ms);
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 34d2629fc3f..f597ee35758 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -23,6 +23,7 @@
 #define AGS_ENGINE_AC_GAME_STATE_H
 
 #include "common/std/memory.h"
+#include "common/std/unordered_set.h"
 #include "common/std/vector.h"
 #include "ags/shared/ac/character_info.h"
 #include "ags/shared/ac/keycode.h"
@@ -229,7 +230,7 @@ struct GameState {
 	short temporarily_turned_off_character = 0;  // Hide Player Charactr ticked
 	short inv_backwards_compatibility = 0;  // tells to use legacy inv_* variables
 	std::vector<int> gui_draw_order; // used only for hit detection now
-	std::vector<AGS::Shared::String> do_once_tokens;
+	std::unordered_set<AGS::Shared::String> do_once_tokens;
 	int   text_min_display_time_ms = 0;
 	int   ignore_user_input_after_text_timeout_ms = 0;
 	int32_t default_audio_type_volumes[MAX_AUDIO_TYPES];
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 44f26710d9e..213a661508f 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -104,6 +104,7 @@ RestoredData::ScriptData::ScriptData()
 
 RestoredData::RestoredData()
 	: FPS(0)
+	, DoOnceCount(0u)
 	, RoomVolume(kRoomVolumeNormal)
 	, CursorID(0)
 	, CursorMode(0) {
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index a1fcebb751a..8734527c2da 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -81,6 +81,8 @@ struct RestoredData {
 	};
 	ScriptData              GlobalScript;
 	std::vector<ScriptData> ScriptModules;
+	// Game state data (loaded ahead)
+	uint32_t				DoOnceCount;
 	// Room data (has to be be preserved until room is loaded)
 	PBitmap                 RoomBkgScene[MAX_ROOM_BGFRAMES];
 	int16_t                 RoomLightLevels[MAX_ROOM_REGIONS];
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 7f385237430..df4a4441d0a 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -179,16 +179,6 @@ static void ReadGameState_Aligned(Stream *in, GameDataVersion data_ver, Restored
 	_GP(play).ReadFromSavegame(&align_s, data_ver, kGSSvgVersion_OldFormat, r_data);
 }
 
-static void restore_game_play_ex_data(Stream *in) {
-	char rbuffer[200];
-	for (size_t i = 0; i < _GP(play).do_once_tokens.size(); ++i) {
-		StrUtil::ReadCStr(rbuffer, in, sizeof(rbuffer));
-		_GP(play).do_once_tokens[i] = rbuffer;
-	}
-
-	in->Seek(_GP(game).numgui * sizeof(int32_t)); // gui_draw_order
-}
-
 static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
 	int screenfadedout_was = _GP(play).screen_is_faded_out;
 	int roomchanges_was = _GP(play).room_changes;
@@ -199,7 +189,14 @@ static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData
 	_GP(play).screen_is_faded_out = screenfadedout_was;
 	_GP(play).room_changes = roomchanges_was;
 
-	restore_game_play_ex_data(in);
+	char rbuffer[200]; // old doonceonly token length
+	for (size_t i = 0; i < r_data.DoOnceCount; ++i) {
+		StrUtil::ReadCStr(rbuffer, in, sizeof(rbuffer));
+		_GP(play).do_once_tokens.insert(rbuffer);
+	}
+
+	// Skip gui_draw_order (no longer applied from saves)
+	in->Seek(_GP(game).numgui * sizeof(int32_t));
 }
 
 static void ReadMoveList_Aligned(Stream *in) {
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index bc0a10a606a..9e75d0fa5b8 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -651,7 +651,6 @@ void engine_init_game_settings() {
 	_GP(play).temporarily_turned_off_character = -1;
 	_GP(play).inv_backwards_compatibility = 0;
 	_GP(play).gamma_adjustment = 100;
-	_GP(play).do_once_tokens.resize(0);
 	_GP(play).music_queue_size = 0;
 	_GP(play).shakesc_length = 0;
 	_GP(play).wait_counter = 0;


Commit: 572bdbfe74b31a594c0f0214e0763167d57287c0
    https://github.com/scummvm/scummvm/commit/572bdbfe74b31a594c0f0214e0763167d57287c0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: implemented Game.ResetDoOnceOnly()

>From upstream 614f1808c78d183d7957249dd19b24a48b3b7836

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index e56c11d0f7f..c5187792d63 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -541,6 +541,10 @@ int Game_DoOnceOnly(const char *token) {
 	return 1;
 }
 
+void Game_ResetDoOnceOnly() {
+	free_do_once_tokens();
+}
+
 int Game_GetTextReadingSpeed() {
 	return _GP(play).text_speed;
 }
@@ -1480,6 +1484,10 @@ RuntimeScriptValue Sc_Game_DoOnceOnly(const RuntimeScriptValue *params, int32_t
 	API_SCALL_INT_POBJ(Game_DoOnceOnly, const char);
 }
 
+RuntimeScriptValue Sc_Game_ResetDoOnceOnly(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID(Game_ResetDoOnceOnly);
+}
+
 // int (int red, int grn, int blu)
 RuntimeScriptValue Sc_Game_GetColorFromRGB(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_INT_PINT3(Game_GetColorFromRGB);
@@ -1738,6 +1746,7 @@ void RegisterGameAPI() {
 	ccAddExternalStaticFunction("Game::InputBox^1",                             Sc_Game_InputBox);
 	ccAddExternalStaticFunction("Game::SetSaveGameDirectory^1",                 Sc_Game_SetSaveGameDirectory);
 	ccAddExternalStaticFunction("Game::StopSound^1",                            Sc_StopAllSounds);
+	ccAddExternalStaticFunction("Game::ResetDoOnceOnly",                        Sc_Game_ResetDoOnceOnly);
 	ccAddExternalStaticFunction("Game::get_CharacterCount",                     Sc_Game_GetCharacterCount);
 	ccAddExternalStaticFunction("Game::get_DialogCount",                        Sc_Game_GetDialogCount);
 	ccAddExternalStaticFunction("Game::get_FileName",                           Sc_Game_GetFileName);


Commit: 072a9ee8249cbc6cafa86e2d8efc1c5563acdad8
    https://github.com/scummvm/scummvm/commit/072a9ee8249cbc6cafa86e2d8efc1c5563acdad8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplified GameSetupStruct and GameState reset in unload_game()

Partially from upstream 55967364f40b53d33ccb6760dfb94335816dba29

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/shared/ac/game_setup_struct.h
    engines/ags/shared/ac/game_setup_struct_base.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index c5187792d63..ef2074dd19f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -427,9 +427,9 @@ void unload_game() {
 	resetRoomStatuses();
 	_GP(thisroom).Free();
 
-	// free game struct last because it contains object counts
-	_GP(game).Free();
-	_GP(play).Free();
+	// Free game state and game struct
+	_GP(play) = GameState();
+	_GP(game) = GameSetupStruct();
 
 	// Reset all resource caches
 	// IMPORTANT: this is hard reset, including locked items
diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 6dc65f2493a..2a4973fb20a 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -67,14 +67,6 @@ GameState::GameState() {
 	_mainViewportHasChanged = false;
 }
 
-void GameState::Free() {
-	_GP(play).FreeProperties();
-	_GP(play).FreeViewportsAndCameras();
-	do_once_tokens.clear();
-	raw_drawing_surface.reset();
-	gui_draw_order.clear();
-}
-
 bool GameState::IsAutoRoomViewport() const {
 	return _isAutoRoomViewport;
 }
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index f597ee35758..a9ab54a68f1 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -268,8 +268,6 @@ struct GameState {
 
 
 	GameState();
-	// Free game resources
-	void Free();
 
 	//
 	// Viewport and camera control.
@@ -391,6 +389,8 @@ struct GameState {
 	void WriteCustomProperties_v340(Shared::Stream *out, GameDataVersion data_ver) const;
 	void ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, GameStateSvgVersion svg_ver, AGS::Engine::RestoredData &r_data);
 	void WriteForSavegame(Shared::Stream *out) const;
+	// This is required for freeing only particular parts when restoring the game;
+	// FIXME: investigate and refactor to be able to simply reset whole object
 	void FreeProperties();
 	void FreeViewportsAndCameras();
 
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index f8ff4c30f7d..81ae23ebf65 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -261,7 +261,11 @@ int RunAGSGame(const String &newgame, unsigned int mode, int data) {
 		return 0;
 	}
 
-	int ee;
+	// Optionally save legacy GlobalInts
+	int savedscriptvars[MAXGSVALUES];
+	if ((mode & RAGMODE_PRESERVEGLOBALINT) != 0) {
+		memcpy(savedscriptvars, _GP(play).globalscriptvars, sizeof(_GP(play).globalscriptvars));
+	}
 
 	unload_old_room();
 	_G(displayed_room) = -10;
@@ -292,10 +296,9 @@ int RunAGSGame(const String &newgame, unsigned int mode, int data) {
 	if (!err)
 		quitprintf("!RunAGSGame: error loading new sprites:\n%s", err->FullMessage().GetCStr());
 
-	if ((mode & RAGMODE_PRESERVEGLOBALINT) == 0) {
-		// reset GlobalInts
-		for (ee = 0; ee < MAXGSVALUES; ee++)
-			_GP(play).globalscriptvars[ee] = 0;
+	// Restore saved GlobalInts
+	if ((mode & RAGMODE_PRESERVEGLOBALINT) != 0) {
+		memcpy(_GP(play).globalscriptvars, savedscriptvars, sizeof(_GP(play).globalscriptvars));
 	}
 
 	engine_init_game_settings();
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 213a661508f..18ea1f4de99 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -368,9 +368,6 @@ void DoBeforeRestore(PreservedParams &pp) {
 		_GP(moduleInst)[i] = nullptr;
 	}
 
-	_GP(play).FreeProperties();
-	_GP(play).FreeViewportsAndCameras();
-
 	delete _G(roominstFork);
 	delete _G(roominst);
 	_G(roominstFork) = nullptr;
@@ -379,8 +376,14 @@ void DoBeforeRestore(PreservedParams &pp) {
 	delete _G(dialogScriptsInst);
 	_G(dialogScriptsInst) = nullptr;
 
+	// reset saved room states
 	resetRoomStatuses();
-	_GP(troom) = RoomStatus(); // reset temp room state
+	// reset temp room state
+	_GP(troom) = RoomStatus();
+	// reset (some of the?) GameState data
+	// FIXME: investigate and refactor to be able to just reset whole object
+	_GP(play).FreeProperties();
+	_GP(play).FreeViewportsAndCameras();
 	free_do_once_tokens();
 
 	// unregister gui controls from API exports
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 24ab4fde08f..82780cec921 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -111,8 +111,11 @@ struct GameSetupStruct : public GameSetupStructBase {
 
 
 	GameSetupStruct();
+	GameSetupStruct(GameSetupStruct &&gss) = default;
 	~GameSetupStruct();
 
+	GameSetupStruct &operator=(GameSetupStruct &&gss) = default;
+
 	void Free();
 
 	// [IKM] Game struct loading code is moved here from Engine's load_game_file
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 8075e528f95..7b39e37822a 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -34,6 +34,7 @@
 #include "common/std/vector.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/ac/game_struct_defines.h"
+#include "ags/shared/ac/words_dictionary.h"
 #include "ags/shared/util/string.h"
 #include "ags/globals.h"
 
@@ -48,7 +49,6 @@ class Stream;
 
 using namespace AGS; // FIXME later
 
-struct WordsDictionary;
 struct CharacterInfo;
 struct ccScript;
 
@@ -95,8 +95,11 @@ struct GameSetupStructBase {
 	// pointer is used for that instead.
 
 	GameSetupStructBase();
+	GameSetupStructBase(GameSetupStructBase &&gss) = default;
 	~GameSetupStructBase();
 
+	GameSetupStructBase &operator=(GameSetupStructBase &&gss) = default;
+
 	void Free();
 	void SetDefaultResolution(GameResolutionType type);
 	void SetDefaultResolution(Size game_res);


Commit: 4019f9305b59d3f226b58d34d4fc30e2d5ec969c
    https://github.com/scummvm/scummvm/commit/4019f9305b59d3f226b58d34d4fc30e2d5ec969c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances

>From upstream b1e4f75141ce88b3146f55ecd9d7a95378428072
and 49e8aa0b566dea4ce025f9530a9f2392dbe67a33

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index ef2074dd19f..ee20e05b67f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -373,36 +373,9 @@ void unload_game() {
 
 	// Free all script instances and script modules
 	ccInstance::FreeInstanceStack();
-	delete _G(gameinstFork);
-	delete _G(gameinst);
-	_G(gameinstFork) = nullptr;
-	_G(gameinst) = nullptr;
-	_GP(gamescript).reset();
-
-	delete _G(dialogScriptsInst);
-	_G(dialogScriptsInst) = nullptr;
-	_GP(dialogScriptsScript).reset();
-
-	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		delete _GP(moduleInstFork)[i];
-		delete _GP(moduleInst)[i];
-		_GP(scriptModules)[i].reset();
-	}
 
-	_GP(moduleInstFork).resize(0);
-	_GP(moduleInst).resize(0);
-	_GP(scriptModules).resize(0);
-	_GP(repExecAlways).moduleHasFunction.resize(0);
-	_GP(lateRepExecAlways).moduleHasFunction.resize(0);
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(0);
-	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(0);
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(0);
-	_G(numScriptModules) = 0;
+	FreeAllScriptInstances();
+	FreeGlobalScripts();
 
 	_GP(charextra).clear();
 	_GP(mls).clear();
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 0e8d1e87637..4ee2a15b369 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -265,10 +265,7 @@ void unload_old_room() {
 	if (_G(croom) == nullptr) ;
 	else if (_G(roominst) != nullptr) {
 		save_room_data_segment();
-		delete _G(roominstFork);
-		delete _G(roominst);
-		_G(roominstFork) = nullptr;
-		_G(roominst) = nullptr;
+		FreeRoomScriptInstance();
 	} else _G(croom)->tsdatasize = 0;
 	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
 	_GP(play).bg_frame = 0;
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 5b788de722f..0c6e013f3cd 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -310,25 +310,6 @@ void LoadLipsyncData() {
 	Debug::Printf(kDbgMsg_Info, "Lipsync data found and loaded");
 }
 
-void AllocScriptModules() {
-	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
-	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
-	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
-	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	for (auto &val : _GP(moduleRepExecAddr)) {
-		val.Invalidate();
-	}
-}
-
 HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver) {
 	GameSetupStruct &game = ents.Game;
 	const ScriptAPIVersion base_api = (ScriptAPIVersion)game.options[OPT_BASESCRIPTAPI];
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 18ea1f4de99..a6171b8a4c9 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -355,26 +355,12 @@ void DoBeforeRestore(PreservedParams &pp) {
 
 	// preserve script data sizes and cleanup scripts
 	pp.GlScDataSize = _G(gameinst)->globaldatasize;
-	delete _G(gameinstFork);
-	delete _G(gameinst);
-	_G(gameinstFork) = nullptr;
-	_G(gameinst) = nullptr;
 	pp.ScMdDataSize.resize(_G(numScriptModules));
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		pp.ScMdDataSize[i] = _GP(moduleInst)[i]->globaldatasize;
-		delete _GP(moduleInstFork)[i];
-		delete _GP(moduleInst)[i];
-		_GP(moduleInstFork)[i] = nullptr;
-		_GP(moduleInst)[i] = nullptr;
 	}
 
-	delete _G(roominstFork);
-	delete _G(roominst);
-	_G(roominstFork) = nullptr;
-	_G(roominst) = nullptr;
-
-	delete _G(dialogScriptsInst);
-	_G(dialogScriptsInst) = nullptr;
+	FreeAllScriptInstances();
 
 	// reset saved room states
 	resetRoomStatuses();
@@ -480,6 +466,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	update_gui_zorder();
 
+	AllocScriptModules();
 	if (create_global_script()) {
 		return new SavegameError(kSvgErr_GameObjectInitFailed,
 		                         String::FromFormat("Unable to recreate global script: %s", cc_get_error().ErrorString.GetCStr()));
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index e0a18fb6e5a..16d08e9f69b 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -213,18 +213,16 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-ccInstance *ccInstance::CreateFromScript(PScript scri) {
+PInstance ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	ccInstance *cinst = new ccInstance();
+	std::shared_ptr<ccInstance> cinst(new ccInstance());
 	if (!cinst->_Create(scri, joined)) {
-		delete cinst;
 		return nullptr;
 	}
-
 	return cinst;
 }
 
@@ -267,7 +265,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-ccInstance *ccInstance::Fork() {
+PInstance ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index d74d2e6f485..48b71f5b077 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -109,6 +109,10 @@ struct ScriptPosition {
 	int32_t         Line;
 };
 
+
+struct ccInstance;
+typedef std::shared_ptr<ccInstance> PInstance;
+
 // Running instance of the script
 struct ccInstance {
 public:
@@ -161,14 +165,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static ccInstance *CreateFromScript(PScript script);
-	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
+	static PInstance CreateFromScript(PScript script);
+	static PInstance CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	ccInstance *Fork();
+	PInstance Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index 72d9f4a7298..e5971ec5621 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -101,7 +101,7 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 
 void ExecutingScript::init() {
 	inst = nullptr;
-	forked = 0;
+	forked = false;
 	numanother = 0;
 	numPostScriptActions = 0;
 
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index c7f77af13e1..420c4880b82 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,7 +59,7 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	ccInstance *inst;
+	PInstance inst;
 	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
 	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
 	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
@@ -68,7 +68,7 @@ struct ExecutingScript {
 	int  numPostScriptActions;
 	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
 	int  numanother;
-	int8 forked;
+	bool forked;
 
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 86f72b59988..7fd9d2c10d8 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -90,18 +90,18 @@ void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) {
 	// run modules
 	// modules need a forkedinst for this to work
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i], funcToRun, funcToRun->moduleHasFunction[i]);
+		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i].get(), funcToRun, funcToRun->moduleHasFunction[i]);
 
 		if (room_changes_was != _GP(play).room_changes)
 			return;
 	}
 
-	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork), funcToRun, funcToRun->globalScriptHasFunction);
+	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork).get(), funcToRun, funcToRun->globalScriptHasFunction);
 
 	if (room_changes_was != _GP(play).room_changes || _G(abort_engine))
 		return;
 
-	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork), funcToRun, funcToRun->roomHasFunction);
+	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork).get(), funcToRun, funcToRun->roomHasFunction);
 }
 
 //-----------------------------------------------------------
@@ -203,29 +203,31 @@ int create_global_script() {
 
 	ccSetOption(SCOPT_AUTOIMPORT, 1);
 
-	std::vector<ccInstance *> instances_for_resolving;
+	// NOTE: this function assumes that the module lists have their elements preallocated!
+
+	std::vector<PInstance> all_insts; // gather all to resolve exports below
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		_GP(moduleInst)[i] = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
-		if (_GP(moduleInst)[i] == nullptr)
+		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
+		if (!inst)
 			return kscript_create_error;
-		instances_for_resolving.push_back(_GP(moduleInst)[i]);
+		_GP(moduleInst)[i] = inst;
+		all_insts.push_back(inst);
 	}
 
 	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
-	if (_G(gameinst) == nullptr)
+	if (!_G(gameinst))
 		return kscript_create_error;
-	instances_for_resolving.push_back(_G(gameinst));
+	all_insts.push_back(_G(gameinst));
 
 	if (_GP(dialogScriptsScript) != nullptr) {
 		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
-		if (_G(dialogScriptsInst) == nullptr)
+		if (!_G(dialogScriptsInst))
 			return kscript_create_error;
-		instances_for_resolving.push_back(_G(dialogScriptsInst));
+		all_insts.push_back(_G(dialogScriptsInst));
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
-	for (size_t instance_idx = 0; instance_idx < instances_for_resolving.size(); instance_idx++) {
-		auto inst = instances_for_resolving[instance_idx];
+	for (auto &inst : all_insts) {
 		if (!inst->ResolveScriptImports(inst->instanceof.get()))
 			return kscript_create_error;
 		if (!inst->ResolveImportFixups(inst->instanceof.get()))
@@ -235,10 +237,11 @@ int create_global_script() {
 	// Create the forks for 'repeatedly_execute_always' after resolving
 	// because they copy their respective originals including the resolve information
 	for (size_t module_idx = 0; module_idx < _G(numScriptModules); module_idx++) {
-		_GP(moduleInstFork)[module_idx] = _GP(moduleInst)[module_idx]->Fork();
-		if (_GP(moduleInstFork)[module_idx] == nullptr)
+		auto fork = _GP(moduleInst)[module_idx]->Fork();
+		if (!fork)
 			return kscript_create_error;
 
+		_GP(moduleInstFork)[module_idx] = fork;
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
@@ -251,12 +254,12 @@ int create_global_script() {
 }
 
 void cancel_all_scripts() {
-	for (int aa = 0; aa < _G(num_scripts); aa++) {
-		if (_G(scripts)[aa].forked)
-			_G(scripts)[aa].inst->AbortAndDestroy();
-		else
-			_G(scripts)[aa].inst->Abort();
-		_G(scripts)[aa].numanother = 0;
+	for (int i = 0; i < _G(num_scripts); ++i) {
+		auto &sc = _G(scripts)[i];
+		if (sc.inst) {
+			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
+		}
+		sc.numanother = 0;
 	}
 	_G(num_scripts) = 0;
 	// in case the script is running on non-blocking thread (rep-exec-always etc)
@@ -265,7 +268,7 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
 		return _G(gameinst);
 	else if (sc_inst == kScInstRoom)
@@ -307,7 +310,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(ccInstance *sci, const char **tsname) {
+static int PrepareTextScript(PInstance sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -327,7 +330,7 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
 		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
 			quit("unable to fork instance for secondary script");
-		_G(scripts)[_G(num_scripts)].forked = 1;
+		_G(scripts)[_G(num_scripts)].forked = true;
 	}
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
@@ -341,7 +344,7 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -445,12 +448,70 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	ccInstance *sci = GetScriptInstanceByType(sc_inst);
+	PInstance sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
 }
 
+void AllocScriptModules() {
+	// NOTE: this preallocation possibly required to safeguard some algorithms
+	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
+	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
+	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	for (auto &val : _GP(moduleRepExecAddr)) {
+		val.Invalidate();
+	}
+}
+
+void FreeAllScriptInstances() {
+	FreeRoomScriptInstance();
+
+	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
+	// or bad things will happen; TODO: investigate and make this less fragile
+	_G(gameinstFork).reset();
+	_G(gameinst).reset();
+	_G(dialogScriptsInst).reset();
+	_GP(moduleInstFork).clear();
+	_GP(moduleInst).clear();
+}
+
+void FreeRoomScriptInstance() {
+	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
+	// or bad things will happen; TODO: investigate and make this less fragile
+	_G(roominstFork).reset();
+	_G(roominst).reset();
+}
+
+void FreeGlobalScripts() {
+	_G(numScriptModules) = 0;
+
+	_GP(gamescript).reset();
+	_GP(scriptModules).clear();
+	_GP(dialogScriptsScript).reset();
+
+	_GP(repExecAlways).moduleHasFunction.clear();
+	_GP(lateRepExecAlways).moduleHasFunction.clear();
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.clear();
+	_GP(renderDialogOptionsFunc).moduleHasFunction.clear();
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.clear();
+}
+
 String GetScriptName(ccInstance *sci) {
 	// TODO: have script name a ccScript's member?
 	// TODO: check script modules too?
@@ -485,8 +546,7 @@ void post_script_cleanup() {
 	ExecutingScript copyof;
 	if (_G(num_scripts) > 0) {
 		copyof = _G(scripts)[_G(num_scripts) - 1];
-		if (_G(scripts)[_G(num_scripts) - 1].forked)
-			delete _G(scripts)[_G(num_scripts) - 1].inst;
+		_G(scripts)[_G(num_scripts) - 1].inst.reset();
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
@@ -573,7 +633,6 @@ void post_script_cleanup() {
 		if ((_G(displayed_room) != old_room_number) || (_G(load_new_game)))
 			break;
 	}
-	copyof.numanother = 0;
 
 }
 
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index 01b6edff922..ec8b4c24a73 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -22,6 +22,7 @@
 #ifndef AGS_ENGINE_SCRIPT_SCRIPT_H
 #define AGS_ENGINE_SCRIPT_SCRIPT_H
 
+#include "common/std/memory.h"
 #include "common/std/vector.h"
 
 #include "ags/engine/script/cc_instance.h"
@@ -49,12 +50,12 @@ int     run_interaction_script(InteractionScripts *nint, int evnt, int chkAny =
 int     create_global_script();
 void    cancel_all_scripts();
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -68,6 +69,16 @@ int     RunScriptFunctionInRoom(const char *tsname, size_t param_count = 0,
 int     RunScriptFunctionAuto(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 
+// Preallocates script module instances
+void	AllocScriptModules();
+// Delete all the script instance objects
+void	FreeAllScriptInstances();
+// Delete only the current room script instance
+void	FreeRoomScriptInstance();
+// Deletes all the global scripts and modules;
+// this frees all of the bytecode and runtime script memory.
+void	FreeGlobalScripts();
+
 AGS::Shared::String GetScriptName(ccInstance *sci);
 
 //=============================================================================
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index bab3cbebdae..bddfad85901 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -369,8 +369,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<ccInstance *>();
-	_moduleInstFork = new std::vector<ccInstance *>();
+	_moduleInst = new std::vector<PInstance>();
+	_moduleInstFork = new std::vector<PInstance>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d307e4c5736..d24260e7a12 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1260,9 +1260,11 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	ccInstance *_gameinst = nullptr, *_roominst = nullptr;
-	ccInstance *_dialogScriptsInst = nullptr;
-	ccInstance *_gameinstFork = nullptr, *_roominstFork = nullptr;
+	PInstance _gameinst;
+	PInstance _roominst;
+	PInstance _dialogScriptsInst;
+	PInstance _gameinstFork;
+	PInstance _roominstFork;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1284,8 +1286,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<ccInstance *> *_moduleInst;
-	std::vector<ccInstance *> *_moduleInstFork;
+	std::vector<PInstance> *_moduleInst;
+	std::vector<PInstance> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index f53caea5afb..400a9ca2f1f 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -610,7 +610,7 @@ int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int
 	if (_G(inside_script))
 		return -300;
 
-	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 90e338e8750c4835042bbf29e3c88522dfa38d53
    https://github.com/scummvm/scummvm/commit/90e338e8750c4835042bbf29e3c88522dfa38d53
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: hide Dialog::optionscripts under OBSOLETE

They are not used anywhere in Editor nor Engine, looks like a pre-2.5 feature we have no knowledge about.
>From upstream bf0cab71384c6eca996143ac12e56e1af2510004

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/shared/ac/dialog_topic.cpp
    engines/ags/shared/ac/dialog_topic.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index ee20e05b67f..f261d709296 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -385,10 +385,6 @@ void unload_game() {
 	_G(numLipLines) = 0;
 	_G(curLipLine) = -1;
 
-	for (auto &dlg : _G(dialog)) {
-		if (dlg.optionscripts != nullptr)
-			free(dlg.optionscripts);
-	}
 	_G(dialog).clear();
 	_GP(scrDialog).clear();
 
diff --git a/engines/ags/shared/ac/dialog_topic.cpp b/engines/ags/shared/ac/dialog_topic.cpp
index d1251af1f26..aa2ca07e9f4 100644
--- a/engines/ags/shared/ac/dialog_topic.cpp
+++ b/engines/ags/shared/ac/dialog_topic.cpp
@@ -29,8 +29,6 @@ using AGS::Shared::Stream;
 void DialogTopic::ReadFromFile(Stream *in) {
 	in->ReadArray(optionnames, 150 * sizeof(char), MAXTOPICOPTIONS);
 	in->ReadArrayOfInt32(optionflags, MAXTOPICOPTIONS);
-	// optionscripts pointer is not used anywhere in the engine
-	optionscripts = nullptr;
 	in->ReadInt32(); // optionscripts 32-bit pointer
 	in->ReadArrayOfInt16(entrypoints, MAXTOPICOPTIONS);
 	startupentrypoint = in->ReadInt16();
diff --git a/engines/ags/shared/ac/dialog_topic.h b/engines/ags/shared/ac/dialog_topic.h
index dada5a5c2f3..91407993d45 100644
--- a/engines/ags/shared/ac/dialog_topic.h
+++ b/engines/ags/shared/ac/dialog_topic.h
@@ -65,12 +65,15 @@ using namespace AGS; // FIXME later
 struct DialogTopic {
 	char          optionnames[MAXTOPICOPTIONS][150];
 	int32_t       optionflags[MAXTOPICOPTIONS];
-	unsigned char *optionscripts;
 	short         entrypoints[MAXTOPICOPTIONS];
 	short         startupentrypoint;
 	short         codesize;
 	int           numoptions;
 	int           topicFlags;
+	// NOTE: optionscripts is an unknown data from before AGS 2.5
+#ifdef OBSOLETE
+	std::vector<uint8_t> optionscripts;
+#endif
 
 	void ReadFromFile(Shared::Stream *in);
 


Commit: 909fa718b9b202ce623bdbebf30e1be3e2078839
    https://github.com/scummvm/scummvm/commit/909fa718b9b202ce623bdbebf30e1be3e2078839
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store RoomStatuses in std::unique_ptrs

>From upstream 52fc662c74c9f27f24aff554f2b1585b68681ca4

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


diff --git a/engines/ags/engine/ac/room_status.cpp b/engines/ags/engine/ac/room_status.cpp
index 7ca2d283a18..d5cbed7bc88 100644
--- a/engines/ags/engine/ac/room_status.cpp
+++ b/engines/ags/engine/ac/room_status.cpp
@@ -214,17 +214,15 @@ void RoomStatus::WriteToSavegame(Stream *out, GameDataVersion data_ver) const {
 	out->WriteInt32(0);
 }
 
-// JJS: Replacement for the global roomstats array in the original engine.
-
-RoomStatus *room_statuses[MAX_ROOMS];
+std::unique_ptr<RoomStatus> room_statuses[MAX_ROOMS];
 
 // Replaces all accesses to the roomstats array
 RoomStatus *getRoomStatus(int room) {
-	if (room_statuses[room] == nullptr) {
+	if (!room_statuses[room]) {
 		// First access, allocate and initialise the status
-		room_statuses[room] = new RoomStatus();
+		room_statuses[room].reset(new RoomStatus());
 	}
-	return room_statuses[room];
+	return room_statuses[room].get();
 }
 
 // Used in places where it is only important to know whether the player
@@ -237,10 +235,7 @@ bool isRoomStatusValid(int room) {
 
 void resetRoomStatuses() {
 	for (int i = 0; i < MAX_ROOMS; i++) {
-		if (room_statuses[i] != nullptr) {
-			delete room_statuses[i];
-			room_statuses[i] = nullptr;
-		}
+		room_statuses[i].reset();
 	}
 }
 


Commit: 99cbbaacacd2732cab4279b989d9c77c76aa2bfb
    https://github.com/scummvm/scummvm/commit/99cbbaacacd2732cab4279b989d9c77c76aa2bfb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: removed obscure hack from draw_button_background()

>From upstream e7d5488e4a7d53aa0dc00c07204a0cdadb858197

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


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index dc4e987de1e..10c8dd11d32 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -623,14 +623,10 @@ void draw_button_background(Bitmap *ds, int xx1, int yy1, int xx2, int yy2, GUIM
 		ds->FillRect(Rect(xx1, yy1, xx2, yy2), draw_color);
 		draw_color = ds->GetCompatibleColor(16);
 		ds->DrawRect(Rect(xx1, yy1, xx2, yy2), draw_color);
-		/*    draw_color = ds->GetCompatibleColor(opts.tws.backcol); ds->FillRect(Rect(xx1,yy1,xx2,yy2);
-		draw_color = ds->GetCompatibleColor(opts.tws.ds->GetTextColor()); ds->DrawRect(Rect(xx1+1,yy1+1,xx2-1,yy2-1);*/
 	} else {
-		if (_G(loaded_game_file_version) < kGameVersion_262) { // < 2.62
-			// Color 0 wrongly shows as transparent instead of black
-			// From the changelog of 2.62:
-			//  - Fixed text windows getting a black background if colour 0 was
-			//    specified, rather than being transparent.
+		if (_G(loaded_game_file_version) < kGameVersion_262) {
+			// In pre-2.62 color 0 should be treated as "black" instead of "transparent";
+			// this was an unintended effect in older versions (see 2.62 changelog fixes).
 			if (iep->BgColor == 0)
 				iep->BgColor = 16;
 		}
@@ -645,12 +641,7 @@ void draw_button_background(Bitmap *ds, int xx1, int yy1, int xx2, int yy2, GUIM
 		const int topBottomHeight = _GP(game).SpriteInfos[get_but_pic(iep, 6)].Height;
 		// GUI middle space
 		if (iep->BgImage > 0) {
-			if ((_G(loaded_game_file_version) <= kGameVersion_272) // 2.xx
-			        && (_GP(spriteset)[iep->BgImage]->GetWidth() == 1)
-			        && (_GP(spriteset)[iep->BgImage]->GetHeight() == 1)
-			        && (*((const unsigned int *)_GP(spriteset)[iep->BgImage]->GetData()) == 0x00FF00FF)) {
-				// Don't draw fully transparent dummy GUI backgrounds
-			} else {
+			{
 				// offset the background image and clip it so that it is drawn
 				// such that the border graphics can have a transparent outside
 				// edge


Commit: ac85f7fc5268b2b35c91e3b3e75f130c2cca110e
    https://github.com/scummvm/scummvm/commit/ac85f7fc5268b2b35c91e3b3e75f130c2cca110e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unified API assertions for view, loop and frame

>From upstream 1fe83591970e6a87d1f97c8f4e2d752a3a35b301

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/global_view_frame.cpp
    engines/ags/engine/ac/object.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 074ef0ae2f8..42e3df5293e 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -556,11 +556,8 @@ void Character_LockView(CharacterInfo *chap, int vii) {
 }
 
 void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) {
-
-	if ((vii < 1) || (vii > _GP(game).numviews)) {
-		quitprintf("!SetCharacterView: invalid view number (You said %d, max is %d)", vii, _GP(game).numviews);
-	}
-	vii--;
+	vii--; // convert to 0-based
+	AssertView("SetCharacterView", vii);
 
 	debug_script_log("%s: View locked to %d", chap->scrname, vii + 1);
 	if (chap->idleleft < 0) {
@@ -601,8 +598,7 @@ void Character_LockViewAlignedEx(CharacterInfo *chap, int vii, int loop, int ali
 
 	Character_LockViewEx(chap, vii, stopMoving);
 
-	if ((loop < 0) || (loop >= _GP(views)[chap->view].numLoops))
-		quit("!SetCharacterViewEx: invalid loop specified");
+	AssertLoop("SetCharacterViewEx", chap->view, loop);
 
 	chap->loop = loop;
 	chap->frame = 0;
@@ -628,15 +624,8 @@ void Character_LockViewFrame(CharacterInfo *chaa, int view, int loop, int frame)
 }
 
 void Character_LockViewFrameEx(CharacterInfo *chaa, int view, int loop, int frame, int stopMoving) {
-
 	Character_LockViewEx(chaa, view, stopMoving);
-
-	view--;
-	if ((loop < 0) || (loop >= _GP(views)[view].numLoops))
-		quit("!SetCharacterFrame: invalid loop specified");
-	if ((frame < 0) || (frame >= _GP(views)[view].loops[loop].numFrames))
-		quit("!SetCharacterFrame: invalid frame specified");
-
+	AssertFrame("SetCharacterFrame", view - 1, loop, frame);
 	chaa->loop = loop;
 	chaa->frame = frame;
 }
@@ -1317,11 +1306,9 @@ int Character_GetLoop(CharacterInfo *chaa) {
 }
 
 void Character_SetLoop(CharacterInfo *chaa, int newval) {
-	if ((newval < 0) || (newval >= _GP(views)[chaa->view].numLoops))
-		quit("!Character.Loop: invalid loop number for this view");
+	AssertLoop("Character.Loop", chaa->view, newval);
 
 	chaa->loop = newval;
-
 	if (chaa->frame >= _GP(views)[chaa->view].loops[chaa->loop].numFrames)
 		chaa->frame = 0;
 }
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index f261d709296..785f6b19fc3 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -464,40 +464,53 @@ int Game_GetSpriteHeight(int spriteNum) {
 	return game_to_data_coord(_GP(game).SpriteInfos[spriteNum].Height);
 }
 
-int Game_GetLoopCountForView(int viewNumber) {
-	if ((viewNumber < 1) || (viewNumber > _GP(game).numviews))
-		quit("!GetGameParameter: invalid view specified");
-
-	return _GP(views)[viewNumber - 1].numLoops;
+void AssertView(const char *apiname, int view) {
+	// NOTE: we assume (here and below) that the view is already in an internal 0-based range.
+	// but when printing an error we will use (view + 1) for compliance with the script API.
+	if ((view < 0) || (view >= _GP(game).numviews))
+		quitprintf("!%s: invalid view %d (range is 1..%d)", apiname, view + 1, _GP(game).numviews);
 }
 
-int Game_GetRunNextSettingForLoop(int viewNumber, int loopNumber) {
-	if ((viewNumber < 1) || (viewNumber > _GP(game).numviews))
-		quit("!GetGameParameter: invalid view specified");
-	if ((loopNumber < 0) || (loopNumber >= _GP(views)[viewNumber - 1].numLoops))
-		quit("!GetGameParameter: invalid loop specified");
+void AssertLoop(const char *apiname, int view, int loop) {
+	AssertView(apiname, view);
+	if (_GP(views)[view].numLoops == 0)
+		quitprintf("!%s: view %d does not have any loops.", apiname, view + 1);
+	if ((loop < 0) || (loop >= _GP(views)[view].numLoops))
+		quitprintf("!%s: invalid loop number %d for view %d (range is 0..%d).",
+				   apiname, loop, view + 1, _GP(views)[view].numLoops - 1);
+}
 
-	return (_GP(views)[viewNumber - 1].loops[loopNumber].RunNextLoop()) ? 1 : 0;
+void AssertFrame(const char *apiname, int view, int loop, int frame) {
+	AssertLoop(apiname, view, loop);
+	if (_GP(views)[view].loops[loop].numFrames == 0)
+		quitprintf("!%s: view %d loop %d does not have any frames", apiname, view + 1, loop);
+	if ((frame < 0) || (frame >= _GP(views)[view].loops[loop].numFrames))
+		quitprintf("!%s: invalid frame number %d for view %d loop %d (range is 0..%d)",
+				   apiname, frame, view + 1, loop, _GP(views)[view].loops[loop].numFrames - 1);
 }
 
-int Game_GetFrameCountForLoop(int viewNumber, int loopNumber) {
-	if ((viewNumber < 1) || (viewNumber > _GP(game).numviews))
-		quit("!GetGameParameter: invalid view specified");
-	if ((loopNumber < 0) || (loopNumber >= _GP(views)[viewNumber - 1].numLoops))
-		quit("!GetGameParameter: invalid loop specified");
+int Game_GetLoopCountForView(int view) {
+	view--; // convert to 0-based
+	AssertView("Game.GetLoopCountForView", view);
+	return _GP(views)[view].numLoops;
+}
 
-	return _GP(views)[viewNumber - 1].loops[loopNumber].numFrames;
+int Game_GetRunNextSettingForLoop(int view, int loop) {
+	view--; // convert to 0-based
+	AssertLoop("Game.GetRunNextSettingForLoop", view, loop);
+	return (_GP(views)[view].loops[loop].RunNextLoop()) ? 1 : 0;
 }
 
-ScriptViewFrame *Game_GetViewFrame(int viewNumber, int loopNumber, int frame) {
-	if ((viewNumber < 1) || (viewNumber > _GP(game).numviews))
-		quit("!GetGameParameter: invalid view specified");
-	if ((loopNumber < 0) || (loopNumber >= _GP(views)[viewNumber - 1].numLoops))
-		quit("!GetGameParameter: invalid loop specified");
-	if ((frame < 0) || (frame >= _GP(views)[viewNumber - 1].loops[loopNumber].numFrames))
-		quit("!GetGameParameter: invalid frame specified");
+int Game_GetFrameCountForLoop(int view, int loop) {
+	view--; // convert to 0-based
+	AssertLoop("Game.GetFrameCountForLoop", view, loop);
+	return _GP(views)[view].loops[loop].numFrames;
+}
 
-	ScriptViewFrame *sdt = new ScriptViewFrame(viewNumber - 1, loopNumber, frame);
+ScriptViewFrame *Game_GetViewFrame(int view, int loop, int frame) {
+	view--; // convert to 0-based
+	AssertFrame("Game.GetViewFrame", view, loop, frame);
+	ScriptViewFrame *sdt = new ScriptViewFrame(view, loop, frame);
 	ccRegisterManagedObject(sdt, sdt);
 	return sdt;
 }
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index 491a426d15b..475e5eee80b 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -98,6 +98,12 @@ const char *Game_GetSaveSlotDescription(int slnum);
 
 const char *Game_GetGlobalStrings(int index);
 
+// View, loop, frame parameter assertions.
+// WARNING: these functions assume that view is already in an internal 0-based range.
+void AssertView(const char *apiname, int view);
+void AssertLoop(const char *apiname, int view, int loop);
+void AssertFrame(const char *apiname, int view, int loop, int frame);
+
 int Game_GetInventoryItemCount();
 int Game_GetFontCount();
 int Game_GetMouseCursorCount();
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 81ae23ebf65..d10360d652c 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -327,15 +327,7 @@ int GetGameParameter(int parm, int data1, int data2, int data3) {
 	case GP_FRAMEIMAGE:
 	case GP_FRAMESOUND:
 	case GP_ISFRAMEFLIPPED: {
-		if ((data1 < 1) || (data1 > _GP(game).numviews)) {
-			quitprintf("!GetGameParameter: invalid view specified (v: %d, l: %d, f: %d)", data1, data2, data3);
-		}
-		if ((data2 < 0) || (data2 >= _GP(views)[data1 - 1].numLoops)) {
-			quitprintf("!GetGameParameter: invalid loop specified (v: %d, l: %d, f: %d)", data1, data2, data3);
-		}
-		if ((data3 < 0) || (data3 >= _GP(views)[data1 - 1].loops[data2].numFrames)) {
-			quitprintf("!GetGameParameter: invalid frame specified (v: %d, l: %d, f: %d)", data1, data2, data3);
-		}
+		AssertFrame("GetGameParameter", data1 - 1, data2, data3);
 
 		ViewFrame *pvf = &_GP(views)[data1 - 1].loops[data2].frames[data3];
 
diff --git a/engines/ags/engine/ac/global_view_frame.cpp b/engines/ags/engine/ac/global_view_frame.cpp
index bd5c56810f7..be2a80c0c12 100644
--- a/engines/ags/engine/ac/global_view_frame.cpp
+++ b/engines/ags/engine/ac/global_view_frame.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/global_view_frame.h"
 #include "ags/shared/ac/common.h"
+#include "ags/engine/ac/game.h"
 #include "ags/shared/ac/view.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/debugging/debug_log.h"
@@ -30,15 +31,8 @@
 namespace AGS3 {
 
 void SetFrameSound(int vii, int loop, int frame, int sound) {
-	if ((vii < 1) || (vii > _GP(game).numviews))
-		quit("!SetFrameSound: invalid view number");
-	vii--;
-
-	if (loop >= _GP(views)[vii].numLoops)
-		quit("!SetFrameSound: invalid loop number");
-
-	if (frame >= _GP(views)[vii].loops[loop].numFrames)
-		quit("!SetFrameSound: invalid frame number");
+	vii--; // convert to 0-based
+	AssertFrame("SetFrameSound", vii, loop, frame);
 
 	if (sound < 1) {
 		_GP(views)[vii].loops[loop].frames[frame].sound = -1;
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 57f5d259094..bf26e7bfa14 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -24,6 +24,7 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/character.h"
+#include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_object.h"
 #include "ags/engine/ac/global_translation.h"
@@ -612,12 +613,7 @@ void ValidateViewAnimParams(const char *apiname, int &repeat, int &blocking, int
 void ValidateViewAnimVLF(const char *apiname, int view, int loop, int &sframe) {
 	// NOTE: we assume that the view is already in an internal 0-based range.
 	// but when printing an error we will use (view + 1) for compliance with the script API.
-	if ((view < 0) || (view >= _GP(game).numviews))
-		quitprintf("!%s: invalid view %d (range is 1..%d).", apiname, view + 1, _GP(game).numviews);
-	if (_GP(views)[view].numLoops == 0)
-		quitprintf("!%s: view %d does not have any loops.", apiname, view + 1);
-	if (loop < 0 || loop >= _GP(views)[view].numLoops)
-		quitprintf("!%s: invalid loop number %d for view %d (range is 0..%d).", apiname, loop, view + 1, _GP(views)[view].numLoops - 1);
+	AssertLoop(apiname, view, loop);
 
 	if (_GP(views)[view].loops[loop].numFrames < 1)
 		debug_script_warn("%s: view %d loop %d does not have any frames, will use a frame placeholder.",


Commit: 0f14a257de56d85807e6b40b3a54fd0e6af32cf6
    https://github.com/scummvm/scummvm/commit/0f14a257de56d85807e6b40b3a54fd0e6af32cf6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidied adjust_x/y_for_guis and skip offscreen GUIs

>From upstream f4abff9ddf5dc70e26d2c6728044992a951c6645

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


diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 57f926c5a29..61462d637ac 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -449,50 +449,56 @@ void update_gui_disabled_status() {
 	}
 }
 
-int adjust_x_for_guis(int xx, int yy) {
+static bool should_skip_adjust_for_gui(const GUIMain &gui) {
+	return
+		// not shown
+		!gui.IsDisplayed() ||
+		// completely offscreen
+		!IsRectInsideRect(_GP(play).GetUIViewport(), RectWH(gui.X, gui.Y, gui.Width, gui.Height)) ||
+		// fully transparent (? FIXME: this only checks background, but not controls)
+		((gui.BgColor == 0) && (gui.BgImage < 1)) || (gui.Transparency == 255);
+}
+
+int adjust_x_for_guis(int x, int y) {
 	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0))
-		return xx;
+		return x;
 	// If it's covered by a GUI, move it right a bit
-	for (int aa = 0; aa < _GP(game).numgui; aa++) {
-		if (!_GP(guis)[aa].IsDisplayed())
+	for (const auto &gui : _GP(guis)) {
+		if (should_skip_adjust_for_gui(gui))
 			continue;
-		if ((_GP(guis)[aa].X > xx) || (_GP(guis)[aa].Y > yy) || (_GP(guis)[aa].Y + _GP(guis)[aa].Height < yy))
+		// higher, lower or to the right from the message (?)
+		if ((gui.X > x) || (gui.Y > y) || (gui.Y + gui.Height < y))
 			continue;
-		// totally transparent GUI, ignore
-		if (((_GP(guis)[aa].BgColor == 0) && (_GP(guis)[aa].BgImage < 1)) || (_GP(guis)[aa].Transparency == 255))
-			continue;
-
 		// try to deal with full-width GUIs across the top
-		if (_GP(guis)[aa].X + _GP(guis)[aa].Width >= get_fixed_pixel_size(280))
+		// FIXME: using a harcoded width in pixels...
+		if (gui.X + gui.Width >= get_fixed_pixel_size(280))
 			continue;
-
-		if (xx < _GP(guis)[aa].X + _GP(guis)[aa].Width)
-			xx = _GP(guis)[aa].X + _GP(guis)[aa].Width + 2;
+		// Fix coordinates if x is inside the gui
+		if (x < gui.X + gui.Width)
+			x = gui.X + gui.Width + 2;
 	}
-	return xx;
+	return x;
 }
 
-int adjust_y_for_guis(int yy) {
+int adjust_y_for_guis(int y) {
 	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0))
-		return yy;
+		return y;
 	// If it's covered by a GUI, move it down a bit
-	for (int aa = 0; aa < _GP(game).numgui; aa++) {
-		if (!_GP(guis)[aa].IsDisplayed())
-			continue;
-		if (_GP(guis)[aa].Y > yy)
+	for (const auto &gui : _GP(guis)) {
+		if (should_skip_adjust_for_gui(gui))
 			continue;
-		// totally transparent GUI, ignore
-		if (((_GP(guis)[aa].BgColor == 0) && (_GP(guis)[aa].BgImage < 1)) || (_GP(guis)[aa].Transparency == 255))
+		// lower than the message
+		if (gui.Y > y)
 			continue;
-
 		// try to deal with full-height GUIs down the left or right
-		if (_GP(guis)[aa].Height > get_fixed_pixel_size(50))
+		// FIXME: using a harcoded height in pixels...
+		if (gui.Height > get_fixed_pixel_size(50))
 			continue;
-
-		if (yy < _GP(guis)[aa].Y + _GP(guis)[aa].Height)
-			yy = _GP(guis)[aa].Y + _GP(guis)[aa].Height + 2;
+		// Fix coordinates if y is inside the gui
+		if (y < gui.Y + gui.Height)
+			y = gui.Y + gui.Height + 2;
 	}
-	return yy;
+	return y;
 }
 
 int gui_get_interactable(int x, int y) {


Commit: 959d8946069c483c48277089c076e24e64b35d2b
    https://github.com/scummvm/scummvm/commit/959d8946069c483c48277089c076e24e64b35d2b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed first Sierra speechline not accounting for disabled gui

>From upstream 34957df168f27d6eeffc0f821f0d2632af404b89

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 42e3df5293e..ba8a2ec84c0 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2597,7 +2597,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 				if (yy < 0 && _GP(play).speech_portrait_placement) {
 					ovr_yp = _GP(play).speech_portrait_y;
 				} else if (yy < 0)
-					ovr_yp = adjust_y_for_guis(ovr_yp);
+					ovr_yp = adjust_y_for_guis(ovr_yp, true /* displayspeech is always blocking */);
 				else
 					ovr_yp = yy;
 
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 61462d637ac..3551254a0af 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -459,9 +459,9 @@ static bool should_skip_adjust_for_gui(const GUIMain &gui) {
 		((gui.BgColor == 0) && (gui.BgImage < 1)) || (gui.Transparency == 255);
 }
 
-int adjust_x_for_guis(int x, int y) {
-	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0))
-		return x;
+int adjust_x_for_guis(int x, int y, bool assume_blocking) {
+	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0 || assume_blocking))
+		return x;  // All GUI off (or will be when the message is displayed)
 	// If it's covered by a GUI, move it right a bit
 	for (const auto &gui : _GP(guis)) {
 		if (should_skip_adjust_for_gui(gui))
@@ -480,9 +480,9 @@ int adjust_x_for_guis(int x, int y) {
 	return x;
 }
 
-int adjust_y_for_guis(int y) {
-	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0))
-		return y;
+int adjust_y_for_guis(int y, bool assume_blocking) {
+	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0 || assume_blocking))
+		return y;  // All GUI off (or will be when the message is displayed)
 	// If it's covered by a GUI, move it down a bit
 	for (const auto &gui : _GP(guis)) {
 		if (should_skip_adjust_for_gui(gui))
diff --git a/engines/ags/engine/ac/gui.h b/engines/ags/engine/ac/gui.h
index 3bbeceaa0a5..188904fd437 100644
--- a/engines/ags/engine/ac/gui.h
+++ b/engines/ags/engine/ac/gui.h
@@ -75,8 +75,8 @@ void    update_gui_zorder();
 void    export_gui_controls(int ee);
 void    unexport_gui_controls(int ee);
 void    update_gui_disabled_status();
-int     adjust_x_for_guis(int xx, int yy);
-int     adjust_y_for_guis(int yy);
+int     adjust_x_for_guis(int x, int y, bool assume_blocking = false);
+int     adjust_y_for_guis(int y, bool assume_blocking = false);
 
 int     gui_get_interactable(int x, int y);
 int     gui_on_mouse_move();


Commit: b1e76179649f7446d147d7597b0ccacf4bf792d6
    https://github.com/scummvm/scummvm/commit/b1e76179649f7446d147d7597b0ccacf4bf792d6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store game's ccInstances as unique_ptrs instead

>From upstream 1f6abf4b678f77825c710d4ae18b57ff8f882c93

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index f9926afa65c..d4aa416f9f5 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -175,7 +175,7 @@ int run_dialog_script(int dialogID, int offse, int optionIndex) {
 		char func_name[100];
 		snprintf(func_name, sizeof(func_name), "_run_dialog%d", dialogID);
 		RuntimeScriptValue params[]{ optionIndex };
-		RunScriptFunction(_G(dialogScriptsInst), func_name, 1, params);
+		RunScriptFunction(_G(dialogScriptsInst).get(), func_name, 1, params);
 		result = _G(dialogScriptsInst)->returnValue;
 	} else {
 		// old dialog format
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 14def7f39ea..2c4a13ea94c 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -60,7 +60,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 	int toret;
 
 	if (includeRoom && _G(roominst)) {
-		toret = RunScriptFunction(_G(roominst), tsname, numParams, params);
+		toret = RunScriptFunction(_G(roominst).get(), tsname, numParams, params);
 		if (_G(abort_engine))
 			return -1;
 
@@ -72,7 +72,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 
 	// run script modules
 	for (auto &module_inst : _GP(moduleInst)) {
-		toret = RunScriptFunction(module_inst, tsname, numParams, params);
+		toret = RunScriptFunction(module_inst.get(), tsname, numParams, params);
 
 		if (_G(eventClaimed) == EVENT_CLAIMED) {
 			_G(eventClaimed) = eventClaimedOldValue;
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 4ee2a15b369..1daae0e32f5 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -966,7 +966,7 @@ void check_new_room() {
 void compile_room_script() {
 	cc_clear_error();
 
-	_G(roominst) = ccInstance::CreateFromScript(_GP(thisroom).CompiledScript);
+	_G(roominst).reset(ccInstance::CreateFromScript(_GP(thisroom).CompiledScript));
 	if (cc_has_error() || (_G(roominst) == nullptr)) {
 		quitprintf("Unable to create local script:\n%s", cc_get_error().ErrorString.GetCStr());
 	}
@@ -977,7 +977,7 @@ void compile_room_script() {
 	if (!_G(roominst)->ResolveImportFixups(_G(roominst)->instanceof.get()))
 		quitprintf("Unable to resolve import fixups in room script:\n%s", cc_get_error().ErrorString.GetCStr());
 
-	_G(roominstFork) = _G(roominst)->Fork();
+	_G(roominstFork).reset(_G(roominst)->Fork());
 	if (_G(roominstFork) == nullptr)
 		quitprintf("Unable to create forked room instance:\n%s", cc_get_error().ErrorString.GetCStr());
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 16d08e9f69b..4749f4df603 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -213,13 +213,13 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-PInstance ccInstance::CreateFromScript(PScript scri) {
+ccInstance *ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	std::shared_ptr<ccInstance> cinst(new ccInstance());
+	ccInstance *cinst = new ccInstance();
 	if (!cinst->_Create(scri, joined)) {
 		return nullptr;
 	}
@@ -265,7 +265,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-PInstance ccInstance::Fork() {
+ccInstance *ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
@@ -1720,6 +1720,8 @@ bool ccInstance::_Create(PScript scri, ccInstance *joined) {
 }
 
 void ccInstance::Free() {
+	// When the base script has no more "instances",
+	// remove all script exports
 	if (instanceof != nullptr) {
 		instanceof->instances--;
 		if (instanceof->instances == 0) {
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 48b71f5b077..b99ccea90e9 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -110,9 +110,6 @@ struct ScriptPosition {
 };
 
 
-struct ccInstance;
-typedef std::shared_ptr<ccInstance> PInstance;
-
 // Running instance of the script
 struct ccInstance {
 public:
@@ -165,14 +162,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static PInstance CreateFromScript(PScript script);
-	static PInstance CreateEx(PScript scri, ccInstance *joined);
+	static ccInstance *CreateFromScript(PScript script);
+	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	PInstance Fork();
+	ccInstance *Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index e5971ec5621..58a20f974c2 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -99,20 +99,4 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 		script.Params[p] = params[p];
 }
 
-void ExecutingScript::init() {
-	inst = nullptr;
-	forked = false;
-	numanother = 0;
-	numPostScriptActions = 0;
-
-	memset(postScriptActions, 0, sizeof(postScriptActions));
-	memset(postScriptActionNames, 0, sizeof(postScriptActionNames));
-	memset(postScriptSaveSlotDescription, 0, sizeof(postScriptSaveSlotDescription));
-	memset(postScriptActionData, 0, sizeof(postScriptActionData));
-}
-
-ExecutingScript::ExecutingScript() {
-	init();
-}
-
 } // namespace AGS3
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index 420c4880b82..5e6ab762ea0 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,21 +59,21 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	PInstance inst;
-	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
-	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
-	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
-	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC];
-	int  postScriptActionData[MAX_QUEUED_ACTIONS];
-	int  numPostScriptActions;
-	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
-	int  numanother;
-	bool forked;
+	ccInstance *inst = nullptr;
+	// owned fork; CHECKME: this seem unused in the current engine
+	std::unique_ptr<ccInstance> forkedInst{};
+	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS]{};
+	const char *postScriptActionNames[MAX_QUEUED_ACTIONS]{};
+	ScriptPosition postScriptActionPositions[MAX_QUEUED_ACTIONS]{};
+	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC]{};
+	int postScriptActionData[MAX_QUEUED_ACTIONS]{};
+	int numPostScriptActions = 0;
+	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS]{};
+	int numanother = 0;
 
+	ExecutingScript() = default;
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
-	void init();
-	ExecutingScript();
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 7fd9d2c10d8..21017ef4715 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -59,7 +59,7 @@ static char scfunctionname[MAX_FUNCTION_NAME_LEN + 1];
 int run_dialog_request(int parmtr) {
 	_GP(play).stop_dialog_at_end = DIALOG_RUNNING;
 	RuntimeScriptValue params[]{ parmtr };
-	RunScriptFunction(_G(gameinst), "dialog_request", 1, params);
+	RunScriptFunction(_G(gameinst).get(), "dialog_request", 1, params);
 
 	if (_GP(play).stop_dialog_at_end == DIALOG_STOP) {
 		_GP(play).stop_dialog_at_end = DIALOG_NONE;
@@ -205,25 +205,25 @@ int create_global_script() {
 
 	// NOTE: this function assumes that the module lists have their elements preallocated!
 
-	std::vector<PInstance> all_insts; // gather all to resolve exports below
+	std::vector<ccInstance *> all_insts; // gather all to resolve exports below
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
 		if (!inst)
 			return kscript_create_error;
-		_GP(moduleInst)[i] = inst;
+		_GP(moduleInst)[i].reset(inst);
 		all_insts.push_back(inst);
 	}
 
-	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
+	_G(gameinst).reset(ccInstance::CreateFromScript(_GP(gamescript)));
 	if (!_G(gameinst))
 		return kscript_create_error;
-	all_insts.push_back(_G(gameinst));
+	all_insts.push_back(_G(gameinst).get());
 
-	if (_GP(dialogScriptsScript) != nullptr) {
-		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
+	if (_GP(dialogScriptsScript)) {
+		_G(dialogScriptsInst).reset(ccInstance::CreateFromScript(_GP(dialogScriptsScript)));
 		if (!_G(dialogScriptsInst))
 			return kscript_create_error;
-		all_insts.push_back(_G(dialogScriptsInst));
+		all_insts.push_back(_G(dialogScriptsInst).get());
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
@@ -241,11 +241,11 @@ int create_global_script() {
 		if (!fork)
 			return kscript_create_error;
 
-		_GP(moduleInstFork)[module_idx] = fork;
+		_GP(moduleInstFork)[module_idx].reset(fork);
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
-	_G(gameinstFork) = _G(gameinst)->Fork();
+	_G(gameinstFork).reset(_G(gameinst)->Fork());
 	if (_G(gameinstFork) == nullptr)
 		return kscript_create_error;
 
@@ -257,7 +257,7 @@ void cancel_all_scripts() {
 	for (int i = 0; i < _G(num_scripts); ++i) {
 		auto &sc = _G(scripts)[i];
 		if (sc.inst) {
-			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
+			(sc.forkedInst) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
 		}
 		sc.numanother = 0;
 	}
@@ -268,11 +268,11 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
-		return _G(gameinst);
+		return _G(gameinst).get();
 	else if (sc_inst == kScInstRoom)
-		return _G(roominst);
+		return _G(roominst).get();
 	return nullptr;
 }
 
@@ -310,7 +310,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(PInstance sci, const char **tsname) {
+static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -322,16 +322,19 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 		cc_error("script is already in execution");
 		return -3;
 	}
-	_G(scripts)[_G(num_scripts)].init();
-	_G(scripts)[_G(num_scripts)].inst = sci;
+	ExecutingScript exscript;
 	// CHECKME: this conditional block will never run, because
 	// function would have quit earlier (deprecated functionality?)
 	if (sci->IsBeingRun()) {
-		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
-		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
+		auto fork = sci->Fork();
+		if (!fork)
 			quit("unable to fork instance for secondary script");
-		_G(scripts)[_G(num_scripts)].forked = true;
+		exscript.forkedInst.reset(fork);
+		exscript.inst = fork;
+	} else {
+		exscript.inst = sci;
 	}
+	_G(scripts)[_G(num_scripts)] = std::move(exscript);
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
 	if (_G(num_scripts) >= MAX_SCRIPT_AT_ONCE)
@@ -344,7 +347,7 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -390,8 +393,8 @@ int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const
 
 void RunScriptFunctionInModules(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
 	for (size_t i = 0; i < _G(numScriptModules); ++i)
-		RunScriptFunction(_GP(moduleInst)[i], tsname, param_count, params);
-	RunScriptFunction(_G(gameinst), tsname, param_count, params);
+		RunScriptFunction(_GP(moduleInst)[i].get(), tsname, param_count, params);
+	RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
 }
 
 int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -399,7 +402,7 @@ int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const Runtim
 	// identified by having no parameters;
 	// TODO: this is a hack, this should be defined either by function type, or as an arg
 	const bool strict_room_event = (param_count == 0);
-	int toret = RunScriptFunction(_G(roominst), tsname, param_count, params);
+	int toret = RunScriptFunction(_G(roominst).get(), tsname, param_count, params);
 	// If it's a obligatory room event, and return code means missing function - error
 	if (strict_room_event && (toret == -18))
 		quitprintf("RunScriptFunction: error %d (%s) trying to run '%s'   (Room %d)",
@@ -413,13 +416,13 @@ static int RunUnclaimableEvent(const char *tsname) {
 	const int restore_game_count_was = _G(gameHasBeenRestored);
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		if (!_GP(moduleRepExecAddr)[i].IsNull())
-			RunScriptFunction(_GP(moduleInst)[i], tsname);
+			RunScriptFunction(_GP(moduleInst)[i].get(), tsname);
 		// Break on room change or save restoration
 		if ((room_changes_was != _GP(play).room_changes) ||
 			(restore_game_count_was != _G(gameHasBeenRestored)))
 			return 0;
 	}
-	return RunScriptFunction(_G(gameinst), tsname);
+	return RunScriptFunction(_G(gameinst).get(), tsname);
 }
 
 static int RunClaimableEvent(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -429,7 +432,7 @@ static int RunClaimableEvent(const char *tsname, size_t param_count, const Runti
 	// Break on event claim
 	if (eventWasClaimed)
 		return toret;
-	return RunScriptFunction(_G(gameinst), tsname, param_count, params);
+	return RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
 }
 
 int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -448,7 +451,7 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	PInstance sci = GetScriptInstanceByType(sc_inst);
+	ccInstance *sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
@@ -456,8 +459,8 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 
 void AllocScriptModules() {
 	// NOTE: this preallocation possibly required to safeguard some algorithms
-	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
-	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInst).resize(_G(numScriptModules));
+	_GP(moduleInstFork).resize(_G(numScriptModules));
 	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
 	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
 	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
@@ -544,9 +547,9 @@ void post_script_cleanup() {
 		quit(cc_get_error().ErrorString);
 
 	ExecutingScript copyof;
-	if (_G(num_scripts) > 0) {
-		copyof = _G(scripts)[_G(num_scripts) - 1];
-		_G(scripts)[_G(num_scripts) - 1].inst.reset();
+	if (_G(num_scripts) > 0) {  // save until the end of function
+		copyof = std::move(_G(scripts)[_G(num_scripts) - 1]);
+		copyof.forkedInst.reset(); // don't need it further
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index ec8b4c24a73..2a9cbfcd8c6 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -50,12 +50,12 @@ int     run_interaction_script(InteractionScripts *nint, int evnt, int chkAny =
 int     create_global_script();
 void    cancel_all_scripts();
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -100,6 +100,19 @@ void    can_run_delayed_command();
 bool    get_script_position(ScriptPosition &script_pos);
 AGS::Shared::String cc_get_callstack(int max_lines = INT_MAX);
 
+// [ikm] we keep ccInstances saved in unique_ptrs globally for now
+// (e.g. as opposed to shared_ptrs), because the script handling part of the
+// engine is quite fragile and prone to errors whenever the instance is not
+// **deleted** in precise time. This is related to:
+// - ccScript's "instances" counting, which affects script exports reg/unreg;
+// - loadedInstances array.
+// One of the examples is the save restoration, that may occur in the midst
+// of a post-script cleanup process, whilst the engine's stack still has
+// references to the ccInstances that are going to be deleted on cleanup.
+// Ideally, this part of the engine should be refactored awhole with a goal
+// to make it safe and consistent.
+typedef std::unique_ptr<ccInstance> UInstance;
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index bddfad85901..31a10802bb8 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -369,8 +369,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<PInstance>();
-	_moduleInstFork = new std::vector<PInstance>();
+	_moduleInst = new std::vector<UInstance>();
+	_moduleInstFork = new std::vector<UInstance>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d24260e7a12..4a05ac85f00 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1260,11 +1260,11 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	PInstance _gameinst;
-	PInstance _roominst;
-	PInstance _dialogScriptsInst;
-	PInstance _gameinstFork;
-	PInstance _roominstFork;
+	UInstance _gameinst;
+	UInstance _roominst;
+	UInstance _dialogScriptsInst;
+	UInstance _gameinstFork;
+	UInstance _roominstFork;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1286,8 +1286,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<PInstance> *_moduleInst;
-	std::vector<PInstance> *_moduleInstFork;
+	std::vector<UInstance> *_moduleInst;
+	std::vector<UInstance> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 400a9ca2f1f..4e24c330797 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -601,16 +601,18 @@ int IAGSEngine::IsSpriteAlphaBlended(int32 slot) {
 void IAGSEngine::DisableSound() {
 	shutdown_sound();
 }
+
 int IAGSEngine::CanRunScriptFunctionNow() {
 	if (_G(inside_script))
 		return 0;
 	return 1;
 }
+
 int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2, long arg3) {
 	if (_G(inside_script))
 		return -300;
 
-	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 253187f4e26887c46e301a46dbc02eb19af097ef
    https://github.com/scummvm/scummvm/commit/253187f4e26887c46e301a46dbc02eb19af097ef
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
Revert "AGS: Engine: store game's ccInstances as unique_ptrs instead"

This reverts commit 547cd40186876f17914f6a96261aaf5c4dfc21b4.

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index d4aa416f9f5..f9926afa65c 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -175,7 +175,7 @@ int run_dialog_script(int dialogID, int offse, int optionIndex) {
 		char func_name[100];
 		snprintf(func_name, sizeof(func_name), "_run_dialog%d", dialogID);
 		RuntimeScriptValue params[]{ optionIndex };
-		RunScriptFunction(_G(dialogScriptsInst).get(), func_name, 1, params);
+		RunScriptFunction(_G(dialogScriptsInst), func_name, 1, params);
 		result = _G(dialogScriptsInst)->returnValue;
 	} else {
 		// old dialog format
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 2c4a13ea94c..14def7f39ea 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -60,7 +60,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 	int toret;
 
 	if (includeRoom && _G(roominst)) {
-		toret = RunScriptFunction(_G(roominst).get(), tsname, numParams, params);
+		toret = RunScriptFunction(_G(roominst), tsname, numParams, params);
 		if (_G(abort_engine))
 			return -1;
 
@@ -72,7 +72,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 
 	// run script modules
 	for (auto &module_inst : _GP(moduleInst)) {
-		toret = RunScriptFunction(module_inst.get(), tsname, numParams, params);
+		toret = RunScriptFunction(module_inst, tsname, numParams, params);
 
 		if (_G(eventClaimed) == EVENT_CLAIMED) {
 			_G(eventClaimed) = eventClaimedOldValue;
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 1daae0e32f5..4ee2a15b369 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -966,7 +966,7 @@ void check_new_room() {
 void compile_room_script() {
 	cc_clear_error();
 
-	_G(roominst).reset(ccInstance::CreateFromScript(_GP(thisroom).CompiledScript));
+	_G(roominst) = ccInstance::CreateFromScript(_GP(thisroom).CompiledScript);
 	if (cc_has_error() || (_G(roominst) == nullptr)) {
 		quitprintf("Unable to create local script:\n%s", cc_get_error().ErrorString.GetCStr());
 	}
@@ -977,7 +977,7 @@ void compile_room_script() {
 	if (!_G(roominst)->ResolveImportFixups(_G(roominst)->instanceof.get()))
 		quitprintf("Unable to resolve import fixups in room script:\n%s", cc_get_error().ErrorString.GetCStr());
 
-	_G(roominstFork).reset(_G(roominst)->Fork());
+	_G(roominstFork) = _G(roominst)->Fork();
 	if (_G(roominstFork) == nullptr)
 		quitprintf("Unable to create forked room instance:\n%s", cc_get_error().ErrorString.GetCStr());
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 4749f4df603..16d08e9f69b 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -213,13 +213,13 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-ccInstance *ccInstance::CreateFromScript(PScript scri) {
+PInstance ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	ccInstance *cinst = new ccInstance();
+	std::shared_ptr<ccInstance> cinst(new ccInstance());
 	if (!cinst->_Create(scri, joined)) {
 		return nullptr;
 	}
@@ -265,7 +265,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-ccInstance *ccInstance::Fork() {
+PInstance ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
@@ -1720,8 +1720,6 @@ bool ccInstance::_Create(PScript scri, ccInstance *joined) {
 }
 
 void ccInstance::Free() {
-	// When the base script has no more "instances",
-	// remove all script exports
 	if (instanceof != nullptr) {
 		instanceof->instances--;
 		if (instanceof->instances == 0) {
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index b99ccea90e9..48b71f5b077 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -110,6 +110,9 @@ struct ScriptPosition {
 };
 
 
+struct ccInstance;
+typedef std::shared_ptr<ccInstance> PInstance;
+
 // Running instance of the script
 struct ccInstance {
 public:
@@ -162,14 +165,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static ccInstance *CreateFromScript(PScript script);
-	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
+	static PInstance CreateFromScript(PScript script);
+	static PInstance CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	ccInstance *Fork();
+	PInstance Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index 58a20f974c2..e5971ec5621 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -99,4 +99,20 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 		script.Params[p] = params[p];
 }
 
+void ExecutingScript::init() {
+	inst = nullptr;
+	forked = false;
+	numanother = 0;
+	numPostScriptActions = 0;
+
+	memset(postScriptActions, 0, sizeof(postScriptActions));
+	memset(postScriptActionNames, 0, sizeof(postScriptActionNames));
+	memset(postScriptSaveSlotDescription, 0, sizeof(postScriptSaveSlotDescription));
+	memset(postScriptActionData, 0, sizeof(postScriptActionData));
+}
+
+ExecutingScript::ExecutingScript() {
+	init();
+}
+
 } // namespace AGS3
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index 5e6ab762ea0..420c4880b82 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,21 +59,21 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	ccInstance *inst = nullptr;
-	// owned fork; CHECKME: this seem unused in the current engine
-	std::unique_ptr<ccInstance> forkedInst{};
-	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS]{};
-	const char *postScriptActionNames[MAX_QUEUED_ACTIONS]{};
-	ScriptPosition postScriptActionPositions[MAX_QUEUED_ACTIONS]{};
-	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC]{};
-	int postScriptActionData[MAX_QUEUED_ACTIONS]{};
-	int numPostScriptActions = 0;
-	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS]{};
-	int numanother = 0;
+	PInstance inst;
+	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
+	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
+	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
+	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC];
+	int  postScriptActionData[MAX_QUEUED_ACTIONS];
+	int  numPostScriptActions;
+	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
+	int  numanother;
+	bool forked;
 
-	ExecutingScript() = default;
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
+	void init();
+	ExecutingScript();
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 21017ef4715..7fd9d2c10d8 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -59,7 +59,7 @@ static char scfunctionname[MAX_FUNCTION_NAME_LEN + 1];
 int run_dialog_request(int parmtr) {
 	_GP(play).stop_dialog_at_end = DIALOG_RUNNING;
 	RuntimeScriptValue params[]{ parmtr };
-	RunScriptFunction(_G(gameinst).get(), "dialog_request", 1, params);
+	RunScriptFunction(_G(gameinst), "dialog_request", 1, params);
 
 	if (_GP(play).stop_dialog_at_end == DIALOG_STOP) {
 		_GP(play).stop_dialog_at_end = DIALOG_NONE;
@@ -205,25 +205,25 @@ int create_global_script() {
 
 	// NOTE: this function assumes that the module lists have their elements preallocated!
 
-	std::vector<ccInstance *> all_insts; // gather all to resolve exports below
+	std::vector<PInstance> all_insts; // gather all to resolve exports below
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
 		if (!inst)
 			return kscript_create_error;
-		_GP(moduleInst)[i].reset(inst);
+		_GP(moduleInst)[i] = inst;
 		all_insts.push_back(inst);
 	}
 
-	_G(gameinst).reset(ccInstance::CreateFromScript(_GP(gamescript)));
+	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
 	if (!_G(gameinst))
 		return kscript_create_error;
-	all_insts.push_back(_G(gameinst).get());
+	all_insts.push_back(_G(gameinst));
 
-	if (_GP(dialogScriptsScript)) {
-		_G(dialogScriptsInst).reset(ccInstance::CreateFromScript(_GP(dialogScriptsScript)));
+	if (_GP(dialogScriptsScript) != nullptr) {
+		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
 		if (!_G(dialogScriptsInst))
 			return kscript_create_error;
-		all_insts.push_back(_G(dialogScriptsInst).get());
+		all_insts.push_back(_G(dialogScriptsInst));
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
@@ -241,11 +241,11 @@ int create_global_script() {
 		if (!fork)
 			return kscript_create_error;
 
-		_GP(moduleInstFork)[module_idx].reset(fork);
+		_GP(moduleInstFork)[module_idx] = fork;
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
-	_G(gameinstFork).reset(_G(gameinst)->Fork());
+	_G(gameinstFork) = _G(gameinst)->Fork();
 	if (_G(gameinstFork) == nullptr)
 		return kscript_create_error;
 
@@ -257,7 +257,7 @@ void cancel_all_scripts() {
 	for (int i = 0; i < _G(num_scripts); ++i) {
 		auto &sc = _G(scripts)[i];
 		if (sc.inst) {
-			(sc.forkedInst) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
+			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
 		}
 		sc.numanother = 0;
 	}
@@ -268,11 +268,11 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
-		return _G(gameinst).get();
+		return _G(gameinst);
 	else if (sc_inst == kScInstRoom)
-		return _G(roominst).get();
+		return _G(roominst);
 	return nullptr;
 }
 
@@ -310,7 +310,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(ccInstance *sci, const char **tsname) {
+static int PrepareTextScript(PInstance sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -322,19 +322,16 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 		cc_error("script is already in execution");
 		return -3;
 	}
-	ExecutingScript exscript;
+	_G(scripts)[_G(num_scripts)].init();
+	_G(scripts)[_G(num_scripts)].inst = sci;
 	// CHECKME: this conditional block will never run, because
 	// function would have quit earlier (deprecated functionality?)
 	if (sci->IsBeingRun()) {
-		auto fork = sci->Fork();
-		if (!fork)
+		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
+		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
 			quit("unable to fork instance for secondary script");
-		exscript.forkedInst.reset(fork);
-		exscript.inst = fork;
-	} else {
-		exscript.inst = sci;
+		_G(scripts)[_G(num_scripts)].forked = true;
 	}
-	_G(scripts)[_G(num_scripts)] = std::move(exscript);
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
 	if (_G(num_scripts) >= MAX_SCRIPT_AT_ONCE)
@@ -347,7 +344,7 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -393,8 +390,8 @@ int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, cons
 
 void RunScriptFunctionInModules(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
 	for (size_t i = 0; i < _G(numScriptModules); ++i)
-		RunScriptFunction(_GP(moduleInst)[i].get(), tsname, param_count, params);
-	RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
+		RunScriptFunction(_GP(moduleInst)[i], tsname, param_count, params);
+	RunScriptFunction(_G(gameinst), tsname, param_count, params);
 }
 
 int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -402,7 +399,7 @@ int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const Runtim
 	// identified by having no parameters;
 	// TODO: this is a hack, this should be defined either by function type, or as an arg
 	const bool strict_room_event = (param_count == 0);
-	int toret = RunScriptFunction(_G(roominst).get(), tsname, param_count, params);
+	int toret = RunScriptFunction(_G(roominst), tsname, param_count, params);
 	// If it's a obligatory room event, and return code means missing function - error
 	if (strict_room_event && (toret == -18))
 		quitprintf("RunScriptFunction: error %d (%s) trying to run '%s'   (Room %d)",
@@ -416,13 +413,13 @@ static int RunUnclaimableEvent(const char *tsname) {
 	const int restore_game_count_was = _G(gameHasBeenRestored);
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		if (!_GP(moduleRepExecAddr)[i].IsNull())
-			RunScriptFunction(_GP(moduleInst)[i].get(), tsname);
+			RunScriptFunction(_GP(moduleInst)[i], tsname);
 		// Break on room change or save restoration
 		if ((room_changes_was != _GP(play).room_changes) ||
 			(restore_game_count_was != _G(gameHasBeenRestored)))
 			return 0;
 	}
-	return RunScriptFunction(_G(gameinst).get(), tsname);
+	return RunScriptFunction(_G(gameinst), tsname);
 }
 
 static int RunClaimableEvent(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -432,7 +429,7 @@ static int RunClaimableEvent(const char *tsname, size_t param_count, const Runti
 	// Break on event claim
 	if (eventWasClaimed)
 		return toret;
-	return RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
+	return RunScriptFunction(_G(gameinst), tsname, param_count, params);
 }
 
 int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -451,7 +448,7 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	ccInstance *sci = GetScriptInstanceByType(sc_inst);
+	PInstance sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
@@ -459,8 +456,8 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 
 void AllocScriptModules() {
 	// NOTE: this preallocation possibly required to safeguard some algorithms
-	_GP(moduleInst).resize(_G(numScriptModules));
-	_GP(moduleInstFork).resize(_G(numScriptModules));
+	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
 	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
 	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
 	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
@@ -547,9 +544,9 @@ void post_script_cleanup() {
 		quit(cc_get_error().ErrorString);
 
 	ExecutingScript copyof;
-	if (_G(num_scripts) > 0) {  // save until the end of function
-		copyof = std::move(_G(scripts)[_G(num_scripts) - 1]);
-		copyof.forkedInst.reset(); // don't need it further
+	if (_G(num_scripts) > 0) {
+		copyof = _G(scripts)[_G(num_scripts) - 1];
+		_G(scripts)[_G(num_scripts) - 1].inst.reset();
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index 2a9cbfcd8c6..ec8b4c24a73 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -50,12 +50,12 @@ int     run_interaction_script(InteractionScripts *nint, int evnt, int chkAny =
 int     create_global_script();
 void    cancel_all_scripts();
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -100,19 +100,6 @@ void    can_run_delayed_command();
 bool    get_script_position(ScriptPosition &script_pos);
 AGS::Shared::String cc_get_callstack(int max_lines = INT_MAX);
 
-// [ikm] we keep ccInstances saved in unique_ptrs globally for now
-// (e.g. as opposed to shared_ptrs), because the script handling part of the
-// engine is quite fragile and prone to errors whenever the instance is not
-// **deleted** in precise time. This is related to:
-// - ccScript's "instances" counting, which affects script exports reg/unreg;
-// - loadedInstances array.
-// One of the examples is the save restoration, that may occur in the midst
-// of a post-script cleanup process, whilst the engine's stack still has
-// references to the ccInstances that are going to be deleted on cleanup.
-// Ideally, this part of the engine should be refactored awhole with a goal
-// to make it safe and consistent.
-typedef std::unique_ptr<ccInstance> UInstance;
-
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 31a10802bb8..bddfad85901 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -369,8 +369,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<UInstance>();
-	_moduleInstFork = new std::vector<UInstance>();
+	_moduleInst = new std::vector<PInstance>();
+	_moduleInstFork = new std::vector<PInstance>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 4a05ac85f00..d24260e7a12 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1260,11 +1260,11 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	UInstance _gameinst;
-	UInstance _roominst;
-	UInstance _dialogScriptsInst;
-	UInstance _gameinstFork;
-	UInstance _roominstFork;
+	PInstance _gameinst;
+	PInstance _roominst;
+	PInstance _dialogScriptsInst;
+	PInstance _gameinstFork;
+	PInstance _roominstFork;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1286,8 +1286,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<UInstance> *_moduleInst;
-	std::vector<UInstance> *_moduleInstFork;
+	std::vector<PInstance> *_moduleInst;
+	std::vector<PInstance> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 4e24c330797..400a9ca2f1f 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -601,18 +601,16 @@ int IAGSEngine::IsSpriteAlphaBlended(int32 slot) {
 void IAGSEngine::DisableSound() {
 	shutdown_sound();
 }
-
 int IAGSEngine::CanRunScriptFunctionNow() {
 	if (_G(inside_script))
 		return 0;
 	return 1;
 }
-
 int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2, long arg3) {
 	if (_G(inside_script))
 		return -300;
 
-	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 8153a591210c0ffa426113b860923fa4b077dc3a
    https://github.com/scummvm/scummvm/commit/8153a591210c0ffa426113b860923fa4b077dc3a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
Revert "AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances"

This reverts commit 016323f1f9797827a36a77ae3a5859b56d3b0998.

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 785f6b19fc3..19c1d92cb7c 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -373,9 +373,36 @@ void unload_game() {
 
 	// Free all script instances and script modules
 	ccInstance::FreeInstanceStack();
+	delete _G(gameinstFork);
+	delete _G(gameinst);
+	_G(gameinstFork) = nullptr;
+	_G(gameinst) = nullptr;
+	_GP(gamescript).reset();
+
+	delete _G(dialogScriptsInst);
+	_G(dialogScriptsInst) = nullptr;
+	_GP(dialogScriptsScript).reset();
+
+	for (size_t i = 0; i < _G(numScriptModules); ++i) {
+		delete _GP(moduleInstFork)[i];
+		delete _GP(moduleInst)[i];
+		_GP(scriptModules)[i].reset();
+	}
 
-	FreeAllScriptInstances();
-	FreeGlobalScripts();
+	_GP(moduleInstFork).resize(0);
+	_GP(moduleInst).resize(0);
+	_GP(scriptModules).resize(0);
+	_GP(repExecAlways).moduleHasFunction.resize(0);
+	_GP(lateRepExecAlways).moduleHasFunction.resize(0);
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(0);
+	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(0);
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(0);
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(0);
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(0);
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(0);
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(0);
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(0);
+	_G(numScriptModules) = 0;
 
 	_GP(charextra).clear();
 	_GP(mls).clear();
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 4ee2a15b369..0e8d1e87637 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -265,7 +265,10 @@ void unload_old_room() {
 	if (_G(croom) == nullptr) ;
 	else if (_G(roominst) != nullptr) {
 		save_room_data_segment();
-		FreeRoomScriptInstance();
+		delete _G(roominstFork);
+		delete _G(roominst);
+		_G(roominstFork) = nullptr;
+		_G(roominst) = nullptr;
 	} else _G(croom)->tsdatasize = 0;
 	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
 	_GP(play).bg_frame = 0;
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 0c6e013f3cd..5b788de722f 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -310,6 +310,25 @@ void LoadLipsyncData() {
 	Debug::Printf(kDbgMsg_Info, "Lipsync data found and loaded");
 }
 
+void AllocScriptModules() {
+	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
+	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
+	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	for (auto &val : _GP(moduleRepExecAddr)) {
+		val.Invalidate();
+	}
+}
+
 HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver) {
 	GameSetupStruct &game = ents.Game;
 	const ScriptAPIVersion base_api = (ScriptAPIVersion)game.options[OPT_BASESCRIPTAPI];
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index a6171b8a4c9..18ea1f4de99 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -355,12 +355,26 @@ void DoBeforeRestore(PreservedParams &pp) {
 
 	// preserve script data sizes and cleanup scripts
 	pp.GlScDataSize = _G(gameinst)->globaldatasize;
+	delete _G(gameinstFork);
+	delete _G(gameinst);
+	_G(gameinstFork) = nullptr;
+	_G(gameinst) = nullptr;
 	pp.ScMdDataSize.resize(_G(numScriptModules));
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		pp.ScMdDataSize[i] = _GP(moduleInst)[i]->globaldatasize;
+		delete _GP(moduleInstFork)[i];
+		delete _GP(moduleInst)[i];
+		_GP(moduleInstFork)[i] = nullptr;
+		_GP(moduleInst)[i] = nullptr;
 	}
 
-	FreeAllScriptInstances();
+	delete _G(roominstFork);
+	delete _G(roominst);
+	_G(roominstFork) = nullptr;
+	_G(roominst) = nullptr;
+
+	delete _G(dialogScriptsInst);
+	_G(dialogScriptsInst) = nullptr;
 
 	// reset saved room states
 	resetRoomStatuses();
@@ -466,7 +480,6 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	update_gui_zorder();
 
-	AllocScriptModules();
 	if (create_global_script()) {
 		return new SavegameError(kSvgErr_GameObjectInitFailed,
 		                         String::FromFormat("Unable to recreate global script: %s", cc_get_error().ErrorString.GetCStr()));
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 16d08e9f69b..e0a18fb6e5a 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -213,16 +213,18 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-PInstance ccInstance::CreateFromScript(PScript scri) {
+ccInstance *ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	std::shared_ptr<ccInstance> cinst(new ccInstance());
+	ccInstance *cinst = new ccInstance();
 	if (!cinst->_Create(scri, joined)) {
+		delete cinst;
 		return nullptr;
 	}
+
 	return cinst;
 }
 
@@ -265,7 +267,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-PInstance ccInstance::Fork() {
+ccInstance *ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 48b71f5b077..d74d2e6f485 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -109,10 +109,6 @@ struct ScriptPosition {
 	int32_t         Line;
 };
 
-
-struct ccInstance;
-typedef std::shared_ptr<ccInstance> PInstance;
-
 // Running instance of the script
 struct ccInstance {
 public:
@@ -165,14 +161,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static PInstance CreateFromScript(PScript script);
-	static PInstance CreateEx(PScript scri, ccInstance *joined);
+	static ccInstance *CreateFromScript(PScript script);
+	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	PInstance Fork();
+	ccInstance *Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index e5971ec5621..72d9f4a7298 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -101,7 +101,7 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 
 void ExecutingScript::init() {
 	inst = nullptr;
-	forked = false;
+	forked = 0;
 	numanother = 0;
 	numPostScriptActions = 0;
 
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index 420c4880b82..c7f77af13e1 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,7 +59,7 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	PInstance inst;
+	ccInstance *inst;
 	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
 	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
 	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
@@ -68,7 +68,7 @@ struct ExecutingScript {
 	int  numPostScriptActions;
 	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
 	int  numanother;
-	bool forked;
+	int8 forked;
 
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 7fd9d2c10d8..86f72b59988 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -90,18 +90,18 @@ void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) {
 	// run modules
 	// modules need a forkedinst for this to work
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i].get(), funcToRun, funcToRun->moduleHasFunction[i]);
+		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i], funcToRun, funcToRun->moduleHasFunction[i]);
 
 		if (room_changes_was != _GP(play).room_changes)
 			return;
 	}
 
-	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork).get(), funcToRun, funcToRun->globalScriptHasFunction);
+	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork), funcToRun, funcToRun->globalScriptHasFunction);
 
 	if (room_changes_was != _GP(play).room_changes || _G(abort_engine))
 		return;
 
-	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork).get(), funcToRun, funcToRun->roomHasFunction);
+	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork), funcToRun, funcToRun->roomHasFunction);
 }
 
 //-----------------------------------------------------------
@@ -203,31 +203,29 @@ int create_global_script() {
 
 	ccSetOption(SCOPT_AUTOIMPORT, 1);
 
-	// NOTE: this function assumes that the module lists have their elements preallocated!
-
-	std::vector<PInstance> all_insts; // gather all to resolve exports below
+	std::vector<ccInstance *> instances_for_resolving;
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
-		if (!inst)
+		_GP(moduleInst)[i] = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
+		if (_GP(moduleInst)[i] == nullptr)
 			return kscript_create_error;
-		_GP(moduleInst)[i] = inst;
-		all_insts.push_back(inst);
+		instances_for_resolving.push_back(_GP(moduleInst)[i]);
 	}
 
 	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
-	if (!_G(gameinst))
+	if (_G(gameinst) == nullptr)
 		return kscript_create_error;
-	all_insts.push_back(_G(gameinst));
+	instances_for_resolving.push_back(_G(gameinst));
 
 	if (_GP(dialogScriptsScript) != nullptr) {
 		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
-		if (!_G(dialogScriptsInst))
+		if (_G(dialogScriptsInst) == nullptr)
 			return kscript_create_error;
-		all_insts.push_back(_G(dialogScriptsInst));
+		instances_for_resolving.push_back(_G(dialogScriptsInst));
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
-	for (auto &inst : all_insts) {
+	for (size_t instance_idx = 0; instance_idx < instances_for_resolving.size(); instance_idx++) {
+		auto inst = instances_for_resolving[instance_idx];
 		if (!inst->ResolveScriptImports(inst->instanceof.get()))
 			return kscript_create_error;
 		if (!inst->ResolveImportFixups(inst->instanceof.get()))
@@ -237,11 +235,10 @@ int create_global_script() {
 	// Create the forks for 'repeatedly_execute_always' after resolving
 	// because they copy their respective originals including the resolve information
 	for (size_t module_idx = 0; module_idx < _G(numScriptModules); module_idx++) {
-		auto fork = _GP(moduleInst)[module_idx]->Fork();
-		if (!fork)
+		_GP(moduleInstFork)[module_idx] = _GP(moduleInst)[module_idx]->Fork();
+		if (_GP(moduleInstFork)[module_idx] == nullptr)
 			return kscript_create_error;
 
-		_GP(moduleInstFork)[module_idx] = fork;
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
@@ -254,12 +251,12 @@ int create_global_script() {
 }
 
 void cancel_all_scripts() {
-	for (int i = 0; i < _G(num_scripts); ++i) {
-		auto &sc = _G(scripts)[i];
-		if (sc.inst) {
-			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
-		}
-		sc.numanother = 0;
+	for (int aa = 0; aa < _G(num_scripts); aa++) {
+		if (_G(scripts)[aa].forked)
+			_G(scripts)[aa].inst->AbortAndDestroy();
+		else
+			_G(scripts)[aa].inst->Abort();
+		_G(scripts)[aa].numanother = 0;
 	}
 	_G(num_scripts) = 0;
 	// in case the script is running on non-blocking thread (rep-exec-always etc)
@@ -268,7 +265,7 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
 		return _G(gameinst);
 	else if (sc_inst == kScInstRoom)
@@ -310,7 +307,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(PInstance sci, const char **tsname) {
+static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -330,7 +327,7 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
 		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
 			quit("unable to fork instance for secondary script");
-		_G(scripts)[_G(num_scripts)].forked = true;
+		_G(scripts)[_G(num_scripts)].forked = 1;
 	}
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
@@ -344,7 +341,7 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -448,70 +445,12 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	PInstance sci = GetScriptInstanceByType(sc_inst);
+	ccInstance *sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
 }
 
-void AllocScriptModules() {
-	// NOTE: this preallocation possibly required to safeguard some algorithms
-	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
-	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
-	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
-	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	for (auto &val : _GP(moduleRepExecAddr)) {
-		val.Invalidate();
-	}
-}
-
-void FreeAllScriptInstances() {
-	FreeRoomScriptInstance();
-
-	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
-	// or bad things will happen; TODO: investigate and make this less fragile
-	_G(gameinstFork).reset();
-	_G(gameinst).reset();
-	_G(dialogScriptsInst).reset();
-	_GP(moduleInstFork).clear();
-	_GP(moduleInst).clear();
-}
-
-void FreeRoomScriptInstance() {
-	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
-	// or bad things will happen; TODO: investigate and make this less fragile
-	_G(roominstFork).reset();
-	_G(roominst).reset();
-}
-
-void FreeGlobalScripts() {
-	_G(numScriptModules) = 0;
-
-	_GP(gamescript).reset();
-	_GP(scriptModules).clear();
-	_GP(dialogScriptsScript).reset();
-
-	_GP(repExecAlways).moduleHasFunction.clear();
-	_GP(lateRepExecAlways).moduleHasFunction.clear();
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.clear();
-	_GP(renderDialogOptionsFunc).moduleHasFunction.clear();
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.clear();
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.clear();
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.clear();
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.clear();
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.clear();
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.clear();
-}
-
 String GetScriptName(ccInstance *sci) {
 	// TODO: have script name a ccScript's member?
 	// TODO: check script modules too?
@@ -546,7 +485,8 @@ void post_script_cleanup() {
 	ExecutingScript copyof;
 	if (_G(num_scripts) > 0) {
 		copyof = _G(scripts)[_G(num_scripts) - 1];
-		_G(scripts)[_G(num_scripts) - 1].inst.reset();
+		if (_G(scripts)[_G(num_scripts) - 1].forked)
+			delete _G(scripts)[_G(num_scripts) - 1].inst;
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
@@ -633,6 +573,7 @@ void post_script_cleanup() {
 		if ((_G(displayed_room) != old_room_number) || (_G(load_new_game)))
 			break;
 	}
+	copyof.numanother = 0;
 
 }
 
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index ec8b4c24a73..01b6edff922 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -22,7 +22,6 @@
 #ifndef AGS_ENGINE_SCRIPT_SCRIPT_H
 #define AGS_ENGINE_SCRIPT_SCRIPT_H
 
-#include "common/std/memory.h"
 #include "common/std/vector.h"
 
 #include "ags/engine/script/cc_instance.h"
@@ -50,12 +49,12 @@ int     run_interaction_script(InteractionScripts *nint, int evnt, int chkAny =
 int     create_global_script();
 void    cancel_all_scripts();
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -69,16 +68,6 @@ int     RunScriptFunctionInRoom(const char *tsname, size_t param_count = 0,
 int     RunScriptFunctionAuto(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 
-// Preallocates script module instances
-void	AllocScriptModules();
-// Delete all the script instance objects
-void	FreeAllScriptInstances();
-// Delete only the current room script instance
-void	FreeRoomScriptInstance();
-// Deletes all the global scripts and modules;
-// this frees all of the bytecode and runtime script memory.
-void	FreeGlobalScripts();
-
 AGS::Shared::String GetScriptName(ccInstance *sci);
 
 //=============================================================================
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index bddfad85901..bab3cbebdae 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -369,8 +369,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<PInstance>();
-	_moduleInstFork = new std::vector<PInstance>();
+	_moduleInst = new std::vector<ccInstance *>();
+	_moduleInstFork = new std::vector<ccInstance *>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d24260e7a12..d307e4c5736 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1260,11 +1260,9 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	PInstance _gameinst;
-	PInstance _roominst;
-	PInstance _dialogScriptsInst;
-	PInstance _gameinstFork;
-	PInstance _roominstFork;
+	ccInstance *_gameinst = nullptr, *_roominst = nullptr;
+	ccInstance *_dialogScriptsInst = nullptr;
+	ccInstance *_gameinstFork = nullptr, *_roominstFork = nullptr;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1286,8 +1284,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<PInstance> *_moduleInst;
-	std::vector<PInstance> *_moduleInstFork;
+	std::vector<ccInstance *> *_moduleInst;
+	std::vector<ccInstance *> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 400a9ca2f1f..f53caea5afb 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -610,7 +610,7 @@ int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int
 	if (_G(inside_script))
 		return -300;
 
-	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 6b2af4cced174759c727ef8d302fc72ff1694e55
    https://github.com/scummvm/scummvm/commit/6b2af4cced174759c727ef8d302fc72ff1694e55
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: picked out plugin's save / restore data event runs

This is not precisely a part of a particular save format, but rather a command to plugin callback system.
In theory it might be also possible to implement step by step reading and writing with some engine's action in between.

>From upstream df0151983e077e577eee8691a61d03be44c24cbd

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


diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 18ea1f4de99..ebee94905e8 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -761,6 +761,20 @@ void SaveGameState(Stream *out) {
 	SavegameComponents::WriteAllCommon(out);
 }
 
+void ReadPluginSaveData(Stream *in) {
+	auto pluginFileHandle = AGSE_RESTOREGAME;
+	pl_set_file_handle(pluginFileHandle, in);
+	pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle);
+	pl_clear_file_handle();
+}
+
+void WritePluginSaveData(Stream *out) {
+	auto pluginFileHandle = AGSE_SAVEGAME;
+	pl_set_file_handle(pluginFileHandle, out);
+	pl_run_plugin_hooks(AGSE_SAVEGAME, pluginFileHandle);
+	pl_clear_file_handle();
+}
+
 } // namespace Engine
 } // namespace AGS
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 0f9609c09a6..1dc8e1b6d2c 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -53,7 +53,6 @@
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_slider.h"
 #include "ags/shared/gui/gui_textbox.h"
-#include "ags/plugins/ags_plugin.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/script.h"
@@ -999,18 +998,12 @@ HSaveError ReadManagedPool(Stream *in, int32_t cmp_ver, const PreservedParams &
 }
 
 HSaveError WritePluginData(Stream *out) {
-	auto pluginFileHandle = AGSE_SAVEGAME;
-	pl_set_file_handle(pluginFileHandle, out);
-	pl_run_plugin_hooks(AGSE_SAVEGAME, pluginFileHandle);
-	pl_clear_file_handle();
+	WritePluginSaveData(out);
 	return HSaveError::None();
 }
 
 HSaveError ReadPluginData(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
-	auto pluginFileHandle = AGSE_RESTOREGAME;
-	pl_set_file_handle(pluginFileHandle, in);
-	pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle);
-	pl_clear_file_handle();
+	ReadPluginSaveData(in);
 	return HSaveError::None();
 }
 
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index 8734527c2da..69941808829 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -34,6 +34,7 @@ namespace AGS {
 namespace Engine {
 
 using AGS::Shared::Bitmap;
+using AGS::Shared::Stream;
 
 typedef std::shared_ptr<Bitmap> PBitmap;
 
@@ -138,6 +139,13 @@ struct RestoredData {
 	RestoredData();
 };
 
+// Runs plugin events, requesting to read save data from the given stream.
+// NOTE: there's no error check in this function, because plugin API currently
+// does not let plugins report any errors when restoring their saved data.
+void ReadPluginSaveData(Stream *in);
+// Runs plugin events, requesting to write save data into the given stream.
+void WritePluginSaveData(Stream *out);
+
 } // namespace Engine
 } // namespace AGS
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index df4a4441d0a..b86456acc93 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -58,7 +58,6 @@
 #include "ags/shared/gui/gui_slider.h"
 #include "ags/shared/gui/gui_textbox.h"
 #include "ags/engine/media/audio/audio.h"
-#include "ags/plugins/ags_plugin.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/script/cc_common.h"
@@ -512,10 +511,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 	if (!err)
 		return err;
 
-	auto pluginFileHandle = AGSE_RESTOREGAME;
-	pl_set_file_handle(pluginFileHandle, in);
-	pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle);
-	pl_clear_file_handle();
+	ReadPluginSaveData(in);
 	if (static_cast<uint32_t>(in->ReadInt32()) != MAGICNUMBER)
 		return new SavegameError(kSvgErr_InconsistentPlugin);
 


Commit: 5bb17700193622f9a81b07f595226182933a2c21
    https://github.com/scummvm/scummvm/commit/5bb17700193622f9a81b07f595226182933a2c21
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed IAGSEngine::GetFontType() not using proper font test

>From upstream fda06dba238c19f4ea226417933751b640afd39f

Changed paths:
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index f53caea5afb..7566f520c58 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -562,7 +562,7 @@ int IAGSEngine::GetFontType(int32 fontNum) {
 	if ((fontNum < 0) || (fontNum >= _GP(game).numfonts))
 		return FNT_INVALID;
 
-	if (font_supports_extended_characters(fontNum))
+	if (is_bitmap_font(fontNum))
 		return FNT_TTF;
 
 	return FNT_SCI;


Commit: 3d981f1159ce485de378e27ad7d37464f8a9005a
    https://github.com/scummvm/scummvm/commit/3d981f1159ce485de378e27ad7d37464f8a9005a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: SpriteCache: separated Remove() and Dispose() methods for convenience

>From upstream 92c0f64e47c782e2532760a6b3a10a1b0b5978b6

Changed paths:
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 0eaa4ae69da..1300bb01139 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -462,7 +462,7 @@ void free_dynamic_sprite(int gotSlot) {
 	if ((_GP(game).SpriteInfos[gotSlot].Flags & SPF_DYNAMICALLOC) == 0)
 		quitprintf("!DeleteSprite: Attempted to free static sprite %d that was not loaded by the script", gotSlot);
 
-	_GP(spriteset).RemoveSprite(gotSlot, true);
+	_GP(spriteset).DisposeSprite(gotSlot);
 
 	_GP(game).SpriteInfos[gotSlot].Flags = 0;
 	_GP(game).SpriteInfos[gotSlot].Width = 0;
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 24bee58940f..2f2974e2da8 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -143,14 +143,22 @@ void SpriteCache::SubstituteBitmap(sprkey_t index, Bitmap *sprite) {
 	SprCacheLog("SubstituteBitmap: %d", index);
 }
 
-void SpriteCache::RemoveSprite(sprkey_t index, bool freeMemory) {
+Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
 	if (index < 0 || (size_t)index >= _spriteData.size())
-		return;
-
-	if (freeMemory)
-		delete _spriteData[index].Image;
+		return nullptr;
+	Bitmap *image = _spriteData[index].Image;
 	InitNullSpriteParams(index);
 	SprCacheLog("RemoveSprite: %d", index);
+	return image;
+}
+
+void SpriteCache::DisposeSprite(sprkey_t index) {
+	assert(index >= 0); // out of positive range indexes are valid to fail
+	if (index < 0 || (size_t)index >= _spriteData.size())
+		return;
+	delete _spriteData[index].Image;
+	InitNullSpriteParams(index);
+	SprCacheLog("RemoveAndDispose: %d", index);
 }
 
 sprkey_t SpriteCache::EnlargeTo(sprkey_t topmost) {
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 9da4408bb29..f02083c5362 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -126,8 +126,10 @@ public:
 	void        Precache(sprkey_t index);
 	// Remap the given index to the sprite 0
 	void        RemapSpriteToSprite0(sprkey_t index);
-	// Unregisters sprite from the bank and optionally deletes bitmap
-	void        RemoveSprite(sprkey_t index, bool freeMemory);
+	// Unregisters sprite from the bank and returns the bitmap
+	Bitmap		*RemoveSprite(sprkey_t index);
+	// Deletes particular sprite, marks slot as unused
+	void		DisposeSprite(sprkey_t index);
 	// Deletes all loaded (non-locked, non-external) images from the cache;
 	// this keeps all the auxiliary sprite information intact
 	void        DisposeAll();


Commit: 90f94f74c0ecaeb7b16c16ad4d3db2c69d98f05f
    https://github.com/scummvm/scummvm/commit/90f94f74c0ecaeb7b16c16ad4d3db2c69d98f05f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: reduced spriteset's [] operator use where possible

The spriteset's [] operator loads a sprite, but some operations may
 omit that and check the preloaded information instead.
This also makes it more certain where the actual sprite's bitmap is necessary.
>From upstream df30fc1edd30d4f334addaf7883286e745dea2c3

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/global_drawing_surface.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/view_frame.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/shared/ac/game_struct_defines.h
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_slider.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index ba8a2ec84c0..df81e83f7e3 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2576,7 +2576,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 
 			if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
 				// QFG4-style whole screen picture
-				closeupface = BitmapHelper::CreateBitmap(ui_view.GetWidth(), ui_view.GetHeight(), _GP(spriteset)[viptr->loops[0].frames[0].pic]->GetColorDepth());
+				closeupface = BitmapHelper::CreateBitmap(ui_view.GetWidth(), ui_view.GetHeight());
 				closeupface->Clear(0);
 				if (xx < 0 && _GP(play).speech_portrait_placement) {
 					_G(facetalk_qfg4_override_placement_x) = true;
@@ -2601,7 +2601,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 				else
 					ovr_yp = yy;
 
-				closeupface = BitmapHelper::CreateTransparentBitmap(bigx + 1, bigy + 1, _GP(spriteset)[viptr->loops[0].frames[0].pic]->GetColorDepth());
+				closeupface = BitmapHelper::CreateTransparentBitmap(bigx + 1, bigy + 1);
 				ovr_type = OVER_PICTURE;
 
 				if (yy < 0)
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 10c8dd11d32..ee334c7c820 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -602,7 +602,7 @@ int get_font_outline_padding(int font) {
 
 void do_corner(Bitmap *ds, int sprn, int x, int y, int offx, int offy) {
 	if (sprn < 0) return;
-	if (_GP(spriteset)[sprn] == nullptr) {
+	if (!_GP(spriteset).DoesSpriteExist(sprn)) {
 		sprn = 0;
 	}
 
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 466e6863b87..b1d78a54d0d 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1177,9 +1177,6 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 								 bool optimize_by_position, // allow to optimize walk-behind merging using object's pos
 								 bool force_software) {
 	const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
-	const int coldept = _GP(spriteset)[pic]->GetColorDepth();
-	const int src_sprwidth = _GP(game).SpriteInfos[pic].Width;
-	const int src_sprheight = _GP(game).SpriteInfos[pic].Height;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
@@ -1268,7 +1265,10 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	}
 
 	// Not cached, so draw the image
-
+	Bitmap *sprite = _GP(spriteset)[pic];
+	const int coldept = sprite->GetColorDepth();
+	const int src_sprwidth = sprite->GetWidth();
+	const int src_sprheight = sprite->GetHeight();
 	bool actsps_used = false;
 	if (!use_hw_transform) {
 		// draw the base sprite, scaled and flipped as appropriate
@@ -1285,14 +1285,14 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 		// direct read from source bitmap, where possible
 		Bitmap *blit_from = nullptr;
 		if (!actsps_used)
-			blit_from = _GP(spriteset)[pic];
+			blit_from = sprite;
 
 		apply_tint_or_light(actsp, light_level, tint_level, tint_red,
 			tint_green, tint_blue, tint_light, coldept,
 			blit_from);
 	} else if (!actsps_used) {
 		// no scaling, flipping or tinting was done, so just blit it normally
-		actsp.Bmp->Blit(_GP(spriteset)[pic], 0, 0);
+		actsp.Bmp->Blit(sprite, 0, 0);
 	}
 
 	// Create the cached image and store it
@@ -1366,7 +1366,7 @@ void prepare_and_add_object_gfx(const ObjectCache &objsav, ObjTexture &actsp, bo
 // Generates RoomObject's raw bitmap and saves in actsps; updates object cache.
 bool construct_object_gfx(int objid, bool force_software) {
 	const RoomObject &obj = _G(objs)[objid];
-	if (_GP(spriteset)[obj.num] == nullptr)
+	if (!_GP(spriteset).DoesSpriteExist(obj.num))
 		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", objid, obj.num);
 
 	ObjectCache objsrc(obj.num, obj.tint_r, obj.tint_g, obj.tint_b,
@@ -1474,7 +1474,7 @@ bool construct_char_gfx(int charid, bool force_software) {
 	const CharacterExtras &chex = _GP(charextra)[charid];
 	const ViewFrame *vf = &_GP(views)[chin.view].loops[chin.loop].frames[chin.frame];
 	const int pic = vf->pic;
-	if (_GP(spriteset)[pic] == nullptr)
+	if (!_GP(spriteset).DoesSpriteExist(pic))
 		quitprintf("There was an error drawing character %d. Its current frame's sprite, %d, is invalid.", charid, pic);
 
 	ObjectCache chsrc(pic, chex.tint_r, chex.tint_g, chex.tint_b,
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index 59e186e3cfd..f82b4885611 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -210,7 +210,7 @@ void DrawingSurface_DrawImage(ScriptDrawingSurface *sds,
 		int dst_x, int dst_y, int slot, int trans,
 		int dst_width, int dst_height,
 		int src_x, int src_y, int src_width, int src_height) {
-	if ((slot < 0) || (_GP(spriteset)[slot] == nullptr))
+	if ((slot < 0) || (!_GP(spriteset).DoesSpriteExist(slot)))
 		quit("!DrawingSurface.DrawImage: invalid sprite slot number specified");
 	DrawingSurface_DrawImageImpl(sds, _GP(spriteset)[slot], dst_x, dst_y, trans, dst_width, dst_height,
 		src_x, src_y, src_width, src_height, slot, (_GP(game).SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) != 0);
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 1300bb01139..bc87fe581bf 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -84,6 +84,7 @@ int DynamicSprite_GetHeight(ScriptDynamicSprite *sds) {
 }
 
 int DynamicSprite_GetColorDepth(ScriptDynamicSprite *sds) {
+	// Dynamic sprite ensures the sprite exists always
 	int depth = _GP(spriteset)[sds->slot]->GetColorDepth();
 	if (depth == 15)
 		depth = 16;
@@ -104,12 +105,13 @@ void DynamicSprite_Resize(ScriptDynamicSprite *sds, int width, int height) {
 		quitprintf("!DynamicSprite.Resize: new size is too large: %d x %d", width, height);
 
 	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(spriteset)[sds->slot]->GetColorDepth());
-	newPic->StretchBlt(_GP(spriteset)[sds->slot],
-	                   RectWH(0, 0, _GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height),
-	                   RectWH(0, 0, width, height));
+	Bitmap *sprite = _GP(spriteset)[sds->slot];
+	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
+	newPic->StretchBlt(sprite,
+					   RectWH(0, 0, _GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height),
+					   RectWH(0, 0, width, height));
 
-	delete _GP(spriteset)[sds->slot];
+	delete sprite;  // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -123,11 +125,12 @@ void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction) {
 		quit("!DynamicSprite.Flip: sprite has been deleted");
 
 	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(_GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height, _GP(spriteset)[sds->slot]->GetColorDepth());
+	Bitmap *sprite = _GP(spriteset)[sds->slot];
+	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(sprite->GetWidth(), sprite->GetHeight(), sprite->GetColorDepth());
 
 	// AGS script FlipDirection corresponds to internal GraphicFlip
-	newPic->FlipBlt(_GP(spriteset)[sds->slot], 0, 0, static_cast<GraphicFlip>(direction));
-	delete _GP(spriteset)[sds->slot];
+	newPic->FlipBlt(sprite, 0, 0, static_cast<GraphicFlip>(direction));
+	delete sprite;  // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -171,11 +174,12 @@ void DynamicSprite_ChangeCanvasSize(ScriptDynamicSprite *sds, int width, int hei
 	data_to_game_coords(&x, &y);
 	data_to_game_coords(&width, &height);
 
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, _GP(spriteset)[sds->slot]->GetColorDepth());
+	Bitmap *sprite = _GP(spriteset)[sds->slot];
+	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth());
 	// blit it into the enlarged image
-	newPic->Blit(_GP(spriteset)[sds->slot], 0, 0, x, y, _GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height);
+	newPic->Blit(sprite, 0, 0, x, y, sprite->GetWidth(), sprite->GetHeight());
 
-	delete _GP(spriteset)[sds->slot];
+	delete sprite; // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -194,11 +198,12 @@ void DynamicSprite_Crop(ScriptDynamicSprite *sds, int x1, int y1, int width, int
 	if ((width > _GP(game).SpriteInfos[sds->slot].Width) || (height > _GP(game).SpriteInfos[sds->slot].Height))
 		quit("!DynamicSprite.Crop: requested to crop an area larger than the source");
 
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(spriteset)[sds->slot]->GetColorDepth());
+	Bitmap *sprite = _GP(spriteset)[sds->slot];
+	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
 	// blit it cropped
-	newPic->Blit(_GP(spriteset)[sds->slot], x1, y1, 0, 0, newPic->GetWidth(), newPic->GetHeight());
+	newPic->Blit(sprite, x1, y1, 0, 0, newPic->GetWidth(), newPic->GetHeight());
 
-	delete _GP(spriteset)[sds->slot];
+	delete sprite; // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -233,14 +238,15 @@ void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int he
 	angle = (angle * 256) / 360;
 
 	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, _GP(spriteset)[sds->slot]->GetColorDepth());
+	Bitmap *sprite = _GP(spriteset)[sds->slot];
+	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth());
 
 	// rotate the sprite about its centre
 	// (+ width%2 fixes one pixel offset problem)
-	newPic->RotateBlt(_GP(spriteset)[sds->slot], width / 2 + width % 2, height / 2,
-	                  _GP(game).SpriteInfos[sds->slot].Width / 2, _GP(game).SpriteInfos[sds->slot].Height / 2, itofix(angle));
+	newPic->RotateBlt(sprite, width / 2 + width % 2, height / 2,
+					  sprite->GetWidth() / 2, sprite->GetHeight() / 2, itofix(angle));
 
-	delete _GP(spriteset)[sds->slot];
+	delete sprite; // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -253,7 +259,7 @@ void DynamicSprite_Tint(ScriptDynamicSprite *sds, int red, int green, int blue,
 
 	tint_image(newPic, source, red, green, blue, saturation, (luminance * 25) / 10);
 
-	delete source;
+	delete source;  // FIXME: instead do this while replacing sprite with existing id
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
diff --git a/engines/ags/engine/ac/global_drawing_surface.cpp b/engines/ags/engine/ac/global_drawing_surface.cpp
index 9a85b6a3ff9..9ae498b98b2 100644
--- a/engines/ags/engine/ac/global_drawing_surface.cpp
+++ b/engines/ags/engine/ac/global_drawing_surface.cpp
@@ -168,12 +168,13 @@ void RawPrintMessageWrapped(int xx, int yy, int wid, int font, int msgm) {
 }
 
 void RawDrawImageCore(int xx, int yy, int slot, int alpha) {
-	if ((slot < 0) || (_GP(spriteset)[slot] == nullptr))
+	if ((slot < 0) || (!_GP(spriteset).DoesSpriteExist(slot)))
 		quit("!RawDrawImage: invalid sprite slot number specified");
 	RAW_START();
 
-	if (_GP(spriteset)[slot]->GetColorDepth() != RAW_SURFACE()->GetColorDepth()) {
-		debug_script_warn("RawDrawImage: Sprite %d colour depth %d-bit not same as background depth %d-bit", slot, _GP(spriteset)[slot]->GetColorDepth(), RAW_SURFACE()->GetColorDepth());
+	Bitmap *sprite = _GP(spriteset)[slot];
+	if (sprite->GetColorDepth() != RAW_SURFACE()->GetColorDepth()) {
+		debug_script_warn("RawDrawImage: Sprite %d colour depth %d-bit not same as background depth %d-bit", slot, sprite->GetColorDepth(), RAW_SURFACE()->GetColorDepth());
 	}
 
 	draw_sprite_slot_support_alpha(RAW_SURFACE(), false, xx, yy, slot, kBlendMode_Alpha, alpha);
@@ -224,7 +225,7 @@ void RawDrawImageTransparent(int xx, int yy, int slot, int legacy_transparency)
 	RawDrawImageTrans(xx, yy, slot, GfxDef::LegacyTrans100ToAlpha255(legacy_transparency));
 }
 void RawDrawImageResized(int xx, int yy, int gotSlot, int width, int height) {
-	if ((gotSlot < 0) || (_GP(spriteset)[gotSlot] == nullptr))
+	if ((gotSlot < 0) || (!_GP(spriteset).DoesSpriteExist(gotSlot)))
 		quit("!RawDrawImageResized: invalid sprite slot number specified");
 	// very small, don't draw it
 	if ((width < 1) || (height < 1))
@@ -234,10 +235,11 @@ void RawDrawImageResized(int xx, int yy, int gotSlot, int width, int height) {
 	data_to_game_coords(&width, &height);
 
 	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(spriteset)[gotSlot]->GetColorDepth());
-	newPic->StretchBlt(_GP(spriteset)[gotSlot],
-	                   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
-	                   RectWH(0, 0, width, height));
+	Bitmap *sprite = _GP(spriteset)[gotSlot];
+	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
+	newPic->StretchBlt(sprite,
+					   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
+					   RectWH(0, 0, width, height));
 
 	RAW_START();
 	if (newPic->GetColorDepth() != RAW_SURFACE()->GetColorDepth())
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index d10360d652c..70f6f63d2a7 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -164,10 +164,11 @@ int LoadSaveSlotScreenshot(int slnum, int width, int height) {
 		return gotSlot;
 
 	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(spriteset)[gotSlot]->GetColorDepth());
-	newPic->StretchBlt(_GP(spriteset)[gotSlot],
-	                   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
-	                   RectWH(0, 0, width, height));
+	Bitmap *sprite = _GP(spriteset)[gotSlot];
+	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
+	newPic->StretchBlt(sprite,
+					   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
+					   RectWH(0, 0, width, height));
 
 	// replace the bitmap in the sprite set
 	free_dynamic_sprite(gotSlot);
diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index 07301a5579c..44117eca674 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -151,8 +151,8 @@ void CheckViewFrame(int view, int loop, int frame, int sound_volume) {
 void DrawViewFrame(Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha_blend) {
 	// NOTE: DrawViewFrame supports alpha blending only since OPT_SPRITEALPHA;
 	// this is why there's no sense in blending if it's not set (will do no good anyway).
+	Bitmap *vf_bmp = _GP(spriteset)[vframe->pic];
 	if (alpha_blend && _GP(game).options[OPT_SPRITEALPHA] == kSpriteAlphaRender_Proper) {
-		Bitmap *vf_bmp = _GP(spriteset)[vframe->pic];
 		Bitmap *src = vf_bmp;
 		if (vframe->flags & VFLG_FLIPSPRITE) {
 			src = new Bitmap(vf_bmp->GetWidth(), vf_bmp->GetHeight(), vf_bmp->GetColorDepth());
@@ -163,9 +163,9 @@ void DrawViewFrame(Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha
 			delete src;
 	} else {
 		if (vframe->flags & VFLG_FLIPSPRITE)
-			ds->FlipBlt(_GP(spriteset)[vframe->pic], x, y, Shared::kFlip_Horizontal);
+			ds->FlipBlt(vf_bmp, x, y, Shared::kFlip_Horizontal);
 		else
-			ds->Blit(_GP(spriteset)[vframe->pic], x, y, Shared::kBitmap_Transparency);
+			ds->Blit(vf_bmp, x, y, Shared::kBitmap_Transparency);
 	}
 }
 
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index f41b130feee..1dccf8d65fc 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -76,11 +76,11 @@ bool GUIMain::HasAlphaChannel() const {
 //=============================================================================
 
 int get_adjusted_spritewidth(int spr) {
-	return _GP(spriteset)[spr]->GetWidth();
+	return _GP(game).SpriteInfos[spr].Width;
 }
 
 int get_adjusted_spriteheight(int spr) {
-	return _GP(spriteset)[spr]->GetHeight();
+	return _GP(game).SpriteInfos[spr].Height;
 }
 
 bool is_sprite_alpha(int spr) {
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 954109bf6c7..e7a71dd6ec9 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -360,8 +360,8 @@ bool run_service_key_controls(KeyInput &out_key) {
 			ln += Common::sprintf_s(&infobuf[ln], sizeof(infobuf) - ln,
 			        "[Object %d: (%d,%d) size (%d x %d) on:%d moving:%s animating:%d slot:%d trnsp:%d clkble:%d",
 			        ff, _G(objs)[ff].x, _G(objs)[ff].y,
-			        (_GP(spriteset)[_G(objs)[ff].num] != nullptr) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Width : 0,
-			        (_GP(spriteset)[_G(objs)[ff].num] != nullptr) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Height : 0,
+			        (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Width : 0),
+			        (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Height : 0),
 			        _G(objs)[ff].on,
 			        (_G(objs)[ff].moving > 0) ? "yes" : "no", _G(objs)[ff].cycling,
 			        _G(objs)[ff].num, _G(objs)[ff].transparent,
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 909397328ec..5b67b07946e 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -236,6 +236,8 @@ struct SpriteInfo {
 
 	SpriteInfo();
 
+	inline Size GetResolution() const { return Size(Width, Height); }
+
 	//
 	// Legacy game support
 	//
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 2f2974e2da8..41399c40631 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -209,6 +209,10 @@ bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
 	return index >= 0 && (size_t)index < _spriteData.size() && _spriteData[index].DoesSpriteExist();
 }
 
+Size SpriteCache::GetSpriteResolution(sprkey_t index) const {
+	return DoesSpriteExist(index) ? _sprInfos[index].GetResolution() : Size();
+}
+
 Bitmap *SpriteCache::operator [] (sprkey_t index) {
 	// invalid sprite slot
 	if (index < 0 || (size_t)index >= _spriteData.size())
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index f02083c5362..6a62f8fdc7c 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -109,6 +109,8 @@ public:
 	// Tells if there is a sprite registered for the given index;
 	// this includes sprites that were explicitly assigned but failed to init and were remapped
 	bool        DoesSpriteExist(sprkey_t index) const;
+	// Returns sprite's resolution; or empty Size if sprite does not exist
+	Size		GetSpriteResolution(sprkey_t index) const;
 	// Makes sure sprite cache has allocated slots for all sprites up to the given inclusive limit;
 	// returns requested index on success, or -1 on failure.
 	sprkey_t    EnlargeTo(sprkey_t topmost);
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index ec34468ede7..40da084de03 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -113,7 +113,7 @@ Rect GUIButton::CalcGraphicRect(bool clipped) {
 		if (IsClippingImage())
 			return rc;
 		// Main button graphic
-		if (CurrentImage >= 0 && _GP(spriteset)[CurrentImage] != nullptr)
+		if (CurrentImage >= 0 && _GP(spriteset).DoesSpriteExist(CurrentImage))
 			rc = SumRects(rc, RectWH(0, 0, get_adjusted_spritewidth(CurrentImage), get_adjusted_spriteheight(CurrentImage)));
 		// Optionally merge with the inventory pic
 		if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
@@ -340,7 +340,7 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 	// NOTE: the CLIP flag only clips the image, not the text
 	if (IsClippingImage() && !GUI::Options.ClipControls)
 		ds->SetClip(RectWH(x, y, Width, Height));
-	if (_GP(spriteset)[CurrentImage] != nullptr)
+	if (_GP(spriteset).DoesSpriteExist(CurrentImage))
 		draw_gui_sprite(ds, CurrentImage, x, y, true);
 
 	// Draw active inventory item
@@ -365,9 +365,8 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 
 	if ((draw_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Greyout)) {
 		// darken the button when disabled
-		GUI::DrawDisabledEffect(ds, RectWH(x, y,
-			_GP(spriteset)[CurrentImage]->GetWidth(),
-			_GP(spriteset)[CurrentImage]->GetHeight()));
+		const Size sz = _GP(spriteset).GetSpriteResolution(CurrentImage);
+		GUI::DrawDisabledEffect(ds, RectWH(x, y, sz.Width, sz.Height));
 	}
 
 	// Don't print Text of (INV) (INVSHR) (INVNS)
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 82539fada88..cfbd24c61c0 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -257,7 +257,7 @@ void GUIMain::DrawSelf(Bitmap *ds) {
 
 	SET_EIP(378);
 
-	if (BgImage > 0 && _GP(spriteset)[BgImage] != nullptr)
+	if (BgImage > 0 && _GP(spriteset).DoesSpriteExist(BgImage))
 		draw_gui_sprite(ds, BgImage, 0, 0, false);
 
 	SET_EIP(379);
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index 6c865f8ee1c..dc29dfa2104 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -83,7 +83,7 @@ void GUISlider::UpdateMetrics() {
 		MaxValue = MinValue + 1;
 	Value = Math::Clamp(Value, MinValue, MaxValue);
 	// Test if sprite is available; // TODO: return a placeholder from spriteset instead!
-	const int handle_im = ((HandleImage > 0) && _GP(spriteset)[HandleImage]) ? HandleImage : 0;
+	const int handle_im = ((HandleImage > 0) && _GP(spriteset).DoesSpriteExist(HandleImage)) ? HandleImage : 0;
 
 	// Depending on slider's orientation, thickness is either Height or Width
 	const int thickness = IsHorizontal() ? Height : Width;
@@ -179,7 +179,7 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 	}
 
 	// Test if sprite is available; // TODO: return a placeholder from spriteset instead!
-	const int handle_im = ((HandleImage > 0) && _GP(spriteset)[HandleImage]) ? HandleImage : 0;
+	const int handle_im = ((HandleImage > 0) && _GP(spriteset).DoesSpriteExist(HandleImage)) ? HandleImage : 0;
 	if (handle_im > 0) // handle is a sprite
 	{
 		draw_gui_sprite(ds, handle_im, handle.Left, handle.Top, true);


Commit: 9225909b774fb66870d6570cacdb4d7c69d8b4dc
    https://github.com/scummvm/scummvm/commit/9225909b774fb66870d6570cacdb4d7c69d8b4dc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: disable vsync in the "infinite fps" mode

>From upstream 689c4e459077d754e5dcbf8e4d97489122ac1e59

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index b1d78a54d0d..159bcbb3d42 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -691,9 +691,13 @@ void render_to_screen() {
 	construct_engine_overlay();
 
 	// Try set new vsync value, and remember the actual result
-	bool new_vsync = _G(gfxDriver)->SetVsync(_GP(scsystem).vsync > 0);
-	if (new_vsync != (_GP(scsystem).vsync > 0))
-		System_SetVSyncInternal(new_vsync);
+	if (isTimerFpsMaxed()) {
+		_G(gfxDriver)->SetVsync(false);
+	} else {
+		bool new_vsync = _G(gfxDriver)->SetVsync(_GP(scsystem).vsync > 0);
+		if (new_vsync != _GP(scsystem).vsync)
+			System_SetVSyncInternal(new_vsync);
+	}
 
 	bool succeeded = false;
 	while (!succeeded && !_G(want_exit) && !_G(abort_engine)) {


Commit: 24e85071167c7bff23840a18deecf2993eec9a5c
    https://github.com/scummvm/scummvm/commit/24e85071167c7bff23840a18deecf2993eec9a5c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support RTL in Button and ListBox

>From upstream 24d9894f9bc9111af0714378e25ab12c2534e7fe

Changed paths:
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/ac/game_version.h
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_label.h
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 1dccf8d65fc..b1c55a4b67c 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -98,6 +98,18 @@ int get_eip_guiobj() {
 namespace AGS {
 namespace Shared {
 
+String GUI::TransformTextForDrawing(const String &text, bool translate) {
+	String res_text = translate ? String(get_translation(text.GetCStr())) : text;
+	if (_GP(game).options[OPT_RIGHTLEFTWRITE] != 0)
+		(get_uformat() == U_UTF8) ? res_text.ReverseUTF8() : res_text.Reverse();
+	return res_text;
+}
+
+size_t GUI::SplitLinesForDrawing(const char *text, SplitLines &lines, int font, int width, size_t max_lines) {
+	// Use the engine's word wrap tool, to have RTL writing and other features
+	return break_up_text_into_lines(text, lines, width, font);
+}
+
 bool GUIObject::IsClickable() const {
 	return (Flags & kGUICtrl_Clickable) != 0;
 }
@@ -123,11 +135,6 @@ void GUILabel::PrepareTextToDraw() {
 	replace_macro_tokens((Flags & kGUICtrl_Translated) ? get_translation(Text.GetCStr()) : Text.GetCStr(), _textToDraw);
 }
 
-size_t GUILabel::SplitLinesForDrawing(SplitLines &lines) {
-	// Use the engine's word wrap tool, to have hebrew-style writing and other features
-	return break_up_text_into_lines(_textToDraw.GetCStr(), lines, Width, Font);
-}
-
 void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color) {
 	wouttext_outline(ds, x + 1 + get_fixed_pixel_size(1), y + 1 + get_fixed_pixel_size(1), Font, text_color, Text.GetCStr());
 	if (IsGUIEnabled(this)) {
@@ -139,17 +146,11 @@ void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_colo
 }
 
 void GUIListBox::PrepareTextToDraw(const String &text) {
-	if (Flags & kGUICtrl_Translated)
-		_textToDraw = get_translation(text.GetCStr());
-	else
-		_textToDraw = text;
+	_textToDraw = GUI::TransformTextForDrawing(text, (Flags & kGUICtrl_Translated) != 0);
 }
 
 void GUIButton::PrepareTextToDraw() {
-	if (Flags & kGUICtrl_Translated)
-		_textToDraw = get_translation(_text.GetCStr());
-	else
-		_textToDraw = _text;
+	_textToDraw = GUI::TransformTextForDrawing(_text, (Flags & kGUICtrl_Translated) != 0);
 }
 
 } // namespace Shared
diff --git a/engines/ags/shared/ac/game_version.h b/engines/ags/shared/ac/game_version.h
index d3263644d71..b2178069a66 100644
--- a/engines/ags/shared/ac/game_version.h
+++ b/engines/ags/shared/ac/game_version.h
@@ -122,6 +122,8 @@ New font load flags, control backward compatible font behavior
 Idle animation speed, modifiable hotspot names, fixed video frame
 3.6.0.21:
 Some adjustments to gui text alignment.
+3.6.1:
+In RTL mode all text is reversed, not only wrappable (labels etc).
 */
 
 enum GameDataVersion {
@@ -158,7 +160,8 @@ enum GameDataVersion {
 	kGameVersion_360_11 = 3060011,
 	kGameVersion_360_16 = 3060016,
 	kGameVersion_360_21 = 3060021,
-	kGameVersion_Current = kGameVersion_360_21
+	kGameVersion_361 = 3060100,
+	kGameVersion_Current = kGameVersion_361
 };
 
 } // namespace AGS3
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index dec3e4df9da..0338f1f34dd 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -63,7 +63,7 @@ Rect GUILabel::CalcGraphicRect(bool clipped) {
 	// - macro value change (score, overhotspot etc)
 	Rect rc = RectWH(0, 0, Width, Height);
 	PrepareTextToDraw();
-	if (SplitLinesForDrawing(_GP(Lines)) == 0)
+	if (GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), _GP(Lines), Font, Width) == 0)
 		return rc;
 	const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
 		((_G(loaded_game_file_version) < kGameVersion_360) && (get_font_flags(Font) & FFLG_DEFLINESPACING)) ?
@@ -91,7 +91,7 @@ void GUILabel::Draw(Bitmap *ds, int x, int y) {
 	// TODO: need to find a way to cache text prior to drawing;
 	// but that will require to update all gui controls when translation is changed in game
 	PrepareTextToDraw();
-	if (SplitLinesForDrawing(_GP(Lines)) == 0)
+	if (GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), _GP(Lines), Font, Width) == 0)
 		return;
 
 	color_t text_color = ds->GetCompatibleColor(TextColor);
diff --git a/engines/ags/shared/gui/gui_label.h b/engines/ags/shared/gui/gui_label.h
index 93ab4bbe8ad..54d7234104f 100644
--- a/engines/ags/shared/gui/gui_label.h
+++ b/engines/ags/shared/gui/gui_label.h
@@ -28,8 +28,6 @@
 
 namespace AGS3 {
 
-class SplitLines;
-
 namespace AGS {
 namespace Shared {
 
@@ -63,11 +61,11 @@ public:
 
 private:
 	void PrepareTextToDraw();
-	size_t SplitLinesForDrawing(SplitLines &lines);
 
 	// Information on macros contained within Text field
 	GUILabelMacro _textMacro;
 	// prepared text buffer/cache
+	// TODO: cache split lines instead?
 	String _textToDraw;
 };
 
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 8fbce491ffe..5252fb64f36 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -42,8 +42,7 @@ class Stream;
 }
 using namespace AGS; // FIXME later
 
-// There were issues when including header caused conflicts
-struct GameSetupStruct;
+class SplitLines;
 
 #define LEGACY_MAX_OBJS_ON_GUI             30
 
@@ -241,6 +240,14 @@ void DrawTextAligned(Bitmap *ds, const char *text, int font, color_t text_color,
 // Draw text aligned horizontally inside given bounds
 void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_color, int x1, int x2, int y, FrameAlignment align);
 
+// Parses the string and returns combination of label macro flags
+GUILabelMacro FindLabelMacros(const String &text);
+// Wraps given text to make it fit into width, stores it in the lines
+size_t SplitLinesForDrawing(const char *text, SplitLines &lines, int font, int width, size_t max_lines = -1);
+// Applies text transformation necessary for rendering, in accordance to the
+// current game settings, such as right-to-left render, and anything else
+String TransformTextForDrawing(const String &text, bool translate);
+
 // Mark all existing GUI for redraw
 void MarkAllGUIForUpdate(bool redraw, bool reset_over_ctrl);
 // Mark all translatable GUI controls for redraw
@@ -254,9 +261,6 @@ void MarkSpecialLabelsForUpdate(GUILabelMacro macro);
 // also marks buttons with inventory icon mode
 void MarkInventoryForUpdate(int char_id, bool is_player);
 
-// Parses the string and returns combination of label macro flags
-GUILabelMacro FindLabelMacros(const String &text);
-
 // Reads all GUIs and their controls.
 // WARNING: the data is read into the global arrays (guis, guibuts, and so on)
 // TODO: remove is_savegame param after dropping support for old saves


Commit: c05a1c413461ef9b90773e757a2f3da92b1821d6
    https://github.com/scummvm/scummvm/commit/c05a1c413461ef9b90773e757a2f3da92b1821d6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: apply text direction based on guicontrol flags and game version

* If controls have "translated" flags
* For Buttons and ListBoxes: if game is >= 3.6.1

Changed paths:
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/string.h
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_defines.h
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_label.h
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index d97420bc25b..4b73d893dfa 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -273,7 +273,7 @@ DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) {
 	return DynObjectRef(handle, obj_ptr);
 }
 
-size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
+size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
 	if (fonnt == -1)
 		fonnt = _GP(play).normal_font;
 
@@ -295,7 +295,7 @@ size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, i
 
 	// Right-to-left just means reverse the text then
 	// write it as normal
-	if (_GP(game).options[OPT_RIGHTLEFTWRITE])
+	if (apply_direction && (_GP(game).options[OPT_RIGHTLEFTWRITE] != 0))
 		for (size_t rr = 0; rr < lines.Count(); rr++) {
 			(get_uformat() == U_UTF8) ?
 				lines[rr].ReverseUTF8() :
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index 40ca00b2b53..bb25538782c 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -55,7 +55,11 @@ class SplitLines;
 // Break up the text into lines restricted by the given width;
 // returns number of lines, or 0 if text cannot be split well to fit in this width.
 // Does additional processing, like removal of voice-over tags and text reversal if right-to-left text display is on.
-size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines = -1);
+// Optionally applies text direction rules (apply_direction param), otherwise leaves left-to-right always.
+size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines = -1);
+inline size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines = -1) {
+	return break_up_text_into_lines(todis, true, lines, wii, fonnt, max_lines);
+}
 void check_strlen(char *ptt);
 void my_strncpy(char *dest, const char *src, int len);
 
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index b1c55a4b67c..5a13fa9e229 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -98,16 +98,24 @@ int get_eip_guiobj() {
 namespace AGS {
 namespace Shared {
 
-String GUI::TransformTextForDrawing(const String &text, bool translate) {
+String GUI::ApplyTextDirection(const String &text) {
+	if (_GP(game).options[OPT_RIGHTLEFTWRITE] == 0)
+		return text;
+	String res_text = text;
+	(get_uformat() == U_UTF8) ? res_text.ReverseUTF8() : res_text.Reverse();
+	return res_text;
+}
+
+String GUI::TransformTextForDrawing(const String &text, bool translate, bool apply_direction) {
 	String res_text = translate ? String(get_translation(text.GetCStr())) : text;
-	if (_GP(game).options[OPT_RIGHTLEFTWRITE] != 0)
-		(get_uformat() == U_UTF8) ? res_text.ReverseUTF8() : res_text.Reverse();
+	if (translate && apply_direction)
+		res_text = ApplyTextDirection(res_text);
 	return res_text;
 }
 
-size_t GUI::SplitLinesForDrawing(const char *text, SplitLines &lines, int font, int width, size_t max_lines) {
+size_t GUI::SplitLinesForDrawing(const char *text, bool is_translated, SplitLines &lines, int font, int width, size_t max_lines) {
 	// Use the engine's word wrap tool, to have RTL writing and other features
-	return break_up_text_into_lines(text, lines, width, font);
+	return break_up_text_into_lines(text, is_translated, lines, width, font);
 }
 
 bool GUIObject::IsClickable() const {
@@ -131,8 +139,10 @@ void GUIObject::ClearChanged() {
 	_hasChanged = false;
 }
 
-void GUILabel::PrepareTextToDraw() {
-	replace_macro_tokens((Flags & kGUICtrl_Translated) ? get_translation(Text.GetCStr()) : Text.GetCStr(), _textToDraw);
+int GUILabel::PrepareTextToDraw() {
+	const bool is_translated = Flags & kGUICtrl_Translated;
+	replace_macro_tokens(is_translated ? get_translation(Text.GetCStr()) : Text.GetCStr(), _textToDraw);
+	return GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), is_translated, _GP(Lines), Font, Width);
 }
 
 void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color) {
@@ -146,11 +156,11 @@ void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_colo
 }
 
 void GUIListBox::PrepareTextToDraw(const String &text) {
-	_textToDraw = GUI::TransformTextForDrawing(text, (Flags & kGUICtrl_Translated) != 0);
+	_textToDraw = GUI::TransformTextForDrawing(text, (Flags & kGUICtrl_Translated) != 0, (_G(loaded_game_file_version) >= kGameVersion_361));
 }
 
 void GUIButton::PrepareTextToDraw() {
-	_textToDraw = GUI::TransformTextForDrawing(_text, (Flags & kGUICtrl_Translated) != 0);
+	_textToDraw = GUI::TransformTextForDrawing(_text, (Flags & kGUICtrl_Translated) != 0, (_G(loaded_game_file_version) >= kGameVersion_361));
 }
 
 } // namespace Shared
diff --git a/engines/ags/shared/gui/gui_defines.h b/engines/ags/shared/gui/gui_defines.h
index 97d0bc9521b..508cf0fcd07 100644
--- a/engines/ags/shared/gui/gui_defines.h
+++ b/engines/ags/shared/gui/gui_defines.h
@@ -142,7 +142,7 @@ enum GUIControlFlags {
 	kGUICtrl_Translated = 0x0080, // 3.3.0.1132
 	kGUICtrl_Deleted = 0x8000, // unused (probably remains from the old editor?)
 
-	kGUICtrl_DefFlags = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable,
+	kGUICtrl_DefFlags = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable | kGUICtrl_Translated,
 	// flags that had inverse meaning in old formats
 	kGUICtrl_OldFmtXorMask = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable
 };
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index 0338f1f34dd..f8385d2cd08 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -62,8 +62,7 @@ Rect GUILabel::CalcGraphicRect(bool clipped) {
 	// - translation change
 	// - macro value change (score, overhotspot etc)
 	Rect rc = RectWH(0, 0, Width, Height);
-	PrepareTextToDraw();
-	if (GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), _GP(Lines), Font, Width) == 0)
+	if (PrepareTextToDraw() == 0)
 		return rc;
 	const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
 		((_G(loaded_game_file_version) < kGameVersion_360) && (get_font_flags(Font) & FFLG_DEFLINESPACING)) ?
@@ -90,8 +89,7 @@ Rect GUILabel::CalcGraphicRect(bool clipped) {
 void GUILabel::Draw(Bitmap *ds, int x, int y) {
 	// TODO: need to find a way to cache text prior to drawing;
 	// but that will require to update all gui controls when translation is changed in game
-	PrepareTextToDraw();
-	if (GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), _GP(Lines), Font, Width) == 0)
+	if (PrepareTextToDraw() == 0)
 		return;
 
 	color_t text_color = ds->GetCompatibleColor(TextColor);
diff --git a/engines/ags/shared/gui/gui_label.h b/engines/ags/shared/gui/gui_label.h
index 54d7234104f..68bf094c5b2 100644
--- a/engines/ags/shared/gui/gui_label.h
+++ b/engines/ags/shared/gui/gui_label.h
@@ -60,7 +60,9 @@ public:
 	HorAlignment TextAlignment;
 
 private:
-	void PrepareTextToDraw();
+	// Transforms the Text property to a drawn text, applies translation,
+	// replaces macros, splits and wraps, etc; returns number of lines.
+	int PrepareTextToDraw();
 
 	// Information on macros contained within Text field
 	GUILabelMacro _textMacro;
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 5252fb64f36..14fffb3f77d 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -217,6 +217,8 @@ namespace GUI {
 extern GuiVersion GameGuiVersion;
 extern GuiOptions Options;
 
+// Applies current text direction setting (may depend on multiple factors)
+String ApplyTextDirection(const String &text);
 // Calculates the text's draw position, given the alignment
 // optionally returns the real graphical rect that the text would occupy
 Point CalcTextPosition(const char *text, int font, const Rect &frame, FrameAlignment align, Rect *gr_rect = nullptr);
@@ -242,11 +244,12 @@ void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_col
 
 // Parses the string and returns combination of label macro flags
 GUILabelMacro FindLabelMacros(const String &text);
-// Wraps given text to make it fit into width, stores it in the lines
-size_t SplitLinesForDrawing(const char *text, SplitLines &lines, int font, int width, size_t max_lines = -1);
 // Applies text transformation necessary for rendering, in accordance to the
 // current game settings, such as right-to-left render, and anything else
-String TransformTextForDrawing(const String &text, bool translate);
+String TransformTextForDrawing(const String &text, bool translate, bool apply_direction);
+// Wraps given text to make it fit into width, stores it in the lines;
+// apply_direction param tells whether text direction setting should be applied
+size_t SplitLinesForDrawing(const char *text, bool apply_direction, SplitLines &lines, int font, int width, size_t max_lines = -1);
 
 // Mark all existing GUI for redraw
 void MarkAllGUIForUpdate(bool redraw, bool reset_over_ctrl);


Commit: 848d2b4ae5390cb97fbd5912614c6e9389329243
    https://github.com/scummvm/scummvm/commit/848d2b4ae5390cb97fbd5912614c6e9389329243
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support RTL in TextBox

>From upstream b016b4c221afb4396d33684ef01412eaee7781d7

Changed paths:
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_textbox.cpp
    engines/ags/shared/gui/gui_textbox.h


diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 5a13fa9e229..a03e6fad2d0 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -146,12 +146,24 @@ int GUILabel::PrepareTextToDraw() {
 }
 
 void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color) {
-	wouttext_outline(ds, x + 1 + get_fixed_pixel_size(1), y + 1 + get_fixed_pixel_size(1), Font, text_color, Text.GetCStr());
+	_textToDraw = Text;
+	bool reverse = false;
+	if ((_G(loaded_game_file_version) >= kGameVersion_361) && ((Flags & kGUICtrl_Translated) != 0)) {
+		_textToDraw = GUI::ApplyTextDirection(Text);
+		reverse = _GP(game).options[OPT_RIGHTLEFTWRITE] != 0;
+	}
+
+	Line tpos = GUI::CalcTextPositionHor(_textToDraw.GetCStr(), Font,
+										 x + 1 + get_fixed_pixel_size(1), x + Width - 1, y + 1 + get_fixed_pixel_size(1),
+										 reverse ? kAlignTopRight : kAlignTopLeft);
+	wouttext_outline(ds, tpos.X1, tpos.Y1, Font, text_color, _textToDraw.GetCStr());
+
 	if (IsGUIEnabled(this)) {
 		// draw a cursor
-		int draw_at_x = x + get_text_width(Text.GetCStr(), Font) + 3;
+		const int cursor_width = get_fixed_pixel_size(5);
+		int draw_at_x = reverse ? tpos.X1 - 3 - cursor_width : tpos.X2 + 3;
 		int draw_at_y = y + 1 + get_font_height(Font);
-		ds->DrawRect(Rect(draw_at_x, draw_at_y, draw_at_x + get_fixed_pixel_size(5), draw_at_y + (get_fixed_pixel_size(1) - 1)), text_color);
+		ds->DrawRect(Rect(draw_at_x, draw_at_y, draw_at_x + cursor_width, draw_at_y + (get_fixed_pixel_size(1) - 1)), text_color);
 	}
 }
 
diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index 2a95dd8fd12..7338402ac67 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -153,6 +153,9 @@ void GUITextBox::ReadFromFile(Stream *in, GuiVersion gui_version) {
 
 	if (TextColor == 0)
 		TextColor = 16;
+	// Text boxes input is never "translated" in regular sense,
+	// but they use this flag to apply text direction
+	Flags |= kGUICtrl_Translated;
 }
 
 void GUITextBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
diff --git a/engines/ags/shared/gui/gui_textbox.h b/engines/ags/shared/gui/gui_textbox.h
index fe81ee7f343..1734bb48de3 100644
--- a/engines/ags/shared/gui/gui_textbox.h
+++ b/engines/ags/shared/gui/gui_textbox.h
@@ -59,6 +59,7 @@ public:
 
 private:
 	int32_t TextBoxFlags;
+	String  _textToDraw;
 
 	void DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color);
 };


Commit: db4637ed38f98d2671d8e9404d8e229ed82d4c2e
    https://github.com/scummvm/scummvm/commit/db4637ed38f98d2671d8e9404d8e229ed82d4c2e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Editor: moved GUIListBox.Translated property to GUIControl parent

This should not break old projects, as the properties are read by names, and
serialization mechanism does not distinct parent properties from child properties.
Partially from upstream 7879455f3082d3d58f519afc95f44aec1f540872

Changed paths:
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index 40da084de03..b23f697ddfe 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -298,8 +298,6 @@ void GUIButton::ReadFromFile(Stream *in, GuiVersion gui_version) {
 	if (TextColor == 0)
 		TextColor = 16;
 	CurrentImage = Image;
-	// All buttons are translated at the moment
-	Flags |= kGUICtrl_Translated;
 }
 
 void GUIButton::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index f8385d2cd08..0a3164b1617 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -144,8 +144,6 @@ void GUILabel::ReadFromFile(Stream *in, GuiVersion gui_version) {
 
 	if (TextColor == 0)
 		TextColor = 16;
-	// All labels are translated at the moment
-	Flags |= kGUICtrl_Translated;
 
 	_textMacro = GUI::FindLabelMacros(Text);
 }
diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index 7338402ac67..2a95dd8fd12 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -153,9 +153,6 @@ void GUITextBox::ReadFromFile(Stream *in, GuiVersion gui_version) {
 
 	if (TextColor == 0)
 		TextColor = 16;
-	// Text boxes input is never "translated" in regular sense,
-	// but they use this flag to apply text direction
-	Flags |= kGUICtrl_Translated;
 }
 
 void GUITextBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {


Commit: 1c32a8538cc94ebf530484c484283f7671f2aa06
    https://github.com/scummvm/scummvm/commit/1c32a8538cc94ebf530484c484283f7671f2aa06
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: force translate Buttons and Labels in pre-3.6.1 games

Most gui controls never had editable Translated property before (except ListBox).
>From upstream 6990853cb659efd476126907ca30ca6e4eeffd00

Changed paths:
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index a03e6fad2d0..9e2a2a7c4e8 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -148,6 +148,8 @@ int GUILabel::PrepareTextToDraw() {
 void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color) {
 	_textToDraw = Text;
 	bool reverse = false;
+	// Text boxes input is never "translated" in regular sense,
+	// but they use this flag to apply text direction
 	if ((_G(loaded_game_file_version) >= kGameVersion_361) && ((Flags & kGUICtrl_Translated) != 0)) {
 		_textToDraw = GUI::ApplyTextDirection(Text);
 		reverse = _GP(game).options[OPT_RIGHTLEFTWRITE] != 0;
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index a1ed728201f..2d8a97609bc 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -30,6 +30,8 @@
 #include "ags/shared/core/asset_manager.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/shared/game/main_game_file.h"
+#include "ags/shared/gui/gui_button.h"
+#include "ags/shared/gui/gui_label.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/script/cc_common.h"
@@ -596,6 +598,16 @@ void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
 	}
 }
 
+void UpgradeGUI(GameSetupStruct &game, GameDataVersion data_ver) {
+	// Previously, Buttons and Labels had a fixed Translated behavior
+	if (data_ver < kGameVersion_361) {
+		for (auto &btn : _GP(guibuts))
+			btn.SetTranslated(true); // always translated
+		for (auto &lbl : _GP(guilabels))
+			lbl.SetTranslated(true); // always translated
+	}
+}
+
 void UpgradeMouseCursors(GameSetupStruct &game, GameDataVersion data_ver) {
 	if (data_ver <= kGameVersion_272) {
 		// Change cursor.view from 0 to -1 for non-animating cursors.
@@ -833,6 +845,7 @@ HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver
 	UpgradeFonts(game, data_ver);
 	UpgradeAudio(game, ents, data_ver);
 	UpgradeCharacters(game, data_ver);
+	UpgradeGUI(game, data_ver);
 	UpgradeMouseCursors(game, data_ver);
 	SetDefaultGlobalMessages(game);
 	// Global talking animation speed


Commit: 3d1e23b2a1ca7491ab04540986e0ae52a0465899
    https://github.com/scummvm/scummvm/commit/3d1e23b2a1ca7491ab04540986e0ae52a0465899
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: remember overlay index and try it before looking again

>From upstream 8fffaf0b6f844b946dc4bb1ad92c8dd14294fead
and 34ef9bf5f271411e72b5ebd062482b60e391593b

Changed paths:
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 7ae75e3e9f7..1c8ef6f6a37 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -37,9 +37,12 @@ int ScriptOverlay::Dispose(const char *address, bool force) {
 	// since the managed object is being deleted, remove the
 	// reference so it doesn't try and dispose something else
 	// with that handle later
-	int overlayIndex = find_overlay_of_type(overlayId);
-	if (overlayIndex >= 0) {
-		_GP(screenover)[overlayIndex].associatedOverlayHandle = 0;
+	if (overlayId >= 0) {
+		int overlayIndex = find_overlay_of_type(overlayId);
+		if (overlayIndex >= 0) {
+			_GP(screenover)
+			[overlayIndex].associatedOverlayHandle = 0;
+		}
 	}
 
 	// if this is being removed voluntarily (ie. pointer out of
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 6369ff360a5..c4f87c20ba3 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -386,13 +386,25 @@ void remove_screen_overlay(int type) {
 }
 
 int find_overlay_of_type(int type) {
-	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
-		if (_GP(screenover)[i].type == type) return i;
+	assert(type >= 0);
+	int idx = _GP(overlookup)[type];
+	if (idx >= 0 && idx < (int)_GP(screenover).size() && _GP(screenover)[idx].type == type)
+		return idx;
+	for (idx = 0; idx < (int)_GP(screenover).size(); ++idx) {
+		if (_GP(screenover)[idx].type == type) {
+			_GP(overlookup)
+			[type] = idx;
+			return idx;
+		}
 	}
 	return -1;
 }
+
 size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnum, Bitmap *piccy,
-	int pic_offx, int pic_offy, bool has_alpha) {
+							   int pic_offx, int pic_offy, bool has_alpha) {
+	const auto new_size = _GP(screenover).size() + OVER_CUSTOM + 2;
+	if (_GP(overlookup).size() < new_size)
+		_GP(overlookup).resize(new_size);
 	if (type == OVER_CUSTOM) {
 		// find an unused custom ID; TODO: find a better approach!
 		for (int id = OVER_CUSTOM + 1; (size_t)id <= _GP(screenover).size() + OVER_CUSTOM + 1; ++id) {
@@ -435,6 +447,7 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 		_GP(play).speech_face_schandle = over.associatedOverlayHandle;
 	}
 	over.MarkChanged();
+	_GP(overlookup)[type] = _GP(screenover).size();
 	_GP(screenover).push_back(std::move(over));
 	return _GP(screenover).size() - 1;
 }
@@ -492,7 +505,10 @@ Point get_overlay_position(const ScreenOverlay &over) {
 }
 
 void recreate_overlay_ddbs() {
-	for (auto &over : _GP(screenover)) {
+	_GP(overlookup).resize(_GP(screenover).size() + OVER_CUSTOM + 2);
+	for (size_t i = 0; i < _GP(screenover).size(); i++) {
+		auto &over = _GP(screenover)[i];
+		_GP(overlookup)[over.type] = i;
 		if (over.ddb)
 			_G(gfxDriver)->DestroyDDB(over.ddb);
 		over.ddb = nullptr; // is generated during first draw pass
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index bab3cbebdae..14deebc9b57 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -333,6 +333,7 @@ Globals::Globals() {
 
 	// overlay.cpp globals
 	_screenover = new std::vector<ScreenOverlay>();
+	_overlookup = new std::vector<int>(128);
 
 	// plugins globals
 	_engineExports = new Plugins::Core::EngineExports();
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d307e4c5736..1b495d5b502 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1181,6 +1181,7 @@ public:
 	 */
 
 	std::vector<ScreenOverlay> *_screenover;
+	std::vector<int> *_overlookup;
 	int _is_complete_overlay = 0;
 
 	/**@}*/


Commit: 3cfeee9c0a6770bef733731be2a3a3e25196c807
    https://github.com/scummvm/scummvm/commit/3cfeee9c0a6770bef733731be2a3a3e25196c807
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support RTL in DrawString

>From upstream 6de602d9086c351d32217be675379f078cc29099

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


diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index f82b4885611..fbb83661ee5 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -322,7 +322,8 @@ void DrawingSurface_DrawString(ScriptDrawingSurface *sds, int xx, int yy, int fo
 		text_color = ds->GetCompatibleColor(1);
 		debug_script_warn("RawPrint: Attempted to use hi-color on 256-col background");
 	}
-	wouttext_outline(ds, xx, yy, font, text_color, text);
+	String res_str = GUI::ApplyTextDirection(text);
+	wouttext_outline(ds, xx, yy, font, text_color, res_str.GetCStr());
 	sds->FinishedDrawing();
 }
 


Commit: f2a5f68e8c7c1dc761b728bd730bf56bca84dbbd
    https://github.com/scummvm/scummvm/commit/f2a5f68e8c7c1dc761b728bd730bf56bca84dbbd
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use string wrap to make transtree.find a tiny bit faster

>From upstream da1d05a7d5a643aef5eb5a919014a616505d3c6e

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


diff --git a/engines/ags/engine/ac/global_translation.cpp b/engines/ags/engine/ac/global_translation.cpp
index bfc1162bb42..578e285f158 100644
--- a/engines/ags/engine/ac/global_translation.cpp
+++ b/engines/ags/engine/ac/global_translation.cpp
@@ -52,7 +52,7 @@ const char *get_translation(const char *text) {
 #endif
 
 	const auto &transtree = get_translation_tree();
-	const auto it = transtree.find(text);
+	const auto it = transtree.find(String::Wrapper(text));
 	if (it != transtree.end())
 		return it->_value.GetCStr();
 


Commit: 0f5d6a1f5ec0b6062e750466d172e6564c87269b
    https://github.com/scummvm/scummvm/commit/0f5d6a1f5ec0b6062e750466d172e6564c87269b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store plugin infos in std::vector

Partially from upstream 32fb3fbe2d7186fafcf948dba92b5347b12cb7ee

Changed paths:
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/game_init.h
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/ags_plugin.h
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/game/plugin_info.h


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 5b788de722f..79a6cb00a91 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -76,8 +76,6 @@ String GetGameInitErrorText(GameInitErrorType err) {
 		return "Too many audio types for this engine to handle.";
 	case kGameInitErr_EntityInitFail:
 		return "Failed to initialize game entities.";
-	case kGameInitErr_TooManyPlugins:
-		return "Too many plugins for this engine to handle.";
 	case kGameInitErr_PluginNameInvalid:
 		return "Plugin name is invalid.";
 	case kGameInitErr_NoGlobalScript:
diff --git a/engines/ags/engine/game/game_init.h b/engines/ags/engine/game/game_init.h
index f96e0502826..5b12ad3d1de 100644
--- a/engines/ags/engine/game/game_init.h
+++ b/engines/ags/engine/game/game_init.h
@@ -45,7 +45,6 @@ enum GameInitErrorType {
 	kGameInitErr_NoFonts,
 	kGameInitErr_TooManyAudioTypes,
 	kGameInitErr_EntityInitFail,
-	kGameInitErr_TooManyPlugins,
 	kGameInitErr_PluginNameInvalid,
 	kGameInitErr_NoGlobalScript,
 	kGameInitErr_ScriptLinkFailed
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 7566f520c58..06088397265 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -157,13 +157,13 @@ void IAGSEngine::UnrequestEventHook(int32 event) {
 }
 
 int IAGSEngine::GetSavedData(char *buffer, int32 bufsize) {
-	int savedatasize = _GP(plugins)[this->pluginId].savedatasize;
+	int savedatasize = _GP(plugins)[this->pluginId].savedata.size();
 
 	if (bufsize < savedatasize)
 		quit("!IAGSEngine::GetSavedData: buffer too small");
 
 	if (savedatasize > 0)
-		memcpy(buffer, _GP(plugins)[this->pluginId].savedata, savedatasize);
+		memcpy(buffer, &_GP(plugins)[this->pluginId].savedata.front(), savedatasize);
 
 	return savedatasize;
 }
@@ -782,10 +782,6 @@ void pl_stop_plugins() {
 		if (_GP(plugins)[a].available) {
 			_GP(plugins)[a]._plugin->AGS_EngineShutdown();
 			_GP(plugins)[a].wantHook = 0;
-			if (_GP(plugins)[a].savedata) {
-				free(_GP(plugins)[a].savedata);
-				_GP(plugins)[a].savedata = nullptr;
-			}
 			if (!_GP(plugins)[a].builtin) {
 				_GP(plugins)[a].library.Unload();
 			}
@@ -848,8 +844,6 @@ Engine::GameInitError pl_register_plugins(const std::vector<PluginInfo> &infos)
 		String name = info.Name;
 		if (name.GetLast() == '!')
 			continue; // editor-only plugin, ignore it
-		if (_GP(plugins).size() == MAXPLUGINS)
-			return kGameInitErr_TooManyPlugins;
 		// AGS Editor currently saves plugin names in game data with
 		// ".dll" extension appended; we need to take care of that
 		const String name_ext = ".dll";
@@ -865,11 +859,7 @@ Engine::GameInitError pl_register_plugins(const std::vector<PluginInfo> &infos)
 
 		// Copy plugin info
 		apl->filename = name;
-		if (info.DataLen > 0) {
-			apl->savedata = (char *)malloc(info.DataLen);
-			memcpy(apl->savedata, &info.Data.front(), info.DataLen);
-		}
-		apl->savedatasize = info.DataLen;
+		apl->savedata = info.Data;
 
 		// Compatibility with the old SnowRain module
 		if (apl->filename.CompareNoCase("ags_SnowRain20") == 0) {
diff --git a/engines/ags/plugins/ags_plugin.h b/engines/ags/plugins/ags_plugin.h
index 62be0abee94..17450c19927 100644
--- a/engines/ags/plugins/ags_plugin.h
+++ b/engines/ags/plugins/ags_plugin.h
@@ -592,8 +592,7 @@ struct EnginePlugin {
 	AGS::Engine::Library library;
 	Plugins::PluginBase *_plugin = nullptr;
 	bool available = false;
-	char *savedata = nullptr;
-	int savedatasize = 0;
+	std::vector<uint8_t> savedata;
 	int wantHook = 0;
 	int invalidatedRegion = 0;
 	bool builtin = false;
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 2d8a97609bc..7780c3d47bd 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -385,7 +385,6 @@ HGameFileError ReadPlugins(std::vector<PluginInfo> &infos, Stream *in) {
 			info.Data.resize(datasize);
 			in->Read(&info.Data.front(), datasize);
 		}
-		info.DataLen = datasize;
 		infos.push_back(info);
 	}
 	return HGameFileError::None();
diff --git a/engines/ags/shared/game/plugin_info.h b/engines/ags/shared/game/plugin_info.h
index 883b7470891..e19cb360fe7 100644
--- a/engines/ags/shared/game/plugin_info.h
+++ b/engines/ags/shared/game/plugin_info.h
@@ -42,11 +42,9 @@ struct PluginInfo {
 	// (File)name of plugin
 	String      Name;
 	// Custom data for plugin
-	std::vector<char> Data;
-	size_t      DataLen;
+	std::vector<uint8_t> Data;
 
-	PluginInfo() : DataLen(0) {
-	}
+	PluginInfo() = default;
 };
 
 } // namespace Shared


Commit: df0cfa9ba41a3b87af9946e9df73c1ca12c4822c
    https://github.com/scummvm/scummvm/commit/df0cfa9ba41a3b87af9946e9df73c1ca12c4822c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in BitmapToVideoMem() replaced char* ptrs with uint8_t*

>From upstream 8d85d02a0c6e2a42b5932b18b2f30669f2e38655

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 052219d2e73..8170b0cc850 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -414,7 +414,7 @@ __inline void get_pixel_if_not_transparent32(const unsigned int *pixel, unsigned
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
 void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-												 char *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
+												 uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
 	const int src_depth = bitmap->GetColorDepth();
 	bool lastPixelWasTransparent = false;
 	switch (src_depth) {
@@ -558,7 +558,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 }
 
 void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-													   char *dst_ptr, const int dst_pitch) {
+													   uint8_t *dst_ptr, const int dst_pitch) {
 	const int src_depth = bitmap->GetColorDepth();
 
 	switch (src_depth) {
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 15ec5a31e00..309eaee0959 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -299,10 +299,10 @@ protected:
 
 	// Prepares bitmap to be applied to the texture, copies pixels to the provided buffer
 	void BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-		char *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
+						  uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
 	// Same but optimized for opaque source bitmaps which ignore transparent "mask color"
 	void BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-		char *dst_ptr, const int dst_pitch);
+								uint8_t *dst_ptr, const int dst_pitch);
 
 	// Stage virtual screen is used to let plugins draw custom graphics
 	// in between render stages (between room and GUI, after GUI, and so on)


Commit: d8d4a77724fb6a16d01190c5ff5f744286a9facb
    https://github.com/scummvm/scummvm/commit/d8d4a77724fb6a16d01190c5ff5f744286a9facb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hide "screenover" vector behind interface functions

>From upstream 3b049092875293b0960013910586f3adb017c862

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_overlay.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/overlay.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index df81e83f7e3..4bc4cfd015c 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -714,12 +714,12 @@ void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *t
 
 ScriptOverlay *Character_SayBackground(CharacterInfo *chaa, const char *texx) {
 	int ovltype = DisplaySpeechBackground(chaa->index_id, texx);
-	int ovri = find_overlay_of_type(ovltype);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(ovltype);
+	if (!over)
 		quit("!SayBackground internal error: no overlay");
 
 	// Create script object with an internal ref, keep at least until internal timeout
-	return create_scriptoverlay(_GP(screenover)[ovri], true);
+	return create_scriptoverlay(*over, true);
 }
 
 void Character_SetAsPlayer(CharacterInfo *chaa) {
@@ -2337,9 +2337,10 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 
 	if (_GP(play).bgspeech_stay_on_display == 0) {
 		// remove any background speech
-		for (size_t i = 0; i < _GP(screenover).size();) {
-			if (_GP(screenover)[i].timeout > 0)
-				remove_screen_overlay(_GP(screenover)[i].type);
+		auto &overs = get_overlays();
+		for (size_t i = 0; i < overs.size();) {
+			if (overs[i].timeout > 0)
+				remove_screen_overlay(overs[i].type);
 			else
 				i++;
 		}
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index ee334c7c820..58e62cfa537 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -276,11 +276,12 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	}
 
 	size_t nse = add_screen_overlay(roomlayer, xx, yy, ovrtype, text_window_ds, adjustedXX - xx, adjustedYY - yy, alphaChannel);
+	auto &over = get_overlay(nse); // FIXME: optimize return value
 	// we should not delete text_window_ds here, because it is now owned by Overlay
 
 	// If it's a non-blocking overlay type, then we're done here
 	if (disp_type >= DISPLAYTEXT_NORMALOVERLAY) {
-		return &_GP(screenover)[nse];
+		return &over;
 	}
 
 	//
@@ -371,10 +372,10 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 			_GP(play).messagetime = 2;
 
 		if (!overlayPositionFixed) {
-			_GP(screenover)[nse].SetRoomRelative(true);
-			VpPoint vpt = _GP(play).GetRoomViewport(0)->ScreenToRoom(_GP(screenover)[nse].x, _GP(screenover)[nse].y, false);
-			_GP(screenover)[nse].x = vpt.first.X;
-			_GP(screenover)[nse].y = vpt.first.Y;
+			over.SetRoomRelative(true);
+			VpPoint vpt = _GP(play).GetRoomViewport(0)->ScreenToRoom(over.x, over.y, false);
+			over.x = vpt.first.X;
+			over.y = vpt.first.Y;
 		}
 
 		GameLoopUntilNoOverlay();
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 159bcbb3d42..b666f949772 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1548,8 +1548,8 @@ void add_walkbehind_image(size_t index, Shared::Bitmap *bmp, int x, int y) {
 
 // Add active room overlays to the sprite list
 static void add_roomovers_for_drawing() {
-	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
-		auto &over = _GP(screenover)[i];
+	const auto &overs = get_overlays();
+	for (const auto &over : overs) {
 		if (!over.IsRoomLayer()) continue; // not a room layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
@@ -1746,8 +1746,8 @@ void draw_gui_and_overlays() {
 	clear_sprite_list();
 
 	// Add active overlays to the sprite list
-	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
-		auto &over = _GP(screenover)[i];
+	const auto &overs = get_overlays();
+	for (const auto &over : overs) {
 		if (over.IsRoomLayer()) continue; // not a ui layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
@@ -1969,12 +1969,13 @@ static void construct_ui_view() {
 // but does not put them on screen yet - that's done in respective construct_*_view functions
 static void construct_overlays() {
 	const bool is_software_mode = !_G(gfxDriver)->HasAcceleratedTransform();
-	if (_GP(overlaybmp).size() < _GP(screenover).size()) {
-		_GP(overlaybmp).resize(_GP(screenover).size());
-		_GP(screenovercache).resize(_GP(screenover).size());
+	auto &overs = get_overlays();
+	if (_GP(overlaybmp).size() < overs.size()) {
+		_GP(overlaybmp).resize(overs.size());
+		_GP(screenovercache).resize(overs.size());
 	}
-	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
-		auto &over = _GP(screenover)[i];
+	for (size_t i = 0; i < overs.size(); ++i) {
+		auto &over = overs[i];
 		if (over.transparency == 255) continue; // skip fully transparent
 
 		bool has_changed = over.HasChanged();
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 1c8ef6f6a37..fd5d0a7ace6 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -38,10 +38,9 @@ int ScriptOverlay::Dispose(const char *address, bool force) {
 	// reference so it doesn't try and dispose something else
 	// with that handle later
 	if (overlayId >= 0) {
-		int overlayIndex = find_overlay_of_type(overlayId);
-		if (overlayIndex >= 0) {
-			_GP(screenover)
-			[overlayIndex].associatedOverlayHandle = 0;
+		auto *over = find_overlay_of_type(overlayId);
+		if (over) {
+			over->associatedOverlayHandle = 0;
 		}
 	}
 
@@ -80,7 +79,7 @@ void ScriptOverlay::Unserialize(int index, Stream *in, size_t data_sz) {
 }
 
 void ScriptOverlay::Remove() {
-	int overlayIndex = find_overlay_of_type(overlayId);
+	int overlayIndex = find_overlay_index(overlayId);
 	if (overlayIndex < 0) {
 		debug_script_warn("Overlay.Remove: overlay is invalid, could have been removed earlier.");
 		return;
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 19c1d92cb7c..00f5b7376ce 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1376,7 +1376,8 @@ void game_sprite_updated(int sprnum) {
 		}
 	}
 	// overlays
-	for (auto &over : _GP(screenover)) {
+	auto &overs = get_overlays();
+	for (auto &over : overs) {
 		if (over.GetSpriteNum() == sprnum)
 			over.MarkChanged();
 	}
@@ -1447,7 +1448,8 @@ void game_sprite_deleted(int sprnum) {
 		}
 	}
 	// overlays
-	for (auto &over : _GP(screenover)) {
+	auto &overs = get_overlays();
+	for (auto &over : overs) {
 		if (over.GetSpriteNum() == sprnum)
 			over.SetSpriteNum(0);
 	}
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 5501b8bdbf6..9903f3db3d0 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -531,19 +531,20 @@ void DisplaySpeechAt(int xx, int yy, int wii, int aschar, const char *spch) {
 
 int DisplaySpeechBackground(int charid, const char *speel) {
 	// remove any previous background speech for this character
-	for (size_t i = 0; i < _GP(screenover).size();) {
-		if (_GP(screenover)[i].bgSpeechForChar == charid)
+	const auto &overs = get_overlays();
+	for (size_t i = 0; i < overs.size(); ++i) {
+		if (overs[i].bgSpeechForChar == charid) {
 			remove_screen_overlay_index(i);
-		else
-			i++;
+			break;
+		}
 	}
 
 	int ovrl = CreateTextOverlay(OVR_AUTOPLACE, charid, _GP(play).GetUIViewport().GetWidth() / 2, FONT_SPEECH,
 	                             -_GP(game).chars[charid].talkcolor, get_translation(speel), DISPLAYTEXT_NORMALOVERLAY);
 
-	int scid = find_overlay_of_type(ovrl);
-	_GP(screenover)[scid].bgSpeechForChar = charid;
-	_GP(screenover)[scid].timeout = GetTextDisplayTime(speel, 1);
+	auto *over = find_overlay_of_type(ovrl);
+	over->bgSpeechForChar = charid;
+	over->timeout = GetTextDisplayTime(speel, 1);
 	return ovrl;
 }
 
diff --git a/engines/ags/engine/ac/global_overlay.cpp b/engines/ags/engine/ac/global_overlay.cpp
index f03f705175d..c0a4e709966 100644
--- a/engines/ags/engine/ac/global_overlay.cpp
+++ b/engines/ags/engine/ac/global_overlay.cpp
@@ -32,7 +32,8 @@ using namespace Shared;
 using namespace Engine;
 
 void RemoveOverlay(int ovrid) {
-	if (find_overlay_of_type(ovrid) < 0) quit("!RemoveOverlay: invalid overlay id passed");
+	if (find_overlay_of_type(ovrid) == nullptr)
+		quit("!RemoveOverlay: invalid overlay id passed");
 	remove_screen_overlay(ovrid);
 }
 
@@ -66,14 +67,15 @@ void SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int text_col
 void MoveOverlay(int ovrid, int newx, int newy) {
 	data_to_game_coords(&newx, &newy);
 
-	int ovri = find_overlay_of_type(ovrid);
-	if (ovri < 0) quit("!MoveOverlay: invalid overlay ID specified");
-	_GP(screenover)[ovri].x = newx;
-	_GP(screenover)[ovri].y = newy;
+	auto *over = find_overlay_of_type(ovrid);
+	if (!over)
+		quit("!MoveOverlay: invalid overlay ID specified");
+	over->x = newx;
+	over->y = newy;
 }
 
 int IsOverlayValid(int ovrid) {
-	if (find_overlay_of_type(ovrid) < 0)
+	if (find_overlay_of_type(ovrid) == nullptr)
 		return 0;
 
 	return 1;
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index c4f87c20ba3..731541d787f 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -55,12 +55,11 @@ void Overlay_Remove(ScriptOverlay *sco) {
 }
 
 void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_color, const char *text) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!Overlay.SetText: invalid overlay ID specified");
-	auto &over = _GP(screenover)[ovri];
-	const int x = over.x;
-	const int y = over.y;
+	const int x = over->x;
+	const int y = over->y;
 
 	// TODO: find a nice way to refactor and share these code pieces
 	// from CreateTextOverlay
@@ -82,50 +81,50 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 										 width, fontid, allow_shrink, has_alpha);
 
 	// Update overlay properties
-	over.SetImage(image, adj_x - dummy_x, adj_y - dummy_y);
-	over.SetAlphaChannel(has_alpha);
-	over.ddb = nullptr; // is generated during first draw pass
+	over->SetImage(image, adj_x - dummy_x, adj_y - dummy_y);
+	over->SetAlphaChannel(has_alpha);
+	over->ddb = nullptr; // is generated during first draw pass
 }
 
 int Overlay_GetX(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	Point pos = get_overlay_position(_GP(screenover)[ovri]);
+	Point pos = get_overlay_position(*over);
 	return game_to_data_coord(pos.X);
 }
 
 void Overlay_SetX(ScriptOverlay *scover, int newx) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	_GP(screenover)[ovri].x = data_to_game_coord(newx);
+	over->x = data_to_game_coord(newx);
 }
 
 int Overlay_GetY(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	Point pos = get_overlay_position(_GP(screenover)[ovri]);
+	Point pos = get_overlay_position(*over);
 	return game_to_data_coord(pos.Y);
 }
 
 void Overlay_SetY(ScriptOverlay *scover, int newy) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	_GP(screenover)[ovri].y = data_to_game_coord(newy);
+	over->y = data_to_game_coord(newy);
 }
 
 int Overlay_GetGraphic(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return _GP(screenover)[ovri].GetSpriteNum();
+	return over->GetSpriteNum();
 }
 
 void Overlay_SetGraphic(ScriptOverlay *scover, int slot) {
@@ -133,45 +132,45 @@ void Overlay_SetGraphic(ScriptOverlay *scover, int slot) {
 		debug_script_warn("Overlay.SetGraphic: sprite %d is invalid", slot);
 		slot = 0;
 	}
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	_GP(screenover)[ovri].SetSpriteNum(slot);
+	over->SetSpriteNum(slot);
 }
 
 bool Overlay_InRoom(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return _GP(screenover)[ovri].IsRoomLayer();
+	return over->IsRoomLayer();
 }
 
 int Overlay_GetWidth(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].scaleWidth);
+	return game_to_data_coord(over->scaleWidth);
 }
 
 int Overlay_GetHeight(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].scaleHeight);
+	return game_to_data_coord(over->scaleHeight);
 }
 
 int Overlay_GetGraphicWidth(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].GetImage()->GetWidth());
+	return game_to_data_coord(over->GetImage()->GetWidth());
 }
 
 int Overlay_GetGraphicHeight(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].GetImage()->GetHeight());
+	return game_to_data_coord(over->GetImage()->GetHeight());
 }
 
 void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
@@ -188,17 +187,17 @@ void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
 }
 
 void Overlay_SetWidth(ScriptOverlay *scover, int width) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	Overlay_SetScaledSize(_GP(screenover)[ovri], width, game_to_data_coord(_GP(screenover)[ovri].scaleHeight));
+	Overlay_SetScaledSize(*over, width, game_to_data_coord(over->scaleHeight));
 }
 
 void Overlay_SetHeight(ScriptOverlay *scover, int height) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
-	Overlay_SetScaledSize(_GP(screenover)[ovri], game_to_data_coord(_GP(screenover)[ovri].scaleWidth), height);
+	Overlay_SetScaledSize(*over, game_to_data_coord(over->scaleWidth), height);
 }
 
 int Overlay_GetValid(ScriptOverlay *scover) {
@@ -274,37 +273,37 @@ ScriptOverlay *Overlay_CreateRoomTextual(int x, int y, int width, int font, int
 }
 
 int Overlay_GetTransparency(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	return GfxDef::LegacyTrans255ToTrans100(_GP(screenover)[ovri].transparency);
+	return GfxDef::LegacyTrans255ToTrans100(over->transparency);
 }
 
 void Overlay_SetTransparency(ScriptOverlay *scover, int trans) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 	if ((trans < 0) | (trans > 100))
 		quit("!SetTransparency: transparency value must be between 0 and 100");
 
-	_GP(screenover)[ovri].transparency = GfxDef::Trans100ToLegacyTrans255(trans);
+	over->transparency = GfxDef::Trans100ToLegacyTrans255(trans);
 }
 
 int Overlay_GetZOrder(ScriptOverlay *scover) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	return _GP(screenover)[ovri].zorder;
+	return over->zorder;
 }
 
 void Overlay_SetZOrder(ScriptOverlay *scover, int zorder) {
-	int ovri = find_overlay_of_type(scover->overlayId);
-	if (ovri < 0)
+	auto *over = find_overlay_of_type(scover->overlayId);
+	if (!over)
 		quit("!invalid overlay ID specified");
 
-	_GP(screenover)[ovri].zorder = zorder;
+	over->zorder = zorder;
 }
 
 //=============================================================================
@@ -385,7 +384,12 @@ void remove_screen_overlay(int type) {
 	}
 }
 
-int find_overlay_of_type(int type) {
+ScreenOverlay &get_overlay(int index) {
+	assert(index >= 0 && index < _GP(screenover).size());
+	return _GP(screenover)[index];
+}
+
+int find_overlay_index(int type) {
 	assert(type >= 0);
 	int idx = _GP(overlookup)[type];
 	if (idx >= 0 && idx < (int)_GP(screenover).size() && _GP(screenover)[idx].type == type)
@@ -400,6 +404,11 @@ int find_overlay_of_type(int type) {
 	return -1;
 }
 
+ScreenOverlay *find_overlay_of_type(int type) {
+	int idx = find_overlay_index(type);
+	return idx >= 0 ? &_GP(screenover)[idx] : nullptr;
+}
+
 size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnum, Bitmap *piccy,
 							   int pic_offx, int pic_offy, bool has_alpha) {
 	const auto new_size = _GP(screenover).size() + OVER_CUSTOM + 2;
@@ -408,7 +417,7 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	if (type == OVER_CUSTOM) {
 		// find an unused custom ID; TODO: find a better approach!
 		for (int id = OVER_CUSTOM + 1; (size_t)id <= _GP(screenover).size() + OVER_CUSTOM + 1; ++id) {
-			if (find_overlay_of_type(id) == -1) {
+			if (find_overlay_of_type(id) == nullptr) {
 				type = id; break;
 			}
 		}
@@ -516,6 +525,10 @@ void recreate_overlay_ddbs() {
 	}
 }
 
+std::vector<ScreenOverlay> &get_overlays() {
+	return _GP(screenover);
+}
+
 //=============================================================================
 //
 // Script API Functions
diff --git a/engines/ags/engine/ac/overlay.h b/engines/ags/engine/ac/overlay.h
index 3046edc88a9..628242c5cc3 100644
--- a/engines/ags/engine/ac/overlay.h
+++ b/engines/ags/engine/ac/overlay.h
@@ -22,6 +22,7 @@
 #ifndef AGS_ENGINE_AC_OVERLAY_H
 #define AGS_ENGINE_AC_OVERLAY_H
 
+#include "common/std/vector.h"
 #include "ags/shared/util/geometry.h"
 #include "ags/engine/ac/screen_overlay.h"
 #include "ags/engine/ac/dynobj/script_overlay.h"
@@ -49,7 +50,9 @@ ScreenOverlay *Overlay_CreateGraphicCore(bool room_layer, int x, int y, int slot
 ScreenOverlay *Overlay_CreateTextCore(bool room_layer, int x, int y, int width, int font, int text_color,
 									  const char *text, int disp_type, int allow_shrink);
 
-int  find_overlay_of_type(int type);
+ScreenOverlay &get_overlay(int index);
+ScreenOverlay *find_overlay_of_type(int type);
+int  find_overlay_index(int type);
 void remove_screen_overlay(int type);
 // Calculates overlay position in its respective layer (screen or room)
 Point get_overlay_position(const ScreenOverlay &over);
@@ -62,6 +65,8 @@ void remove_screen_overlay_index(size_t over_idx);
 ScriptOverlay *create_scriptoverlay(ScreenOverlay &over, bool internal_ref = false);
 void recreate_overlay_ddbs();
 
+std::vector<ScreenOverlay> &get_overlays();
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 1dc8e1b6d2c..0c19c8fbd60 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -757,8 +757,9 @@ HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, const PreservedPa
 }
 
 HSaveError WriteOverlays(Stream *out) {
-	out->WriteInt32(_GP(screenover).size());
-	for (const auto &over : _GP(screenover)) {
+	const auto &overs = get_overlays();
+	out->WriteInt32(overs.size());
+	for (const auto &over : overs) {
 		over.WriteToFile(out);
 		if (!over.IsSpriteReference())
 			serialize_bitmap(over.GetImage(), out);
@@ -768,6 +769,7 @@ HSaveError WriteOverlays(Stream *out) {
 
 HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	size_t over_count = in->ReadInt32();
+	auto &overs = get_overlays();
 	for (size_t i = 0; i < over_count; ++i) {
 		ScreenOverlay over;
 		bool has_bitmap;
@@ -778,7 +780,7 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 			over.scaleWidth = over.GetImage()->GetWidth();
 			over.scaleHeight = over.GetImage()->GetHeight();
 		}
-		_GP(screenover).push_back(std::move(over));
+		overs.push_back(std::move(over));
 	}
 	return HSaveError::None();
 }
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index b86456acc93..cbc0ee45f51 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -319,9 +319,10 @@ static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
 static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size_t num_overs) {
 	AlignedStream align_s(in, Shared::kAligned_Read);
 	has_bitmap.resize(num_overs);
+	auto &overs = get_overlays();
 	for (size_t i = 0; i < num_overs; ++i) {
 		bool has_bm;
-		_GP(screenover)[i].ReadFromFile(&align_s, has_bm, 0);
+		overs[i].ReadFromFile(&align_s, has_bm, 0);
 		has_bitmap[i] = has_bm;
 		align_s.Reset();
 	}
@@ -329,12 +330,13 @@ static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size
 
 static void restore_game_overlays(Stream *in) {
 	size_t num_overs = in->ReadInt32();
-	_GP(screenover).resize(num_overs);
+	auto &overs = get_overlays();
+	overs.resize(num_overs);
 	std::vector<bool> has_bitmap;
 	ReadOverlays_Aligned(in, has_bitmap, num_overs);
 	for (size_t i = 0; i < num_overs; ++i) {
 		if (has_bitmap[i])
-			_GP(screenover)[i].SetImage(read_serialized_bitmap(in), _GP(screenover)[i].offsetX, _GP(screenover)[i].offsetY);
+			overs[i].SetImage(read_serialized_bitmap(in), overs[i].offsetX, overs[i].offsetY);
 	}
 }
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index a1e3b667d19..8ea04cf09aa 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -224,10 +224,12 @@ void update_following_exactly_characters(const std::vector<int> &followingAsShee
 
 void update_overlay_timers() {
 	// update overlay timers
-	for (size_t i = 0; i < _GP(screenover).size();) {
-		if (_GP(screenover)[i].timeout > 0) {
-			_GP(screenover)[i].timeout--;
-			if (_GP(screenover)[i].timeout == 0) {
+	auto &overs = get_overlays();
+	for (size_t i = 0; i < overs.size();) {
+		auto &over = overs[i];
+		if (over.timeout > 0) {
+			over.timeout--;
+			if (over.timeout == 0) {
 				remove_screen_overlay_index(i);
 				continue;
 			}
@@ -388,7 +390,8 @@ void update_sierra_speech() {
 			int view_frame_x = 0;
 			int view_frame_y = 0;
 
-			Bitmap *frame_pic = _GP(screenover)[_G(face_talking)].GetImage();
+			auto &face_over = get_overlay(_G(face_talking));
+			Bitmap *frame_pic = face_over.GetImage();
 			if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
 				// QFG4-style fullscreen dialog
 				if (_G(facetalk_qfg4_override_placement_x)) {
@@ -415,8 +418,8 @@ void update_sierra_speech() {
 				DrawViewFrame(frame_pic, blink_vf, view_frame_x, view_frame_y, face_has_alpha);
 			}
 
-			_GP(screenover)[_G(face_talking)].SetAlphaChannel(face_has_alpha);
-			_GP(screenover)[_G(face_talking)].MarkChanged();
+			face_over.SetAlphaChannel(face_has_alpha);
+			face_over.MarkChanged();
 		}  // end if updatedFrame
 	}
 }


Commit: f9087ab3785de709962c3aa4638fbcd0f48623e1
    https://github.com/scummvm/scummvm/commit/f9087ab3785de709962c3aa4638fbcd0f48623e1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: optimized ScreenOverlays storage (vector variant)

Keep ScreenOverlays in std::vector, but do not erase elements
 in the middle and invalidate them instead. Record free indexes
 in a helper stack.
 When creating a new overlay, first check for the recorded free indexes.
Overlay's type now equals storage index.

This speeds up both creation/deletion of overlays, and their access.

Removed the gap between "fixed" overlay types and custom ones,
 to save space. If we ever need more special overlays, these could
 be created as custom and their ids saved in a variable (similar to
 how background speech works). Previously present special overlay
  types are left for backwards compatibility (and to simplify the transition).
  From upstream f7d2f162fa55aa1b6f37195d890ebe047a204154

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_overlay.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/overlay.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 4bc4cfd015c..7033e6786ed 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -714,7 +714,7 @@ void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *t
 
 ScriptOverlay *Character_SayBackground(CharacterInfo *chaa, const char *texx) {
 	int ovltype = DisplaySpeechBackground(chaa->index_id, texx);
-	auto *over = find_overlay_of_type(ovltype);
+	auto *over = get_overlay(ovltype);
 	if (!over)
 		quit("!SayBackground internal error: no overlay");
 
@@ -2338,11 +2338,9 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	if (_GP(play).bgspeech_stay_on_display == 0) {
 		// remove any background speech
 		auto &overs = get_overlays();
-		for (size_t i = 0; i < overs.size();) {
-			if (overs[i].timeout > 0)
-				remove_screen_overlay(overs[i].type);
-			else
-				i++;
+		for (auto &over : overs) {
+			if (over.timeout > 0)
+				remove_screen_overlay(over.type);
 		}
 	}
 	_G(said_text) = 1;
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 58e62cfa537..bbb0148f24f 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -276,12 +276,12 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	}
 
 	size_t nse = add_screen_overlay(roomlayer, xx, yy, ovrtype, text_window_ds, adjustedXX - xx, adjustedYY - yy, alphaChannel);
-	auto &over = get_overlay(nse); // FIXME: optimize return value
+	auto *over = get_overlay(nse); // FIXME: optimize return value
 	// we should not delete text_window_ds here, because it is now owned by Overlay
 
 	// If it's a non-blocking overlay type, then we're done here
 	if (disp_type >= DISPLAYTEXT_NORMALOVERLAY) {
-		return &over;
+		return over;
 	}
 
 	//
@@ -372,10 +372,10 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 			_GP(play).messagetime = 2;
 
 		if (!overlayPositionFixed) {
-			over.SetRoomRelative(true);
-			VpPoint vpt = _GP(play).GetRoomViewport(0)->ScreenToRoom(over.x, over.y, false);
-			over.x = vpt.first.X;
-			over.y = vpt.first.Y;
+			over->SetRoomRelative(true);
+			VpPoint vpt = _GP(play).GetRoomViewport(0)->ScreenToRoom(over->x, over->y, false);
+			over->x = vpt.first.X;
+			over->y = vpt.first.Y;
 		}
 
 		GameLoopUntilNoOverlay();
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index b666f949772..71cfbe86100 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1550,6 +1550,7 @@ void add_walkbehind_image(size_t index, Shared::Bitmap *bmp, int x, int y) {
 static void add_roomovers_for_drawing() {
 	const auto &overs = get_overlays();
 	for (const auto &over : overs) {
+		if (over.type < 0) continue; // empty slot
 		if (!over.IsRoomLayer()) continue; // not a room layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
@@ -1748,6 +1749,7 @@ void draw_gui_and_overlays() {
 	// Add active overlays to the sprite list
 	const auto &overs = get_overlays();
 	for (const auto &over : overs) {
+		if (over.type < 0) continue; // empty slot
 		if (over.IsRoomLayer()) continue; // not a ui layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
@@ -1976,6 +1978,7 @@ static void construct_overlays() {
 	}
 	for (size_t i = 0; i < overs.size(); ++i) {
 		auto &over = overs[i];
+		if (over.type < 0) continue; // empty slot
 		if (over.transparency == 255) continue; // skip fully transparent
 
 		bool has_changed = over.HasChanged();
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index fd5d0a7ace6..6da5b41a3dc 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -38,7 +38,7 @@ int ScriptOverlay::Dispose(const char *address, bool force) {
 	// reference so it doesn't try and dispose something else
 	// with that handle later
 	if (overlayId >= 0) {
-		auto *over = find_overlay_of_type(overlayId);
+		auto *over = get_overlay(overlayId);
 		if (over) {
 			over->associatedOverlayHandle = 0;
 		}
@@ -79,12 +79,11 @@ void ScriptOverlay::Unserialize(int index, Stream *in, size_t data_sz) {
 }
 
 void ScriptOverlay::Remove() {
-	int overlayIndex = find_overlay_index(overlayId);
-	if (overlayIndex < 0) {
+	if (overlayId < 0) {
 		debug_script_warn("Overlay.Remove: overlay is invalid, could have been removed earlier.");
 		return;
 	}
-	remove_screen_overlay_index(overlayIndex);
+	remove_screen_overlay(overlayId);
 	overlayId = -1;
 }
 
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 9903f3db3d0..d726c2a07ff 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -531,10 +531,11 @@ void DisplaySpeechAt(int xx, int yy, int wii, int aschar, const char *spch) {
 
 int DisplaySpeechBackground(int charid, const char *speel) {
 	// remove any previous background speech for this character
+	// TODO: have a map character -> bg speech over?
 	const auto &overs = get_overlays();
 	for (size_t i = 0; i < overs.size(); ++i) {
 		if (overs[i].bgSpeechForChar == charid) {
-			remove_screen_overlay_index(i);
+			remove_screen_overlay(i);
 			break;
 		}
 	}
@@ -542,7 +543,7 @@ int DisplaySpeechBackground(int charid, const char *speel) {
 	int ovrl = CreateTextOverlay(OVR_AUTOPLACE, charid, _GP(play).GetUIViewport().GetWidth() / 2, FONT_SPEECH,
 	                             -_GP(game).chars[charid].talkcolor, get_translation(speel), DISPLAYTEXT_NORMALOVERLAY);
 
-	auto *over = find_overlay_of_type(ovrl);
+	auto *over = get_overlay(ovrl);
 	over->bgSpeechForChar = charid;
 	over->timeout = GetTextDisplayTime(speel, 1);
 	return ovrl;
diff --git a/engines/ags/engine/ac/global_overlay.cpp b/engines/ags/engine/ac/global_overlay.cpp
index c0a4e709966..163eaa7dc6b 100644
--- a/engines/ags/engine/ac/global_overlay.cpp
+++ b/engines/ags/engine/ac/global_overlay.cpp
@@ -32,7 +32,7 @@ using namespace Shared;
 using namespace Engine;
 
 void RemoveOverlay(int ovrid) {
-	if (find_overlay_of_type(ovrid) == nullptr)
+	if (!get_overlay(ovrid))
 		quit("!RemoveOverlay: invalid overlay id passed");
 	remove_screen_overlay(ovrid);
 }
@@ -67,7 +67,7 @@ void SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int text_col
 void MoveOverlay(int ovrid, int newx, int newy) {
 	data_to_game_coords(&newx, &newy);
 
-	auto *over = find_overlay_of_type(ovrid);
+	auto *over = get_overlay(ovrid);
 	if (!over)
 		quit("!MoveOverlay: invalid overlay ID specified");
 	over->x = newx;
@@ -75,10 +75,7 @@ void MoveOverlay(int ovrid, int newx, int newy) {
 }
 
 int IsOverlayValid(int ovrid) {
-	if (find_overlay_of_type(ovrid) == nullptr)
-		return 0;
-
-	return 1;
+	return (get_overlay(ovrid) != nullptr) ? 1 : 0;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 731541d787f..f034919cd4f 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -55,7 +55,7 @@ void Overlay_Remove(ScriptOverlay *sco) {
 }
 
 void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_color, const char *text) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!Overlay.SetText: invalid overlay ID specified");
 	const int x = over->x;
@@ -87,7 +87,7 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 }
 
 int Overlay_GetX(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -96,7 +96,7 @@ int Overlay_GetX(ScriptOverlay *scover) {
 }
 
 void Overlay_SetX(ScriptOverlay *scover, int newx) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -104,7 +104,7 @@ void Overlay_SetX(ScriptOverlay *scover, int newx) {
 }
 
 int Overlay_GetY(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -113,7 +113,7 @@ int Overlay_GetY(ScriptOverlay *scover) {
 }
 
 void Overlay_SetY(ScriptOverlay *scover, int newy) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -121,7 +121,7 @@ void Overlay_SetY(ScriptOverlay *scover, int newy) {
 }
 
 int Overlay_GetGraphic(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return over->GetSpriteNum();
@@ -132,42 +132,42 @@ void Overlay_SetGraphic(ScriptOverlay *scover, int slot) {
 		debug_script_warn("Overlay.SetGraphic: sprite %d is invalid", slot);
 		slot = 0;
 	}
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	over->SetSpriteNum(slot);
 }
 
 bool Overlay_InRoom(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return over->IsRoomLayer();
 }
 
 int Overlay_GetWidth(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return game_to_data_coord(over->scaleWidth);
 }
 
 int Overlay_GetHeight(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return game_to_data_coord(over->scaleHeight);
 }
 
 int Overlay_GetGraphicWidth(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return game_to_data_coord(over->GetImage()->GetWidth());
 }
 
 int Overlay_GetGraphicHeight(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	return game_to_data_coord(over->GetImage()->GetHeight());
@@ -187,14 +187,14 @@ void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
 }
 
 void Overlay_SetWidth(ScriptOverlay *scover, int width) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	Overlay_SetScaledSize(*over, width, game_to_data_coord(over->scaleHeight));
 }
 
 void Overlay_SetHeight(ScriptOverlay *scover, int height) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	Overlay_SetScaledSize(*over, game_to_data_coord(over->scaleWidth), height);
@@ -273,7 +273,7 @@ ScriptOverlay *Overlay_CreateRoomTextual(int x, int y, int width, int font, int
 }
 
 int Overlay_GetTransparency(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -281,7 +281,7 @@ int Overlay_GetTransparency(ScriptOverlay *scover) {
 }
 
 void Overlay_SetTransparency(ScriptOverlay *scover, int trans) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 	if ((trans < 0) | (trans > 100))
@@ -291,7 +291,7 @@ void Overlay_SetTransparency(ScriptOverlay *scover, int trans) {
 }
 
 int Overlay_GetZOrder(ScriptOverlay *scover) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -299,7 +299,7 @@ int Overlay_GetZOrder(ScriptOverlay *scover) {
 }
 
 void Overlay_SetZOrder(ScriptOverlay *scover, int zorder) {
-	auto *over = find_overlay_of_type(scover->overlayId);
+	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
 
@@ -348,11 +348,12 @@ static void dispose_overlay(ScreenOverlay &over) {
 	}
 }
 
-void remove_screen_overlay_index(size_t over_idx) {
-	assert(over_idx < _GP(screenover).size());
-	if (over_idx >= _GP(screenover).size())
+void remove_screen_overlay(int type) {
+	assert(type >= 0 && type < _GP(screenover).size());
+	if (type < 0 || type >= _GP(screenover).size() || _GP(screenover)[type].type < 0)
 		return; // something is wrong
-	ScreenOverlay &over = _GP(screenover)[over_idx];
+
+	ScreenOverlay &over = _GP(screenover)[type];
 	// TODO: move these custom settings outside of this function
 	if (over.type == _GP(play).complete_overlay_on) {
 		_GP(play).complete_overlay_on = 0;
@@ -368,60 +369,36 @@ void remove_screen_overlay_index(size_t over_idx) {
 		invalidate_and_subref(over);
 	}
 	dispose_overlay(over);
-	_GP(screenover).erase(_GP(screenover).begin() + over_idx);
-	// if an overlay before the sierra-style speech one is removed, update the index
-	// TODO: this is bad, need more generic system to store overlay references
-	if ((size_t)_G(face_talking) > over_idx)
-		_G(face_talking)--;
+    // Don't erase vector elements, instead set invalid and record free index
+    _GP(screenover)[type] = ScreenOverlay();
+    if (type >= OVER_FIRSTFREE)
+        _GP(over_free_ids).push(type);
 }
 
-void remove_screen_overlay(int type) {
-	for (size_t i = 0; i < _GP(screenover).size();) {
-		if (type < 0 || _GP(screenover)[i].type == type)
-			remove_screen_overlay_index(i);
-		else
-			i++;
-	}
+void remove_all_overlays() {
+	for (int i = 0; i < _GP(screenover).size(); ++i)
+		remove_screen_overlay(i);
 }
 
-ScreenOverlay &get_overlay(int index) {
+ScreenOverlay *get_overlay(int index) {
 	assert(index >= 0 && index < _GP(screenover).size());
-	return _GP(screenover)[index];
-}
-
-int find_overlay_index(int type) {
-	assert(type >= 0);
-	int idx = _GP(overlookup)[type];
-	if (idx >= 0 && idx < (int)_GP(screenover).size() && _GP(screenover)[idx].type == type)
-		return idx;
-	for (idx = 0; idx < (int)_GP(screenover).size(); ++idx) {
-		if (_GP(screenover)[idx].type == type) {
-			_GP(overlookup)
-			[type] = idx;
-			return idx;
-		}
-	}
-	return -1;
-}
-
-ScreenOverlay *find_overlay_of_type(int type) {
-	int idx = find_overlay_index(type);
-	return idx >= 0 ? &_GP(screenover)[idx] : nullptr;
+	return (index >= 0 && index < _GP(screenover).size()) ? &_GP(screenover)[index] : nullptr;
 }
 
 size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnum, Bitmap *piccy,
 							   int pic_offx, int pic_offy, bool has_alpha) {
-	const auto new_size = _GP(screenover).size() + OVER_CUSTOM + 2;
-	if (_GP(overlookup).size() < new_size)
-		_GP(overlookup).resize(new_size);
 	if (type == OVER_CUSTOM) {
-		// find an unused custom ID; TODO: find a better approach!
-		for (int id = OVER_CUSTOM + 1; (size_t)id <= _GP(screenover).size() + OVER_CUSTOM + 1; ++id) {
-			if (find_overlay_of_type(id) == nullptr) {
-				type = id; break;
-			}
+		// Find a free ID
+		if (_GP(over_free_ids).size() > 0) {
+			type = _GP(over_free_ids).front();
+			_GP(over_free_ids).pop();
+		} else {
+			type = std::max((uint)OVER_FIRSTFREE, _GP(screenover).size());
 		}
 	}
+
+	if (_GP(screenover).size() <= type)
+		_GP(screenover).resize(type + 1);
 	ScreenOverlay over;
 	if (piccy) {
 		over.SetImage(piccy, pic_offx, pic_offy);
@@ -456,9 +433,8 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 		_GP(play).speech_face_schandle = over.associatedOverlayHandle;
 	}
 	over.MarkChanged();
-	_GP(overlookup)[type] = _GP(screenover).size();
-	_GP(screenover).push_back(std::move(over));
-	return _GP(screenover).size() - 1;
+	_GP(screenover)[type] = std::move(over);
+	return type;
 }
 
 size_t add_screen_overlay(bool roomlayer, int x, int y, int type, int sprnum) {
@@ -513,15 +489,18 @@ Point get_overlay_position(const ScreenOverlay &over) {
 	}
 }
 
-void recreate_overlay_ddbs() {
-	_GP(overlookup).resize(_GP(screenover).size() + OVER_CUSTOM + 2);
-	for (size_t i = 0; i < _GP(screenover).size(); i++) {
+void restore_overlays() {
+	// Will have to readjust free ids records, as overlays may be restored in any random slots
+	while (!_GP(over_free_ids).empty()) {
+		_GP(over_free_ids).pop();
+	}
+	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
 		auto &over = _GP(screenover)[i];
-		_GP(overlookup)[over.type] = i;
-		if (over.ddb)
-			_G(gfxDriver)->DestroyDDB(over.ddb);
-		over.ddb = nullptr; // is generated during first draw pass
-		over.MarkChanged();
+		if (over.type >= 0) {
+			over.MarkChanged(); // force recreate texture on next draw
+		} else if (i >= OVER_FIRSTFREE) {
+			_GP(over_free_ids).push(i);
+		}
 	}
 }
 
diff --git a/engines/ags/engine/ac/overlay.h b/engines/ags/engine/ac/overlay.h
index 628242c5cc3..9a1f15b84fb 100644
--- a/engines/ags/engine/ac/overlay.h
+++ b/engines/ags/engine/ac/overlay.h
@@ -50,20 +50,19 @@ ScreenOverlay *Overlay_CreateGraphicCore(bool room_layer, int x, int y, int slot
 ScreenOverlay *Overlay_CreateTextCore(bool room_layer, int x, int y, int width, int font, int text_color,
 									  const char *text, int disp_type, int allow_shrink);
 
-ScreenOverlay &get_overlay(int index);
-ScreenOverlay *find_overlay_of_type(int type);
-int  find_overlay_index(int type);
-void remove_screen_overlay(int type);
+ScreenOverlay *get_overlay(int type);
 // Calculates overlay position in its respective layer (screen or room)
 Point get_overlay_position(const ScreenOverlay &over);
 size_t add_screen_overlay(bool roomlayer, int x, int y, int type, int sprnum);
 size_t add_screen_overlay(bool roomlayer, int x, int y, int type, Shared::Bitmap *piccy, bool has_alpha);
 size_t add_screen_overlay(bool roomlayer, int x, int y, int type, Shared::Bitmap *piccy, int pic_offx, int pic_offy, bool has_alpha);
-void remove_screen_overlay_index(size_t over_idx);
+void remove_screen_overlay(int type);
+void remove_all_overlays();
 // Creates and registers a managed script object for // Creates and registers a managed script object for existing overlay object;
 // optionally adds an internal engine reference to prevent object's disposal
 ScriptOverlay *create_scriptoverlay(ScreenOverlay &over, bool internal_ref = false);
-void recreate_overlay_ddbs();
+// Restores overlays, e.g. after restoring a game save
+void restore_overlays();
 
 std::vector<ScreenOverlay> &get_overlays();
 
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 0e8d1e87637..6b7c7439855 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -273,7 +273,7 @@ void unload_old_room() {
 	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
 	_GP(play).bg_frame = 0;
 	_GP(play).bg_frame_locked = 0;
-	remove_screen_overlay(-1);
+	remove_all_overlays();
 	delete _G(raw_saved_screen);
 	_G(raw_saved_screen) = nullptr;
 	for (int ff = 0; ff < MAX_ROOM_BGFRAMES; ff++)
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index 08639869abd..2cdcb84d871 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -109,15 +109,20 @@ const int LegacyRoomVolumeFactor = 30;
 #define MODE_CUSTOM1 8
 #define MODE_CUSTOM2 9
 
+// Fixed Overlay IDs
 #define OVER_TEXTMSG  1
 #define OVER_COMPLETE 2
 #define OVER_PICTURE  3
 #define OVER_TEXTSPEECH 4
-#define OVER_CUSTOM   100
+#define OVER_FIRSTFREE 5
+#define OVER_CUSTOM   -1
+// Overlay parameters
 #define OVR_AUTOPLACE 30000
+
 #define FOR_ANIMATION 1
 #define FOR_SCRIPT    2
 #define FOR_EXITLOOP  3
+
 // an actsps index offset for characters
 #define ACTSP_OBJSOFF (MAX_ROOM_OBJECTS)
 // a 1-based movelist index offset for characters
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index 01f12c06b98..d6a1aeb7157 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -58,7 +58,7 @@ enum OverlayFlags {
 struct ScreenOverlay {
 	// Texture
 	Engine::IDriverDependantBitmap *ddb = nullptr;
-	int type = 0, timeout = 0;
+	int type = -1, timeout = 0;
 	// Note that x,y are overlay's properties, that define its position in script;
 	// but real drawn position is x + offsetX, y + offsetY;
 	int x = 0, y = 0;
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index ebee94905e8..93e149e687a 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -336,7 +336,7 @@ void DoBeforeRestore(PreservedParams &pp) {
 	unload_old_room();
 	delete _G(raw_saved_screen);
 	_G(raw_saved_screen) = nullptr;
-	remove_screen_overlay(-1);
+	remove_all_overlays();
 	_GP(play).complete_overlay_on = 0;
 	_GP(play).text_overlay_on = 0;
 
@@ -621,7 +621,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	adjust_fonts_for_render_mode(_GP(game).options[OPT_ANTIALIASFONTS] != 0);
 
-	recreate_overlay_ddbs();
+	restore_overlays();
 
 	GUI::MarkAllGUIForUpdate(true, true);
 
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 0c19c8fbd60..55b0712bfc2 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -776,7 +776,7 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		over.ReadFromFile(in, has_bitmap, cmp_ver);
 		if (has_bitmap)
 			over.SetImage(read_serialized_bitmap(in), over.offsetX, over.offsetY);
-		if (over.scaleWidth <= 0 || over.scaleHeight <= 0) {
+		if (has_bitmap && (over.scaleWidth <= 0 || over.scaleHeight <= 0)) {
 			over.scaleWidth = over.GetImage()->GetWidth();
 			over.scaleHeight = over.GetImage()->GetHeight();
 		}
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 8ea04cf09aa..40dcf2f3ae1 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -225,16 +225,13 @@ void update_following_exactly_characters(const std::vector<int> &followingAsShee
 void update_overlay_timers() {
 	// update overlay timers
 	auto &overs = get_overlays();
-	for (size_t i = 0; i < overs.size();) {
-		auto &over = overs[i];
+	for (auto &over : overs) {
 		if (over.timeout > 0) {
 			over.timeout--;
 			if (over.timeout == 0) {
-				remove_screen_overlay_index(i);
-				continue;
+				remove_screen_overlay(over.type);
 			}
 		}
-		i++;
 	}
 }
 
@@ -390,8 +387,9 @@ void update_sierra_speech() {
 			int view_frame_x = 0;
 			int view_frame_y = 0;
 
-			auto &face_over = get_overlay(_G(face_talking));
-			Bitmap *frame_pic = face_over.GetImage();
+			auto *face_over = get_overlay(_G(face_talking));
+			assert(face_over != nullptr);
+			Bitmap *frame_pic = face_over->GetImage();
 			if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
 				// QFG4-style fullscreen dialog
 				if (_G(facetalk_qfg4_override_placement_x)) {
@@ -418,8 +416,8 @@ void update_sierra_speech() {
 				DrawViewFrame(frame_pic, blink_vf, view_frame_x, view_frame_y, face_has_alpha);
 			}
 
-			face_over.SetAlphaChannel(face_has_alpha);
-			face_over.MarkChanged();
+			face_over->SetAlphaChannel(face_has_alpha);
+			face_over->MarkChanged();
 		}  // end if updatedFrame
 	}
 }
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 14deebc9b57..27dc14c6fdb 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -333,7 +333,7 @@ Globals::Globals() {
 
 	// overlay.cpp globals
 	_screenover = new std::vector<ScreenOverlay>();
-	_overlookup = new std::vector<int>(128);
+	_over_free_ids = new std::queue<int32_t>();
 
 	// plugins globals
 	_engineExports = new Plugins::Core::EngineExports();
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 1b495d5b502..1eb0c149f58 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1181,7 +1181,7 @@ public:
 	 */
 
 	std::vector<ScreenOverlay> *_screenover;
-	std::vector<int> *_overlookup;
+	std::queue<int32_t> *_over_free_ids;
 	int _is_complete_overlay = 0;
 
 	/**@}*/


Commit: 9cd644115a3197011d6a921d2c8ae33fc9ac73cc
    https://github.com/scummvm/scummvm/commit/9cd644115a3197011d6a921d2c8ae33fc9ac73cc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed overlay read and write in a save: don't save empty slots

>From upstream 05638253550f7bd1089cfd3670078cd2157d9d92

Changed paths:
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp


diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 55b0712bfc2..7d60042b0b8 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -758,29 +758,44 @@ HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, const PreservedPa
 
 HSaveError WriteOverlays(Stream *out) {
 	const auto &overs = get_overlays();
-	out->WriteInt32(overs.size());
+	// Calculate and save valid overlays only
+	uint32_t valid_count = 0;
+	soff_t count_off = out->GetPosition();
+	out->WriteInt32(0);
 	for (const auto &over : overs) {
+		if (over.type < 0)
+			continue;
+		valid_count++;
 		over.WriteToFile(out);
 		if (!over.IsSpriteReference())
 			serialize_bitmap(over.GetImage(), out);
 	}
+	out->Seek(count_off, kSeekBegin);
+	out->WriteInt32(valid_count);
+	out->Seek(0, kSeekEnd);
 	return HSaveError::None();
 }
 
 HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+	// Remember that overlay indexes may be non-sequential
+	// the vector may be resized during read
 	size_t over_count = in->ReadInt32();
 	auto &overs = get_overlays();
+	overs.resize(over_count); // reserve minimal size
 	for (size_t i = 0; i < over_count; ++i) {
 		ScreenOverlay over;
 		bool has_bitmap;
 		over.ReadFromFile(in, has_bitmap, cmp_ver);
+		assert(over.type >= 0);
 		if (has_bitmap)
 			over.SetImage(read_serialized_bitmap(in), over.offsetX, over.offsetY);
 		if (has_bitmap && (over.scaleWidth <= 0 || over.scaleHeight <= 0)) {
 			over.scaleWidth = over.GetImage()->GetWidth();
 			over.scaleHeight = over.GetImage()->GetHeight();
 		}
-		overs.push_back(std::move(over));
+		if (overs.size() <= over.type)
+			overs.resize(over.type + 1);
+		overs[over.type] = std::move(over);
 	}
 	return HSaveError::None();
 }
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index cbc0ee45f51..d1bd395dc3f 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -319,22 +319,31 @@ static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
 static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size_t num_overs) {
 	AlignedStream align_s(in, Shared::kAligned_Read);
 	has_bitmap.resize(num_overs);
+	// Remember that overlay indexes may be non-sequential
 	auto &overs = get_overlays();
 	for (size_t i = 0; i < num_overs; ++i) {
 		bool has_bm;
-		overs[i].ReadFromFile(&align_s, has_bm, 0);
-		has_bitmap[i] = has_bm;
+		ScreenOverlay over;
+		over.ReadFromFile(&align_s, has_bm, 0);
+		if (overs.size() <= over.type) {
+			overs.resize(over.type + 1);
+			has_bitmap.resize(over.type + 1);
+		}
+		overs[over.type] = std::move(over);
+		has_bitmap[over.type] = has_bm;
 		align_s.Reset();
 	}
 }
 
 static void restore_game_overlays(Stream *in) {
 	size_t num_overs = in->ReadInt32();
+	// Remember that overlay indexes may be not sequential,
+	// the vector may be resized during read
 	auto &overs = get_overlays();
 	overs.resize(num_overs);
-	std::vector<bool> has_bitmap;
+	std::vector<bool> has_bitmap(num_overs);
 	ReadOverlays_Aligned(in, has_bitmap, num_overs);
-	for (size_t i = 0; i < num_overs; ++i) {
+	for (size_t i = 0; i < overs.size(); ++i) {
 		if (has_bitmap[i])
 			overs[i].SetImage(read_serialized_bitmap(in), overs[i].offsetX, overs[i].offsetY);
 	}


Commit: a010fddc550ce160358dda9a35a52f232b0c65f9
    https://github.com/scummvm/scummvm/commit/a010fddc550ce160358dda9a35a52f232b0c65f9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: safety checks for invalid id when reading overlays

>From upstream ed520227cea80bd2503451b61780b4e6be962cc3

Changed paths:
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp


diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 7d60042b0b8..295d9b15695 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -786,7 +786,8 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		ScreenOverlay over;
 		bool has_bitmap;
 		over.ReadFromFile(in, has_bitmap, cmp_ver);
-		assert(over.type >= 0);
+		if (over.type < 0)
+			continue; // safety abort
 		if (has_bitmap)
 			over.SetImage(read_serialized_bitmap(in), over.offsetX, over.offsetY);
 		if (has_bitmap && (over.scaleWidth <= 0 || over.scaleHeight <= 0)) {
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index d1bd395dc3f..787da1f1900 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -325,13 +325,15 @@ static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size
 		bool has_bm;
 		ScreenOverlay over;
 		over.ReadFromFile(&align_s, has_bm, 0);
+		align_s.Reset();
+		if (over.type < 0)
+			continue; // safety abort
 		if (overs.size() <= over.type) {
 			overs.resize(over.type + 1);
 			has_bitmap.resize(over.type + 1);
 		}
 		overs[over.type] = std::move(over);
 		has_bitmap[over.type] = has_bm;
-		align_s.Reset();
 	}
 }
 


Commit: aedaa4ce880da58e869d433e9aafbb3584fc3888
    https://github.com/scummvm/scummvm/commit/aedaa4ce880da58e869d433e9aafbb3584fc3888
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixes for overlay id comparisons

>From upstream 11f0448681ae1370b8b5d58f33efc528916a6112

Changed paths:
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp


diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index f034919cd4f..55260658274 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -349,8 +349,7 @@ static void dispose_overlay(ScreenOverlay &over) {
 }
 
 void remove_screen_overlay(int type) {
-	assert(type >= 0 && type < _GP(screenover).size());
-	if (type < 0 || type >= _GP(screenover).size() || _GP(screenover)[type].type < 0)
+	if (type < 0 || static_cast<uint32_t>(type) >= _GP(screenover).size() || _GP(screenover)[type].type < 0)
 		return; // something is wrong
 
 	ScreenOverlay &over = _GP(screenover)[type];
@@ -376,13 +375,12 @@ void remove_screen_overlay(int type) {
 }
 
 void remove_all_overlays() {
-	for (int i = 0; i < _GP(screenover).size(); ++i)
-		remove_screen_overlay(i);
+	for (auto &over : _GP(screenover))
+		remove_screen_overlay(over.type);
 }
 
-ScreenOverlay *get_overlay(int index) {
-	assert(index >= 0 && index < _GP(screenover).size());
-	return (index >= 0 && index < _GP(screenover).size()) ? &_GP(screenover)[index] : nullptr;
+ScreenOverlay *get_overlay(int type) {
+	return (type >= 0 && static_cast<uint32_t>(type) < _GP(screenover).size() && _GP(screenover)[type].type >= 0) ? &_GP(screenover)[type] : nullptr;
 }
 
 size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnum, Bitmap *piccy,
@@ -397,7 +395,7 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 		}
 	}
 
-	if (_GP(screenover).size() <= type)
+	if (_GP(screenover).size() <= static_cast<uint32_t>(type))
 		_GP(screenover).resize(type + 1);
 	ScreenOverlay over;
 	if (piccy) {
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 295d9b15695..e649ea85153 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -794,7 +794,7 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 			over.scaleWidth = over.GetImage()->GetWidth();
 			over.scaleHeight = over.GetImage()->GetHeight();
 		}
-		if (overs.size() <= over.type)
+		if (overs.size() <= static_cast<uint32_t>(over.type))
 			overs.resize(over.type + 1);
 		overs[over.type] = std::move(over);
 	}
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 787da1f1900..d0af068bfb3 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -328,7 +328,7 @@ static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size
 		align_s.Reset();
 		if (over.type < 0)
 			continue; // safety abort
-		if (overs.size() <= over.type) {
+		if (overs.size() <= static_cast<uint32_t>(over.type)) {
 			overs.resize(over.type + 1);
 			has_bitmap.resize(over.type + 1);
 		}


Commit: 0bb33fcfc56d67a2304b304096365baf5721a9e9
    https://github.com/scummvm/scummvm/commit/0bb33fcfc56d67a2304b304096365baf5721a9e9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: made sure game res inited in InitGameState()

This is more logical than calling this from "engine_setup",
 and also fixes running another AGS game from current game after commit 5596736 .
>From upstream a82d8147d4424b0aa83762d810db96cff31d7af9

Changed paths:
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/engine_setup.cpp
    engines/ags/engine/main/engine_setup.h


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 79a6cb00a91..21dc88fcd03 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -46,6 +46,7 @@
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gfx/ddb.h"
 #include "ags/shared/gui/gui_label.h"
+#include "ags/shared/gui/gui_inv.h"
 #include "ags/engine/media/audio/audio_system.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/plugins/plugin_engine.h"
@@ -308,6 +309,95 @@ void LoadLipsyncData() {
 	Debug::Printf(kDbgMsg_Info, "Lipsync data found and loaded");
 }
 
+// Convert guis position and size to proper game resolution.
+// Necessary for pre 3.1.0 games only to sync with modern engine.
+static void ConvertGuiToGameRes(GameSetupStruct &game, GameDataVersion data_ver) {
+	if (data_ver >= kGameVersion_310)
+		return;
+
+	const int mul = game.GetDataUpscaleMult();
+	for (int i = 0; i < game.numcursors; ++i) {
+		game.mcurs[i].hotx *= mul;
+		game.mcurs[i].hoty *= mul;
+	}
+
+	for (int i = 0; i < game.numinvitems; ++i) {
+		game.invinfo[i].hotx *= mul;
+		game.invinfo[i].hoty *= mul;
+	}
+
+	for (int i = 0; i < game.numgui; ++i) {
+		GUIMain *cgp = &_GP(guis)[i];
+		cgp->X *= mul;
+		cgp->Y *= mul;
+		if (cgp->Width < 1)
+			cgp->Width = 1;
+		if (cgp->Height < 1)
+			cgp->Height = 1;
+		// This is probably a way to fix GUIs meant to be covering whole screen
+		if (cgp->Width == game.GetDataRes().Width - 1)
+			cgp->Width = game.GetDataRes().Width;
+
+		cgp->Width *= mul;
+		cgp->Height *= mul;
+
+		cgp->PopupAtMouseY *= mul;
+
+		for (int j = 0; j < cgp->GetControlCount(); ++j) {
+			GUIObject *guio = cgp->GetControl(j);
+			guio->X *= mul;
+			guio->Y *= mul;
+			guio->Width *= mul;
+			guio->Height *= mul;
+			guio->IsActivated = false;
+			guio->OnResized();
+		}
+	}
+}
+
+// Convert certain coordinates to data resolution (only if it's different from game resolution).
+// Necessary for 3.1.0 and above games with legacy "low-res coordinates" setting.
+static void ConvertObjectsToDataRes(GameSetupStruct &game, GameDataVersion data_ver) {
+	if (data_ver < kGameVersion_310 || game.GetDataUpscaleMult() == 1)
+		return;
+
+	const int mul = game.GetDataUpscaleMult();
+	for (int i = 0; i < game.numcharacters; ++i) {
+		game.chars[i].x /= mul;
+		game.chars[i].y /= mul;
+	}
+
+	for (auto &inv : _GP(guiinv)) {
+		inv.ItemWidth /= mul;
+		inv.ItemHeight /= mul;
+		inv.OnResized();
+	}
+}
+
+void InitGameResolution(GameSetupStruct &game, GameDataVersion data_ver) {
+	Debug::Printf("Initializing resolution settings");
+	const Size game_size = game.GetGameRes();
+	_GP(usetup).textheight = get_font_height_outlined(0) + 1;
+
+	Debug::Printf(kDbgMsg_Info, "Game native resolution: %d x %d (%d bit)%s", game_size.Width, game_size.Height, game.color_depth * 8,
+				  game.IsLegacyLetterbox() ? " letterbox-by-design" : "");
+
+	// Backwards compatible resolution conversions
+	ConvertGuiToGameRes(game, data_ver);
+	ConvertObjectsToDataRes(game, data_ver);
+
+	// Assign general game viewports
+	Rect viewport = RectWH(game_size);
+	_GP(play).SetMainViewport(viewport);
+	_GP(play).SetUIViewport(viewport);
+
+	// Assign ScriptSystem's resolution variables
+	_GP(scsystem).width = game.GetGameRes().Width;
+	_GP(scsystem).height = game.GetGameRes().Height;
+	_GP(scsystem).viewport_width = game_to_data_coord(_GP(play).GetMainViewport().GetWidth());
+	_GP(scsystem).viewport_height = game_to_data_coord(_GP(play).GetMainViewport().GetHeight());
+}
+
 void AllocScriptModules() {
 	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
 	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
@@ -400,6 +490,7 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 	//
 	// 5. Initialize runtime state of certain game objects
 	//
+	InitGameResolution(game, data_ver);
 	for (auto &label : _GP(guilabels)) {
 		// labels are not clickable by default
 		label.SetClickable(false);
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 9e75d0fa5b8..198ea32f1e1 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -1151,8 +1151,6 @@ int initialize_engine(const ConfigTree &startup_opts) {
 
 	_G(our_eip) = -179;
 
-	engine_init_resolution_settings(_GP(game).GetGameRes());
-
 	engine_adjust_for_rotation_settings();
 
 	// Attempt to initialize graphics mode
diff --git a/engines/ags/engine/main/engine_setup.cpp b/engines/ags/engine/main/engine_setup.cpp
index 0e44ac564d9..46cfa055932 100644
--- a/engines/ags/engine/main/engine_setup.cpp
+++ b/engines/ags/engine/main/engine_setup.cpp
@@ -23,9 +23,7 @@
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/display.h"
 #include "ags/engine/ac/draw.h"
-#include "ags/shared/ac/game_version.h"
 #include "ags/engine/ac/game_setup.h"
-#include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_game.h"
 #include "ags/engine/ac/mouse.h"
@@ -51,94 +49,6 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-// Convert guis position and size to proper game resolution.
-// Necessary for pre 3.1.0 games only to sync with modern engine.
-void convert_gui_to_game_resolution(GameDataVersion filever) {
-	if (filever >= kGameVersion_310)
-		return;
-
-	const int mul = _GP(game).GetDataUpscaleMult();
-	for (int i = 0; i < _GP(game).numcursors; ++i) {
-		_GP(game).mcurs[i].hotx *= mul;
-		_GP(game).mcurs[i].hoty *= mul;
-	}
-
-	for (int i = 0; i < _GP(game).numinvitems; ++i) {
-		_GP(game).invinfo[i].hotx *= mul;
-		_GP(game).invinfo[i].hoty *= mul;
-	}
-
-	for (int i = 0; i < _GP(game).numgui; ++i) {
-		GUIMain *cgp = &_GP(guis)[i];
-		cgp->X *= mul;
-		cgp->Y *= mul;
-		if (cgp->Width < 1)
-			cgp->Width = 1;
-		if (cgp->Height < 1)
-			cgp->Height = 1;
-		// This is probably a way to fix GUIs meant to be covering whole screen
-		if (cgp->Width == _GP(game).GetDataRes().Width - 1)
-			cgp->Width = _GP(game).GetDataRes().Width;
-
-		cgp->Width *= mul;
-		cgp->Height *= mul;
-
-		cgp->PopupAtMouseY *= mul;
-
-		for (int j = 0; j < cgp->GetControlCount(); ++j) {
-			GUIObject *guio = cgp->GetControl(j);
-			guio->X *= mul;
-			guio->Y *= mul;
-			guio->Width *= mul;
-			guio->Height *= mul;
-			guio->IsActivated = false;
-			guio->OnResized();
-		}
-	}
-}
-
-// Convert certain coordinates to data resolution (only if it's different from game resolution).
-// Necessary for 3.1.0 and above games with legacy "low-res coordinates" setting.
-void convert_objects_to_data_resolution(GameDataVersion filever) {
-	if (filever < kGameVersion_310 || _GP(game).GetDataUpscaleMult() == 1)
-		return;
-
-	const int mul = _GP(game).GetDataUpscaleMult();
-	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].x /= mul;
-		_GP(game).chars[i].y /= mul;
-	}
-
-	for (auto &inv : _GP(guiinv)) {
-		inv.ItemWidth /= mul;
-		inv.ItemHeight /= mul;
-		inv.OnResized();
-	}
-}
-
-void engine_setup_system_gamesize() {
-	_GP(scsystem).width = _GP(game).GetGameRes().Width;
-	_GP(scsystem).height = _GP(game).GetGameRes().Height;
-	_GP(scsystem).coldepth = _GP(game).GetColorDepth();
-	_GP(scsystem).viewport_width = game_to_data_coord(_GP(play).GetMainViewport().GetWidth());
-	_GP(scsystem).viewport_height = game_to_data_coord(_GP(play).GetMainViewport().GetHeight());
-}
-
-void engine_init_resolution_settings(const Size game_size) {
-	Debug::Printf("Initializing resolution settings");
-	_GP(usetup).textheight = get_font_height_outlined(0) + 1;
-
-	Debug::Printf(kDbgMsg_Info, "Game native resolution: %d x %d (%d bit)%s", game_size.Width, game_size.Height, _GP(game).color_depth * 8,
-	              _GP(game).IsLegacyLetterbox() ? " letterbox-by-design" : "");
-
-	convert_gui_to_game_resolution(_G(loaded_game_file_version));
-	convert_objects_to_data_resolution(_G(loaded_game_file_version));
-
-	Rect viewport = RectWH(game_size);
-	_GP(play).SetMainViewport(viewport);
-	_GP(play).SetUIViewport(viewport);
-	engine_setup_system_gamesize();
-}
 
 void engine_adjust_for_rotation_settings() {
 #if 0
diff --git a/engines/ags/engine/main/engine_setup.h b/engines/ags/engine/main/engine_setup.h
index a723cd41882..0310e9177b3 100644
--- a/engines/ags/engine/main/engine_setup.h
+++ b/engines/ags/engine/main/engine_setup.h
@@ -27,9 +27,6 @@
 
 namespace AGS3 {
 
-// Sets up game viewport and object scaling parameters depending on _GP(game).
-// TODO: this is part of the game init, not engine init, move it later
-void engine_init_resolution_settings(const Size game_size);
 // Setup engine after the graphics mode has changed
 void engine_post_gfxmode_setup(const Size &init_desktop, const DisplayMode &old_dm);
 // Prepare engine for graphics mode release; could be called before switching display mode too


Commit: 7ba6cf903c746b868fa9097692d879aba3bdafd3
    https://github.com/scummvm/scummvm/commit/7ba6cf903c746b868fa9097692d879aba3bdafd3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Removed redundant code from engine init

>From upstream f0268dbd9bc40521132bd8465af06581abe6a27

Changed paths:
    engines/ags/engine/main/config.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/game_file.cpp


diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp
index 311d590c5e0..16c8166bf08 100644
--- a/engines/ags/engine/main/config.cpp
+++ b/engines/ags/engine/main/config.cpp
@@ -24,7 +24,6 @@
 //
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
-#include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_translation.h"
 #include "ags/engine/ac/path_helper.h"
 #include "ags/shared/ac/sprite_cache.h"
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 198ea32f1e1..b6cc6f41994 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -318,12 +318,6 @@ void engine_init_keyboard() {
 	/* do nothing */
 }
 
-void engine_init_timer() {
-	Debug::Printf(kDbgMsg_Info, "Install timer");
-
-	skipMissedTicks();
-}
-
 void engine_init_audio() {
 #if !AGS_PLATFORM_SCUMMVM
 	if (usetup.audio_backend != 0) {
@@ -1109,10 +1103,6 @@ int initialize_engine(const ConfigTree &startup_opts) {
 
 	engine_init_mouse();
 
-	_G(our_eip) = -197;
-
-	engine_init_timer();
-
 	_G(our_eip) = -198;
 
 	engine_init_audio();
diff --git a/engines/ags/engine/main/game_file.cpp b/engines/ags/engine/main/game_file.cpp
index 7e3c1271ec3..704ed40596e 100644
--- a/engines/ags/engine/main/game_file.cpp
+++ b/engines/ags/engine/main/game_file.cpp
@@ -30,7 +30,6 @@
 #include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
-#include "ags/engine/ac/game_state.h"
 #include "ags/shared/ac/game_struct_defines.h"
 #include "ags/engine/ac/gui.h"
 #include "ags/engine/ac/view_frame.h"


Commit: 178826a9d844d2251ee851c43af4d56e0622418b
    https://github.com/scummvm/scummvm/commit/178826a9d844d2251ee851c43af4d56e0622418b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: init few more things along the GameState struct

This fixes more variables not initialized after 5596736 .
>From upstream 99db1355bf9eb00f03ff2a10d01d9f1b80363945

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/speech.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/engine.h
    engines/ags/engine/main/game_start.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 00f5b7376ce..50f7d892219 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -681,10 +681,11 @@ const char *Game_GetSpeechVoxFilename() {
 }
 
 bool Game_ChangeSpeechVox(const char *newFilename) {
-	if (!init_voicepak(newFilename)) {
+	_GP(play).voice_avail = init_voicepak(newFilename);
+	if (!_GP(play).voice_avail) {
 		// if failed (and was not default)- fallback to default
 		if (strlen(newFilename) > 0)
-			init_voicepak();
+			_GP(play).voice_avail = init_voicepak();
 		return false;
 	}
 	return true;
diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp
index 11350380143..6d4bc195e51 100644
--- a/engines/ags/engine/ac/speech.cpp
+++ b/engines/ags/engine/ac/speech.cpp
@@ -101,7 +101,7 @@ bool init_voicepak(const String &name) {
 		return true; // same pak already assigned
 
 	// First remove existing voice packs
-	_GP(play).voice_avail = false;
+	_GP(ResPaths).VoiceAvail = false;
 	// FIXME: don't remove the default speech.vox when changing pak, as this causes a crash in Beyond the Edge of Owlsgard
 	// Duplicate checks are already present so this shouldn't cause problems but still, it should be looked into
 	if (_GP(ResPaths).SpeechPak.Name.CompareNoCase("speech.vox") != 0)
@@ -112,7 +112,7 @@ bool init_voicepak(const String &name) {
 	String speech_filepath = find_assetlib(speech_file);
 	if (!speech_filepath.IsEmpty()) {
 		Debug::Printf(kDbgMsg_Info, "Voice pack found: %s", speech_file.GetCStr());
-		_GP(play).voice_avail = true;
+		_GP(ResPaths).VoiceAvail = true;
 	} else {
 		Debug::Printf(kDbgMsg_Info, "Was not able to init voice pack '%s': file not found or of unknown format.",
 			speech_file.GetCStr());
@@ -125,7 +125,7 @@ bool init_voicepak(const String &name) {
 		speech_subdir = name.IsEmpty() ? _GP(ResPaths).VoiceDir2 : Path::ConcatPaths(_GP(ResPaths).VoiceDir2, name);
 		if (File::IsDirectory(speech_subdir) && !FindFile::OpenFiles(speech_subdir).AtEnd()) {
 			Debug::Printf(kDbgMsg_Info, "Optional voice directory is defined: %s", speech_subdir.GetCStr());
-			_GP(play).voice_avail = true;
+			_GP(ResPaths).VoiceAvail = true;
 		}
 	}
 
@@ -137,7 +137,7 @@ bool init_voicepak(const String &name) {
 	_GP(ResPaths).VoiceDirSub = speech_subdir;
 	_GP(AssetMgr)->AddLibrary(_GP(ResPaths).VoiceDirSub, "voice");
 	_GP(AssetMgr)->AddLibrary(_GP(ResPaths).SpeechPak.Path, "voice");
-	return _GP(play).voice_avail;
+	return _GP(ResPaths).VoiceAvail;
 }
 
 String get_voicepak_name() {
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index b6cc6f41994..2fa4994ad34 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -271,13 +271,11 @@ void engine_locate_speech_pak() {
 }
 
 void engine_locate_audio_pak() {
-	_GP(play).separate_music_lib = false;
 	String music_file = _GP(game).GetAudioVOXName();
 	String music_filepath = find_assetlib(music_file);
 	if (!music_filepath.IsEmpty()) {
 		if (_GP(AssetMgr)->AddLibrary(music_filepath) == kAssetNoError) {
 			Debug::Printf(kDbgMsg_Info, "%s found and initialized.", music_file.GetCStr());
-			_GP(play).separate_music_lib = true;
 			_GP(ResPaths).AudioPak.Name = music_file;
 			_GP(ResPaths).AudioPak.Path = music_filepath;
 		} else {
@@ -333,8 +331,6 @@ void engine_init_audio() {
 
 	if (!_GP(usetup).audio_enabled) {
 		// all audio is disabled
-		_GP(play).voice_avail = false;
-		_GP(play).separate_music_lib = false;
 		Debug::Printf(kDbgMsg_Info, "Audio is disabled");
 	}
 }
@@ -352,11 +348,6 @@ void engine_init_debug() {
 	}
 }
 
-void engine_init_rand() {
-	_GP(play).randseed = g_system->getMillis();
-	::AGS::g_vm->setRandomNumberSeed(_GP(play).randseed);
-}
-
 void engine_init_pathfinder() {
 	init_pathfinder(_G(loaded_game_file_version));
 }
@@ -526,6 +517,18 @@ void engine_init_game_settings() {
 	_G(our_eip) = -7;
 	Debug::Printf("Initialize game settings");
 
+	// Initialize randomizer
+	_GP(play).randseed = g_system->getMillis();
+	::AGS::g_vm->setRandomNumberSeed(_GP(play).randseed);
+
+	if (_GP(usetup).audio_enabled) {
+		_GP(play).separate_music_lib = !_GP(ResPaths).AudioPak.Name.IsEmpty();
+		_GP(play).voice_avail = _GP(ResPaths).VoiceAvail;
+	} else {
+		_GP(play).voice_avail = false;
+		_GP(play).separate_music_lib = false;
+	}
+
 	// Setup a text encoding mode depending on the game data hint
 	if (_GP(game).options[OPT_GAMETEXTENCODING] == 65001) // utf-8 codepage number
 		set_uformat(U_UTF8);
@@ -633,6 +636,7 @@ void engine_init_game_settings() {
 	_GP(play).bg_frame_locked = 0;
 	_GP(play).bg_anim_delay = 0;
 	_GP(play).anim_background_speed = 0;
+	_GP(play).mouse_cursor_hidden = 0;
 	_GP(play).silent_midi = 0;
 	_GP(play).current_music_repeating = 0;
 	_GP(play).skip_until_char_stops = -1;
@@ -711,6 +715,7 @@ void engine_init_game_settings() {
 	_GP(play).show_single_dialog_option = 0;
 	_GP(play).keep_screen_during_instant_transition = 0;
 	_GP(play).read_dialog_option_colour = -1;
+	_GP(play).stop_dialog_at_end = DIALOG_NONE;
 	_GP(play).speech_portrait_placement = 0;
 	_GP(play).speech_portrait_x = 0;
 	_GP(play).speech_portrait_y = 0;
@@ -1113,8 +1118,6 @@ int initialize_engine(const ConfigTree &startup_opts) {
 
 	_G(our_eip) = -10;
 
-	engine_init_rand();
-
 	engine_init_pathfinder();
 
 	set_game_speed(40);
diff --git a/engines/ags/engine/main/engine.h b/engines/ags/engine/main/engine.h
index 08f79e61214..9cec1e3fd3d 100644
--- a/engines/ags/engine/main/engine.h
+++ b/engines/ags/engine/main/engine.h
@@ -61,6 +61,7 @@ struct ResourcePaths {
 	PackLocation AudioPak;   // audio package
 	PackLocation SpeechPak;  // voice-over package
 	String       DataDir;    // path to the data directory
+	bool		 VoiceAvail = false; // tells whether voice files available in either location
 	// NOTE: optional directories are currently only for compatibility with Editor (game test runs)
 	// This is bit ugly, but remain so until more flexible configuration is designed
 	String       DataDir2;   // optional data directory
diff --git a/engines/ags/engine/main/game_start.cpp b/engines/ags/engine/main/game_start.cpp
index 4ed18111ed9..4f837f877df 100644
--- a/engines/ags/engine/main/game_start.cpp
+++ b/engines/ags/engine/main/game_start.cpp
@@ -110,7 +110,6 @@ void initialize_start_and_play_game(int override_start_room, int loadSave) {
 
 	set_cursor_mode(MODE_WALK);
 
-	::AGS::g_vm->setRandomNumberSeed(_GP(play).randseed);
 	if (override_start_room)
 		_G(playerchar)->room = override_start_room;
 


Commit: 5c5b7fb026a25398a921224f7dc72c6be50edfcb
    https://github.com/scummvm/scummvm/commit/5c5b7fb026a25398a921224f7dc72c6be50edfcb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: force std message box for debug info

This is necessary, because game settings may include
"display all messages as speech".
>From upstream 3c9e072ff1fa246524e39c0063a2c6dee1209304

Changed paths:
    engines/ags/engine/ac/global_debug.cpp
    engines/ags/engine/ac/global_display.cpp
    engines/ags/engine/ac/global_display.h
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/ac/global_debug.cpp b/engines/ags/engine/ac/global_debug.cpp
index 66e70f01996..fa079007476 100644
--- a/engines/ags/engine/ac/global_debug.cpp
+++ b/engines/ags/engine/ac/global_debug.cpp
@@ -96,7 +96,7 @@ void script_debug(int cmdd, int dataa) {
 		//    Display("invorder decided there are %d items[display %d",_GP(play).inv_numorder,_GP(play).inv_numdisp);
 	} else if (cmdd == 1) {
 		String toDisplay = GetRuntimeInfo();
-		Display(toDisplay.GetCStr());
+		DisplayMB(toDisplay.GetCStr());
 		//    Display("shftR: %d  shftG: %d  shftB: %d", _G(_rgb_r_shift_16), _G(_rgb_g_shift_16), _G(_rgb_b_shift_16));
 		//    Display("Remaining memory: %d kb",_go32_dpmi_remaining_virtual_memory()/1024);
 		//Display("Play char bcd: %d",->GetColorDepth(_GP(spriteset)[_GP(views)[_G(playerchar)->view].frames[_G(playerchar)->loop][_G(playerchar)->frame].pic]));
diff --git a/engines/ags/engine/ac/global_display.cpp b/engines/ags/engine/ac/global_display.cpp
index 1b3fe53a818..c02b0a97723 100644
--- a/engines/ags/engine/ac/global_display.cpp
+++ b/engines/ags/engine/ac/global_display.cpp
@@ -43,6 +43,8 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+void DisplayAtYImpl(int ypos, const char *texx, bool as_speech);
+
 void Display(const char *texx, ...) {
 	char displbuf[STD_BUFFER_SIZE];
 	va_list ap;
@@ -56,6 +58,10 @@ void DisplaySimple(const char *text) {
 	DisplayAtY(-1, text);
 }
 
+void DisplayMB(const char *text) {
+	DisplayAtYImpl(-1, text, false);
+}
+
 void DisplayTopBar(int ypos, int ttexcol, int backcol, const char *title, const char *text) {
 	// FIXME: refactor source_text_length and get rid of this ugly hack!
 	const int real_text_sourcelen = _G(source_text_length);
@@ -152,7 +158,7 @@ void DisplayAt(int xxp, int yyp, int widd, const char *text) {
 	_display_at(xxp, yyp, widd, text, DISPLAYTEXT_MESSAGEBOX, 0, 0, 0, false);
 }
 
-void DisplayAtY(int ypos, const char *texx) {
+void DisplayAtYImpl(int ypos, const char *texx, bool as_speech) {
 	const Rect &ui_view = _GP(play).GetUIViewport();
 	if ((ypos < -1) || (ypos >= ui_view.GetHeight()))
 		quitprintf("!DisplayAtY: invalid Y co-ordinate supplied (used: %d; valid: 0..%d)", ypos, ui_view.GetHeight());
@@ -166,7 +172,7 @@ void DisplayAtY(int ypos, const char *texx) {
 	if (ypos > 0)
 		ypos = data_to_game_coord(ypos);
 
-	if (_GP(game).options[OPT_ALWAYSSPCH])
+	if (as_speech)
 		DisplaySpeechAt(-1, (ypos > 0) ? game_to_data_coord(ypos) : ypos, -1, _GP(game).playercharacter, texx);
 	else {
 		// Normal "Display" in text box
@@ -183,6 +189,10 @@ void DisplayAtY(int ypos, const char *texx) {
 	}
 }
 
+void DisplayAtY(int ypos, const char *texx) {
+	DisplayAtYImpl(ypos, texx, _GP(game).options[OPT_ALWAYSSPCH] != 0);
+}
+
 void SetSpeechStyle(int newstyle) {
 	if ((newstyle < 0) || (newstyle > 3))
 		quit("!SetSpeechStyle: must use a SPEECH_* constant as parameter");
diff --git a/engines/ags/engine/ac/global_display.h b/engines/ags/engine/ac/global_display.h
index 109c2da4c50..ff98330657f 100644
--- a/engines/ags/engine/ac/global_display.h
+++ b/engines/ags/engine/ac/global_display.h
@@ -28,6 +28,7 @@ namespace AGS3 {
 
 void Display(const char *texx, ...); // applies translation
 void DisplaySimple(const char *text); // does not apply translation
+void DisplayMB(const char *text); // forces standard Display message box
 void DisplayAt(int xxp, int yyp, int widd, const char *text);
 void DisplayAtY(int ypos, const char *texx);
 void DisplayMessage(int msnum);
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index e7a71dd6ec9..f8ed675539c 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -367,14 +367,14 @@ bool run_service_key_controls(KeyInput &out_key) {
 			        _G(objs)[ff].num, _G(objs)[ff].transparent,
 			        ((_G(objs)[ff].flags & OBJF_NOINTERACT) != 0) ? 0 : 1);
 		}
-		Display(infobuf);
+		DisplayMB(infobuf);
 		int chd = _GP(game).playercharacter;
 		char bigbuffer[STD_BUFFER_SIZE] = "CHARACTERS IN THIS ROOM:[";
 		for (int ff = 0; ff < _GP(game).numcharacters; ff++) {
 			if (_GP(game).chars[ff].room != _G(displayed_room)) continue;
 			if (strlen(bigbuffer) > 430) {
 				Common::strcat_s(bigbuffer, "and more...");
-				Display(bigbuffer);
+				DisplayMB(bigbuffer);
 				Common::strcpy_s(bigbuffer, "CHARACTERS IN THIS ROOM (cont'd):[");
 			}
 			chd = ff;
@@ -387,7 +387,7 @@ bool run_service_key_controls(KeyInput &out_key) {
 			        _GP(game).chars[chd].walking, _GP(game).chars[chd].animating, _GP(game).chars[chd].following,
 			        _GP(game).chars[chd].flags, _GP(game).chars[chd].wait, _GP(charextra)[chd].zoom);
 		}
-		Display(bigbuffer);
+		DisplayMB(bigbuffer);
 		return false;
 	}
 


Commit: 72a89a8bab28d23add9d3f400dfd04310041cc09
    https://github.com/scummvm/scummvm/commit/72a89a8bab28d23add9d3f400dfd04310041cc09
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: proper GameState initialization

This should have been done at 5596736.
>From upstream 2ba13acde4b686d73148bcf094f60e425f589cfe

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 2a4973fb20a..757c9fb26c2 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -46,25 +46,6 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 GameState::GameState() {
-	Common::fill(&globalvars[0], &globalvars[MAXGLOBALVARS], 0);
-	Common::fill(&reserved[0], &reserved[GAME_STATE_RESERVED_INTS], 0);
-	Common::fill(&globalscriptvars[0], &globalscriptvars[MAXGSVALUES], 0);
-	Common::fill(&walkable_areas_on[0], &walkable_areas_on[MAX_WALK_AREAS + 1], 0);
-	Common::fill(&script_timers[0], &script_timers[MAX_TIMERS], 0);
-	Common::fill(&parsed_words[0], &parsed_words[MAX_PARSED_WORDS], 0);
-	Common::fill(&bad_parsed_word[0], &bad_parsed_word[100], 0);
-	Common::fill(&raw_modified[0], &raw_modified[MAX_ROOM_BGFRAMES], 0);
-	Common::fill(&filenumbers[0], &filenumbers[MAXSAVEGAMES], 0);
-	Common::fill(&music_queue[0], &music_queue[MAX_QUEUED_MUSIC], 0);
-	Common::fill(&takeover_from[0], &takeover_from[50], 0);
-	Common::fill(&playmp3file_name[0], &playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN], 0);
-	Common::fill(&globalstrings[0][0], &globalstrings[MAXGLOBALSTRINGS - 1][MAX_MAXSTRLEN], 0);
-	Common::fill(&lastParserEntry[0], &lastParserEntry[MAX_MAXSTRLEN], 0);
-	Common::fill(&game_name[0], &game_name[100], 0);
-	Common::fill(&default_audio_type_volumes[0], &default_audio_type_volumes[MAX_AUDIO_TYPES], 0);
-
-	_isAutoRoomViewport = true;
-	_mainViewportHasChanged = false;
 }
 
 bool GameState::IsAutoRoomViewport() const {
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index a9ab54a68f1..a70f23fd9dc 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -71,21 +71,27 @@ enum GameStateSvgVersion {
 };
 
 
-// Adding to this might need to modify AGSDEFNS.SH and AGSPLUGIN.H
+// Runtime game state
 struct GameState {
+	// WARNING: following is a part of the script and plugin API
+	// (until further notice)
 	int  score = 0;      // player's current score
 	int  usedmode = 0;   // set by ProcessClick to last cursor mode used
 	int  disabled_user_interface = 0;  // >0 while in cutscene/etc
 	int  gscript_timer = 0;    // obsolete
 	int  debug_mode = 0;       // whether we're in debug mode
-	int32_t globalvars[MAXGLOBALVARS];  // obsolete
+	int32_t globalvars[MAXGLOBALVARS]{};  // obsolete
 	int  messagetime = 0;      // time left for auto-remove messages
 	int  usedinv = 0;          // inventory item last used
-	int  inv_top = 0, inv_numdisp = 0, obsolete_inv_numorder = 0, inv_numinline = 0;
+	int  inv_top = 0;
+	int  inv_numdisp = 0;
+	int  obsolete_inv_numorder = 0;
+	int  inv_numinline = 0;
 	int  text_speed = 0;       // how quickly text is removed
 	int  sierra_inv_color = 0; // background used to paint defualt inv window
 	int  talkanim_speed = 0;   // animation speed of talking anims
-	int  inv_item_wid = 0, inv_item_hit = 0;  // set by SetInvDimensions
+	int  inv_item_wid = 0;
+	int  inv_item_hit = 0;  // set by SetInvDimensions
 	int  speech_text_shadow = 0;         // colour of outline fonts (default black)
 	int  swap_portrait_side = 0;         // sierra-style speech swap sides
 	int  speech_textwindow_gui = 0;      // textwindow used for sierra-style speech
@@ -107,7 +113,8 @@ struct GameState {
 	int  fast_forward = 0;           // player has elected to skip cutscene
 	int  room_width = 0;      // width of current room
 	int  room_height = 0;     // height of current room
-	// ** up to here is referenced in the plugin interface
+	// ** end of the part exposed to plugin API (script API continues)
+
 	int  game_speed_modifier = 0;
 	int  score_sound = 0;
 	int  takeover_data = 0;  // value passed to RunAGSGame in previous game
@@ -121,7 +128,7 @@ struct GameState {
 	// (this is designed to work in text-only mode)
 	int  disable_antialiasing = 0;
 	int  text_speed_modifier = 0;
-	HorAlignment text_align = (HorAlignment)0;
+	HorAlignment text_align = kHAlignNone;
 	int  speech_bubble_width = 0;
 	int  min_dialogoption_width = 0;
 	int  disable_dialog_parser = 0;
@@ -134,7 +141,7 @@ struct GameState {
 	int  screenshot_width = 0;
 	int  screenshot_height = 0;
 	int  top_bar_font = 0;
-	HorAlignment speech_text_align = (HorAlignment)0;
+	HorAlignment speech_text_align = kHAlignNone;
 	int  auto_use_walkto_points = 0;
 	int  inventory_greys_out = 0;
 	int  skip_speech_specific_key = 0;
@@ -152,8 +159,10 @@ struct GameState {
 	int  speech_display_post_time_ms = 0; // keep speech text/portrait on screen after text/voice has finished playing = 0;
 	// no speech animation is supposed to be played at this time
 	int  dialog_options_highlight_color = 0; // The colour used for highlighted (hovered over) text in dialog options
-	int32_t reserved[GAME_STATE_RESERVED_INTS];  // make sure if a future version adds a var, it doesn't mess anything up
-	// ** up to here is referenced in the script "game." object
+	int32_t reserved[GAME_STATE_RESERVED_INTS]{};  // make sure if a future version adds a var, it doesn't mess anything up
+	// ** end of the part exposed to script API
+	//
+
 	long  randseed = 0;    // random seed
 	int   player_on_region = 0;    // player's current region
 	int   screen_is_faded_out = 0; // the screen is currently black
@@ -163,22 +172,30 @@ struct GameState {
 	short wait_counter = 0;
 	int8  wait_skipped_by = 0; // tells how last blocking wait was skipped [not serialized]
 	int   wait_skipped_by_data = 0; // extended data telling how last blocking wait was skipped [not serialized]
-	short mboundx1 = 0, mboundx2 = 0, mboundy1 = 0, mboundy2 = 0;
+	short mboundx1 = 0;
+	short mboundx2 = 0;
+	short mboundy1 = 0;
+	short mboundy2 = 0;
 	int   fade_effect = 0;
 	int   bg_frame_locked = 0;
-	int32_t globalscriptvars[MAXGSVALUES];
-	int   cur_music_number = 0, music_repeat = 0;
+	int32_t globalscriptvars[MAXGSVALUES]{};
+	int   cur_music_number = 0;
+	int   music_repeat = 0;
 	int   music_master_volume = 0;
 	int   digital_master_volume = 0;
-	char  walkable_areas_on[MAX_WALK_AREAS + 1];
+	char  walkable_areas_on[MAX_WALK_AREAS + 1]{};
 	short screen_flipped = 0;
-	int   entered_at_x = 0, entered_at_y = 0, entered_edge = 0;
+	int   entered_at_x = 0;
+	int   entered_at_y = 0;
+	int   entered_edge = 0;
 	bool  voice_avail; // whether voice-over is available
 	SpeechMode speech_mode; // speech mode (text, voice, or both)
 	int   speech_skip_style = 0;
-	int32_t   script_timers[MAX_TIMERS];
-	int   sound_volume = 0, speech_volume = 0;
-	int   normal_font = 0, speech_font = 0;
+	int32_t   script_timers[MAX_TIMERS]{};
+	int   sound_volume = 0;
+	int   speech_volume = 0;
+	int   normal_font = 0;
+	int   speech_font = 0;
 	int8  key_skip_wait = 0;
 	int   swap_portrait_lastchar = 0;
 	int   swap_portrait_lastlastchar = 0;
@@ -186,22 +203,26 @@ struct GameState {
 	int   in_conversation = 0;
 	int   screen_tint = 0;
 	int   num_parsed_words = 0;
-	short parsed_words[MAX_PARSED_WORDS];
-	char  bad_parsed_word[100];
+	short parsed_words[MAX_PARSED_WORDS]{};
+	char  bad_parsed_word[100]{};
 	int   raw_color = 0;
-	int32_t raw_modified[MAX_ROOM_BGFRAMES];
+	int32_t raw_modified[MAX_ROOM_BGFRAMES]{};
 	Shared::PBitmap raw_drawing_surface = 0;
-	short filenumbers[MAXSAVEGAMES];
+	short filenumbers[MAXSAVEGAMES]{};
 	int   room_changes = 0;
 	int   mouse_cursor_hidden = 0;
 	int   silent_midi = 0;
 	int   silent_midi_channel = 0;
 	int   current_music_repeating = 0;  // remember what the loop flag was when this music started
 	unsigned long shakesc_delay = 0;  // unsigned long to match _G(loopcounter)
-	int   shakesc_amount = 0, shakesc_length = 0;
-	int   rtint_red = 0, rtint_green = 0, rtint_blue = 0;
-	int   rtint_level = 0, rtint_light = 0;
-	bool  rtint_enabled = 0;
+	int   shakesc_amount = 0;
+	int   shakesc_length = 0;
+	int   rtint_red = 0;
+	int   rtint_green = 0;
+	int   rtint_blue = 0;
+	int   rtint_level = 0;
+	int   rtint_light = 0;
+	bool  rtint_enabled = false;
 	int   end_cutscene_music = 0;
 	int   skip_until_char_stops = 0;
 	int   get_loc_name_last_time = 0;
@@ -209,7 +230,7 @@ struct GameState {
 	int   restore_cursor_mode_to = 0;
 	int   restore_cursor_image_to = 0;
 	short music_queue_size = 0;
-	short music_queue[MAX_QUEUED_MUSIC];
+	short music_queue[MAX_QUEUED_MUSIC]{};
 	short new_music_queue_size = 0;
 	short crossfading_out_channel = 0;
 	short crossfade_step = 0;
@@ -218,12 +239,12 @@ struct GameState {
 	short crossfading_in_channel = 0;
 	short crossfade_in_volume_per_step = 0;
 	short crossfade_final_volume_in = 0;
-	QueuedAudioItem new_music_queue[MAX_QUEUED_MUSIC];
-	char  takeover_from[50];
-	char  playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN];
-	char  globalstrings[MAXGLOBALSTRINGS][MAX_MAXSTRLEN];
-	char  lastParserEntry[MAX_MAXSTRLEN];
-	char  game_name[MAX_GAME_STATE_NAME_LENGTH];
+	QueuedAudioItem new_music_queue[MAX_QUEUED_MUSIC]{};
+	char  takeover_from[50]{};
+	char  playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN]{};
+	char  globalstrings[MAXGLOBALSTRINGS][MAX_MAXSTRLEN]{};
+	char  lastParserEntry[MAX_MAXSTRLEN]{};
+	char  game_name[MAX_GAME_STATE_NAME_LENGTH]{};
 	int   ground_level_areas_disabled = 0;
 	int   next_screen_transition = 0;
 	int   gamma_adjustment = 0;
@@ -233,11 +254,11 @@ struct GameState {
 	std::unordered_set<AGS::Shared::String> do_once_tokens;
 	int   text_min_display_time_ms = 0;
 	int   ignore_user_input_after_text_timeout_ms = 0;
-	int32_t default_audio_type_volumes[MAX_AUDIO_TYPES];
+	int32_t default_audio_type_volumes[MAX_AUDIO_TYPES]{};
 
 	// Dynamic custom property values for characters and items
 	std::vector<AGS::Shared::StringIMap> charProps;
-	AGS::Shared::StringIMap invProps[MAX_INV];
+	AGS::Shared::StringIMap invProps[MAX_INV]{};
 
 	// Dynamic speech state
 	//
@@ -399,7 +420,7 @@ private:
 	void UpdateRoomCamera(int index);
 
 	// Defines if the room viewport should be adjusted to the room size automatically.
-	bool _isAutoRoomViewport = false;
+	bool _isAutoRoomViewport = true;
 	// Main viewport defines the rectangle of the drawn and interactable area;
 	// in the most basic case it will be equal to the game size.
 	Rect _mainViewport;
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 2fa4994ad34..7f6d93df5f1 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -513,6 +513,8 @@ int engine_init_sprites() {
 	return 0;
 }
 
+// TODO: this should not be a part of "engine_" function group,
+// move this elsewhere (InitGameState?).
 void engine_init_game_settings() {
 	_G(our_eip) = -7;
 	Debug::Printf("Initialize game settings");
@@ -596,6 +598,9 @@ void engine_init_game_settings() {
 		if (_GP(game).invinfo[ee].flags & IFLG_STARTWITH) _G(playerchar)->inv[ee] = 1;
 		else _G(playerchar)->inv[ee] = 0;
 	}
+
+	//
+	// TODO: following big initialization sequence could be in GameState ctor
 	_GP(play).score = 0;
 	_GP(play).sierra_inv_color = 7;
 	// copy the value set by the editor
@@ -750,14 +755,6 @@ void engine_init_game_settings() {
 	memset(&_GP(play).script_timers[0], 0, MAX_TIMERS * sizeof(int));
 	memset(&_GP(play).default_audio_type_volumes[0], -1, MAX_AUDIO_TYPES * sizeof(int));
 
-	// reset graphical script vars (they're still used by some games)
-	for (ee = 0; ee < MAXGLOBALVARS; ee++)
-		_GP(play).globalvars[ee] = 0;
-	for (ee = 0; ee < MAXGSVALUES; ee++)
-		_GP(play).globalscriptvars[ee] = 0;
-	for (ee = 0; ee < MAXGLOBALSTRINGS; ee++)
-		_GP(play).globalstrings[ee][0] = 0;
-
 	if (!_GP(usetup).translation.IsEmpty())
 		Game_ChangeTranslation(_GP(usetup).translation.GetCStr());
 


Commit: a0f50c8cc9ceeff592889d7eb02bf7b035132875
    https://github.com/scummvm/scummvm/commit/a0f50c8cc9ceeff592889d7eb02bf7b035132875
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix declaration

Partially from upstream f513af2a8d77ca3dedf0231d59537cd13cb655fb

Changed paths:
    engines/ags/engine/ac/game_state.h


diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index a70f23fd9dc..37fc0498154 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -258,7 +258,7 @@ struct GameState {
 
 	// Dynamic custom property values for characters and items
 	std::vector<AGS::Shared::StringIMap> charProps;
-	AGS::Shared::StringIMap invProps[MAX_INV]{};
+	AGS::Shared::StringIMap invProps[MAX_INV];
 
 	// Dynamic speech state
 	//


Commit: 92d702c4abca68162d35515a76f3122855402568
    https://github.com/scummvm/scummvm/commit/92d702c4abca68162d35515a76f3122855402568
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Fix DoOnceOnly not finding any tokens

Was broken in d65935c2761ddf89d9e4270a61100cf09bdf0633

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 50f7d892219..82d751b57ea 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -543,10 +543,10 @@ ScriptViewFrame *Game_GetViewFrame(int view, int loop, int frame) {
 }
 
 int Game_DoOnceOnly(const char *token) {
-	if (_GP(play).do_once_tokens.count(String::Wrapper(token)) > 0)
+	if (_GP(play).do_once_tokens.find(String::Wrapper(token)) != _GP(play).do_once_tokens.end())
 		return 0;
-	_GP(play).do_once_tokens.insert(token);
 
+	_GP(play).do_once_tokens.insert(token);
 	return 1;
 }
 


Commit: ee1f1053d7a89b7caaf390cf1f4e8c57141cd57d
    https://github.com/scummvm/scummvm/commit/ee1f1053d7a89b7caaf390cf1f4e8c57141cd57d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.2)

Partially from upstream 549b5644954750d1d34eacc4a05e4f2cfaef74cb

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 0d231429a90..9adf538ef94 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.1"
+#define ACI_VERSION_STR      "3.6.1.2"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.1
+#define ACI_VERSION_MSRC_DEF  3.6.1.2
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 9f9eaa1e34b318e4e562600d3a6d369bf5916443
    https://github.com/scummvm/scummvm/commit/9f9eaa1e34b318e4e562600d3a6d369bf5916443
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: implemented ResourceCache as an abstract class

>From upstream 6c2a844131008a56a276e5fbb833c68545efe4ac

Changed paths:
  A engines/ags/shared/util/resource_cache.h


diff --git a/engines/ags/shared/util/resource_cache.h b/engines/ags/shared/util/resource_cache.h
new file mode 100644
index 00000000000..47c63f29de4
--- /dev/null
+++ b/engines/ags/shared/util/resource_cache.h
@@ -0,0 +1,663 @@
+/* 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/>.
+ *
+ */
+
+//=============================================================================
+//
+// ResourceCache is an abstract storage that tracks use history with MRU list.
+// Cache is limited to a certain size, in bytes.
+// When a total size of items reaches the limit, and more items are put into,
+// the Cache uses MRU list to find the least used items and disposes them
+// one by one until the necessary space is freed.
+// ResourceCache's implementations must provide a method for calculating an
+// item's size.
+//
+// Supports copyable and movable items, have 2 variants of Put function for
+// each of them. This lets it store both std::shared_ptr and std::unique_ptr.
+//
+// TODO: support data Priority, which tells which items may be disposed
+// when adding new item and surpassing the cache limit.
+//
+// TODO: as an option, consider supporting a specialized container type that
+// has an associative container's interface, but is optimized for having most
+// keys allocated in large continious sequences by default.
+// This may be suitable for e.g. sprites, and may (or may not?) save some mem.
+//
+// Only for the reference: one of the ideas is for container to have a table
+// of arrays of fixed size internally. When getting an item the hash would be
+// first divided on array size to find the array the item resides in, then the
+// item is taken from item from slot index = (hash - arrsize * arrindex).
+// Find out if there is already a hash table kind that follows similar
+// principle.
+//
+//=============================================================================
+
+#ifndef AGS_SHARED_UTIL_RESOURCE_CACHE_H
+#define AGS_SHARED_UTIL_RESOURCE_CACHE_H
+
+#include "common/std/list.h"
+#include "common/std/map.h"
+#include "ags/shared/util/string.h"
+
+namespace AGS3 {
+namespace AGS {
+namespace Shared {
+
+template<typename TKey, typename TValue,
+		 typename TSize = size_t, typename HashFn = std::hash<TKey> >
+class ResourceCache {
+public:
+	// Flags determine management rules for the particular item
+	enum ItemFlags {
+		// Locked items are temporarily saved from disposal when freeing cache space;
+		// they still count towards the cache size though.
+		kCacheItem_Locked = 0x0001,
+		// External items are managed strictly by the external user;
+		// do not count towards the cache size, do not prevent caching normal items.
+		// They cannot be locked or released (considered permanently locked),
+		// only removed by request.
+		kCacheItem_External = 0x0002,
+	};
+
+	ResourceCache(TSize max_size = 0u)
+		: _maxSize(max_size), _sectionLocked(_mru.end()) {}
+
+	// Get the MRU cache size limit
+	inline size_t GetMaxCacheSize() const { return _maxSize; }
+	// Get the current total MRU cache size
+	inline size_t GetCacheSize() const { return _cacheSize; }
+	// Get the summed size of locked items (included in total cache size)
+	inline size_t GetLockedSize() const { return _lockedSize; }
+	// Get the summed size of external items (excluded from total cache size)
+	inline size_t GetExternalSize() const { return _externalSize; }
+
+	// Set the MRU cache size limit
+	void SetMaxCacheSize(TSize size = 0u) {
+		_maxSize = size;
+		FreeMem(0u); // makes sure it does not exceed max size
+	}
+
+	// Tells if particular key is in the cache
+	bool Exists(const TKey &key) const {
+		return _storage.find(key) != _storage.end();
+	}
+
+	// Gets the item with the given key if it exists;
+	// reorders the item as recently used.
+	const TValue &Get(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return _dummy; // no such key
+
+		// Unless locked, move the item ref to the beginning of the MRU list
+		const auto &item = it->second;
+		if ((item.Flags & kCacheItem_Locked) == 0)
+			_mru.splice(_mru.begin(), _mru, item.MruIt);
+		return item.Value;
+	}
+
+	// Add particular item into the cache, disposes existing item if such key is already taken.
+	// If a new item will exceed the cache size limit, cache will remove oldest items
+	// in order to free mem.
+	void Put(const TKey &key, const TValue &value, uint32_t flags = 0u) {
+		if (_maxSize == 0)
+			return; // cache is disabled
+		auto it = _storage.find(key);
+		if (it != _storage.end()) {
+			// Remove previous cached item
+			RemoveImpl(it);
+		}
+		PutImpl(key, TValue(value), flags); // make a temp local copy for safe std::move
+	}
+
+	void Put(const TKey &key, TValue &&value, uint32_t flags = 0u) {
+		if (_maxSize == 0)
+			return; // cache is disabled
+		auto it = _storage.find(key);
+		if (it != _storage.end()) {
+			// Remove previous cached item
+			RemoveImpl(it);
+		}
+		PutImpl(key, std::move(value), flags);
+	}
+
+	// Locks the item with the given key,
+	// temporarily excluding it from MRU disposal rules
+	void Lock(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+		auto &item = it->second;
+		if ((item.Flags & kCacheItem_Locked) != 0)
+			return; // already locked
+
+		// Lock item and move to the locked section
+		item.Flags |= kCacheItem_Locked;
+		_mru.splice(_sectionLocked, _mru, item.MruIt); // CHECKME: TEST!!
+		_sectionLocked = item.MruIt;
+		_lockedSize += item.Size;
+	}
+
+	// Releases (unlocks) the item with the given key,
+	// adds it back to MRU disposal rules
+	void Release(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+
+		auto &item = it->second;
+		if ((item.Flags & kCacheItem_External) != 0)
+			return; // never release external data, must be removed by user
+		if ((item.Flags & kCacheItem_Locked) == 0)
+			return; // not locked
+
+		// Unlock, and move the item to the beginning of the MRU list
+		item.Flags &= ~kCacheItem_Locked;
+		if (_sectionLocked == item.MruIt)
+			_sectionLocked = std::next(item.MruIt);
+		_mru.splice(_mru.begin(), _mru, item.MruIt); // CHECKME: TEST!!
+		_lockedSize -= item.Size;
+	}
+
+	// Deletes the cached item
+	void Dispose(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+		RemoveImpl(it);
+	}
+
+	// Removes the item from the cache and returns to the caller.
+	TValue Remove(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return TValue(); // no such key
+		TValue value = std::move(it->second.Value);
+		RemoveImpl(it);
+		return value;
+	}
+
+	// Disposes all items that are not locked or external
+	void DisposeFreeItems() {
+		for (auto mru_it = _sectionLocked; mru_it != _mru.end(); ++mru_it) {
+			auto it = _storage.find(*mru_it);
+			assert(it != _storage.end());
+			auto &item = it->second;
+			_cacheSize -= item.Size;
+			_storage.erase(it);
+			_mru.erase(mru_it);
+		}
+	}
+
+	// Clear the cache, dispose all items
+	void Clear() {
+		_storage.clear();
+		_mru.clear();
+		_sectionLocked = _mru.end();
+		_cacheSize = 0u;
+		_lockedSize = 0u;
+		_externalSize = 0u;
+	}
+
+protected:
+	struct TItem;
+	// MRU list type
+	typedef std::list<TKey> TMruList;
+	// MRU list reference type
+	typedef typename TMruList::iterator TMruIt;
+	// Storage type
+	typedef std::unordered_map<TKey, TItem, HashFn> TStorage;
+
+	struct TItem {
+		TMruIt MruIt; // MRU list reference
+		TValue Value;
+		TSize Size = 0u;
+		uint32_t Flags = 0u; // flags determine management rules for this item
+
+		TItem() = default;
+		TItem(const TItem &item) = default;
+		TItem(TItem &&item) = default;
+		TItem(const TMruIt &mru_it, const TValue &value, const TSize size, uint32_t flags)
+			: MruIt(mru_it), Value(value), Size(size), Flags(flags) {}
+		TItem(const TMruIt &mru_it, TValue &&value, const TSize size, uint32_t flags)
+			: MruIt(mru_it), Value(std::move(value)), Size(size), Flags(flags) {}
+		TItem &operator=(const TItem &item) = default;
+		TItem &operator=(TItem &&item) = default;
+	};
+
+	// Calculates item size; expects to return 0 if an item is invalid
+	// and should not be added to the cache.
+	virtual TSize CalcSize(const TValue &item) = 0;
+
+private:
+	// Add particular item into the cache.
+	// If a new item will exceed the cache size limit, cache will remove oldest items
+	// in order to free mem.
+	void PutImpl(const TKey &key, TValue &&value, uint32_t flags) {
+		// Request item's size, and test if it's a valid item
+		TSize size = CalcSize(value);
+		if (size == 0u)
+			return; // invalid item
+
+		if ((flags & kCacheItem_External) == 0) {
+			// clear up space before adding
+			if (_cacheSize + size > _maxSize)
+				FreeMem(size);
+			_cacheSize += size;
+		} else {
+			// always mark external data as locked, easier to handle
+			flags |= kCacheItem_Locked;
+			_externalSize += size;
+		}
+
+		// Prepare a MRU slot, then add an item
+		TMruIt mru_it = _mru.end();
+		// only normal items are added to MRU at all
+		if ((flags & kCacheItem_External) == 0) {
+			if ((flags & kCacheItem_Locked) == 0) {
+				// normal item, add to the list
+				mru_it = _mru.insert(_mru.begin(), key);
+			} else {
+				// locked item, add to the dedicated list section
+				mru_it = _mru.insert(_sectionLocked, key);
+				_sectionLocked = mru_it;
+				_lockedSize += size;
+			}
+		}
+		TItem item = TItem(mru_it, std::move(value), size, flags);
+		_storage[key] = std::move(item);
+	}
+	// Removes the item from the container
+	void RemoveImpl(typename TStorage::iterator it) {
+		auto &item = it->second;
+		// normal items are removed from MRU, and discounted from cache size
+		if ((item.Flags & kCacheItem_External) == 0) {
+			TMruIt mru_it = item.MruIt;
+			if (_sectionLocked == mru_it)
+				_sectionLocked = std::next(mru_it);
+			_cacheSize -= item.Size;
+			if ((item.Flags & kCacheItem_Locked) != 0)
+				_lockedSize -= item.Size;
+			_mru.erase(mru_it);
+		} else {
+			_externalSize -= item.Size;
+		}
+		_storage.erase(it);
+	}
+	// Remove the oldest (least recently used) item in cache
+	void DisposeOldest() {
+		assert(_mru.begin() != _sectionLocked);
+		if (_mru.begin() == _sectionLocked)
+			return;
+		// Remove from the storage and mru list
+		auto mru_it = std::prev(_sectionLocked);
+		auto it = _storage.find(*mru_it);
+		assert(it != _storage.end());
+		auto &item = it->second;
+		assert((item.Flags & (kCacheItem_Locked | kCacheItem_External)) == 0);
+		_cacheSize -= item.Size;
+		_storage.erase(it);
+		_mru.erase(mru_it);
+	}
+	// Keep disposing oldest elements until cache has at least the given free space
+	void FreeMem(size_t space) {
+		// TODO: consider sprite cache's behavior where it would just clear
+		// whole cache in case disposing one by one were taking too much iterations
+		while ((_mru.begin() != _sectionLocked) && (_cacheSize + space > _maxSize)) {
+			DisposeOldest();
+		}
+	}
+
+	// Size of tracked data stored in this cache;
+	// note that this is an abstract value, which may or not refer to an
+	// actual size in bytes, and depends on the implementation.
+	TSize _cacheSize = 0u;
+	// Size of data locked (forbidden from disposal),
+	// this size is *included* in _cacheSize; provided for stats.
+	TSize _lockedSize = 0u;
+	// Size of the external data, that is - data that does not count towards
+	// cache limit, and which is not our reponsibility; provided for stats.
+	TSize _externalSize = 0u;
+	// Maximal size of tracked data.
+	// When the inserted item increases the cache size past this limit,
+	// the cache will try to free the space by removing oldest items.
+	// "External" data does not count towards this limit.
+	TSize _maxSize = 0u;
+	// MRU list: the way to track which items were used recently.
+	// When clearing up space for new items, cache first deletes the items
+	// that were last time used long ago.
+	TMruList _mru;
+	// A locked section border iterator, points to the *last* locked item
+	// starting from the end of the list, or equals _mru.end() if there's none.
+	TMruIt _sectionLocked;
+	// Key-to-mru lookup map
+	TStorage _storage;
+	// Dummy value, return in case of a missing key
+	TValue _dummy;
+};
+
+} // namespace Common
+} // namespace AGS
+
+#endif // __AGS_CN_UTIL__RESOURCECACHE_H#include <list>
+#include "util/string.h"
+#include <unordered_map>
+
+namespace AGS {
+namespace Common {
+
+template<typename TKey, typename TValue,
+		 typename TSize = size_t, typename HashFn = std::hash<TKey> >
+class ResourceCache {
+public:
+	// Flags determine management rules for the particular item
+	enum ItemFlags {
+		// Locked items are temporarily saved from disposal when freeing cache space;
+		// they still count towards the cache size though.
+		kCacheItem_Locked = 0x0001,
+		// External items are managed strictly by the external user;
+		// do not count towards the cache size, do not prevent caching normal items.
+		// They cannot be locked or released (considered permanently locked),
+		// only removed by request.
+		kCacheItem_External = 0x0002,
+	};
+
+	ResourceCache(TSize max_size = 0u)
+		: _maxSize(max_size), _sectionLocked(_mru.end()) {}
+
+	// Get the MRU cache size limit
+	inline size_t GetMaxCacheSize() const { return _maxSize; }
+	// Get the current total MRU cache size
+	inline size_t GetCacheSize() const { return _cacheSize; }
+	// Get the summed size of locked items (included in total cache size)
+	inline size_t GetLockedSize() const { return _lockedSize; }
+	// Get the summed size of external items (excluded from total cache size)
+	inline size_t GetExternalSize() const { return _externalSize; }
+
+	// Set the MRU cache size limit
+	void SetMaxCacheSize(TSize size = 0u) {
+		_maxSize = size;
+		FreeMem(0u); // makes sure it does not exceed max size
+	}
+
+	// Tells if particular key is in the cache
+	bool Exists(const TKey &key) const {
+		return _storage.find(key) != _storage.end();
+	}
+
+	// Gets the item with the given key if it exists;
+	// reorders the item as recently used.
+	const TValue &Get(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return _dummy; // no such key
+
+		// Unless locked, move the item ref to the beginning of the MRU list
+		const auto &item = it->second;
+		if ((item.Flags & kCacheItem_Locked) == 0)
+			_mru.splice(_mru.begin(), _mru, item.MruIt);
+		return item.Value;
+	}
+
+	// Add particular item into the cache, disposes existing item if such key is already taken.
+	// If a new item will exceed the cache size limit, cache will remove oldest items
+	// in order to free mem.
+	void Put(const TKey &key, const TValue &value, uint32_t flags = 0u) {
+		if (_maxSize == 0)
+			return; // cache is disabled
+		auto it = _storage.find(key);
+		if (it != _storage.end()) {
+			// Remove previous cached item
+			RemoveImpl(it);
+		}
+		PutImpl(key, TValue(value), flags); // make a temp local copy for safe std::move
+	}
+
+	void Put(const TKey &key, TValue &&value, uint32_t flags = 0u) {
+		if (_maxSize == 0)
+			return; // cache is disabled
+		auto it = _storage.find(key);
+		if (it != _storage.end()) {
+			// Remove previous cached item
+			RemoveImpl(it);
+		}
+		PutImpl(key, std::move(value), flags);
+	}
+
+	// Locks the item with the given key,
+	// temporarily excluding it from MRU disposal rules
+	void Lock(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+		auto &item = it->second;
+		if ((item.Flags & kCacheItem_Locked) != 0)
+			return; // already locked
+
+		// Lock item and move to the locked section
+		item.Flags |= kCacheItem_Locked;
+		_mru.splice(_sectionLocked, _mru, item.MruIt); // CHECKME: TEST!!
+		_sectionLocked = item.MruIt;
+		_lockedSize += item.Size;
+	}
+
+	// Releases (unlocks) the item with the given key,
+	// adds it back to MRU disposal rules
+	void Release(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+
+		auto &item = it->second;
+		if ((item.Flags & kCacheItem_External) != 0)
+			return; // never release external data, must be removed by user
+		if ((item.Flags & kCacheItem_Locked) == 0)
+			return; // not locked
+
+		// Unlock, and move the item to the beginning of the MRU list
+		item.Flags &= ~kCacheItem_Locked;
+		if (_sectionLocked == item.MruIt)
+			_sectionLocked = std::next(item.MruIt);
+		_mru.splice(_mru.begin(), _mru, item.MruIt); // CHECKME: TEST!!
+		_lockedSize -= item.Size;
+	}
+
+	// Deletes the cached item
+	void Dispose(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return; // no such key
+		RemoveImpl(it);
+	}
+
+	// Removes the item from the cache and returns to the caller.
+	TValue Remove(const TKey &key) {
+		auto it = _storage.find(key);
+		if (it == _storage.end())
+			return TValue(); // no such key
+		TValue value = std::move(it->second.Value);
+		RemoveImpl(it);
+		return value;
+	}
+
+	// Disposes all items that are not locked or external
+	void DisposeFreeItems() {
+		for (auto mru_it = _sectionLocked; mru_it != _mru.end(); ++mru_it) {
+			auto it = _storage.find(*mru_it);
+			assert(it != _storage.end());
+			auto &item = it->second;
+			_cacheSize -= item.Size;
+			_storage.erase(it);
+			_mru.erase(mru_it);
+		}
+	}
+
+	// Clear the cache, dispose all items
+	void Clear() {
+		_storage.clear();
+		_mru.clear();
+		_sectionLocked = _mru.end();
+		_cacheSize = 0u;
+		_lockedSize = 0u;
+		_externalSize = 0u;
+	}
+
+protected:
+	struct TItem;
+	// MRU list type
+	typedef std::list<TKey> TMruList;
+	// MRU list reference type
+	typedef typename TMruList::iterator TMruIt;
+	// Storage type
+	typedef std::unordered_map<TKey, TItem, HashFn> TStorage;
+
+	struct TItem {
+		TMruIt MruIt; // MRU list reference
+		TValue Value;
+		TSize Size = 0u;
+		uint32_t Flags = 0u; // flags determine management rules for this item
+
+		TItem() = default;
+		TItem(const TItem &item) = default;
+		TItem(TItem &&item) = default;
+		TItem(const TMruIt &mru_it, const TValue &value, const TSize size, uint32_t flags)
+			: MruIt(mru_it), Value(value), Size(size), Flags(flags) {}
+		TItem(const TMruIt &mru_it, TValue &&value, const TSize size, uint32_t flags)
+			: MruIt(mru_it), Value(std::move(value)), Size(size), Flags(flags) {}
+		TItem &operator=(const TItem &item) = default;
+		TItem &operator=(TItem &&item) = default;
+	};
+
+	// Calculates item size; expects to return 0 if an item is invalid
+	// and should not be added to the cache.
+	virtual TSize CalcSize(const TValue &item) = 0;
+
+private:
+	// Add particular item into the cache.
+	// If a new item will exceed the cache size limit, cache will remove oldest items
+	// in order to free mem.
+	void PutImpl(const TKey &key, TValue &&value, uint32_t flags) {
+		// Request item's size, and test if it's a valid item
+		TSize size = CalcSize(value);
+		if (size == 0u)
+			return; // invalid item
+
+		if ((flags & kCacheItem_External) == 0) {
+			// clear up space before adding
+			if (_cacheSize + size > _maxSize)
+				FreeMem(size);
+			_cacheSize += size;
+		} else {
+			// always mark external data as locked, easier to handle
+			flags |= kCacheItem_Locked;
+			_externalSize += size;
+		}
+
+		// Prepare a MRU slot, then add an item
+		TMruIt mru_it = _mru.end();
+		// only normal items are added to MRU at all
+		if ((flags & kCacheItem_External) == 0) {
+			if ((flags & kCacheItem_Locked) == 0) {
+				// normal item, add to the list
+				mru_it = _mru.insert(_mru.begin(), key);
+			} else {
+				// locked item, add to the dedicated list section
+				mru_it = _mru.insert(_sectionLocked, key);
+				_sectionLocked = mru_it;
+				_lockedSize += size;
+			}
+		}
+		TItem item = TItem(mru_it, std::move(value), size, flags);
+		_storage[key] = std::move(item);
+	}
+	// Removes the item from the container
+	void RemoveImpl(typename TStorage::iterator it) {
+		auto &item = it->second;
+		// normal items are removed from MRU, and discounted from cache size
+		if ((item.Flags & kCacheItem_External) == 0) {
+			TMruIt mru_it = item.MruIt;
+			if (_sectionLocked == mru_it)
+				_sectionLocked = std::next(mru_it);
+			_cacheSize -= item.Size;
+			if ((item.Flags & kCacheItem_Locked) != 0)
+				_lockedSize -= item.Size;
+			_mru.erase(mru_it);
+		} else {
+			_externalSize -= item.Size;
+		}
+		_storage.erase(it);
+	}
+	// Remove the oldest (least recently used) item in cache
+	void DisposeOldest() {
+		assert(_mru.begin() != _sectionLocked);
+		if (_mru.begin() == _sectionLocked)
+			return;
+		// Remove from the storage and mru list
+		auto mru_it = std::prev(_sectionLocked);
+		auto it = _storage.find(*mru_it);
+		assert(it != _storage.end());
+		auto &item = it->second;
+		assert((item.Flags & (kCacheItem_Locked | kCacheItem_External)) == 0);
+		_cacheSize -= item.Size;
+		_storage.erase(it);
+		_mru.erase(mru_it);
+	}
+	// Keep disposing oldest elements until cache has at least the given free space
+	void FreeMem(size_t space) {
+		// TODO: consider sprite cache's behavior where it would just clear
+		// whole cache in case disposing one by one were taking too much iterations
+		while ((_mru.begin() != _sectionLocked) && (_cacheSize + space > _maxSize)) {
+			DisposeOldest();
+		}
+	}
+
+	// Size of tracked data stored in this cache;
+	// note that this is an abstract value, which may or not refer to an
+	// actual size in bytes, and depends on the implementation.
+	TSize _cacheSize = 0u;
+	// Size of data locked (forbidden from disposal),
+	// this size is *included* in _cacheSize; provided for stats.
+	TSize _lockedSize = 0u;
+	// Size of the external data, that is - data that does not count towards
+	// cache limit, and which is not our reponsibility; provided for stats.
+	TSize _externalSize = 0u;
+	// Maximal size of tracked data.
+	// When the inserted item increases the cache size past this limit,
+	// the cache will try to free the space by removing oldest items.
+	// "External" data does not count towards this limit.
+	TSize _maxSize = 0u;
+	// MRU list: the way to track which items were used recently.
+	// When clearing up space for new items, cache first deletes the items
+	// that were last time used long ago.
+	TMruList _mru;
+	// A locked section border iterator, points to the *last* locked item
+	// starting from the end of the list, or equals _mru.end() if there's none.
+	TMruIt _sectionLocked;
+	// Key-to-mru lookup map
+	TStorage _storage;
+	// Dummy value, return in case of a missing key
+	TValue _dummy;
+};
+
+} // namespace Shared
+} // namespace AGS
+} // namespace AGS3
+
+#endif // AGS_SHARED_UTIL_RESOURCE_CACHE_H
\ No newline at end of file


Commit: 65237c4ae888f4c4ee9cc8c6003e95f2b413736b
    https://github.com/scummvm/scummvm/commit/65237c4ae888f4c4ee9cc8c6003e95f2b413736b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in GraphicsDriver made alpha & opaque params consistent

Partially from upstream b10108614a82622de15bb835d65597947952f0d9

Changed paths:
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/gfx_driver_base.cpp
    engines/ags/engine/gfx/gfx_driver_base.h
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index ce5a91ce628..3efcb9d6171 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -249,18 +249,18 @@ IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateDDB(int width, int
 	return new ALSoftwareBitmap(width, height, color_depth, opaque);
 }
 
-IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) {
-	return new ALSoftwareBitmap(bitmap, opaque, hasAlpha);
+IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque) {
+	return new ALSoftwareBitmap(bitmap, has_alpha, opaque);
 }
 
 IDriverDependantBitmap *ScummVMRendererGraphicsDriver::CreateRenderTargetDDB(int width, int height, int color_depth, bool opaque) {
 	return new ALSoftwareBitmap(width, height, color_depth, opaque);
 }
 
-void ScummVMRendererGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) {
+void ScummVMRendererGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Bitmap *bitmap, bool has_alpha) {
 	ALSoftwareBitmap *alSwBmp = (ALSoftwareBitmap *)bitmapToUpdate;
 	alSwBmp->_bmp = bitmap;
-	alSwBmp->_hasAlpha = hasAlpha;
+	alSwBmp->_hasAlpha = has_alpha;
 }
 
 void ScummVMRendererGraphicsDriver::DestroyDDB(IDriverDependantBitmap *bitmap) {
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 493e73fece1..4328321e9c5 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -91,13 +91,13 @@ public:
 		_stretchToHeight = _height;
 	}
 
-	ALSoftwareBitmap(Bitmap *bmp, bool opaque, bool hasAlpha) {
+	ALSoftwareBitmap(Bitmap *bmp, bool has_alpha, bool opaque) {
 		_bmp = bmp;
 		_width = bmp->GetWidth();
 		_height = bmp->GetHeight();
 		_colDepth = bmp->GetColorDepth();
 		_opaque = opaque;
-		_hasAlpha = hasAlpha;
+		_hasAlpha = has_alpha;
 		_stretchToWidth = _width;
 		_stretchToHeight = _height;
 	}
@@ -181,18 +181,18 @@ public:
 	void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) override;
 	int  GetCompatibleBitmapFormat(int color_depth) override;
 	IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque) override;
-	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) override;
+	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque) override;
 	IDriverDependantBitmap *CreateRenderTargetDDB(int width, int height, int color_depth, bool opaque) override;
-	void UpdateDDBFromBitmap(IDriverDependantBitmap *ddb, Bitmap *bitmap, bool hasAlpha) override;
+	void UpdateDDBFromBitmap(IDriverDependantBitmap *ddb, Bitmap *bitmap, bool has_alpha) override;
 	void DestroyDDB(IDriverDependantBitmap *ddb) override;
 
 	IDriverDependantBitmap *GetSharedDDB(uint32_t /*sprite_id*/,
-		Bitmap *bitmap, bool hasAlpha, bool opaque) override {
+		Bitmap *bitmap, bool has_alpha, bool opaque) override {
 		// Software renderer does not require a texture cache, because it uses bitmaps directly
-		return CreateDDBFromBitmap(bitmap, hasAlpha, opaque);
+		return CreateDDBFromBitmap(bitmap, has_alpha, opaque);
 	}
 
-	void UpdateSharedDDB(uint32_t /*sprite_id*/, Bitmap */*bitmap*/, bool /*hasAlpha*/, bool /*opaque*/) override {
+	void UpdateSharedDDB(uint32_t /*sprite_id*/, Bitmap */*bitmap*/, bool /*has_alpha*/, bool /*opaque*/) override {
 		/* do nothing */
 	}
 	void ClearSharedDDB(uint32_t /*sprite_id*/) override {
diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 8170b0cc850..1e4612f4e3e 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -197,14 +197,14 @@ bool VideoMemoryGraphicsDriver::GetStageMatrixes(RenderMatrixes &rm) {
 	return true;
 }
 
-IDriverDependantBitmap *VideoMemoryGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) {
+IDriverDependantBitmap *VideoMemoryGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque) {
 	IDriverDependantBitmap * ddb = CreateDDB(bitmap->GetWidth(), bitmap->GetHeight(), bitmap->GetColorDepth(), opaque);
 	if (ddb)
-		UpdateDDBFromBitmap(ddb, bitmap, hasAlpha);
+		UpdateDDBFromBitmap(ddb, bitmap, has_alpha);
 	return ddb;
 }
 
-IDriverDependantBitmap *VideoMemoryGraphicsDriver::GetSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool hasAlpha, bool opaque) {
+IDriverDependantBitmap *VideoMemoryGraphicsDriver::GetSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool has_alpha, bool opaque) {
 	const auto found = _txRefs.find(sprite_id);
 	if (found != _txRefs.end()) {
 		const auto &item = found->_value;
@@ -215,7 +215,7 @@ IDriverDependantBitmap *VideoMemoryGraphicsDriver::GetSharedDDB(uint32_t sprite_
 	// Create and add a new element
 	std::shared_ptr<TextureData> txdata(CreateTextureData(bitmap->GetWidth(), bitmap->GetHeight(), opaque));
 	txdata->ID = sprite_id;
-	UpdateTextureData(txdata.get(), bitmap, opaque, hasAlpha);
+	UpdateTextureData(txdata.get(), bitmap, has_alpha, opaque);
 	// only add into the map when has valid sprite ID
 	if (sprite_id != UINT32_MAX) {
 		_txRefs[sprite_id] = TextureCacheItem(txdata,
@@ -224,7 +224,7 @@ IDriverDependantBitmap *VideoMemoryGraphicsDriver::GetSharedDDB(uint32_t sprite_
 	return CreateDDB(txdata, bitmap->GetWidth(), bitmap->GetHeight(), bitmap->GetColorDepth(), opaque);
 }
 
-void VideoMemoryGraphicsDriver::UpdateSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool hasAlpha, bool opaque) {
+void VideoMemoryGraphicsDriver::UpdateSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool has_alpha, bool opaque) {
 	const auto found = _txRefs.find(sprite_id);
 	if (found == _txRefs.end())
 		return;
@@ -236,7 +236,7 @@ void VideoMemoryGraphicsDriver::UpdateSharedDDB(uint32_t sprite_id, Bitmap *bitm
 	// otherwise - detach shared texture (don't delete the data yet, as it may be in use)
 	const auto &res = found->_value.Res;
 	if (res.Width == bitmap->GetWidth() && res.Height == bitmap->GetHeight() && res.ColorDepth == bitmap->GetColorDepth()) {
-		UpdateTextureData(txdata.get(), bitmap, opaque, hasAlpha);
+		UpdateTextureData(txdata.get(), bitmap, has_alpha, opaque);
 	} else {
 		txdata->ID = UINT32_MAX;
 		_txRefs.erase(found);
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 309eaee0959..256dcd43ae3 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -248,13 +248,13 @@ public:
 	// Creates new texture using given parameters
 	IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque) override = 0;
 	// Creates new texture and copy bitmap contents over
-	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque = false) override;
+	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque = false) override;
 	// Get shared texture from cache, or create from bitmap and assign ID
-	IDriverDependantBitmap *GetSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool hasAlpha, bool opaque) override;
+	IDriverDependantBitmap *GetSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool has_alpha, bool opaque) override;
 	// Removes the shared texture reference, will force the texture to recreate next time
 	void ClearSharedDDB(uint32_t sprite_id) override;
 	// Updates shared texture data, but only if it is present in the cache
-	void UpdateSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool hasAlpha, bool opaque) override;
+	void UpdateSharedDDB(uint32_t sprite_id, Bitmap *bitmap, bool has_alpha, bool opaque) override;
 	void DestroyDDB(IDriverDependantBitmap* ddb) override;
 
 	// Sets stage screen parameters for the current batch.
@@ -264,7 +264,7 @@ protected:
 	// Create texture data with the given parameters
 	virtual TextureData *CreateTextureData(int width, int height, bool opaque, bool as_render_target = false) = 0;
 	// Update texture data from the given bitmap
-	virtual void UpdateTextureData(TextureData *txdata, Bitmap *bmp, bool opaque, bool hasAlpha) = 0;
+	virtual void UpdateTextureData(TextureData *txdata, Bitmap *bmp, bool has_alpha, bool opaque) = 0;
 	// Create DDB using preexisting texture data
 	virtual IDriverDependantBitmap *CreateDDB(std::shared_ptr<TextureData> txdata,
 		  int width, int height, int color_depth, bool opaque) = 0;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 3655f5a0a11..e1c65f5ce95 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -139,12 +139,12 @@ public:
 	// Creates a "raw" DDB, without pixel initialization
 	virtual IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque = false) = 0;
 	// Creates DDB, initializes from the given bitmap.
-	virtual IDriverDependantBitmap *CreateDDBFromBitmap(Shared::Bitmap *bitmap, bool hasAlpha, bool opaque = false) = 0;
+	virtual IDriverDependantBitmap *CreateDDBFromBitmap(Shared::Bitmap *bitmap, bool has_alpha, bool opaque = false) = 0;
 	// Creates DDB intended to be used as a render target (allow render other DDBs on it).
 	virtual IDriverDependantBitmap *CreateRenderTargetDDB(int width, int height, int color_depth, bool opaque = false) = 0;
 	// Updates DBB using the given bitmap; bitmap must have same size and format
 	// as the one that this DDB was initialized with.
-	virtual void UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Shared::Bitmap *bitmap, bool hasAlpha) = 0;
+	virtual void UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Shared::Bitmap *bitmap, bool has_alpha) = 0;
 	// Destroy the DDB.
 	virtual void DestroyDDB(IDriverDependantBitmap *bitmap) = 0;
 
@@ -154,8 +154,8 @@ public:
 	// be applied to the shared texture data. Currently it's possible to share same
 	// texture data, but update it with different "opaque" values, which breaks logic.
 	virtual IDriverDependantBitmap *GetSharedDDB(uint32_t sprite_id,
-		Shared::Bitmap *bitmap = nullptr, bool hasAlpha = true, bool opaque = false) = 0;
-	virtual void UpdateSharedDDB(uint32_t sprite_id, Shared::Bitmap *bitmap = nullptr, bool hasAlpha = true, bool opaque = false) = 0;
+		Shared::Bitmap *bitmap = nullptr, bool has_alpha = true, bool opaque = false) = 0;
+	virtual void UpdateSharedDDB(uint32_t sprite_id, Shared::Bitmap *bitmap = nullptr, bool has_alpha = true, bool opaque = false) = 0;
 	// Removes the shared texture reference, will force the texture to recreate next time
 	virtual void ClearSharedDDB(uint32_t sprite_id) = 0;
 


Commit: 6c8b82523d632314327776d08751b4ff4a41a5fa
    https://github.com/scummvm/scummvm/commit/6c8b82523d632314327776d08751b4ff4a41a5fa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: let configure texture cache size

Partially from upsteam e84ca2191319fc20426c91bc5719e2503a4ccbf0

Changed paths:
    engines/ags/engine/ac/game_setup.h
    engines/ags/engine/main/config.cpp


diff --git a/engines/ags/engine/ac/game_setup.h b/engines/ags/engine/ac/game_setup.h
index 778c2057922..ecc67f25e86 100644
--- a/engines/ags/engine/ac/game_setup.h
+++ b/engines/ags/engine/ac/game_setup.h
@@ -59,6 +59,9 @@ using AGS::Shared::String;
  // for options that may be changed at runtime (and later written back
  // to the config file).
 struct GameSetup {
+	static const size_t DefSpriteCacheSize = (128 * 1024); // 128 MB
+	static const size_t DefTexCacheSize = (128 * 1024);    // 128 MB
+
 	bool  audio_enabled;
 	String audio_driver;
 	int   textheight; // text height used on the certain built-in GUI // TODO: move out to game class?
@@ -88,7 +91,8 @@ struct GameSetup {
 	MouseSpeedDef mouse_speed_def;
 	bool  RenderAtScreenRes; // render sprites at screen resolution, as opposed to native one
 	int   Supersampling;
-	size_t SpriteCacheSize = 0u;
+	size_t SpriteCacheSize = DefSpriteCacheSize;
+	size_t TextureCacheSize = DefTexCacheSize;
 	bool  clear_cache_on_room_change; // for low-end devices: clear resource caches on room change
 	bool  load_latest_save; // load latest saved game on launch
 	ScreenRotation rotation;
diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp
index 16c8166bf08..e6ac161812a 100644
--- a/engines/ags/engine/main/config.cpp
+++ b/engines/ags/engine/main/config.cpp
@@ -356,10 +356,12 @@ void apply_config(const ConfigTree &cfg) {
 
 		// Resource caches and options
 		_GP(usetup).clear_cache_on_room_change = CfgReadBoolInt(cfg, "misc", "clear_cache_on_room_change", _GP(usetup).clear_cache_on_room_change);
-		int cache_size_kb = CfgReadInt(cfg, "misc", "cachemax", DEFAULTCACHESIZE_KB);
+		int cache_size_kb = CfgReadInt(cfg, "graphics", "sprite_cache_size", GameSetup::DefSpriteCacheSize);
 		if (cache_size_kb > 0)
 			_GP(usetup).SpriteCacheSize = cache_size_kb * 1024;
-
+		cache_size_kb = CfgReadInt(cfg, "graphics", "texture_cache_size", GameSetup::DefTexCacheSize);
+		if (cache_size_kb > 0)
+			_GP(usetup).TextureCacheSize = cache_size_kb * 1024;
 		// Mouse options
 		_GP(usetup).mouse_auto_lock = CfgReadBoolInt(cfg, "mouse", "auto_lock");
 		_GP(usetup).mouse_speed = CfgReadFloat(cfg, "mouse", "speed", 1.f);


Commit: 34a00ea83b37e698bf212916ff60890fafdb632b
    https://github.com/scummvm/scummvm/commit/34a00ea83b37e698bf212916ff60890fafdb632b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix typos: loose -> lose

>From upstream 9a51653eff376c1663f70222884d6ee672e5e7c3

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 82d751b57ea..8a43d2ce16f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1198,7 +1198,7 @@ int __GetLocationType(int xxx, int yyy, int allowHotspot0) {
 	return winner;
 }
 
-// Called whenever game looses input focus
+// Called whenever game loses input focus
 void display_switch_out() {
 	Debug::Printf("Switching out from the game");
 	_G(switched_away) = true;
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index 475e5eee80b..a54db714360 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -194,7 +194,7 @@ void stop_fast_forwarding();
 
 int __GetLocationType(int xxx, int yyy, int allowHotspot0);
 
-// Called whenever game looses input focus
+// Called whenever game loses input focus
 void display_switch_out();
 // Called whenever game gets input focus
 void display_switch_in();


Commit: a05c63e4dfdb3965c02c0048adcfd1f0cbee98a6
    https://github.com/scummvm/scummvm/commit/a05c63e4dfdb3965c02c0048adcfd1f0cbee98a6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix cache config and read legacy values too

>From upstream 9b2880e8b7c31891cad672fec9dc1124f9f3cc7c

Changed paths:
    engines/ags/engine/ac/game_setup.h
    engines/ags/engine/main/config.cpp
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/game_setup.h b/engines/ags/engine/ac/game_setup.h
index ecc67f25e86..ab99993516c 100644
--- a/engines/ags/engine/ac/game_setup.h
+++ b/engines/ags/engine/ac/game_setup.h
@@ -91,8 +91,8 @@ struct GameSetup {
 	MouseSpeedDef mouse_speed_def;
 	bool  RenderAtScreenRes; // render sprites at screen resolution, as opposed to native one
 	int   Supersampling;
-	size_t SpriteCacheSize = DefSpriteCacheSize;
-	size_t TextureCacheSize = DefTexCacheSize;
+	size_t SpriteCacheSize = DefSpriteCacheSize;  // in KB
+	size_t TextureCacheSize = DefTexCacheSize;  // in KB
 	bool  clear_cache_on_room_change; // for low-end devices: clear resource caches on room change
 	bool  load_latest_save; // load latest saved game on launch
 	ScreenRotation rotation;
diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp
index e6ac161812a..efc4a8b883f 100644
--- a/engines/ags/engine/main/config.cpp
+++ b/engines/ags/engine/main/config.cpp
@@ -263,14 +263,20 @@ static void read_legacy_graphics_config(const ConfigTree &cfg) {
 	_GP(usetup).Screen.Params.RefreshRate = CfgReadInt(cfg, "misc", "refresh");
 }
 
+static void read_legacy_config(const ConfigTree &cfg) {
+	read_legacy_graphics_config(cfg);
+
+	_GP(usetup).SpriteCacheSize = CfgReadInt(cfg, "misc", "cachemax", _GP(usetup).SpriteCacheSize);
+}
+
 void override_config_ext(ConfigTree &cfg) {
 	_G(platform)->ReadConfiguration(cfg);
 }
 
 void apply_config(const ConfigTree &cfg) {
-	// Legacy graphics settings has to be translated into new options;
+	// Legacy settings have to be translated into new options;
 	// they must be read first, to let newer options override them, if ones are present
-	read_legacy_graphics_config(cfg);
+	read_legacy_config(cfg);
 
 	{
 		// Audio options
@@ -356,12 +362,9 @@ void apply_config(const ConfigTree &cfg) {
 
 		// Resource caches and options
 		_GP(usetup).clear_cache_on_room_change = CfgReadBoolInt(cfg, "misc", "clear_cache_on_room_change", _GP(usetup).clear_cache_on_room_change);
-		int cache_size_kb = CfgReadInt(cfg, "graphics", "sprite_cache_size", GameSetup::DefSpriteCacheSize);
-		if (cache_size_kb > 0)
-			_GP(usetup).SpriteCacheSize = cache_size_kb * 1024;
-		cache_size_kb = CfgReadInt(cfg, "graphics", "texture_cache_size", GameSetup::DefTexCacheSize);
-		if (cache_size_kb > 0)
-			_GP(usetup).TextureCacheSize = cache_size_kb * 1024;
+		_GP(usetup).SpriteCacheSize = CfgReadInt(cfg, "graphics", "sprite_cache_size", _GP(usetup).SpriteCacheSize);
+		_GP(usetup).TextureCacheSize = CfgReadInt(cfg, "graphics", "texture_cache_size", _GP(usetup).TextureCacheSize);
+
 		// Mouse options
 		_GP(usetup).mouse_auto_lock = CfgReadBoolInt(cfg, "mouse", "auto_lock");
 		_GP(usetup).mouse_speed = CfgReadFloat(cfg, "mouse", "speed", 1.f);
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 7f6d93df5f1..73e43175e71 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -509,7 +509,7 @@ int engine_init_sprites() {
 	}
 
 	if (_GP(usetup).SpriteCacheSize > 0)
-		_GP(spriteset).SetMaxCacheSize(_GP(usetup).SpriteCacheSize);
+		_GP(spriteset).SetMaxCacheSize(_GP(usetup).SpriteCacheSize * 1024);
 	return 0;
 }
 


Commit: a3d471074f94fb2d1656e71ab0b7072d092d5338
    https://github.com/scummvm/scummvm/commit/a3d471074f94fb2d1656e71ab0b7072d092d5338
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Engine: query gfxdriver for texture mem, and optionally restrict cache

Changed paths:
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/graphics_driver.h
    engines/ags/shared/util/resource_cache.h


diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 4328321e9c5..101e0aab110 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -180,6 +180,10 @@ public:
 	// Clears the screen rectangle. The coordinates are expected in the **native game resolution**.
 	void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) override;
 	int  GetCompatibleBitmapFormat(int color_depth) override;
+	size_t GetAvailableTextureMemory() override {
+		// not using textures for sprites anyway
+		return 0;
+	}
 	IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque) override;
 	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool has_alpha, bool opaque) override;
 	IDriverDependantBitmap *CreateRenderTargetDDB(int width, int height, int color_depth, bool opaque) override;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index e1c65f5ce95..c71cdd35d8c 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -135,6 +135,8 @@ public:
 	// Gets closest recommended bitmap format (currently - only color depth) for the given original format.
 	// Engine needs to have game bitmaps brought to the certain range of formats, easing conversion into the video bitmaps.
 	virtual int  GetCompatibleBitmapFormat(int color_depth) = 0;
+	// Returns available texture memory, or 0 if this query is not supported
+	virtual size_t GetAvailableTextureMemory() = 0;
 
 	// Creates a "raw" DDB, without pixel initialization
 	virtual IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque = false) = 0;
diff --git a/engines/ags/shared/util/resource_cache.h b/engines/ags/shared/util/resource_cache.h
index 47c63f29de4..90abaacb0ab 100644
--- a/engines/ags/shared/util/resource_cache.h
+++ b/engines/ags/shared/util/resource_cache.h
@@ -89,7 +89,7 @@ public:
 	inline size_t GetExternalSize() const { return _externalSize; }
 
 	// Set the MRU cache size limit
-	void SetMaxCacheSize(TSize size = 0u) {
+	void SetMaxCacheSize(TSize size) {
 		_maxSize = size;
 		FreeMem(0u); // makes sure it does not exceed max size
 	}


Commit: e572c4333cf7a3c69cd3016336d26d94ce3233a6
    https://github.com/scummvm/scummvm/commit/e572c4333cf7a3c69cd3016336d26d94ce3233a6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: print cache sizes to log

Partially from upstream 98550532869e1e22a8f23c76cd000a3579167628

Changed paths:
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 73e43175e71..3bdd1bbd3d1 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -510,6 +510,7 @@ int engine_init_sprites() {
 
 	if (_GP(usetup).SpriteCacheSize > 0)
 		_GP(spriteset).SetMaxCacheSize(_GP(usetup).SpriteCacheSize * 1024);
+	Debug::Printf("Sprite cache set: %zu KB", _GP(spriteset).GetMaxCacheSize() / 1024);
 	return 0;
 }
 


Commit: 6c517c2dbd05f074e34ca385ad3d5a83caf0789a
    https://github.com/scummvm/scummvm/commit/6c517c2dbd05f074e34ca385ad3d5a83caf0789a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: corrected few File::Open and File::Test calls

Partially from upstream b717b457f9d5ac92400e9e59951c936501846f8d

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


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 51a7307fd47..d337e163574 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -66,7 +66,7 @@ int File_Exists(const char *fnmm) {
 	if (rp.AssetMgr)
 		return _GP(AssetMgr)->DoesAssetExist(rp.FullPath);
 
-	return (File::TestReadFile(rp.FullPath) || File::TestReadFile(rp.AltPath)) ? 1 : 0;
+	return (File::IsFile(rp.FullPath) || File::IsFile(rp.AltPath)) ? 1 : 0;
 }
 
 int File_Delete(const char *fnmm) {


Commit: 5edf71bad91344d525601349e171630e8e1b1594
    https://github.com/scummvm/scummvm/commit/5edf71bad91344d525601349e171630e8e1b1594
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Implemented AppendPath

Partially from upstream 411540ece569afe2227d6eee13262f7828b777a7

Changed paths:
    engines/ags/shared/util/path.cpp
    engines/ags/shared/util/path.h


diff --git a/engines/ags/shared/util/path.cpp b/engines/ags/shared/util/path.cpp
index a0dc266e3a0..0a9db7106f1 100644
--- a/engines/ags/shared/util/path.cpp
+++ b/engines/ags/shared/util/path.cpp
@@ -189,6 +189,15 @@ String MakeRelativePath(const String &base, const String &path) {
 	return rel_path;
 }
 
+String &AppendPath(String &path, const String &child) {
+	if (path.IsEmpty())
+		path = child;
+	else if (!child.IsEmpty())
+		path.AppendFmt("/%s", child.GetCStr());
+	FixupPath(path);
+	return path;
+}
+
 String ConcatPaths(const String &parent, const String &child) {
 	if (parent.IsEmpty())
 		return child;
diff --git a/engines/ags/shared/util/path.h b/engines/ags/shared/util/path.h
index 90289385dad..4df816a8424 100644
--- a/engines/ags/shared/util/path.h
+++ b/engines/ags/shared/util/path.h
@@ -82,11 +82,13 @@ String  MakeAbsolutePath(const String &path);
 // if walking out of the 'base'. Returns empty string on failure.
 // NOTE: the 'base' is only considered a directory if it has a trailing slash.
 String  MakeRelativePath(const String &base, const String &path);
+// Creates path by combining directory, file name and extension
+String  MakePath(const String &parent, const String &filename, const String &ext);
+// Appends another section to existing path
+String  &AppendPath(String &path, const String &child);
 // Concatenates parent and relative paths
 String  ConcatPaths(const String &parent, const String &child);
 String  ConcatPaths(String &buf, const String &parent, const String &child);
-// Creates path by combining directory, file name and extension
-String  MakePath(const String &parent, const String &filename, const String &ext);
 // Splits path into components, divided by path separator
 std::vector<String> Split(const String &path);
 


Commit: 710f053d6b4314746c4a013e4cddb716374dc53d
    https://github.com/scummvm/scummvm/commit/710f053d6b4314746c4a013e4cddb716374dc53d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.3)

Partially from upstream 6e1da7f1e52c1000951f6264f5ab688882dbe043

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 9adf538ef94..2b095404f26 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.2"
+#define ACI_VERSION_STR      "3.6.1.3"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.2
+#define ACI_VERSION_MSRC_DEF  3.6.1.3
 #endif
 
 #define SPECIAL_VERSION ""


Commit: a10d4f5201c7120454e87da75af6cf11c9b7f555
    https://github.com/scummvm/scummvm/commit/a10d4f5201c7120454e87da75af6cf11c9b7f555
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: added optional srcName field to Stream, to help with debugging

This field is supposed to store an arbitrary name of the stream's source,
 such as filepath for FileStream.
 From upstream f5e758bec40757eabdf1b96dd58e2b00f2e1088c

Changed paths:
    engines/ags/shared/util/file_stream.cpp
    engines/ags/shared/util/file_stream.h
    engines/ags/shared/util/stream.h


diff --git a/engines/ags/shared/util/file_stream.cpp b/engines/ags/shared/util/file_stream.cpp
index fdf7bcba4aa..6034a83c0c2 100644
--- a/engines/ags/shared/util/file_stream.cpp
+++ b/engines/ags/shared/util/file_stream.cpp
@@ -190,7 +190,7 @@ void FileStream::Open(const String &file_name, FileOpenMode open_mode, FileWorkM
 		if (!_file)
 			error("Invalid attempt to create file - %s", file_name.GetCStr());
 
-		_fileName = file_name;
+		_srcName = file_name;
 	}
 }
 
diff --git a/engines/ags/shared/util/file_stream.h b/engines/ags/shared/util/file_stream.h
index 7cb8f371094..e4fb4acf9e3 100644
--- a/engines/ags/shared/util/file_stream.h
+++ b/engines/ags/shared/util/file_stream.h
@@ -79,7 +79,6 @@ private:
 
 	Common::Stream *_file;
 	const FileWorkMode  _workMode;
-	String _fileName;
 };
 
 } // namespace Shared
diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h
index dcd979ebd0e..6394c7308ac 100644
--- a/engines/ags/shared/util/stream.h
+++ b/engines/ags/shared/util/stream.h
@@ -35,6 +35,7 @@
 #define AGS_SHARED_UTIL_STREAM_H
 
 #include "ags/shared/util/iags_stream.h"
+#include "ags/shared/util/string.h"
 #include "ags/lib/allegro/file.h"
 #include "common/stream.h"
 #include "common/types.h"
@@ -51,8 +52,14 @@ enum StreamWorkMode {
 
 class Stream : public IAGSStream {
 public:
+	Stream() = default;
+	Stream(const String &src_name)
+		: _srcName(src_name) {}
 	virtual ~Stream() {}
 
+	// Returns an optional name of a stream's source, such as a filepath;
+	// primarily for diagnostic purposes
+	const String &GetSrcName() const { return _srcName; }
 	// Tells if the stream has errors
 	virtual bool HasErrors() const {
 		return false;
@@ -91,6 +98,9 @@ public:
 
 	// Fill the requested number of bytes with particular value
 	size_t WriteByteCount(uint8_t b, size_t count);
+
+protected:
+	String _srcName; // optional name of the stream's source (e.g. filepath)
 };
 
 class ScummVMReadStream : public Common::SeekableReadStream {


Commit: 0f7b25e4a46229f9d8fd50c1cacf32951941e877
    https://github.com/scummvm/scummvm/commit/0f7b25e4a46229f9d8fd50c1cacf32951941e877
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: when resolving script file, use filepath saved in Stream

Partially from upstream 49aebe403fb3aba17d312b6596f7582a74a57674

Changed paths:
    engines/ags/shared/util/file_stream.cpp
    engines/ags/shared/util/stream.h


diff --git a/engines/ags/shared/util/file_stream.cpp b/engines/ags/shared/util/file_stream.cpp
index 6034a83c0c2..927bbb5a4e7 100644
--- a/engines/ags/shared/util/file_stream.cpp
+++ b/engines/ags/shared/util/file_stream.cpp
@@ -190,7 +190,7 @@ void FileStream::Open(const String &file_name, FileOpenMode open_mode, FileWorkM
 		if (!_file)
 			error("Invalid attempt to create file - %s", file_name.GetCStr());
 
-		_srcName = file_name;
+		_path = file_name;
 	}
 }
 
diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h
index 6394c7308ac..17b037d17ac 100644
--- a/engines/ags/shared/util/stream.h
+++ b/engines/ags/shared/util/stream.h
@@ -53,13 +53,13 @@ enum StreamWorkMode {
 class Stream : public IAGSStream {
 public:
 	Stream() = default;
-	Stream(const String &src_name)
-		: _srcName(src_name) {}
+	Stream(const String &path)
+		: _path(path) {}
 	virtual ~Stream() {}
 
-	// Returns an optional name of a stream's source, such as a filepath;
+	// Returns an optional path of a stream's source, such as a filepath;
 	// primarily for diagnostic purposes
-	const String &GetSrcName() const { return _srcName; }
+	const String &GetPath() const { return _path; }
 	// Tells if the stream has errors
 	virtual bool HasErrors() const {
 		return false;
@@ -100,7 +100,7 @@ public:
 	size_t WriteByteCount(uint8_t b, size_t count);
 
 protected:
-	String _srcName; // optional name of the stream's source (e.g. filepath)
+	String _path; // optional name of the stream's source (e.g. filepath)
 };
 
 class ScummVMReadStream : public Common::SeekableReadStream {


Commit: 951a8ee7e06f82e9f1d744830d29a49b085d7949
    https://github.com/scummvm/scummvm/commit/951a8ee7e06f82e9f1d744830d29a49b085d7949
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: ScriptFileHandle stores Stream in unique_ptr

>From upstream 6fd4ec5a5e301bd63b34d62b14b96016779a64db

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/file.h
    engines/ags/engine/ac/global_file.cpp


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index d337e163574..3b0c7674d6d 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -575,7 +575,7 @@ int num_open_script_files = 0;
 ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name) {
 	if (stream_ptr) {
 		for (int i = 0; i < num_open_script_files; ++i) {
-			if (stream_ptr == valid_handles[i].stream) {
+			if (stream_ptr == valid_handles[i].stream.get()) {
 				return &valid_handles[i];
 			}
 		}
@@ -602,7 +602,7 @@ ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *oper
 
 Stream *get_valid_file_stream_from_handle(int32_t handle, const char *operation_name) {
 	ScriptFileHandle *sc_handle = check_valid_file_handle_int32(handle, operation_name);
-	return sc_handle ? sc_handle->stream : nullptr;
+	return sc_handle ? sc_handle->stream.get() : nullptr;
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/ac/file.h b/engines/ags/engine/ac/file.h
index 2025308694e..51d9d355aff 100644
--- a/engines/ags/engine/ac/file.h
+++ b/engines/ags/engine/ac/file.h
@@ -28,6 +28,7 @@
 #ifndef AGS_ENGINE_AC_FILE_H
 #define AGS_ENGINE_AC_FILE_H
 
+#include "common/std/memory.h"
 #include "ags/engine/ac/dynobj/script_file.h"
 #include "ags/engine/ac/runtime_defines.h"
 
@@ -57,8 +58,8 @@ int     File_GetError(sc_File *fil);
 int     File_GetPosition(sc_File *fil);
 
 struct ScriptFileHandle {
-	Stream *stream;
-	int32_t  handle;
+	std::unique_ptr<Stream> stream;
+	int32_t  handle = 0;
 };
 extern ScriptFileHandle valid_handles[MAX_OPEN_SCRIPT_FILES + 1];
 extern int num_open_script_files;
diff --git a/engines/ags/engine/ac/global_file.cpp b/engines/ags/engine/ac/global_file.cpp
index 7344368f600..ca59df22a5b 100644
--- a/engines/ags/engine/ac/global_file.cpp
+++ b/engines/ags/engine/ac/global_file.cpp
@@ -96,7 +96,7 @@ int32_t FileOpen(const char *fnmm, Shared::FileOpenMode open_mode, Shared::FileW
 		}
 	}
 
-	valid_handles[useindx].stream = s;
+	valid_handles[useindx].stream.reset(s);
 	if (valid_handles[useindx].stream == nullptr) {
 		debug_script_warn("FileOpen: FAILED: %s", resolved_path.GetCStr());
 		return 0;
@@ -111,9 +111,7 @@ int32_t FileOpen(const char *fnmm, Shared::FileOpenMode open_mode, Shared::FileW
 
 void FileClose(int32_t handle) {
 	ScriptFileHandle *sc_handle = check_valid_file_handle_int32(handle, "FileClose");
-	delete sc_handle->stream;
-	sc_handle->stream = nullptr;
-	sc_handle->handle = 0;
+	*sc_handle = ScriptFileHandle();
 }
 void FileWrite(int32_t handle, const char *towrite) {
 	Stream *out = get_valid_file_stream_from_handle(handle, "FileWrite");


Commit: 887aea022937e209d1214011b4838d2a24e26b6b
    https://github.com/scummvm/scummvm/commit/887aea022937e209d1214011b4838d2a24e26b6b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed incorrect ScalingFar condition in update_object_scale()

>From upstream 5dc8c065a7162f892ecbfbdd1d8d276e75a5eaaa

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


diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index bf26e7bfa14..d61ae020c15 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -465,9 +465,9 @@ void update_object_scale(int &res_zoom, int &res_width, int &res_height,
 	if (use_region_scaling) {
 		// Only apply area zoom if we're on a a valid area:
 		// * either area is > 0, or
-		// * area 0 has valid scaling property
+		// * area 0 has valid scaling property (<> 0)
 		int onarea = get_walkable_area_at_location(objx, objy);
-		if ((onarea > 0) || (_GP(thisroom).WalkAreas[0].ScalingFar > 0)) {
+		if ((onarea > 0) || (_GP(thisroom).WalkAreas[0].ScalingFar != 0)) {
 			zoom = get_area_scaling(onarea, objx, objy);
 		}
 	}


Commit: 8af54c7f21abced2104399ace6357593748d46fe
    https://github.com/scummvm/scummvm/commit/8af54c7f21abced2104399ace6357593748d46fe
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: implemented File.ResolvePath()

File.ResolvePath resolves a script path into the filesystem path, in exact
same way as the engine does it when searching for a file.
Partially from upstream 720114e8a873dc69bf54de4148d2da77ae6561c6

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


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 3b0c7674d6d..5afd762c3a7 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -95,6 +95,14 @@ void *sc_OpenFile(const char *fnmm, int mode) {
 	return scf;
 }
 
+const char *File_ResolvePath(const char *fnmm) {
+	ResolvedPath rp;
+	ResolveScriptPath(fnmm, true, rp);
+	// Make path pretty -
+	String path = Path::MakeAbsolutePath(rp.FullPath);
+	return CreateNewScriptString(path.GetCStr());
+}
+
 void File_Close(sc_File *fil) {
 	fil->Close();
 }
@@ -626,6 +634,10 @@ RuntimeScriptValue Sc_sc_OpenFile(const RuntimeScriptValue *params, int32_t para
 	API_SCALL_OBJAUTO_POBJ_PINT(sc_File, sc_OpenFile, const char);
 }
 
+RuntimeScriptValue Sc_File_ResolvePath(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), File_ResolvePath, const char);
+}
+
 // void (sc_File *fil)
 RuntimeScriptValue Sc_File_Close(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID(sc_File, File_Close);
@@ -713,6 +725,7 @@ void RegisterFileAPI() {
 	ccAddExternalStaticFunction("File::Delete^1", Sc_File_Delete);
 	ccAddExternalStaticFunction("File::Exists^1", Sc_File_Exists);
 	ccAddExternalStaticFunction("File::Open^2", Sc_sc_OpenFile);
+	ccAddExternalStaticFunction("File::ResolvePath^1", Sc_File_ResolvePath);
 	ccAddExternalObjectFunction("File::Close^0", Sc_File_Close);
 	ccAddExternalObjectFunction("File::ReadInt^0", Sc_File_ReadInt);
 	ccAddExternalObjectFunction("File::ReadRawChar^0", Sc_File_ReadRawChar);


Commit: 7e3aac02bf98d0f315e0d9da21ba97db30015beb
    https://github.com/scummvm/scummvm/commit/7e3aac02bf98d0f315e0d9da21ba97db30015beb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Plugin API: added ResolveFilePath()

>From upstream 0f143a54e7eb6997ad86e5d2769af206543d5f2a

Changed paths:
    engines/ags/engine/ac/file.h
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/ags_plugin.h


diff --git a/engines/ags/engine/ac/file.h b/engines/ags/engine/ac/file.h
index 51d9d355aff..afa059c74dc 100644
--- a/engines/ags/engine/ac/file.h
+++ b/engines/ags/engine/ac/file.h
@@ -39,6 +39,7 @@ using AGS::Shared::Stream;
 int     File_Exists(const char *fnmm);
 int     File_Delete(const char *fnmm);
 void *sc_OpenFile(const char *fnmm, int mode);
+const char *File_ResolvePath(const char *fnmm);
 void    File_Close(sc_File *fil);
 void    File_WriteString(sc_File *fil, const char *towrite);
 void    File_WriteInt(sc_File *fil, int towrite);
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 06088397265..11ef4b3a744 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -32,6 +32,7 @@
 #include "ags/engine/ac/display.h"
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/dynamic_sprite.h"
+#include "ags/engine/ac/file.h"
 #include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
@@ -747,6 +748,10 @@ IAGSFontRenderer *IAGSEngine::ReplaceFontRenderer(int fontNumber, IAGSFontRender
 	return old_render;
 }
 
+const char *IAGSEngine::ResolveFilePath(const char *script_path) {
+	return File_ResolvePath(script_path);
+}
+
 void IAGSEngine::GetRenderStageDesc(AGSRenderStageDesc *desc) {
 	if (desc->Version >= 25) {
 		_G(gfxDriver)->GetStageMatrixes((RenderMatrixes &)desc->Matrixes);
diff --git a/engines/ags/plugins/ags_plugin.h b/engines/ags/plugins/ags_plugin.h
index 17450c19927..0a8684b5be3 100644
--- a/engines/ags/plugins/ags_plugin.h
+++ b/engines/ags/plugins/ags_plugin.h
@@ -585,6 +585,10 @@ public:
 	AGSIFUNC(IAGSFontRenderer*) ReplaceFontRenderer2(int fontNumber, IAGSFontRenderer2* newRenderer);
 	// notify the engine that certain custom font has been updated
 	AGSIFUNC(void)  NotifyFontUpdated(int fontNumber);
+
+	// *** BELOW ARE INTERFACE VERSION 27 AND ABOVE ONLY
+	// Resolves a script path to a system filepath, same way as script command File.Open does.
+	AGSIFUNC(const char *)	ResolveFilePath(const char *script_path);
 };
 
 struct EnginePlugin {


Commit: 4ba37782f4a1b1e63100b1f6447130c329aab38a
    https://github.com/scummvm/scummvm/commit/4ba37782f4a1b1e63100b1f6447130c329aab38a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: implemented File_GetPath

>From upstream c68e2b02ebddcd33184270cd95b66eb34521e1d1

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


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 5afd762c3a7..22d0915de8f 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -235,6 +235,13 @@ int File_GetPosition(sc_File *fil) {
 	return (int)stream->GetPosition();
 }
 
+const char *File_GetPath(sc_File *fil) {
+	if (fil->handle <= 0)
+		return nullptr;
+	Stream *stream = get_valid_file_stream_from_handle(fil->handle, "File.Path");
+	return CreateNewScriptString(stream->GetPath());
+}
+
 //=============================================================================
 
 
@@ -720,6 +727,9 @@ RuntimeScriptValue Sc_File_GetPosition(void *self, const RuntimeScriptValue *par
 	API_OBJCALL_INT(sc_File, File_GetPosition);
 }
 
+RuntimeScriptValue Sc_File_GetPath(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_GetPath);
+}
 
 void RegisterFileAPI() {
 	ccAddExternalStaticFunction("File::Delete^1", Sc_File_Delete);
@@ -743,6 +753,7 @@ void RegisterFileAPI() {
 	ccAddExternalObjectFunction("File::get_EOF", Sc_File_GetEOF);
 	ccAddExternalObjectFunction("File::get_Error", Sc_File_GetError);
 	ccAddExternalObjectFunction("File::get_Position", Sc_File_GetPosition);
+	ccAddExternalObjectFunction("File::get_Path", Sc_File_GetPath);
 }
 
 } // namespace AGS3


Commit: 30769694793527c8968aa50a2751a25bc9de7987
    https://github.com/scummvm/scummvm/commit/30769694793527c8968aa50a2751a25bc9de7987
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.4)

Partially from upstream a3c9e81ff42dfc2fd0cd5d77bb3bb495a101cc38

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 2b095404f26..819adf9c2af 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.3"
+#define ACI_VERSION_STR      "3.6.1.4"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.3
+#define ACI_VERSION_MSRC_DEF  3.6.1.4
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 455caa2f8458a1c0a14e58ecd8032642f54ce89c
    https://github.com/scummvm/scummvm/commit/455caa2f8458a1c0a14e58ecd8032642f54ce89c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: some simplification for IAGSEngine::PlaySoundChannel()

partially from upstream c34ec5a2d4ab77e9e1a1ca10bbce97c6528c9b70

Changed paths:
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 11ef4b3a744..7412b840e52 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -502,12 +502,6 @@ void IAGSEngine::PlaySoundChannel(int32 channel, int32 soundType, int32 volume,
 		stop_voice_nonblocking();
 
 	SOUNDCLIP *newcha = nullptr;
-
-	if (((soundType == PSND_MP3STREAM) || (soundType == PSND_OGGSTREAM))
-	        && (loop != 0))
-		quit("IAGSEngine::PlaySoundChannel: streamed samples cannot loop");
-
-	// TODO: find out how engine was supposed to decide on where to load the sound from
 	AssetPath asset_name(filename, "audio");
 
 	switch (soundType) {


Commit: 0f437fb44aeaca1706328c45b3ff382468d79fcc
    https://github.com/scummvm/scummvm/commit/0f437fb44aeaca1706328c45b3ff382468d79fcc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplify free_dynamic_sprite(), also don't quit on bad index

>From upstream 38eaa36a2422327f6feebe497a44f5205eff7f95

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


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index bc87fe581bf..2fc97fa804e 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -460,21 +460,13 @@ void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha) {
 	_GP(game).SpriteInfos[gotSlot].Height = redin->GetHeight();
 }
 
-void free_dynamic_sprite(int gotSlot) {
+void free_dynamic_sprite(int slot) {
+	assert((slot > 0) && (_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC));
+	if (slot <= 0 || (_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC) == 0)
+		return;
 
-	if ((gotSlot < 0) || ((size_t)gotSlot >= _GP(spriteset).GetSpriteSlotCount()))
-		quit("!FreeDynamicSprite: invalid slot number");
-
-	if ((_GP(game).SpriteInfos[gotSlot].Flags & SPF_DYNAMICALLOC) == 0)
-		quitprintf("!DeleteSprite: Attempted to free static sprite %d that was not loaded by the script", gotSlot);
-
-	_GP(spriteset).DisposeSprite(gotSlot);
-
-	_GP(game).SpriteInfos[gotSlot].Flags = 0;
-	_GP(game).SpriteInfos[gotSlot].Width = 0;
-	_GP(game).SpriteInfos[gotSlot].Height = 0;
-
-	game_sprite_deleted(gotSlot);
+	_GP(spriteset).DisposeSprite(slot);
+	game_sprite_deleted(slot);
 }
 
 //=============================================================================


Commit: 48f18177e74ba5e7c0a58f2f5ff2214c911a18ff
    https://github.com/scummvm/scummvm/commit/48f18177e74ba5e7c0a58f2f5ff2214c911a18ff
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: picked out SetObjectFrameSimple that does not play frame sound

>From upstream 98ea0fe0c0b0d82d5caf9d085c8b6c04cdb28c38

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_object.h
    engines/ags/engine/ac/object.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 8a43d2ce16f..f68a5956897 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -498,10 +498,14 @@ void AssertView(const char *apiname, int view) {
 		quitprintf("!%s: invalid view %d (range is 1..%d)", apiname, view + 1, _GP(game).numviews);
 }
 
-void AssertLoop(const char *apiname, int view, int loop) {
+void AssertViewHasLoops(const char *apiname, int view) {
 	AssertView(apiname, view);
 	if (_GP(views)[view].numLoops == 0)
 		quitprintf("!%s: view %d does not have any loops.", apiname, view + 1);
+}
+
+void AssertLoop(const char *apiname, int view, int loop) {
+	AssertViewHasLoops(apiname, view);
 	if ((loop < 0) || (loop >= _GP(views)[view].numLoops))
 		quitprintf("!%s: invalid loop number %d for view %d (range is 0..%d).",
 				   apiname, loop, view + 1, _GP(views)[view].numLoops - 1);
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index a54db714360..f6e83df25e1 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -101,6 +101,7 @@ const char *Game_GetGlobalStrings(int index);
 // View, loop, frame parameter assertions.
 // WARNING: these functions assume that view is already in an internal 0-based range.
 void AssertView(const char *apiname, int view);
+void AssertViewHasLoops(const char *apiname, int view);
 void AssertLoop(const char *apiname, int view, int loop);
 void AssertFrame(const char *apiname, int view, int loop, int frame);
 
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index a904263be50..0d72daf0b89 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -27,6 +27,7 @@
 #include "ags/engine/ac/character.h"
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/event.h"
+#include "ags/engine/ac/game.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_character.h"
@@ -124,39 +125,18 @@ void RemoveObjectTint(int obj) {
 }
 
 void SetObjectView(int obn, int vii) {
-	if (!is_valid_object(obn)) quit("!SetObjectView: invalid object number specified");
+	// According to the old AGS manual, the loop and frame should be both reset to 0
+	SetObjectFrameSimple(obn, vii, 0, 0);
 	debug_script_log("Object %d set to view %d", obn, vii);
-	if ((vii < 1) || (vii > _GP(game).numviews)) {
-		quitprintf("!SetObjectView: invalid view number (You said %d, max is %d)", vii, _GP(game).numviews);
-	}
-	vii--;
-
-	if (vii > UINT16_MAX) {
-		debug_script_warn("Warning: object's (id %d) view %d is outside of internal range (%d), reset to no view",
-		                  obn, vii + 1, UINT16_MAX + 1);
-		SetObjectGraphic(obn, 0);
-		return;
-	}
-
-	_G(objs)[obn].view = (uint16_t)vii;
-	_G(objs)[obn].frame = 0;
-	if (_G(objs)[obn].loop >= _GP(views)[vii].numLoops)
-		_G(objs)[obn].loop = 0;
-	_G(objs)[obn].cycling = 0;
-	int pic = _GP(views)[vii].loops[0].frames[0].pic;
-	_G(objs)[obn].num = Math::InRangeOrDef<uint16_t>(pic, 0);
-	if (pic > UINT16_MAX)
-		debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0", obn, pic, UINT16_MAX);
 }
 
-void SetObjectFrame(int obn, int viw, int lop, int fra) {
-	if (!is_valid_object(obn)) quit("!SetObjectFrame: invalid object number specified");
+bool SetObjectFrameSimple(int obn, int viw, int lop, int fra) {
+	if (!is_valid_object(obn))
+		quitprintf("!SetObjectFrame: invalid object number specified (%d, range is 0 - %d)", obn, 0, _G(croom)->numobj);
 	viw--;
-	if (viw < 0 || viw >= _GP(game).numviews) quitprintf("!SetObjectFrame: invalid view number used (%d, range is 0 - %d)", viw, _GP(game).numviews - 1);
-	if (_GP(views)[viw].numLoops == 0) quitprintf("!SetObjectFrame: view %d has no loops", viw);
+	AssertViewHasLoops("SetObjectFrame", viw);
 
 	auto &obj = _G(objs)[obn];
-
 	// Previous version of Object.SetView had negative loop and frame mean "use latest values",
 	// which also caused SetObjectFrame to act similarly, starting with 2.70.
 	if ((_GP(game).options[OPT_BASESCRIPTAPI] < kScriptAPI_v360) && (_G(loaded_game_file_version) >= kGameVersion_270)) {
@@ -182,7 +162,7 @@ void SetObjectFrame(int obn, int viw, int lop, int fra) {
 		debug_script_warn("Warning: object's (id %d) view/loop/frame (%d/%d/%d) is outside of internal range (%d/%d/%d), reset to no view",
 			obn, viw + 1, lop, fra, UINT16_MAX + 1, UINT16_MAX, UINT16_MAX);
 		SetObjectGraphic(obn, 0);
-		return;
+		return false;
 	}
 
 	obj.view = viw;
@@ -193,7 +173,13 @@ void SetObjectFrame(int obn, int viw, int lop, int fra) {
 	obj.num = Math::InRangeOrDef<uint16_t>(pic, 0);
 	if (pic > UINT16_MAX)
 		debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0", obn, pic, UINT16_MAX);
-	CheckViewFrame(viw, obj.loop, obj.frame);
+	return true;
+}
+
+void SetObjectFrame(int obn, int viw, int lop, int fra) {
+	if (!SetObjectFrameSimple(obn, viw, lop, fra))
+		return;
+	CheckViewFrame(_G(objs)[obn].view, _G(objs)[obn].loop, _G(objs)[obn].frame);
 }
 
 // pass trans=0 for fully solid, trans=100 for fully transparent
diff --git a/engines/ags/engine/ac/global_object.h b/engines/ags/engine/ac/global_object.h
index 5be988bc22b..4fc3aa1e377 100644
--- a/engines/ags/engine/ac/global_object.h
+++ b/engines/ags/engine/ac/global_object.h
@@ -43,7 +43,10 @@ int  GetObjectIDAtRoom(int roomx, int roomy);
 void SetObjectTint(int obj, int red, int green, int blue, int opacity, int luminance);
 void RemoveObjectTint(int obj);
 void SetObjectView(int obn, int vii);
+// Assigns given object to the view's frame, and activates frame (plays linked sound, etc)
 void SetObjectFrame(int obn, int viw, int lop, int fra);
+// Assigns given object to the view's frame
+bool SetObjectFrameSimple(int obn, int viw, int lop, int fra);
 // pass trans=0 for fully solid, trans=100 for fully transparent
 void SetObjectTransparency(int obn, int trans);
 void SetObjectBaseline(int obn, int basel);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index d61ae020c15..78a97e62db0 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -95,7 +95,7 @@ void Object_RemoveTint(ScriptObject *objj) {
 }
 
 void Object_SetView(ScriptObject *objj, int view, int loop, int frame) {
-	SetObjectFrame(objj->id, view, loop, frame);
+	SetObjectFrameSimple(objj->id, view, loop, frame);
 }
 
 void Object_SetTransparency(ScriptObject *objj, int trans) {


Commit: dd8c13c1f0090021ed4223694d9c8088db40471d
    https://github.com/scummvm/scummvm/commit/dd8c13c1f0090021ed4223694d9c8088db40471d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: replaced global evblock* vars with a local ObjectEvent argument

>From upstream 279d3d78ff04423fce137a4907fd42d44ee12969

Changed paths:
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/event.h
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_hotspot.cpp
    engines/ags/engine/ac/global_inventory_item.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_region.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.h
    engines/ags/shared/gui/gui_object.h


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 14def7f39ea..260dc931a3b 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -92,21 +92,21 @@ void run_on_event(int evtype, RuntimeScriptValue &wparam) {
 }
 
 void run_room_event(int id) {
-	_G(evblockbasename) = "room";
+	auto obj_evt = ObjectEvent("room");
 
 	if (_GP(thisroom).EventHandlers != nullptr) {
-		run_interaction_script(_GP(thisroom).EventHandlers.get(), id);
+		run_interaction_script(obj_evt, _GP(thisroom).EventHandlers.get(), id);
 	} else {
-		run_interaction_event(&_G(croom)->intrRoom, id);
+		run_interaction_event(obj_evt, &_G(croom)->intrRoom, id);
 	}
 }
 
 void run_event_block_inv(int invNum, int event) {
-	_G(evblockbasename) = "inventory%d";
+	auto obj_evt = ObjectEvent("inventory%d", invNum);
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
-		run_interaction_script(_GP(game).invScripts[invNum].get(), event);
+		run_interaction_script(obj_evt, _GP(game).invScripts[invNum].get(), event);
 	} else {
-		run_interaction_event(_GP(game).intrInv[invNum].get(), event);
+		run_interaction_event(obj_evt, _GP(game).intrInv[invNum].get(), event);
 	}
 
 }
@@ -148,8 +148,7 @@ void process_event(const EventHappened *evp) {
 	} else if (evp->type == EV_RUNEVBLOCK) {
 		Interaction *evpt = nullptr;
 		PInteractionScripts scriptPtr = nullptr;
-		const char *oldbasename = _G(evblockbasename);
-		int   oldblocknum = _G(evblocknum);
+		ObjectEvent obj_evt;
 
 		if (evp->data1 == EVB_HOTSPOT) {
 
@@ -158,9 +157,8 @@ void process_event(const EventHappened *evp) {
 			else
 				evpt = &_G(croom)->intrHotspot[evp->data2];
 
-			_G(evblockbasename) = "hotspot%d";
-			_G(evblocknum) = evp->data2;
-			//Debug::Printf("Running hotspot interaction for hotspot %d, event %d", evp->data2, evp->data3);
+			obj_evt = ObjectEvent("hotspot%d", evp->data2);
+			// Debug::Printf("Running hotspot interaction for hotspot %d, event %d", evp->data2, evp->data3);
 		} else if (evp->data1 == EVB_ROOM) {
 
 			if (_GP(thisroom).EventHandlers != nullptr)
@@ -168,7 +166,7 @@ void process_event(const EventHappened *evp) {
 			else
 				evpt = &_G(croom)->intrRoom;
 
-			_G(evblockbasename) = "room";
+			obj_evt = ObjectEvent("room");
 			if (evp->data3 == EVROM_BEFOREFADEIN) {
 				_G(in_enters_screen)++;
 				run_on_event(GE_ENTER_ROOM, RuntimeScriptValue().SetInt32(_G(displayed_room)));
@@ -176,21 +174,20 @@ void process_event(const EventHappened *evp) {
 				run_on_event(GE_ENTER_ROOM_AFTERFADE, RuntimeScriptValue().SetInt32(_G(displayed_room)));
 			}
 			//Debug::Printf("Running room interaction, event %d", evp->data3);
+		} else {
+			quit("process_event: RunEvBlock: unknown evb type");
 		}
 
+		assert(scriptPtr || evpt);
 		if (scriptPtr != nullptr) {
-			run_interaction_script(scriptPtr.get(), evp->data3);
-		} else if (evpt != nullptr) {
-			run_interaction_event(evpt, evp->data3);
-		} else
-			quit("process_event: RunEvBlock: unknown evb type");
+			run_interaction_script(obj_evt, scriptPtr.get(), evp->data3);
+		} else {
+			run_interaction_event(obj_evt, evpt, evp->data3);
+		}
 
 		if (_G(abort_engine))
 			return;
 
-		_G(evblockbasename) = oldbasename;
-		_G(evblocknum) = oldblocknum;
-
 		if ((evp->data1 == EVB_ROOM) && (evp->data3 == EVROM_BEFOREFADEIN))
 			_G(in_enters_screen)--;
 	} else if (evp->type == EV_FADEIN) {
diff --git a/engines/ags/engine/ac/event.h b/engines/ags/engine/ac/event.h
index dc34219eab5..570152f2ffc 100644
--- a/engines/ags/engine/ac/event.h
+++ b/engines/ags/engine/ac/event.h
@@ -96,6 +96,10 @@ struct EventHappened {
 	int type = 0;
 	int data1 = 0, data2 = 0, data3 = 0;
 	int player = -1;
+
+	EventHappened() = default;
+	EventHappened(int type_, int data1_, int data2_, int data3_, int player_)
+		: type(type_), data1(data1_), data2(data2_), data3(data3_), player(player_) {}
 };
 
 int run_claimable_event(const char *tsname, bool includeRoom, int numParams, const RuntimeScriptValue *params, bool *eventWasClaimed);
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index d726c2a07ff..7722dc85d71 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -380,16 +380,15 @@ void RunCharacterInteraction(int cc, int mood) {
 	else if (mood == MODE_CUSTOM1) passon = 6;
 	else if (mood == MODE_CUSTOM2) passon = 7;
 
-	_G(evblockbasename) = "character%d";
-	_G(evblocknum) = cc;
+	const auto obj_evt = ObjectEvent("character%d", cc);
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		if (passon >= 0)
-			run_interaction_script(_GP(game).charScripts[cc].get(), passon, 4);
-		run_interaction_script(_GP(game).charScripts[cc].get(), 4);  // any click on char
+			run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), passon, 4);
+		run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), 4);  // any click on char
 	} else {
 		if (passon >= 0)
-			run_interaction_event(_GP(game).intrChar[cc].get(), passon, 4, (passon == 3));
-		run_interaction_event(_GP(game).intrChar[cc].get(), 4);  // any click on char
+			run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), passon, 4, (passon == 3));
+		run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), 4);  // any click on char
 	}
 }
 
diff --git a/engines/ags/engine/ac/global_hotspot.cpp b/engines/ags/engine/ac/global_hotspot.cpp
index d76de2e3e65..6a47492260e 100644
--- a/engines/ags/engine/ac/global_hotspot.cpp
+++ b/engines/ags/engine/ac/global_hotspot.cpp
@@ -112,30 +112,21 @@ void RunHotspotInteraction(int hotspothere, int mood) {
 
 	// can't use the setevent functions because this ProcessClick is only
 	// executed once in a eventlist
-	const char *oldbasename = _G(evblockbasename);
-	int   oldblocknum = _G(evblocknum);
-
-	_G(evblockbasename) = "hotspot%d";
-	_G(evblocknum) = hotspothere;
+	const auto obj_evt = ObjectEvent("hotspot%d", hotspothere);
 
 	if (_GP(thisroom).Hotspots[hotspothere].EventHandlers != nullptr) {
 		if (passon >= 0)
-			run_interaction_script(_GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), passon, 5);
-		run_interaction_script(_GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), 5);  // any click on hotspot
+			run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), passon, 5);
+		run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), 5);  // any click on hotspot
 	} else {
 		if (passon >= 0) {
-			if (run_interaction_event(&_G(croom)->intrHotspot[hotspothere], passon, 5, (passon == 3))) {
-				_G(evblockbasename) = oldbasename;
-				_G(evblocknum) = oldblocknum;
+			if (run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], passon, 5, (passon == 3))) {
 				return;
 			}
 		}
 		// run the 'any click on hs' event
-		run_interaction_event(&_G(croom)->intrHotspot[hotspothere], 5);
+		run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], 5);
 	}
-
-	_G(evblockbasename) = oldbasename;
-	_G(evblocknum) = oldblocknum;
 }
 
 int GetHotspotProperty(int hss, const char *property) {
diff --git a/engines/ags/engine/ac/global_inventory_item.cpp b/engines/ags/engine/ac/global_inventory_item.cpp
index 134377cccb6..c46fecc81ab 100644
--- a/engines/ags/engine/ac/global_inventory_item.cpp
+++ b/engines/ags/engine/ac/global_inventory_item.cpp
@@ -95,7 +95,6 @@ void RunInventoryInteraction(int iit, int modd) {
 	if ((iit < 0) || (iit >= _GP(game).numinvitems))
 		quit("!RunInventoryInteraction: invalid inventory number");
 
-	_G(evblocknum) = iit;
 	if (modd == MODE_LOOK)
 		run_event_block_inv(iit, 0);
 	else if (modd == MODE_HAND)
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 0d72daf0b89..9b122a69940 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -420,21 +420,20 @@ void RunObjectInteraction(int aa, int mood) {
 		cdata = _G(playerchar)->activeinv;
 		_GP(play).usedinv = cdata;
 	}
-	_G(evblockbasename) = "object%d";
-	_G(evblocknum) = aa;
 
+	const auto obj_evt = ObjectEvent("object%d", aa);
 	if (_GP(thisroom).Objects[aa].EventHandlers != nullptr) {
 		if (passon >= 0) {
-			if (run_interaction_script(_GP(thisroom).Objects[aa].EventHandlers.get(), passon, 4))
+			if (run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), passon, 4))
 				return;
 		}
-		run_interaction_script(_GP(thisroom).Objects[aa].EventHandlers.get(), 4);  // any click on obj
+		run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), 4);  // any click on obj
 	} else {
 		if (passon >= 0) {
-			if (run_interaction_event(&_G(croom)->intrObject[aa], passon, 4, (passon == 3)))
+			if (run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], passon, 4, (passon == 3)))
 				return;
 		}
-		run_interaction_event(&_G(croom)->intrObject[aa], 4);  // any click on obj
+		run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], 4);  // any click on obj
 	}
 }
 
diff --git a/engines/ags/engine/ac/global_region.cpp b/engines/ags/engine/ac/global_region.cpp
index 2640b4990fa..f1722136070 100644
--- a/engines/ags/engine/ac/global_region.cpp
+++ b/engines/ags/engine/ac/global_region.cpp
@@ -143,24 +143,12 @@ void RunRegionInteraction(int regnum, int mood) {
 	if ((mood < 0) || (mood > 2))
 		quit("!RunRegionInteraction: invalid event specified");
 
-	// We need a backup, because region interactions can run
-	// while another interaction (eg. hotspot) is in a Wait
-	// command, and leaving our basename would call the wrong
-	// script later on
-	const char *oldbasename = _G(evblockbasename);
-	int   oldblocknum = _G(evblocknum);
-
-	_G(evblockbasename) = "region%d";
-	_G(evblocknum) = regnum;
-
+	const auto obj_evt = ObjectEvent("region%d", regnum);
 	if (_GP(thisroom).Regions[regnum].EventHandlers != nullptr) {
-		run_interaction_script(_GP(thisroom).Regions[regnum].EventHandlers.get(), mood);
+		run_interaction_script(obj_evt, _GP(thisroom).Regions[regnum].EventHandlers.get(), mood);
 	} else {
-		run_interaction_event(&_G(croom)->intrRegion[regnum], mood);
+		run_interaction_event(obj_evt, &_G(croom)->intrRegion[regnum], mood);
 	}
-
-	_G(evblockbasename) = oldbasename;
-	_G(evblocknum) = oldblocknum;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 3551254a0af..a0f7d9c7437 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -567,7 +567,6 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 			_G(mouse_ifacebut_yoffs) = _G(mousey) - (guio->Y) - _GP(guis)[wasongui].Y;
 			int iit = offset_over_inv((GUIInvWindow *)guio);
 			if (iit >= 0) {
-				_G(evblocknum) = iit;
 				_GP(play).used_inv_on = iit;
 				if (_GP(game).options[OPT_HANDLEINVCLICKS]) {
 					// Let the script handle the click
@@ -579,7 +578,6 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 					SetActiveInventory(iit);
 				else
 					RunInventoryInteraction(iit, _G(cur_mode));
-				_G(evblocknum) = -1;
 			}
 		} else quit("clicked on unknown control type");
 		if (_GP(guis)[wasongui].PopupStyle == kGUIPopupMouseY)
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index e67bac48876..335c75e80e4 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -383,7 +383,6 @@ bool InventoryScreen::Run() {
 		if (my < buttonyp) {
 			int clickedon = isonitem;
 			if (clickedon < 0) return true; // continue inventory screen loop
-			_G(evblocknum) = dii[clickedon].num;
 			_GP(play).used_inv_on = dii[clickedon].num;
 
 			if (cmode == MODE_LOOK) {
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 6b7c7439855..885465a6e17 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -950,12 +950,7 @@ void first_room_initialization() {
 void check_new_room() {
 	// if they're in a new room, run Player Enters Screen and on_event(ENTER_ROOM)
 	if ((_G(in_new_room) > 0) & (_G(in_new_room) != 3)) {
-		EventHappened evh;
-		evh.type = EV_RUNEVBLOCK;
-		evh.data1 = EVB_ROOM;
-		evh.data2 = 0;
-		evh.data3 = EVROM_BEFOREFADEIN;
-		evh.player = _GP(game).playercharacter;
+		EventHappened evh(EV_RUNEVBLOCK, EVB_ROOM, 0, EVROM_BEFOREFADEIN, _GP(game).playercharacter);
 		// make sure that any script calls don't re-call enters screen
 		int newroom_was = _G(in_new_room);
 		_G(in_new_room) = 0;
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 86f72b59988..6396659e909 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -117,7 +117,7 @@ void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) {
 // Returns 0 normally, or -1 to indicate that the NewInteraction has
 // become invalid and don't run another interaction on it
 // (eg. a room change occurred)
-int run_interaction_event(Interaction *nint, int evnt, int chkAny, int isInv) {
+int run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny, int isInv) {
 
 	if (evnt < 0 || (size_t)evnt >= nint->Events.size() ||
 	        (nint->Events[evnt].Response.get() == nullptr) || (nint->Events[evnt].Response->Cmds.size() == 0)) {
@@ -130,7 +130,7 @@ int run_interaction_event(Interaction *nint, int evnt, int chkAny, int isInv) {
 			return 0;
 
 		// Otherwise, run unhandled_event
-		run_unhandled_event(evnt);
+		run_unhandled_event(obj_evt, evnt);
 
 		return 0;
 	}
@@ -142,14 +142,14 @@ int run_interaction_event(Interaction *nint, int evnt, int chkAny, int isInv) {
 
 	int cmdsrun = 0, retval = 0;
 	// Right, so there were some commands defined in response to the event.
-	retval = run_interaction_commandlist(nint->Events[evnt].Response.get(), &nint->Events[evnt].TimesRun, &cmdsrun);
+	retval = run_interaction_commandlist(obj_evt, nint->Events[evnt].Response.get(), &nint->Events[evnt].TimesRun, &cmdsrun);
 
 	if (_G(abort_engine))
 		return -1;
 
 	// An inventory interaction, but the wrong item was used
 	if ((isInv) && (cmdsrun == 0))
-		run_unhandled_event(evnt);
+		run_unhandled_event(obj_evt, evnt);
 
 	return retval;
 }
@@ -157,7 +157,7 @@ int run_interaction_event(Interaction *nint, int evnt, int chkAny, int isInv) {
 // Returns 0 normally, or -1 to indicate that the NewInteraction has
 // become invalid and don't run another interaction on it
 // (eg. a room change occurred)
-int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny) {
+int run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint, int evnt, int chkAny) {
 
 	if (((int)nint->ScriptFuncNames.size() <= evnt) || nint->ScriptFuncNames[evnt].IsEmpty()) {
 		// no response defined for this event
@@ -168,21 +168,19 @@ int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny) {
 			return 0;
 
 		// Otherwise, run unhandled_event
-		run_unhandled_event(evnt);
-
+		run_unhandled_event(obj_evt, evnt);
 		return 0;
 	}
 
 	if (_GP(play).check_interaction_only) {
-		_GP(play).check_interaction_only = 2;
+		_GP(play).check_interaction_only = 2;  // CHECKME: wth is "2"?
 		return -1;
 	}
 
 	int room_was = _GP(play).room_changes;
 
-	RuntimeScriptValue rval_null;
-
-	if ((strstr(_G(evblockbasename), "character") != nullptr) || (strstr(_G(evblockbasename), "inventory") != nullptr)) {
+	if ((strstr(obj_evt.BlockName.GetCStr(), "character") != nullptr) ||
+		(strstr(obj_evt.BlockName.GetCStr(), "inventory") != nullptr)) {
 		// Character or Inventory (global script)
 		QueueScriptFunction(kScInstGame, nint->ScriptFuncNames[evnt].GetCStr());
 	} else {
@@ -190,12 +188,10 @@ int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny) {
 		QueueScriptFunction(kScInstRoom, nint->ScriptFuncNames[evnt].GetCStr());
 	}
 
-	int retval = 0;
 	// if the room changed within the action
 	if (room_was != _GP(play).room_changes)
-		retval = -1;
-
-	return retval;
+		return -1;
+	return 0;
 }
 
 int create_global_script() {
@@ -641,13 +637,13 @@ struct TempEip {
 // the 'cmdsrun' parameter counts how many commands are run.
 // if a 'Inv Item Was Used' check does not pass, it doesn't count
 // so cmdsrun remains 0 if no inventory items matched
-int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int *cmdsrun) {
-	size_t i;
-
+int run_interaction_commandlist(const ObjectEvent &obj_evt, InteractionCommandList *nicl, int *timesrun, int *cmdsrun) {
 	if (nicl == nullptr)
 		return -1;
 
-	for (i = 0; i < nicl->Cmds.size(); i++) {
+	const char *evblockbasename = obj_evt.BlockName.GetCStr();
+	const int evblocknum = obj_evt.BlockID;
+	for (size_t i = 0; i < nicl->Cmds.size(); i++) {
 		cmdsrun[0] ++;
 		int room_was = _GP(play).room_changes;
 
@@ -657,14 +653,14 @@ int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int
 		case 1: { // Run script
 			TempEip tempip(4001);
 			RuntimeScriptValue rval_null;
-			if ((strstr(_G(evblockbasename), "character") != nullptr) || (strstr(_G(evblockbasename), "inventory") != nullptr)) {
+			if ((strstr(evblockbasename, "character") != nullptr) || (strstr(evblockbasename, "inventory") != nullptr)) {
 				// Character or Inventory (global script)
-				const char *torun = make_ts_func_name(_G(evblockbasename), _G(evblocknum), nicl->Cmds[i].Data[0].Value);
+				const char *torun = make_ts_func_name(evblockbasename, evblocknum, nicl->Cmds[i].Data[0].Value);
 				// we are already inside the mouseclick event of the script, can't nest calls
 				QueueScriptFunction(kScInstGame, torun);
 			} else {
 				// Other (room script)
-				const char *torun = make_ts_func_name(_G(evblockbasename), _G(evblocknum), nicl->Cmds[i].Data[0].Value);
+				const char *torun = make_ts_func_name(evblockbasename, evblocknum, nicl->Cmds[i].Data[0].Value);
 				QueueScriptFunction(kScInstRoom, torun);
 			}
 			break;
@@ -742,24 +738,24 @@ int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int
 			if (_GP(play).usedinv == IPARAM1) {
 				if (_GP(game).options[OPT_NOLOSEINV] == 0)
 					lose_inventory(_GP(play).usedinv);
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			} else
 				cmdsrun[0] --;
 			break;
 		case 21: // if player has inventory item
 			if (_G(playerchar)->inv[IPARAM1] > 0)
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		case 22: // if a character is moving
 			if (_GP(game).chars[IPARAM1].walking)
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		case 23: // if two variables are equal
 			if (IPARAM1 == IPARAM2)
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		case 24: // Stop character walking
@@ -832,17 +828,17 @@ int run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int
 			break;
 		case 45: // If player character is
 			if (GetPlayerCharacter() == IPARAM1)
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		case 46: // if cursor mode is
 			if (GetCursorMode() == IPARAM1)
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		case 47: // if player has been to room
 			if (HasBeenToRoom(IPARAM1))
-				if (run_interaction_commandlist(nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
+				if (run_interaction_commandlist(obj_evt, nicl->Cmds[i].Children.get(), timesrun, cmdsrun))
 					return -1;
 			break;
 		default:
@@ -868,21 +864,24 @@ void can_run_delayed_command() {
 		quit("!This command cannot be used within non-blocking events such as " REP_EXEC_ALWAYS_NAME);
 }
 
-void run_unhandled_event(int evnt) {
+void run_unhandled_event(const ObjectEvent &obj_evt, int evnt) {
 
 	if (_GP(play).check_interaction_only)
 		return;
 
+	const char *evblockbasename = obj_evt.BlockName.GetCStr();
+	const int evblocknum = obj_evt.BlockID;
 	int evtype = 0;
-	if (ags_strnicmp(_G(evblockbasename), "hotspot", 7) == 0) evtype = 1;
-	else if (ags_strnicmp(_G(evblockbasename), "object", 6) == 0) evtype = 2;
-	else if (ags_strnicmp(_G(evblockbasename), "character", 9) == 0) evtype = 3;
-	else if (ags_strnicmp(_G(evblockbasename), "inventory", 9) == 0) evtype = 5;
-	else if (ags_strnicmp(_G(evblockbasename), "region", 6) == 0)
+
+	if (ags_strnicmp(evblockbasename, "hotspot", 7) == 0) evtype = 1;
+	else if (ags_strnicmp(evblockbasename, "object", 6) == 0) evtype = 2;
+	else if (ags_strnicmp(evblockbasename, "character", 9) == 0) evtype = 3;
+	else if (ags_strnicmp(evblockbasename, "inventory", 9) == 0) evtype = 5;
+	else if (ags_strnicmp(evblockbasename, "region", 6) == 0)
 		return;  // no unhandled_events for regions
 
 	// clicked Hotspot 0, so change the type code
-	if ((evtype == 1) & (_G(evblocknum) == 0) & (evnt != 0) & (evnt != 5) & (evnt != 6))
+	if ((evtype == 1) & (evblocknum == 0) & (evnt != 0) & (evnt != 5) & (evnt != 6))
 		evtype = 4;
 	if ((evtype == 1) & ((evnt == 0) | (evnt == 5) | (evnt == 6)))
 		;  // character stands on hotspot, mouse moves over hotspot, any click
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index 01b6edff922..079f8ca7b04 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -33,6 +33,7 @@
 
 namespace AGS3 {
 
+using AGS::Shared::String;
 using AGS::Shared::Interaction;
 using AGS::Shared::InteractionCommandList;
 using AGS::Shared::InteractionScripts;
@@ -42,10 +43,31 @@ using AGS::Shared::InteractionVariable;
 #define REP_EXEC_ALWAYS_NAME "repeatedly_execute_always"
 #define REP_EXEC_NAME "repeatedly_execute"
 
+// ObjectEvent - a struct holds data of the object's interaction event,
+// such as object's reference and accompanying parameters
+struct ObjectEvent {
+	// Name of the script block to run, may be used as a formatting string;
+	// has a form of "objecttype%d"
+	String BlockName;
+	// Script block's ID, commonly corresponds to the object's ID
+	int BlockID = 0;
+
+	ObjectEvent() = default;
+	ObjectEvent(const String &block_name, int block_id = 0)
+		: BlockName(block_name), BlockID(block_id) {}
+};
+
 int     run_dialog_request(int parmtr);
 void    run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun);
-int     run_interaction_event(Interaction *nint, int evnt, int chkAny = -1, int isInv = 0);
-int     run_interaction_script(InteractionScripts *nint, int evnt, int chkAny = -1);
+// Runs the ObjectEvent using an old interaction callback type of 'evnt' index,
+// or alternatively of 'chkAny' index, if previous does not exist;
+// 'isInv' tells if this is a inventory event (it has a slightly different handling for that)
+int     run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny = -1, int isInv = 0);
+// Runs the ObjectEvent using a script callback of 'evnt' index,
+// or alternatively of 'chkAny' index, if previous does not exist
+int     run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint, int evnt, int chkAny = -1);
+int     run_interaction_commandlist(const ObjectEvent &obj_evt, InteractionCommandList *nicl, int *timesrun, int *cmdsrun);
+void    run_unhandled_event(const ObjectEvent &obj_evt, int evnt);
 int     create_global_script();
 void    cancel_all_scripts();
 
@@ -68,7 +90,7 @@ int     RunScriptFunctionInRoom(const char *tsname, size_t param_count = 0,
 int     RunScriptFunctionAuto(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 
-AGS::Shared::String GetScriptName(ccInstance *sci);
+String  GetScriptName(ccInstance *sci);
 
 //=============================================================================
 
@@ -79,15 +101,13 @@ char *make_ts_func_name(const char *base, int iii, int subd);
 void    post_script_cleanup();
 void    quit_with_script_error(const char *functionName);
 int     get_nivalue(InteractionCommandList *nic, int idx, int parm);
-int     run_interaction_commandlist(InteractionCommandList *nicl, int *timesrun, int *cmdsrun);
 InteractionVariable *get_interaction_variable(int varindx);
 InteractionVariable *FindGraphicalVariable(const char *varName);
-void    run_unhandled_event(int evnt);
 void    can_run_delayed_command();
 
 // Gets current running script position
 bool    get_script_position(ScriptPosition &script_pos);
-AGS::Shared::String cc_get_callstack(int max_lines = INT_MAX);
+String  cc_get_callstack(int max_lines = INT_MAX);
 
 } // namespace AGS3
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 1eb0c149f58..2d2cd28785a 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -688,9 +688,6 @@ public:
 
 	std::vector<EventHappened> *_events;
 
-	const char *_evblockbasename = nullptr;
-	int _evblocknum = 0;
-
 	int _inside_processevent = 0;
 	int _eventClaimed = 0;
 
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 0996dd9e9b1..7bcf6b9d5d6 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -140,6 +140,7 @@ protected:
 	bool     _hasChanged;
 
 	// TODO: explicit event names & handlers for every event
+    // FIXME: these must be static!! per type
 	int32_t  _scEventCount;                    // number of supported script events
 	String   _scEventNames[MAX_GUIOBJ_EVENTS]; // script event names
 	String   _scEventArgs[MAX_GUIOBJ_EVENTS];  // script handler params


Commit: 51cf1f15686042f642add544594bed215007962b
    https://github.com/scummvm/scummvm/commit/51cf1f15686042f642add544594bed215007962b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidied code in RunObjectInteraction functions

>From upstream c20764239b3a8a234c473feb4f7009afdc3c4c6d

Changed paths:
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/event.h
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_hotspot.cpp
    engines/ags/engine/ac/global_inventory_item.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_region.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 260dc931a3b..f308b4242da 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -101,16 +101,6 @@ void run_room_event(int id) {
 	}
 }
 
-void run_event_block_inv(int invNum, int event) {
-	auto obj_evt = ObjectEvent("inventory%d", invNum);
-	if (_G(loaded_game_file_version) > kGameVersion_272) {
-		run_interaction_script(obj_evt, _GP(game).invScripts[invNum].get(), event);
-	} else {
-		run_interaction_event(obj_evt, _GP(game).intrInv[invNum].get(), event);
-	}
-
-}
-
 // event list functions
 void setevent(int evtyp, int ev1, int ev2, int ev3) {
 	EventHappened evt;
diff --git a/engines/ags/engine/ac/event.h b/engines/ags/engine/ac/event.h
index 570152f2ffc..18aca337e27 100644
--- a/engines/ags/engine/ac/event.h
+++ b/engines/ags/engine/ac/event.h
@@ -106,7 +106,6 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 // runs the global script on_event fnuction
 void run_on_event(int evtype, RuntimeScriptValue &wparam);
 void run_room_event(int id);
-void run_event_block_inv(int invNum, int event);
 // event list functions
 void setevent(int evtyp, int ev1 = 0, int ev2 = -1000, int ev3 = -1000);
 void force_event(int evtyp, int ev1 = 0, int ev2 = -1000, int ev3 = -1000);
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 7722dc85d71..a07df2140b5 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -368,27 +368,37 @@ void RunCharacterInteraction(int cc, int mood) {
 	if (!is_valid_character(cc))
 		quit("!RunCharacterInteraction: invalid character");
 
-	int passon = -1, cdata = -1;
-	if (mood == MODE_LOOK) passon = 0;
-	else if (mood == MODE_HAND) passon = 1;
-	else if (mood == MODE_TALK) passon = 2;
-	else if (mood == MODE_USE) {
-		passon = 3;
-		cdata = _G(playerchar)->activeinv;
-		_GP(play).usedinv = cdata;
-	} else if (mood == MODE_PICKUP) passon = 5;
-	else if (mood == MODE_CUSTOM1) passon = 6;
-	else if (mood == MODE_CUSTOM2) passon = 7;
+	// convert cursor mode to event index (in character event table)
+	// TODO: probably move this conversion table elsewhere? should be a global info
+	int evnt;
+	switch (mood) {
+		case MODE_LOOK:	evnt = 0; break;
+		case MODE_HAND:	evnt = 1; break;
+		case MODE_TALK:	evnt = 2; break;
+		case MODE_USE: evnt = 3; break;
+		case MODE_PICKUP: evnt = 5;	break;
+		case MODE_CUSTOM1: evnt = 6; break;
+		case MODE_CUSTOM2: evnt = 7; break;
+		default: evnt = -1;	break;
+	}
+	const int anyclick_evt = 4; // TODO: make global constant (character any-click evt)
+
+	// For USE verb: remember active inventory
+	if (mood == MODE_USE) {
+		_GP(play).usedinv = _G(playerchar)->activeinv;
+	}
 
 	const auto obj_evt = ObjectEvent("character%d", cc);
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
-		if (passon >= 0)
-			run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), passon, 4);
-		run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), 4);  // any click on char
+		if ((evnt >= 0) &&
+			run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), evnt, anyclick_evt) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), anyclick_evt); // any click on char
 	} else {
-		if (passon >= 0)
-			run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), passon, 4, (passon == 3));
-		run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), 4);  // any click on char
+		if ((evnt >= 0) &&
+			run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), evnt, anyclick_evt, (mood == MODE_USE)) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), anyclick_evt); // any click on char
 	}
 }
 
diff --git a/engines/ags/engine/ac/global_hotspot.cpp b/engines/ags/engine/ac/global_hotspot.cpp
index 6a47492260e..05245976cfe 100644
--- a/engines/ags/engine/ac/global_hotspot.cpp
+++ b/engines/ags/engine/ac/global_hotspot.cpp
@@ -91,18 +91,26 @@ void GetHotspotName(int hotspot, char *buffer) {
 
 void RunHotspotInteraction(int hotspothere, int mood) {
 
-	int passon = -1, cdata = -1;
-	if (mood == MODE_TALK) passon = 4;
-	else if (mood == MODE_WALK) passon = 0;
-	else if (mood == MODE_LOOK) passon = 1;
-	else if (mood == MODE_HAND) passon = 2;
-	else if (mood == MODE_PICKUP) passon = 7;
-	else if (mood == MODE_CUSTOM1) passon = 8;
-	else if (mood == MODE_CUSTOM2) passon = 9;
-	else if (mood == MODE_USE) {
-		passon = 3;
-		cdata = _G(playerchar)->activeinv;
-		_GP(play).usedinv = cdata;
+	// convert cursor mode to event index (in hotspot event table)
+	// TODO: probably move this conversion table elsewhere? should be a global info
+	// TODO: find out what is hotspot event with index 6 (5 is any-click)
+	int evnt;
+	switch (mood) {
+		case MODE_WALK:	evnt = 0; break;
+		case MODE_LOOK:	evnt = 1; break;
+		case MODE_HAND:	evnt = 2; break;
+		case MODE_TALK:	evnt = 4; break;
+		case MODE_USE: evnt = 3; break;
+		case MODE_PICKUP: evnt = 7; break;
+		case MODE_CUSTOM1: evnt = 8; break;
+		case MODE_CUSTOM2: evnt = 9; break;
+		default: evnt = -1; break;
+	}
+	const int anyclick_evt = 5; // TODO: make global constant (hotspot any-click evt)
+
+	// For USE verb: remember active inventory
+	if (mood == MODE_USE) {
+		_GP(play).usedinv = _G(playerchar)->activeinv;
 	}
 
 	if ((_GP(game).options[OPT_WALKONLOOK] == 0) & (mood == MODE_LOOK));
@@ -114,18 +122,16 @@ void RunHotspotInteraction(int hotspothere, int mood) {
 	// executed once in a eventlist
 	const auto obj_evt = ObjectEvent("hotspot%d", hotspothere);
 
-	if (_GP(thisroom).Hotspots[hotspothere].EventHandlers != nullptr) {
-		if (passon >= 0)
-			run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), passon, 5);
-		run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), 5);  // any click on hotspot
+	if (_G(loaded_game_file_version) > kGameVersion_272) {
+		if ((evnt >= 0) &&
+			run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), evnt, anyclick_evt) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_script(obj_evt, _GP(thisroom).Hotspots[hotspothere].EventHandlers.get(), anyclick_evt); // any click on hotspot
 	} else {
-		if (passon >= 0) {
-			if (run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], passon, 5, (passon == 3))) {
-				return;
-			}
-		}
-		// run the 'any click on hs' event
-		run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], 5);
+		if ((evnt >= 0) &&
+			run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], evnt, anyclick_evt, (mood == MODE_USE)) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_event(obj_evt, &_G(croom)->intrHotspot[hotspothere], anyclick_evt); // any click on hotspot
 	}
 }
 
diff --git a/engines/ags/engine/ac/global_inventory_item.cpp b/engines/ags/engine/ac/global_inventory_item.cpp
index c46fecc81ab..2d5c19fe8da 100644
--- a/engines/ags/engine/ac/global_inventory_item.cpp
+++ b/engines/ags/engine/ac/global_inventory_item.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "ags/shared/ac/common.h"
+#include "ags/engine/ac/event.h"
 #include "ags/shared/ac/game_setup_struct.h"
+#include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_gui.h"
 #include "ags/engine/ac/global_inventory_item.h"
 #include "ags/engine/ac/global_translation.h"
@@ -30,8 +32,7 @@
 #include "ags/engine/ac/string.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_inv.h"
-#include "ags/engine/ac/event.h"
-#include "ags/engine/ac/game_state.h"
+#include "ags/engine/script/script.h"
 
 namespace AGS3 {
 
@@ -91,21 +92,36 @@ int GetInvGraphic(int indx) {
 	return _GP(game).invinfo[indx].pic;
 }
 
-void RunInventoryInteraction(int iit, int modd) {
+void RunInventoryInteraction(int iit, int mood) {
 	if ((iit < 0) || (iit >= _GP(game).numinvitems))
 		quit("!RunInventoryInteraction: invalid inventory number");
 
-	if (modd == MODE_LOOK)
-		run_event_block_inv(iit, 0);
-	else if (modd == MODE_HAND)
-		run_event_block_inv(iit, 1);
-	else if (modd == MODE_USE) {
+	// convert cursor mode to event index (in inventoryitem event table)
+	// TODO: probably move this conversion table elsewhere? should be a global info
+	int evnt;
+	switch (mood) {
+		case MODE_LOOK:	evnt = 0; break;
+		case MODE_HAND:	evnt = 1; break;
+		case MODE_TALK:	evnt = 2; break;
+		case MODE_USE: evnt = 3; break;
+		default: evnt = -1; break;
+	}
+	const int otherclick_evt = 4; // TODO: make global constant (inventory other-click evt)
+
+	// For USE verb: remember active inventory
+	if (mood == MODE_USE) {
 		_GP(play).usedinv = _G(playerchar)->activeinv;
-		run_event_block_inv(iit, 3);
-	} else if (modd == MODE_TALK)
-		run_event_block_inv(iit, 2);
-	else // other click on inventory
-		run_event_block_inv(iit, 4);
+	}
+
+	if (evnt < 0) // on any non-supported mode - use "other-click"
+		evnt = otherclick_evt;
+
+	auto obj_evt = ObjectEvent("inventory%d", iit);
+	if (_G(loaded_game_file_version) > kGameVersion_272) {
+		run_interaction_script(obj_evt, _GP(game).invScripts[iit].get(), evnt);
+	} else {
+		run_interaction_event(obj_evt, _GP(game).intrInv[iit].get(), evnt);
+	}
 }
 
 int IsInventoryInteractionAvailable(int item, int mood) {
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 9b122a69940..c00ad0c641f 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -408,32 +408,38 @@ void SetObjectIgnoreWalkbehinds(int cha, int clik) {
 void RunObjectInteraction(int aa, int mood) {
 	if (!is_valid_object(aa))
 		quit("!RunObjectInteraction: invalid object number for current room");
-	int passon = -1, cdata = -1;
-	if (mood == MODE_LOOK) passon = 0;
-	else if (mood == MODE_HAND) passon = 1;
-	else if (mood == MODE_TALK) passon = 2;
-	else if (mood == MODE_PICKUP) passon = 5;
-	else if (mood == MODE_CUSTOM1) passon = 6;
-	else if (mood == MODE_CUSTOM2) passon = 7;
-	else if (mood == MODE_USE) {
-		passon = 3;
-		cdata = _G(playerchar)->activeinv;
-		_GP(play).usedinv = cdata;
+
+	// convert cursor mode to event index (in character event table)
+	// TODO: probably move this conversion table elsewhere? should be a global info
+	int evnt;
+	switch (mood) {
+		case MODE_LOOK:	evnt = 0; break;
+		case MODE_HAND:	evnt = 1; break;
+		case MODE_TALK:	evnt = 2; break;
+		case MODE_USE: evnt = 3; break;
+		case MODE_PICKUP: evnt = 5;	break;
+		case MODE_CUSTOM1: evnt = 6; break;
+		case MODE_CUSTOM2: evnt = 7; break;
+		default: evnt = -1;	break;
+	}
+	const int anyclick_evt = 4; // TODO: make global constant (character any-click evt)
+
+	// For USE verb: remember active inventory
+	if (mood == MODE_USE) {
+		_GP(play).usedinv = _G(playerchar)->activeinv;
 	}
 
 	const auto obj_evt = ObjectEvent("object%d", aa);
-	if (_GP(thisroom).Objects[aa].EventHandlers != nullptr) {
-		if (passon >= 0) {
-			if (run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), passon, 4))
-				return;
-		}
-		run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), 4);  // any click on obj
+	if (_G(loaded_game_file_version) > kGameVersion_272) {
+		if ((evnt >= 0) &&
+			run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), evnt, anyclick_evt) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), anyclick_evt); // any click on obj
 	} else {
-		if (passon >= 0) {
-			if (run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], passon, 4, (passon == 3)))
-				return;
-		}
-		run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], 4);  // any click on obj
+		if ((evnt >= 0) &&
+			run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], evnt, anyclick_evt, (mood == MODE_USE)) < 0)
+			return; // game state changed, don't do "any click"
+		run_interaction_event(obj_evt, &_G(croom)->intrObject[aa], anyclick_evt); // any click on obj
 	}
 }
 
diff --git a/engines/ags/engine/ac/global_region.cpp b/engines/ags/engine/ac/global_region.cpp
index f1722136070..f78844dea26 100644
--- a/engines/ags/engine/ac/global_region.cpp
+++ b/engines/ags/engine/ac/global_region.cpp
@@ -143,8 +143,10 @@ void RunRegionInteraction(int regnum, int mood) {
 	if ((mood < 0) || (mood > 2))
 		quit("!RunRegionInteraction: invalid event specified");
 
+	// NOTE: for Regions the mode has specific meanings (NOT verbs):
+	// 0 - stands on region, 1 - walks onto region, 2 - walks off region
 	const auto obj_evt = ObjectEvent("region%d", regnum);
-	if (_GP(thisroom).Regions[regnum].EventHandlers != nullptr) {
+	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		run_interaction_script(obj_evt, _GP(thisroom).Regions[regnum].EventHandlers.get(), mood);
 	} else {
 		run_interaction_event(obj_evt, &_G(croom)->intrRegion[regnum], mood);
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index a0f7d9c7437..985c0f52f19 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -572,8 +572,8 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 					// Let the script handle the click
 					// LEFTINV is 5, RIGHTINV is 6
 					force_event(EV_TEXTSCRIPT, TS_MCLICK, wasbutdown + 4);
-				} else if (wasbutdown == 2)  // right-click is always Look
-					run_event_block_inv(iit, 0);
+				} else if (wasbutdown == kMouseRight)  // right-click is always Look
+					RunInventoryInteraction(iit, MODE_LOOK);
 				else if (_G(cur_mode) == MODE_HAND)
 					SetActiveInventory(iit);
 				else
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index 335c75e80e4..8f0a91ffb66 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -29,22 +29,24 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/global_character.h"
 #include "ags/engine/ac/global_display.h"
+#include "ags/engine/ac/global_inventory_item.h"
 #include "ags/engine/ac/global_room.h"
 #include "ags/engine/ac/mouse.h"
+#include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/sys_events.h"
+#include "ags/engine/ac/timer.h"
+#include "ags/engine/ac/dynobj/cc_character.h"
+#include "ags/engine/ac/dynobj/cc_inventory.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/gui/gui_dialog.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/engine/main/game_run.h"
+#include "ags/engine/media/audio/audio_system.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/script/runtime_script_value.h"
-#include "ags/engine/ac/dynobj/cc_character.h"
-#include "ags/engine/ac/dynobj/cc_inventory.h"
 #include "ags/shared/util/math.h"
-#include "ags/engine/media/audio/audio_system.h"
-#include "ags/engine/ac/timer.h"
 #include "ags/shared/util/wgt2_allg.h"
+
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
@@ -386,7 +388,7 @@ bool InventoryScreen::Run() {
 			_GP(play).used_inv_on = dii[clickedon].num;
 
 			if (cmode == MODE_LOOK) {
-				run_event_block_inv(dii[clickedon].num, 0);
+				RunInventoryInteraction(dii[clickedon].num, MODE_LOOK);
 				// in case the script did anything to the screen, redraw it
 				UpdateGameOnce();
 
@@ -400,7 +402,7 @@ bool InventoryScreen::Run() {
 				int activeinvwas = _G(playerchar)->activeinv;
 				_G(playerchar)->activeinv = toret;
 
-				run_event_block_inv(dii[clickedon].num, 3);
+				RunInventoryInteraction(dii[clickedon].num, MODE_USE);
 
 				// if the script didn't change it, then put it back
 				if (_G(playerchar)->activeinv == toret)
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index 2cdcb84d871..792cc13b23e 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -98,16 +98,21 @@ const int LegacyRoomVolumeFactor = 30;
 #define FONT_NORMAL     _GP(play).normal_font
 //#define FONT_SPEECHBACK 1
 #define FONT_SPEECH     _GP(play).speech_font
-#define MODE_WALK 0
-#define MODE_LOOK 1
-#define MODE_HAND 2
-#define MODE_TALK 3
-#define MODE_USE  4
-#define MODE_PICKUP 5
-#define CURS_ARROW  6
-#define CURS_WAIT   7
-#define MODE_CUSTOM1 8
-#define MODE_CUSTOM2 9
+
+// Standard interaction verbs (aka cursor modes)
+#define MODE_WALK		0
+#define MODE_LOOK		1
+#define MODE_HAND		2
+#define MODE_TALK		3
+#define MODE_USE		4
+#define MODE_PICKUP 	5
+// aka MODE_POINTER
+#define CURS_ARROW		6
+// aka MODE_WAIT
+#define CURS_WAIT		7
+#define MODE_CUSTOM1	8
+#define MODE_CUSTOM2	9
+#define NUM_STANDARD_VERBS 10
 
 // Fixed Overlay IDs
 #define OVER_TEXTMSG  1
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 6396659e909..4dd8458d299 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -104,20 +104,7 @@ void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) {
 	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork), funcToRun, funcToRun->roomHasFunction);
 }
 
-//-----------------------------------------------------------
-// [IKM] 2012-06-22
-//
-// run_interaction_event() and run_interaction_script()
-// are *almost* identical, except for the first parameter
-// type.
-// May these types be made children of the same base?
-//-----------------------------------------------------------
-
-
-// Returns 0 normally, or -1 to indicate that the NewInteraction has
-// become invalid and don't run another interaction on it
-// (eg. a room change occurred)
-int run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny, int isInv) {
+int run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny, bool isInv) {
 
 	if (evnt < 0 || (size_t)evnt >= nint->Events.size() ||
 	        (nint->Events[evnt].Response.get() == nullptr) || (nint->Events[evnt].Response->Cmds.size() == 0)) {
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index 079f8ca7b04..aaa63ac34f2 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -59,12 +59,20 @@ struct ObjectEvent {
 
 int     run_dialog_request(int parmtr);
 void    run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun);
+
+// TODO: run_interaction_event() and run_interaction_script()
+// are in most part duplicating each other, except for the script callback run method.
+// May these types be made children of the same base, or stored in a group struct?
+// This would also let simplify the calling code in RunObjectInteraction, etc.
+//
 // Runs the ObjectEvent using an old interaction callback type of 'evnt' index,
 // or alternatively of 'chkAny' index, if previous does not exist;
 // 'isInv' tells if this is a inventory event (it has a slightly different handling for that)
-int     run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny = -1, int isInv = 0);
+// Returns 0 normally, or -1 telling of a game state change (eg. a room change occured).
+int     run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny = -1, bool isInv = false);
 // Runs the ObjectEvent using a script callback of 'evnt' index,
 // or alternatively of 'chkAny' index, if previous does not exist
+// Returns 0 normally, or -1 telling of a game state change (eg. a room change occured).
 int     run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint, int evnt, int chkAny = -1);
 int     run_interaction_commandlist(const ObjectEvent &obj_evt, InteractionCommandList *nicl, int *timesrun, int *cmdsrun);
 void    run_unhandled_event(const ObjectEvent &obj_evt, int evnt);


Commit: 26167025c4c1b5465d5da75f54a9b10395a6f076
    https://github.com/scummvm/scummvm/commit/26167025c4c1b5465d5da75f54a9b10395a6f076
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed GUI::TransformTextForDrawing() using translate flag wrong

>From upstream 5ccf8214e98059737b76b813a10128f268ab2f77

Changed paths:
    engines/ags/engine/gui/gui_engine.cpp


diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 9e2a2a7c4e8..974a35d7191 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -140,7 +140,7 @@ void GUIObject::ClearChanged() {
 }
 
 int GUILabel::PrepareTextToDraw() {
-	const bool is_translated = Flags & kGUICtrl_Translated;
+	const bool is_translated = (Flags & kGUICtrl_Translated) != 0;
 	replace_macro_tokens(is_translated ? get_translation(Text.GetCStr()) : Text.GetCStr(), _textToDraw);
 	return GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), is_translated, _GP(Lines), Font, Width);
 }


Commit: 720b7a53ec51e1b81b4b8169a3241835f01b4195
    https://github.com/scummvm/scummvm/commit/720b7a53ec51e1b81b4b8169a3241835f01b4195
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: object interaction events take obj pointer and mode as params

Partially from upstream 1897504aa30fda5f7d5cc9d9538cb49944c70151

Changed paths:
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h


diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index 792cc13b23e..d6399ad1b61 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -100,6 +100,7 @@ const int LegacyRoomVolumeFactor = 30;
 #define FONT_SPEECH     _GP(play).speech_font
 
 // Standard interaction verbs (aka cursor modes)
+#define MODE_NONE	   -1
 #define MODE_WALK		0
 #define MODE_LOOK		1
 #define MODE_HAND		2
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 4dd8458d299..69ff03cdcf6 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -146,7 +146,7 @@ int run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evn
 // (eg. a room change occurred)
 int run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint, int evnt, int chkAny) {
 
-	if (((int)nint->ScriptFuncNames.size() <= evnt) || nint->ScriptFuncNames[evnt].IsEmpty()) {
+	if (evnt < 0 || static_cast<size_t>(evnt) >= nint->ScriptFuncNames.size() || nint->ScriptFuncNames[evnt].IsEmpty()) {
 		// no response defined for this event
 		// If there is a response for "Any Click", then abort now so as to
 		// run that instead
@@ -166,13 +166,25 @@ int run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint,
 
 	int room_was = _GP(play).room_changes;
 
-	if ((strstr(obj_evt.BlockName.GetCStr(), "character") != nullptr) ||
-		(strstr(obj_evt.BlockName.GetCStr(), "inventory") != nullptr)) {
-		// Character or Inventory (global script)
-		QueueScriptFunction(kScInstGame, nint->ScriptFuncNames[evnt].GetCStr());
-	} else {
-		// Other (room script)
-		QueueScriptFunction(kScInstRoom, nint->ScriptFuncNames[evnt].GetCStr());
+	// TODO: find a way to generalize all the following hard-coded behavior
+
+	// Character or Inventory require a global script call
+	const ScriptInstType inst_type = (strstr(obj_evt.BlockName.GetCStr(), "character") != nullptr) || (strstr(obj_evt.BlockName.GetCStr(), "inventory") != nullptr) ?
+		kScInstGame : kScInstRoom;
+
+	// Room events do not require additional params
+	if ((strstr(obj_evt.BlockName.GetCStr(), "room") != nullptr)) {
+		QueueScriptFunction(inst_type, nint->ScriptFuncNames[evnt].GetCStr());
+	}
+	// Regions only require 1 param - dynobj ref
+	else if ((strstr(obj_evt.BlockName.GetCStr(), "region") != nullptr)) {
+		QueueScriptFunction(inst_type, nint->ScriptFuncNames[evnt].GetCStr(), 1, &obj_evt.DynObj);
+	}
+	// Other types (characters, objects, invitems, hotspots) require
+	// 2 params - dynobj ref and the interaction mode (aka verb)
+	else {
+		RuntimeScriptValue params[]{obj_evt.DynObj, RuntimeScriptValue().SetInt32(obj_evt.Mode)};
+		QueueScriptFunction(inst_type, nint->ScriptFuncNames[evnt].GetCStr(), 2, params);
 	}
 
 	// if the room changed within the action
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index aaa63ac34f2..826d8a2ea00 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -51,10 +51,17 @@ struct ObjectEvent {
 	String BlockName;
 	// Script block's ID, commonly corresponds to the object's ID
 	int BlockID = 0;
+	// Dynamic object this event was called for (if applicable)
+	RuntimeScriptValue DynObj;
+	// Interaction mode that triggered this event (if applicable)
+	int Mode = MODE_NONE;
 
 	ObjectEvent() = default;
 	ObjectEvent(const String &block_name, int block_id = 0)
 		: BlockName(block_name), BlockID(block_id) {}
+	ObjectEvent(const String &block_name, int block_id,
+				const RuntimeScriptValue &dyn_obj, int mode = MODE_NONE)
+		: BlockName(block_name), BlockID(block_id), DynObj(dyn_obj), Mode(mode) {}
 };
 
 int     run_dialog_request(int parmtr);


Commit: f35fe9b9888ae5143bffcc13e03b01b9db103533
    https://github.com/scummvm/scummvm/commit/f35fe9b9888ae5143bffcc13e03b01b9db103533
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: added Object.AnimationVolume, like in Character

Missed to add this in 3.6.0 (thought it is not necessary for some reason...)
>From upstream 480a8de9d3c5dc36eaaa3e4be94a1edf00445d68

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_object.h
    engines/ags/engine/ac/view_frame.cpp
    engines/ags/engine/ac/view_frame.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 7033e6786ed..a9ea8a7f99c 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1083,7 +1083,7 @@ int Character_GetAnimationVolume(CharacterInfo *chaa) {
 
 void Character_SetAnimationVolume(CharacterInfo *chaa, int newval) {
 
-	_GP(charextra)[chaa->index_id].anim_volume = std::min(newval, 100); // negative means default
+	_GP(charextra)[chaa->index_id].anim_volume = Math::Clamp(newval, 0, 100);
 }
 
 int Character_GetBaseline(CharacterInfo *chaa) {
@@ -2065,11 +2065,17 @@ void stop_character_anim(CharacterInfo *chap) { // TODO: may expand with resetti
 	_GP(charextra)[chap->index_id].cur_anim_volume = 100;
 }
 
+// Process the current animation frame for the character:
+// play linked sounds, and so forth.
 void CheckViewFrameForCharacter(CharacterInfo *chi) {
-	CheckViewFrame(chi->view, chi->loop, chi->frame, GetCharacterFrameVolume(chi));
+	const auto &chex = _GP(charextra)[chi->index_id];
+	const int frame_vol = CalcFrameSoundVolume(
+		chex.anim_volume, chex.cur_anim_volume,
+		(chi->flags & CHF_SCALEVOLUME) ? chex.zoom : 100);
+	CheckViewFrame(chi->view, chi->loop, chi->frame, frame_vol);
 }
 
-int GetCharacterFrameVolume(CharacterInfo * chi) {
+int GetCharacterFrameVolume(CharacterInfo *chi) {
 	// We view the audio property relation as the relation of the entities:
 	// system -> audio type -> audio emitter (character) -> animation's audio
 	// therefore the sound volume is a multiplication of factors.
diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index ef6b358ee00..0372102e563 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -44,8 +44,8 @@ void CharacterExtras::ReadFromSavegame(Stream *in, int save_ver) {
 	animwait = in->ReadInt16();
 	if (save_ver >= 2) // expanded at ver 2
 	{
-		anim_volume = in->ReadInt8();
-		cur_anim_volume = in->ReadInt8();
+		anim_volume = static_cast<uint8_t>(in->ReadInt8());
+		cur_anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		in->ReadInt8(); // reserved to fill int32
 		in->ReadInt8();
 	}
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index f728c1e8cec..f8293b32b08 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -43,7 +43,7 @@ struct CharacterExtras {
 	// TODO: consider having both fixed AABB and volatile one that changes with animation frame (unless you change how anims work)
 	short width = 0;
 	short height = 0;
-	short zoom = 0;
+	short zoom = 100;
 	short xwas = 0;
 	short ywas = 0;
 	short tint_r = 0;
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index c00ad0c641f..be08365c926 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -179,7 +179,7 @@ bool SetObjectFrameSimple(int obn, int viw, int lop, int fra) {
 void SetObjectFrame(int obn, int viw, int lop, int fra) {
 	if (!SetObjectFrameSimple(obn, viw, lop, fra))
 		return;
-	CheckViewFrame(_G(objs)[obn].view, _G(objs)[obn].loop, _G(objs)[obn].frame);
+	_G(objs)[obn].CheckViewFrame();
 }
 
 // pass trans=0 for fully solid, trans=100 for fully transparent
@@ -238,8 +238,9 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
 	obj.num = Math::InRangeOrDef<uint16_t>(pic, 0);
 	if (pic > UINT16_MAX)
 		debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0", obn, pic, UINT16_MAX);
-	obj.anim_volume = Math::Clamp(volume, 0, 100);
-	CheckViewFrame(obj.view, loopn, obj.frame, obj.anim_volume);
+	obj.cur_anim_volume = Math::Clamp(volume, 0, 100);
+
+	_G(objs)[obn].CheckViewFrame();
 
 	if (blocking)
 		GameLoopUntilValueIsZero(&obj.cycling);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 78a97e62db0..a24d6e0840e 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -109,6 +109,15 @@ int Object_GetTransparency(ScriptObject *objj) {
 	return GfxDef::LegacyTrans255ToTrans100(_G(objs)[objj->id].transparent);
 }
 
+int Object_GetAnimationVolume(ScriptObject *objj) {
+	return _G(objs)[objj->id].anim_volume;
+}
+
+void Object_SetAnimationVolume(ScriptObject *objj, int newval) {
+
+	_G(objs)[objj->id].anim_volume = Math::Clamp(newval, 0, 100);
+}
+
 void Object_SetBaseline(ScriptObject *objj, int basel) {
 	SetObjectBaseline(objj->id, basel);
 }
@@ -854,6 +863,14 @@ RuntimeScriptValue Sc_Object_GetAnimating(void *self, const RuntimeScriptValue *
 	API_OBJCALL_INT(ScriptObject, Object_GetAnimating);
 }
 
+RuntimeScriptValue Sc_Object_GetAnimationVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_INT(ScriptObject, Object_GetAnimationVolume);
+}
+
+RuntimeScriptValue Sc_Object_SetAnimationVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT(ScriptObject, Object_SetAnimationVolume);
+}
+
 // int (ScriptObject *objj)
 RuntimeScriptValue Sc_Object_GetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptObject, Object_GetBaseline);
@@ -1050,6 +1067,8 @@ void RegisterObjectAPI() {
 	ccAddExternalStaticFunction("Object::GetAtScreenXY^2", Sc_GetObjectAtScreen);
 
 	ccAddExternalObjectFunction("Object::get_Animating", Sc_Object_GetAnimating);
+	ccAddExternalObjectFunction("Object::get_AnimationVolume", Sc_Object_GetAnimationVolume),
+	ccAddExternalObjectFunction("Object::set_AnimationVolume", Sc_Object_SetAnimationVolume),
 	ccAddExternalObjectFunction("Object::get_Baseline", Sc_Object_GetBaseline);
 	ccAddExternalObjectFunction("Object::set_Baseline", Sc_Object_SetBaseline);
 	ccAddExternalObjectFunction("Object::get_BlockingHeight", Sc_Object_GetBlockingHeight);
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index 285d218b742..219bad051e5 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -130,6 +130,7 @@ int     SetFirstAnimFrame(int view, int loop, int sframe, int direction);
 // loop and frame values are passed by reference and will be updated;
 // returns whether the animation should continue.
 bool    CycleViewAnim(int view, uint16_t &loop, uint16_t &frame, bool forwards, int repeat);
+void	CheckViewFrameForObject(RoomObject *obj);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 0204c601d41..4f9333e6107 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -96,7 +96,13 @@ void RoomObject::UpdateCyclingView(int ref_id) {
 		return;
 
 	wait = vfptr->speed + overall_speed;
-	CheckViewFrame(view, loop, frame, anim_volume);
+	CheckViewFrame();
+}
+
+void RoomObject::CheckViewFrame() {
+	// NOTE: room objects don't have "scale volume" flag at the moment
+	const int frame_vol = CalcFrameSoundVolume(anim_volume, cur_anim_volume);
+	AGS3::CheckViewFrame(view, loop, frame, frame_vol);
 }
 
 void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
@@ -128,10 +134,12 @@ void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
 		name = StrUtil::ReadString(in);
 	}
 	if (save_ver >= 2) {
-		anim_volume = in->ReadInt8();
+		// anim vols order inverted compared to character, by mistake :(
+		cur_anim_volume = static_cast<uint8_t>(in->ReadInt8());
+		anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		in->ReadInt8(); // reserved to fill int32
 		in->ReadInt8();
-		in->ReadInt8();
+
 	}
 }
 
@@ -161,10 +169,10 @@ void RoomObject::WriteToSavegame(Stream *out) const {
 	out->WriteInt16(blocking_width);
 	out->WriteInt16(blocking_height);
 	StrUtil::WriteString(name, out);
-	out->WriteInt8(anim_volume);
+	out->WriteInt8(static_cast<uint8_t>(cur_anim_volume));
+	out->WriteInt8(static_cast<uint8_t>(anim_volume));
 	out->WriteInt8(0); // reserved to fill int32
 	out->WriteInt8(0);
-	out->WriteInt8(0);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index 88c91ed5e3a..8928d04b698 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -75,7 +75,8 @@ struct RoomObject {
 	int8  flags;
 	// Down to here is a part of the plugin API
 	short blocking_width, blocking_height;
-	int   anim_volume = -1; // current animation volume
+	int	  anim_volume = 100;     // default animation volume (relative factor)
+	int   cur_anim_volume = 100; // current animation sound volume (relative factor)
 	Shared::String name;
 
 	RoomObject();
@@ -113,6 +114,9 @@ struct RoomObject {
 	}
 
 	void UpdateCyclingView(int ref_id);
+	// Process the current animation frame for the room object:
+	// play linked sounds, and so forth.
+	void CheckViewFrame();
 
 	void ReadFromSavegame(Shared::Stream *in, int save_ver);
 	void WriteToSavegame(Shared::Stream *out) const;
diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index 44117eca674..b8a423790e2 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -117,6 +117,22 @@ void precache_view(int view) {
 	}
 }
 
+int CalcFrameSoundVolume(int obj_vol, int anim_vol, int scale) {
+	// We view the audio property relation as the relation of the entities:
+	// system -> audio type -> audio emitter (object, character) -> animation's audio
+	// therefore the sound volume is a multiplication of factors.
+	int frame_vol = 100; // default to full volume
+	// Object's animation volume property
+	frame_vol = frame_vol * obj_vol / 100;
+	// Active animation volume
+	frame_vol = frame_vol * anim_vol / 100;
+	// Zoom volume scaling (optional)
+	// NOTE: historically scales only in 0-100 range :/
+	scale = Math::Clamp(scale, 0, 100);
+	frame_vol = frame_vol * scale / 100;
+	return frame_vol;
+}
+
 // Handle the new animation frame (play linked sounds, etc)
 void CheckViewFrame(int view, int loop, int frame, int sound_volume) {
 	ScriptAudioChannel *channel = nullptr;
@@ -139,7 +155,7 @@ void CheckViewFrame(int view, int loop, int frame, int sound_volume) {
 			channel = play_audio_clip_by_index(_GP(views)[view].loops[loop].frames[frame].sound);
 		}
 	}
-	if (channel && (sound_volume >= 0)) {
+	if (channel) {
 		sound_volume = Math::Clamp(sound_volume, 0, 100);
 		auto *ch = AudioChans::GetChannel(channel->id);
 		if (ch)
diff --git a/engines/ags/engine/ac/view_frame.h b/engines/ags/engine/ac/view_frame.h
index 9b8df0177a7..f29b0417256 100644
--- a/engines/ags/engine/ac/view_frame.h
+++ b/engines/ags/engine/ac/view_frame.h
@@ -51,9 +51,13 @@ int  ViewFrame_GetLoop(ScriptViewFrame *svf);
 int  ViewFrame_GetFrame(ScriptViewFrame *svf);
 
 void precache_view(int view);
+// Calculate the frame sound volume from different factors;
+// pass scale as 100 if volume scaling is disabled
+// NOTE: historically scales only in 0-100 range :/
+int CalcFrameSoundVolume(int obj_vol, int anim_vol, int scale = 100);
 // Handle the new animation frame (play linked sounds, etc);
- // sound_volume is an optional relative factor, -1 means not use
- void CheckViewFrame(int view, int loop, int frame, int sound_volume = -1);
+// sound_volume is an optional *relative* factor, 100 is default (unchanged)
+void CheckViewFrame(int view, int loop, int frame, int sound_volume = 100);
 // draws a view frame, flipped if appropriate
 void DrawViewFrame(Shared::Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha_blend = false);
 


Commit: 2ae4a1ebd85fc8e52194b5b39dc744e3affb3417
    https://github.com/scummvm/scummvm/commit/2ae4a1ebd85fc8e52194b5b39dc744e3affb3417
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: some refactor + apply volume for character face animations

>From upstream a31beb6b76243902e75a49e901165c9565e8553a

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_object.h
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index a9ea8a7f99c..be1d059b9d7 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2056,7 +2056,7 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int n
 	chap->wait = sppd + _GP(views)[chap->view].loops[loopn].frames[chap->frame].speed;
 	_GP(charextra)[chap->index_id].cur_anim_volume = Math::Clamp(volume, 0, 100);
 
-	CheckViewFrameForCharacter(chap);
+	_GP(charextra)[chap->index_id].CheckViewFrame(chap);
 }
 
 void stop_character_anim(CharacterInfo *chap) { // TODO: may expand with resetting more properties,
@@ -2065,16 +2065,6 @@ void stop_character_anim(CharacterInfo *chap) { // TODO: may expand with resetti
 	_GP(charextra)[chap->index_id].cur_anim_volume = 100;
 }
 
-// Process the current animation frame for the character:
-// play linked sounds, and so forth.
-void CheckViewFrameForCharacter(CharacterInfo *chi) {
-	const auto &chex = _GP(charextra)[chi->index_id];
-	const int frame_vol = CalcFrameSoundVolume(
-		chex.anim_volume, chex.cur_anim_volume,
-		(chi->flags & CHF_SCALEVOLUME) ? chex.zoom : 100);
-	CheckViewFrame(chi->view, chi->loop, chi->frame, frame_vol);
-}
-
 int GetCharacterFrameVolume(CharacterInfo *chi) {
 	// We view the audio property relation as the relation of the entities:
 	// system -> audio type -> audio emitter (character) -> animation's audio
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index 448db9de154..e48a7e48a8f 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -208,7 +208,6 @@ void FindReasonableLoopForCharacter(CharacterInfo *chap);
 void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk);
 int  wantMoveNow(CharacterInfo *chi, CharacterExtras *chex);
 void setup_player_character(int charid);
-void CheckViewFrameForCharacter(CharacterInfo *chi);
 int  GetCharacterFrameVolume(CharacterInfo *chi);
 Shared::Bitmap *GetCharacterImage(int charid, int *isFlipped);
 CharacterInfo *GetCharacterAtScreen(int xx, int yy);
diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index 0372102e563..1db2b286c99 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -20,12 +20,23 @@
  */
 
 #include "ags/engine/ac/character_extras.h"
+#include "ags/engine/ac/view_frame.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
 using AGS::Shared::Stream;
 
+int CharacterExtras::GetFrameSoundVolume(CharacterInfo *chi) const {
+	return AGS3::CalcFrameSoundVolume(
+		anim_volume, cur_anim_volume,
+		(chi->flags & CHF_SCALEVOLUME) ? zoom : 100);
+}
+
+void CharacterExtras::CheckViewFrame(CharacterInfo *chi) {
+	AGS3::CheckViewFrame(chi->view, chi->loop, chi->frame, GetFrameSoundVolume(chi));
+}
+
 void CharacterExtras::ReadFromSavegame(Stream *in, int save_ver) {
 	in->ReadArrayOfInt16(invorder, MAX_INVORDER);
 	invorder_count = in->ReadInt16();
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index f8293b32b08..81b015a7d49 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -22,6 +22,7 @@
 #ifndef AGS_ENGINE_AC_CHARACTER_EXTRAS_H
 #define AGS_ENGINE_AC_CHARACTER_EXTRAS_H
 
+#include "ags/shared/ac/character_info.h"
 #include "ags/engine/ac/runtime_defines.h"
 
 namespace AGS3 {
@@ -57,6 +58,12 @@ struct CharacterExtras {
 	int   anim_volume = 100; // default animation volume (relative factor)
 	int   cur_anim_volume = 100; // current animation sound volume (relative factor)
 
+	// Calculate wanted frame sound volume based on multiple factors
+	int GetFrameSoundVolume(CharacterInfo *chi) const;
+	// Process the current animation frame for the character:
+	// play linked sounds, and so forth.
+	void CheckViewFrame(CharacterInfo *chi);
+
 	void ReadFromSavegame(Shared::Stream *in, int save_ver);
 	void WriteToSavegame(Shared::Stream *out);
 };
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 75e1a787019..d77bf3598b3 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -238,7 +238,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 				// use standing pic
 				chex->animwait = 0;
 				frame = 0;
-				CheckViewFrameForCharacter(this);
+				chex->CheckViewFrame(this);
 			}
 		} else if (chex->animwait > 0) {
 			chex->animwait--;
@@ -263,13 +263,15 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 				else
 					walkwait = 0;
 
-				CheckViewFrameForCharacter(this);
+				chex->CheckViewFrame(this);
 			}
 		}
 	}
 }
 
 int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
+	CharacterExtras *chex = &_GP(charextra)[index_id];
+
 	// not moving, but animating
 	// idleleft is <0 while idle view is playing (.animating is 0)
 	if (((animating != 0) || (idleleft < 0)) &&
@@ -293,7 +295,7 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
 
 			if (frame != fraa) {
 				frame = fraa;
-				CheckViewFrameForCharacter(this);
+				chex->CheckViewFrame(this);
 			}
 
 			//continue;
@@ -336,7 +338,7 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
 				wait += get_anim_delay();
 
 			if (frame != oldframe)
-				CheckViewFrameForCharacter(this);
+				chex->CheckViewFrame(this);
 
 			if (done_anim)
 				stop_character_anim(this);
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 4f9333e6107..9caad3e3645 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -99,10 +99,14 @@ void RoomObject::UpdateCyclingView(int ref_id) {
 	CheckViewFrame();
 }
 
-void RoomObject::CheckViewFrame() {
+// Calculate wanted frame sound volume based on multiple factors
+int RoomObject::GetFrameSoundVolume() const {
 	// NOTE: room objects don't have "scale volume" flag at the moment
-	const int frame_vol = CalcFrameSoundVolume(anim_volume, cur_anim_volume);
-	AGS3::CheckViewFrame(view, loop, frame, frame_vol);
+	return AGS3::CalcFrameSoundVolume(anim_volume, cur_anim_volume);
+}
+
+void RoomObject::CheckViewFrame() {
+	AGS3::CheckViewFrame(view, loop, frame, GetFrameSoundVolume());
 }
 
 void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
diff --git a/engines/ags/engine/ac/room_object.h b/engines/ags/engine/ac/room_object.h
index 8928d04b698..a7cb34ad161 100644
--- a/engines/ags/engine/ac/room_object.h
+++ b/engines/ags/engine/ac/room_object.h
@@ -114,6 +114,8 @@ struct RoomObject {
 	}
 
 	void UpdateCyclingView(int ref_id);
+	// Calculate wanted frame sound volume based on multiple factors
+	int  GetFrameSoundVolume() const;
 	// Process the current animation frame for the room object:
 	// play linked sounds, and so forth.
 	void CheckViewFrame();
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 40dcf2f3ae1..b2f2f6ac6aa 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -377,11 +377,12 @@ void update_sierra_speech() {
 		// _G(is_text_overlay) might be 0 if it was only just destroyed this loop
 		if ((updatedFrame) && (_GP(play).text_overlay_on > 0)) {
 
-			const int frame_vol = GetCharacterFrameVolume(_G(facetalkchar));
+			const auto &talking_chex = _GP(charextra)[_G(facetalkchar)->index_id];
+			const int frame_vol = talking_chex.GetFrameSoundVolume(_G(facetalkchar));
 			if (updatedFrame & 1)
-				CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe), frame_vol);
+				CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe));
 			if (updatedFrame & 2)
-				CheckViewFrame(_G(facetalkchar)->blinkview, _G(facetalkBlinkLoop), _G(facetalkchar)->blinkframe, frame_vol);
+				CheckViewFrame(_G(facetalkchar)->blinkview, _G(facetalkBlinkLoop), _G(facetalkchar)->blinkframe);
 
 			int thisPic = _GP(views)[_G(facetalkview)].loops[_G(facetalkloop)].frames[_G(facetalkframe)].pic;
 			int view_frame_x = 0;


Commit: 82a656691fd282289ddeb9d3b2dc881342f3a5aa
    https://github.com/scummvm/scummvm/commit/82a656691fd282289ddeb9d3b2dc881342f3a5aa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.5)

Partially From upstream 9bb443b18e5b3dc742c0d6a5c7207358c6ad39f2

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 819adf9c2af..20fe063a9e1 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.4"
+#define ACI_VERSION_STR      "3.6.1.5"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.4
+#define ACI_VERSION_MSRC_DEF  3.6.1.5
 #endif
 
 #define SPECIAL_VERSION ""


Commit: f62f2704231d94586aa1f076ffda619e3bf5bca7
    https://github.com/scummvm/scummvm/commit/f62f2704231d94586aa1f076ffda619e3bf5bca7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use enum for Text script callback types

>From upstream 047f6fb020564bca9c6d99d1f6c1474386e3da97

Changed paths:
    engines/ags/engine/ac/event.h
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/script/script.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/event.h b/engines/ags/engine/ac/event.h
index 18aca337e27..7f216736c3a 100644
--- a/engines/ags/engine/ac/event.h
+++ b/engines/ags/engine/ac/event.h
@@ -53,16 +53,20 @@ namespace AGS3 {
 // new room event
 #define EV_NEWROOM    5
 // Text script callback types:
+enum kTS_CallbackTypes {
+	kTS_None = 0,
 // repeatedly execute
-#define TS_REPEAT     1
+	kTS_Repeat,
 // on key press
-#define TS_KEYPRESS   2
+	kTS_KeyPress,
 // mouse click
-#define TS_MCLICK     3
+	kTS_MouseClick,
 // on text input
-#define TS_TEXTINPUT  4
+	kTS_TextInput,
 // script callback types number
-#define TS_NUM        5
+	kTS_Num
+};
+
 // Room event types:
 // hotspot event
 #define EVB_HOTSPOT   1
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 985c0f52f19..52b3249a91a 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -571,7 +571,7 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 				if (_GP(game).options[OPT_HANDLEINVCLICKS]) {
 					// Let the script handle the click
 					// LEFTINV is 5, RIGHTINV is 6
-					force_event(EV_TEXTSCRIPT, TS_MCLICK, wasbutdown + 4);
+					force_event(EV_TEXTSCRIPT, kTS_MouseClick, wasbutdown + 4);
 				} else if (wasbutdown == kMouseRight)  // right-click is always Look
 					RunInventoryInteraction(iit, MODE_LOOK);
 				else if (_G(cur_mode) == MODE_HAND)
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index f8ed675539c..e154a097efe 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -111,7 +111,7 @@ static void game_loop_do_early_script_update() {
 	if (_G(in_new_room) == 0) {
 		// Run the room and game script repeatedly_execute
 		run_function_on_non_blocking_thread(&_GP(repExecAlways));
-		setevent(EV_TEXTSCRIPT, TS_REPEAT);
+		setevent(EV_TEXTSCRIPT, kTS_Repeat);
 		setevent(EV_RUNEVBLOCK, EVB_ROOM, 0, EVROM_REPEXEC);
 	}
 }
@@ -228,13 +228,13 @@ static void check_mouse_controls() {
 			_G(wasongui) = mongu;
 			_G(wasbutdown) = mbut;
 		} else
-			setevent(EV_TEXTSCRIPT, TS_MCLICK, mbut);
+			setevent(EV_TEXTSCRIPT, kTS_MouseClick, mbut);
 	}
 
 	if (mwheelz < 0)
-		setevent(EV_TEXTSCRIPT, TS_MCLICK, 9);
+		setevent(EV_TEXTSCRIPT, kTS_MouseClick, 9);
 	else if (mwheelz > 0)
-		setevent(EV_TEXTSCRIPT, TS_MCLICK, 8);
+		setevent(EV_TEXTSCRIPT, kTS_MouseClick, 8);
 }
 
 
@@ -525,11 +525,11 @@ static void check_keyboard_controls() {
 		const int sckeymod = ki.Mod;
 		if (old_keyhandle || (ki.UChar == 0)) {
 			debug_script_log("Running on_key_press keycode %d, mod %d", sckey, sckeymod);
-			setevent(EV_TEXTSCRIPT, TS_KEYPRESS, sckey, sckeymod);
+			setevent(EV_TEXTSCRIPT, kTS_KeyPress, sckey, sckeymod);
 		}
 		if (!old_keyhandle && (ki.UChar > 0)) {
 			debug_script_log("Running on_text_input char %s (%d)", ki.Text, ki.UChar);
-			setevent(EV_TEXTSCRIPT, TS_TEXTINPUT, ki.UChar);
+			setevent(EV_TEXTSCRIPT, kTS_TextInput, ki.UChar);
 		}
 	}
 }
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 69ff03cdcf6..e9233c0c991 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -435,8 +435,8 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 	}
 	// Claimable event is run in all the script modules and room script,
 	// before running in the globalscript instance
-	if ((strcmp(tsname, _G(tsnames)[TS_KEYPRESS]) == 0) || (strcmp(tsname, _G(tsnames)[TS_MCLICK]) == 0) ||
-		(strcmp(tsname, _G(tsnames)[TS_TEXTINPUT]) == 0) || (strcmp(tsname, "on_event") == 0)) {
+	if ((strcmp(tsname, _G(tsnames)[kTS_KeyPress]) == 0) || (strcmp(tsname, _G(tsnames)[kTS_MouseClick]) == 0) ||
+		(strcmp(tsname, _G(tsnames)[kTS_TextInput]) == 0) || (strcmp(tsname, "on_event") == 0)) {
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 2d2cd28785a..7521d7b5672 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -691,7 +691,7 @@ public:
 	int _inside_processevent = 0;
 	int _eventClaimed = 0;
 
-	const char *_tsnames[TS_NUM] = { nullptr, REP_EXEC_NAME, "on_key_press", "on_mouse_click", "on_text_input" };
+	const char *_tsnames[kTS_Num] = { nullptr, REP_EXEC_NAME, "on_key_press", "on_mouse_click", "on_text_input" };
 
 	/**@}*/
 


Commit: f866475b7f296da8b54aa6d2e23f700b8f0a33c6
    https://github.com/scummvm/scummvm/commit/f866475b7f296da8b54aa6d2e23f700b8f0a33c6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: REALLY apply character's anim volume to portrait animation

+ process frame features for the first portrait frame too.
>From upstream efc3941fe335d0cf4a34dbb65a75c012f8d6df40

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index be1d059b9d7..42472b6b4dd 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2667,7 +2667,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			textcol = -textcol;
 			overlayPositionFixed = true;
 			// Process the first portrait view frame
-			const int frame_vol = GetCharacterFrameVolume(_G(facetalkchar));
+			const int frame_vol = _GP(charextra)[_G(facetalkchar)->index_id].GetFrameSoundVolume(_G(facetalkchar));
 			CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe), frame_vol);
 		} else if (useview >= 0) {
 			// Lucasarts-style speech
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index b2f2f6ac6aa..244dca18b0a 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -380,9 +380,9 @@ void update_sierra_speech() {
 			const auto &talking_chex = _GP(charextra)[_G(facetalkchar)->index_id];
 			const int frame_vol = talking_chex.GetFrameSoundVolume(_G(facetalkchar));
 			if (updatedFrame & 1)
-				CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe));
+				CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe), frame_vol);
 			if (updatedFrame & 2)
-				CheckViewFrame(_G(facetalkchar)->blinkview, _G(facetalkBlinkLoop), _G(facetalkchar)->blinkframe);
+				CheckViewFrame(_G(facetalkchar)->blinkview, _G(facetalkBlinkLoop), _G(facetalkchar)->blinkframe, frame_vol);
 
 			int thisPic = _GP(views)[_G(facetalkview)].loops[_G(facetalkloop)].frames[_G(facetalkframe)].pic;
 			int view_frame_x = 0;


Commit: 551c493648800a1842030a7883bab7accefc9304
    https://github.com/scummvm/scummvm/commit/551c493648800a1842030a7883bab7accefc9304
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.6)

Partially from upstream 31abfbdc506317d5bb58d0ca97e3e073a0543b80

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 20fe063a9e1..bf49fc0ae6f 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.5"
+#define ACI_VERSION_STR      "3.6.1.6"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.5
+#define ACI_VERSION_MSRC_DEF  3.6.1.6
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 9333d7046610d34759610600588ea173e0f080a3
    https://github.com/scummvm/scummvm/commit/9333d7046610d34759610600588ea173e0f080a3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: check (de)compression result in SpriteFile, + tidy deflate func

Partially from upstream efa54fb852c6b3e59f9526405d073d677f89190d

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp
    engines/ags/shared/util/compress.cpp
    engines/ags/shared/util/compress.h


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 0c2e0887be8..0e46bebcdaa 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -371,7 +371,7 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	ReadSprHeader(hdr, _stream.get(), _version, _compress);
 	if (hdr.BPP == 0) return HError::None(); // empty slot, this is normal
 	int bpp = hdr.BPP, w = hdr.Width, h = hdr.Height;
-	Bitmap *image = BitmapHelper::CreateBitmap(w, h, bpp * 8);
+	std::unique_ptr<Bitmap> image(BitmapHelper::CreateBitmap(w, h, bpp * 8));
 	if (image == nullptr) {
 		return new Error(String::FromFormat("LoadSprite: failed to allocate bitmap %d (%dx%d%d).",
 			index, w, h, bpp * 8));
@@ -401,20 +401,25 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 		((_version >= kSprfVersion_StorageFormats) || _compress != kSprCompress_None) ?
 		(uint32_t)_stream->ReadInt32() : (w * h * bpp);
 	if (hdr.Compress != kSprCompress_None) {
+		// TODO: rewrite this to only make a choice once the SpriteFile is initialized
+		// and use either function ptr or a decompressing stream class object
 		if (in_data_size == 0) {
-			delete image;
 			return new Error(String::FromFormat("LoadSprite: bad compressed data for sprite %d.", index));
 		}
+		bool result;
 		switch (hdr.Compress) {
-		case kSprCompress_RLE: rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
+		case kSprCompress_RLE: result = rle_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get());
 			break;
-		case kSprCompress_LZW: lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
+		case kSprCompress_LZW: result = lzw_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
 			break;
-		case kSprCompress_Deflate: inflate_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
+		case kSprCompress_Deflate: result = inflate_decompress(im_data.Buf, im_data.Size, im_data.BPP, _stream.get(), in_data_size);
 			break;
-		default: assert(!"Unsupported compression type!"); break;
+		default: assert(!"Unsupported compression type!"); result = false; break;
 		}
 		// TODO: test that not more than data_size was read!
+		if (!result) {
+			return new Error(String::FromFormat("LoadSprite: failed to decompress pixel array for sprite %d.", index));
+		}
 	}
 	// Otherwise (no compression) read directly
 	else {
@@ -432,10 +437,10 @@ HError SpriteFile::LoadSprite(sprkey_t index, Shared::Bitmap *&sprite) {
 	}
 	// Finally revert storage options
 	if (pal_bpp > 0) {
-		UnpackIndexedBitmap(image, im_data.Buf, im_data.Size, palette, hdr.PalCount);
+		UnpackIndexedBitmap(image.get(), im_data.Buf, im_data.Size, palette, hdr.PalCount);
 	}
 
-	sprite = image;
+	sprite = image.release(); // FIXME: pass unique_ptr in this function
 	_curPos = index + 1; // mark correct pos
 	return HError::None();
 }
@@ -639,19 +644,22 @@ void SpriteFileWriter::WriteBitmap(Bitmap *image) {
 	if (_compress != kSprCompress_Deflate)
 		warning("TODO: Deflate not implemented, writing uncompressed BMP");
 	else if (_compress != kSprCompress_None) {
+		// TODO: rewrite this to only make a choice once the SpriteFile is initialized
+		// and use either function ptr or a decompressing stream class object
 		compress = _compress;
 		VectorStream mems(_membuf, kStream_Write);
+		bool result;
 		switch (compress) {
-		case kSprCompress_RLE: rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+		case kSprCompress_RLE: result = rle_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
 			break;
-		case kSprCompress_LZW: lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+		case kSprCompress_LZW: result = lzw_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
 			break;
-		case kSprCompress_Deflate: deflate_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
+		case kSprCompress_Deflate: result = deflate_compress(im_data.Buf, im_data.Size, im_data.BPP, &mems);
 			break;
-		default: assert(!"Unsupported compression type!"); break;
+		default: assert(!"Unsupported compression type!"); result = false; break;
 		}
 		// mark to write as a plain byte array
-		im_data = ImBufferCPtr(&_membuf[0], _membuf.size(), 1);
+		im_data = result ? ImBufferCPtr(&_membuf[0], _membuf.size(), 1) : ImBufferCPtr();
 	}
 
 	// Write the final data
diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp
index 87a8719083b..26bcc918055 100644
--- a/engines/ags/shared/util/compress.cpp
+++ b/engines/ags/shared/util/compress.cpp
@@ -154,8 +154,6 @@ static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
-		if (in->HasErrors())
-			break;
 
 		int8 cx = ix;
 		if (cx == -128)
@@ -183,7 +181,7 @@ static int cunpackbitl(uint8_t *line, size_t size, Stream *in) {
 		}
 	}
 
-	return in->HasErrors() ? -1 : 0;
+	return 0;
 }
 
 static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
@@ -191,8 +189,6 @@ static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
-		if (in->HasErrors())
-			break;
 
 		int8 cx = ix;
 		if (cx == -128)
@@ -220,7 +216,7 @@ static int cunpackbitl16(uint16_t *line, size_t size, Stream *in) {
 		}
 	}
 
-	return in->HasErrors() ? -1 : 0;
+	return 0;
 }
 
 static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
@@ -228,8 +224,6 @@ static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
 
 	while (n < size) {
 		int ix = in->ReadByte();     // get index byte
-		if (in->HasErrors())
-			break;
 
 		int8 cx = ix;
 		if (cx == -128)
@@ -257,25 +251,27 @@ static int cunpackbitl32(uint32_t *line, size_t size, Stream *in) {
 		}
 	}
 
-	return in->HasErrors() ? -1 : 0;
+	return 0;
 }
 
-void rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Stream *out) {
+bool rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Stream *out) {
 	switch (image_bpp) {
 	case 1: cpackbitl(data, data_sz, out); break;
 	case 2: cpackbitl16(reinterpret_cast<const uint16_t *>(data), data_sz / sizeof(uint16_t), out); break;
 	case 4: cpackbitl32(reinterpret_cast<const uint32_t *>(data), data_sz / sizeof(uint32_t), out); break;
 	default: assert(0); break;
 	}
+	return true;
 }
 
-void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
+bool rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Stream *in) {
 	switch (image_bpp) {
 	case 1: cunpackbitl(data, data_sz, in); break;
 	case 2: cunpackbitl16(reinterpret_cast<uint16_t *>(data), data_sz / sizeof(uint16_t), in); break;
 	case 4: cunpackbitl32(reinterpret_cast<uint32_t *>(data), data_sz / sizeof(uint32_t), in); break;
 	default: assert(0); break;
 	}
+	return true;
 }
 
 void save_rle_bitmap8(Stream *out, const Bitmap *bmp, const RGB(*pal)[256]) {
@@ -333,25 +329,25 @@ void skip_rle_bitmap8(Stream *in) {
 // LZW
 //-----------------------------------------------------------------------------
 
-void lzw_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *out) {
+bool lzw_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *out) {
 	// LZW algorithm that we use fails on sequence less than 16 bytes.
 	if (data_sz < 16) {
 		out->Write(data, data_sz);
-		return;
+		return true;
 	}
 	MemoryStream mem_in(data, data_sz);
-	lzwcompress(&mem_in, out);
+	return lzwcompress(&mem_in, out);
 }
 
-void lzw_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *in, size_t in_sz) {
+bool lzw_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Shared::Stream *in, size_t in_sz) {
 	// LZW algorithm that we use fails on sequence less than 16 bytes.
 	if (data_sz < 16) {
 		in->Read(data, data_sz);
-		return;
+		return true;
 	}
 	std::vector<uint8_t> in_buf(in_sz);
 	in->Read(in_buf.data(), in_sz);
-	lzwexpand(in_buf.data(), in_sz, data, data_sz);
+	return lzwexpand(in_buf.data(), in_sz, data, data_sz);
 }
 
 void save_lzw(Stream *out, const Bitmap *bmpp, const RGB(*pal)[256]) {
@@ -431,14 +427,15 @@ Bitmap *load_lzw(Stream *in, int dst_bpp, RGB(*pal)[256]) {
 	return bmm;
 }
 
-void deflate_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *out) {
+bool deflate_compress(const uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *out) {
 	// TODO
+	return false;
 }
 
-void inflate_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *in, size_t in_sz) {
+bool inflate_decompress(uint8_t *data, size_t data_sz, int /*image_bpp*/, Stream *in, size_t in_sz) {
 	std::vector<uint8_t> in_buf(in_sz);
 	in->Read(in_buf.data(), in_sz);
-	Common::inflateZlib(data, (unsigned long)data_sz, in_buf.data(), in_sz);
+	return Common::inflateZlib(data, (unsigned long)data_sz, in_buf.data(), in_sz);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h
index e3feee48d5e..266265a82c1 100644
--- a/engines/ags/shared/util/compress.h
+++ b/engines/ags/shared/util/compress.h
@@ -36,8 +36,8 @@ class Bitmap;
 
 using namespace AGS; // FIXME later
 
-void rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
-void rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
+bool rle_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+bool rle_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in);
 // Packs a 8-bit bitmap using RLE compression, and writes into stream along with the palette
 void save_rle_bitmap8(Shared::Stream *out, const Shared::Bitmap *bmp, const RGB(*pal)[256] = nullptr);
 // Reads a 8-bit bitmap with palette from the stream and unpacks from RLE
@@ -46,16 +46,16 @@ Shared::Bitmap *load_rle_bitmap8(Shared::Stream *in, RGB(*pal)[256] = nullptr);
 void skip_rle_bitmap8(Shared::Stream *in);
 
 // LZW compression
-void lzw_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
-void lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
+bool lzw_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+bool lzw_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
 // Saves bitmap with an optional palette compressed by LZW
 void save_lzw(Shared::Stream *out, const Shared::Bitmap *bmpp, const RGB(*pal)[256] = nullptr);
 // Loads bitmap decompressing
 Shared::Bitmap *load_lzw(Shared::Stream *in, int dst_bpp, RGB (*pal)[256] = nullptr);
 
 // Deflate compression
-void deflate_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
-void inflate_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
+bool deflate_compress(const uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *out);
+bool inflate_decompress(uint8_t *data, size_t data_sz, int image_bpp, Shared::Stream *in, size_t in_sz);
 
 } // namespace AGS3
 


Commit: bd1b5df2787c8628e3a9dc93d6160b0df310ac2e
    https://github.com/scummvm/scummvm/commit/bd1b5df2787c8628e3a9dc93d6160b0df310ac2e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.7)

Partially from upstream 88c8261460ada5c2712a1ee008987c9ad2b539e1

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 bf49fc0ae6f..2ea10615499 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.6"
+#define ACI_VERSION_STR      "3.6.1.7"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.6
+#define ACI_VERSION_MSRC_DEF  3.6.1.7
 #endif
 
 #define SPECIAL_VERSION ""


Commit: d7613f9653807d9954433c74400ceecc247951bc
    https://github.com/scummvm/scummvm/commit/d7613f9653807d9954433c74400ceecc247951bc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Marked few more game script variables as readonly

Partially From upstream 4afaf73e6ff92959a75705f0ec962b6c90a72331

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/gui_inv.cpp
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 757c9fb26c2..26de444e8a6 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -399,7 +399,7 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	usedinv = in->ReadInt32();
 	inv_top = in->ReadInt32();
 	inv_numdisp = in->ReadInt32();
-	obsolete_inv_numorder = in->ReadInt32();
+	inv_numorder = in->ReadInt32();
 	inv_numinline = in->ReadInt32();
 	text_speed = in->ReadInt32();
 	sierra_inv_color = in->ReadInt32();
@@ -622,7 +622,7 @@ void GameState::WriteForSavegame(Shared::Stream *out) const {
 	out->WriteInt32(usedinv);
 	out->WriteInt32(inv_top);
 	out->WriteInt32(inv_numdisp);
-	out->WriteInt32(obsolete_inv_numorder);
+	out->WriteInt32(inv_numorder);
 	out->WriteInt32(inv_numinline);
 	out->WriteInt32(text_speed);
 	out->WriteInt32(sierra_inv_color);
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 37fc0498154..9809e18739c 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -83,10 +83,12 @@ struct GameState {
 	int32_t globalvars[MAXGLOBALVARS]{};  // obsolete
 	int  messagetime = 0;      // time left for auto-remove messages
 	int  usedinv = 0;          // inventory item last used
-	int  inv_top = 0;
-	int  inv_numdisp = 0;
-	int  obsolete_inv_numorder = 0;
-	int  inv_numinline = 0;
+	// Following inv_* variables are legacy variables controlling
+	// the single player's inventory (used prior to supporting custom InvWindow controls).
+	int  inv_top = 0;       // topmost index of displayed player's inventory
+	int  inv_numdisp = 0;   // number of player's items displayed at once
+	int  inv_numorder = 0;  // total number of player's items
+	int  inv_numinline = 0; // number of items displayed on a single inventory row
 	int  text_speed = 0;       // how quickly text is removed
 	int  sierra_inv_color = 0; // background used to paint defualt inv window
 	int  talkanim_speed = 0;   // animation speed of talking anims
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index a07df2140b5..9f54edfdbba 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -476,7 +476,7 @@ void update_invorder() {
 		}
 	}
 	// backwards compatibility
-	_GP(play).obsolete_inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
+	_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
 	GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
 }
 
@@ -486,7 +486,7 @@ void add_inventory(int inum) {
 
 	Character_AddInventory(_G(playerchar), &_G(scrInv)[inum], SCR_NO_VALUE);
 
-	_GP(play).obsolete_inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
+	_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
 }
 
 void lose_inventory(int inum) {
@@ -495,7 +495,7 @@ void lose_inventory(int inum) {
 
 	Character_LoseInventory(_G(playerchar), &_G(scrInv)[inum]);
 
-	_GP(play).obsolete_inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
+	_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
 }
 
 void AddInventoryToCharacter(int charid, int inum) {
diff --git a/engines/ags/engine/ac/gui_inv.cpp b/engines/ags/engine/ac/gui_inv.cpp
index d80e71955e9..00f1fd0e515 100644
--- a/engines/ags/engine/ac/gui_inv.cpp
+++ b/engines/ags/engine/ac/gui_inv.cpp
@@ -54,7 +54,7 @@ void GUIInvWindow::Draw(Bitmap *ds, int x, int y) {
 	// TODO: find a way to not have this inside GUIInvWindow::Draw!
 	_GP(play).inv_numinline = ColCount;
 	_GP(play).inv_numdisp = RowCount * ColCount;
-	_GP(play).obsolete_inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
+	_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
 	// if the user changes top_inv_item, switch into backwards compat mode
 	if (_GP(play).inv_top != 0)
 		_GP(play).inv_backwards_compatibility = 1;
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 3bdd1bbd3d1..89d84a41092 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -620,7 +620,7 @@ void engine_init_game_settings() {
 	_GP(play).debug_mode = _GP(game).options[OPT_DEBUGMODE];
 	_GP(play).inv_top = 0;
 	_GP(play).inv_numdisp = 0;
-	_GP(play).obsolete_inv_numorder = 0;
+	_GP(play).inv_numorder = 0;
 	_GP(play).text_speed = 15;
 	_GP(play).text_min_display_time_ms = 1000;
 	_GP(play).ignore_user_input_after_text_timeout_ms = 500;


Commit: b318d39e0b5539f5b95f0bc93c7da4874de5b311
    https://github.com/scummvm/scummvm/commit/b318d39e0b5539f5b95f0bc93c7da4874de5b311
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tiny cleanup in ReadDynamicSprites()

>From upstream b257a0cd32a84fbb63d6b03cfbd10f3cc5732caa

Changed paths:
    engines/ags/engine/game/savegame_components.cpp


diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index e649ea85153..090ff06634d 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -750,8 +750,7 @@ HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, const PreservedPa
 	for (int i = 0; i < spr_count; ++i) {
 		int id = in->ReadInt32();
 		int flags = in->ReadInt32();
-		add_dynamic_sprite(id, read_serialized_bitmap(in));
-		_GP(game).SpriteInfos[id].Flags = flags;
+		add_dynamic_sprite(id, read_serialized_bitmap(in), (flags & SPF_ALPHACHANNEL) != 0);
 	}
 	return err;
 }


Commit: f22600c02177b82cee1b7111a73248d27a24053e
    https://github.com/scummvm/scummvm/commit/f22600c02177b82cee1b7111a73248d27a24053e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.8)

>From upstream 8fa320fea25d141e443fe8970430959a7805e9c2

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 2ea10615499..e16c693070d 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.7"
+#define ACI_VERSION_STR      "3.6.1.8"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.7
+#define ACI_VERSION_MSRC_DEF  3.6.1.8
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 5b66b860af79484d417170e2003b0a114553144b
    https://github.com/scummvm/scummvm/commit/5b66b860af79484d417170e2003b0a114553144b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: separate functions for getting game's set and real fps

>From upstream cacaa9be6f7a0194b89fd2255acc9b8b612622b4

Changed paths:
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/main/game_run.h
    engines/ags/engine/main/update.cpp
    engines/ags/engine/media/audio/audio.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index bbb0148f24f..8e971544660 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -449,7 +449,7 @@ int GetTextDisplayLength(const char *text) {
 
 int GetTextDisplayTime(const char *text, int canberel) {
 	int uselen = 0;
-	auto fpstimer = ::lround(get_current_fps());
+	auto fpstimer = ::lround(get_game_fps());
 
 	// if it's background speech, make it stay relative to game speed
 	if ((canberel == 1) && (_GP(play).bgspeech_game_speed == 1))
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 71cfbe86100..244f00a96ed 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -31,6 +31,7 @@
 #include "ags/engine/ac/display.h"
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/draw_software.h"
+#include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
@@ -66,8 +67,8 @@
 #include "ags/engine/gfx/gfx_util.h"
 #include "ags/engine/gfx/graphics_driver.h"
 #include "ags/engine/gfx/blender.h"
+#include "ags/engine/main/game_run.h"
 #include "ags/engine/media/audio/audio_system.h"
-#include "ags/engine/ac/game.h"
 #include "ags/ags.h"
 #include "ags/globals.h"
 
@@ -1686,6 +1687,7 @@ void draw_fps(const Rect &viewport) {
 
 	char fps_buffer[60];
 	// Don't display fps if we don't have enough information (because loop count was just reset)
+	float fps = get_real_fps();
 	if (!isnan(_G(fps))) {
 		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", _G(fps), base_buffer);
 	} else {
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 70f6f63d2a7..39b72efdc2e 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -389,7 +389,7 @@ void SetGameSpeed(int newspd) {
 }
 
 int GetGameSpeed() {
-	return ::lround(get_current_fps()) - _GP(play).game_speed_modifier;
+	return ::lround(get_game_fps()) - _GP(play).game_speed_modifier;
 }
 
 int SetGameOption(int opt, int newval) {
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index e154a097efe..b2373684b35 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -333,12 +333,6 @@ bool run_service_key_controls(KeyInput &out_key) {
 		quit("!|");
 	}
 
-	// debug console
-	if ((agskey == '`') && (_GP(play).debug_mode > 0)) {
-		_G(display_console) = !_G(display_console);
-		return false;
-	}
-
 	if ((agskey == eAGSKeyCodeCtrlE) && (_G(display_fps) == kFPS_Forced)) {
 		// if --fps paramter is used, Ctrl+E will max out frame rate
 		setTimerFps(isTimerFpsMaxed() ? _G(frames_per_second) : 1000);
@@ -770,7 +764,7 @@ static void game_loop_update_fps() {
 	}
 }
 
-float get_current_fps() {
+float get_game_fps() {
 	// if we have maxed out framerate then return the frame rate we're seeing instead
 	// fps must be greater that 0 or some timings will take forever.
 	if (isTimerFpsMaxed() && _G(fps) > 0.0f) {
@@ -779,6 +773,10 @@ float get_current_fps() {
 	return _G(frames_per_second);
 }
 
+float get_real_fps() {
+	return _G(fps);
+}
+
 void set_loop_counter(unsigned int new_counter) {
 	_G(loopcounter) = new_counter;
 	_G(t1) = AGS_Clock::now();
@@ -930,7 +928,7 @@ static void UpdateMouseOverLocation() {
 	}
 }
 
-// Checks if user interface should remain disabled for now
+
 // Checks if user interface should remain disabled for now
 static bool ShouldStayInWaitMode() {
 	if (_G(restrict_until).type == 0)
diff --git a/engines/ags/engine/main/game_run.h b/engines/ags/engine/main/game_run.h
index 033713e209e..02f1fc15f91 100644
--- a/engines/ags/engine/main/game_run.h
+++ b/engines/ags/engine/main/game_run.h
@@ -57,7 +57,9 @@ void UpdateGameAudioOnly();
 void UpdateCursorAndDrawables();
 // Gets current logical game FPS, this is normally a fixed number set in script;
 // in case of "maxed fps" mode this function returns real measured FPS.
-float get_current_fps();
+float get_game_fps();
+// Gets real fps, calculated based on the game performance.
+float get_real_fps();
 // Runs service key controls, returns false if no key was pressed or key input was claimed by the engine,
 // otherwise returns true and provides a keycode.
 bool run_service_key_controls(KeyInput &kgn);
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 244dca18b0a..71715a49214 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -257,7 +257,7 @@ void update_speech_and_messages() {
 		if (!_GP(play).speech_in_post_state && (_GP(play).fast_forward == 0) && (_GP(play).messagetime < 1)) {
 			_GP(play).speech_in_post_state = true;
 			if (_GP(play).speech_display_post_time_ms > 0) {
-				_GP(play).messagetime = ::lround(_GP(play).speech_display_post_time_ms * get_current_fps() / 1000.0f);
+				_GP(play).messagetime = ::lround(_GP(play).speech_display_post_time_ms * get_game_fps() / 1000.0f);
 			}
 		}
 
diff --git a/engines/ags/engine/media/audio/audio.cpp b/engines/ags/engine/media/audio/audio.cpp
index 7741e2637e1..2b8a9398047 100644
--- a/engines/ags/engine/media/audio/audio.cpp
+++ b/engines/ags/engine/media/audio/audio.cpp
@@ -833,7 +833,7 @@ void update_audio_system_on_game_loop() {
 					// we want to crossfade, and we know how far through
 					// the tune we are
 					int takesSteps = calculate_max_volume() / _GP(game).options[OPT_CROSSFADEMUSIC];
-					int takesMs = ::lround(takesSteps * 1000.0f / get_current_fps());
+					int takesMs = ::lround(takesSteps * 1000.0f / get_game_fps());
 					if (curpos >= muslen - takesMs)
 						play_next_queued();
 				}
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 27dc14c6fdb..4148bdcc3da 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -163,7 +163,6 @@ Globals::Globals() {
 	_pushbuttonlightcolor = COL253;
 
 	// debug.cpp globals
-	_fps = std::numeric_limits<float>::quiet_NaN();
 	_display_fps = kFPS_Hide;
 	_debug_line = new String[DEBUG_CONSOLE_NUMLINES];
 	_DebugMsgBuff = new std::unique_ptr<AGS::Engine::MessageBuffer>();
@@ -265,6 +264,9 @@ Globals::Globals() {
 	_StaticInventoryArray = new StaticArray();
 	_StaticDialogArray = new StaticArray();
 
+	// game_run.cpp globals
+	_fps = std::numeric_limits<float>::quiet_NaN();
+
 	_scummvmGfxFilter = new AGS::Engine::GfxFilterInfo("StdScale", "Nearest-neighbour");
 
 	// gfxfilter_aad3d.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 7521d7b5672..8b277948b04 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -513,7 +513,6 @@ public:
 	String *_debug_line;
 	int _first_debug_line = 0, _last_debug_line = 0, _display_console = 0;
 
-	float _fps;
 	int _display_fps;
 	std::unique_ptr<AGS::Engine::MessageBuffer> *_DebugMsgBuff;
 	std::unique_ptr<AGS::Engine::LogFile> *_DebugLogFile;
@@ -842,6 +841,7 @@ public:
 		int data2 = 0;
 	} _restrict_until;
 
+	float _fps;
 	unsigned int _loopcounter = 0;
 	unsigned int _lastcounter = 0;
 	int _numEventsAtStartOfFunction = 0;


Commit: 9c633e250e99f0eb7df8f590977326b5118200c2
    https://github.com/scummvm/scummvm/commit/9c633e250e99f0eb7df8f590977326b5118200c2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: cut out DebugConsole implementation, as unusable

This "console" implementation has multiple issues, making it unusable in practice:
- saves and displays only few lines, cannot scroll back;
while multiple game messages may be printed per a single game frame;
- messages cannot be filtered at runtime;
- uses a game font of a fixed number, which in unreliable (as the font may not be suitable);

There's not much value in its code; if a new console will be wanted in the future, then it
would be best to reimplement one from scratch, but with a good planning first.
>From upstream c303590efca4632fef0fb2ab3143faba6e605ba8

Changed paths:
  R engines/ags/engine/debugging/console_output_target.cpp
  R engines/ags/engine/debugging/console_output_target.h
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/debugging/debug.cpp
    engines/ags/engine/debugging/debug_log.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/module.mk


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 244f00a96ed..373be3289e5 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2112,34 +2112,6 @@ void construct_engine_overlay() {
 	const Rect &viewport = RectWH(_GP(game).GetGameRes());
 	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform());
 
-	// draw the debug console, if appropriate
-	if ((_GP(play).debug_mode > 0) && (_G(display_console) != 0)) {
-		const int font = FONT_NORMAL;
-		int ypp = 1;
-		int txtspacing = get_font_linespacing(font);
-		int barheight = get_text_lines_surf_height(font, DEBUG_CONSOLE_NUMLINES - 1) + 4;
-
-		if (_G(debugConsoleBuffer) == nullptr) {
-			_G(debugConsoleBuffer) = CreateCompatBitmap(viewport.GetWidth(), barheight);
-		}
-
-		color_t draw_color = _G(debugConsoleBuffer)->GetCompatibleColor(15);
-		_G(debugConsoleBuffer)->FillRect(Rect(0, 0, viewport.GetWidth() - 1, barheight), draw_color);
-		color_t text_color = _G(debugConsoleBuffer)->GetCompatibleColor(16);
-		for (int jj = _G(first_debug_line); jj != _G(last_debug_line); jj = (jj + 1) % DEBUG_CONSOLE_NUMLINES) {
-			wouttextxy(_G(debugConsoleBuffer), 1, ypp, font, text_color, _G(debug_line)[jj].GetCStr());
-			ypp += txtspacing;
-		}
-
-		if (_G(debugConsole) == nullptr)
-			_G(debugConsole) = _G(gfxDriver)->CreateDDBFromBitmap(_G(debugConsoleBuffer), false, true);
-		else
-			_G(gfxDriver)->UpdateDDBFromBitmap(_G(debugConsole), _G(debugConsoleBuffer), false);
-
-		_G(gfxDriver)->DrawSprite(0, 0, _G(debugConsole));
-		invalidate_sprite_glob(0, 0, _G(debugConsole));
-	}
-
 	if (_G(display_fps) != kFPS_Hide)
 		draw_fps(viewport);
 
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index f68a5956897..301714cee64 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -187,7 +187,6 @@ int Game_GetDialogCount() {
 
 void set_debug_mode(bool on) {
 	_GP(play).debug_mode = on ? 1 : 0;
-	debug_set_console(on);
 }
 
 void set_game_speed(int new_fps) {
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index d6399ad1b61..fd020db17f6 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -90,7 +90,6 @@ const int LegacyRoomVolumeFactor = 30;
 #define SCR_COLOR_TRANSPARENT -1
 
 
-#define DEBUG_CONSOLE_NUMLINES 6
 #define TXT_SCOREBAR        29
 #define MAXSCORE _GP(play).totalscore
 
diff --git a/engines/ags/engine/debugging/console_output_target.cpp b/engines/ags/engine/debugging/console_output_target.cpp
deleted file mode 100644
index 91be9ea2de8..00000000000
--- a/engines/ags/engine/debugging/console_output_target.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/* 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 "ags/engine/debugging/console_output_target.h"
-#include "ags/engine/debugging/debug_log.h"
-#include "ags/globals.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Engine {
-
-ConsoleOutputTarget::ConsoleOutputTarget() {
-}
-
-ConsoleOutputTarget::~ConsoleOutputTarget() {}
-
-void ConsoleOutputTarget::PrintMessage(const DebugMessage &msg) {
-	// limit number of characters for console
-	// TODO: is there a way to find out how many characters can fit in?
-	_G(debug_line)[_G(last_debug_line)] = msg.Text.Left(99);
-
-	_G(last_debug_line) = (_G(last_debug_line) + 1) % DEBUG_CONSOLE_NUMLINES;
-	if (_G(last_debug_line) == _G(first_debug_line))
-		_G(first_debug_line) = (_G(first_debug_line) + 1) % DEBUG_CONSOLE_NUMLINES;
-}
-
-} // namespace Engine
-} // namespace AGS
-} // namespace AGS3
diff --git a/engines/ags/engine/debugging/console_output_target.h b/engines/ags/engine/debugging/console_output_target.h
deleted file mode 100644
index 2e602a4dbbc..00000000000
--- a/engines/ags/engine/debugging/console_output_target.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* 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/>.
- *
- */
-
-//=============================================================================
-//
-// ConsoleOutputTarget prints messages onto in-game console GUI (available
-// only if the game was compiled in debug mode).
-//
-//=============================================================================
-
-#ifndef AGS_ENGINE_DEBUGGING_CONSOLE_OUTPUT_TARGET_H
-#define AGS_ENGINE_DEBUGGING_CONSOLE_OUTPUT_TARGET_H
-
-#include "ags/shared/debugging/output_handler.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Engine {
-
-using Shared::String;
-using Shared::DebugMessage;
-
-class ConsoleOutputTarget : public AGS::Shared::IOutputHandler {
-public:
-	ConsoleOutputTarget();
-	virtual ~ConsoleOutputTarget();
-
-	void PrintMessage(const DebugMessage &msg) override;
-};
-
-} // namespace Engine
-} // namespace AGS
-} // namespace AGS3
-
-#endif
diff --git a/engines/ags/engine/debugging/debug.cpp b/engines/ags/engine/debugging/debug.cpp
index 7ce9e1946ab..ac465142855 100644
--- a/engines/ags/engine/debugging/debug.cpp
+++ b/engines/ags/engine/debugging/debug.cpp
@@ -32,7 +32,6 @@
 #include "ags/engine/debugging/debugger.h"
 #include "ags/shared/debugging/debug_manager.h"
 #include "ags/shared/debugging/out.h"
-#include "ags/engine/debugging/console_output_target.h"
 #include "ags/engine/debugging/log_file.h"
 #include "ags/engine/debugging/message_buffer.h"
 #include "ags/engine/main/config.h"
@@ -91,7 +90,6 @@ void send_message_to_debugger(const std::vector<std::pair<String, String> > &tag
 static const char *OutputMsgBufID = "buffer";
 static const char *OutputFileID = "file";
 static const char *OutputSystemID = "stdout";
-static const char *OutputGameConsoleID = "console";
 
 PDebugOutput create_log_output(const String &name, const String &path = "", LogFile::OpenMode open_mode = LogFile::kLogFile_Overwrite) {
 	// Else create new one, if we know this ID
@@ -110,9 +108,6 @@ PDebugOutput create_log_output(const String &name, const String &path = "", LogF
 		Debug::Printf(kDbgMsg_Info, "Logging to %s", logfile_path.GetCStr());
 		auto dbgout = _GP(DbgMgr).RegisterOutput(OutputFileID, _GP(DebugLogFile).get(), kDbgMsg_None);
 		return dbgout;
-	} else if (name.CompareNoCase(OutputGameConsoleID) == 0) {
-		_GP(DebugConsole).reset(new ConsoleOutputTarget());
-		return _GP(DbgMgr).RegisterOutput(OutputGameConsoleID, _GP(DebugConsole).get(), kDbgMsg_None);
 	}
 	return nullptr;
 }
@@ -243,17 +238,6 @@ void apply_debug_config(const ConfigTree &cfg) {
 #endif
 	});
 
-	// Init game console if the game was compiled in Debug mode or is run in test mode
-	if (_GP(game).options[OPT_DEBUGMODE] != 0 || (_G(debug_flags) & DBG_DEBUGMODE) != 0) {
-		apply_log_config(cfg, OutputGameConsoleID,
-		                 /* defaults */
-		true, {
-			DbgGroupOption(kDbgGroup_Main, kDbgMsg_All),
-			DbgGroupOption(kDbgGroup_Game, kDbgMsg_All)
-		});
-		debug_set_console(true);
-	}
-
 	// If the game was compiled in Debug mode *and* there's no regular file log,
 	// then open "warnings.log" for printing script warnings.
 	if (_GP(game).options[OPT_DEBUGMODE] != 0 && !_GP(DebugLogFile)) {
@@ -273,12 +257,6 @@ void shutdown_debug() {
 
 	_GP(DebugMsgBuff).reset();
 	_GP(DebugLogFile).reset();
-	_GP(DebugConsole).reset();
-}
-
-void debug_set_console(bool enable) {
-	if (_GP(DebugConsole))
-		_GP(DbgMgr).GetOutput(OutputGameConsoleID)->SetEnabled(enable);
 }
 
 // Prepends message text with current room number and running script info, then logs result
diff --git a/engines/ags/engine/debugging/debug_log.h b/engines/ags/engine/debugging/debug_log.h
index 51caf6805e4..6d740712b90 100644
--- a/engines/ags/engine/debugging/debug_log.h
+++ b/engines/ags/engine/debugging/debug_log.h
@@ -36,8 +36,6 @@ void init_debug(const AGS::Shared::ConfigTree &cfg, bool stderr_only);
 void apply_debug_config(const AGS::Shared::ConfigTree &cfg);
 void shutdown_debug();
 
-void debug_set_console(bool enable);
-
 // prints debug messages of given type tagged with kDbgGroup_Game,
 // prepending it with current room number and script position info
 void debug_script_print(AGS::Shared::MessageType mt, const char *msg, ...);
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 4148bdcc3da..e2d6b196da4 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -77,7 +77,6 @@
 #include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/ac/dynobj/script_system.h"
 #include "ags/engine/ac/statobj/static_array.h"
-#include "ags/engine/debugging/console_output_target.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/engine/debugging/log_file.h"
 #include "ags/engine/debugging/message_buffer.h"
@@ -164,10 +163,8 @@ Globals::Globals() {
 
 	// debug.cpp globals
 	_display_fps = kFPS_Hide;
-	_debug_line = new String[DEBUG_CONSOLE_NUMLINES];
 	_DebugMsgBuff = new std::unique_ptr<AGS::Engine::MessageBuffer>();
 	_DebugLogFile = new std::unique_ptr<AGS::Engine::LogFile>();
-	_DebugConsole = new std::unique_ptr<AGS::Engine::ConsoleOutputTarget>();
 
 	// debug_manager.cpp globals
 	_DbgMgr = new AGS::Shared::DebugManager();
@@ -443,10 +440,8 @@ Globals::~Globals() {
 	delete[] _oswi;
 
 	// debug.cpp globals
-	delete[] _debug_line;
 	delete _DebugMsgBuff;
 	delete _DebugLogFile;
-	delete _DebugConsole;
 
 	// debug_manager.cpp globals
 	delete _DbgMgr;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 8b277948b04..01719274cdf 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -510,13 +510,9 @@ public:
 
 	int _debug_flags = 0;
 
-	String *_debug_line;
-	int _first_debug_line = 0, _last_debug_line = 0, _display_console = 0;
-
 	int _display_fps;
 	std::unique_ptr<AGS::Engine::MessageBuffer> *_DebugMsgBuff;
 	std::unique_ptr<AGS::Engine::LogFile> *_DebugLogFile;
-	std::unique_ptr<AGS::Engine::ConsoleOutputTarget> *_DebugConsole;
 
 	/**@}*/
 
@@ -586,7 +582,6 @@ public:
 	AGS::Engine::IGraphicsDriver *_gfxDriver = nullptr;
 	AGS::Engine::IDriverDependantBitmap *_blankImage = nullptr;
 	AGS::Engine::IDriverDependantBitmap *_blankSidebarImage = nullptr;
-	AGS::Engine::IDriverDependantBitmap *_debugConsole = nullptr;
 
 	// actsps is used for temporary storage of the bitamp and texture
 	// of the latest version of the sprite (room objects and characters);
@@ -611,7 +606,6 @@ public:
 	bool _current_background_is_dirty = false;
 	// Room background sprite
 	AGS::Engine::IDriverDependantBitmap *_roomBackgroundBmp = nullptr;
-	AGS::Shared::Bitmap *_debugConsoleBuffer = nullptr;
 	// whether there are currently remnants of a DisplaySpeech
 	bool _screen_is_dirty = false;
 	AGS::Shared::Bitmap *_raw_saved_screen = nullptr;
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 3238ce660e7..2734cfa69df 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -224,7 +224,6 @@ MODULE_OBJS = \
 	engine/ac/dynobj/script_view_frame.o \
 	engine/ac/statobj/ags_static_object.o \
 	engine/ac/statobj/static_array.o \
-	engine/debugging/console_output_target.o \
 	engine/debugging/debug.o \
 	engine/debugging/file_based_ags_debugger.o \
 	engine/debugging/log_file.o \


Commit: 84f74939597f0cc5b53e71e715e67fdcffacb5aa
    https://github.com/scummvm/scummvm/commit/84f74939597f0cc5b53e71e715e67fdcffacb5aa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support scaling character sprite offsets after char's own zoom

Historically, neither Character.z (vertical offset), nor picture offsets (ones set by Character.LockViewAligned() and Character.LockViewOffset())
 were not scaled along with the character's sprite.
Although it seems like that would be a right thing to do in a majority of practical applications of these properties.

This change introduces a separate zoom factor for these parameters. It's done separately to be able to maintain backwards compatibility.
 In backwards mode this factor is always 100 (%), in the new mode it is always set equal to regular character's zoom (whether got from
  a manual property, or a walkable area scaling).
The switch between modes is controlled by OPT_SCALECHAROFFSETS. This supposed to be always set in the new games. But this is also
 a backup opportunity to disable this behavior, if a user finds it making it impossible to achieve a previously possible effect: in such case
  one may disable this when compiling a game, or call SetGameOption(OPT_SCALECHAROFFSETS, 0) in script.

Code-wise, I had to move get_effective_y() method to CharacterExtras class, which makes more sense, as that's a runtime character class,
 as opposed to CharacterInfo which is a game data serialization class, shared with the Editor.

There's still alot of potential for refactor and tidying the code, but I'd leave that for another task.

>From upstream a2b0c88379f37fdaf44155e37f372b7de725db21

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 42472b6b4dd..d2f2c9095f7 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -511,7 +511,7 @@ int Character_IsCollidingWithObject(CharacterInfo *chin, ScriptObject *objid) {
 	int charWidth = charpic->GetWidth();
 	int charHeight = charpic->GetHeight();
 	int o2x = chin->x - game_to_data_coord(charWidth) / 2;
-	int o2y = chin->get_effective_y() - 5;  // only check feet
+	int o2y = _GP(charextra)[chin->index_id].GetEffectiveY(chin) - 5; // only check feet
 
 	if ((o2x >= o1x - game_to_data_coord(charWidth)) &&
 	        (o2x <= o1x + game_to_data_coord(objWidth)) &&
@@ -2139,17 +2139,18 @@ void update_character_scale(int charid) {
 		chin.frame = 0;
 	}
 
-	int zoom, scale_width, scale_height;
+	int zoom, zoom_offs, scale_width, scale_height;
 	update_object_scale(zoom, scale_width, scale_height,
 						chin.x, chin.y, _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic,
 						chex.zoom, (chin.flags & CHF_MANUALSCALING) == 0);
+	zoom_offs = (_GP(game).options[OPT_SCALECHAROFFSETS] != 0) ? zoom : 100;
 
 	// Calculate the X & Y co-ordinates of where the sprite will be;
 	// for the character sprite's origin is at the bottom-mid of a sprite.
 	const int atxp = (data_to_game_coord(chin.x)) - scale_width / 2;
 	const int atyp = (data_to_game_coord(chin.y) - scale_height)
 					 // adjust the Y positioning for the character's Z co-ord
-					 - data_to_game_coord(chin.z);
+					 - (data_to_game_coord(chin.z) * zoom_offs / 100);
 
 	// Save calculated properties
 	chex.width = scale_width;
@@ -2157,6 +2158,7 @@ void update_character_scale(int charid) {
 	chin.actx = atxp;
 	chin.acty = atyp;
 	chex.zoom = zoom;
+	chex.zoom_offs = zoom_offs;
 }
 
 int is_pos_on_character(int xx, int yy) {
@@ -2180,7 +2182,7 @@ int is_pos_on_character(int xx, int yy) {
 		if (usewid == 0) usewid = _GP(game).SpriteInfos[sppic].Width;
 		if (usehit == 0) usehit = _GP(game).SpriteInfos[sppic].Height;
 		int xxx = chin->x - game_to_data_coord(usewid) / 2;
-		int yyy = chin->get_effective_y() - game_to_data_coord(usehit);
+		int yyy = _GP(charextra)[cc].GetEffectiveY(chin) - game_to_data_coord(usehit);
 
 		int mirrored = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE;
 		Bitmap *theImage = GetCharacterImage(cc, &mirrored);
@@ -2466,7 +2468,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		if (tdyp < 0) {
 			int sppic = _GP(views)[speakingChar->view].loops[speakingChar->loop].frames[0].pic;
 			int height = (_GP(charextra)[aschar].height < 1) ? _GP(game).SpriteInfos[sppic].Height : _GP(charextra)[aschar].height;
-			tdyp = view->RoomToScreen(0, data_to_game_coord(_GP(game).chars[aschar].get_effective_y()) - height).first.Y
+			tdyp = view->RoomToScreen(0, data_to_game_coord(_GP(charextra)[aschar].GetEffectiveY(speakingChar)) - height).first.Y
 			       - get_fixed_pixel_size(5);
 			if (isThought) // if it's a thought, lift it a bit further up
 				tdyp -= get_fixed_pixel_size(10);
@@ -2835,6 +2837,12 @@ int update_lip_sync(int talkview, int talkloop, int *talkframeptr) {
 	return talkwait;
 }
 
+void restore_characters() {
+	for (int i = 0; i < _GP(game).numcharacters; ++i) {
+		_GP(charextra)[i].zoom_offs = (_GP(game).options[OPT_SCALECHAROFFSETS] != 0) ? _GP(charextra)[i].zoom : 100;
+	}
+}
+
 Rect GetCharacterRoomBBox(int charid, bool use_frame_0) {
 	int width, height;
 	const CharacterExtras &chex = _GP(charextra)[charid];
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index e48a7e48a8f..2927b3a64cc 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -230,6 +230,9 @@ int get_character_currently_talking();
 void DisplaySpeech(const char *texx, int aschar);
 int update_lip_sync(int talkview, int talkloop, int *talkframeptr);
 
+// Recalculate dynamic character properties, e.g. after restoring a game save
+void restore_characters();
+
 // Calculates character's bounding box in room coordinates (takes only in-room transform into account)
 // use_frame_0 optionally tells to use frame 0 of current loop instead of current frame.
 Rect GetCharacterRoomBBox(int charid, bool use_frame_0 = false);
diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index 1db2b286c99..8588cbf762d 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -27,6 +27,10 @@ namespace AGS3 {
 
 using AGS::Shared::Stream;
 
+int CharacterExtras::GetEffectiveY(CharacterInfo *chi) const {
+	return chi->y - (chi->z * zoom_offs) / 100;
+}
+
 int CharacterExtras::GetFrameSoundVolume(CharacterInfo *chi) const {
 	return AGS3::CalcFrameSoundVolume(
 		anim_volume, cur_anim_volume,
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index 81b015a7d49..744a57b1125 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -58,6 +58,14 @@ struct CharacterExtras {
 	int   anim_volume = 100; // default animation volume (relative factor)
 	int   cur_anim_volume = 100; // current animation sound volume (relative factor)
 
+	// Following fields are deriatives of the above (calculated from these
+	// and other factors), and hence are not serialized.
+	//
+	// zoom factor of sprite offsets, fixed at 100 in backwards compatible mode
+	int zoom_offs = 100;
+
+	int GetEffectiveY(CharacterInfo *chi) const; // return Y - Z
+
 	// Calculate wanted frame sound volume based on multiple factors
 	int GetFrameSoundVolume(CharacterInfo *chi) const;
 	// Process the current animation frame for the character:
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index d77bf3598b3..2bb9a6cebd8 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -43,10 +43,6 @@ using namespace AGS::Shared;
 
 #define Random __Rand
 
-int CharacterInfo::get_effective_y() const {
-	return y - z;
-}
-
 int CharacterInfo::get_baseline() const {
 	if (baseline < 1)
 		return y;
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 373be3289e5..3b06c1a187b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1516,8 +1516,8 @@ void prepare_characters_for_drawing() {
 		ObjTexture &actsp = _GP(actsps)[charid + ACTSP_OBJSOFF];
 
 		// Calculate sprite top-left position in the room and baseline
-		const int atx = chin.actx + chin.pic_xoffs;
-		const int aty = chin.acty + chin.pic_yoffs;
+		const int atx = chin.actx + chin.pic_xoffs * chex.zoom_offs / 100;
+		const int aty = chin.acty + chin.pic_yoffs * chex.zoom_offs / 100;
 		int usebasel = chin.get_baseline();
 
         // Generate raw bitmap in ObjTexture and store parameters in ObjectCache.
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index be08365c926..8e901ab83a3 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -459,8 +459,8 @@ int GetThingRect(int thing, _Rect *rect) {
 		int charwid = game_to_data_coord(GetCharacterWidth(thing));
 		rect->x1 = _GP(game).chars[thing].x - (charwid / 2);
 		rect->x2 = rect->x1 + charwid;
-		rect->y1 = _GP(game).chars[thing].get_effective_y() - game_to_data_coord(GetCharacterHeight(thing));
-		rect->y2 = _GP(game).chars[thing].get_effective_y();
+		rect->y1 = _GP(charextra)[thing].GetEffectiveY(&_GP(game).chars[thing]) - game_to_data_coord(GetCharacterHeight(thing));
+		rect->y2 = _GP(charextra)[thing].GetEffectiveY(&_GP(game).chars[thing]);
 	} else if (is_valid_object(thing - OVERLAPPING_OBJECT)) {
 		int objid = thing - OVERLAPPING_OBJECT;
 		if (_G(objs)[objid].on != 1)
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 55260658274..c0c5cfda089 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -462,7 +462,7 @@ Point get_overlay_position(const ScreenOverlay &over) {
 		const int height = (_GP(charextra)[charid].height < 1) ? _GP(game).SpriteInfos[charpic].Height : _GP(charextra)[charid].height;
 		Point screenpt = view->RoomToScreen(
 			data_to_game_coord(_GP(game).chars[charid].x),
-			data_to_game_coord(_GP(game).chars[charid].get_effective_y()) - height).first;
+			data_to_game_coord(_GP(charextra)[charid].GetEffectiveY(&_GP(game).chars[charid])) - height).first;
 		Bitmap *pic = over.GetImage();
 		int tdxp = std::max(0, screenpt.X - pic->GetWidth() / 2);
 		int tdyp = screenpt.Y - get_fixed_pixel_size(5);
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 93e149e687a..522969aac6b 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -621,6 +621,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	adjust_fonts_for_render_mode(_GP(game).options[OPT_ANTIALIASFONTS] != 0);
 
+	restore_characters();
 	restore_overlays();
 
 	GUI::MarkAllGUIForUpdate(true, true);
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index ecd68430554..60c5aac9bc4 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -130,7 +130,6 @@ struct CharacterInfo {
 	char  scrname[MAX_SCRIPT_NAME_LEN];
 	int8  on;
 
-	int get_effective_y() const;   // return Y - Z
 	int get_baseline() const;      // return baseline, or Y if not set
 	int get_blocking_top() const;    // return Y - BlockingHeight/2
 	int get_blocking_bottom() const; // return Y + BlockingHeight/2
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 5b67b07946e..79dfa305bdc 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -89,7 +89,8 @@ namespace AGS3 {
 #define OPT_GAMETEXTENCODING 49 // how the text in the game data should be interpreted
 #define OPT_KEYHANDLEAPI    50 // key handling mode (old/new)
 #define OPT_CUSTOMENGINETAG 51 // custom engine tag (for overriding behavior)
-#define OPT_HIGHESTOPTION   OPT_CUSTOMENGINETAG
+#define OPT_SCALECHAROFFSETS 52 // apply character scaling to the sprite offsets (z, locked offs)
+#define OPT_HIGHESTOPTION   OPT_SCALECHAROFFSETS
 #define OPT_NOMODMUSIC      98 // [DEPRECATED]
 #define OPT_LIPSYNCTEXT     99
 


Commit: d03da046754bb242fc226d949704e388ba56ff75
    https://github.com/scummvm/scummvm/commit/d03da046754bb242fc226d949704e388ba56ff75
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: hide GUIButton's image properties behind the get/set methods

from upstream 56659d75d0bbf31b4679be89e9b9a3bfbc259f97

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_button.cpp
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_button.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index e20e4911411..1b4bac5d0c3 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -46,13 +46,9 @@ using namespace AGS::Shared;
 
 // Update the actual button's image from the current animation frame
 void UpdateButtonState(const AnimatingGUIButton &abtn) {
-	_GP(guibuts)[abtn.buttonid].Image = _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic;
-	if (_GP(guibuts)[abtn.buttonid].CurrentImage != _GP(guibuts)[abtn.buttonid].Image) {
-		_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
-		_GP(guibuts)[abtn.buttonid].MarkChanged();
-	}
-	_GP(guibuts)[abtn.buttonid].PushedImage = 0;
-	_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
+	_GP(guibuts)[abtn.buttonid].SetPushedImage(0);
+	_GP(guibuts)[abtn.buttonid].SetMouseOverImage(0);
+	_GP(guibuts)[abtn.buttonid].SetNormalImage(_GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic);
 }
 
 void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat, int blocking, int direction, int sframe, int volume) {
@@ -143,28 +139,24 @@ void Button_SetClipImage(GUIButton *butt, int newval) {
 
 int Button_GetGraphic(GUIButton *butt) {
 	// return currently displayed pic
-	if (butt->CurrentImage < 0)
-		return butt->Image;
-	return butt->CurrentImage;
+	if (butt->GetCurrentImage() < 0)
+		return butt->GetNormalImage();
+	return butt->GetCurrentImage();
 }
 
 int Button_GetMouseOverGraphic(GUIButton *butt) {
-	return butt->MouseOverImage;
+	return butt->GetMouseOverImage();
 }
 
 void Button_SetMouseOverGraphic(GUIButton *guil, int slotn) {
 	debug_script_log("GUI %d Button %d mouseover set to slot %d", guil->ParentId, guil->Id, slotn);
 
-	if ((guil->IsMouseOver != 0) && (guil->IsPushed == 0) && (guil->CurrentImage != slotn)) {
-		guil->CurrentImage = slotn;
-		guil->MarkChanged();
-	}
-	guil->MouseOverImage = slotn;
+	guil->SetMouseOverImage(slotn);
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
 int Button_GetNormalGraphic(GUIButton *butt) {
-	return butt->Image;
+	return butt->GetNormalImage();
 }
 
 void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
@@ -179,31 +171,23 @@ void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
 		height = _GP(game).SpriteInfos[slotn].Height;
 	}
 
-	if ((slotn != guil->Image) || (width != guil->Width) || (height != guil->Height)) {
-		// normal pic - update if mouse is not over, or if there's no MouseOverImage
-		if (((guil->IsMouseOver == 0) || (guil->MouseOverImage < 1)) && (guil->IsPushed == 0))
-			guil->CurrentImage = slotn;
-		guil->Image = slotn;
+	if ((slotn != guil->GetNormalImage()) || (width != guil->Width) || (height != guil->Height)) {
+		guil->SetNormalImage(slotn);
 		guil->Width = width;
 		guil->Height = height;
-		guil->MarkChanged();
 	}
 
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
 int Button_GetPushedGraphic(GUIButton *butt) {
-	return butt->PushedImage;
+	return butt->GetPushedImage();
 }
 
 void Button_SetPushedGraphic(GUIButton *guil, int slotn) {
 	debug_script_log("GUI %d Button %d pushed set to slot %d", guil->ParentId, guil->Id, slotn);
 
-	if (guil->IsPushed && (guil->CurrentImage != slotn)) {
-		guil->CurrentImage = slotn;
-		guil->MarkChanged();
-	}
-	guil->PushedImage = slotn;
+	guil->SetPushedImage(slotn);
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 8e971544660..f2ce82fcb4a 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -614,7 +614,7 @@ void do_corner(Bitmap *ds, int sprn, int x, int y, int offx, int offy) {
 
 int get_but_pic(GUIMain *guo, int indx) {
 	int butid = guo->GetControlID(indx);
-	return butid >= 0 ? _GP(guibuts)[butid].Image : 0;
+	return butid >= 0 ? _GP(guibuts)[butid].GetNormalImage() : 0;
 }
 
 void draw_button_background(Bitmap *ds, int xx1, int yy1, int xx2, int yy2, GUIMain *iep) {
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 301714cee64..6c719285e77 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1369,7 +1369,7 @@ void game_sprite_updated(int sprnum) {
 	}
 	// gui buttons
 	for (auto &but : _GP(guibuts)) {
-		if (but.CurrentImage == sprnum) {
+		if (but.GetCurrentImage() == sprnum) {
 			but.MarkChanged();
 		}
 	}
@@ -1410,15 +1410,15 @@ void game_sprite_deleted(int sprnum) {
 	}
 	// gui buttons
 	for (auto &but : _GP(guibuts)) {
-		if (but.Image == sprnum)
-			but.Image = 0;
-		if (but.MouseOverImage == sprnum)
-			but.MouseOverImage = 0;
-		if (but.PushedImage == sprnum)
-			but.PushedImage = 0;
-
-		if (but.CurrentImage == sprnum) {
-			but.CurrentImage = 0;
+		if (but.GetCurrentImage() == sprnum)
+			but.SetCurrentImage(0);
+		if (but.GetMouseOverImage() == sprnum)
+			but.SetMouseOverImage(0);
+		if (but.GetPushedImage() == sprnum)
+			but.SetPushedImage(0);
+
+		if (but.GetCurrentImage() == sprnum) {
+			but.SetCurrentImage(0);
 			but.MarkChanged();
 		}
 	}
diff --git a/engines/ags/engine/ac/global_button.cpp b/engines/ags/engine/ac/global_button.cpp
index f87346a06e7..c868d6ae9d4 100644
--- a/engines/ags/engine/ac/global_button.cpp
+++ b/engines/ags/engine/ac/global_button.cpp
@@ -68,17 +68,17 @@ int GetButtonPic(int guin, int objn, int ptype) {
 
 	if (ptype == 0) {
 		// currently displayed pic
-		if (guil->CurrentImage < 0)
-			return guil->Image;
-		return guil->CurrentImage;
+		if (guil->GetCurrentImage() < 0)
+			return guil->GetNormalImage();
+		return guil->GetCurrentImage();
 	} else if (ptype == 1) {
 		// nomal pic
-		return guil->Image;
+		return guil->GetNormalImage();
 	} else if (ptype == 2) {
 		// mouseover pic
-		return guil->MouseOverImage;
+		return guil->GetMouseOverImage();
 	} else { // pushed pic
-		return guil->PushedImage;
+		return guil->GetPushedImage();
 	}
 }
 
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index b23f697ddfe..28774a665ab 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -60,10 +60,10 @@ FrameAlignment ConvertLegacyButtonAlignment(LegacyButtonAlignment align) {
 
 
 GUIButton::GUIButton() {
-	Image = -1;
-	MouseOverImage = -1;
-	PushedImage = -1;
-	CurrentImage = -1;
+	_image = -1;
+	_mouseOverImage = -1;
+	_pushedImage = -1;
+	_currentImage = -1;
 	Font = 0;
 	TextColor = 0;
 	TextAlignment = kAlignTopCenter;
@@ -83,26 +83,42 @@ GUIButton::GUIButton() {
 }
 
 bool GUIButton::HasAlphaChannel() const {
-	return ((CurrentImage > 0) && is_sprite_alpha(CurrentImage)) ||
+	return ((_currentImage > 0) && is_sprite_alpha(_currentImage)) ||
 		(!_unnamed && is_font_antialiased(Font));
 }
 
+int32_t GUIButton::GetCurrentImage() const {
+	return _currentImage;
+}
+
+int32_t GUIButton::GetNormalImage() const {
+	return _image;
+}
+
+int32_t GUIButton::GetMouseOverImage() const {
+	return _mouseOverImage;
+}
+
+int32_t GUIButton::GetPushedImage() const {
+	return _pushedImage;
+}
+
+GUIButtonPlaceholder GUIButton::GetPlaceholder() const {
+	return _placeholder;
+}
+
 const String &GUIButton::GetText() const {
 	return _text;
 }
 
 bool GUIButton::IsImageButton() const {
-	return Image > 0;
+	return _image > 0;
 }
 
 bool GUIButton::IsClippingImage() const {
 	return (Flags & kGUICtrl_Clip) != 0;
 }
 
-GUIButtonPlaceholder GUIButton::GetPlaceholder() const {
-	return _placeholder;
-}
-
 Rect GUIButton::CalcGraphicRect(bool clipped) {
 	if (clipped)
 		return RectWH(0, 0, Width, Height);
@@ -113,8 +129,8 @@ Rect GUIButton::CalcGraphicRect(bool clipped) {
 		if (IsClippingImage())
 			return rc;
 		// Main button graphic
-		if (CurrentImage >= 0 && _GP(spriteset).DoesSpriteExist(CurrentImage))
-			rc = SumRects(rc, RectWH(0, 0, get_adjusted_spritewidth(CurrentImage), get_adjusted_spriteheight(CurrentImage)));
+		if (_currentImage >= 0 && _GP(spriteset).DoesSpriteExist(_currentImage))
+			rc = SumRects(rc, RectWH(0, 0, get_adjusted_spritewidth(_currentImage), get_adjusted_spriteheight(_currentImage)));
 		// Optionally merge with the inventory pic
 		if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
 			Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)),
@@ -155,8 +171,8 @@ void GUIButton::Draw(Bitmap *ds, int x, int y) {
 		draw_disabled = false;
 	}
 	// TODO: should only change properties in reaction to particular events
-	if (CurrentImage <= 0 || draw_disabled)
-		CurrentImage = Image;
+	if (_currentImage <= 0 || draw_disabled)
+		_currentImage = _image;
 
 	if (draw_disabled && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
 		// buttons off when disabled - no point carrying on
@@ -178,6 +194,38 @@ void GUIButton::SetClipImage(bool on) {
 		Flags &= ~kGUICtrl_Clip;
 }
 
+void GUIButton::SetCurrentImage(int32_t image) {
+	if (_currentImage == image)
+		return;
+
+	_currentImage = image;
+	MarkChanged();
+}
+
+void GUIButton::SetMouseOverImage(int32_t image) {
+	if (_mouseOverImage == image)
+		return;
+
+	_mouseOverImage = image;
+	UpdateCurrentImage();
+}
+
+void GUIButton::SetNormalImage(int32_t image) {
+	if (_image == image)
+		return;
+
+	_image = image;
+	UpdateCurrentImage();
+}
+
+void GUIButton::SetPushedImage(int32_t image) {
+	if (_pushedImage == image)
+		return;
+
+	_pushedImage = image;
+	UpdateCurrentImage();
+}
+
 void GUIButton::SetText(const String &text) {
 	if (_text == text)
 		return;
@@ -203,54 +251,66 @@ void GUIButton::SetText(const String &text) {
 
 
 bool GUIButton::OnMouseDown() {
-	int new_image = (PushedImage > 0) ? PushedImage : CurrentImage;
-	if (CurrentImage != new_image || !IsImageButton())
+	int new_image = (_pushedImage > 0) ? _pushedImage : _currentImage;
+	if (_currentImage != new_image || !IsImageButton())
 		MarkChanged();
-	CurrentImage = new_image;
+	_currentImage = new_image;
 	IsPushed = true;
 	return false;
 }
 
 void GUIButton::OnMouseEnter() {
-	int new_image = (IsPushed && PushedImage > 0) ? PushedImage :
-		(MouseOverImage > 0) ? MouseOverImage : Image;
-	if ((CurrentImage != new_image) || (IsPushed && !IsImageButton())) {
-		CurrentImage = new_image;
+	int new_image = (IsPushed && _pushedImage > 0) ? _pushedImage :
+		(_mouseOverImage > 0) ? _mouseOverImage : _image;
+	if ((_currentImage != new_image) || (IsPushed && !IsImageButton())) {
+		_currentImage = new_image;
 		MarkChanged();
 	}
 	IsMouseOver = true;
 }
 
 void GUIButton::OnMouseLeave() {
-	if ((CurrentImage != Image) || (IsPushed && !IsImageButton())) {
-		CurrentImage = Image;
+	if ((_currentImage != _image) || (IsPushed && !IsImageButton())) {
+		_currentImage = _image;
 		MarkChanged();
 	}
 	IsMouseOver = false;
 }
 
 void GUIButton::OnMouseUp() {
-	int new_image = Image;
+	int new_image = _image;
 	if (IsMouseOver) {
-		if (MouseOverImage > 0)
-			new_image = MouseOverImage;
+		if (_mouseOverImage > 0)
+			new_image = _mouseOverImage;
 		if (IsGUIEnabled(this) && IsClickable())
 			IsActivated = true;
 	}
 
-	if ((CurrentImage != new_image) || (IsPushed && !IsImageButton())) {
-		CurrentImage = new_image;
+	if ((_currentImage != new_image) || (IsPushed && !IsImageButton())) {
+		_currentImage = new_image;
 		MarkChanged();
 	}
 	IsPushed = false;
 }
 
+void GUIButton::UpdateCurrentImage() {
+	if (IsPushed && (_pushedImage > 0)) {
+		_currentImage = _pushedImage;
+	} else if (IsMouseOver && (_mouseOverImage > 0)) {
+		_currentImage = _mouseOverImage;
+	} else {
+		_currentImage = _image;
+	}
+
+	MarkChanged();
+}
+
 void GUIButton::WriteToFile(Stream *out) const {
 	GUIObject::WriteToFile(out);
 
-	out->WriteInt32(Image);
-	out->WriteInt32(MouseOverImage);
-	out->WriteInt32(PushedImage);
+	out->WriteInt32(_image);
+	out->WriteInt32(_mouseOverImage);
+	out->WriteInt32(_pushedImage);
 	out->WriteInt32(Font);
 	out->WriteInt32(TextColor);
 	out->WriteInt32(ClickAction[kGUIClickLeft]);
@@ -265,11 +325,11 @@ void GUIButton::WriteToFile(Stream *out) const {
 void GUIButton::ReadFromFile(Stream *in, GuiVersion gui_version) {
 	GUIObject::ReadFromFile(in, gui_version);
 
-	Image = in->ReadInt32();
-	MouseOverImage = in->ReadInt32();
-	PushedImage = in->ReadInt32();
+	_image = in->ReadInt32();
+	_mouseOverImage = in->ReadInt32();
+	_pushedImage = in->ReadInt32();
 	if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
-		CurrentImage = in->ReadInt32();
+		_currentImage = in->ReadInt32();
 		IsPushed = in->ReadInt32() != 0;
 		IsMouseOver = in->ReadInt32() != 0;
 	}
@@ -297,22 +357,22 @@ void GUIButton::ReadFromFile(Stream *in, GuiVersion gui_version) {
 
 	if (TextColor == 0)
 		TextColor = 16;
-	CurrentImage = Image;
+	_currentImage = _image;
 }
 
 void GUIButton::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
 	GUIObject::ReadFromSavegame(in, svg_ver);
 	// Properties
-	Image = in->ReadInt32();
-	MouseOverImage = in->ReadInt32();
-	PushedImage = in->ReadInt32();
+	_image = in->ReadInt32();
+	_mouseOverImage = in->ReadInt32();
+	_pushedImage = in->ReadInt32();
 	Font = in->ReadInt32();
 	TextColor = in->ReadInt32();
 	SetText(StrUtil::ReadString(in));
 	if (svg_ver >= kGuiSvgVersion_350)
 		TextAlignment = (FrameAlignment)in->ReadInt32();
 	// Dynamic state
-	CurrentImage = in->ReadInt32();
+	_currentImage = in->ReadInt32();
 
 	// Update current state after reading
 	IsPushed = false;
@@ -322,24 +382,24 @@ void GUIButton::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
 void GUIButton::WriteToSavegame(Stream *out) const {
 	// Properties
 	GUIObject::WriteToSavegame(out);
-	out->WriteInt32(Image);
-	out->WriteInt32(MouseOverImage);
-	out->WriteInt32(PushedImage);
+	out->WriteInt32(_image);
+	out->WriteInt32(_mouseOverImage);
+	out->WriteInt32(_pushedImage);
 	out->WriteInt32(Font);
 	out->WriteInt32(TextColor);
 	StrUtil::WriteString(GetText(), out);
 	out->WriteInt32(TextAlignment);
 	// Dynamic state
-	out->WriteInt32(CurrentImage);
+	out->WriteInt32(_currentImage);
 }
 
 void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
-	assert(CurrentImage >= 0);
+	assert(_currentImage >= 0);
 	// NOTE: the CLIP flag only clips the image, not the text
 	if (IsClippingImage() && !GUI::Options.ClipControls)
 		ds->SetClip(RectWH(x, y, Width, Height));
-	if (_GP(spriteset).DoesSpriteExist(CurrentImage))
-		draw_gui_sprite(ds, CurrentImage, x, y, true);
+	if (_GP(spriteset).DoesSpriteExist(_currentImage))
+		draw_gui_sprite(ds, _currentImage, x, y, true);
 
 	// Draw active inventory item
 	if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
@@ -363,7 +423,7 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 
 	if ((draw_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Greyout)) {
 		// darken the button when disabled
-		const Size sz = _GP(spriteset).GetSpriteResolution(CurrentImage);
+		const Size sz = _GP(spriteset).GetSpriteResolution(_currentImage);
 		GUI::DrawDisabledEffect(ds, RectWH(x, y, sz.Width, sz.Height));
 	}
 
diff --git a/engines/ags/shared/gui/gui_button.h b/engines/ags/shared/gui/gui_button.h
index 73cb46a6870..6706105a6a2 100644
--- a/engines/ags/shared/gui/gui_button.h
+++ b/engines/ags/shared/gui/gui_button.h
@@ -72,15 +72,23 @@ public:
 	GUIButton();
 
 	bool HasAlphaChannel() const override;
+	int32_t GetCurrentImage() const;
+	int32_t GetNormalImage() const;
+	int32_t GetMouseOverImage() const;
+	int32_t GetPushedImage() const;
+	GUIButtonPlaceholder GetPlaceholder() const;
 	const String &GetText() const;
 	bool IsImageButton() const;
 	bool IsClippingImage() const;
-	GUIButtonPlaceholder GetPlaceholder() const;
 
 	// Operations
 	Rect CalcGraphicRect(bool clipped) override;
 	void Draw(Bitmap *ds, int x = 0, int y = 0) override;
 	void SetClipImage(bool on);
+	void SetCurrentImage(int32_t image);
+	void SetMouseOverImage(int32_t image);
+	void SetNormalImage(int32_t image);
+	void SetPushedImage(int32_t image);
 	void SetText(const String &text);
 
 	// Events
@@ -97,10 +105,6 @@ public:
 
 	// TODO: these members are currently public; hide them later
 	public:
-	int32_t     Image;
-	int32_t     MouseOverImage;
-	int32_t     PushedImage;
-	int32_t     CurrentImage;
 	int32_t     Font;
 	color_t     TextColor;
 	FrameAlignment TextAlignment;
@@ -117,7 +121,14 @@ private:
 	void DrawText(Bitmap *ds, int x, int y, bool draw_disabled);
 	void DrawTextButton(Bitmap *ds, int x, int y, bool draw_disabled);
 	void PrepareTextToDraw();
-
+	// Update current image depending on the button's state
+	void UpdateCurrentImage();
+
+	int32_t _image;
+	int32_t _mouseOverImage;
+	int32_t _pushedImage;
+	// Active displayed image
+	int32_t _currentImage;
 	// Text property set by user
 	String _text;
 	// type of content placeholder, if any


Commit: bd1bf5931527aa63922f793aab2dfb027102489f
    https://github.com/scummvm/scummvm/commit/bd1bf5931527aa63922f793aab2dfb027102489f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: hide GUIObject's size behind the get/set methods

>From upstream ca442e0f9259d74a2bc6ccd7d28e59381ac1b9d1

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/ac/gui_inv.cpp
    engines/ags/engine/ac/listbox.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_inv.cpp
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_listbox.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_slider.cpp
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 1b4bac5d0c3..b8aea62cd71 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -171,10 +171,9 @@ void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
 		height = _GP(game).SpriteInfos[slotn].Height;
 	}
 
-	if ((slotn != guil->GetNormalImage()) || (width != guil->Width) || (height != guil->Height)) {
+	if ((slotn != guil->GetNormalImage()) || (width != guil->GetWidth()) || (height != guil->GetHeight())) {
 		guil->SetNormalImage(slotn);
-		guil->Width = width;
-		guil->Height = height;
+		guil->SetSize(width, height);
 	}
 
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index f9926afa65c..603c7498631 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -365,7 +365,7 @@ int write_dialog_options(Bitmap *ds, bool ds_has_alpha, int dlgxp, int curyp, in
 			break_up_text_into_lines(get_translation(dtop->optionnames[(int)disporder[i]]), _GP(Lines), areawid-(2*padding+2+bullet_wid), usingfont);\
 			needheight += get_text_lines_surf_height(usingfont, _GP(Lines).Count()) + data_to_game_coord(_GP(game).options[OPT_DIALOGGAP]);\
 		}\
-		if (parserInput) needheight += parserInput->Height + data_to_game_coord(_GP(game).options[OPT_DIALOGGAP]);\
+		if (parserInput) needheight += parserInput->GetHeight() + data_to_game_coord(_GP(game).options[OPT_DIALOGGAP]);\
 	}
 
 
@@ -502,7 +502,7 @@ void DialogOptions::Prepare(int _dlgnum, bool _runGameLoopsInBackground) {
 	parserActivated = 0;
 	if ((dtop->topicFlags & DTFLG_SHOWPARSER) && (_GP(play).disable_dialog_parser == 0)) {
 		parserInput = new GUITextBox();
-		parserInput->Height = lineheight + get_fixed_pixel_size(4);
+		parserInput->SetHeight(lineheight + get_fixed_pixel_size(4));
 		parserInput->SetShowBorder(true);
 		parserInput->Font = usingfont;
 	}
@@ -749,7 +749,7 @@ void DialogOptions::Redraw() {
 	if (parserInput) {
 		// Set up the text box, if present
 		parserInput->Y = curyp + data_to_game_coord(_GP(game).options[OPT_DIALOGGAP]);
-		parserInput->Width = areawid - get_fixed_pixel_size(10);
+		parserInput->SetWidth(areawid - get_fixed_pixel_size(10));
 		parserInput->TextColor = _G(playerchar)->talkcolor;
 		if (mouseison == DLG_OPTION_PARSER)
 			parserInput->TextColor = forecol;
@@ -758,7 +758,7 @@ void DialogOptions::Redraw() {
 			draw_gui_sprite_v330(ds, _GP(game).dialog_bullet, parserInput->X, parserInput->Y, options_surface_has_alpha);
 		}
 
-		parserInput->Width -= bullet_wid;
+		parserInput->SetWidth(parserInput->GetWidth() - bullet_wid);
 		parserInput->X += bullet_wid;
 
 		parserInput->Draw(ds, parserInput->X, parserInput->Y);
@@ -859,7 +859,7 @@ bool DialogOptions::Run() {
 			relative_mousey -= dirtyy;
 
 		if ((relative_mousey > parserInput->Y) &&
-			(relative_mousey < parserInput->Y + parserInput->Height))
+			(relative_mousey < parserInput->Y + parserInput->GetHeight()))
 			mouseison = DLG_OPTION_PARSER;
 
 		if (parserInput->IsActivated)
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 3b06c1a187b..db70709d4ed 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1717,7 +1717,7 @@ void draw_gui_controls(GUIMain &gui) {
 	for (int i = 0; i < gui.GetControlCount(); ++i, ++draw_index) {
 		GUIObject *obj = gui.GetControl(i);
 		if (!obj->IsVisible() ||
-			(obj->Width <= 0 || obj->Height <= 0) ||
+			(obj->GetSize().IsNull()) ||
 			(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
 			continue;
 		if (!obj->HasChanged())
@@ -1850,7 +1850,7 @@ void draw_gui_and_overlays() {
 		for (const auto &obj_id : _GP(guis)[s.id].GetControlsDrawOrder()) {
 			GUIObject *obj = _GP(guis)[s.id].GetControl(obj_id);
 			if (!obj->IsVisible() ||
-				(obj->Width <= 0 || obj->Height <= 0) ||
+				(obj->GetSize().IsNull()) ||
 				(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
 				continue;
 			const auto &obj_tx = _GP(guiobjbg)[draw_index + obj_id];
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index d6410af2c84..94080199793 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -182,21 +182,19 @@ void GUIControl_SetPosition(GUIObject *guio, int xx, int yy) {
 
 
 int GUIControl_GetWidth(GUIObject *guio) {
-	return game_to_data_coord(guio->Width);
+	return game_to_data_coord(guio->GetWidth());
 }
 
 void GUIControl_SetWidth(GUIObject *guio, int newwid) {
-	guio->Width = data_to_game_coord(newwid);
-	guio->OnResized();
+	guio->SetWidth(data_to_game_coord(newwid));
 }
 
 int GUIControl_GetHeight(GUIObject *guio) {
-	return game_to_data_coord(guio->Height);
+	return game_to_data_coord(guio->GetHeight());
 }
 
 void GUIControl_SetHeight(GUIObject *guio, int newhit) {
-	guio->Height = data_to_game_coord(newhit);
-	guio->OnResized();
+	guio->SetHeight(data_to_game_coord(newhit));
 }
 
 void GUIControl_SetSize(GUIObject *guio, int newwid, int newhit) {
diff --git a/engines/ags/engine/ac/gui_inv.cpp b/engines/ags/engine/ac/gui_inv.cpp
index 00f1fd0e515..84487735310 100644
--- a/engines/ags/engine/ac/gui_inv.cpp
+++ b/engines/ags/engine/ac/gui_inv.cpp
@@ -85,7 +85,7 @@ void GUIInvWindow::Draw(Bitmap *ds, int x, int y) {
 		GUI::Options.DisabledStyle == kGuiDis_Greyout &&
 		_GP(play).inventory_greys_out == 1) {
 		// darken the inventory when disabled
-		GUI::DrawDisabledEffect(ds, RectWH(x, y, Width, Height));
+		GUI::DrawDisabledEffect(ds, RectWH(x, y, _width, _height));
 	}
 }
 
diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp
index 83af1d5cfcc..ef071a41110 100644
--- a/engines/ags/engine/ac/listbox.cpp
+++ b/engines/ags/engine/ac/listbox.cpp
@@ -195,7 +195,7 @@ int ListBox_GetItemAtLocation(GUIListBox *listbox, int x, int y) {
 	x = (x - listbox->X) - _GP(guis)[listbox->ParentId].X;
 	y = (y - listbox->Y) - _GP(guis)[listbox->ParentId].Y;
 
-	if ((x < 0) || (y < 0) || (x >= listbox->Width) || (y >= listbox->Height))
+	if ((x < 0) || (y < 0) || (x >= listbox->GetWidth()) || (y >= listbox->GetHeight()))
 		return -1;
 
 	return listbox->GetItemAt(x, y);
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 21dc88fcd03..83e129ecf4c 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -347,8 +347,8 @@ static void ConvertGuiToGameRes(GameSetupStruct &game, GameDataVersion data_ver)
 			GUIObject *guio = cgp->GetControl(j);
 			guio->X *= mul;
 			guio->Y *= mul;
-			guio->Width *= mul;
-			guio->Height *= mul;
+			Size sz = guio->GetSize() * mul;
+			guio->SetSize(sz.Width, sz.Height);
 			guio->IsActivated = false;
 			guio->OnResized();
 		}
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 974a35d7191..9632d98895f 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -142,7 +142,7 @@ void GUIObject::ClearChanged() {
 int GUILabel::PrepareTextToDraw() {
 	const bool is_translated = (Flags & kGUICtrl_Translated) != 0;
 	replace_macro_tokens(is_translated ? get_translation(Text.GetCStr()) : Text.GetCStr(), _textToDraw);
-	return GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), is_translated, _GP(Lines), Font, Width);
+	return GUI::SplitLinesForDrawing(_textToDraw.GetCStr(), is_translated, _GP(Lines), Font, _width);
 }
 
 void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_color) {
@@ -156,7 +156,7 @@ void GUITextBox::DrawTextBoxContents(Bitmap *ds, int x, int y, color_t text_colo
 	}
 
 	Line tpos = GUI::CalcTextPositionHor(_textToDraw.GetCStr(), Font,
-										 x + 1 + get_fixed_pixel_size(1), x + Width - 1, y + 1 + get_fixed_pixel_size(1),
+										 x + 1 + get_fixed_pixel_size(1), x + _width - 1, y + 1 + get_fixed_pixel_size(1),
 										 reverse ? kAlignTopRight : kAlignTopLeft);
 	wouttext_outline(ds, tpos.X1, tpos.Y1, Font, text_color, _textToDraw.GetCStr());
 
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index 28774a665ab..b5af5f5f087 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -121,10 +121,10 @@ bool GUIButton::IsClippingImage() const {
 
 Rect GUIButton::CalcGraphicRect(bool clipped) {
 	if (clipped)
-		return RectWH(0, 0, Width, Height);
+		return RectWH(0, 0, _width, _height);
 
 	// TODO: need to find a way to cache image and text position, or there'll be some repetition
-	Rect rc = RectWH(0, 0, Width, Height);
+	Rect rc = RectWH(0, 0, _width, _height);
 	if (IsImageButton()) {
 		if (IsClippingImage())
 			return rc;
@@ -137,14 +137,14 @@ Rect GUIButton::CalcGraphicRect(bool clipped) {
 				get_adjusted_spriteheight(_G(gui_inv_pic)));
 			GUIButtonPlaceholder place = _placeholder;
 			if (place == kButtonPlace_InvItemAuto) {
-				place = ((inv_sz.Width > Width - 6) || (inv_sz.Height > Height - 6)) ?
+				place = ((inv_sz.Width > _width - 6) || (inv_sz.Height > _height - 6)) ?
 					kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
 			}
 
 			Rect inv_rc = (place == kButtonPlace_InvItemStretch) ?
-				RectWH(0 + 3, 0 + 3, Width - 6, Height - 6) :
-				RectWH(0 + Width / 2 - inv_sz.Width / 2,
-					0 + Height / 2 - inv_sz.Height / 2,
+				RectWH(0 + 3, 0 + 3, _width - 6, _height - 6) :
+				RectWH(0 + _width / 2 - inv_sz.Width / 2,
+					0 + _height / 2 - inv_sz.Height / 2,
 					inv_sz.Width, inv_sz.Height);
 			rc = SumRects(rc, inv_rc);
 		}
@@ -152,7 +152,7 @@ Rect GUIButton::CalcGraphicRect(bool clipped) {
 	// Optionally merge with the button text
 	if (!IsImageButton() || ((_placeholder == kButtonPlace_None) && !_unnamed)) {
 		PrepareTextToDraw();
-		Rect frame = RectWH(0 + 2, 0 + 2, Width - 4, Height - 4);
+		Rect frame = RectWH(0 + 2, 0 + 2, _width - 4, _height - 4);
 		if (IsPushed && IsMouseOver) {
 			frame.Left++;
 			frame.Top++;
@@ -294,6 +294,8 @@ void GUIButton::OnMouseUp() {
 }
 
 void GUIButton::UpdateCurrentImage() {
+	int was_image = _currentImage;
+
 	if (IsPushed && (_pushedImage > 0)) {
 		_currentImage = _pushedImage;
 	} else if (IsMouseOver && (_mouseOverImage > 0)) {
@@ -302,7 +304,8 @@ void GUIButton::UpdateCurrentImage() {
 		_currentImage = _image;
 	}
 
-	MarkChanged();
+	if (was_image != _currentImage)
+		MarkChanged();
 }
 
 void GUIButton::WriteToFile(Stream *out) const {
@@ -397,7 +400,7 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 	assert(_currentImage >= 0);
 	// NOTE: the CLIP flag only clips the image, not the text
 	if (IsClippingImage() && !GUI::Options.ClipControls)
-		ds->SetClip(RectWH(x, y, Width, Height));
+		ds->SetClip(RectWH(x, y, _width, _height));
 	if (_GP(spriteset).DoesSpriteExist(_currentImage))
 		draw_gui_sprite(ds, _currentImage, x, y, true);
 
@@ -406,17 +409,17 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 		Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)), get_adjusted_spriteheight(_G(gui_inv_pic)));
 		GUIButtonPlaceholder place = _placeholder;
 		if (place == kButtonPlace_InvItemAuto) {
-			place = ((inv_sz.Width > Width - 6) || (inv_sz.Height > Height - 6)) ?
+			place = ((inv_sz.Width > _width - 6) || (inv_sz.Height > _height - 6)) ?
 				kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
 		}
 
 		if (place == kButtonPlace_InvItemStretch) {
-			ds->StretchBlt(_GP(spriteset)[_G(gui_inv_pic)], RectWH(x + 3, y + 3, Width - 6, Height - 6),
+			ds->StretchBlt(_GP(spriteset)[_G(gui_inv_pic)], RectWH(x + 3, y + 3, _width - 6, _height - 6),
 				kBitmap_Transparency);
 		} else {
 			draw_gui_sprite(ds, _G(gui_inv_pic),
-				x + Width / 2 - inv_sz.Width / 2,
-				y + Height / 2 - inv_sz.Height / 2,
+				x + _width / 2 - inv_sz.Width / 2,
+				y + _height / 2 - inv_sz.Height / 2,
 				true);
 		}
 	}
@@ -440,7 +443,7 @@ void GUIButton::DrawText(Bitmap *ds, int x, int y, bool draw_disabled) {
 	// but that will require to update all gui controls when translation is changed in game
 	PrepareTextToDraw();
 
-	Rect frame = RectWH(x + 2, y + 2, Width - 4, Height - 4);
+	Rect frame = RectWH(x + 2, y + 2, _width - 4, _height - 4);
 	if (IsPushed && IsMouseOver) {
 		// move the Text a bit while pushed
 		frame.Left++;
@@ -454,10 +457,10 @@ void GUIButton::DrawText(Bitmap *ds, int x, int y, bool draw_disabled) {
 
 void GUIButton::DrawTextButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 	color_t draw_color = ds->GetCompatibleColor(7);
-	ds->FillRect(Rect(x, y, x + Width - 1, y + Height - 1), draw_color);
+	ds->FillRect(Rect(x, y, x + _width - 1, y + _height - 1), draw_color);
 	if (Flags & kGUICtrl_Default) {
 		draw_color = ds->GetCompatibleColor(16);
-		ds->DrawRect(Rect(x - 1, y - 1, x + Width, y + Height), draw_color);
+		ds->DrawRect(Rect(x - 1, y - 1, x + _width, y + _height), draw_color);
 	}
 
 	// TODO: use color constants instead of literal numbers
@@ -466,16 +469,16 @@ void GUIButton::DrawTextButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 	else
 		draw_color = ds->GetCompatibleColor(8);
 
-	ds->DrawLine(Line(x, y + Height - 1, x + Width - 1, y + Height - 1), draw_color);
-	ds->DrawLine(Line(x + Width - 1, y, x + Width - 1, y + Height - 1), draw_color);
+	ds->DrawLine(Line(x, y + _height - 1, x + _width - 1, y + _height - 1), draw_color);
+	ds->DrawLine(Line(x + _width - 1, y, x + _width - 1, y + _height - 1), draw_color);
 
 	if (draw_disabled || (IsMouseOver && IsPushed))
 		draw_color = ds->GetCompatibleColor(8);
 	else
 		draw_color = ds->GetCompatibleColor(15);
 
-	ds->DrawLine(Line(x, y, x + Width - 1, y), draw_color);
-	ds->DrawLine(Line(x, y, x, y + Height - 1), draw_color);
+	ds->DrawLine(Line(x, y, x + _width - 1, y), draw_color);
+	ds->DrawLine(Line(x, y, x, y + _height - 1), draw_color);
 
 	DrawText(ds, x, y, draw_disabled);
 }
diff --git a/engines/ags/shared/gui/gui_inv.cpp b/engines/ags/shared/gui/gui_inv.cpp
index 922fa6b065a..3fb48366044 100644
--- a/engines/ags/shared/gui/gui_inv.cpp
+++ b/engines/ags/shared/gui/gui_inv.cpp
@@ -84,10 +84,10 @@ void GUIInvWindow::ReadFromFile(Stream *in, GuiVersion gui_version) {
 
 	if (_G(loaded_game_file_version) >= kGameVersion_270) {
 		// ensure that some items are visible
-		if (ItemWidth > Width)
-			ItemWidth = Width;
-		if (ItemHeight > Height)
-			ItemHeight = Height;
+		if (ItemWidth > _width)
+			ItemWidth = _width;
+		if (ItemHeight > _height)
+			ItemHeight = _height;
 	}
 
 	CalculateNumCells();
@@ -115,11 +115,11 @@ void GUIInvWindow::CalculateNumCells() {
 		ColCount = 0;
 		RowCount = 0;
 	} else if (_G(loaded_game_file_version) >= kGameVersion_270) {
-		ColCount = Width / data_to_game_coord(ItemWidth);
-		RowCount = Height / data_to_game_coord(ItemHeight);
+		ColCount = _width / data_to_game_coord(ItemWidth);
+		RowCount = _height / data_to_game_coord(ItemHeight);
 	} else {
-		ColCount = floor((float)Width / (float)data_to_game_coord(ItemWidth) + 0.5f);
-		RowCount = floor((float)Height / (float)data_to_game_coord(ItemHeight) + 0.5f);
+		ColCount = floor((float)_width / (float)data_to_game_coord(ItemWidth) + 0.5f);
+		RowCount = floor((float)_height / (float)data_to_game_coord(ItemHeight) + 0.5f);
 	}
 }
 
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index 0a3164b1617..f561efbf908 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -55,13 +55,13 @@ GUILabelMacro GUILabel::GetTextMacros() const {
 
 Rect GUILabel::CalcGraphicRect(bool clipped) {
 	if (clipped)
-		return RectWH(0, 0, Width, Height);
+		return RectWH(0, 0, _width, _height);
 
 	// TODO: need to find a way to text position, or there'll be some repetition
 	// have to precache text and size on some events:
 	// - translation change
 	// - macro value change (score, overhotspot etc)
-	Rect rc = RectWH(0, 0, Width, Height);
+	Rect rc = RectWH(0, 0, _width, _height);
 	if (PrepareTextToDraw() == 0)
 		return rc;
 	const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
@@ -73,9 +73,9 @@ Rect GUILabel::CalcGraphicRect(bool clipped) {
 	int at_y = 0;
 	Line max_line;
 	for (size_t i = 0;
-		i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= Height);
+		i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= _height);
 		++i, at_y += linespacing) {
-		Line lpos = GUI::CalcTextPositionHor(_GP(Lines)[i].GetCStr(), Font, 0, 0 + Width - 1, at_y,
+		Line lpos = GUI::CalcTextPositionHor(_GP(Lines)[i].GetCStr(), Font, 0, 0 + _width - 1, at_y,
 			(FrameAlignment)TextAlignment);
 		max_line.X2 = MAX(max_line.X2, lpos.X2);
 	}
@@ -101,9 +101,9 @@ void GUILabel::Draw(Bitmap *ds, int x, int y) {
 	const bool limit_by_label_frame = _G(loaded_game_file_version) >= kGameVersion_272;
 	int at_y = y;
 	for (size_t i = 0;
-		i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= y + Height);
+		i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= y + _height);
 		++i, at_y += linespacing) {
-		GUI::DrawTextAlignedHor(ds, _GP(Lines)[i].GetCStr(), Font, text_color, x, x + Width - 1, at_y,
+		GUI::DrawTextAlignedHor(ds, _GP(Lines)[i].GetCStr(), Font, text_color, x, x + _width - 1, at_y,
 			(FrameAlignment)TextAlignment);
 	}
 }
diff --git a/engines/ags/shared/gui/gui_listbox.cpp b/engines/ags/shared/gui/gui_listbox.cpp
index 6427dbceeed..aaa75afc458 100644
--- a/engines/ags/shared/gui/gui_listbox.cpp
+++ b/engines/ags/shared/gui/gui_listbox.cpp
@@ -76,22 +76,22 @@ bool GUIListBox::IsSvgIndex() const {
 }
 
 bool GUIListBox::IsInRightMargin(int x) const {
-	if (x >= (Width - get_fixed_pixel_size(6)) && IsBorderShown() && AreArrowsShown())
+	if (x >= (_width - get_fixed_pixel_size(6)) && IsBorderShown() && AreArrowsShown())
 		return 1;
 	return 0;
 }
 
 Rect GUIListBox::CalcGraphicRect(bool clipped) {
 	if (clipped)
-		return RectWH(0, 0, Width, Height);
+		return RectWH(0, 0, _width, _height);
 
 	// TODO: need to find a way to text position, or there'll be some repetition
 	// have to precache text and size on some events:
 	// - translation change
 	// - macro value change (score, overhotspot etc)
-	Rect rc = RectWH(0, 0, Width, Height);
+	Rect rc = RectWH(0, 0, _width, _height);
 	UpdateMetrics();
-	const int width = Width - 1;
+	const int width = _width - 1;
 	const int pixel_size = get_fixed_pixel_size(1);
 	int right_hand_edge = width - pixel_size - 1;
 	// calculate the scroll bar's width if necessary
@@ -135,8 +135,8 @@ void GUIListBox::Clear() {
 }
 
 void GUIListBox::Draw(Bitmap *ds, int x, int y) {
-	const int width = Width - 1;
-	const int height = Height - 1;
+	const int width = _width - 1;
+	const int height = _height - 1;
 	const int pixel_size = get_fixed_pixel_size(1);
 
 	color_t text_color = ds->GetCompatibleColor(TextColor);
@@ -179,7 +179,7 @@ void GUIListBox::Draw(Bitmap *ds, int x, int y) {
 
 	Rect old_clip = ds->GetClip();
 	if (scrollbar && GUI::Options.ClipControls)
-		ds->SetClip(Rect(x, y, right_hand_edge + 1, y + Height - 1));
+		ds->SetClip(Rect(x, y, right_hand_edge + 1, y + _height - 1));
 	for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) {
 		int at_y = y + pixel_size + item * RowHeight;
 		if (item + TopItem == SelectedItem) {
@@ -277,9 +277,9 @@ void GUIListBox::SetItemText(int index, const String &text) {
 bool GUIListBox::OnMouseDown() {
 	if (IsInRightMargin(MousePos.X)) {
 		int top_item = TopItem;
-		if (MousePos.Y < Height / 2 && TopItem > 0)
+		if (MousePos.Y < _height / 2 && TopItem > 0)
 			top_item = TopItem - 1;
-		if (MousePos.Y >= Height / 2 && ItemCount > TopItem + VisibleItemCount)
+		if (MousePos.Y >= _height / 2 && ItemCount > TopItem + VisibleItemCount)
 			top_item = TopItem + 1;
 		if (TopItem != top_item) {
 			TopItem = top_item;
@@ -313,7 +313,7 @@ void GUIListBox::UpdateMetrics() {
 	int font_height = (_G(loaded_game_file_version) < kGameVersion_360_21) ?
 		get_font_height(Font) : get_font_height_outlined(Font);
 	RowHeight = font_height + get_fixed_pixel_size(2); // +1 top/bottom margin
-	VisibleItemCount = Height / RowHeight;
+	VisibleItemCount = _height / RowHeight;
 	if (ItemCount <= VisibleItemCount)
 		TopItem = 0; // reset scroll if all items are visible
 }
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index cfbd24c61c0..1777668b2a3 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -275,8 +275,9 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 		set_eip_guiobj(_ctrlDrawOrder[ctrl_index]);
 
 		GUIObject *objToDraw = _controls[_ctrlDrawOrder[ctrl_index]];
+		Size obj_size = objToDraw->GetSize();
 
-		if (!objToDraw->IsVisible() || (objToDraw->Width <= 0 || objToDraw->Height <= 0))
+		if (!objToDraw->IsVisible() || (obj_size.Width <= 0 || obj_size.Height <= 0))
 			continue;
 		if (!objToDraw->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
 			continue;
@@ -284,7 +285,7 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 		// Depending on draw properties - draw directly on the gui surface, or use a buffer
 		if (objToDraw->GetTransparency() == 0) {
 			if (GUI::Options.ClipControls && objToDraw->IsContentClipped())
-				ds->SetClip(RectWH(objToDraw->X, objToDraw->Y, objToDraw->Width, objToDraw->Height));
+				ds->SetClip(RectWH(objToDraw->X, objToDraw->Y, obj_size.Width, obj_size.Height));
 			else
 				ds->ResetClip();
 			objToDraw->Draw(ds, objToDraw->X, objToDraw->Y);
@@ -303,22 +304,22 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 			if (GUI::Options.OutlineControls)
 				selectedColour = 13;
 			color_t draw_color = ds->GetCompatibleColor(selectedColour);
-			DrawBlob(ds, objToDraw->X + objToDraw->Width - get_fixed_pixel_size(1) - 1, objToDraw->Y, draw_color);
-			DrawBlob(ds, objToDraw->X, objToDraw->Y + objToDraw->Height - get_fixed_pixel_size(1) - 1, draw_color);
+			DrawBlob(ds, objToDraw->X + obj_size.Width - get_fixed_pixel_size(1) - 1, objToDraw->Y, draw_color);
+			DrawBlob(ds, objToDraw->X, objToDraw->Y + obj_size.Height - get_fixed_pixel_size(1) - 1, draw_color);
 			DrawBlob(ds, objToDraw->X, objToDraw->Y, draw_color);
-			DrawBlob(ds, objToDraw->X + objToDraw->Width - get_fixed_pixel_size(1) - 1,
-				objToDraw->Y + objToDraw->Height - get_fixed_pixel_size(1) - 1, draw_color);
+			DrawBlob(ds, objToDraw->X + obj_size.Width - get_fixed_pixel_size(1) - 1,
+				objToDraw->Y + obj_size.Height - get_fixed_pixel_size(1) - 1, draw_color);
 		}
 		if (GUI::Options.OutlineControls) {
 			// draw a dotted outline round all objects
 			color_t draw_color = ds->GetCompatibleColor(selectedColour);
-			for (int i = 0; i < objToDraw->Width; i += 2) {
+			for (int i = 0; i < obj_size.Width; i += 2) {
 				ds->PutPixel(i + objToDraw->X, objToDraw->Y, draw_color);
-				ds->PutPixel(i + objToDraw->X, objToDraw->Y + objToDraw->Height - 1, draw_color);
+				ds->PutPixel(i + objToDraw->X, objToDraw->Y + obj_size.Height - 1, draw_color);
 			}
-			for (int i = 0; i < objToDraw->Height; i += 2) {
+			for (int i = 0; i < obj_size.Height; i += 2) {
 				ds->PutPixel(objToDraw->X, i + objToDraw->Y, draw_color);
-				ds->PutPixel(objToDraw->X + objToDraw->Width - 1, i + objToDraw->Y, draw_color);
+				ds->PutPixel(objToDraw->X + obj_size.Width - 1, i + objToDraw->Y, draw_color);
 			}
 		}
 	}
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index 4cdba043f07..1ea02a5db70 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -34,8 +34,8 @@ GUIObject::GUIObject() {
 	Flags = kGUICtrl_DefFlags;
 	X = 0;
 	Y = 0;
-	Width = 0;
-	Height = 0;
+	_width = 0;
+	_height = 0;
 	ZOrder = -1;
 	IsActivated = false;
 	_transparency = 0;
@@ -72,7 +72,7 @@ bool GUIObject::IsEnabled() const {
 }
 
 bool GUIObject::IsOverControl(int x, int y, int leeway) const {
-	return x >= X && y >= Y && x < (X + Width + leeway) && y < (Y + Height + leeway);
+	return x >= X && y >= Y && x < (X + _width + leeway) && y < (Y + _height + leeway);
 }
 
 bool GUIObject::IsTranslated() const {
@@ -99,6 +99,14 @@ void GUIObject::SetEnabled(bool on) {
 		Flags &= ~kGUICtrl_Enabled;
 }
 
+void GUIObject::SetSize(int width, int height) {
+	if (_width != width || _height != height) {
+		_width = width;
+		_height = height;
+		OnResized();
+	}
+}
+
 void GUIObject::SetTranslated(bool on) {
 	if (on != ((Flags & kGUICtrl_Translated) != 0))
 		MarkChanged();
@@ -130,8 +138,8 @@ void GUIObject::WriteToFile(Stream *out) const {
 	out->WriteInt32(Flags);
 	out->WriteInt32(X);
 	out->WriteInt32(Y);
-	out->WriteInt32(Width);
-	out->WriteInt32(Height);
+	out->WriteInt32(_width);
+	out->WriteInt32(_height);
 	out->WriteInt32(ZOrder);
 	Name.Write(out);
 	out->WriteInt32(_scEventCount);
@@ -146,8 +154,8 @@ void GUIObject::ReadFromFile(Stream *in, GuiVersion gui_version) {
 		Flags ^= kGUICtrl_OldFmtXorMask;
 	X = in->ReadInt32();
 	Y = in->ReadInt32();
-	Width = in->ReadInt32();
-	Height = in->ReadInt32();
+	_width = in->ReadInt32();
+	_height = in->ReadInt32();
 	ZOrder = in->ReadInt32();
 	if (gui_version < kGuiVersion_350) { // NOTE: reading into actual variables only for old savegame support
 		IsActivated = in->ReadInt32() != 0;
@@ -180,8 +188,8 @@ void GUIObject::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
 		Flags ^= kGUICtrl_OldFmtXorMask;
 	X = in->ReadInt32();
 	Y = in->ReadInt32();
-	Width = in->ReadInt32();
-	Height = in->ReadInt32();
+	_width = in->ReadInt32();
+	_height = in->ReadInt32();
 	ZOrder = in->ReadInt32();
 	// Dynamic state
 	IsActivated = in->ReadBool() ? 1 : 0;
@@ -198,8 +206,8 @@ void GUIObject::WriteToSavegame(Stream *out) const {
 	out->WriteInt32(Flags);
 	out->WriteInt32(X);
 	out->WriteInt32(Y);
-	out->WriteInt32(Width);
-	out->WriteInt32(Height);
+	out->WriteInt32(_width);
+	out->WriteInt32(_height);
 	out->WriteInt32(ZOrder);
 	// Dynamic state
 	out->WriteBool(IsActivated != 0);
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 7bcf6b9d5d6..c245ac1a107 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -60,6 +60,9 @@ public:
 	bool            IsVisible() const;
 	// implemented separately in engine and editor
 	bool            IsClickable() const;
+	Size            GetSize() const { return Size(_width, _height); }
+	int             GetWidth() const { return _width; }
+	int             GetHeight() const { return _height; }
 	int             GetTransparency() const { return _transparency; }
 	// Compatibility: should the control's graphic be clipped to its x,y,w,h
     virtual bool    IsContentClipped() const { return true; }
@@ -70,13 +73,16 @@ public:
 	// Returns the (untransformed!) visual rectangle of this control,
 	// in *relative* coordinates, optionally clipped by the logical size
 	virtual Rect    CalcGraphicRect(bool /*clipped*/) {
-		return RectWH(0, 0, Width, Height);
+		return RectWH(0, 0, _width, _height);
 	}
 	virtual void    Draw(Bitmap *ds, int x = 0, int y = 0) {
 		(void)ds; (void)x; (void)y;
 	}
 	void            SetClickable(bool on);
 	void            SetEnabled(bool on);
+	void            SetSize(int width, int height);
+	inline void     SetWidth(int width) { SetSize(width, _height); }
+	inline void     SetHeight(int height) { SetSize(_width, height); }
 	void            SetTranslated(bool on);
 	void            SetVisible(bool on);
 	void            SetTransparency(int trans);
@@ -127,8 +133,6 @@ public:
 
 	int32_t  X;
 	int32_t  Y;
-	int32_t  Width;
-	int32_t  Height;
 	int32_t  ZOrder;
 	bool     IsActivated; // signals user interaction
 
@@ -136,6 +140,8 @@ public:
 
 protected:
 	uint32_t Flags;      // generic style and behavior flags
+	int32_t  _width;
+	int32_t  _height;
 	int32_t  _transparency; // "incorrect" alpha (in legacy 255-range units)
 	bool     _hasChanged;
 
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index dc29dfa2104..0848c9a1c95 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -46,7 +46,7 @@ GUISlider::GUISlider() {
 }
 
 bool GUISlider::IsHorizontal() const {
-	return Width > Height;
+	return _width > _height;
 }
 
 bool GUISlider::HasAlphaChannel() const {
@@ -65,7 +65,7 @@ Rect GUISlider::CalcGraphicRect(bool /*clipped*/) {
 	// Sliders are never clipped as of 3.6.0
 	// TODO: precalculate everything on width/height/graphic change!!
 	UpdateMetrics();
-	Rect logical = RectWH(0, 0, Width, Height);
+	Rect logical = RectWH(0, 0, _width, _height);
 	Rect bar = _cachedBar;
 	Rect handle = _cachedHandle;
 	return Rect(
@@ -86,7 +86,7 @@ void GUISlider::UpdateMetrics() {
 	const int handle_im = ((HandleImage > 0) && _GP(spriteset).DoesSpriteExist(HandleImage)) ? HandleImage : 0;
 
 	// Depending on slider's orientation, thickness is either Height or Width
-	const int thickness = IsHorizontal() ? Height : Width;
+	const int thickness = IsHorizontal() ? _height : _width;
 	// "thick_f" is the factor for calculating relative element positions
 	const int thick_f = thickness / 3; // one third of the control's thickness
 	// Bar thickness
@@ -113,8 +113,8 @@ void GUISlider::UpdateMetrics() {
 	if (IsHorizontal()) // horizontal slider
 	{
 		// Value pos is a coordinate corresponding to current slider's value
-		bar = RectWH(1, Height / 2 - thick_f, Width - 1, bar_thick);
-		handle_range = Width - 4;
+		bar = RectWH(1, _height / 2 - thick_f, _width - 1, bar_thick);
+		handle_range = _width - 4;
 		int value_pos = (int)(((float)(Value - MinValue) * (float)handle_range) / (float)(MaxValue - MinValue));
 		handle = RectWH((bar.Left + get_fixed_pixel_size(2)) - (handle_sz.Width / 2) + 1 + value_pos - 2,
 			bar.Top + (bar.GetHeight() - handle_sz.Height) / 2,
@@ -123,8 +123,8 @@ void GUISlider::UpdateMetrics() {
 	}
 	// vertical slider
 	else {
-		bar = RectWH(Width / 2 - thick_f, 1, bar_thick, Height - 1);
-		handle_range = Height - 4;
+		bar = RectWH(_width / 2 - thick_f, 1, bar_thick, _height - 1);
+		handle_range = _height - 4;
 		int value_pos = (int)(((float)(MaxValue - Value) * (float)handle_range) / (float)(MaxValue - MinValue));
 		handle = RectWH(bar.Left + (bar.GetWidth() - handle_sz.Width) / 2,
 			(bar.Top + get_fixed_pixel_size(2)) - (handle_sz.Height / 2) + 1 + value_pos - 2,
@@ -151,11 +151,11 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 		if (IsHorizontal()) {
 			x_inc = get_adjusted_spritewidth(BgImage);
 			// centre the image vertically
-			bar.Top = y + (Height / 2) - get_adjusted_spriteheight(BgImage) / 2;
+			bar.Top = y + (_height / 2) - get_adjusted_spriteheight(BgImage) / 2;
 		} else {
 			y_inc = get_adjusted_spriteheight(BgImage);
 			// centre the image horizontally
-			bar.Left = x + (Width / 2) - get_adjusted_spritewidth(BgImage) / 2;
+			bar.Left = x + (_width / 2) - get_adjusted_spritewidth(BgImage) / 2;
 		}
 		int cx = bar.Left;
 		int cy = bar.Top;
@@ -212,7 +212,7 @@ void GUISlider::OnMouseMove(int x, int y) {
 	if (IsHorizontal())
 		value = (int)(((float)((x - X) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 	else
-		value = (int)(((float)(((Y + Height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
+		value = (int)(((float)(((Y + _height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 
 	value = Math::Clamp(value, MinValue, MaxValue);
 	if (value != Value) {
diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index 2a95dd8fd12..b4ce3b86657 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -53,10 +53,10 @@ bool GUITextBox::IsBorderShown() const {
 
 Rect GUITextBox::CalcGraphicRect(bool clipped) {
 	if (clipped)
-		return RectWH(0, 0, Width, Height);
+		return RectWH(0, 0, _width, _height);
 
 	// TODO: need to find a way to cache text position, or there'll be some repetition
-	Rect rc = RectWH(0, 0, Width, Height);
+	Rect rc = RectWH(0, 0, _width, _height);
 	Point text_at(1 + get_fixed_pixel_size(1), 1 + get_fixed_pixel_size(1));
 	Rect text_rc = GUI::CalcTextGraphicalRect(Text.GetCStr(), Font, text_at);
 	if (IsGUIEnabled(this)) {
@@ -75,9 +75,9 @@ void GUITextBox::Draw(Bitmap *ds, int x, int y) {
 	color_t text_color = ds->GetCompatibleColor(TextColor);
 	color_t draw_color = ds->GetCompatibleColor(TextColor);
 	if (IsBorderShown()) {
-		ds->DrawRect(RectWH(x, y, Width, Height), draw_color);
+		ds->DrawRect(RectWH(x, y, _width, _height), draw_color);
 		if (get_fixed_pixel_size(1) > 1) {
-			ds->DrawRect(Rect(x + 1, y + 1, x + Width - get_fixed_pixel_size(1), y + Height - get_fixed_pixel_size(1)), draw_color);
+			ds->DrawRect(Rect(x + 1, y + 1, x + _width - get_fixed_pixel_size(1), y + _height - get_fixed_pixel_size(1)), draw_color);
 		}
 	}
 	DrawTextBoxContents(ds, x, y, text_color);
@@ -116,7 +116,7 @@ void GUITextBox::OnKeyPress(const KeyInput &ki) {
 		Text.Append(ki.Text) :
 		Text.AppendChar(ki.UChar);
 	// if the new string is too long, remove the new character
-	if (get_text_width(Text.GetCStr(), Font) > (Width - (6 + get_fixed_pixel_size(5))))
+	if (get_text_width(Text.GetCStr(), Font) > (_width - (6 + get_fixed_pixel_size(5))))
 		Backspace(Text);
 	MarkChanged();
 }


Commit: 0da5748fa01a80bfc6b2f33d523244902362ba1b
    https://github.com/scummvm/scummvm/commit/0da5748fa01a80bfc6b2f33d523244902362ba1b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix FPS calculation

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index db70709d4ed..d55f1123002 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1688,8 +1688,8 @@ void draw_fps(const Rect &viewport) {
 	char fps_buffer[60];
 	// Don't display fps if we don't have enough information (because loop count was just reset)
 	float fps = get_real_fps();
-	if (!isnan(_G(fps))) {
-		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", _G(fps), base_buffer);
+	if (!isnan(fps)) {
+		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", fps, base_buffer);
 	} else {
 		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: --.- / %s", base_buffer);
 	}


Commit: 22cb981566a5d519a378036a0c5f264f35529adf
    https://github.com/scummvm/scummvm/commit/22cb981566a5d519a378036a0c5f264f35529adf
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: rewrote MoveList to avoid packed shorts

>From upstream d463adac63817fd08eb757038f90c730726124d3
and 09203df6efb19cf0f2eafd098c798733bd85cfe0

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/global_debug.cpp
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/ac/route_finder_impl_legacy.cpp
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/engine/main/update.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/shared/util/geometry.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index d2f2c9095f7..207f8445d5e 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -154,8 +154,9 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 		return;
 	}
 
+	cmls->pos[cmls->numstage] = { x, y };
 	// They're already walking there anyway
-	if (cmls->lastx == x && cmls->lasty == y)
+	if (cmls->last.X == x && cmls->last.Y == y)
 		return;
 
 	int move_speed_x, move_speed_y;
@@ -169,12 +170,11 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 	// a pathfinder api, and then we'll convert old and new last step back.
 	// TODO: figure out a better way of processing this!
 	const int last_stage = cmls->numstage - 1;
-	cmls->pos[last_stage] = ((room_to_mask_coord(cmls->pos[last_stage] >> 16)) << 16) | ((room_to_mask_coord(cmls->pos[last_stage] & 0xFFFF)) & 0xFFFF);
+	cmls->pos[last_stage] = { room_to_mask_coord(cmls->pos[last_stage].X), room_to_mask_coord(cmls->pos[last_stage].Y) };
 	const int dst_x = room_to_mask_coord(x);
 	const int dst_y = room_to_mask_coord(y);
-	if (add_waypoint_direct(cmls, dst_x, dst_y, move_speed_x, move_speed_y)) {
+	if(add_waypoint_direct(cmls, dst_x, dst_y, move_speed_x, move_speed_y))
 		convert_move_path_to_room_resolution(cmls, last_stage, last_stage + 1);
-	}
 }
 
 void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat,
@@ -1322,7 +1322,7 @@ int Character_GetMoving(CharacterInfo *chaa) {
 int Character_GetDestinationX(CharacterInfo *chaa) {
 	if (chaa->walking) {
 		MoveList *cmls = &_GP(mls)[chaa->walking % TURNING_AROUND];
-		return cmls->pos[cmls->numstage - 1] >> 16;
+		return cmls->pos[cmls->numstage - 1].X;
 	} else
 		return chaa->x;
 }
@@ -1330,7 +1330,7 @@ int Character_GetDestinationX(CharacterInfo *chaa) {
 int Character_GetDestinationY(CharacterInfo *chaa) {
 	if (chaa->walking) {
 		MoveList *cmls = &_GP(mls)[chaa->walking % TURNING_AROUND];
-		return cmls->pos[cmls->numstage - 1] & 0xFFFF;
+		return cmls->pos[cmls->numstage - 1].Y;
 	} else
 		return chaa->y;
 }
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index d55f1123002..0d4c2a56ff3 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2190,10 +2190,10 @@ void update_room_debug() {
 				mlsnum %= TURNING_AROUND;
 			const MoveList &cmls = _GP(mls)[mlsnum];
 			for (int i = 0; i < cmls.numstage - 1; i++) {
-				short srcx = short((cmls.pos[i] >> 16) & 0x00ffff);
-				short srcy = short(cmls.pos[i] & 0x00ffff);
-				short targetx = short((cmls.pos[i + 1] >> 16) & 0x00ffff);
-				short targety = short(cmls.pos[i + 1] & 0x00ffff);
+				short srcx = cmls.pos[i].X;
+				short srcy = cmls.pos[i].Y;
+				short targetx = cmls.pos[i + 1].X;
+				short targety = cmls.pos[i + 1].Y;
 				_GP(debugMoveListObj).Bmp->DrawLine(Line(srcx / mult, srcy / mult, targetx / mult, targety / mult),
 					MakeColor(i + 1));
 			}
diff --git a/engines/ags/engine/ac/global_debug.cpp b/engines/ags/engine/ac/global_debug.cpp
index fa079007476..aa8a52e6b40 100644
--- a/engines/ags/engine/ac/global_debug.cpp
+++ b/engines/ags/engine/ac/global_debug.cpp
@@ -150,10 +150,10 @@ void script_debug(int cmdd, int dataa) {
 			mlsnum %= TURNING_AROUND;
 		MoveList *cmls = &_GP(mls)[mlsnum];
 		for (int i = 0; i < cmls->numstage - 1; i++) {
-			short srcx = short((cmls->pos[i] >> 16) & 0x00ffff);
-			short srcy = short(cmls->pos[i] & 0x00ffff);
-			short targetx = short((cmls->pos[i + 1] >> 16) & 0x00ffff);
-			short targety = short(cmls->pos[i + 1] & 0x00ffff);
+			short srcx = cmls->pos[i].X;
+			short srcy = cmls->pos[i].Y;
+			short targetx = cmls->pos[i + 1].X;
+			short targety = cmls->pos[i + 1].Y;
 			tempw->DrawLine(Line(srcx, srcy, targetx, targety), MakeColor(i + 1));
 		}
 
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index 571fc400717..021518b66b5 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -29,16 +29,20 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 void MoveList::ReadFromFile_Legacy(Stream *in) {
-	in->ReadArrayOfInt32(pos, MAXNEEDSTAGES_LEGACY);
+	for (int i = 0; i < MAXNEEDSTAGES_LEGACY; ++i) {
+		// X & Y was packed as high/low shorts, and hence reversed in lo-end
+		pos[i].Y = in->ReadInt16();
+		pos[i].X = in->ReadInt16();
+	}
 	numstage = in->ReadInt32();
 	in->ReadArrayOfInt32(xpermove, MAXNEEDSTAGES_LEGACY);
 	in->ReadArrayOfInt32(ypermove, MAXNEEDSTAGES_LEGACY);
-	fromx = in->ReadInt32();
-	fromy = in->ReadInt32();
+	from.X = in->ReadInt32();
+	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	onpart = in->ReadInt32();
-	lastx = in->ReadInt32();
-	lasty = in->ReadInt32();
+	last.X = in->ReadInt32();
+	last.Y = in->ReadInt32();
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 }
@@ -56,16 +60,20 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 		                         String::FromFormat("Incompatible number of movelist steps (count: %d, max : %d).", numstage, MAXNEEDSTAGES));
 	}
 
-	fromx = in->ReadInt32();
-	fromy = in->ReadInt32();
+	from.X = in->ReadInt32();
+	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	onpart = in->ReadInt32();
-	lastx = in->ReadInt32();
-	lasty = in->ReadInt32();
+	last.X = in->ReadInt32();
+	last.Y = in->ReadInt32();
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
-	in->ReadArrayOfInt32(pos, numstage);
+	for (int i = 0; i < numstage; ++i) {
+		// X & Y was packed as high/low shorts, and hence reversed in lo-end
+		pos[i].Y = in->ReadInt16();
+		pos[i].X = in->ReadInt16();
+	}
 	in->ReadArrayOfInt32(xpermove, numstage);
 	in->ReadArrayOfInt32(ypermove, numstage);
 	return HSaveError::None();
@@ -73,16 +81,20 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 
 void MoveList::WriteToFile(Stream *out) {
 	out->WriteInt32(numstage);
-	out->WriteInt32(fromx);
-	out->WriteInt32(fromy);
+	out->WriteInt32(from.X);
+	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
 	out->WriteInt32(onpart);
-	out->WriteInt32(lastx);
-	out->WriteInt32(lasty);
+	out->WriteInt32(last.X);
+	out->WriteInt32(last.Y);
 	out->WriteInt8(doneflag);
 	out->WriteInt8(direct);
 
-	out->WriteArrayOfInt32(pos, numstage);
+	for (int i = 0; i < numstage; ++i) {
+		// X & Y was packed as high/low shorts, and hence reversed in lo-end
+		out->WriteInt16(pos[i].Y);
+		out->WriteInt16(pos[i].X);
+	}
 	out->WriteArrayOfInt32(xpermove, numstage);
 	out->WriteArrayOfInt32(ypermove, numstage);
 }
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index cc957949318..e92bfff6268 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -19,11 +19,12 @@
  *
  */
 
-#ifndef AGS_ENGINE_AC_MOVE_H
-#define AGS_ENGINE_AC_MOVE_H
+#ifndef AGS_ENGINE_AC_MOVE_LIST_H
+#define AGS_ENGINE_AC_MOVE_LIST_H
 
 #include "ags/lib/allegro.h" // fixed math
 #include "ags/engine/game/savegame.h"
+#include "ags/shared/util/geometry.h"
 
 namespace AGS3 {
 
@@ -38,15 +39,23 @@ using namespace AGS; // FIXME later
 #define MAXNEEDSTAGES 256
 #define MAXNEEDSTAGES_LEGACY 40
 
+enum MoveListDoneFlags {
+	kMoveListDone_X = 0x01,
+	kMoveListDone_Y = 0x02,
+	kMoveListDone_XY = kMoveListDone_X | kMoveListDone_Y
+};
+
 struct MoveList {
-	int32_t pos[MAXNEEDSTAGES] = {};
-	int   numstage = 0;
-	fixed xpermove[MAXNEEDSTAGES] = {}, ypermove[MAXNEEDSTAGES] = {};
-	int   fromx = 0, fromy = 0;
-	int   onstage = 0, onpart = 0;
-	int   lastx = 0, lasty = 0;
-	int8  doneflag = 0;
-	int8  direct = 0;  // MoveCharDirect was used or not
+	int 	numstage = 0;
+	Point 	pos[MAXNEEDSTAGES];
+	fixed 	xpermove[MAXNEEDSTAGES]{};
+	fixed 	ypermove[MAXNEEDSTAGES]{};
+	Point 	from;
+	int 	onstage = 0;
+	int 	onpart = 0;
+	Point 	last;
+	uint8_t doneflag = 0u;
+	uint8_t direct = 0; // MoveCharDirect was used or not
 
 	void ReadFromFile_Legacy(Shared::Stream *in);
 	AGS::Engine::HSaveError ReadFromFile(Shared::Stream *in, int32_t cmp_ver);
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 885465a6e17..f1ad9f6d74f 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1041,19 +1041,11 @@ void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_st
 	if (_GP(thisroom).MaskResolution == _GP(game).GetDataUpscaleMult())
 		return;
 
-	if (from_step == 0) {
-		ml->fromx = mask_to_room_coord(ml->fromx);
-		ml->fromy = mask_to_room_coord(ml->fromy);
-	}
-	if (to_step == ml->numstage - 1) {
-		ml->lastx = mask_to_room_coord(ml->lastx);
-		ml->lasty = mask_to_room_coord(ml->lasty);
-	}
+	ml->from = {mask_to_room_coord(ml->from.X), mask_to_room_coord(ml->from.Y)};
+	ml->last = {mask_to_room_coord(ml->last.X), mask_to_room_coord(ml->last.Y)};
 
 	for (int i = from_step; i <= to_step; i++) {
-		uint16_t lowPart = mask_to_room_coord(ml->pos[i] & 0x0000ffff);
-		uint16_t highPart = mask_to_room_coord((ml->pos[i] >> 16) & 0x0000ffff);
-		ml->pos[i] = ((int)highPart << 16) | (lowPart & 0x0000ffff);
+		ml->pos[i] = {mask_to_room_coord(ml->pos[i].X), mask_to_room_coord(ml->pos[i].Y)};
 	}
 
 	// If speed is scaling with MaskResolution...
diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index 129bd3e4a54..62d0ae3cdcf 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -42,8 +42,6 @@ namespace AGS {
 namespace Engine {
 namespace RouteFinder {
 
-#define MAKE_INTCOORD(x,y) (((unsigned short)x << 16) | ((unsigned short)y))
-
 static const int MAXNAVPOINTS = MAXNEEDSTAGES;
 
 void init_pathfinder() {
@@ -101,7 +99,7 @@ static int find_route_jps(int fromx, int fromy, int destx, int desty) {
 		int x, y;
 		_GP(nav).UnpackSquare(cpath[i], x, y);
 
-		_G(navpoints)[_G(num_navpoints)++] = MAKE_INTCOORD(x, y);
+		_G(navpoints)[_G(num_navpoints)++] = {x, y};
 	}
 
 	return 1;
@@ -125,10 +123,10 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 		return;
 	}
 
-	short ourx = (mlsp->pos[aaa] >> 16) & 0x000ffff;
-	short oury = (mlsp->pos[aaa] & 0x000ffff);
-	short destx = ((mlsp->pos[aaa + 1] >> 16) & 0x000ffff);
-	short desty = (mlsp->pos[aaa + 1] & 0x000ffff);
+	short ourx = mlsp->pos[aaa].X;
+	short oury = mlsp->pos[aaa].Y;
+	short destx = mlsp->pos[aaa + 1].X;
+	short desty = mlsp->pos[aaa + 1].Y;
 
 	// Special case for vertical and horizontal movements
 	if (ourx == destx) {
@@ -197,8 +195,8 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 
 	if (ignore_walls || can_see_from(srcx, srcy, xx, yy)) {
 		_G(num_navpoints) = 2;
-		_G(navpoints)[0] = MAKE_INTCOORD(srcx, srcy);
-		_G(navpoints)[1] = MAKE_INTCOORD(xx, yy);
+		_G(navpoints)[0] = {srcx, srcy};
+		_G(navpoints)[1] = {xx, yy};
 	} else {
 		if ((nocross == 0) && (_G(wallscreen)->GetPixel(xx, yy) == 0))
 			return 0; // clicked on a wall
@@ -221,7 +219,7 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 
 	int mlist = movlst;
 	_GP(mls)[mlist].numstage = _G(num_navpoints);
-	memcpy(&_GP(mls)[mlist].pos[0], &_G(navpoints)[0], sizeof(int) * _G(num_navpoints));
+	memcpy(&_GP(mls)[mlist].pos[0], &_G(navpoints)[0], sizeof(Point) * _G(num_navpoints));
 #ifdef DEBUG_PATHFINDER
 	AGS::Shared::Debug::Printf("stages: %d\n", _G(num_navpoints));
 #endif
@@ -232,13 +230,11 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 		calculate_move_stage(&_GP(mls)[mlist], i, fix_speed_x, fix_speed_y);
 	}
 
-	_GP(mls)[mlist].fromx = srcx;
-	_GP(mls)[mlist].fromy = srcy;
+	_GP(mls)[mlist].from = { srcx, srcy };
 	_GP(mls)[mlist].onstage = 0;
 	_GP(mls)[mlist].onpart = 0;
 	_GP(mls)[mlist].doneflag = 0;
-	_GP(mls)[mlist].lastx = -1;
-	_GP(mls)[mlist].lasty = -1;
+	_GP(mls)[mlist].last = { -1, -1};
 	return mlist;
 }
 
@@ -248,11 +244,11 @@ bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int
 
 	const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
 	const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
-	mlsp->pos[mlsp->numstage] = MAKE_INTCOORD(x, y);
+	mlsp->pos[mlsp->numstage] = {x, y};
 	calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
 	mlsp->numstage++;
-	mlsp->lastx = x;
-	mlsp->lasty = y;
+	mlsp->last.X = x;
+	mlsp->last.Y = y;
 	return true;
 }
 
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 8e66f045e33..88ac559819f 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -658,10 +658,10 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 		return;
 	}
 
-	short ourx = (mlsp->pos[aaa] >> 16) & 0x000ffff;
-	short oury = (mlsp->pos[aaa] & 0x000ffff);
-	short destx = ((mlsp->pos[aaa + 1] >> 16) & 0x000ffff);
-	short desty = (mlsp->pos[aaa + 1] & 0x000ffff);
+	short ourx = mlsp->pos[aaa].X;
+	short oury = mlsp->pos[aaa].Y;
+	short destx = mlsp->pos[aaa + 1].X;
+	short desty = mlsp->pos[aaa + 1].Y;
 
 	// Special case for vertical and horizontal movements
 	if (ourx == destx) {
@@ -728,8 +728,6 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 #endif
 }
 
-#define MAKE_INTCOORD(x,y) (((unsigned short)x << 16) | ((unsigned short)y))
-
 int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
 	assert(onscreen != nullptr);
 	assert((int)_GP(mls).size() > movlst);
@@ -786,14 +784,16 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 	}
 
 	if (pathbackstage >= 0) {
-		int nearestpos = 0, nearestindx;
-		int reallyneed[MAXNEEDSTAGES], numstages = 0;
-		reallyneed[numstages] = MAKE_INTCOORD(srcx, srcy);
+		Point nearestpos;
+		int nearestindx;
+		Point reallyneed[MAXNEEDSTAGES];
+		int numstages = 0;
+		reallyneed[numstages] = {srcx, srcy};
 		numstages++;
 		nearestindx = -1;
 
 stage_again:
-		nearestpos = 0;
+		nearestpos = {};
 		aaa = 1;
 		// find the furthest point that can be seen from this stage
 		for (aaa = pathbackstage - 1; aaa >= 0; aaa--) {
@@ -801,26 +801,26 @@ stage_again:
 			AGS::Shared::Debug::Printf("stage %2d: %2d,%2d\n", aaa, pathbackx[aaa], pathbacky[aaa]);
 #endif
 			if (can_see_from(srcx, srcy, pathbackx[aaa], pathbacky[aaa])) {
-				nearestpos = MAKE_INTCOORD(pathbackx[aaa], pathbacky[aaa]);
+				nearestpos = {pathbackx[aaa], pathbacky[aaa]};
 				nearestindx = aaa;
 			}
 		}
 
-		if ((nearestpos == 0) && (can_see_from(srcx, srcy, xx, yy) == 0) &&
+		if ((nearestpos.Equals(0,0)) && (can_see_from(srcx, srcy, xx, yy) == 0) &&
 		        (srcx >= 0) && (srcy >= 0) && (srcx < _G(wallscreen)->GetWidth()) && (srcy < _G(wallscreen)->GetHeight()) && (pathbackstage > 0)) {
 			// If we couldn't see anything, we're stuck in a corner so advance
 			// to the next square anyway (but only if they're on the screen)
 			nearestindx = pathbackstage - 1;
-			nearestpos = MAKE_INTCOORD(pathbackx[nearestindx], pathbacky[nearestindx]);
+			nearestpos = {pathbackx[nearestindx], pathbacky[nearestindx]};
 		}
 
-		if (nearestpos > 0) {
+		if ((nearestpos.X + nearestpos.Y) > 0) { // NOTE: we only deal with positive coordinates here
 			reallyneed[numstages] = nearestpos;
 			numstages++;
 			if (numstages >= MAXNEEDSTAGES - 1)
 				quit("too many stages for auto-walk");
-			srcx = (nearestpos >> 16) & 0x000ffff;
-			srcy = nearestpos & 0x000ffff;
+			srcx = nearestpos.X;
+			srcy = nearestpos.Y;
 #ifdef DEBUG_PATHFINDER
 			AGS::Shared::Debug::Printf("Added: %d, %d pbs:%d", srcx, srcy, pathbackstage);
 #endif
@@ -830,13 +830,13 @@ stage_again:
 		}
 
 		if (finalpartx >= 0) {
-			reallyneed[numstages] = MAKE_INTCOORD(finalpartx, finalparty);
+			reallyneed[numstages] = {finalpartx, finalparty};
 			numstages++;
 		}
 
 		// Make sure the end co-ord is in there
-		if (reallyneed[numstages - 1] != MAKE_INTCOORD(xx, yy)) {
-			reallyneed[numstages] = MAKE_INTCOORD(xx, yy);
+		if (reallyneed[numstages - 1] != Point(xx, yy)) {
+			reallyneed[numstages] = {xx, yy};
 			numstages++;
 		}
 
@@ -848,7 +848,7 @@ stage_again:
 #endif
 		int mlist = movlst;
 		_GP(mls)[mlist].numstage = numstages;
-		memcpy(&_GP(mls)[mlist].pos[0], &reallyneed[0], sizeof(int) * numstages);
+		memcpy(&_GP(mls)[mlist].pos[0], &reallyneed[0], sizeof(Point) * numstages);
 #ifdef DEBUG_PATHFINDER
 		AGS::Shared::Debug::Printf("stages: %d\n", numstages);
 #endif
@@ -859,13 +859,11 @@ stage_again:
 			calculate_move_stage(&_GP(mls)[mlist], aaa, fix_speed_x, fix_speed_y);
 		}
 
-		_GP(mls)[mlist].fromx = orisrcx;
-		_GP(mls)[mlist].fromy = orisrcy;
+		_GP(mls)[mlist].from = {orisrcx, orisrcy};
 		_GP(mls)[mlist].onstage = 0;
 		_GP(mls)[mlist].onpart = 0;
 		_GP(mls)[mlist].doneflag = 0;
-		_GP(mls)[mlist].lastx = -1;
-		_GP(mls)[mlist].lasty = -1;
+		_GP(mls)[mlist].last = {-1, -1};
 #ifdef DEBUG_PATHFINDER
 		// getch();
 #endif
@@ -886,11 +884,11 @@ bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int
 
 	const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
 	const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
-	mlsp->pos[mlsp->numstage] = MAKE_INTCOORD(x, y);
+	mlsp->pos[mlsp->numstage] = {x, y};
 	calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
 	mlsp->numstage++;
-	mlsp->lastx = x;
-	mlsp->lasty = y;
+	mlsp->last.X = x;
+	mlsp->last.Y = y;
 	return true;
 }
 
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index fd020db17f6..a47f8f9a31d 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -154,6 +154,8 @@ const int LegacyRoomVolumeFactor = 30;
 
 #define STD_BUFFER_SIZE 3000
 
+// NOTE: these flags are merged with the MoveList index;
+// but this means that the number of MoveList users will be limited by 1000
 #define TURNING_AROUND     1000
 #define TURNING_BACKWARDS 10000
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 71715a49214..c0b85339bd4 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -59,10 +59,10 @@ int do_movelist_move(short *mlnum, int *xx, int *yy) {
 	cmls = &_GP(mls)[mlnum[0]];
 	fixed xpermove = cmls->xpermove[cmls->onstage], ypermove = cmls->ypermove[cmls->onstage];
 
-	short targetx = short((cmls->pos[cmls->onstage + 1] >> 16) & 0x00ffff);
-	short targety = short(cmls->pos[cmls->onstage + 1] & 0x00ffff);
+	int targetx = cmls->pos[cmls->onstage + 1].X;
+	int targety = cmls->pos[cmls->onstage + 1].Y;
 	int xps = xx[0], yps = yy[0];
-	if (cmls->doneflag & 1) {
+	if (cmls->doneflag & kMoveListDone_X) {
 		// if the X-movement has finished, and the Y-per-move is < 1, finish
 		// This can cause jump at the end, but without it the character will
 		// walk on the spot for a while if the Y-per-move is for example 0.2
@@ -90,9 +90,9 @@ int do_movelist_move(short *mlnum, int *xx, int *yy) {
 		// Y per move is > -1, so finish the move
 		else if ((ypermove & 0xffff0000) == 0xffff0000)
 			targety += adjAmnt;
-	} else xps = cmls->fromx + (int)(fixtof(xpermove) * (float)cmls->onpart);
+	} else xps = cmls->from.X + (int)(fixtof(xpermove) * (float)cmls->onpart);
 
-	if (cmls->doneflag & 2) {
+	if (cmls->doneflag & kMoveListDone_Y) {
 		// Y-movement has finished
 
 		int adjAmnt = 3;
@@ -117,11 +117,11 @@ int do_movelist_move(short *mlnum, int *xx, int *yy) {
 		/*    int xpmm=(xpermove >> 16) & 0x0000ffff;
 		//    if ((xpmm==0) | (xpmm==0xffff)) cmls->doneflag|=1;
 		    if (xpmm==0) cmls->doneflag|=1;*/
-	} else yps = cmls->fromy + (int)(fixtof(ypermove) * (float)cmls->onpart);
+	} else yps = cmls->from.Y + (int)(fixtof(ypermove) * (float)cmls->onpart);
 	// check if finished horizontal movement
 	if (((xpermove > 0) && (xps >= targetx)) ||
 	        ((xpermove < 0) && (xps <= targetx))) {
-		cmls->doneflag |= 1;
+		cmls->doneflag |= kMoveListDone_X;
 		xps = targetx;
 		// if the Y is almost there too, finish it
 		// this is new in v2.40
@@ -129,32 +129,27 @@ int do_movelist_move(short *mlnum, int *xx, int *yy) {
 		/*if (abs(yps - targety) <= 2)
 		  yps = targety;*/
 	} else if (xpermove == 0)
-		cmls->doneflag |= 1;
+		cmls->doneflag |= kMoveListDone_X;
 	// check if finished vertical movement
 	if ((ypermove > 0) & (yps >= targety)) {
-		cmls->doneflag |= 2;
+		cmls->doneflag |= kMoveListDone_Y;
 		yps = targety;
 	} else if ((ypermove < 0) & (yps <= targety)) {
-		cmls->doneflag |= 2;
+		cmls->doneflag |= kMoveListDone_Y;
 		yps = targety;
 	} else if (ypermove == 0)
-		cmls->doneflag |= 2;
+		cmls->doneflag |= kMoveListDone_Y;
 
-	if ((cmls->doneflag & 0x03) == 3) {
+	if ((cmls->doneflag & kMoveListDone_XY) == kMoveListDone_XY) {
 		// this stage is done, go on to the next stage
-		// signed shorts to ensure that numbers like -20 do not become 65515
-		cmls->fromx = (signed short)((cmls->pos[cmls->onstage + 1] >> 16) & 0x000ffff);
-		cmls->fromy = (signed short)(cmls->pos[cmls->onstage + 1] & 0x000ffff);
-		if ((cmls->fromx > 65000) || (cmls->fromy > 65000))
-			quit("do_movelist: int to short rounding error");
-
-		cmls->onstage++;
-		cmls->onpart = -1;
-		cmls->doneflag &= 0xf0;
-		cmls->lastx = -1;
+		cmls->from = cmls->pos[cmls->onstage + 1];
+
+		cmls->onstage++; cmls->onpart = -1; cmls->doneflag = 0;
+		cmls->last.X = -1;
+
 		if (cmls->onstage < cmls->numstage) {
-			xps = cmls->fromx;
-			yps = cmls->fromy;
+			xps = cmls->from.X;
+			yps = cmls->from.Y;
 		}
 		if (cmls->onstage >= cmls->numstage - 1) {  // last stage is just dest pos
 			cmls->numstage = 0;
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index e2d6b196da4..f0e87561bd4 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -346,7 +346,7 @@ Globals::Globals() {
 	_rgb_table = new RGB_MAP();
 
 	// route_finder_impl.cpp globals
-	_navpoints = new int32_t[MAXNEEDSTAGES];
+	_navpoints = new Point[MAXNEEDSTAGES];
 	_nav = new Navigation();
 	_route_finder_impl = new std::unique_ptr<IRouteFinder>();
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 01719274cdf..68c1e702e8a 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1220,7 +1220,7 @@ public:
 	 * @{
 	 */
 
-	int32_t *_navpoints;
+	Point *_navpoints;
 	Navigation *_nav;
 	int _num_navpoints = 0;
 	AGS::Shared::Bitmap *_wallscreen = nullptr;
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 7412b840e52..e160c5a840b 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -714,8 +714,8 @@ int IAGSEngine::GetMovementPathLastWaypoint(int32 pathId) {
 }
 
 void IAGSEngine::GetMovementPathWaypointLocation(int32 pathId, int32 waypoint, int32 *x, int32 *y) {
-	*x = (_GP(mls)[pathId % TURNING_AROUND].pos[waypoint] >> 16) & 0x0000ffff;
-	*y = (_GP(mls)[pathId % TURNING_AROUND].pos[waypoint] & 0x0000ffff);
+	*x = _GP(mls)[pathId % TURNING_AROUND].pos[waypoint].X;
+	*y = _GP(mls)[pathId % TURNING_AROUND].pos[waypoint].Y;
 }
 
 void IAGSEngine::GetMovementPathWaypointSpeed(int32 pathId, int32 waypoint, int32 *xSpeed, int32 *ySpeed) {
diff --git a/engines/ags/shared/util/geometry.h b/engines/ags/shared/util/geometry.h
index 0f7b27ed13c..6be8a57075b 100644
--- a/engines/ags/shared/util/geometry.h
+++ b/engines/ags/shared/util/geometry.h
@@ -109,6 +109,10 @@ struct Point {
 	inline Point operator +(const Point &p) const {
 		return Point(X + p.X, Y + p.Y);
 	}
+
+	inline bool Equals(const int x, const int y) const {
+		return X == x && Y == y;
+	}
 };
 
 struct Line {


Commit: 56c790c838cf5b746343a5ed117192c28f18750e
    https://github.com/scummvm/scummvm/commit/56c790c838cf5b746343a5ed117192c28f18750e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidy do_movelist_move() and MoveList further

>From upstream 3292c4cf7ff549832fdd0249ac1ee6d805e91f93

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/ac/route_finder_impl_legacy.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/engine/main/update.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 207f8445d5e..6726c1819b9 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -154,9 +154,9 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 		return;
 	}
 
-	cmls->pos[cmls->numstage] = { x, y };
+	cmls->pos[cmls->numstage] = {x, y};
 	// They're already walking there anyway
-	if (cmls->last.X == x && cmls->last.Y == y)
+	if (cmls->pos[cmls->numstage] == cmls->pos[cmls->numstage - 1])
 		return;
 
 	int move_speed_x, move_speed_y;
@@ -1795,7 +1795,7 @@ int has_hit_another_character(int sourceChar) {
 int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *chex) {
 	int ntf = 0, xwas = chi->x, ywas = chi->y;
 
-	if (do_movelist_move(&chi->walking, &chi->x, &chi->y) == 2) {
+	if (do_movelist_move(chi->walking, chi->x, chi->y) == 2) {
 		if ((chi->flags & CHF_MOVENOTWALK) == 0)
 			fix_player_sprite(&_GP(mls)[chi->walking], chi);
 	}
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index 021518b66b5..71701bd3efd 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -41,8 +41,8 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	onpart = in->ReadInt32();
-	last.X = in->ReadInt32();
-	last.Y = in->ReadInt32();
+	in->ReadInt32(); // UNUSED
+	in->ReadInt32(); // UNUSED
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 }
@@ -64,8 +64,8 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	onpart = in->ReadInt32();
-	last.X = in->ReadInt32();
-	last.Y = in->ReadInt32();
+	in->ReadInt32(); // UNUSED
+	in->ReadInt32();
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
@@ -85,8 +85,8 @@ void MoveList::WriteToFile(Stream *out) {
 	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
 	out->WriteInt32(onpart);
-	out->WriteInt32(last.X);
-	out->WriteInt32(last.Y);
+	out->WriteInt32(0); // UNUSED
+	out->WriteInt32(0);
 	out->WriteInt8(doneflag);
 	out->WriteInt8(direct);
 
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index e92bfff6268..767ce40f7de 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -48,12 +48,13 @@ enum MoveListDoneFlags {
 struct MoveList {
 	int 	numstage = 0;
 	Point 	pos[MAXNEEDSTAGES];
+	// xpermove and ypermove contain number of pixels done per a single step
+	// along x and y axes; i.e. this is a movement vector, per path stage
 	fixed 	xpermove[MAXNEEDSTAGES]{};
 	fixed 	ypermove[MAXNEEDSTAGES]{};
 	Point 	from;
-	int 	onstage = 0;
-	int 	onpart = 0;
-	Point 	last;
+	int 	onstage = 0; // current path stage
+	int 	onpart = 0; // total number of steps done on this stage
 	uint8_t doneflag = 0u;
 	uint8_t direct = 0; // MoveCharDirect was used or not
 
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index f1ad9f6d74f..eb5e8fd72a3 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1042,7 +1042,6 @@ void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_st
 		return;
 
 	ml->from = {mask_to_room_coord(ml->from.X), mask_to_room_coord(ml->from.Y)};
-	ml->last = {mask_to_room_coord(ml->last.X), mask_to_room_coord(ml->last.Y)};
 
 	for (int i = from_step; i <= to_step; i++) {
 		ml->pos[i] = {mask_to_room_coord(ml->pos[i].X), mask_to_room_coord(ml->pos[i].Y)};
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 9caad3e3645..7a2176d64b9 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -75,7 +75,7 @@ int RoomObject::get_baseline() const {
 void RoomObject::UpdateCyclingView(int ref_id) {
 	if (on != 1) return;
 	if (moving > 0) {
-		do_movelist_move(&moving, &x, &y);
+		do_movelist_move(moving, x, y);
 	}
 	if (cycling == 0) return;
 	if (view == RoomObject::NoView) return;
diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index 62d0ae3cdcf..474a2879083 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -234,7 +234,6 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 	_GP(mls)[mlist].onstage = 0;
 	_GP(mls)[mlist].onpart = 0;
 	_GP(mls)[mlist].doneflag = 0;
-	_GP(mls)[mlist].last = { -1, -1};
 	return mlist;
 }
 
@@ -247,8 +246,6 @@ bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int
 	mlsp->pos[mlsp->numstage] = {x, y};
 	calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
 	mlsp->numstage++;
-	mlsp->last.X = x;
-	mlsp->last.Y = y;
 	return true;
 }
 
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 88ac559819f..17ceef43289 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -863,7 +863,6 @@ stage_again:
 		_GP(mls)[mlist].onstage = 0;
 		_GP(mls)[mlist].onpart = 0;
 		_GP(mls)[mlist].doneflag = 0;
-		_GP(mls)[mlist].last = {-1, -1};
 #ifdef DEBUG_PATHFINDER
 		// getch();
 #endif
@@ -887,8 +886,6 @@ bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int
 	mlsp->pos[mlsp->numstage] = {x, y};
 	calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
 	mlsp->numstage++;
-	mlsp->last.X = x;
-	mlsp->last.Y = y;
 	return true;
 }
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index c0b85339bd4..e8e66209c52 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -52,118 +52,114 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-int do_movelist_move(short *mlnum, int *xx, int *yy) {
-	int need_to_fix_sprite = 0;
-	if (mlnum[0] < 1) quit("movelist_move: attempted to move on a non-exist movelist");
-	MoveList *cmls;
-	cmls = &_GP(mls)[mlnum[0]];
-	fixed xpermove = cmls->xpermove[cmls->onstage], ypermove = cmls->ypermove[cmls->onstage];
-
-	int targetx = cmls->pos[cmls->onstage + 1].X;
-	int targety = cmls->pos[cmls->onstage + 1].Y;
-	int xps = xx[0], yps = yy[0];
-	if (cmls->doneflag & kMoveListDone_X) {
-		// if the X-movement has finished, and the Y-per-move is < 1, finish
-		// This can cause jump at the end, but without it the character will
-		// walk on the spot for a while if the Y-per-move is for example 0.2
-		//    if ((ypermove & 0xfffff000) == 0) cmls->doneflag|=2;
-		//    int ypmm=(ypermove >> 16) & 0x0000ffff;
-
-		// NEW 2.15 SR-1 plan: if X-movement has finished, and Y-per-move is < 1,
-		// allow it to finish more easily by moving target zone
-
-		int adjAmnt = 3;
-		// 2.70: if the X permove is also <=1, don't do the skipping
-		if (((xpermove & 0xffff0000) == 0xffff0000) ||
-		        ((xpermove & 0xffff0000) == 0x00000000))
-			adjAmnt = 2;
-
-		// 2.61 RC1: correct this to work with > -1 as well as < 1
-		if (ypermove == 0) {
-		}
-		// Y per move is < 1, so finish the move
-		else if ((ypermove & 0xffff0000) == 0)
-			targety -= adjAmnt;
-		// Y per move is -1 exactly, don't snap to finish
-		else if (ypermove == (fixed)0xffff0000) {
-		}
-		// Y per move is > -1, so finish the move
-		else if ((ypermove & 0xffff0000) == 0xffff0000)
-			targety += adjAmnt;
-	} else xps = cmls->from.X + (int)(fixtof(xpermove) * (float)cmls->onpart);
-
-	if (cmls->doneflag & kMoveListDone_Y) {
-		// Y-movement has finished
-
-		int adjAmnt = 3;
+static void movelist_handle_remainer(const fixed &xpermove, const fixed &ypermove, int &targety) {
+	// Old comment about ancient behavior:
+	// if the X-movement has finished, and the Y-per-move is < 1, finish
+	// This can cause jump at the end, but without it the character will
+	// walk on the spot for a while if the Y-per-move is for example 0.2
+	//    if ((ypermove & 0xfffff000) == 0) cmls.doneflag|=2;
+	//    int ypmm=(ypermove >> 16) & 0x0000ffff;
+
+	// NEW 2.15 SR-1 plan: if X-movement has finished, and Y-per-move is < 1,
+	// allow it to finish more easily by moving target zone
+
+	int tfix = 3;
+	// 2.70: if the X permove is also <=1, don't skip as far
+	if (((xpermove & 0xffff0000) == 0xffff0000) ||
+		((xpermove & 0xffff0000) == 0x00000000))
+		tfix = 2;
+
+	// 2.61 RC1: correct this to work with > -1 as well as < 1
+	if (ypermove == 0) {
+	}
+	// Y per move is < 1, so finish the move
+	else if ((ypermove & 0xffff0000) == 0)
+		targety -= tfix;
+	// Y per move is -1 exactly, don't snap to finish
+	else if (ypermove == 0xffff0000) {
+	}
+	// Y per move is > -1, so finish the move
+	else if ((ypermove & 0xffff0000) == 0xffff0000)
+		targety += tfix;
+}
 
-		// if the Y permove is also <=1, don't skip as far
-		if (((ypermove & 0xffff0000) == 0xffff0000) ||
-		        ((ypermove & 0xffff0000) == 0x00000000))
-			adjAmnt = 2;
+int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
+	// TODO: find out why movelist 0 is not being used
+	assert(mslot >= 1);
+	if (mslot < 1)
+		return 0;
+
+	int need_to_fix_sprite = 0; // TODO: find out what this value means and refactor
+	MoveList &cmls = _GP(mls)[mslot];
+	fixed xpermove = cmls.xpermove[cmls.onstage];
+	fixed ypermove = cmls.ypermove[cmls.onstage];
+	int targetx = cmls.pos[cmls.onstage + 1].X;
+	int targety = cmls.pos[cmls.onstage + 1].Y;
+	int xps = pos_x, yps = pos_y;
+
+	if (cmls.doneflag & kMoveListDone_X) {
+		// X-move has finished, handle the Y-move remainer
+		movelist_handle_remainer(xpermove, ypermove, targety);
+	} else {
+		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)cmls.onpart);
+	}
 
-		if (xpermove == 0) {
-		}
-		// Y per move is < 1, so finish the move
-		else if ((xpermove & 0xffff0000) == 0)
-			targetx -= adjAmnt;
-		// X per move is -1 exactly, don't snap to finish
-		else if (xpermove == (fixed)0xffff0000) {
-		}
-		// X per move is > -1, so finish the move
-		else if ((xpermove & 0xffff0000) == 0xffff0000)
-			targetx += adjAmnt;
-
-		/*    int xpmm=(xpermove >> 16) & 0x0000ffff;
-		//    if ((xpmm==0) | (xpmm==0xffff)) cmls->doneflag|=1;
-		    if (xpmm==0) cmls->doneflag|=1;*/
-	} else yps = cmls->from.Y + (int)(fixtof(ypermove) * (float)cmls->onpart);
+	if (cmls.doneflag & kMoveListDone_Y) {
+		// Y-move has finished, handle the X-move remainer
+		movelist_handle_remainer(ypermove, xpermove, targetx);
+	} else {
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)cmls.onpart);
+	}
 	// check if finished horizontal movement
 	if (((xpermove > 0) && (xps >= targetx)) ||
-	        ((xpermove < 0) && (xps <= targetx))) {
-		cmls->doneflag |= kMoveListDone_X;
+		((xpermove < 0) && (xps <= targetx))) {
+		cmls.doneflag |= kMoveListDone_X;
 		xps = targetx;
+		// Comment about old engine behavior:
 		// if the Y is almost there too, finish it
 		// this is new in v2.40
 		// removed in 2.70
 		/*if (abs(yps - targety) <= 2)
 		  yps = targety;*/
-	} else if (xpermove == 0)
-		cmls->doneflag |= kMoveListDone_X;
+	} else if (xpermove == 0) { // NOTE: do not snap pos_x to target in this case (?)
+		cmls.doneflag |= kMoveListDone_X;
+	}
 	// check if finished vertical movement
-	if ((ypermove > 0) & (yps >= targety)) {
-		cmls->doneflag |= kMoveListDone_Y;
+	if (((ypermove > 0) && (yps >= targety)) ||
+		((ypermove < 0) & (yps <= targety))) {
+		cmls.doneflag |= kMoveListDone_Y;
 		yps = targety;
-	} else if ((ypermove < 0) & (yps <= targety)) {
-		cmls->doneflag |= kMoveListDone_Y;
-		yps = targety;
-	} else if (ypermove == 0)
-		cmls->doneflag |= kMoveListDone_Y;
+	} else if (ypermove == 0) { // NOTE: do not snap pos_y to target in this case (?)
+		cmls.doneflag |= kMoveListDone_Y;
+	}
 
-	if ((cmls->doneflag & kMoveListDone_XY) == kMoveListDone_XY) {
+	if ((cmls.doneflag & kMoveListDone_XY) == kMoveListDone_XY) {
 		// this stage is done, go on to the next stage
-		cmls->from = cmls->pos[cmls->onstage + 1];
-
-		cmls->onstage++; cmls->onpart = -1; cmls->doneflag = 0;
-		cmls->last.X = -1;
+		cmls.from = cmls.pos[cmls.onstage + 1];
+		cmls.onstage++;
+		cmls.onpart = -1;
+		cmls.doneflag = 0;
+		if (cmls.onstage < cmls.numstage) {
+			xps = cmls.from.X;
+			yps = cmls.from.Y;
+		}
 
-		if (cmls->onstage < cmls->numstage) {
-			xps = cmls->from.X;
-			yps = cmls->from.Y;
+		if (cmls.onstage >= cmls.numstage - 1) { // last stage is just dest pos
+			cmls.numstage = 0;
+			mslot = 0;
+			need_to_fix_sprite = 1; // TODO: find out what this means
+		} else {
+			need_to_fix_sprite = 2; // TODO: find out what this means
 		}
-		if (cmls->onstage >= cmls->numstage - 1) {  // last stage is just dest pos
-			cmls->numstage = 0;
-			mlnum[0] = 0;
-			need_to_fix_sprite = 1;
-		} else need_to_fix_sprite = 2;
 	}
-	cmls->onpart++;
-	xx[0] = xps;
-	yy[0] = yps;
+
+	// Make a step along the current vector and return
+	cmls.onpart++;
+	pos_x = xps;
+	pos_y = yps;
 	return need_to_fix_sprite;
 }
 
-
 void update_script_timers() {
 	if (_GP(play).gscript_timer > 0) _GP(play).gscript_timer--;
 	for (int aa = 0; aa < MAX_TIMERS; aa++) {
diff --git a/engines/ags/engine/main/update.h b/engines/ags/engine/main/update.h
index af804c571a9..e78d7cfe005 100644
--- a/engines/ags/engine/main/update.h
+++ b/engines/ags/engine/main/update.h
@@ -24,7 +24,12 @@
 
 namespace AGS3 {
 
-int do_movelist_move(short *mlnum, int *xx, int *yy);
+// Update MoveList of certain index, save current position;
+// *resets* mslot to zero if path is complete.
+// returns "need_to_fix_sprite" value, which may be 0,1,2;
+// TODO: find out what this return value means, and refactor.
+// TODO: do not reset mslot in this function, reset externally instead.
+int do_movelist_move(short &mslot, int &pos_x, int &pos_y);
 void update_stuff();
 
 } // namespace AGS3


Commit: f1a9a35758ba9ae807f4d6dac1396c287af22c7e
    https://github.com/scummvm/scummvm/commit/f1a9a35758ba9ae807f4d6dac1396c287af22c7e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: a new fix for character stalling before finishing movement

AGS historically has a hidden problem: its pathfinder and walking algorithm uses math with relatively low precision.
 This may result in rounding mistakes. What this means in practice is that under some circumstances the
  "movement direction" (vector) is slightly off, e.g. more towards X than Y (or other way). In such case the character
   may reach the X destination while Y destination is still few pixels away.

How is this issue solved currently in AGS (and for a long time): when AGS finds out that one coordinate is complete,
 but another is not (for example X done and Y is still not reached), it starts waiting until character reaches Y, but also
  accepts a certain "mistake", in 2-3 pixels. If character is already within 2-3 pixels from the destination, then it just stops.
 This makes its real stopping point inaccurate, but nothing else happens.
 However, if character is further away, then it keeps "walking", but because the movement vector is still pointing
  in the same direction, usually it takes few moments for it to reach the proper Y coordinate.
   This results in a buggy effect known as "walking in place".

This commit introduces a new solution: turn the walking vector to make it point right along the unfinished axis,
 while keeping its length (that means - walking speed), in order to reach the destination faster.
This should make character make some extra short move, properly animating, but this may look more natural
 than just moving legs in place.

NOTE: I still keep the "destination fix" for the old games, as they may be scripted specifically to test
 where character has stopped moving.
 From upstream c3f03dea01a897b23795298e77db56506e44902f

Changed paths:
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index 71701bd3efd..c6fd6c0f63d 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -28,6 +28,13 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
+float MoveList::GetStepLength() const {
+	assert(numstage > 0);
+	float permove_x = fixtof(xpermove[onstage]);
+	float permove_y = fixtof(ypermove[onstage]);
+	return sqrt(permove_x * permove_x + permove_y * permove_y);
+}
+
 void MoveList::ReadFromFile_Legacy(Stream *in) {
 	for (int i = 0; i < MAXNEEDSTAGES_LEGACY; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 767ce40f7de..5cca780b1e3 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -58,6 +58,10 @@ struct MoveList {
 	uint8_t doneflag = 0u;
 	uint8_t direct = 0; // MoveCharDirect was used or not
 
+	// Gets a movelist's step length, in coordinate units
+	// (normally the coord unit is a game pixel)
+	float GetStepLength() const;
+
 	void ReadFromFile_Legacy(Shared::Stream *in);
 	AGS::Engine::HSaveError ReadFromFile(Shared::Stream *in, int32_t cmp_ver);
 	void WriteToFile(Shared::Stream *out);
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index e8e66209c52..0fdca3f325a 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -52,7 +52,11 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-static void movelist_handle_remainer(const fixed &xpermove, const fixed &ypermove, int &targety) {
+
+// Optionally fixes target position, when one axis is left to move along.
+// This is done only for backwards compatibility now.
+// Uses generic parameters.
+static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermove, int &targety) {
 	// Old comment about ancient behavior:
 	// if the X-movement has finished, and the Y-per-move is < 1, finish
 	// This can cause jump at the end, but without it the character will
@@ -62,6 +66,8 @@ static void movelist_handle_remainer(const fixed &xpermove, const fixed &ypermov
 
 	// NEW 2.15 SR-1 plan: if X-movement has finished, and Y-per-move is < 1,
 	// allow it to finish more easily by moving target zone
+	// NOTE: interesting fact: this fix was also done for the strictly vertical
+	// move, probably because of the logical mistake in condition.
 
 	int tfix = 3;
 	// 2.70: if the X permove is also <=1, don't skip as far
@@ -83,6 +89,16 @@ static void movelist_handle_remainer(const fixed &xpermove, const fixed &ypermov
 		targety += tfix;
 }
 
+// Handle remaining move along a single axis; uses generic parameters.
+static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove, const int xdistance,
+									 const int onpart, const float step_length, int &fin_ymove, int &fin_onpart) {
+	// Walk along the remaining axis with the full walking speed
+	assert(xpermove != 0 && ypermove != 0);
+	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
+	float onpart_to_dist = (float)xdistance / fixtof(xpermove);
+	fin_onpart = (int)((float)onpart - onpart_to_dist);
+}
+
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	// TODO: find out why movelist 0 is not being used
 	assert(mslot >= 1);
@@ -96,21 +112,33 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	int targetx = cmls.pos[cmls.onstage + 1].X;
 	int targety = cmls.pos[cmls.onstage + 1].Y;
 	int xps = pos_x, yps = pos_y;
-
-	if (cmls.doneflag & kMoveListDone_X) {
-		// X-move has finished, handle the Y-move remainer
-		movelist_handle_remainer(xpermove, ypermove, targety);
-	} else {
-		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)cmls.onpart);
+	const bool do_fix_target = _G(loaded_game_file_version) < kGameVersion_361;
+	fixed fin_xmove = 0, fin_ymove = 0;
+	int fin_onpart = 0;
+
+	// Handle possible move remainers
+	if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
+		if (do_fix_target)
+			movelist_handle_targetfix(xpermove, ypermove, targety);
+		if (xpermove != 0)
+			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X, cmls.onpart, cmls.GetStepLength(), fin_ymove, fin_onpart);
+	}
+	if ((xpermove != 0) && (cmls.doneflag & kMoveListDone_Y) != 0) { // Y-move has finished, handle the X-move remainer
+		if (do_fix_target)
+			movelist_handle_targetfix(xpermove, ypermove, targety);
+		if (ypermove != 0)
+			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y, cmls.onpart, cmls.GetStepLength(), fin_xmove, fin_onpart);
 	}
 
-	if (cmls.doneflag & kMoveListDone_Y) {
-		// Y-move has finished, handle the X-move remainer
-		movelist_handle_remainer(ypermove, xpermove, targetx);
-	} else {
-		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)cmls.onpart);
+	// Calculate next positions, as required
+	if ((cmls.doneflag & kMoveListDone_X) == 0) {
+		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)cmls.onpart) + (int)(fixtof(fin_xmove) * (float)fin_onpart);
+	}
+	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)cmls.onpart) + (int)(fixtof(fin_ymove) * (float)fin_onpart);
 	}
-	// check if finished horizontal movement
+
+	// Check if finished horizontal movement
 	if (((xpermove > 0) && (xps >= targetx)) ||
 		((xpermove < 0) && (xps <= targetx))) {
 		cmls.doneflag |= kMoveListDone_X;
@@ -124,7 +152,8 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	} else if (xpermove == 0) { // NOTE: do not snap pos_x to target in this case (?)
 		cmls.doneflag |= kMoveListDone_X;
 	}
-	// check if finished vertical movement
+
+	// Check if finished vertical movement
 	if (((ypermove > 0) && (yps >= targety)) ||
 		((ypermove < 0) & (yps <= targety))) {
 		cmls.doneflag |= kMoveListDone_Y;
@@ -133,6 +162,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		cmls.doneflag |= kMoveListDone_Y;
 	}
 
+	// Handle end of move stage
 	if ((cmls.doneflag & kMoveListDone_XY) == kMoveListDone_XY) {
 		// this stage is done, go on to the next stage
 		cmls.from = cmls.pos[cmls.onstage + 1];


Commit: 19496a8e24f0e89d2cd1b7c44c22e5108de33951
    https://github.com/scummvm/scummvm/commit/19496a8e24f0e89d2cd1b7c44c22e5108de33951
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed walking remainer duplicates speed

This complements/fixes c3f03de
>From upstream e699ca77b43da7c07691c52c50e6f786ec74a911

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 0fdca3f325a..10848236c79 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -91,12 +91,13 @@ static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermo
 
 // Handle remaining move along a single axis; uses generic parameters.
 static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove, const int xdistance,
-									 const int onpart, const float step_length, int &fin_ymove, int &fin_onpart) {
+									 const float step_length, int &onpart, int &fin_ymove, int &fin_onpart) {
 	// Walk along the remaining axis with the full walking speed
 	assert(xpermove != 0 && ypermove != 0);
 	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
 	float onpart_to_dist = (float)xdistance / fixtof(xpermove);
 	fin_onpart = (int)((float)onpart - onpart_to_dist);
+	onpart = onpart_to_dist;
 }
 
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
@@ -112,6 +113,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	int targetx = cmls.pos[cmls.onstage + 1].X;
 	int targety = cmls.pos[cmls.onstage + 1].Y;
 	int xps = pos_x, yps = pos_y;
+	int main_onpart = cmls.onpart;
 	const bool do_fix_target = _G(loaded_game_file_version) < kGameVersion_361;
 	fixed fin_xmove = 0, fin_ymove = 0;
 	int fin_onpart = 0;
@@ -121,21 +123,21 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		if (do_fix_target)
 			movelist_handle_targetfix(xpermove, ypermove, targety);
 		if (xpermove != 0)
-			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X, cmls.onpart, cmls.GetStepLength(), fin_ymove, fin_onpart);
+			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X, cmls.GetStepLength(), main_onpart, fin_ymove, fin_onpart);
 	}
 	if ((xpermove != 0) && (cmls.doneflag & kMoveListDone_Y) != 0) { // Y-move has finished, handle the X-move remainer
 		if (do_fix_target)
 			movelist_handle_targetfix(xpermove, ypermove, targety);
 		if (ypermove != 0)
-			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y, cmls.onpart, cmls.GetStepLength(), fin_xmove, fin_onpart);
+			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y, cmls.GetStepLength(), main_onpart, fin_xmove, fin_onpart);
 	}
 
 	// Calculate next positions, as required
 	if ((cmls.doneflag & kMoveListDone_X) == 0) {
-		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)cmls.onpart) + (int)(fixtof(fin_xmove) * (float)fin_onpart);
+		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)main_onpart) + (int)(fixtof(fin_xmove) * (float)fin_onpart);
 	}
 	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
-		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)cmls.onpart) + (int)(fixtof(fin_ymove) * (float)fin_onpart);
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)main_onpart) + (int)(fixtof(fin_ymove) * (float)fin_onpart);
 	}
 
 	// Check if finished horizontal movement


Commit: 99ca176e588778ed721c40587d0f34f92d1c4fed
    https://github.com/scummvm/scummvm/commit/99ca176e588778ed721c40587d0f34f92d1c4fed
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support changing walking speed during walk

Recalculate MoveList, substituting the walking speed factor.
>From upstream ca0a4e94c35c4d365299fabc33576b7283f275bf

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/route_finder.cpp
    engines/ags/engine/ac/route_finder.h
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/ac/route_finder_impl.h
    engines/ags/engine/ac/route_finder_impl_legacy.cpp
    engines/ags/globals.h
    engines/ags/shared/ac/character_info.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 6726c1819b9..2b750ac4441 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -855,7 +855,7 @@ void Character_SetOption(CharacterInfo *chaa, int flag, int yesorno) {
 void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed) {
 	if ((xspeed == 0) || (yspeed == 0))
 		quit("!SetCharacterSpeedEx: invalid speed value");
-	if (chaa->walking) {
+	if ((chaa->walking > 0) && (_G(loaded_game_file_version) < kGameVersion_350)) {
 		debug_script_warn("Character_SetSpeed: cannot change speed while walking");
 		return;
 	}
@@ -863,14 +863,19 @@ void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed) {
 	xspeed = Math::Clamp(xspeed, (int)INT16_MIN, (int)INT16_MAX);
 	yspeed = Math::Clamp(yspeed, (int)INT16_MIN, (int)INT16_MAX);
 
-	chaa->walkspeed = xspeed;
+	uint16_t old_speedx = chaa->walkspeed;
+	uint16_t old_speedy = ((chaa->walkspeed_y == UNIFORM_WALK_SPEED) ? chaa->walkspeed : chaa->walkspeed_y);
 
+	chaa->walkspeed = xspeed;
 	if (yspeed == xspeed)
 		chaa->walkspeed_y = UNIFORM_WALK_SPEED;
 	else
 		chaa->walkspeed_y = yspeed;
-}
 
+	if (chaa->walking > 0) {
+		recalculate_move_speeds(&_GP(mls)[chaa->walking % TURNING_AROUND], old_speedx, old_speedy, xspeed, yspeed);
+	}
+}
 
 void Character_StopMoving(CharacterInfo *charp) {
 
diff --git a/engines/ags/engine/ac/route_finder.cpp b/engines/ags/engine/ac/route_finder.cpp
index d936f969815..c31a878d724 100644
--- a/engines/ags/engine/ac/route_finder.cpp
+++ b/engines/ags/engine/ac/route_finder.cpp
@@ -51,6 +51,9 @@ public:
 	int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override {
 		return AGS::Engine::RouteFinder::find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
 	}
+	void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) override {
+		AGS::Engine::RouteFinder::recalculate_move_speeds(mlsp, old_speed_x, old_speed_y, new_speed_x, new_speed_y);
+	}
 	bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) override {
 		return AGS::Engine::RouteFinder::add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
 	}
@@ -78,6 +81,9 @@ public:
 	int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override {
 		return AGS::Engine::RouteFinderLegacy::find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
 	}
+	void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) override {
+		assert(false); // not supported
+	}
 	bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) override {
 		return AGS::Engine::RouteFinder::add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
 	}
@@ -116,6 +122,10 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 	return _GP(route_finder_impl)->find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
 }
 
+void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) {
+	_GP(route_finder_impl)->recalculate_move_speeds(mlsp, old_speed_x, old_speed_y, new_speed_x, new_speed_y);
+}
+
 bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
 	return _GP(route_finder_impl)->add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
 }
diff --git a/engines/ags/engine/ac/route_finder.h b/engines/ags/engine/ac/route_finder.h
index f52d112737d..425c8f6cc02 100644
--- a/engines/ags/engine/ac/route_finder.h
+++ b/engines/ags/engine/ac/route_finder.h
@@ -46,6 +46,7 @@ public:
 	virtual int can_see_from(int x1, int y1, int x2, int y2) = 0;
 	virtual void get_lastcpos(int &lastcx, int &lastcy) = 0;
 	virtual int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) = 0;
+	virtual void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) = 0;
 	virtual bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) = 0;
 };
 
@@ -58,6 +59,7 @@ int can_see_from(int x1, int y1, int x2, int y2);
 void get_lastcpos(int &lastcx, int &lastcy);
 
 int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0);
+void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y);
 // Append a waypoint to the move list, skip pathfinding
 bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y);
 
diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index 474a2879083..b43e95f6864 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -114,6 +114,31 @@ inline fixed input_speed_to_fixed(int speed_val) {
 	}
 }
 
+void set_route_move_speed(int speed_x, int speed_y) {
+	_G(move_speed_x) = input_speed_to_fixed(speed_x);
+	_G(move_speed_y) = input_speed_to_fixed(speed_y);
+}
+
+inline fixed calc_move_speed_at_angle(fixed speed_x, fixed speed_y, fixed xdist, fixed ydist) {
+	fixed useMoveSpeed;
+	if (speed_x == speed_y) {
+		useMoveSpeed = speed_x;
+	} else {
+		// different X and Y move speeds
+		// the X proportion of the movement is (x / (x + y))
+		fixed xproportion = fixdiv(xdist, (xdist + ydist));
+
+		if (speed_x > speed_y) {
+			// speed = y + ((1 - xproportion) * (x - y))
+			useMoveSpeed = speed_y + fixmul(xproportion, speed_x - speed_y);
+		} else {
+			// speed = x + (xproportion * (y - x))
+			useMoveSpeed = speed_x + fixmul(itofix(1) - xproportion, speed_y - speed_x);
+		}
+	}
+	return useMoveSpeed;
+}
+
 // Calculates the X and Y per game loop, for this stage of the movelist
 void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
 	// work out the x & y per move. First, opp/adj=tan, so work out the angle
@@ -150,23 +175,7 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 	fixed xdist = itofix(abs(ourx - destx));
 	fixed ydist = itofix(abs(oury - desty));
 
-	fixed useMoveSpeed;
-
-	if (move_speed_x == move_speed_y) {
-		useMoveSpeed = move_speed_x;
-	} else {
-		// different X and Y move speeds
-		// the X proportion of the movement is (x / (x + y))
-		fixed xproportion = fixdiv(xdist, (xdist + ydist));
-
-		if (move_speed_x > move_speed_y) {
-			// speed = y + ((1 - xproportion) * (x - y))
-			useMoveSpeed = move_speed_y + fixmul(xproportion, move_speed_x - move_speed_y);
-		} else {
-			// speed = x + (xproportion * (y - x))
-			useMoveSpeed = move_speed_x + fixmul(itofix(1) - xproportion, move_speed_y - move_speed_x);
-		}
-	}
+	fixed useMoveSpeed = calc_move_speed_at_angle(move_speed_x, move_speed_y, xdist, ydist);
 
 	fixed angl = fixatan(fixdiv(ydist, xdist));
 
@@ -187,6 +196,50 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 	mlsp->ypermove[aaa] = newymove;
 }
 
+void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) {
+	const fixed old_movspeed_x = input_speed_to_fixed(old_speed_x);
+	const fixed old_movspeed_y = input_speed_to_fixed(old_speed_y);
+	const fixed new_movspeed_x = input_speed_to_fixed(new_speed_x);
+	const fixed new_movspeed_y = input_speed_to_fixed(new_speed_y);
+	// save current stage's step lengths, for later onpart's update
+	const fixed old_stage_xpermove = mlsp->xpermove[mlsp->onstage];
+	const fixed old_stage_ypermove = mlsp->ypermove[mlsp->onstage];
+
+	for (int i = 0; (i < mlsp->numstage) && ((mlsp->xpermove[i] != 0) || (mlsp->ypermove[i] != 0)); ++i) {
+		// First three cases where the speed is a plain factor, therefore
+		// we may simply divide on old one and multiple on a new one
+		if ((old_movspeed_x == old_movspeed_y) || // diagonal move at straight 45 degrees
+			(mlsp->xpermove[i] == 0) ||           // straight vertical move
+			(mlsp->ypermove[i] == 0))             // straight horizontal move
+		{
+			mlsp->xpermove[i] = fixdiv(fixmul(mlsp->xpermove[i], new_movspeed_x), old_movspeed_x);
+			mlsp->ypermove[i] = fixdiv(fixmul(mlsp->ypermove[i], new_movspeed_y), old_movspeed_y);
+		} else {
+			// Move at angle has adjusted speed factor, which we must recalculate first
+			short ourx = mlsp->pos[i].X;
+			short oury = mlsp->pos[i].Y;
+			short destx = mlsp->pos[i + 1].X;
+			short desty = mlsp->pos[i + 1].Y;
+
+			fixed xdist = itofix(abs(ourx - destx));
+			fixed ydist = itofix(abs(oury - desty));
+			fixed old_speed_at_angle = calc_move_speed_at_angle(old_movspeed_x, old_movspeed_y, xdist, ydist);
+			fixed new_speed_at_angle = calc_move_speed_at_angle(new_movspeed_x, new_movspeed_y, xdist, ydist);
+
+			mlsp->xpermove[i] = fixdiv(fixmul(mlsp->xpermove[i], new_speed_at_angle), old_speed_at_angle);
+			mlsp->ypermove[i] = fixdiv(fixmul(mlsp->ypermove[i], new_speed_at_angle), old_speed_at_angle);
+		}
+	}
+
+	// now adjust current passed stage fraction
+	if (mlsp->onpart >= 0) {
+		if (old_stage_xpermove != 0)
+			mlsp->onpart = (int)(fixtof(old_stage_xpermove) * (float)mlsp->onpart) / fixtof(mlsp->xpermove[mlsp->onstage]);
+		else
+			mlsp->onpart = (int)(fixtof(old_stage_ypermove) * (float)mlsp->onpart) / fixtof(mlsp->ypermove[mlsp->onstage]);
+	}
+}
+
 int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
 
 	_G(wallscreen) = onscreen;
diff --git a/engines/ags/engine/ac/route_finder_impl.h b/engines/ags/engine/ac/route_finder_impl.h
index 03683611662..c443e784e26 100644
--- a/engines/ags/engine/ac/route_finder_impl.h
+++ b/engines/ags/engine/ac/route_finder_impl.h
@@ -48,6 +48,7 @@ int can_see_from(int x1, int y1, int x2, int y2);
 void get_lastcpos(int &lastcx, int &lastcy);
 
 int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0);
+void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y);
 bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y);
 
 } // namespace RouteFinder
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 17ceef43289..94a1e468028 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -647,6 +647,11 @@ inline fixed input_speed_to_fixed(int speed_val) {
 	}
 }
 
+void set_route_move_speed(int speed_x, int speed_y) {
+	_G(move_speed_x) = input_speed_to_fixed(speed_x);
+	_G(move_speed_y) = input_speed_to_fixed(speed_y);
+}
+
 // Calculates the X and Y per game loop, for this stage of the movelist
 void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
 	assert(mlsp != nullptr);
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 68c1e702e8a..95196eded14 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1224,6 +1224,7 @@ public:
 	Navigation *_nav;
 	int _num_navpoints = 0;
 	AGS::Shared::Bitmap *_wallscreen = nullptr;
+	fixed _move_speed_x, _move_speed_y;
 	int _lastcx = 0, _lastcy = 0;
 	std::unique_ptr<IRouteFinder> *_route_finder_impl;
 
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 60c5aac9bc4..b41d4e503fe 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -121,7 +121,7 @@ struct CharacterInfo {
 	short pic_xoffs; // this is fixed in screen coordinates
 	short walkwaitcounter;
 	uint16_t loop, frame;
-	short walking;
+	short walking;  // stores movelist index, optionally +TURNING_AROUND
 	short animating; // stores CHANIM_* flags in lower byte and delay in upper byte
 	short walkspeed, animspeed;
 	short inv[MAX_INV];


Commit: 2dc8b6735000995c175509a8619e69945751bb37
    https://github.com/scummvm/scummvm/commit/2dc8b6735000995c175509a8619e69945751bb37
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in MoveList store steps as fractional values, for recalculation

Make MoveList::onpart a fixed point value too, allow more precise "current progress"
recalculation when the moving speed factor changes.
Possible TODO: on move update, the first step after recalculation should bring `onpart`
 the the nearest integer, to make the movement progress more consistent.

>From upstream b28ca9f86b8b70a73e4f6fa6f1c72ce76f29ecf6

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 2b750ac4441..b07121c9bed 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1822,7 +1822,7 @@ int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *che
 
 		if ((chi->walking < 1) || (chi->walking >= TURNING_AROUND)) ;
 		else if (_GP(mls)[chi->walking].onpart > 0) {
-			_GP(mls)[chi->walking].onpart --;
+			_GP(mls)[chi->walking].onpart -= itofix(1);
 			chi->x = xwas;
 			chi->y = ywas;
 		}
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 5cca780b1e3..3a454c354c8 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -47,14 +47,18 @@ enum MoveListDoneFlags {
 
 struct MoveList {
 	int 	numstage = 0;
+	// Waypoints, per stage
 	Point 	pos[MAXNEEDSTAGES];
 	// xpermove and ypermove contain number of pixels done per a single step
 	// along x and y axes; i.e. this is a movement vector, per path stage
 	fixed 	xpermove[MAXNEEDSTAGES]{};
 	fixed 	ypermove[MAXNEEDSTAGES]{};
-	Point 	from;
 	int 	onstage = 0; // current path stage
-	int 	onpart = 0; // total number of steps done on this stage
+	Point from;  // current stage's starting position
+	// Steps made during current stage;
+	// distance passed is calculated as xpermove[onstage] * onpart;
+	// made a fractional value to let recalculate movelist dynamically
+	fixed onpart = 0;
 	uint8_t doneflag = 0u;
 	uint8_t direct = 0; // MoveCharDirect was used or not
 
diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index b43e95f6864..b282bc77911 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -234,9 +234,9 @@ void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, i
 	// now adjust current passed stage fraction
 	if (mlsp->onpart >= 0) {
 		if (old_stage_xpermove != 0)
-			mlsp->onpart = (int)(fixtof(old_stage_xpermove) * (float)mlsp->onpart) / fixtof(mlsp->xpermove[mlsp->onstage]);
+			mlsp->onpart = fixdiv(fixmul(mlsp->onpart, old_stage_xpermove), mlsp->xpermove[mlsp->onstage]);
 		else
-			mlsp->onpart = (int)(fixtof(old_stage_ypermove) * (float)mlsp->onpart) / fixtof(mlsp->ypermove[mlsp->onstage]);
+			mlsp->onpart = fixdiv(fixmul(mlsp->onpart, old_stage_ypermove), mlsp->ypermove[mlsp->onstage]);
 	}
 }
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 10848236c79..5bf84828f12 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -91,12 +91,12 @@ static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermo
 
 // Handle remaining move along a single axis; uses generic parameters.
 static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove, const int xdistance,
-									 const float step_length, int &onpart, int &fin_ymove, int &fin_onpart) {
+									 const float step_length, fixed &onpart, fixed &fin_ymove, fixed &fin_onpart) {
 	// Walk along the remaining axis with the full walking speed
 	assert(xpermove != 0 && ypermove != 0);
 	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
-	float onpart_to_dist = (float)xdistance / fixtof(xpermove);
-	fin_onpart = (int)((float)onpart - onpart_to_dist);
+	fixed onpart_to_dist = ftofix((float)xdistance / fixtof(xpermove));
+	fin_onpart = onpart - onpart_to_dist;
 	onpart = onpart_to_dist;
 }
 
@@ -116,7 +116,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	int main_onpart = cmls.onpart;
 	const bool do_fix_target = _G(loaded_game_file_version) < kGameVersion_361;
 	fixed fin_xmove = 0, fin_ymove = 0;
-	int fin_onpart = 0;
+	fixed fin_onpart = 0;
 
 	// Handle possible move remainers
 	if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
@@ -134,10 +134,10 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 
 	// Calculate next positions, as required
 	if ((cmls.doneflag & kMoveListDone_X) == 0) {
-		xps = cmls.from.X + (int)(fixtof(xpermove) * (float)main_onpart) + (int)(fixtof(fin_xmove) * (float)fin_onpart);
+		xps = cmls.from.X + (int)(fixtof(xpermove) * fixtof(main_onpart)) + (int)(fixtof(fin_xmove) * fixtof(fin_onpart));
 	}
 	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
-		yps = cmls.from.Y + (int)(fixtof(ypermove) * (float)main_onpart) + (int)(fixtof(fin_ymove) * (float)fin_onpart);
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * fixtof(main_onpart)) + (int)(fixtof(fin_ymove) * fixtof(fin_onpart));
 	}
 
 	// Check if finished horizontal movement
@@ -169,7 +169,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		// this stage is done, go on to the next stage
 		cmls.from = cmls.pos[cmls.onstage + 1];
 		cmls.onstage++;
-		cmls.onpart = -1;
+		cmls.onpart = itofix(-1);
 		cmls.doneflag = 0;
 		if (cmls.onstage < cmls.numstage) {
 			xps = cmls.from.X;
@@ -186,7 +186,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	}
 
 	// Make a step along the current vector and return
-	cmls.onpart++;
+	cmls.onpart += itofix(1);
 	pos_x = xps;
 	pos_y = yps;
 	return need_to_fix_sprite;


Commit: dadd1500efdb6f44b80eeaac33e63de618d9f043
    https://github.com/scummvm/scummvm/commit/dadd1500efdb6f44b80eeaac33e63de618d9f043
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: save MoveLists as a separate component, store onpart fixed-pt

Backport the good idea from ags4, where movelists are written in their own save file
section, as separate data objects.
Remember that we have to keep loading them from within Character/Room data in old
saves though.

Save `onpart`member as fixed-point value. MoveLists from old saves have to convert it
from integer.
>From upstream 216bf453ec007d8d1545315837c6beadb49bc52f

Changed paths:
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame_components.cpp


diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index c6fd6c0f63d..7564f52ee7c 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -47,7 +47,7 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 	from.X = in->ReadInt32();
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
-	onpart = in->ReadInt32();
+	onpart = itofix(in->ReadInt32());
 	in->ReadInt32(); // UNUSED
 	in->ReadInt32(); // UNUSED
 	doneflag = in->ReadInt8();
@@ -55,12 +55,16 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 }
 
 HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
+	*this = MoveList();
+
 	if (cmp_ver < 1) {
 		ReadFromFile_Legacy(in);
 		return HSaveError::None();
 	}
 
 	numstage = in->ReadInt32();
+	if ((numstage == 0) && cmp_ver >= 2)
+		return HSaveError::None();
 	// TODO: reimplement MoveList stages as vector to avoid these limits
 	if (numstage > MAXNEEDSTAGES) {
 		return new SavegameError(kSvgErr_IncompatibleEngine,
@@ -76,6 +80,9 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
+	if (cmp_ver < 2)
+		onpart = itofix(onpart); // convert to fixed-point value
+
 	for (int i = 0; i < numstage; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
 		pos[i].Y = in->ReadInt16();
@@ -86,8 +93,11 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	return HSaveError::None();
 }
 
-void MoveList::WriteToFile(Stream *out) {
+void MoveList::WriteToFile(Stream *out) const {
 	out->WriteInt32(numstage);
+	if (numstage == 0)
+		return;
+
 	out->WriteInt32(from.X);
 	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 3a454c354c8..779bb0875b8 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -68,7 +68,7 @@ struct MoveList {
 
 	void ReadFromFile_Legacy(Shared::Stream *in);
 	AGS::Engine::HSaveError ReadFromFile(Shared::Stream *in, int32_t cmp_ver);
-	void WriteToFile(Shared::Stream *out);
+	void WriteToFile(Shared::Stream *out) const;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index 0d1c5cc0656..c2e95bd9bd3 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -54,7 +54,8 @@ enum RoomStatSvgVersion {
 	kRoomStatSvgVersion_Initial = 0,
 	kRoomStatSvgVersion_36025 = 3,
 	kRoomStatSvgVersion_36041 = 4,
-	kRoomStatSvgVersion_Current = kRoomStatSvgVersion_36041
+	kRoomStatSvgVersion_36109 = 5,
+	kRoomStatSvgVersion_Current = kRoomStatSvgVersion_36109
 };
 
 // RoomStatus contains everything about a room that could change at runtime.
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 090ff06634d..7f46641ebe4 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -485,8 +485,6 @@ HSaveError WriteCharacters(Stream *out) {
 		Properties::WriteValues(_GP(play).charProps[i], out);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			WriteTimesRun272(*_GP(game).intrChar[i], out);
-		// character movement path cache
-		_GP(mls)[CHMLSOFFS + i].WriteToFile(out);
 	}
 	return HSaveError::None();
 }
@@ -501,10 +499,12 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 		Properties::ReadValues(_GP(play).charProps[i], in);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
-		// character movement path cache
-		err = _GP(mls)[CHMLSOFFS + i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0);
-		if (!err)
-			return err;
+		// character movement path (for old saves)
+		if (cmp_ver < 3) {
+			err = _GP(mls)[CHMLSOFFS + i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0);
+			if (!err)
+				return err;
+		}
 	}
 	return err;
 }
@@ -935,13 +935,6 @@ HSaveError WriteThisRoom(Stream *out) {
 		out->WriteInt32(_GP(thisroom).WalkAreas[i].ScalingNear);
 	}
 
-	// room object movement paths cache
-	// CHECKME: not sure why it saves (object count + 1) move lists
-	out->WriteInt32(_GP(thisroom).Objects.size() + 1);
-	for (size_t i = 0; i < _GP(thisroom).Objects.size() + 1; ++i) {
-		_GP(mls)[i].WriteToFile(out);
-	}
-
 	// room music volume
 	out->WriteInt32(_GP(thisroom).Options.MusicVolume);
 
@@ -981,14 +974,17 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		r_data.RoomZoomLevels2[i] = in->ReadInt32();
 	}
 
-	// room object movement paths cache
-	int objmls_count = in->ReadInt32();
-	if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists"))
-		return err;
-	for (int i = 0; i < objmls_count; ++i) {
-		err = _GP(mls)[i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0); // FIXME cmp_ver, ugly
-		if (!err)
+	// room object movement paths, for old saves
+	if (cmp_ver < kRoomStatSvgVersion_36109) {
+		int objmls_count = in->ReadInt32();
+		if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists"))
 			return err;
+		const int mls_cmp_ver = cmp_ver > 0 ? 1 : 0;
+		for (int i = 0; i < objmls_count; ++i) {
+			err = _GP(mls)[i].ReadFromFile(in, mls_cmp_ver);
+			if (!err)
+				return err;
+		}
 	}
 
 	// save the new room music vol for later use
@@ -1001,6 +997,31 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 	return HSaveError::None();
 }
 
+HSaveError WriteMoveLists(Stream *out) {
+	out->WriteInt32(static_cast<int32_t>(_GP(mls).size()));
+	for (const auto &movelist : _GP(mls)) {
+		movelist.WriteToFile(out);
+	}
+	return HSaveError::None();
+}
+
+HSaveError ReadMoveLists(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+	HSaveError err;
+	size_t movelist_count = in->ReadInt32();
+	// TODO: this assertion is needed only because mls size is fixed to the
+	// number of characters + max number of objects, where each game object
+	// has a fixed movelist index. It may be removed if movelists will be
+	// allocated on demand with an arbitrary index instead.
+	if (!AssertGameContent(err, movelist_count, _GP(mls).size(), "Move Lists"))
+		return err;
+	for (size_t i = 0; i < movelist_count; ++i) {
+		err = _GP(mls)[i].ReadFromFile(in, cmp_ver);
+		if (!err)
+			return err;
+	}
+	return err;
+}
+
 HSaveError WriteManagedPool(Stream *out) {
 	ccSerializeAllObjects(out);
 	return HSaveError::None();
@@ -1036,7 +1057,7 @@ struct ComponentHandler {
 
 // Array of supported components
 struct ComponentHandlers {
-	const ComponentHandler _items[17] = {
+	const ComponentHandler _items[18] = {
 		{
 			"Game State",
 			kGSSvgVersion_350_10,
@@ -1053,7 +1074,7 @@ struct ComponentHandlers {
 		},
 		{
 			"Characters",
-			2,
+			3,
 			0,
 			WriteCharacters,
 			ReadCharacters
@@ -1123,18 +1144,25 @@ struct ComponentHandlers {
 		},
 		{
 			"Room States",
-			kRoomStatSvgVersion_36041,
+			kRoomStatSvgVersion_36109,
 			kRoomStatSvgVersion_Initial,
 			WriteRoomStates,
 			ReadRoomStates
 		},
 		{
 			"Loaded Room State",
-			kRoomStatSvgVersion_36041, // must correspond to "Room States"
+			kRoomStatSvgVersion_36109, // must correspond to "Room States"
 			kRoomStatSvgVersion_Initial,
 			WriteThisRoom,
 			ReadThisRoom
 		},
+		{
+			"Move Lists",
+			2,
+			0,
+			WriteMoveLists,
+			ReadMoveLists
+		},
 		{
 			"Managed Pool",
 			0,


Commit: 7d2b747cae71c07aade241a6a6ba9e883a3c5440
    https://github.com/scummvm/scummvm/commit/7d2b747cae71c07aade241a6a6ba9e883a3c5440
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: smooth transition when ordering to walk while walking

This is an attempt to keep character moving seamlessly when a new walking order
 is made while the character is already walking.

Historically, engine had a problem with this, because of how a walking step is calculated.
In short, a MoveList struct increments a number of "steps", but these "steps" may be less
than pixel, in which case it takes >1 frames to make a visual change.
When a new walking order is made, this step counter is reset. This means that if player,
for example, keeps clicking around, the character will be "stuttering".

In this commit we try following solution: if the character is already walking, then save
last value of a fraction of "unfinished" pixel step, and apply it to the new movelist as
a starting step counter.
This works pretty well for in case of manual orders.

I must note however, that still, due to the nature of this counter, there may be issues
if a new Walk order is assigned each game frame. This may be done e.g. in script.
In this case, depending on various factors (relation between x and y distance,
walking speed, linking to animation, etc) the character may visually walk in a "ladder"
instead of a straight line.
This is probably will not be noticed when commanded by a player, as players commonly
do not click that frequently.

At this point I'm uncertain whether fixing the above would be only a matter of changing
the types of variables (fixed -> float, in attempt to reduce conversion mistakes), or a matter
of changing the core movelist mechanics.

>From upstream a56e62ba65f16628265244a2da217c591b2a1271

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index b07121c9bed..0dbc25496d8 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1616,10 +1616,15 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 	// moving it looks smoother
 	int oldframe = chin->frame;
 	int waitWas = 0, animWaitWas = 0;
+	float wasStepFrac = 0.f;
 	// if they are currently walking, save the current Wait
 	if (chin->walking) {
 		waitWas = chin->walkwait;
 		animWaitWas = _GP(charextra)[chac].animwait;
+		const auto &movelist = _GP(mls)[chin->walking % TURNING_AROUND];
+		// We set (fraction + 1), because movelist is always +1 ahead of current character pos;
+		if (movelist.onpart > 0)
+			wasStepFrac = movelist.GetPixelUnitFraction() + movelist.GetStepLength();
 	}
 
 	StopMoving(chac);
@@ -1647,6 +1652,10 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 		_GP(mls)[mslot].direct = ignwal;
 		convert_move_path_to_room_resolution(&_GP(mls)[mslot]);
 
+		if (wasStepFrac > 0.f) {
+			_GP(mls)[mslot].SetPixelUnitFraction(wasStepFrac);
+		}
+
 		// cancel any pending waits on current animations
 		// or if they were already moving, keep the current wait -
 		// this prevents a glitch if MoveCharacter is called when they
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index 7564f52ee7c..c366f5eccf3 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -35,6 +35,18 @@ float MoveList::GetStepLength() const {
 	return sqrt(permove_x * permove_x + permove_y * permove_y);
 }
 
+float MoveList::GetPixelUnitFraction() const {
+	assert(numstage > 0);
+	float distance = GetStepLength() * fixtof(onpart);
+	return distance - floor(distance);
+}
+
+void MoveList::SetPixelUnitFraction(float frac) {
+	assert(numstage > 0);
+	float permove_dist = GetStepLength();
+	onpart = permove_dist > 0.f ? ftofix((1.f / permove_dist) * frac) : 0;
+}
+
 void MoveList::ReadFromFile_Legacy(Stream *in) {
 	for (int i = 0; i < MAXNEEDSTAGES_LEGACY; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 779bb0875b8..2fdaabaf961 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -65,6 +65,11 @@ struct MoveList {
 	// Gets a movelist's step length, in coordinate units
 	// (normally the coord unit is a game pixel)
 	float GetStepLength() const;
+	// Gets a fraction of a coordinate unit that is in progress of stepping over;
+	// (normally the coord unit is a game pixel)
+	float GetPixelUnitFraction() const;
+	// Sets a step progress to this fraction of a coordinate unit
+	void SetPixelUnitFraction(float frac);
 
 	void ReadFromFile_Legacy(Shared::Stream *in);
 	AGS::Engine::HSaveError ReadFromFile(Shared::Stream *in, int32_t cmp_ver);


Commit: 56b3fd3299ea7fd81eaea793a998ac378fb3fa21
    https://github.com/scummvm/scummvm/commit/56b3fd3299ea7fd81eaea793a998ac378fb3fa21
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: converted MoveList::onpart to float

>From upstream b0ba1ac225c0b4e5093e58ff70051cf0e297f06b

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/ac/route_finder_impl_legacy.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/shared/util/bbop.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 0dbc25496d8..62530f1612b 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1623,7 +1623,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 		animWaitWas = _GP(charextra)[chac].animwait;
 		const auto &movelist = _GP(mls)[chin->walking % TURNING_AROUND];
 		// We set (fraction + 1), because movelist is always +1 ahead of current character pos;
-		if (movelist.onpart > 0)
+		if (movelist.onpart > 0.f)
 			wasStepFrac = movelist.GetPixelUnitFraction() + movelist.GetStepLength();
 	}
 
@@ -1830,8 +1830,8 @@ int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *che
 		}
 
 		if ((chi->walking < 1) || (chi->walking >= TURNING_AROUND)) ;
-		else if (_GP(mls)[chi->walking].onpart > 0) {
-			_GP(mls)[chi->walking].onpart -= itofix(1);
+		else if (_GP(mls)[chi->walking].onpart > 0.f) {
+			_GP(mls)[chi->walking].onpart -= 1.f;
 			chi->x = xwas;
 			chi->y = ywas;
 		}
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index c366f5eccf3..e3f5298f574 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/engine/ac/move_list.h"
 #include "ags/shared/ac/common.h"
+#include "ags/shared/util/bbop.h"
 #include "ags/shared/util/stream.h"
 
 namespace AGS3 {
@@ -37,14 +38,14 @@ float MoveList::GetStepLength() const {
 
 float MoveList::GetPixelUnitFraction() const {
 	assert(numstage > 0);
-	float distance = GetStepLength() * fixtof(onpart);
+	float distance = GetStepLength() * onpart;
 	return distance - floor(distance);
 }
 
 void MoveList::SetPixelUnitFraction(float frac) {
 	assert(numstage > 0);
 	float permove_dist = GetStepLength();
-	onpart = permove_dist > 0.f ? ftofix((1.f / permove_dist) * frac) : 0;
+	onpart = permove_dist > 0.f ? (1.f / permove_dist) * frac : 0.f;
 }
 
 void MoveList::ReadFromFile_Legacy(Stream *in) {
@@ -59,7 +60,7 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 	from.X = in->ReadInt32();
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
-	onpart = itofix(in->ReadInt32());
+	onpart = static_cast<float>(in->ReadInt32());
 	in->ReadInt32(); // UNUSED
 	in->ReadInt32(); // UNUSED
 	doneflag = in->ReadInt8();
@@ -86,14 +87,16 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	from.X = in->ReadInt32();
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
-	onpart = in->ReadInt32();
+	BBOp::IntFloatSwap onpart_u(in->ReadInt32());
 	in->ReadInt32(); // UNUSED
 	in->ReadInt32();
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
 	if (cmp_ver < 2)
-		onpart = itofix(onpart); // convert to fixed-point value
+		onpart = static_cast<float>(onpart_u.val.i32);
+	else
+		onpart = onpart_u.val.f;
 
 	for (int i = 0; i < numstage; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
@@ -113,7 +116,7 @@ void MoveList::WriteToFile(Stream *out) const {
 	out->WriteInt32(from.X);
 	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
-	out->WriteInt32(onpart);
+	out->WriteInt32(BBOp::IntFloatSwap(onpart).val.i32);
 	out->WriteInt32(0); // UNUSED
 	out->WriteInt32(0);
 	out->WriteInt8(doneflag);
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 2fdaabaf961..d87a571263b 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -58,7 +58,7 @@ struct MoveList {
 	// Steps made during current stage;
 	// distance passed is calculated as xpermove[onstage] * onpart;
 	// made a fractional value to let recalculate movelist dynamically
-	fixed onpart = 0;
+	float onpart = 0.f;
 	uint8_t doneflag = 0u;
 	uint8_t direct = 0; // MoveCharDirect was used or not
 
diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index b282bc77911..e235d7cf40e 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -232,11 +232,11 @@ void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, i
 	}
 
 	// now adjust current passed stage fraction
-	if (mlsp->onpart >= 0) {
+	if (mlsp->onpart >= 0.f) {
 		if (old_stage_xpermove != 0)
-			mlsp->onpart = fixdiv(fixmul(mlsp->onpart, old_stage_xpermove), mlsp->xpermove[mlsp->onstage]);
+			mlsp->onpart = (mlsp->onpart * fixtof(old_stage_xpermove)) / fixtof(mlsp->xpermove[mlsp->onstage]);
 		else
-			mlsp->onpart = fixdiv(fixmul(mlsp->onpart, old_stage_ypermove), mlsp->ypermove[mlsp->onstage]);
+			mlsp->onpart = (mlsp->onpart * fixtof(old_stage_ypermove)) / fixtof(mlsp->ypermove[mlsp->onstage]);
 	}
 }
 
@@ -285,7 +285,7 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 
 	_GP(mls)[mlist].from = { srcx, srcy };
 	_GP(mls)[mlist].onstage = 0;
-	_GP(mls)[mlist].onpart = 0;
+	_GP(mls)[mlist].onpart = 0.f;
 	_GP(mls)[mlist].doneflag = 0;
 	return mlist;
 }
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 94a1e468028..985dd277e22 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -866,7 +866,7 @@ stage_again:
 
 		_GP(mls)[mlist].from = {orisrcx, orisrcy};
 		_GP(mls)[mlist].onstage = 0;
-		_GP(mls)[mlist].onpart = 0;
+		_GP(mls)[mlist].onpart = 0.f;
 		_GP(mls)[mlist].doneflag = 0;
 #ifdef DEBUG_PATHFINDER
 		// getch();
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 5bf84828f12..f2adcea272f 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -91,11 +91,11 @@ static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermo
 
 // Handle remaining move along a single axis; uses generic parameters.
 static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove, const int xdistance,
-									 const float step_length, fixed &onpart, fixed &fin_ymove, fixed &fin_onpart) {
+									 const float step_length, float &onpart, fixed &fin_ymove, float &fin_onpart) {
 	// Walk along the remaining axis with the full walking speed
 	assert(xpermove != 0 && ypermove != 0);
 	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
-	fixed onpart_to_dist = ftofix((float)xdistance / fixtof(xpermove));
+	float onpart_to_dist = (float)xdistance / fixtof(xpermove);
 	fin_onpart = onpart - onpart_to_dist;
 	onpart = onpart_to_dist;
 }
@@ -113,10 +113,10 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	int targetx = cmls.pos[cmls.onstage + 1].X;
 	int targety = cmls.pos[cmls.onstage + 1].Y;
 	int xps = pos_x, yps = pos_y;
-	int main_onpart = cmls.onpart;
 	const bool do_fix_target = _G(loaded_game_file_version) < kGameVersion_361;
+	float main_onpart = cmls.onpart;
 	fixed fin_xmove = 0, fin_ymove = 0;
-	fixed fin_onpart = 0;
+	float fin_onpart = 0;
 
 	// Handle possible move remainers
 	if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
@@ -134,10 +134,10 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 
 	// Calculate next positions, as required
 	if ((cmls.doneflag & kMoveListDone_X) == 0) {
-		xps = cmls.from.X + (int)(fixtof(xpermove) * fixtof(main_onpart)) + (int)(fixtof(fin_xmove) * fixtof(fin_onpart));
+		xps = cmls.from.X + (int)(fixtof(xpermove) * main_onpart) + (int)(fixtof(fin_xmove) * fin_onpart);
 	}
 	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
-		yps = cmls.from.Y + (int)(fixtof(ypermove) * fixtof(main_onpart)) + (int)(fixtof(fin_ymove) * fixtof(fin_onpart));
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * main_onpart) + (int)(fixtof(fin_ymove) * fin_onpart);
 	}
 
 	// Check if finished horizontal movement
@@ -169,7 +169,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		// this stage is done, go on to the next stage
 		cmls.from = cmls.pos[cmls.onstage + 1];
 		cmls.onstage++;
-		cmls.onpart = itofix(-1);
+		cmls.onpart = -1.f;
 		cmls.doneflag = 0;
 		if (cmls.onstage < cmls.numstage) {
 			xps = cmls.from.X;
@@ -186,7 +186,7 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	}
 
 	// Make a step along the current vector and return
-	cmls.onpart += itofix(1);
+	cmls.onpart += 1.f;
 	pos_x = xps;
 	pos_y = yps;
 	return need_to_fix_sprite;
diff --git a/engines/ags/shared/util/bbop.h b/engines/ags/shared/util/bbop.h
index 5ed820be52c..424a70d03ba 100644
--- a/engines/ags/shared/util/bbop.h
+++ b/engines/ags/shared/util/bbop.h
@@ -67,6 +67,17 @@ inline int FlagToNoFlag(int value, int flag1, int flag2) {
 
 
 namespace BitByteOperations {
+
+struct IntFloatSwap {
+	union {
+		float f;
+		int32_t i32;
+	} val;
+
+	explicit IntFloatSwap(int32_t i) { val.i32 = i; }
+	explicit IntFloatSwap(float f) { val.f = f; }
+};
+
 inline int16_t SwapBytesInt16(const int16_t val) {
 	return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);
 }
@@ -82,14 +93,9 @@ inline int64_t SwapBytesInt64(const int64_t val) {
 }
 
 inline float SwapBytesFloat(const float val) {
-	// (c) SDL2
-	union {
-		float f;
-		uint32_t ui32;
-	} swapper;
-	swapper.f = val;
-	swapper.ui32 = SwapBytesInt32(swapper.ui32);
-	return swapper.f;
+	IntFloatSwap swap(val);
+	swap.val.i32 = SwapBytesInt32(swap.val.i32);
+	return swap.val.f;
 }
 
 inline int16_t Int16FromLE(const int16_t val) {


Commit: f0d0caa6d1c5178dedae390d4fe77c2fc41c81e0
    https://github.com/scummvm/scummvm/commit/f0d0caa6d1c5178dedae390d4fe77c2fc41c81e0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: when fast-forwarding, skip blocking Say instantly

>From upstream 67e77534a64cb6bb464f72ffdeb41630f8126af4

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


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index f2ce82fcb4a..e1ffee0372b 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -259,9 +259,12 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	if (disp_type < DISPLAYTEXT_NORMALOVERLAY)
 		remove_screen_overlay(_GP(play).text_overlay_on);
 
-	int adjustedXX, adjustedYY;
-	bool alphaChannel;
-	Bitmap *text_window_ds = create_textual_image(text, asspch, isThought, xx, yy, adjustedXX, adjustedYY, wii, usingfont, allowShrink, alphaChannel);
+	// If fast-forwarding, then skip any blocking message immediately
+	if (_GP(play).fast_forward && (disp_type < DISPLAYTEXT_NORMALOVERLAY)) {
+		_GP(play).SetWaitSkipResult(SKIP_AUTOTIMER);
+		_GP(play).messagetime = -1;
+		return nullptr;
+	}
 
 	//
 	// Configure and create an overlay object
@@ -275,6 +278,10 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	default: ovrtype = disp_type; break; // must be precreated overlay id
 	}
 
+	int adjustedXX, adjustedYY;
+	bool alphaChannel;
+	Bitmap *text_window_ds = create_textual_image(text, asspch, isThought, xx, yy, adjustedXX, adjustedYY, wii, usingfont, allowShrink, alphaChannel);
+
 	size_t nse = add_screen_overlay(roomlayer, xx, yy, ovrtype, text_window_ds, adjustedXX - xx, adjustedYY - yy, alphaChannel);
 	auto *over = get_overlay(nse); // FIXME: optimize return value
 	// we should not delete text_window_ds here, because it is now owned by Overlay
@@ -289,13 +296,6 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	//
 
 	if (disp_type == DISPLAYTEXT_MESSAGEBOX) {
-		// If fast-forwarding, then skip immediately
-		if (_GP(play).fast_forward) {
-			remove_screen_overlay(OVER_TEXTMSG);
-			_GP(play).SetWaitSkipResult(SKIP_AUTOTIMER);
-			post_display_cleanup();
-			return nullptr;
-		}
 
 		int countdown = GetTextDisplayTime(text);
 		int skip_setting = user_to_internal_skip_speech((SkipSpeechStyle)_GP(play).skip_display);
@@ -366,11 +366,6 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 		invalidate_screen();
 	} else {
 		/* DISPLAYTEXT_SPEECH */
-		// if the speech does not time out, but we are skipping a cutscene,
-		// allow it to time out
-		if ((_GP(play).messagetime < 0) && (_GP(play).fast_forward))
-			_GP(play).messagetime = 2;
-
 		if (!overlayPositionFixed) {
 			over->SetRoomRelative(true);
 			VpPoint vpt = _GP(play).GetRoomViewport(0)->ScreenToRoom(over->x, over->y, false);


Commit: 45f011ecd8dba63bd51b054535b77285ddb6b957
    https://github.com/scummvm/scummvm/commit/45f011ecd8dba63bd51b054535b77285ddb6b957
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS; Engine: store MoveList final move as members, and serialize

This is to avoid recalculating exact same values on each movelist update.
The "final move" may continue for a while if the "short" axis of the move
vector is finished earlier than the "long" one.
When serializing, use 2 freed int32s, left after removing 2 unused member variables.

>From upstream 57e369ec2bcf6a8eee61c45be8266f45e8a0c922

Changed paths:
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index e3f5298f574..d9bb2717cef 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -49,6 +49,7 @@ void MoveList::SetPixelUnitFraction(float frac) {
 }
 
 void MoveList::ReadFromFile_Legacy(Stream *in) {
+	*this = MoveList(); // reset struct
 	for (int i = 0; i < MAXNEEDSTAGES_LEGACY; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
 		pos[i].Y = in->ReadInt16();
@@ -68,16 +69,16 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 }
 
 HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
-	*this = MoveList();
-
 	if (cmp_ver < 1) {
 		ReadFromFile_Legacy(in);
 		return HSaveError::None();
 	}
 
+	*this = MoveList(); // reset struct
 	numstage = in->ReadInt32();
-	if ((numstage == 0) && cmp_ver >= 2)
+	if ((numstage == 0) && cmp_ver >= 2) {
 		return HSaveError::None();
+	}
 	// TODO: reimplement MoveList stages as vector to avoid these limits
 	if (numstage > MAXNEEDSTAGES) {
 		return new SavegameError(kSvgErr_IncompatibleEngine,
@@ -88,16 +89,11 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	BBOp::IntFloatSwap onpart_u(in->ReadInt32());
-	in->ReadInt32(); // UNUSED
-	in->ReadInt32();
+	int finmove = in->ReadInt32();
+	BBOp::IntFloatSwap finpart_u(in->ReadInt32());
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
-	if (cmp_ver < 2)
-		onpart = static_cast<float>(onpart_u.val.i32);
-	else
-		onpart = onpart_u.val.f;
-
 	for (int i = 0; i < numstage; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
 		pos[i].Y = in->ReadInt16();
@@ -105,6 +101,18 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	}
 	in->ReadArrayOfInt32(xpermove, numstage);
 	in->ReadArrayOfInt32(ypermove, numstage);
+
+	// Some variables require conversion depending on a save version
+	if (cmp_ver < 2) {
+		onpart = static_cast<float>(onpart_u.val.i32);
+		fin_move = 0;
+		fin_from_part = 0.f;
+	} else {
+		onpart = onpart_u.val.f;
+		fin_move = finmove;
+		fin_from_part = finpart_u.val.f;
+	}
+
 	return HSaveError::None();
 }
 
@@ -117,8 +125,8 @@ void MoveList::WriteToFile(Stream *out) const {
 	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
 	out->WriteInt32(BBOp::IntFloatSwap(onpart).val.i32);
-	out->WriteInt32(0); // UNUSED
-	out->WriteInt32(0);
+	out->WriteInt32(fin_move);
+	out->WriteInt32(BBOp::IntFloatSwap(fin_from_part).val.i32);
 	out->WriteInt8(doneflag);
 	out->WriteInt8(direct);
 
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index d87a571263b..91c5db68fd2 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -54,11 +54,15 @@ struct MoveList {
 	fixed 	xpermove[MAXNEEDSTAGES]{};
 	fixed 	ypermove[MAXNEEDSTAGES]{};
 	int 	onstage = 0; // current path stage
-	Point from;  // current stage's starting position
+	Point 	from;  // current stage's starting position
 	// Steps made during current stage;
 	// distance passed is calculated as xpermove[onstage] * onpart;
 	// made a fractional value to let recalculate movelist dynamically
-	float onpart = 0.f;
+	float 	onpart = 0.f;
+	// Final section move speed and steps, used when an object
+	// finishes one of the axes sooner than the other
+	fixed	fin_move = 0;
+	float	fin_from_part = 0.f;
 	uint8_t doneflag = 0u;
 	uint8_t direct = 0; // MoveCharDirect was used or not
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index f2adcea272f..b4fb2d8e21f 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -90,14 +90,12 @@ static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermo
 }
 
 // Handle remaining move along a single axis; uses generic parameters.
-static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove, const int xdistance,
-									 const float step_length, float &onpart, fixed &fin_ymove, float &fin_onpart) {
+static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove,
+									 const int xdistance, const float step_length, fixed &fin_ymove, float &fin_from_part) {
 	// Walk along the remaining axis with the full walking speed
 	assert(xpermove != 0 && ypermove != 0);
 	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
-	float onpart_to_dist = (float)xdistance / fixtof(xpermove);
-	fin_onpart = onpart - onpart_to_dist;
-	onpart = onpart_to_dist;
+	fin_from_part = (float)xdistance / fixtof(xpermove);
 }
 
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
@@ -108,60 +106,60 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 
 	int need_to_fix_sprite = 0; // TODO: find out what this value means and refactor
 	MoveList &cmls = _GP(mls)[mslot];
-	fixed xpermove = cmls.xpermove[cmls.onstage];
-	fixed ypermove = cmls.ypermove[cmls.onstage];
+	const fixed xpermove = cmls.xpermove[cmls.onstage];
+	const fixed ypermove = cmls.ypermove[cmls.onstage];
+	const fixed fin_move = cmls.fin_move;
+	const float main_onpart = (cmls.fin_from_part > 0.f) ? cmls.fin_from_part : cmls.onpart;
+	const float fin_onpart = cmls.onpart - main_onpart;
 	int targetx = cmls.pos[cmls.onstage + 1].X;
 	int targety = cmls.pos[cmls.onstage + 1].Y;
 	int xps = pos_x, yps = pos_y;
-	const bool do_fix_target = _G(loaded_game_file_version) < kGameVersion_361;
-	float main_onpart = cmls.onpart;
-	fixed fin_xmove = 0, fin_ymove = 0;
-	float fin_onpart = 0;
-
-	// Handle possible move remainers
-	if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
-		if (do_fix_target)
+
+	// Old-style optional move target fixup
+	if (_G(loaded_game_file_version) < kGameVersion_361) {
+		if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
 			movelist_handle_targetfix(xpermove, ypermove, targety);
-		if (xpermove != 0)
-			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X, cmls.GetStepLength(), main_onpart, fin_ymove, fin_onpart);
-	}
-	if ((xpermove != 0) && (cmls.doneflag & kMoveListDone_Y) != 0) { // Y-move has finished, handle the X-move remainer
-		if (do_fix_target)
+		} else if ((xpermove != 0) && (cmls.doneflag & kMoveListDone_Y) != 0) { // Y-move has finished, handle the X-move remainer
 			movelist_handle_targetfix(xpermove, ypermove, targety);
-		if (ypermove != 0)
-			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y, cmls.GetStepLength(), main_onpart, fin_xmove, fin_onpart);
+		}
 	}
 
 	// Calculate next positions, as required
 	if ((cmls.doneflag & kMoveListDone_X) == 0) {
-		xps = cmls.from.X + (int)(fixtof(xpermove) * main_onpart) + (int)(fixtof(fin_xmove) * fin_onpart);
+		xps = cmls.from.X + (int)(fixtof(xpermove) * main_onpart) + (int)(fixtof(fin_move) * fin_onpart);
 	}
 	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
-		yps = cmls.from.Y + (int)(fixtof(ypermove) * main_onpart) + (int)(fixtof(fin_ymove) * fin_onpart);
+		yps = cmls.from.Y + (int)(fixtof(ypermove) * main_onpart) + (int)(fixtof(fin_move) * fin_onpart);
 	}
 
 	// Check if finished horizontal movement
-	if (((xpermove > 0) && (xps >= targetx)) ||
-		((xpermove < 0) && (xps <= targetx))) {
-		cmls.doneflag |= kMoveListDone_X;
-		xps = targetx;
-		// Comment about old engine behavior:
-		// if the Y is almost there too, finish it
-		// this is new in v2.40
-		// removed in 2.70
-		/*if (abs(yps - targety) <= 2)
-		  yps = targety;*/
-	} else if (xpermove == 0) { // NOTE: do not snap pos_x to target in this case (?)
-		cmls.doneflag |= kMoveListDone_X;
+	if ((cmls.doneflag & kMoveListDone_X) == 0) {
+		if (((xpermove > 0) && (xps >= targetx)) || ((xpermove < 0) && (xps <= targetx))) {
+			cmls.doneflag |= kMoveListDone_X;
+			xps = targetx; // snap to the target (in case run over)
+			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X,
+									 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
+			// Comment about old engine behavior:
+			// if the Y is almost there too, finish it
+			// this is new in v2.40
+			// removed in 2.70
+			/*if (abs(yps - targety) <= 2)
+			  yps = targety;*/
+		} else if (xpermove == 0) {
+			cmls.doneflag |= kMoveListDone_X;
+		}
 	}
 
 	// Check if finished vertical movement
-	if (((ypermove > 0) && (yps >= targety)) ||
-		((ypermove < 0) & (yps <= targety))) {
-		cmls.doneflag |= kMoveListDone_Y;
-		yps = targety;
-	} else if (ypermove == 0) { // NOTE: do not snap pos_y to target in this case (?)
-		cmls.doneflag |= kMoveListDone_Y;
+	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
+		if (((ypermove > 0) && (yps >= targety)) || ((ypermove < 0) & (yps <= targety))) {
+			cmls.doneflag |= kMoveListDone_Y;
+			yps = targety; // snap to the target (in case run over)
+			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y,
+									 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
+		} else if (ypermove == 0) {
+			cmls.doneflag |= kMoveListDone_Y;
+		}
 	}
 
 	// Handle end of move stage
@@ -170,6 +168,8 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		cmls.from = cmls.pos[cmls.onstage + 1];
 		cmls.onstage++;
 		cmls.onpart = -1.f;
+		cmls.fin_from_part = 0.f;
+		cmls.fin_move = 0;
 		cmls.doneflag = 0;
 		if (cmls.onstage < cmls.numstage) {
 			xps = cmls.from.X;


Commit: 9021cd0f04b897912914e1f0f98fe44408b3fc22
    https://github.com/scummvm/scummvm/commit/9021cd0f04b897912914e1f0f98fe44408b3fc22
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: another small fix in do_movelist_move()

>From upstream 5789ac12160f389fd880a494c154f740b3b9c715

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index b4fb2d8e21f..8a3bd5fd667 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -137,8 +137,9 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		if (((xpermove > 0) && (xps >= targetx)) || ((xpermove < 0) && (xps <= targetx))) {
 			cmls.doneflag |= kMoveListDone_X;
 			xps = targetx; // snap to the target (in case run over)
-			movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X,
-									 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
+			if (ypermove != 0)
+				movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X,
+										 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
 			// Comment about old engine behavior:
 			// if the Y is almost there too, finish it
 			// this is new in v2.40
@@ -155,8 +156,9 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		if (((ypermove > 0) && (yps >= targety)) || ((ypermove < 0) & (yps <= targety))) {
 			cmls.doneflag |= kMoveListDone_Y;
 			yps = targety; // snap to the target (in case run over)
-			movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y,
-									 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
+			if (xpermove != 0)
+				movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y,
+										 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
 		} else if (ypermove == 0) {
 			cmls.doneflag |= kMoveListDone_Y;
 		}


Commit: 4799ff06d75514937ee09cb23108354ac8dff860
    https://github.com/scummvm/scummvm/commit/4799ff06d75514937ee09cb23108354ac8dff860
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed silly typo in dataver condition in Character_SetSpeed()

>From upstream 9ffaf228534fd305b8253ff2197f5c819ca49899

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 62530f1612b..00a85eaaf4e 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -855,7 +855,7 @@ void Character_SetOption(CharacterInfo *chaa, int flag, int yesorno) {
 void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed) {
 	if ((xspeed == 0) || (yspeed == 0))
 		quit("!SetCharacterSpeedEx: invalid speed value");
-	if ((chaa->walking > 0) && (_G(loaded_game_file_version) < kGameVersion_350)) {
+	if ((chaa->walking > 0) && (_G(loaded_game_file_version) < kGameVersion_361)) {
 		debug_script_warn("Character_SetSpeed: cannot change speed while walking");
 		return;
 	}


Commit: 50b34a65c23ae4aadb520a30bdd322f2786a362f
    https://github.com/scummvm/scummvm/commit/50b34a65c23ae4aadb520a30bdd322f2786a362f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: bit more refactor of movelist update, picked "move done" check

>From upstream 36d18a543405b40f2259266707cc513e8e8f6646

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 8a3bd5fd667..55f214b2404 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -98,6 +98,45 @@ static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove,
 	fin_from_part = (float)xdistance / fixtof(xpermove);
 }
 
+// Handle remaining move fixup, but only if necessary
+static void movelist_handle_remainer(MoveList &m) {
+	assert(m.numstage > 0);
+	const fixed xpermove = m.xpermove[m.onstage];
+	const fixed ypermove = m.ypermove[m.onstage];
+	const Point target = m.pos[m.onstage + 1];
+	// Apply remainer to movelists where only ONE axis was completed, and another remains
+	if ((xpermove != 0) && (ypermove != 0)) {
+		if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_X)
+			movelist_handle_remainer(xpermove, ypermove, target.X - m.from.X,
+									 m.GetStepLength(), m.fin_move, m.fin_from_part);
+		else if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_Y)
+			movelist_handle_remainer(ypermove, xpermove, target.Y - m.from.Y,
+									 m.GetStepLength(), m.fin_move, m.fin_from_part);
+	}
+}
+
+// Test if move completed, returns if just completed
+static bool movelist_handle_donemove(const uint8_t testflag, const fixed xpermove, const int targetx, uint8_t &doneflag, int &xps) {
+	if ((doneflag & testflag) != 0)
+		return false; // already done before
+
+	if (((xpermove > 0) && (xps >= targetx)) || ((xpermove < 0) && (xps <= targetx))) {
+		doneflag |= testflag;
+		xps = targetx; // snap to the target (in case run over)
+		// Comment about old engine behavior:
+		// if the Y is almost there too, finish it
+		// this is new in v2.40
+		// removed in 2.70
+		/*if (abs(yps - targety) <= 2)
+			yps = targety;*/
+		return true;
+	} else if (xpermove == 0) {
+		doneflag |= testflag;
+		return true;
+	}
+	return false;
+}
+
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	// TODO: find out why movelist 0 is not being used
 	assert(mslot >= 1);
@@ -111,16 +150,15 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	const fixed fin_move = cmls.fin_move;
 	const float main_onpart = (cmls.fin_from_part > 0.f) ? cmls.fin_from_part : cmls.onpart;
 	const float fin_onpart = cmls.onpart - main_onpart;
-	int targetx = cmls.pos[cmls.onstage + 1].X;
-	int targety = cmls.pos[cmls.onstage + 1].Y;
+	Point target = cmls.pos[cmls.onstage + 1];
 	int xps = pos_x, yps = pos_y;
 
 	// Old-style optional move target fixup
 	if (_G(loaded_game_file_version) < kGameVersion_361) {
 		if ((ypermove != 0) && (cmls.doneflag & kMoveListDone_X) != 0) { // X-move has finished, handle the Y-move remainer
-			movelist_handle_targetfix(xpermove, ypermove, targety);
+			movelist_handle_targetfix(xpermove, ypermove, target.Y);
 		} else if ((xpermove != 0) && (cmls.doneflag & kMoveListDone_Y) != 0) { // Y-move has finished, handle the X-move remainer
-			movelist_handle_targetfix(xpermove, ypermove, targety);
+			movelist_handle_targetfix(xpermove, ypermove, target.Y);
 		}
 	}
 
@@ -132,36 +170,11 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 		yps = cmls.from.Y + (int)(fixtof(ypermove) * main_onpart) + (int)(fixtof(fin_move) * fin_onpart);
 	}
 
-	// Check if finished horizontal movement
-	if ((cmls.doneflag & kMoveListDone_X) == 0) {
-		if (((xpermove > 0) && (xps >= targetx)) || ((xpermove < 0) && (xps <= targetx))) {
-			cmls.doneflag |= kMoveListDone_X;
-			xps = targetx; // snap to the target (in case run over)
-			if (ypermove != 0)
-				movelist_handle_remainer(xpermove, ypermove, targetx - cmls.from.X,
-										 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
-			// Comment about old engine behavior:
-			// if the Y is almost there too, finish it
-			// this is new in v2.40
-			// removed in 2.70
-			/*if (abs(yps - targety) <= 2)
-			  yps = targety;*/
-		} else if (xpermove == 0) {
-			cmls.doneflag |= kMoveListDone_X;
-		}
-	}
-
-	// Check if finished vertical movement
-	if ((cmls.doneflag & kMoveListDone_Y) == 0) {
-		if (((ypermove > 0) && (yps >= targety)) || ((ypermove < 0) & (yps <= targety))) {
-			cmls.doneflag |= kMoveListDone_Y;
-			yps = targety; // snap to the target (in case run over)
-			if (xpermove != 0)
-				movelist_handle_remainer(ypermove, xpermove, targety - cmls.from.Y,
-										 cmls.GetStepLength(), cmls.fin_move, cmls.fin_from_part);
-		} else if (ypermove == 0) {
-			cmls.doneflag |= kMoveListDone_Y;
-		}
+	// Check if finished either horizontal or vertical movement;
+	// if any was finished just now, then also handle remainer fixup
+	if (movelist_handle_donemove(kMoveListDone_X, xpermove, target.X, cmls.doneflag, xps) ||
+		movelist_handle_donemove(kMoveListDone_Y, ypermove, target.Y, cmls.doneflag, yps)) {
+		movelist_handle_remainer(cmls);
 	}
 
 	// Handle end of move stage


Commit: e3e6b75af5be14ce1cb116bb9d5152137196ed15
    https://github.com/scummvm/scummvm/commit/e3e6b75af5be14ce1cb116bb9d5152137196ed15
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: reverted serializing movelist remainer, recalc on restore

Sorry for these changes, i decided that it's annoying to save the "fixup"
values used only in rare circumstances, that may be easily calculated
from basic MoveList members.
>From upstream 13e59c4855a4ddcf9bfe5f935d6de1191debaf1e

Changed paths:
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/engine/main/update.h


diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index d9bb2717cef..1675d26060f 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -89,8 +89,8 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	from.Y = in->ReadInt32();
 	onstage = in->ReadInt32();
 	BBOp::IntFloatSwap onpart_u(in->ReadInt32());
-	int finmove = in->ReadInt32();
-	BBOp::IntFloatSwap finpart_u(in->ReadInt32());
+	in->ReadInt32(); // UNUSED
+	in->ReadInt32(); // UNUSED
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
 
@@ -103,15 +103,10 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	in->ReadArrayOfInt32(ypermove, numstage);
 
 	// Some variables require conversion depending on a save version
-	if (cmp_ver < 2) {
+	if (cmp_ver < 2)
 		onpart = static_cast<float>(onpart_u.val.i32);
-		fin_move = 0;
-		fin_from_part = 0.f;
-	} else {
+	else
 		onpart = onpart_u.val.f;
-		fin_move = finmove;
-		fin_from_part = finpart_u.val.f;
-	}
 
 	return HSaveError::None();
 }
@@ -125,8 +120,8 @@ void MoveList::WriteToFile(Stream *out) const {
 	out->WriteInt32(from.Y);
 	out->WriteInt32(onstage);
 	out->WriteInt32(BBOp::IntFloatSwap(onpart).val.i32);
-	out->WriteInt32(fin_move);
-	out->WriteInt32(BBOp::IntFloatSwap(fin_from_part).val.i32);
+	out->WriteInt32(0); // UNUSED
+	out->WriteInt32(0); // UNUSED
 	out->WriteInt8(doneflag);
 	out->WriteInt8(direct);
 
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 91c5db68fd2..3ffd8adc6a1 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -59,12 +59,14 @@ struct MoveList {
 	// distance passed is calculated as xpermove[onstage] * onpart;
 	// made a fractional value to let recalculate movelist dynamically
 	float 	onpart = 0.f;
+	uint8_t doneflag = 0u;
+	uint8_t direct = 0; // MoveCharDirect was used or not
+
+	// Dynamic fixups, not serialized
 	// Final section move speed and steps, used when an object
 	// finishes one of the axes sooner than the other
 	fixed	fin_move = 0;
 	float	fin_from_part = 0.f;
-	uint8_t doneflag = 0u;
-	uint8_t direct = 0; // MoveCharDirect was used or not
 
 	// Gets a movelist's step length, in coordinate units
 	// (normally the coord unit is a game pixel)
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 522969aac6b..96a01928b3a 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -54,6 +54,7 @@
 #include "ags/engine/main/game_run.h"
 #include "ags/engine/main/engine.h"
 #include "ags/engine/main/main.h"
+#include "ags/engine/main/update.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/engine/platform/base/sys_main.h"
 #include "ags/plugins/ags_plugin.h"
@@ -623,6 +624,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	restore_characters();
 	restore_overlays();
+	restore_movelists();
 
 	GUI::MarkAllGUIForUpdate(true, true);
 
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 55f214b2404..dd6b13e3de9 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -104,12 +104,12 @@ static void movelist_handle_remainer(MoveList &m) {
 	const fixed xpermove = m.xpermove[m.onstage];
 	const fixed ypermove = m.ypermove[m.onstage];
 	const Point target = m.pos[m.onstage + 1];
-	// Apply remainer to movelists where only ONE axis was completed, and another remains
+	// Apply remainer to movelists where LONGER axis was completed, and SHORTER remains
 	if ((xpermove != 0) && (ypermove != 0)) {
-		if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_X)
+		if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_X && (ypermove < xpermove))
 			movelist_handle_remainer(xpermove, ypermove, target.X - m.from.X,
 									 m.GetStepLength(), m.fin_move, m.fin_from_part);
-		else if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_Y)
+		else if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_Y && (xpermove < ypermove))
 			movelist_handle_remainer(ypermove, xpermove, target.Y - m.from.Y,
 									 m.GetStepLength(), m.fin_move, m.fin_from_part);
 	}
@@ -207,6 +207,14 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 	return need_to_fix_sprite;
 }
 
+void restore_movelists() {
+	// Recalculate move remainer fixups, where necessary
+	for (auto &m : _GP(mls)) {
+		if (m.numstage > 0)
+			movelist_handle_remainer(m);
+	}
+}
+
 void update_script_timers() {
 	if (_GP(play).gscript_timer > 0) _GP(play).gscript_timer--;
 	for (int aa = 0; aa < MAX_TIMERS; aa++) {
diff --git a/engines/ags/engine/main/update.h b/engines/ags/engine/main/update.h
index e78d7cfe005..aa33bc1a296 100644
--- a/engines/ags/engine/main/update.h
+++ b/engines/ags/engine/main/update.h
@@ -30,6 +30,9 @@ namespace AGS3 {
 // TODO: find out what this return value means, and refactor.
 // TODO: do not reset mslot in this function, reset externally instead.
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y);
+// Recalculate derived (non-serialized) values in movelists
+void restore_movelists();
+// Update various things on the game frame (historical code mess...)
 void update_stuff();
 
 } // namespace AGS3


Commit: 4ef7d8af4801849545a6156b286497a3d4eb7aad
    https://github.com/scummvm/scummvm/commit/4ef7d8af4801849545a6156b286497a3d4eb7aad
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.9)

Partially from upstream 2c111fcdc838452d11169c4877357d4d1f35cdc6

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 e16c693070d..398cbe88f7e 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.8"
+#define ACI_VERSION_STR      "3.6.1.9"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.8
+#define ACI_VERSION_MSRC_DEF  3.6.1.9
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 9c7e4143876630a3f45e4788b3833c9537c2cf7b
    https://github.com/scummvm/scummvm/commit/9c7e4143876630a3f45e4788b3833c9537c2cf7b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: another small fix in do_movelist_move()

>From upstream 354917c602144f5f1c902bfb44f970af860e799b

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index dd6b13e3de9..ee1231a88be 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -129,12 +129,10 @@ static bool movelist_handle_donemove(const uint8_t testflag, const fixed xpermov
 		// removed in 2.70
 		/*if (abs(yps - targety) <= 2)
 			yps = targety;*/
-		return true;
 	} else if (xpermove == 0) {
 		doneflag |= testflag;
-		return true;
 	}
-	return false;
+	return (doneflag & testflag) != 0;
 }
 
 int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
@@ -172,10 +170,10 @@ int do_movelist_move(short &mslot, int &pos_x, int &pos_y) {
 
 	// Check if finished either horizontal or vertical movement;
 	// if any was finished just now, then also handle remainer fixup
-	if (movelist_handle_donemove(kMoveListDone_X, xpermove, target.X, cmls.doneflag, xps) ||
-		movelist_handle_donemove(kMoveListDone_Y, ypermove, target.Y, cmls.doneflag, yps)) {
+	bool done_now = movelist_handle_donemove(kMoveListDone_X, xpermove, target.X, cmls.doneflag, xps);
+	done_now |= movelist_handle_donemove(kMoveListDone_Y, ypermove, target.Y, cmls.doneflag, yps);
+	if (done_now)
 		movelist_handle_remainer(cmls);
-	}
 
 	// Handle end of move stage
 	if ((cmls.doneflag & kMoveListDone_XY) == kMoveListDone_XY) {


Commit: b52fe376d19e8aa28359a823365b7ae0b58edde7
    https://github.com/scummvm/scummvm/commit/b52fe376d19e8aa28359a823365b7ae0b58edde7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: inlined several getters in GUIMain and GUIObject

>From upstream 121a989b407542763553123f2a5a5fbd11b09e27

Changed paths:
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h


diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 9632d98895f..044e1d90b86 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -118,10 +118,6 @@ size_t GUI::SplitLinesForDrawing(const char *text, bool is_translated, SplitLine
 	return break_up_text_into_lines(text, is_translated, lines, width, font);
 }
 
-bool GUIObject::IsClickable() const {
-	return (Flags & kGUICtrl_Clickable) != 0;
-}
-
 void GUIObject::MarkChanged() {
 	_hasChanged = true;
 	_GP(guis)[ParentId].MarkControlsChanged();
@@ -131,10 +127,6 @@ void GUIObject::NotifyParentChanged() {
 	_GP(guis)[ParentId].MarkControlsChanged();
 }
 
-bool GUIObject::HasChanged() const {
-	return _hasChanged;
-}
-
 void GUIObject::ClearChanged() {
 	_hasChanged = false;
 }
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 1777668b2a3..c5a6626942e 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -149,18 +149,6 @@ const std::vector<int> &GUIMain::GetControlsDrawOrder() const {
 	return _ctrlDrawOrder;
 }
 
-bool GUIMain::IsClickable() const {
-	return (_flags & kGUIMain_Clickable) != 0;
-}
-
-bool GUIMain::IsConcealed() const {
-	return (_flags & kGUIMain_Concealed) != 0;
-}
-
-bool GUIMain::IsDisplayed() const {
-	return IsVisible() && !IsConcealed();
-}
-
 bool GUIMain::IsInteractableAt(int x, int y) const {
 	if (!IsDisplayed())
 		return false;
@@ -176,22 +164,6 @@ bool GUIMain::IsInteractableAt(int x, int y) const {
 	return false;
 }
 
-bool GUIMain::IsTextWindow() const {
-	return (_flags & kGUIMain_TextWindow) != 0;
-}
-
-bool GUIMain::IsVisible() const {
-	return (_flags & kGUIMain_Visible) != 0;
-}
-
-bool GUIMain::HasChanged() const {
-	return _hasChanged;
-}
-
-bool GUIMain::HasControlsChanged() const {
-	return _hasControlsChanged;
-}
-
 void GUIMain::MarkChanged() {
 	_hasChanged = true;
 }
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 14fffb3f77d..5fdb4f1fba0 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -77,29 +77,29 @@ public:
 	// Tells if the gui background supports alpha channel
 	bool	HasAlphaChannel() const;
 	// Tells if GUI will react on clicking on it
-	bool	IsClickable() const;
+	bool	IsClickable() const { return (_flags & kGUIMain_Clickable) != 0; }
 	// Tells if GUI's visibility is overridden and it won't be displayed on
 	// screen regardless of Visible property (until concealed mode is off).
-	bool	IsConcealed() const;
+	bool	IsConcealed() const { return (_flags & kGUIMain_Concealed) != 0; }
 	// Tells if gui is actually meant to be displayed on screen.
 	// Normally Visible property determines whether GUI is allowed to be seen,
 	// but there may be other settings that override it.
-	bool	IsDisplayed() const;
+	bool	IsDisplayed() const { return IsVisible() && !IsConcealed(); }
 	// Tells if given coordinates are within interactable area of gui
 	// NOTE: this currently tests for actual visibility and Clickable property
 	bool	IsInteractableAt(int x, int y) const;
 	// Tells if gui is a text window
-	bool	IsTextWindow() const;
+	bool	IsTextWindow() const { return (_flags & kGUIMain_TextWindow) != 0; }
 	// Tells if GUI is *allowed* to be displayed and interacted with.
 	// This does not necessarily mean that it is displayed right now, because
 	// GUI may be hidden for other reasons, including overriding behavior.
 	// For example GUI with kGUIPopupMouseY style will not be shown unless
 	// mouse cursor is at certain position on screen.
-	bool	IsVisible() const;
+	bool	IsVisible() const { return (_flags & kGUIMain_Visible) != 0; }
 
 	// Tells if GUI has graphically changed recently
-	bool	HasChanged() const;
-	bool	HasControlsChanged() const;
+	bool	HasChanged() const { return _hasChanged; }
+	bool	HasControlsChanged() const { return _hasControlsChanged; }
 	// Manually marks GUI as graphically changed
 	// NOTE: this only matters if GUI's own graphic changes (content, size etc),
 	// but not its state (visible) or texture drawing mode (transparency, etc).
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index 1ea02a5db70..9506b2cbaad 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -63,26 +63,10 @@ String GUIObject::GetEventArgs(int event) const {
 	return _scEventArgs[event];
 }
 
-bool GUIObject::IsDeleted() const {
-	return (Flags & kGUICtrl_Deleted) != 0;
-}
-
-bool GUIObject::IsEnabled() const {
-	return (Flags & kGUICtrl_Enabled) != 0;
-}
-
 bool GUIObject::IsOverControl(int x, int y, int leeway) const {
 	return x >= X && y >= Y && x < (X + _width + leeway) && y < (Y + _height + leeway);
 }
 
-bool GUIObject::IsTranslated() const {
-	return (Flags & kGUICtrl_Translated) != 0;
-}
-
-bool GUIObject::IsVisible() const {
-	return (Flags & kGUICtrl_Visible) != 0;
-}
-
 void GUIObject::SetClickable(bool on) {
 	if (on)
 		Flags |= kGUICtrl_Clickable;
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index c245ac1a107..b234ddcb7d4 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -51,15 +51,13 @@ public:
 	String          GetEventArgs(int event) const;
 	int             GetEventCount() const;
 	String          GetEventName(int event) const;
-	bool            IsDeleted() const;
-	// tells if control itself is enabled
-	bool            IsEnabled() const;
+	bool			IsClickable() const { return (Flags & kGUICtrl_Clickable) != 0; }
+	bool			IsDeleted() const { return (Flags & kGUICtrl_Deleted) != 0; }
+	bool			IsEnabled() const { return (Flags & kGUICtrl_Enabled) != 0; }
+	bool			IsTranslated() const { return (Flags & kGUICtrl_Translated) != 0; }
+	bool			IsVisible() const { return (Flags & kGUICtrl_Visible) != 0; }
 	// overridable routine to determine whether the mouse is over the control
 	virtual bool    IsOverControl(int x, int y, int leeway) const;
-	bool            IsTranslated() const;
-	bool            IsVisible() const;
-	// implemented separately in engine and editor
-	bool            IsClickable() const;
 	Size            GetSize() const { return Size(_width, _height); }
 	int             GetWidth() const { return _width; }
 	int             GetHeight() const { return _height; }
@@ -124,7 +122,7 @@ public:
 	// Notifies parent GUI that this control has changed its state (but not graphic)
 	void     NotifyParentChanged();
 
-	bool     HasChanged() const;
+	bool     HasChanged() const { return _hasChanged; };
 	void     ClearChanged();
 
 	int32_t  Id;         // GUI object's identifier


Commit: 14702fcc886514e4196dddb41aeab528cfc2a515
    https://github.com/scummvm/scummvm/commit/14702fcc886514e4196dddb41aeab528cfc2a515
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: use GUIButton::UpdateCurrentImage() consistently

>From upstream 7989edbb4866af495fb7eb5b0142365f82aefb4b

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_button.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index b8aea62cd71..adfeadd6036 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -46,9 +46,8 @@ using namespace AGS::Shared;
 
 // Update the actual button's image from the current animation frame
 void UpdateButtonState(const AnimatingGUIButton &abtn) {
-	_GP(guibuts)[abtn.buttonid].SetPushedImage(0);
-	_GP(guibuts)[abtn.buttonid].SetMouseOverImage(0);
-	_GP(guibuts)[abtn.buttonid].SetNormalImage(_GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic);
+    // Assign view frame as normal image and reset all the rest
+    _GP(guibuts)[abtn.buttonid].SetImages(_GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic, 0, 0);
 }
 
 void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat, int blocking, int direction, int sframe, int volume) {
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index b5af5f5f087..5fea3753d60 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -226,6 +226,13 @@ void GUIButton::SetPushedImage(int32_t image) {
 	UpdateCurrentImage();
 }
 
+void GUIButton::SetImages(int32_t normal, int32_t over, int32_t pushed) {
+	_image = normal;
+	_mouseOverImage = over;
+	_pushedImage = pushed;
+	UpdateCurrentImage();
+}
+
 void GUIButton::SetText(const String &text) {
 	if (_text == text)
 		return;
@@ -249,63 +256,51 @@ void GUIButton::SetText(const String &text) {
 	MarkChanged();
 }
 
-
 bool GUIButton::OnMouseDown() {
-	int new_image = (_pushedImage > 0) ? _pushedImage : _currentImage;
-	if (_currentImage != new_image || !IsImageButton())
-		MarkChanged();
-	_currentImage = new_image;
 	IsPushed = true;
+	if (!IsImageButton())
+		MarkChanged();
+	UpdateCurrentImage();
 	return false;
 }
 
 void GUIButton::OnMouseEnter() {
-	int new_image = (IsPushed && _pushedImage > 0) ? _pushedImage :
-		(_mouseOverImage > 0) ? _mouseOverImage : _image;
-	if ((_currentImage != new_image) || (IsPushed && !IsImageButton())) {
-		_currentImage = new_image;
-		MarkChanged();
-	}
 	IsMouseOver = true;
+	if (IsPushed && !IsImageButton())
+		MarkChanged();
+	UpdateCurrentImage();
 }
 
 void GUIButton::OnMouseLeave() {
-	if ((_currentImage != _image) || (IsPushed && !IsImageButton())) {
-		_currentImage = _image;
-		MarkChanged();
-	}
 	IsMouseOver = false;
+	if (IsPushed && !IsImageButton())
+		MarkChanged();
+	UpdateCurrentImage();
 }
 
 void GUIButton::OnMouseUp() {
-	int new_image = _image;
 	if (IsMouseOver) {
-		if (_mouseOverImage > 0)
-			new_image = _mouseOverImage;
 		if (IsGUIEnabled(this) && IsClickable())
 			IsActivated = true;
 	}
-
-	if ((_currentImage != new_image) || (IsPushed && !IsImageButton())) {
-		_currentImage = new_image;
-		MarkChanged();
-	}
 	IsPushed = false;
+	if (IsPushed && !IsImageButton())
+		MarkChanged();
+	UpdateCurrentImage();
 }
 
 void GUIButton::UpdateCurrentImage() {
-	int was_image = _currentImage;
+	int new_image = _currentImage;
 
 	if (IsPushed && (_pushedImage > 0)) {
-		_currentImage = _pushedImage;
+		new_image = _pushedImage;
 	} else if (IsMouseOver && (_mouseOverImage > 0)) {
-		_currentImage = _mouseOverImage;
+		new_image = _mouseOverImage;
 	} else {
-		_currentImage = _image;
+		new_image = _image;
 	}
 
-	if (was_image != _currentImage)
-		MarkChanged();
+	SetCurrentImage(new_image);
 }
 
 void GUIButton::WriteToFile(Stream *out) const {
diff --git a/engines/ags/shared/gui/gui_button.h b/engines/ags/shared/gui/gui_button.h
index 6706105a6a2..36c8bfda6c3 100644
--- a/engines/ags/shared/gui/gui_button.h
+++ b/engines/ags/shared/gui/gui_button.h
@@ -89,6 +89,7 @@ public:
 	void SetMouseOverImage(int32_t image);
 	void SetNormalImage(int32_t image);
 	void SetPushedImage(int32_t image);
+	void SetImages(int32_t normal, int32_t over, int32_t pushed);
 	void SetText(const String &text);
 
 	// Events


Commit: 528a0a333f98675a3fbfd28acbdc4bc4abb860f7
    https://github.com/scummvm/scummvm/commit/528a0a333f98675a3fbfd28acbdc4bc4abb860f7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: consistently reset "mouse-over" when GUI interact state changes

>From upstream 39147657c363d59ebfc9f9cb865ace0a38c95b90

Changed paths:
    engines/ags/engine/ac/global_gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h


diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp
index 6c142ed47de..7a7710b1fe4 100644
--- a/engines/ags/engine/ac/global_gui.cpp
+++ b/engines/ags/engine/ac/global_gui.cpp
@@ -74,8 +74,6 @@ void InterfaceOn(int ifn) {
 	debug_script_log("GUI %d turned on", ifn);
 	// modal interface
 	if (_GP(guis)[ifn].PopupStyle == kGUIPopupModal) PauseGame();
-	_GP(guis)[ifn].MarkControlsChanged();
-	_GP(guis)[ifn].ResetOverControl(); // clear the cached mouse position
 	_GP(guis)[ifn].Poll(_G(mousex), _G(mousey));
 }
 
@@ -86,12 +84,6 @@ void InterfaceOff(int ifn) {
 	}
 	debug_script_log("GUI %d turned off", ifn);
 	_GP(guis)[ifn].SetVisible(false);
-	if (_GP(guis)[ifn].MouseOverCtrl >= 0) {
-		// Make sure that the overpic is turned off when the GUI goes off
-		_GP(guis)[ifn].GetControl(_GP(guis)[ifn].MouseOverCtrl)->OnMouseLeave();
-	}
-	_GP(guis)[ifn].MarkControlsChanged();
-	_GP(guis)[ifn].ResetOverControl(); // clear the cached mouse position
 	// modal interface
 	if (_GP(guis)[ifn].PopupStyle == kGUIPopupModal) UnPauseGame();
 }
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index 94080199793..717a56d341d 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -65,9 +65,6 @@ void GUIControl_SetVisible(GUIObject *guio, int visible) {
 	const bool on = visible != 0;
 	if (on != guio->IsVisible()) {
 		guio->SetVisible(on);
-		// Make sure that the overpic is turned off when the GUI goes off
-		if (!on && (_GP(guis)[guio->ParentId].MouseOverCtrl == guio->Id))
-			_GP(guis)[guio->ParentId].ResetOverControl();
 	}
 }
 
@@ -78,13 +75,10 @@ int GUIControl_GetClickable(GUIObject *guio) {
 }
 
 void GUIControl_SetClickable(GUIObject *guio, int enabled) {
-	if (enabled)
-		guio->SetClickable(true);
-	else
-		guio->SetClickable(false);
-
-	// clickable property may change control behavior under mouse
-	_GP(guis)[guio->ParentId].MarkControlsChanged();
+	const bool on = enabled != 0;
+	if (on != guio->IsClickable()) {
+		guio->SetClickable(on);
+	}
 }
 
 int GUIControl_GetEnabled(GUIObject *guio) {
@@ -155,7 +149,7 @@ int GUIControl_GetX(GUIObject *guio) {
 
 void GUIControl_SetX(GUIObject *guio, int xx) {
 	guio->X = data_to_game_coord(xx);
-	_GP(guis)[guio->ParentId].MarkControlsChanged(); // update control under cursor
+	_GP(guis)[guio->ParentId].NotifyControlPosition(); // update control under cursor
 }
 
 int GUIControl_GetY(GUIObject *guio) {
@@ -164,7 +158,7 @@ int GUIControl_GetY(GUIObject *guio) {
 
 void GUIControl_SetY(GUIObject *guio, int yy) {
 	guio->Y = data_to_game_coord(yy);
-	_GP(guis)[guio->ParentId].MarkControlsChanged(); // update control under cursor
+	_GP(guis)[guio->ParentId].NotifyControlPosition(); // update control under cursor
 }
 
 int GUIControl_GetZOrder(GUIObject *guio) {
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 044e1d90b86..0c75920fda3 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -120,11 +120,21 @@ size_t GUI::SplitLinesForDrawing(const char *text, bool is_translated, SplitLine
 
 void GUIObject::MarkChanged() {
 	_hasChanged = true;
-	_GP(guis)[ParentId].MarkControlsChanged();
+	_GP(guis)[ParentId].MarkControlChanged();
 }
 
-void GUIObject::NotifyParentChanged() {
-	_GP(guis)[ParentId].MarkControlsChanged();
+void GUIObject::MarkParentChanged() {
+	_GP(guis)[ParentId].MarkControlChanged();
+}
+
+void GUIObject::MarkPositionChanged(bool self_changed) {
+	_hasChanged |= self_changed;
+	_GP(guis)[ParentId].NotifyControlPosition();
+}
+
+void GUIObject::MarkStateChanged(bool self_changed, bool parent_changed) {
+	_hasChanged |= self_changed;
+	_GP(guis)[ParentId].NotifyControlState(Id, self_changed | parent_changed);
 }
 
 void GUIObject::ClearChanged() {
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index c5a6626942e..9b6b48e6eb1 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -65,6 +65,7 @@ void GUIMain::InitDefaults() {
 	_flags = kGUIMain_DefFlags;
 	_hasChanged = true;
 	_hasControlsChanged = true;
+	_polling = false;
 
 	X = 0;
 	Y = 0;
@@ -168,11 +169,31 @@ void GUIMain::MarkChanged() {
 	_hasChanged = true;
 }
 
-void GUIMain::MarkControlsChanged() {
+void GUIMain::MarkControlChanged() {
 	_hasControlsChanged = true;
-	// force it to re-check for which control is under the mouse
+}
+
+void GUIMain::NotifyControlPosition() {
+	// Force it to re-check for which control is under the mouse
+	MouseWasAt.X = -1;
+	MouseWasAt.Y = -1;
+	_hasControlsChanged = true; // for software render, and in case of shape change
+}
+
+void GUIMain::NotifyControlState(int objid, bool mark_changed) {
 	MouseWasAt.X = -1;
 	MouseWasAt.Y = -1;
+	_hasControlsChanged |= mark_changed;
+	// Update cursor-over-control state, if necessary
+	const int overctrl = MouseOverCtrl;
+	if (!_polling &&
+		(objid >= 0) && (objid == overctrl) &&
+		(!_controls[overctrl]->IsClickable() ||
+		 !_controls[overctrl]->IsVisible() ||
+		 !_controls[overctrl]->IsEnabled())) {
+		MouseOverCtrl = -1;
+		_controls[overctrl]->OnMouseLeave();
+	}
 }
 
 void GUIMain::ClearChanged() {
@@ -304,6 +325,7 @@ void GUIMain::DrawBlob(Bitmap *ds, int x, int y, color_t draw_color) {
 }
 
 void GUIMain::Poll(int mx, int my) {
+	_polling = true;
 	mx -= X, my -= Y; // translate to GUI's local coordinates
 	if (mx != MouseWasAt.X || my != MouseWasAt.Y) {
 		int ctrl_index = FindControlAtLocal(mx, my, 0, true);
@@ -334,6 +356,7 @@ void GUIMain::Poll(int mx, int my) {
 
 	MouseWasAt.X = mx;
 	MouseWasAt.Y = my;
+	_polling = false;
 }
 
 HError GUIMain::RebuildArray() {
@@ -385,17 +408,17 @@ void GUIMain::ResortZOrder() {
 }
 
 void GUIMain::SetClickable(bool on) {
-	if (on)
-		_flags |= kGUIMain_Clickable;
-	else
-		_flags &= ~kGUIMain_Clickable;
+	if (on != ((_flags & kGUIMain_Clickable) != 0)) {
+		_flags = (_flags & ~kGUIMain_Clickable) | kGUIMain_Clickable * on;
+		ResetOverControl(); // clear the cursor-over-control
+	}
 }
 
 void GUIMain::SetConceal(bool on) {
-	if (on)
-		_flags |= kGUIMain_Concealed;
-	else
-		_flags &= ~kGUIMain_Concealed;
+	if (on != ((_flags & kGUIMain_Concealed) != 0)) {
+		_flags = (_flags & ~kGUIMain_Concealed) | kGUIMain_Concealed * on;
+		ResetOverControl(); // clear the cursor-over-control
+	}
 }
 
 bool GUIMain::SendControlToBack(int32_t index) {
@@ -427,15 +450,12 @@ bool GUIMain::SetControlZOrder(int32_t index, int zorder) {
 		}
 	}
 	ResortZOrder();
-	MarkControlsChanged();
+	NotifyControlPosition();
 	return true;
 }
 
 void GUIMain::SetTextWindow(bool on) {
-	if (on)
-		_flags |= kGUIMain_TextWindow;
-	else
-		_flags &= ~kGUIMain_TextWindow;
+	_flags = (_flags & ~kGUIMain_TextWindow) | kGUIMain_TextWindow * on;
 }
 
 void GUIMain::SetTransparencyAsPercentage(int percent) {
@@ -443,10 +463,10 @@ void GUIMain::SetTransparencyAsPercentage(int percent) {
 }
 
 void GUIMain::SetVisible(bool on) {
-	if (on)
-		_flags |= kGUIMain_Visible;
-	else
-		_flags &= ~kGUIMain_Visible;
+	if (on != ((_flags & kGUIMain_Visible) != 0)) {
+		_flags = (_flags & ~kGUIMain_Visible) | kGUIMain_Visible * on;
+		ResetOverControl(); // clear the cursor-over-control
+	}
 }
 
 void GUIMain::OnMouseButtonDown(int mx, int my) {
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 5fdb4f1fba0..adde3ad9dac 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -104,9 +104,14 @@ public:
 	// NOTE: this only matters if GUI's own graphic changes (content, size etc),
 	// but not its state (visible) or texture drawing mode (transparency, etc).
 	void	MarkChanged();
-	void	MarkControlsChanged();
+	// Marks GUI as having any of its controls changed its looks.
+	void	MarkControlChanged();
 	// Clears changed flag
 	void	ClearChanged();
+	// Notify GUI about any of its controls changing its location.
+	void	NotifyControlPosition();
+	// Notify GUI about one of its controls changing its interactive state.
+	void	NotifyControlState(int objid, bool mark_changed);
 	// Resets control-under-mouse detection.
 	void	ResetOverControl();
 
@@ -201,6 +206,7 @@ private:
 	int32_t _flags;         // style and behavior flags
 	bool    _hasChanged;    // flag tells whether GUI has graphically changed recently
 	bool    _hasControlsChanged;
+	bool	_polling; // inside the polling process
 
 	// Array of types and control indexes in global GUI object arrays;
 	// maps GUI child slots to actual controls and used for rebuilding Controls array
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index 9506b2cbaad..7ad01264dd7 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -68,19 +68,17 @@ bool GUIObject::IsOverControl(int x, int y, int leeway) const {
 }
 
 void GUIObject::SetClickable(bool on) {
-	if (on)
-		Flags |= kGUICtrl_Clickable;
-	else
-		Flags &= ~kGUICtrl_Clickable;
+	if (on != ((Flags & kGUICtrl_Clickable) != 0)) {
+		Flags = (Flags & ~kGUICtrl_Clickable) | kGUICtrl_Clickable * on;
+		MarkStateChanged(false, false); // update cursor-over-control only
+	}
 }
 
 void GUIObject::SetEnabled(bool on) {
-	if (on != ((Flags & kGUICtrl_Enabled) != 0))
-		MarkChanged();
-	if (on)
-		Flags |= kGUICtrl_Enabled;
-	else
-		Flags &= ~kGUICtrl_Enabled;
+	if (on != ((Flags & kGUICtrl_Enabled) != 0)) {
+		Flags = (Flags & ~kGUICtrl_Enabled) | kGUICtrl_Enabled * on;
+		MarkStateChanged(true, true); // may change looks, and update cursor-over-control
+	}
 }
 
 void GUIObject::SetSize(int width, int height) {
@@ -92,27 +90,23 @@ void GUIObject::SetSize(int width, int height) {
 }
 
 void GUIObject::SetTranslated(bool on) {
-	if (on != ((Flags & kGUICtrl_Translated) != 0))
+	if (on != ((Flags & kGUICtrl_Translated) != 0)) {
+		Flags = (Flags & ~kGUICtrl_Translated) | kGUICtrl_Translated * on;
 		MarkChanged();
-	if (on)
-		Flags |= kGUICtrl_Translated;
-	else
-		Flags &= ~kGUICtrl_Translated;
+	}
 }
 
 void GUIObject::SetVisible(bool on) {
-	if (on != ((Flags & kGUICtrl_Visible) != 0))
-		NotifyParentChanged(); // for software mode
-	if (on)
-		Flags |= kGUICtrl_Visible;
-	else
-		Flags &= ~kGUICtrl_Visible;
+	if (on != ((Flags & kGUICtrl_Visible) != 0)) {
+		Flags = (Flags & ~kGUICtrl_Visible) | kGUICtrl_Visible * on;
+		MarkStateChanged(false, true); // for software mode, and to update cursor-over-control
+	}
 }
 
 void GUIObject::SetTransparency(int trans) {
 	if (_transparency != trans) {
 		_transparency = trans;
-		NotifyParentChanged(); // for software mode
+		MarkParentChanged(); // for software mode
 	}
 }
 
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index b234ddcb7d4..04ee891cb79 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -105,7 +105,7 @@ public:
 	virtual void    OnMouseUp() {
 	}
 	// Control was resized
-	virtual void    OnResized() { MarkChanged(); }
+	virtual void    OnResized() { MarkPositionChanged(true); }
 
 	// Serialization
 	virtual void    ReadFromFile(Shared::Stream *in, GuiVersion gui_version);
@@ -116,14 +116,17 @@ public:
 	// TODO: these members are currently public; hide them later
 public:
 	// Manually marks GUIObject as graphically changed
-	// NOTE: this only matters if control's own graphic changes (content, size etc),
-	// but not its state (visible) or texture drawing mode (transparency, etc).
-	void     MarkChanged();
-	// Notifies parent GUI that this control has changed its state (but not graphic)
-	void     NotifyParentChanged();
-
-	bool     HasChanged() const { return _hasChanged; };
-	void     ClearChanged();
+	// NOTE: this only matters if control's own graphic changes, but not its
+	// logical (visible, clickable, etc) or visual (e.g. transparency) state.
+	void	MarkChanged();
+	// Notifies parent GUI that this control has changed its visual state
+	void	MarkParentChanged();
+	// Notifies parent GUI that this control has changed its location (pos, size)
+	void	MarkPositionChanged(bool self_changed);
+	// Notifies parent GUI that this control's interactive state has changed
+	void	MarkStateChanged(bool self_changed, bool parent_changed);
+	bool	HasChanged() const { return _hasChanged; };
+	void	ClearChanged();
 
 	int32_t  Id;         // GUI object's identifier
 	int32_t  ParentId;   // id of parent GUI


Commit: f327ebdf1bd0d44ec2da42c4c790a2f943a73e76
    https://github.com/scummvm/scummvm/commit/f327ebdf1bd0d44ec2da42c4c790a2f943a73e76
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: replaced literal save version values with enum constants

>From upstream 49b4d21b921dbdd6fc251a464a7b7c4c47035d66

Changed paths:
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_status.cpp
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/ac/screen_overlay.cpp
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/shared/ac/mouse_cursor.cpp
    engines/ags/shared/ac/mouse_cursor.h
    engines/ags/shared/game/room_version.h
    engines/ags/shared/gui/gui_defines.h


diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index 8588cbf762d..9c34d006c82 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -57,7 +57,7 @@ void CharacterExtras::ReadFromSavegame(Stream *in, int save_ver) {
 	process_idle_this_time = in->ReadInt8();
 	slow_move_counter = in->ReadInt8();
 	animwait = in->ReadInt16();
-	if (save_ver >= 2) // expanded at ver 2
+	if (save_ver >= kCharSvgVersion_36025)
 	{
 		anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		cur_anim_volume = static_cast<uint8_t>(in->ReadInt8());
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index 744a57b1125..904c55d7d79 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -35,6 +35,13 @@ class Stream;
 }
 using namespace AGS; // FIXME later
 
+enum CharacterSvgVersion {
+	kCharSvgVersion_Initial = 0,
+	// 1 was skipped somehow
+	kCharSvgVersion_36025 = 2, // animation volume
+	kCharSvgVersion_36109 = 3, // removed movelists, save externally
+};
+
 // The CharacterInfo struct size is fixed because it's exposed to script
 // and plugin API, therefore new stuff has to go here
 struct CharacterExtras {
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index 1675d26060f..f924b2b81a0 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -69,14 +69,14 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 }
 
 HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
-	if (cmp_ver < 1) {
+	if (cmp_ver < kMoveSvgVersion_350) {
 		ReadFromFile_Legacy(in);
 		return HSaveError::None();
 	}
 
 	*this = MoveList(); // reset struct
 	numstage = in->ReadInt32();
-	if ((numstage == 0) && cmp_ver >= 2) {
+	if ((numstage == 0) && cmp_ver >= kMoveSvgVersion_36109) {
 		return HSaveError::None();
 	}
 	// TODO: reimplement MoveList stages as vector to avoid these limits
@@ -103,7 +103,7 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	in->ReadArrayOfInt32(ypermove, numstage);
 
 	// Some variables require conversion depending on a save version
-	if (cmp_ver < 2)
+	if (cmp_ver < kMoveSvgVersion_36109)
 		onpart = static_cast<float>(onpart_u.val.i32);
 	else
 		onpart = onpart_u.val.f;
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 3ffd8adc6a1..40ef514819e 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -45,6 +45,12 @@ enum MoveListDoneFlags {
 	kMoveListDone_XY = kMoveListDone_X | kMoveListDone_Y
 };
 
+enum MoveListSvgVersion {
+	kMoveSvgVersion_Initial = 0,
+	kMoveSvgVersion_350,   // new pathfinder, arbitrary number of stages
+	kMoveSvgVersion_36109, // skip empty lists, progress as float
+};
+
 struct MoveList {
 	int 	numstage = 0;
 	// Waypoints, per stage
diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 7a2176d64b9..262f773e48b 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -25,6 +25,7 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/object.h"
+#include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/view_frame.h"
 #include "ags/engine/debugging/debug_log.h"
@@ -134,10 +135,10 @@ void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
 	flags = in->ReadInt8();
 	blocking_width = in->ReadInt16();
 	blocking_height = in->ReadInt16();
-	if (save_ver >= 1) {
+	if (save_ver >= kRoomStatSvgVersion_36016) {
 		name = StrUtil::ReadString(in);
 	}
-	if (save_ver >= 2) {
+	if (save_ver >= kRoomStatSvgVersion_36025) {
 		// anim vols order inverted compared to character, by mistake :(
 		cur_anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		anim_volume = static_cast<uint8_t>(in->ReadInt8());
diff --git a/engines/ags/engine/ac/room_status.cpp b/engines/ags/engine/ac/room_status.cpp
index d5cbed7bc88..54b5c3d6cdb 100644
--- a/engines/ags/engine/ac/room_status.cpp
+++ b/engines/ags/engine/ac/room_status.cpp
@@ -35,7 +35,7 @@ using namespace AGS::Engine;
 
 void HotspotState::ReadFromSavegame(Shared::Stream *in, int save_ver) {
 	Enabled = in->ReadInt8() != 0;
-	if (save_ver > 0) {
+	if (save_ver >= kRoomStatSvgVersion_36016) {
 		Name = StrUtil::ReadString(in);
 	}
 }
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index c2e95bd9bd3..f0a763ebf22 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -49,12 +49,13 @@ struct HotspotState {
 };
 
 // Savegame data format for RoomStatus
-// TODO: fill in other versions (lookup the code history)
 enum RoomStatSvgVersion {
 	kRoomStatSvgVersion_Initial = 0,
-	kRoomStatSvgVersion_36025 = 3,
-	kRoomStatSvgVersion_36041 = 4,
-	kRoomStatSvgVersion_36109 = 5,
+	kRoomStatSvgVersion_36016   = 1, // hotspot and object names
+	// 2 was practically unused
+	kRoomStatSvgVersion_36025   = 3, // object animation volume
+	kRoomStatSvgVersion_36041   = 4, // room state's contentFormat
+	kRoomStatSvgVersion_36109   = 5, // removed movelists, save externally
 	kRoomStatSvgVersion_Current = kRoomStatSvgVersion_36109
 };
 
diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index b0ee8542565..cb5217f0c78 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -76,7 +76,7 @@ void ScreenOverlay::ReadFromFile(Stream *in, bool &has_bitmap, int32_t cmp_ver)
 	timeout = in->ReadInt32();
 	bgSpeechForChar = in->ReadInt32();
 	associatedOverlayHandle = in->ReadInt32();
-	if (cmp_ver >= 3) {
+	if (cmp_ver >= kOverSvgVersion_36025) {
 		_flags = in->ReadInt16();
 	} else {
 		if (in->ReadBool()) // has alpha
@@ -85,18 +85,20 @@ void ScreenOverlay::ReadFromFile(Stream *in, bool &has_bitmap, int32_t cmp_ver)
 			_flags |= kOver_PositionAtRoomXY;
 	}
 
-	if (cmp_ver >= 1) {
+	if (cmp_ver >= kOverSvgVersion_35028) {
 		offsetX = in->ReadInt32();
 		offsetY = in->ReadInt32();
 	}
-	if (cmp_ver >= 2) {
+	if (cmp_ver >= kOverSvgVersion_36008) {
 		zorder = in->ReadInt32();
 		transparency = in->ReadInt32();
 		scaleWidth = in->ReadInt32();
 		scaleHeight = in->ReadInt32();
 	}
 
-	if (_flags & kOver_SpriteReference) {
+	// New saves always save overlay images as a part of the dynamicsprite set;
+	// old saves could contain images saved along with overlays
+	if ((cmp_ver >= kOverSvgVersion_36108) || (_flags & kOver_SpriteReference)) {
 		_sprnum = pic;
 		has_bitmap = false;
 	} else {
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index d6a1aeb7157..ee07d8eb4c8 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -55,6 +55,14 @@ enum OverlayFlags {
 	kOver_SpriteReference = 0x0008   // reference persistent sprite
 };
 
+enum OverlaySvgVersion {
+	kOverSvgVersion_Initial = 0,
+	kOverSvgVersion_35028 	= 1, // offset x,y
+	kOverSvgVersion_36008 	= 2, // z, transparency
+	kOverSvgVersion_36025 	= 3, // merged options into flags
+	kOverSvgVersion_36108 	= 4, // don't save owned sprites (use dynamic sprites)
+};
+
 struct ScreenOverlay {
 	// Texture
 	Engine::IDriverDependantBitmap *ddb = nullptr;
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 7f46641ebe4..9a14769f82e 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -377,13 +377,20 @@ 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, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	// Game content assertion
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).audioClipTypes.size(), "Audio Clip Types"))
 		return err;
 	int total_channels, max_game_channels;
-	if (cmp_ver >= 2) {
+	if (cmp_ver >= kAudioSvgVersion_36009) {
 		total_channels = in->ReadInt8();
 		max_game_channels = in->ReadInt8();
 		in->ReadInt16(); // reserved 2 bytes
@@ -419,7 +426,7 @@ HSaveError ReadAudio(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/
 			chan_info.Pan = in->ReadInt32();
 			chan_info.Speed = 1000;
 			chan_info.Speed = in->ReadInt32();
-			if (cmp_ver >= 1) {
+			if (cmp_ver >= kAudioSvgVersion_35026) {
 				chan_info.XSource = in->ReadInt32();
 				chan_info.YSource = in->ReadInt32();
 				chan_info.MaxDist = in->ReadInt32();
@@ -1057,6 +1064,8 @@ struct ComponentHandler {
 
 // Array of supported components
 struct ComponentHandlers {
+	// NOTE: the new format values should now be defined as AGS version
+	// at which a change was introduced, represented as NN,NN,NN,NN.
 	const ComponentHandler _items[18] = {
 		{
 			"Game State",
@@ -1067,15 +1076,15 @@ struct ComponentHandlers {
 		},
 		{
 			"Audio",
-			2,
-			0,
+			kAudioSvgVersion_36009,
+			kAudioSvgVersion_Initial,
 			WriteAudio,
 			ReadAudio
 		},
 		{
 			"Characters",
-			3,
-			0,
+			kCharSvgVersion_36109,
+			kCharSvgVersion_Initial,
 			WriteCharacters,
 			ReadCharacters
 		},
@@ -1102,8 +1111,8 @@ struct ComponentHandlers {
 		},
 		{
 			"Mouse Cursors",
-			1,
-			0,
+			kCursorSvgVersion_36016,
+			kCursorSvgVersion_Initial,
 			WriteMouseCursors,
 			ReadMouseCursors
 		},
@@ -1123,8 +1132,8 @@ struct ComponentHandlers {
 		},
 		{
 			"Overlays",
-			3,
-			0,
+			kOverSvgVersion_36108,
+			kOverSvgVersion_Initial,
 			WriteOverlays,
 			ReadOverlays
 		},
@@ -1158,8 +1167,8 @@ struct ComponentHandlers {
 		},
 		{
 			"Move Lists",
-			2,
-			0,
+			kMoveSvgVersion_36109,
+			kMoveSvgVersion_Initial,
 			WriteMoveLists,
 			ReadMoveLists
 		},
diff --git a/engines/ags/shared/ac/mouse_cursor.cpp b/engines/ags/shared/ac/mouse_cursor.cpp
index 255b1111ed0..357a60adf35 100644
--- a/engines/ags/shared/ac/mouse_cursor.cpp
+++ b/engines/ags/shared/ac/mouse_cursor.cpp
@@ -60,7 +60,7 @@ void MouseCursor::ReadFromSavegame(Stream *in, int cmp_ver) {
 	hoty = static_cast<int16_t>(in->ReadInt32());
 	view = static_cast<int16_t>(in->ReadInt32());
 	flags = static_cast<int8_t>(in->ReadInt32());
-	if (cmp_ver > 0)
+	if (cmp_ver >= kCursorSvgVersion_36016)
 		animdelay = in->ReadInt32();
 }
 
diff --git a/engines/ags/shared/ac/mouse_cursor.h b/engines/ags/shared/ac/mouse_cursor.h
index 428e0775579..237e9bdf57e 100644
--- a/engines/ags/shared/ac/mouse_cursor.h
+++ b/engines/ags/shared/ac/mouse_cursor.h
@@ -39,6 +39,11 @@ using namespace AGS; // FIXME later
 #define MCF_STANDARD 4
 #define MCF_HOTSPOT  8  // only animate when over hotspot
 
+enum CursorSvgVersion {
+	kCursorSvgVersion_Initial = 0,
+	kCursorSvgVersion_36016 = 1, // animation delay
+};
+
 #define MAX_CURSOR_NAME_LENGTH 10
 
 // IMPORTANT: exposed to plugin API as AGSCursor!
diff --git a/engines/ags/shared/game/room_version.h b/engines/ags/shared/game/room_version.h
index 62d093055f7..05674a13faf 100644
--- a/engines/ags/shared/game/room_version.h
+++ b/engines/ags/shared/game/room_version.h
@@ -57,6 +57,7 @@ namespace AGS3 {
 31:  v3.4.1.5 - removed room object and hotspot name length limits
 32:  v3.5.0 - 64-bit file offsets
 33:  v3.5.0.8 - deprecated room resolution, added mask resolution
+Since then format value is defined as AGS version represented as NN,NN,NN,NN.
 */
 enum RoomFileVersion {
 	kRoomVersion_Undefined = 0,
diff --git a/engines/ags/shared/gui/gui_defines.h b/engines/ags/shared/gui/gui_defines.h
index 508cf0fcd07..0fc2f18caf3 100644
--- a/engines/ags/shared/gui/gui_defines.h
+++ b/engines/ags/shared/gui/gui_defines.h
@@ -52,7 +52,7 @@ namespace AGS3 {
 // 3.4.0      (118): Removed GUI limits
 // 3.5.0      (119): Game data contains GUI properties that previously
 //                   could be set only at runtime.
-//
+// Since then format value is defined as AGS version represented as NN,NN,NN,NN
 //=============================================================================
 
 enum GuiVersion {


Commit: c66a1b5c988e6b66ce4ce3886b54f63db124d6d2
    https://github.com/scummvm/scummvm/commit/c66a1b5c988e6b66ce4ce3886b54f63db124d6d2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed MoveList not getting cleared up on reuse, kept remainer

>From upstream 03de6d3308d260a784d8b33cb1d9d987bb807299

Changed paths:
    engines/ags/engine/ac/route_finder_impl.cpp
    engines/ags/engine/ac/route_finder_impl_legacy.cpp


diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp
index e235d7cf40e..8e30c265546 100644
--- a/engines/ags/engine/ac/route_finder_impl.cpp
+++ b/engines/ags/engine/ac/route_finder_impl.cpp
@@ -240,7 +240,7 @@ void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, i
 	}
 }
 
-int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
+int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int move_id, int nocross, int ignore_walls) {
 
 	_G(wallscreen) = onscreen;
 
@@ -270,9 +270,9 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 	AGS::Shared::Debug::Printf("Route from %d,%d to %d,%d - %d stages", srcx, srcy, xx, yy, _G(num_navpoints));
 #endif
 
-	int mlist = movlst;
-	_GP(mls)[mlist].numstage = _G(num_navpoints);
-	memcpy(&_GP(mls)[mlist].pos[0], &_G(navpoints)[0], sizeof(Point) * _G(num_navpoints));
+	MoveList mlist;
+	mlist.numstage = _G(num_navpoints);
+	memcpy(&mlist.pos[0], &_G(navpoints)[0], sizeof(Point) * _G(num_navpoints));
 #ifdef DEBUG_PATHFINDER
 	AGS::Shared::Debug::Printf("stages: %d\n", _G(num_navpoints));
 #endif
@@ -280,14 +280,12 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
 	const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
 	const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
 	for (int i = 0; i < _G(num_navpoints) - 1; i++) {
-		calculate_move_stage(&_GP(mls)[mlist], i, fix_speed_x, fix_speed_y);
+		calculate_move_stage(&mlist, i, fix_speed_x, fix_speed_y);
 	}
 
-	_GP(mls)[mlist].from = { srcx, srcy };
-	_GP(mls)[mlist].onstage = 0;
-	_GP(mls)[mlist].onpart = 0.f;
-	_GP(mls)[mlist].doneflag = 0;
-	return mlist;
+	mlist.from = {srcx, srcy};
+	_GP(mls)[move_id] = mlist;
+	return move_id;
 }
 
 bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 985dd277e22..6d35359d952 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -733,9 +733,9 @@ void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed mov
 #endif
 }
 
-int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
+int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int move_id, int nocross, int ignore_walls) {
 	assert(onscreen != nullptr);
-	assert((int)_GP(mls).size() > movlst);
+	assert((int)_GP(mls).size() > move_id);
 	assert(pathbackx != nullptr);
 	assert(pathbacky != nullptr);
 
@@ -851,9 +851,9 @@ stage_again:
 #ifdef DEBUG_PATHFINDER
 		AGS::Shared::Debug::Printf("Route from %d,%d to %d,%d - %d stage, %d stages", orisrcx, orisrcy, xx, yy, pathbackstage, numstages);
 #endif
-		int mlist = movlst;
-		_GP(mls)[mlist].numstage = numstages;
-		memcpy(&_GP(mls)[mlist].pos[0], &reallyneed[0], sizeof(Point) * numstages);
+		MoveList mlist;
+		mlist.numstage = numstages;
+		memcpy(&mlist.pos[0], &reallyneed[0], sizeof(Point) * numstages);
 #ifdef DEBUG_PATHFINDER
 		AGS::Shared::Debug::Printf("stages: %d\n", numstages);
 #endif
@@ -861,18 +861,16 @@ stage_again:
 		const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
 		const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
 		for (aaa = 0; aaa < numstages - 1; aaa++) {
-			calculate_move_stage(&_GP(mls)[mlist], aaa, fix_speed_x, fix_speed_y);
+			calculate_move_stage(&mlist, aaa, fix_speed_x, fix_speed_y);
 		}
 
-		_GP(mls)[mlist].from = {orisrcx, orisrcy};
-		_GP(mls)[mlist].onstage = 0;
-		_GP(mls)[mlist].onpart = 0.f;
-		_GP(mls)[mlist].doneflag = 0;
+		mlist.from = {orisrcx, orisrcy};
+		_GP(mls)[move_id] = mlist;
 #ifdef DEBUG_PATHFINDER
 		// getch();
 #endif
 
-		return mlist;
+		return move_id;
 	} else {
 		return 0;
 	}


Commit: 1ee7c47af2a709d43449af45b17e74fd8b5375b1
    https://github.com/scummvm/scummvm/commit/1ee7c47af2a709d43449af45b17e74fd8b5375b1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed a condition in movelist_handle_remainer()

Should compare xpermove and ypermove using abs values,
as they may have different signs.
>From upstream 234db6dc918490c1e2e4a82da8c6bd30987c227e

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index ee1231a88be..b64075def20 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -93,9 +93,10 @@ static void movelist_handle_targetfix(const fixed &xpermove, const fixed &ypermo
 static void movelist_handle_remainer(const fixed xpermove, const fixed ypermove,
 									 const int xdistance, const float step_length, fixed &fin_ymove, float &fin_from_part) {
 	// Walk along the remaining axis with the full walking speed
-	assert(xpermove != 0 && ypermove != 0);
+	assert(xpermove != 0 && ypermove != 0 && step_length >= 0.f);
 	fin_ymove = ypermove > 0 ? ftofix(step_length) : -ftofix(step_length);
 	fin_from_part = (float)xdistance / fixtof(xpermove);
+	assert(fin_from_part >= 0);
 }
 
 // Handle remaining move fixup, but only if necessary
@@ -106,10 +107,10 @@ static void movelist_handle_remainer(MoveList &m) {
 	const Point target = m.pos[m.onstage + 1];
 	// Apply remainer to movelists where LONGER axis was completed, and SHORTER remains
 	if ((xpermove != 0) && (ypermove != 0)) {
-		if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_X && (ypermove < xpermove))
+		if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_X && (abs(ypermove) < abs(xpermove)))
 			movelist_handle_remainer(xpermove, ypermove, target.X - m.from.X,
 									 m.GetStepLength(), m.fin_move, m.fin_from_part);
-		else if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_Y && (xpermove < ypermove))
+		else if ((m.doneflag & kMoveListDone_XY) == kMoveListDone_Y && (abs(xpermove) < abs(ypermove)))
 			movelist_handle_remainer(ypermove, xpermove, target.Y - m.from.Y,
 									 m.GetStepLength(), m.fin_move, m.fin_from_part);
 	}


Commit: dfaa12c25a592c62e9a7f54d5312e01c5bb06b40
    https://github.com/scummvm/scummvm/commit/dfaa12c25a592c62e9a7f54d5312e01c5bb06b40
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: disable auto SetRestartPoint at startup

>From upstream b6008e1265648a9ab491cd630057812a3f388717

Changed paths:
    engines/ags/engine/main/game_start.cpp
    engines/ags/shared/ac/game_version.h


diff --git a/engines/ags/engine/main/game_start.cpp b/engines/ags/engine/main/game_start.cpp
index 4f837f877df..b3ac2b4b92f 100644
--- a/engines/ags/engine/main/game_start.cpp
+++ b/engines/ags/engine/main/game_start.cpp
@@ -93,7 +93,10 @@ void start_game() {
 
 	_G(our_eip) = -43;
 
-	SetRestartPoint();
+	// Only auto-set first restart point in < 3.6.1 games,
+	// since 3.6.1+ users are suggested to set one manually in script.
+	if (_G(loaded_game_file_version) < kGameVersion_361_10)
+		SetRestartPoint();
 
 	_G(our_eip) = -3;
 
diff --git a/engines/ags/shared/ac/game_version.h b/engines/ags/shared/ac/game_version.h
index b2178069a66..dfc965901d3 100644
--- a/engines/ags/shared/ac/game_version.h
+++ b/engines/ags/shared/ac/game_version.h
@@ -124,6 +124,8 @@ Idle animation speed, modifiable hotspot names, fixed video frame
 Some adjustments to gui text alignment.
 3.6.1:
 In RTL mode all text is reversed, not only wrappable (labels etc).
+3.6.1.10:
+Disabled automatic SetRestartPoint.
 */
 
 enum GameDataVersion {
@@ -161,7 +163,8 @@ enum GameDataVersion {
 	kGameVersion_360_16 = 3060016,
 	kGameVersion_360_21 = 3060021,
 	kGameVersion_361 = 3060100,
-	kGameVersion_Current = kGameVersion_361
+	kGameVersion_361_10 = 3060110,
+	kGameVersion_Current = kGameVersion_361_10
 };
 
 } // namespace AGS3


Commit: 813801779c967169a8d1e5f9ac28f054a47831ae
    https://github.com/scummvm/scummvm/commit/813801779c967169a8d1e5f9ac28f054a47831ae
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Added new functions for API scripts registration

These can be used to gradually move to the new style introduced
in upstream 9221ea54c1a81b9f37829d7fafc90ac2806d4eb1

Changed paths:
    engines/ags/engine/script/script_api.h
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h


diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index a1374eb2d33..a366efe19d2 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -58,6 +58,11 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 	return ScriptSprintf(buffer, buf_length, format, nullptr, 0, &arg_ptr);
 }
 
+// Helper macro for registering an API function for both script and plugin,
+// for the common case where they have similar names: the script's "translator"
+// function's name is derived from the real one by adding a "Sc_" prefix.
+#define API_FN_PAIR(FN_NAME) Sc_##FN_NAME, (void *)FN_NAME
+
 // Helper macros for script functions;
 // asserting for internal mistakes; suppressing "unused param" warnings
 #define ASSERT_SELF(METHOD) \
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index ce26c1ec650..686a29779b0 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -40,6 +40,25 @@ bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer
 	return _GP(simp_for_plugin).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
 
+bool ccAddExternalStaticFunction361(const String &name, ScriptAPIFunction *scfn, void *dirfn) {
+	return _GP(simp).add(name, RuntimeScriptValue().SetStaticFunction(scfn), nullptr) != UINT32_MAX &&
+		   (!dirfn ||
+			_GP(simp_for_plugin).add(name, RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)dirfn, name), nullptr) != UINT32_MAX);
+}
+
+bool ccAddExternalObjectFunction361(const String &name, ScriptAPIObjectFunction *scfn, void *dirfn) {
+	return _GP(simp).add(name, RuntimeScriptValue().SetObjectFunction(scfn), nullptr) != UINT32_MAX &&
+		   (!dirfn ||
+			_GP(simp_for_plugin).add(name, RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)dirfn, name), nullptr) != UINT32_MAX);
+}
+
+bool ccAddExternalFunction361(const ScFnRegister &scfnreg) {
+	String name = String::Wrapper(scfnreg.Name);
+	return _GP(simp).add(name, scfnreg.Fn, nullptr) != UINT32_MAX &&
+		   (scfnreg.PlFn.IsNull() ||
+			_GP(simp_for_plugin).add(name, scfnreg.PlFn, nullptr) != UINT32_MAX);
+}
+
 bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *instance) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index e4ab17ed6a8..cde076e1284 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -34,6 +34,19 @@ struct StaticArray;
 
 using AGS::Shared::String;
 
+// Helper struct for declaring a script API function registration
+struct ScFnRegister {
+	const char *Name = nullptr;
+	RuntimeScriptValue Fn;   // for script VM calls
+	RuntimeScriptValue PlFn; // for plugins, direct unsafe call style
+
+	ScFnRegister() = default;
+	ScFnRegister(const char *name, ScriptAPIFunction *fn, void *plfn = nullptr)
+		: Name(name), Fn(RuntimeScriptValue().SetStaticFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
+	ScFnRegister(const char *name, ScriptAPIObjectFunction *fn, void *plfn = nullptr)
+		: Name(name), Fn(RuntimeScriptValue().SetObjectFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
+};
+
 // Following functions register engine API symbols for script and plugins.
 // Calls from script is handled by specific "translator" functions, which
 // unpack script interpreter's values into the real arguments and call the
@@ -55,6 +68,18 @@ void ccRemoveExternalSymbol(const String &name);
 // Remove all external symbols, allowing you to start from scratch
 void ccRemoveAllSymbols();
 
+// FIXME: These functions should replace the older ones; for now they are duplicated to ease
+// the transition
+bool ccAddExternalStaticFunction361(const String &name, ScriptAPIFunction *scfn, void *dirfn = nullptr);
+bool ccAddExternalObjectFunction361(const String &name, ScriptAPIObjectFunction *scfn, void *dirfn = nullptr);
+bool ccAddExternalFunction361(const ScFnRegister &scfnreg);
+// Registers an array of static functions
+template<size_t N>
+inline void ccAddExternalFunctions361(const ScFnRegister (&arr)[N]) {
+	for (const ScFnRegister *it = arr; it != (arr + N); ++it)
+		ccAddExternalFunction361(*it);
+}
+
 // Get the address of an exported variable in the script
 void *ccGetSymbolAddress(const String &name);
 // Get a registered symbol's direct pointer; this is used solely for plugins


Commit: cec77078f6ea9ce61584537e7aa922f82546c9ce
    https://github.com/scummvm/scummvm/commit/cec77078f6ea9ce61584537e7aa922f82546c9ce
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Convert several files to new API registering

Changed paths:
    engines/ags/engine/ac/audio_channel.cpp
    engines/ags/engine/ac/audio_clip.cpp
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/date_time.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/dialog_options_rendering.cpp
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/ac/hotspot.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/inventory_item.cpp
    engines/ags/engine/ac/label.cpp
    engines/ags/engine/ac/listbox.cpp
    engines/ags/engine/ac/math.cpp
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/parser.cpp
    engines/ags/engine/ac/region.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/screen.cpp
    engines/ags/engine/ac/script_containers.cpp
    engines/ags/engine/ac/slider.cpp
    engines/ags/engine/ac/speech.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/system.cpp
    engines/ags/engine/ac/textbox.cpp
    engines/ags/engine/ac/view_frame.cpp


diff --git a/engines/ags/engine/ac/audio_channel.cpp b/engines/ags/engine/ac/audio_channel.cpp
index 6b8a8c4eb4c..e38cba269ac 100644
--- a/engines/ags/engine/ac/audio_channel.cpp
+++ b/engines/ags/engine/ac/audio_channel.cpp
@@ -303,27 +303,31 @@ RuntimeScriptValue Sc_AudioChannel_GetIsPaused(void *self, const RuntimeScriptVa
 }
 
 void RegisterAudioChannelAPI() {
-	ccAddExternalObjectFunction("AudioChannel::Pause^0", Sc_AudioChannel_Pause);
-	ccAddExternalObjectFunction("AudioChannel::Resume^0", Sc_AudioChannel_Resume);
-	ccAddExternalObjectFunction("AudioChannel::Seek^1", Sc_AudioChannel_Seek);
-	ccAddExternalObjectFunction("AudioChannel::SeekMs^1", Sc_AudioChannel_SeekMs);
-	ccAddExternalObjectFunction("AudioChannel::SetRoomLocation^2", Sc_AudioChannel_SetRoomLocation);
-	ccAddExternalObjectFunction("AudioChannel::Stop^0", Sc_AudioChannel_Stop);
-	ccAddExternalObjectFunction("AudioChannel::get_ID", Sc_AudioChannel_GetID);
-	ccAddExternalObjectFunction("AudioChannel::get_IsPaused", Sc_AudioChannel_GetIsPaused);
-	ccAddExternalObjectFunction("AudioChannel::get_IsPlaying", Sc_AudioChannel_GetIsPlaying);
-	ccAddExternalObjectFunction("AudioChannel::get_LengthMs", Sc_AudioChannel_GetLengthMs);
-	ccAddExternalObjectFunction("AudioChannel::get_Panning", Sc_AudioChannel_GetPanning);
-	ccAddExternalObjectFunction("AudioChannel::set_Panning", Sc_AudioChannel_SetPanning);
-	ccAddExternalObjectFunction("AudioChannel::get_PlayingClip", Sc_AudioChannel_GetPlayingClip);
-	ccAddExternalObjectFunction("AudioChannel::get_Position", Sc_AudioChannel_GetPosition);
-	ccAddExternalObjectFunction("AudioChannel::get_PositionMs", Sc_AudioChannel_GetPositionMs);
-	ccAddExternalObjectFunction("AudioChannel::get_Volume", Sc_AudioChannel_GetVolume);
-	ccAddExternalObjectFunction("AudioChannel::set_Volume", Sc_AudioChannel_SetVolume);
-	ccAddExternalObjectFunction("AudioChannel::get_Speed", Sc_AudioChannel_GetSpeed);
-	ccAddExternalObjectFunction("AudioChannel::set_Speed", Sc_AudioChannel_SetSpeed);
-	// For compatibility with  Ahmet Kamil's (aka Gord10) custom engine
-	ccAddExternalObjectFunction("AudioChannel::SetSpeed^1", Sc_AudioChannel_SetSpeed);
+	ScFnRegister audiochan_api[] = {
+		{"AudioChannel::Pause^0", API_FN_PAIR(AudioChannel_Pause)},
+		{"AudioChannel::Resume^0", API_FN_PAIR(AudioChannel_Resume)},
+		{"AudioChannel::Seek^1", API_FN_PAIR(AudioChannel_Seek)},
+		{"AudioChannel::SeekMs^1", API_FN_PAIR(AudioChannel_SeekMs)},
+		{"AudioChannel::SetRoomLocation^2", API_FN_PAIR(AudioChannel_SetRoomLocation)},
+		{"AudioChannel::Stop^0", API_FN_PAIR(AudioChannel_Stop)},
+		{"AudioChannel::get_ID", API_FN_PAIR(AudioChannel_GetID)},
+		{"AudioChannel::get_IsPaused", API_FN_PAIR(AudioChannel_GetIsPaused)},
+		{"AudioChannel::get_IsPlaying", API_FN_PAIR(AudioChannel_GetIsPlaying)},
+		{"AudioChannel::get_LengthMs", API_FN_PAIR(AudioChannel_GetLengthMs)},
+		{"AudioChannel::get_Panning", API_FN_PAIR(AudioChannel_GetPanning)},
+		{"AudioChannel::set_Panning", API_FN_PAIR(AudioChannel_SetPanning)},
+		{"AudioChannel::get_PlayingClip", API_FN_PAIR(AudioChannel_GetPlayingClip)},
+		{"AudioChannel::get_Position", API_FN_PAIR(AudioChannel_GetPosition)},
+		{"AudioChannel::get_PositionMs", API_FN_PAIR(AudioChannel_GetPositionMs)},
+		{"AudioChannel::get_Volume", API_FN_PAIR(AudioChannel_GetVolume)},
+		{"AudioChannel::set_Volume", API_FN_PAIR(AudioChannel_SetVolume)},
+		{"AudioChannel::get_Speed", API_FN_PAIR(AudioChannel_GetSpeed)},
+		{"AudioChannel::set_Speed", API_FN_PAIR(AudioChannel_SetSpeed)},
+		// For compatibility with  Ahmet Kamil's (aka Gord10) custom engine
+		{"AudioChannel::SetSpeed^1", API_FN_PAIR(AudioChannel_SetSpeed)},
+	};
+
+	ccAddExternalFunctions361(audiochan_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/audio_clip.cpp b/engines/ags/engine/ac/audio_clip.cpp
index 0fe43fcd9f8..0278c3cefd6 100644
--- a/engines/ags/engine/ac/audio_clip.cpp
+++ b/engines/ags/engine/ac/audio_clip.cpp
@@ -134,15 +134,19 @@ RuntimeScriptValue Sc_AudioClip_PlayOnChannel(void *self, const RuntimeScriptVal
 }
 
 void RegisterAudioClipAPI() {
-	ccAddExternalObjectFunction("AudioClip::Play^2", Sc_AudioClip_Play);
-	ccAddExternalObjectFunction("AudioClip::PlayFrom^3", Sc_AudioClip_PlayFrom);
-	ccAddExternalObjectFunction("AudioClip::PlayQueued^2", Sc_AudioClip_PlayQueued);
-	ccAddExternalObjectFunction("AudioClip::PlayOnChannel^3", Sc_AudioClip_PlayOnChannel);
-	ccAddExternalObjectFunction("AudioClip::Stop^0", Sc_AudioClip_Stop);
-	ccAddExternalObjectFunction("AudioClip::get_ID", Sc_AudioClip_GetID);
-	ccAddExternalObjectFunction("AudioClip::get_FileType", Sc_AudioClip_GetFileType);
-	ccAddExternalObjectFunction("AudioClip::get_IsAvailable", Sc_AudioClip_GetIsAvailable);
-	ccAddExternalObjectFunction("AudioClip::get_Type", Sc_AudioClip_GetType);
+	ScFnRegister audioclip_api[] = {
+		{"AudioClip::Play^2", API_FN_PAIR(AudioClip_Play)},
+		{"AudioClip::PlayFrom^3", API_FN_PAIR(AudioClip_PlayFrom)},
+		{"AudioClip::PlayQueued^2", API_FN_PAIR(AudioClip_PlayQueued)},
+		{"AudioClip::PlayOnChannel^3", API_FN_PAIR(AudioClip_PlayOnChannel)},
+		{"AudioClip::Stop^0", API_FN_PAIR(AudioClip_Stop)},
+		{"AudioClip::get_ID", API_FN_PAIR(AudioClip_GetID)},
+		{"AudioClip::get_FileType", API_FN_PAIR(AudioClip_GetFileType)},
+		{"AudioClip::get_IsAvailable", API_FN_PAIR(AudioClip_GetIsAvailable)},
+		{"AudioClip::get_Type", API_FN_PAIR(AudioClip_GetType)},
+	};
+
+	ccAddExternalFunctions361(audioclip_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index adfeadd6036..1b730161ee8 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -396,7 +396,7 @@ RuntimeScriptValue Sc_Button_Click(void *self, const RuntimeScriptValue *params,
 	API_OBJCALL_VOID_PINT(GUIButton, Button_Click);
 }
 
-RuntimeScriptValue Sc_Button_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Button_IsAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_BOOL(GUIButton, Button_IsAnimating);
 }
 
@@ -408,46 +408,50 @@ RuntimeScriptValue Sc_Button_SetTextAlignment(void *self, const RuntimeScriptVal
 	API_OBJCALL_VOID_PINT(GUIButton, Button_SetTextAlignment);
 }
 
-RuntimeScriptValue Sc_Button_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Button_GetAnimFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(GUIButton, Button_GetAnimFrame);
 }
 
-RuntimeScriptValue Sc_Button_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Button_GetAnimLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(GUIButton, Button_GetAnimLoop);
 }
 
-RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Button_GetAnimView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(GUIButton, Button_GetAnimView);
 }
 
 void RegisterButtonAPI() {
-	ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate4);
-	ccAddExternalObjectFunction("Button::Animate^7", Sc_Button_Animate7);
-	ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_Animate);
-	ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click);
-	ccAddExternalObjectFunction("Button::GetText^1", Sc_Button_GetText);
-	ccAddExternalObjectFunction("Button::SetText^1", Sc_Button_SetText);
-	ccAddExternalObjectFunction("Button::get_TextAlignment", Sc_Button_GetTextAlignment);
-	ccAddExternalObjectFunction("Button::set_TextAlignment", Sc_Button_SetTextAlignment);
-	ccAddExternalObjectFunction("Button::get_Animating", Sc_Button_GetAnimating);
-	ccAddExternalObjectFunction("Button::get_ClipImage", Sc_Button_GetClipImage);
-	ccAddExternalObjectFunction("Button::set_ClipImage", Sc_Button_SetClipImage);
-	ccAddExternalObjectFunction("Button::get_Font", Sc_Button_GetFont);
-	ccAddExternalObjectFunction("Button::set_Font", Sc_Button_SetFont);
-	ccAddExternalObjectFunction("Button::get_Frame", Sc_Button_GetFrame);
-	ccAddExternalObjectFunction("Button::get_Graphic", Sc_Button_GetGraphic);
-	ccAddExternalObjectFunction("Button::get_Loop", Sc_Button_GetLoop);
-	ccAddExternalObjectFunction("Button::get_MouseOverGraphic", Sc_Button_GetMouseOverGraphic);
-	ccAddExternalObjectFunction("Button::set_MouseOverGraphic", Sc_Button_SetMouseOverGraphic);
-	ccAddExternalObjectFunction("Button::get_NormalGraphic", Sc_Button_GetNormalGraphic);
-	ccAddExternalObjectFunction("Button::set_NormalGraphic", Sc_Button_SetNormalGraphic);
-	ccAddExternalObjectFunction("Button::get_PushedGraphic", Sc_Button_GetPushedGraphic);
-	ccAddExternalObjectFunction("Button::set_PushedGraphic", Sc_Button_SetPushedGraphic);
-	ccAddExternalObjectFunction("Button::get_Text", Sc_Button_GetText_New);
-	ccAddExternalObjectFunction("Button::set_Text", Sc_Button_SetText);
-	ccAddExternalObjectFunction("Button::get_TextColor", Sc_Button_GetTextColor);
-	ccAddExternalObjectFunction("Button::set_TextColor", Sc_Button_SetTextColor);
-	ccAddExternalObjectFunction("Button::get_View", Sc_Button_GetView);
+	ScFnRegister button_api[] = {
+		{"Button::Animate^4", API_FN_PAIR(Button_Animate4)},
+		{"Button::Animate^7", API_FN_PAIR(Button_Animate7)},
+		{"Button::Animate^8", API_FN_PAIR(Button_Animate)},
+		{"Button::Click^1", API_FN_PAIR(Button_Click)},
+		{"Button::GetText^1", API_FN_PAIR(Button_GetText)},
+		{"Button::SetText^1", API_FN_PAIR(Button_SetText)},
+		{"Button::get_TextAlignment", API_FN_PAIR(Button_GetTextAlignment)},
+		{"Button::set_TextAlignment", API_FN_PAIR(Button_SetTextAlignment)},
+		{"Button::get_Animating", API_FN_PAIR(Button_IsAnimating)},
+		{"Button::get_ClipImage", API_FN_PAIR(Button_GetClipImage)},
+		{"Button::set_ClipImage", API_FN_PAIR(Button_SetClipImage)},
+		{"Button::get_Font", API_FN_PAIR(Button_GetFont)},
+		{"Button::set_Font", API_FN_PAIR(Button_SetFont)},
+		{"Button::get_Frame", API_FN_PAIR(Button_GetAnimFrame)},
+		{"Button::get_Graphic", API_FN_PAIR(Button_GetGraphic)},
+		{"Button::get_Loop", API_FN_PAIR(Button_GetAnimLoop)},
+		{"Button::get_MouseOverGraphic", API_FN_PAIR(Button_GetMouseOverGraphic)},
+		{"Button::set_MouseOverGraphic", API_FN_PAIR(Button_SetMouseOverGraphic)},
+		{"Button::get_NormalGraphic", API_FN_PAIR(Button_GetNormalGraphic)},
+		{"Button::set_NormalGraphic", API_FN_PAIR(Button_SetNormalGraphic)},
+		{"Button::get_PushedGraphic", API_FN_PAIR(Button_GetPushedGraphic)},
+		{"Button::set_PushedGraphic", API_FN_PAIR(Button_SetPushedGraphic)},
+		{"Button::get_Text", API_FN_PAIR(Button_GetText_New)},
+		{"Button::set_Text", API_FN_PAIR(Button_SetText)},
+		{"Button::get_TextColor", API_FN_PAIR(Button_GetTextColor)},
+		{"Button::set_TextColor", API_FN_PAIR(Button_SetTextColor)},
+		{"Button::get_View", API_FN_PAIR(Button_GetAnimView)},
+	};
+
+	ccAddExternalFunctions361(button_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/date_time.cpp b/engines/ags/engine/ac/date_time.cpp
index c1d3c0d6dba..b0cbcf9de5a 100644
--- a/engines/ags/engine/ac/date_time.cpp
+++ b/engines/ags/engine/ac/date_time.cpp
@@ -118,14 +118,19 @@ RuntimeScriptValue Sc_DateTime_GetRawTime(void *self, const RuntimeScriptValue *
 }
 
 void RegisterDateTimeAPI() {
-	ccAddExternalStaticFunction("DateTime::get_Now", Sc_DateTime_Now);
-	ccAddExternalObjectFunction("DateTime::get_DayOfMonth", Sc_DateTime_GetDayOfMonth);
-	ccAddExternalObjectFunction("DateTime::get_Hour", Sc_DateTime_GetHour);
-	ccAddExternalObjectFunction("DateTime::get_Minute", Sc_DateTime_GetMinute);
-	ccAddExternalObjectFunction("DateTime::get_Month", Sc_DateTime_GetMonth);
-	ccAddExternalObjectFunction("DateTime::get_RawTime", Sc_DateTime_GetRawTime);
-	ccAddExternalObjectFunction("DateTime::get_Second", Sc_DateTime_GetSecond);
-	ccAddExternalObjectFunction("DateTime::get_Year", Sc_DateTime_GetYear);
+	ScFnRegister datetime_api[] = {
+		{"DateTime::get_Now", API_FN_PAIR(DateTime_Now)},
+
+		{"DateTime::get_DayOfMonth", API_FN_PAIR(DateTime_GetDayOfMonth)},
+		{"DateTime::get_Hour", API_FN_PAIR(DateTime_GetHour)},
+		{"DateTime::get_Minute", API_FN_PAIR(DateTime_GetMinute)},
+		{"DateTime::get_Month", API_FN_PAIR(DateTime_GetMonth)},
+		{"DateTime::get_RawTime", API_FN_PAIR(DateTime_GetRawTime)},
+		{"DateTime::get_Second", API_FN_PAIR(DateTime_GetSecond)},
+		{"DateTime::get_Year", API_FN_PAIR(DateTime_GetYear)},
+	};
+
+	ccAddExternalFunctions361(datetime_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 603c7498631..09a2b1dd714 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -1231,16 +1231,20 @@ RuntimeScriptValue Sc_Dialog_Start(void *self, const RuntimeScriptValue *params,
 }
 
 void RegisterDialogAPI() {
-	ccAddExternalObjectFunction("Dialog::get_ID",               Sc_Dialog_GetID);
-	ccAddExternalObjectFunction("Dialog::get_OptionCount",      Sc_Dialog_GetOptionCount);
-	ccAddExternalObjectFunction("Dialog::get_ShowTextParser",   Sc_Dialog_GetShowTextParser);
-	ccAddExternalObjectFunction("Dialog::DisplayOptions^1",     Sc_Dialog_DisplayOptions);
-	ccAddExternalObjectFunction("Dialog::GetOptionState^1",     Sc_Dialog_GetOptionState);
-	ccAddExternalObjectFunction("Dialog::GetOptionText^1",      Sc_Dialog_GetOptionText);
-	ccAddExternalObjectFunction("Dialog::HasOptionBeenChosen^1", Sc_Dialog_HasOptionBeenChosen);
-	ccAddExternalObjectFunction("Dialog::SetHasOptionBeenChosen^2", Sc_Dialog_SetHasOptionBeenChosen);
-	ccAddExternalObjectFunction("Dialog::SetOptionState^2",     Sc_Dialog_SetOptionState);
-	ccAddExternalObjectFunction("Dialog::Start^0",              Sc_Dialog_Start);
+	ScFnRegister dialog_api[] = {
+		{"Dialog::get_ID", API_FN_PAIR(Dialog_GetID)},
+		{"Dialog::get_OptionCount", API_FN_PAIR(Dialog_GetOptionCount)},
+		{"Dialog::get_ShowTextParser", API_FN_PAIR(Dialog_GetShowTextParser)},
+		{"Dialog::DisplayOptions^1", API_FN_PAIR(Dialog_DisplayOptions)},
+		{"Dialog::GetOptionState^1", API_FN_PAIR(Dialog_GetOptionState)},
+		{"Dialog::GetOptionText^1", API_FN_PAIR(Dialog_GetOptionText)},
+		{"Dialog::HasOptionBeenChosen^1", API_FN_PAIR(Dialog_HasOptionBeenChosen)},
+		{"Dialog::SetHasOptionBeenChosen^2", API_FN_PAIR(Dialog_SetHasOptionBeenChosen)},
+		{"Dialog::SetOptionState^2", API_FN_PAIR(Dialog_SetOptionState)},
+		{"Dialog::Start^0", API_FN_PAIR(Dialog_Start)},
+	};
+
+	ccAddExternalFunctions361(dialog_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dialog_options_rendering.cpp b/engines/ags/engine/ac/dialog_options_rendering.cpp
index 410d8b138cc..c11a2d83622 100644
--- a/engines/ags/engine/ac/dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dialog_options_rendering.cpp
@@ -246,28 +246,32 @@ RuntimeScriptValue Sc_DialogOptionsRendering_SetHasAlphaChannel(void *self, cons
 
 
 void RegisterDialogOptionsRenderingAPI() {
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::Update^0", Sc_DialogOptionsRendering_Update);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::RunActiveOption^0", Sc_DialogOptionsRendering_RunActiveOption);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ActiveOptionID", Sc_DialogOptionsRendering_GetActiveOptionID);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ActiveOptionID", Sc_DialogOptionsRendering_SetActiveOptionID);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_DialogToRender", Sc_DialogOptionsRendering_GetDialogToRender);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Height", Sc_DialogOptionsRendering_GetHeight);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Height", Sc_DialogOptionsRendering_SetHeight);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxX", Sc_DialogOptionsRendering_GetParserTextboxX);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxX", Sc_DialogOptionsRendering_SetParserTextboxX);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxY", Sc_DialogOptionsRendering_GetParserTextboxY);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxY", Sc_DialogOptionsRendering_SetParserTextboxY);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxWidth", Sc_DialogOptionsRendering_GetParserTextboxWidth);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxWidth", Sc_DialogOptionsRendering_SetParserTextboxWidth);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Surface", Sc_DialogOptionsRendering_GetSurface);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Width", Sc_DialogOptionsRendering_GetWidth);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Width", Sc_DialogOptionsRendering_SetWidth);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_X", Sc_DialogOptionsRendering_GetX);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_X", Sc_DialogOptionsRendering_SetX);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Y", Sc_DialogOptionsRendering_GetY);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Y", Sc_DialogOptionsRendering_SetY);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_HasAlphaChannel", Sc_DialogOptionsRendering_GetHasAlphaChannel);
-	ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_HasAlphaChannel", Sc_DialogOptionsRendering_SetHasAlphaChannel);
+	ScFnRegister dialogopt_api[] = {
+		{"DialogOptionsRenderingInfo::Update^0", API_FN_PAIR(DialogOptionsRendering_Update)},
+		{"DialogOptionsRenderingInfo::RunActiveOption^0", API_FN_PAIR(DialogOptionsRendering_RunActiveOption)},
+		{"DialogOptionsRenderingInfo::get_ActiveOptionID", API_FN_PAIR(DialogOptionsRendering_GetActiveOptionID)},
+		{"DialogOptionsRenderingInfo::set_ActiveOptionID", API_FN_PAIR(DialogOptionsRendering_SetActiveOptionID)},
+		{"DialogOptionsRenderingInfo::get_DialogToRender", API_FN_PAIR(DialogOptionsRendering_GetDialogToRender)},
+		{"DialogOptionsRenderingInfo::get_Height", API_FN_PAIR(DialogOptionsRendering_GetHeight)},
+		{"DialogOptionsRenderingInfo::set_Height", API_FN_PAIR(DialogOptionsRendering_SetHeight)},
+		{"DialogOptionsRenderingInfo::get_ParserTextBoxX", API_FN_PAIR(DialogOptionsRendering_GetParserTextboxX)},
+		{"DialogOptionsRenderingInfo::set_ParserTextBoxX", API_FN_PAIR(DialogOptionsRendering_SetParserTextboxX)},
+		{"DialogOptionsRenderingInfo::get_ParserTextBoxY", API_FN_PAIR(DialogOptionsRendering_GetParserTextboxY)},
+		{"DialogOptionsRenderingInfo::set_ParserTextBoxY", API_FN_PAIR(DialogOptionsRendering_SetParserTextboxY)},
+		{"DialogOptionsRenderingInfo::get_ParserTextBoxWidth", API_FN_PAIR(DialogOptionsRendering_GetParserTextboxWidth)},
+		{"DialogOptionsRenderingInfo::set_ParserTextBoxWidth", API_FN_PAIR(DialogOptionsRendering_SetParserTextboxWidth)},
+		{"DialogOptionsRenderingInfo::get_Surface", API_FN_PAIR(DialogOptionsRendering_GetSurface)},
+		{"DialogOptionsRenderingInfo::get_Width", API_FN_PAIR(DialogOptionsRendering_GetWidth)},
+		{"DialogOptionsRenderingInfo::set_Width", API_FN_PAIR(DialogOptionsRendering_SetWidth)},
+		{"DialogOptionsRenderingInfo::get_X", API_FN_PAIR(DialogOptionsRendering_GetX)},
+		{"DialogOptionsRenderingInfo::set_X", API_FN_PAIR(DialogOptionsRendering_SetX)},
+		{"DialogOptionsRenderingInfo::get_Y", API_FN_PAIR(DialogOptionsRendering_GetY)},
+		{"DialogOptionsRenderingInfo::set_Y", API_FN_PAIR(DialogOptionsRendering_SetY)},
+		{"DialogOptionsRenderingInfo::get_HasAlphaChannel", API_FN_PAIR(DialogOptionsRendering_GetHasAlphaChannel)},
+		{"DialogOptionsRenderingInfo::set_HasAlphaChannel", API_FN_PAIR(DialogOptionsRendering_SetHasAlphaChannel)},
+	};
+
+	ccAddExternalFunctions361(dialogopt_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index fbb83661ee5..c2ab462abc9 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -548,31 +548,37 @@ RuntimeScriptValue Sc_DrawingSurface_GetWidth(void *self, const RuntimeScriptVal
 //=============================================================================
 
 void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*compat_api */) {
-	ccAddExternalObjectFunction("DrawingSurface::Clear^1", Sc_DrawingSurface_Clear);
-	ccAddExternalObjectFunction("DrawingSurface::CreateCopy^0", Sc_DrawingSurface_CreateCopy);
-	ccAddExternalObjectFunction("DrawingSurface::DrawCircle^3", Sc_DrawingSurface_DrawCircle);
-	ccAddExternalObjectFunction("DrawingSurface::DrawImage^6", Sc_DrawingSurface_DrawImage6);
-	ccAddExternalObjectFunction("DrawingSurface::DrawImage^10", Sc_DrawingSurface_DrawImage);
-	ccAddExternalObjectFunction("DrawingSurface::DrawLine^5", Sc_DrawingSurface_DrawLine);
-	ccAddExternalObjectFunction("DrawingSurface::DrawMessageWrapped^5", Sc_DrawingSurface_DrawMessageWrapped);
-	ccAddExternalObjectFunction("DrawingSurface::DrawPixel^2", Sc_DrawingSurface_DrawPixel);
-	ccAddExternalObjectFunction("DrawingSurface::DrawRectangle^4", Sc_DrawingSurface_DrawRectangle);
-	ccAddExternalObjectFunction("DrawingSurface::DrawString^104", Sc_DrawingSurface_DrawString);
+	ScFnRegister drawsurf_api[] = {
+		{"DrawingSurface::Clear^1", API_FN_PAIR(DrawingSurface_Clear)},
+		{"DrawingSurface::CreateCopy^0", API_FN_PAIR(DrawingSurface_CreateCopy)},
+		{"DrawingSurface::DrawCircle^3", API_FN_PAIR(DrawingSurface_DrawCircle)},
+		{"DrawingSurface::DrawImage^6", API_FN_PAIR(DrawingSurface_DrawImage6)},
+		{"DrawingSurface::DrawImage^10", API_FN_PAIR(DrawingSurface_DrawImage)},
+		{"DrawingSurface::DrawLine^5", API_FN_PAIR(DrawingSurface_DrawLine)},
+		{"DrawingSurface::DrawMessageWrapped^5", API_FN_PAIR(DrawingSurface_DrawMessageWrapped)},
+		{"DrawingSurface::DrawPixel^2", API_FN_PAIR(DrawingSurface_DrawPixel)},
+		{"DrawingSurface::DrawRectangle^4", API_FN_PAIR(DrawingSurface_DrawRectangle)},
+		{"DrawingSurface::DrawString^104", Sc_DrawingSurface_DrawString},
+		{"DrawingSurface::DrawSurface^2", API_FN_PAIR(DrawingSurface_DrawSurface2)},
+		{"DrawingSurface::DrawSurface^10", API_FN_PAIR(DrawingSurface_DrawSurface)},
+		{"DrawingSurface::DrawTriangle^6", API_FN_PAIR(DrawingSurface_DrawTriangle)},
+		{"DrawingSurface::GetPixel^2", API_FN_PAIR(DrawingSurface_GetPixel)},
+		{"DrawingSurface::Release^0", API_FN_PAIR(DrawingSurface_Release)},
+		{"DrawingSurface::get_DrawingColor", API_FN_PAIR(DrawingSurface_GetDrawingColor)},
+		{"DrawingSurface::set_DrawingColor", API_FN_PAIR(DrawingSurface_SetDrawingColor)},
+		{"DrawingSurface::get_Height", API_FN_PAIR(DrawingSurface_GetHeight)},
+		{"DrawingSurface::get_UseHighResCoordinates", API_FN_PAIR(DrawingSurface_GetUseHighResCoordinates)},
+		{"DrawingSurface::set_UseHighResCoordinates", API_FN_PAIR(DrawingSurface_SetUseHighResCoordinates)},
+		{"DrawingSurface::get_Width", API_FN_PAIR(DrawingSurface_GetWidth)},
+	};
+
+	ccAddExternalFunctions361(drawsurf_api);
+
+	// Few functions have to be selected based on API level
 	if (base_api < kScriptAPI_v350)
-		ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped_Old);
+		ccAddExternalObjectFunction361("DrawingSurface::DrawStringWrapped^6", API_FN_PAIR(DrawingSurface_DrawStringWrapped_Old));
 	else
-		ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped);
-	ccAddExternalObjectFunction("DrawingSurface::DrawSurface^2", Sc_DrawingSurface_DrawSurface2);
-	ccAddExternalObjectFunction("DrawingSurface::DrawSurface^10", Sc_DrawingSurface_DrawSurface);
-	ccAddExternalObjectFunction("DrawingSurface::DrawTriangle^6", Sc_DrawingSurface_DrawTriangle);
-	ccAddExternalObjectFunction("DrawingSurface::GetPixel^2", Sc_DrawingSurface_GetPixel);
-	ccAddExternalObjectFunction("DrawingSurface::Release^0", Sc_DrawingSurface_Release);
-	ccAddExternalObjectFunction("DrawingSurface::get_DrawingColor", Sc_DrawingSurface_GetDrawingColor);
-	ccAddExternalObjectFunction("DrawingSurface::set_DrawingColor", Sc_DrawingSurface_SetDrawingColor);
-	ccAddExternalObjectFunction("DrawingSurface::get_Height", Sc_DrawingSurface_GetHeight);
-	ccAddExternalObjectFunction("DrawingSurface::get_UseHighResCoordinates", Sc_DrawingSurface_GetUseHighResCoordinates);
-	ccAddExternalObjectFunction("DrawingSurface::set_UseHighResCoordinates", Sc_DrawingSurface_SetUseHighResCoordinates);
-	ccAddExternalObjectFunction("DrawingSurface::get_Width", Sc_DrawingSurface_GetWidth);
+		ccAddExternalObjectFunction361("DrawingSurface::DrawStringWrapped^6", API_FN_PAIR(DrawingSurface_DrawStringWrapped));
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 2fc97fa804e..2e900764d3f 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -585,30 +585,34 @@ RuntimeScriptValue Sc_DynamicSprite_CreateFromScreenShot(const RuntimeScriptValu
 	API_SCALL_OBJAUTO_PINT2(ScriptDynamicSprite, DynamicSprite_CreateFromScreenShot);
 }
 
-
 void RegisterDynamicSpriteAPI() {
-	ccAddExternalObjectFunction("DynamicSprite::ChangeCanvasSize^4", Sc_DynamicSprite_ChangeCanvasSize);
-	ccAddExternalObjectFunction("DynamicSprite::CopyTransparencyMask^1", Sc_DynamicSprite_CopyTransparencyMask);
-	ccAddExternalObjectFunction("DynamicSprite::Crop^4", Sc_DynamicSprite_Crop);
-	ccAddExternalObjectFunction("DynamicSprite::Delete", Sc_DynamicSprite_Delete);
-	ccAddExternalObjectFunction("DynamicSprite::Flip^1", Sc_DynamicSprite_Flip);
-	ccAddExternalObjectFunction("DynamicSprite::GetDrawingSurface^0", Sc_DynamicSprite_GetDrawingSurface);
-	ccAddExternalObjectFunction("DynamicSprite::Resize^2", Sc_DynamicSprite_Resize);
-	ccAddExternalObjectFunction("DynamicSprite::Rotate^3", Sc_DynamicSprite_Rotate);
-	ccAddExternalObjectFunction("DynamicSprite::SaveToFile^1", Sc_DynamicSprite_SaveToFile);
-	ccAddExternalObjectFunction("DynamicSprite::Tint^5", Sc_DynamicSprite_Tint);
-	ccAddExternalObjectFunction("DynamicSprite::get_ColorDepth", Sc_DynamicSprite_GetColorDepth);
-	ccAddExternalObjectFunction("DynamicSprite::get_Graphic", Sc_DynamicSprite_GetGraphic);
-	ccAddExternalObjectFunction("DynamicSprite::get_Height", Sc_DynamicSprite_GetHeight);
-	ccAddExternalObjectFunction("DynamicSprite::get_Width", Sc_DynamicSprite_GetWidth);
-	ccAddExternalStaticFunction("DynamicSprite::Create^3", Sc_DynamicSprite_Create);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromBackground", Sc_DynamicSprite_CreateFromBackground);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromDrawingSurface^5", Sc_DynamicSprite_CreateFromDrawingSurface);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromExistingSprite^1", Sc_DynamicSprite_CreateFromExistingSprite_Old);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromExistingSprite^2", Sc_DynamicSprite_CreateFromExistingSprite);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromFile", Sc_DynamicSprite_CreateFromFile);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromSaveGame", Sc_DynamicSprite_CreateFromSaveGame);
-	ccAddExternalStaticFunction("DynamicSprite::CreateFromScreenShot", Sc_DynamicSprite_CreateFromScreenShot);
+	ScFnRegister dynsprite_api[] = {
+		{"DynamicSprite::Create^3", API_FN_PAIR(DynamicSprite_Create)},
+		{"DynamicSprite::CreateFromBackground", API_FN_PAIR(DynamicSprite_CreateFromBackground)},
+		{"DynamicSprite::CreateFromDrawingSurface^5", API_FN_PAIR(DynamicSprite_CreateFromDrawingSurface)},
+		{"DynamicSprite::CreateFromExistingSprite^1", API_FN_PAIR(DynamicSprite_CreateFromExistingSprite_Old)},
+		{"DynamicSprite::CreateFromExistingSprite^2", API_FN_PAIR(DynamicSprite_CreateFromExistingSprite)},
+		{"DynamicSprite::CreateFromFile", API_FN_PAIR(DynamicSprite_CreateFromFile)},
+		{"DynamicSprite::CreateFromSaveGame", API_FN_PAIR(DynamicSprite_CreateFromSaveGame)},
+		{"DynamicSprite::CreateFromScreenShot", API_FN_PAIR(DynamicSprite_CreateFromScreenShot)},
+
+		{"DynamicSprite::ChangeCanvasSize^4", API_FN_PAIR(DynamicSprite_ChangeCanvasSize)},
+		{"DynamicSprite::CopyTransparencyMask^1", API_FN_PAIR(DynamicSprite_CopyTransparencyMask)},
+		{"DynamicSprite::Crop^4", API_FN_PAIR(DynamicSprite_Crop)},
+		{"DynamicSprite::Delete", API_FN_PAIR(DynamicSprite_Delete)},
+		{"DynamicSprite::Flip^1", API_FN_PAIR(DynamicSprite_Flip)},
+		{"DynamicSprite::GetDrawingSurface^0", API_FN_PAIR(DynamicSprite_GetDrawingSurface)},
+		{"DynamicSprite::Resize^2", API_FN_PAIR(DynamicSprite_Resize)},
+		{"DynamicSprite::Rotate^3", API_FN_PAIR(DynamicSprite_Rotate)},
+		{"DynamicSprite::SaveToFile^1", API_FN_PAIR(DynamicSprite_SaveToFile)},
+		{"DynamicSprite::Tint^5", API_FN_PAIR(DynamicSprite_Tint)},
+		{"DynamicSprite::get_ColorDepth", API_FN_PAIR(DynamicSprite_GetColorDepth)},
+		{"DynamicSprite::get_Graphic", API_FN_PAIR(DynamicSprite_GetGraphic)},
+		{"DynamicSprite::get_Height", API_FN_PAIR(DynamicSprite_GetHeight)},
+		{"DynamicSprite::get_Width", API_FN_PAIR(DynamicSprite_GetWidth)},
+	};
+
+	ccAddExternalFunctions361(dynsprite_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 22d0915de8f..d7962f060e6 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -732,28 +732,31 @@ RuntimeScriptValue Sc_File_GetPath(void *self, const RuntimeScriptValue *params,
 }
 
 void RegisterFileAPI() {
-	ccAddExternalStaticFunction("File::Delete^1", Sc_File_Delete);
-	ccAddExternalStaticFunction("File::Exists^1", Sc_File_Exists);
-	ccAddExternalStaticFunction("File::Open^2", Sc_sc_OpenFile);
-	ccAddExternalStaticFunction("File::ResolvePath^1", Sc_File_ResolvePath);
-	ccAddExternalObjectFunction("File::Close^0", Sc_File_Close);
-	ccAddExternalObjectFunction("File::ReadInt^0", Sc_File_ReadInt);
-	ccAddExternalObjectFunction("File::ReadRawChar^0", Sc_File_ReadRawChar);
-	ccAddExternalObjectFunction("File::ReadRawInt^0", Sc_File_ReadRawInt);
-	ccAddExternalObjectFunction("File::ReadRawLine^1", Sc_File_ReadRawLine);
-	ccAddExternalObjectFunction("File::ReadRawLineBack^0", Sc_File_ReadRawLineBack);
-	ccAddExternalObjectFunction("File::ReadString^1", Sc_File_ReadString);
-	ccAddExternalObjectFunction("File::ReadStringBack^0", Sc_File_ReadStringBack);
-	ccAddExternalObjectFunction("File::WriteInt^1", Sc_File_WriteInt);
-	ccAddExternalObjectFunction("File::WriteRawChar^1", Sc_File_WriteRawChar);
-	ccAddExternalObjectFunction("File::WriteRawInt^1", Sc_File_WriteRawInt);
-	ccAddExternalObjectFunction("File::WriteRawLine^1", Sc_File_WriteRawLine);
-	ccAddExternalObjectFunction("File::WriteString^1", Sc_File_WriteString);
-	ccAddExternalObjectFunction("File::Seek^2", Sc_File_Seek);
-	ccAddExternalObjectFunction("File::get_EOF", Sc_File_GetEOF);
-	ccAddExternalObjectFunction("File::get_Error", Sc_File_GetError);
-	ccAddExternalObjectFunction("File::get_Position", Sc_File_GetPosition);
-	ccAddExternalObjectFunction("File::get_Path", Sc_File_GetPath);
+	ScFnRegister file_api[] = {
+		{"File::Delete^1", API_FN_PAIR(File_Delete)},
+		{"File::Exists^1", API_FN_PAIR(File_Exists)},
+		{"File::Open^2", API_FN_PAIR(sc_OpenFile)},
+
+		{"File::Close^0", API_FN_PAIR(File_Close)},
+		{"File::ReadInt^0", API_FN_PAIR(File_ReadInt)},
+		{"File::ReadRawChar^0", API_FN_PAIR(File_ReadRawChar)},
+		{"File::ReadRawInt^0", API_FN_PAIR(File_ReadRawInt)},
+		{"File::ReadRawLine^1", API_FN_PAIR(File_ReadRawLine)},
+		{"File::ReadRawLineBack^0", API_FN_PAIR(File_ReadRawLineBack)},
+		{"File::ReadString^1", API_FN_PAIR(File_ReadString)},
+		{"File::ReadStringBack^0", API_FN_PAIR(File_ReadStringBack)},
+		{"File::WriteInt^1", API_FN_PAIR(File_WriteInt)},
+		{"File::WriteRawChar^1", API_FN_PAIR(File_WriteRawChar)},
+		{"File::WriteRawInt^1", API_FN_PAIR(File_WriteRawInt)},
+		{"File::WriteRawLine^1", API_FN_PAIR(File_WriteRawLine)},
+		{"File::WriteString^1", API_FN_PAIR(File_WriteString)},
+		{"File::Seek^2", API_FN_PAIR(File_Seek)},
+		{"File::get_EOF", API_FN_PAIR(File_GetEOF)},
+		{"File::get_Error", API_FN_PAIR(File_GetError)},
+		{"File::get_Position", API_FN_PAIR(File_GetPosition)},
+	};
+
+	ccAddExternalFunctions361(file_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 52b3249a91a..e6514f9fb47 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -790,46 +790,51 @@ RuntimeScriptValue Sc_GUI_GetShown(void *self, const RuntimeScriptValue *params,
 }
 
 void RegisterGUIAPI() {
-	ccAddExternalObjectFunction("GUI::Centre^0", Sc_GUI_Centre);
-	ccAddExternalObjectFunction("GUI::Click^1", Sc_GUI_Click);
-	ccAddExternalStaticFunction("GUI::GetAtScreenXY^2", Sc_GetGUIAtLocation);
-	ccAddExternalStaticFunction("GUI::ProcessClick^3", Sc_GUI_ProcessClick);
-	ccAddExternalObjectFunction("GUI::SetPosition^2", Sc_GUI_SetPosition);
-	ccAddExternalObjectFunction("GUI::SetSize^2", Sc_GUI_SetSize);
-	ccAddExternalObjectFunction("GUI::get_BackgroundGraphic", Sc_GUI_GetBackgroundGraphic);
-	ccAddExternalObjectFunction("GUI::set_BackgroundGraphic", Sc_GUI_SetBackgroundGraphic);
-	ccAddExternalObjectFunction("GUI::get_BackgroundColor", Sc_GUI_GetBackgroundColor);
-	ccAddExternalObjectFunction("GUI::set_BackgroundColor", Sc_GUI_SetBackgroundColor);
-	ccAddExternalObjectFunction("GUI::get_BorderColor", Sc_GUI_GetBorderColor);
-	ccAddExternalObjectFunction("GUI::set_BorderColor", Sc_GUI_SetBorderColor);
-	ccAddExternalObjectFunction("GUI::get_Clickable", Sc_GUI_GetClickable);
-	ccAddExternalObjectFunction("GUI::set_Clickable", Sc_GUI_SetClickable);
-	ccAddExternalObjectFunction("GUI::get_ControlCount", Sc_GUI_GetControlCount);
-	ccAddExternalObjectFunction("GUI::geti_Controls", Sc_GUI_GetiControls);
-	ccAddExternalObjectFunction("GUI::get_Height", Sc_GUI_GetHeight);
-	ccAddExternalObjectFunction("GUI::set_Height", Sc_GUI_SetHeight);
-	ccAddExternalObjectFunction("GUI::get_ID", Sc_GUI_GetID);
-	ccAddExternalObjectFunction("GUI::get_AsTextWindow", Sc_GUI_AsTextWindow);
-	ccAddExternalObjectFunction("GUI::get_PopupStyle", Sc_GUI_GetPopupStyle);
-	ccAddExternalObjectFunction("GUI::get_PopupYPos", Sc_GUI_GetPopupYPos);
-	ccAddExternalObjectFunction("GUI::set_PopupYPos", Sc_GUI_SetPopupYPos);
-	ccAddExternalObjectFunction("TextWindowGUI::get_TextColor", Sc_GUI_GetTextColor);
-	ccAddExternalObjectFunction("TextWindowGUI::set_TextColor", Sc_GUI_SetTextColor);
-	ccAddExternalObjectFunction("TextWindowGUI::get_TextPadding", Sc_GUI_GetTextPadding);
-	ccAddExternalObjectFunction("TextWindowGUI::set_TextPadding", Sc_GUI_SetTextPadding);
-	ccAddExternalObjectFunction("GUI::get_Transparency", Sc_GUI_GetTransparency);
-	ccAddExternalObjectFunction("GUI::set_Transparency", Sc_GUI_SetTransparency);
-	ccAddExternalObjectFunction("GUI::get_Visible", Sc_GUI_GetVisible);
-	ccAddExternalObjectFunction("GUI::set_Visible", Sc_GUI_SetVisible);
-	ccAddExternalObjectFunction("GUI::get_Width", Sc_GUI_GetWidth);
-	ccAddExternalObjectFunction("GUI::set_Width", Sc_GUI_SetWidth);
-	ccAddExternalObjectFunction("GUI::get_X", Sc_GUI_GetX);
-	ccAddExternalObjectFunction("GUI::set_X", Sc_GUI_SetX);
-	ccAddExternalObjectFunction("GUI::get_Y", Sc_GUI_GetY);
-	ccAddExternalObjectFunction("GUI::set_Y", Sc_GUI_SetY);
-	ccAddExternalObjectFunction("GUI::get_ZOrder", Sc_GUI_GetZOrder);
-	ccAddExternalObjectFunction("GUI::set_ZOrder", Sc_GUI_SetZOrder);
-	ccAddExternalObjectFunction("GUI::get_Shown", Sc_GUI_GetShown);
+	ScFnRegister gui_api[] = {
+		{"GUI::GetAtScreenXY^2", API_FN_PAIR(GetGUIAtLocation)},
+		{"GUI::ProcessClick^3", API_FN_PAIR(GUI_ProcessClick)},
+
+		{"GUI::Centre^0", API_FN_PAIR(GUI_Centre)},
+		{"GUI::Click^1", API_FN_PAIR(GUI_Click)},
+		{"GUI::SetPosition^2", API_FN_PAIR(GUI_SetPosition)},
+		{"GUI::SetSize^2", API_FN_PAIR(GUI_SetSize)},
+		{"GUI::get_BackgroundGraphic", API_FN_PAIR(GUI_GetBackgroundGraphic)},
+		{"GUI::set_BackgroundGraphic", API_FN_PAIR(GUI_SetBackgroundGraphic)},
+		{"GUI::get_BackgroundColor", API_FN_PAIR(GUI_GetBackgroundColor)},
+		{"GUI::set_BackgroundColor", API_FN_PAIR(GUI_SetBackgroundColor)},
+		{"GUI::get_BorderColor", API_FN_PAIR(GUI_GetBorderColor)},
+		{"GUI::set_BorderColor", API_FN_PAIR(GUI_SetBorderColor)},
+		{"GUI::get_Clickable", API_FN_PAIR(GUI_GetClickable)},
+		{"GUI::set_Clickable", API_FN_PAIR(GUI_SetClickable)},
+		{"GUI::get_ControlCount", API_FN_PAIR(GUI_GetControlCount)},
+		{"GUI::geti_Controls", API_FN_PAIR(GUI_GetiControls)},
+		{"GUI::get_Height", API_FN_PAIR(GUI_GetHeight)},
+		{"GUI::set_Height", API_FN_PAIR(GUI_SetHeight)},
+		{"GUI::get_ID", API_FN_PAIR(GUI_GetID)},
+		{"GUI::get_AsTextWindow", API_FN_PAIR(GUI_AsTextWindow)},
+		{"GUI::get_PopupStyle", API_FN_PAIR(GUI_GetPopupStyle)},
+		{"GUI::get_PopupYPos", API_FN_PAIR(GUI_GetPopupYPos)},
+		{"GUI::set_PopupYPos", API_FN_PAIR(GUI_SetPopupYPos)},
+		{"TextWindowGUI::get_TextColor", API_FN_PAIR(GUI_GetTextColor)},
+		{"TextWindowGUI::set_TextColor", API_FN_PAIR(GUI_SetTextColor)},
+		{"TextWindowGUI::get_TextPadding", API_FN_PAIR(GUI_GetTextPadding)},
+		{"TextWindowGUI::set_TextPadding", API_FN_PAIR(GUI_SetTextPadding)},
+		{"GUI::get_Transparency", API_FN_PAIR(GUI_GetTransparency)},
+		{"GUI::set_Transparency", API_FN_PAIR(GUI_SetTransparency)},
+		{"GUI::get_Visible", API_FN_PAIR(GUI_GetVisible)},
+		{"GUI::set_Visible", API_FN_PAIR(GUI_SetVisible)},
+		{"GUI::get_Width", API_FN_PAIR(GUI_GetWidth)},
+		{"GUI::set_Width", API_FN_PAIR(GUI_SetWidth)},
+		{"GUI::get_X", API_FN_PAIR(GUI_GetX)},
+		{"GUI::set_X", API_FN_PAIR(GUI_SetX)},
+		{"GUI::get_Y", API_FN_PAIR(GUI_GetY)},
+		{"GUI::set_Y", API_FN_PAIR(GUI_SetY)},
+		{"GUI::get_ZOrder", API_FN_PAIR(GUI_GetZOrder)},
+		{"GUI::set_ZOrder", API_FN_PAIR(GUI_SetZOrder)},
+		{"GUI::get_Shown", API_FN_PAIR(GUI_GetShown)},
+	};
+
+	ccAddExternalFunctions361(gui_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index 717a56d341d..e4db002d29f 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -375,39 +375,43 @@ RuntimeScriptValue Sc_GUIControl_SetTransparency(void *self, const RuntimeScript
 	API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetTransparency);
 }
 
-
 void RegisterGUIControlAPI() {
-	ccAddExternalObjectFunction("GUIControl::BringToFront^0", Sc_GUIControl_BringToFront);
-	ccAddExternalStaticFunction("GUIControl::GetAtScreenXY^2", Sc_GetGUIControlAtLocation);
-	ccAddExternalObjectFunction("GUIControl::SendToBack^0", Sc_GUIControl_SendToBack);
-	ccAddExternalObjectFunction("GUIControl::SetPosition^2", Sc_GUIControl_SetPosition);
-	ccAddExternalObjectFunction("GUIControl::SetSize^2", Sc_GUIControl_SetSize);
-	ccAddExternalObjectFunction("GUIControl::get_AsButton", Sc_GUIControl_GetAsButton);
-	ccAddExternalObjectFunction("GUIControl::get_AsInvWindow", Sc_GUIControl_GetAsInvWindow);
-	ccAddExternalObjectFunction("GUIControl::get_AsLabel", Sc_GUIControl_GetAsLabel);
-	ccAddExternalObjectFunction("GUIControl::get_AsListBox", Sc_GUIControl_GetAsListBox);
-	ccAddExternalObjectFunction("GUIControl::get_AsSlider", Sc_GUIControl_GetAsSlider);
-	ccAddExternalObjectFunction("GUIControl::get_AsTextBox", Sc_GUIControl_GetAsTextBox);
-	ccAddExternalObjectFunction("GUIControl::get_Clickable", Sc_GUIControl_GetClickable);
-	ccAddExternalObjectFunction("GUIControl::set_Clickable", Sc_GUIControl_SetClickable);
-	ccAddExternalObjectFunction("GUIControl::get_Enabled", Sc_GUIControl_GetEnabled);
-	ccAddExternalObjectFunction("GUIControl::set_Enabled", Sc_GUIControl_SetEnabled);
-	ccAddExternalObjectFunction("GUIControl::get_Height", Sc_GUIControl_GetHeight);
-	ccAddExternalObjectFunction("GUIControl::set_Height", Sc_GUIControl_SetHeight);
-	ccAddExternalObjectFunction("GUIControl::get_ID", Sc_GUIControl_GetID);
-	ccAddExternalObjectFunction("GUIControl::get_OwningGUI", Sc_GUIControl_GetOwningGUI);
-	ccAddExternalObjectFunction("GUIControl::get_Visible", Sc_GUIControl_GetVisible);
-	ccAddExternalObjectFunction("GUIControl::set_Visible", Sc_GUIControl_SetVisible);
-	ccAddExternalObjectFunction("GUIControl::get_Width", Sc_GUIControl_GetWidth);
-	ccAddExternalObjectFunction("GUIControl::set_Width", Sc_GUIControl_SetWidth);
-	ccAddExternalObjectFunction("GUIControl::get_X", Sc_GUIControl_GetX);
-	ccAddExternalObjectFunction("GUIControl::set_X", Sc_GUIControl_SetX);
-	ccAddExternalObjectFunction("GUIControl::get_Y", Sc_GUIControl_GetY);
-	ccAddExternalObjectFunction("GUIControl::set_Y", Sc_GUIControl_SetY);
-	ccAddExternalObjectFunction("GUIControl::get_ZOrder", Sc_GUIControl_GetZOrder);
-	ccAddExternalObjectFunction("GUIControl::set_ZOrder", Sc_GUIControl_SetZOrder);
-	ccAddExternalObjectFunction("GUIControl::get_Transparency", Sc_GUIControl_GetTransparency);
-	ccAddExternalObjectFunction("GUIControl::set_Transparency", Sc_GUIControl_SetTransparency);
+	ScFnRegister guicontrol_api[] = {
+		{"GUIControl::GetAtScreenXY^2", API_FN_PAIR(GetGUIControlAtLocation)},
+
+		{"GUIControl::BringToFront^0", API_FN_PAIR(GUIControl_BringToFront)},
+		{"GUIControl::SendToBack^0", API_FN_PAIR(GUIControl_SendToBack)},
+		{"GUIControl::SetPosition^2", API_FN_PAIR(GUIControl_SetPosition)},
+		{"GUIControl::SetSize^2", API_FN_PAIR(GUIControl_SetSize)},
+		{"GUIControl::get_AsButton", API_FN_PAIR(GUIControl_GetAsButton)},
+		{"GUIControl::get_AsInvWindow", API_FN_PAIR(GUIControl_GetAsInvWindow)},
+		{"GUIControl::get_AsLabel", API_FN_PAIR(GUIControl_GetAsLabel)},
+		{"GUIControl::get_AsListBox", API_FN_PAIR(GUIControl_GetAsListBox)},
+		{"GUIControl::get_AsSlider", API_FN_PAIR(GUIControl_GetAsSlider)},
+		{"GUIControl::get_AsTextBox", API_FN_PAIR(GUIControl_GetAsTextBox)},
+		{"GUIControl::get_Clickable", API_FN_PAIR(GUIControl_GetClickable)},
+		{"GUIControl::set_Clickable", API_FN_PAIR(GUIControl_SetClickable)},
+		{"GUIControl::get_Enabled", API_FN_PAIR(GUIControl_GetEnabled)},
+		{"GUIControl::set_Enabled", API_FN_PAIR(GUIControl_SetEnabled)},
+		{"GUIControl::get_Height", API_FN_PAIR(GUIControl_GetHeight)},
+		{"GUIControl::set_Height", API_FN_PAIR(GUIControl_SetHeight)},
+		{"GUIControl::get_ID", API_FN_PAIR(GUIControl_GetID)},
+		{"GUIControl::get_OwningGUI", API_FN_PAIR(GUIControl_GetOwningGUI)},
+		{"GUIControl::get_Visible", API_FN_PAIR(GUIControl_GetVisible)},
+		{"GUIControl::set_Visible", API_FN_PAIR(GUIControl_SetVisible)},
+		{"GUIControl::get_Width", API_FN_PAIR(GUIControl_GetWidth)},
+		{"GUIControl::set_Width", API_FN_PAIR(GUIControl_SetWidth)},
+		{"GUIControl::get_X", API_FN_PAIR(GUIControl_GetX)},
+		{"GUIControl::set_X", API_FN_PAIR(GUIControl_SetX)},
+		{"GUIControl::get_Y", API_FN_PAIR(GUIControl_GetY)},
+		{"GUIControl::set_Y", API_FN_PAIR(GUIControl_SetY)},
+		{"GUIControl::get_ZOrder", API_FN_PAIR(GUIControl_GetZOrder)},
+		{"GUIControl::set_ZOrder", API_FN_PAIR(GUIControl_SetZOrder)},
+		{"GUIControl::get_Transparency", API_FN_PAIR(GUIControl_GetTransparency)},
+		{"GUIControl::set_Transparency", API_FN_PAIR(GUIControl_SetTransparency)},
+	};
+
+	ccAddExternalFunctions361(guicontrol_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp
index 24b97f47a36..828684565c6 100644
--- a/engines/ags/engine/ac/hotspot.cpp
+++ b/engines/ags/engine/ac/hotspot.cpp
@@ -229,27 +229,30 @@ RuntimeScriptValue Sc_Hotspot_GetWalkToY(void *self, const RuntimeScriptValue *p
 	API_OBJCALL_INT(ScriptHotspot, Hotspot_GetWalkToY);
 }
 
-
-
 void RegisterHotspotAPI() {
-	ccAddExternalStaticFunction("Hotspot::GetAtRoomXY^2", Sc_GetHotspotAtRoom);
-	ccAddExternalStaticFunction("Hotspot::GetAtScreenXY^2", Sc_GetHotspotAtScreen);
-	ccAddExternalStaticFunction("Hotspot::GetDrawingSurface", Sc_Hotspot_GetDrawingSurface);
-	ccAddExternalObjectFunction("Hotspot::GetName^1", Sc_Hotspot_GetName);
-	ccAddExternalObjectFunction("Hotspot::GetProperty^1", Sc_Hotspot_GetProperty);
-	ccAddExternalObjectFunction("Hotspot::GetPropertyText^2", Sc_Hotspot_GetPropertyText);
-	ccAddExternalObjectFunction("Hotspot::GetTextProperty^1", Sc_Hotspot_GetTextProperty);
-	ccAddExternalObjectFunction("Hotspot::SetProperty^2", Sc_Hotspot_SetProperty);
-	ccAddExternalObjectFunction("Hotspot::SetTextProperty^2", Sc_Hotspot_SetTextProperty);
-	ccAddExternalObjectFunction("Hotspot::IsInteractionAvailable^1", Sc_Hotspot_IsInteractionAvailable);
-	ccAddExternalObjectFunction("Hotspot::RunInteraction^1", Sc_Hotspot_RunInteraction);
-	ccAddExternalObjectFunction("Hotspot::get_Enabled", Sc_Hotspot_GetEnabled);
-	ccAddExternalObjectFunction("Hotspot::set_Enabled", Sc_Hotspot_SetEnabled);
-	ccAddExternalObjectFunction("Hotspot::get_ID", Sc_Hotspot_GetID);
-	ccAddExternalObjectFunction("Hotspot::get_Name", Sc_Hotspot_GetName_New);
-	ccAddExternalObjectFunction("Hotspot::set_Name", Sc_Hotspot_SetName);
-	ccAddExternalObjectFunction("Hotspot::get_WalkToX", Sc_Hotspot_GetWalkToX);
-	ccAddExternalObjectFunction("Hotspot::get_WalkToY", Sc_Hotspot_GetWalkToY);
+	ScFnRegister hotspot_api[] = {
+		{"Hotspot::GetAtRoomXY^2", API_FN_PAIR(GetHotspotAtRoom)},
+		{"Hotspot::GetAtScreenXY^2", API_FN_PAIR(GetHotspotAtScreen)},
+		{"Hotspot::GetDrawingSurface", API_FN_PAIR(Hotspot_GetDrawingSurface)},
+
+		{"Hotspot::GetName^1", API_FN_PAIR(Hotspot_GetName)},
+		{"Hotspot::GetProperty^1", API_FN_PAIR(Hotspot_GetProperty)},
+		{"Hotspot::GetPropertyText^2", API_FN_PAIR(Hotspot_GetPropertyText)},
+		{"Hotspot::GetTextProperty^1", API_FN_PAIR(Hotspot_GetTextProperty)},
+		{"Hotspot::SetProperty^2", API_FN_PAIR(Hotspot_SetProperty)},
+		{"Hotspot::SetTextProperty^2", API_FN_PAIR(Hotspot_SetTextProperty)},
+		{"Hotspot::IsInteractionAvailable^1", API_FN_PAIR(Hotspot_IsInteractionAvailable)},
+		{"Hotspot::RunInteraction^1", API_FN_PAIR(Hotspot_RunInteraction)},
+		{"Hotspot::get_Enabled", API_FN_PAIR(Hotspot_GetEnabled)},
+		{"Hotspot::set_Enabled", API_FN_PAIR(Hotspot_SetEnabled)},
+		{"Hotspot::get_ID", API_FN_PAIR(Hotspot_GetID)},
+		{"Hotspot::get_Name", API_FN_PAIR(Hotspot_GetName_New)},
+		{"Hotspot::set_Name", API_FN_PAIR(Hotspot_SetName)},
+		{"Hotspot::get_WalkToX", API_FN_PAIR(Hotspot_GetWalkToX)},
+		{"Hotspot::get_WalkToY", API_FN_PAIR(Hotspot_GetWalkToY)},
+	};
+
+	ccAddExternalFunctions361(hotspot_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index 8f0a91ffb66..c85ea665737 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -591,22 +591,25 @@ RuntimeScriptValue Sc_InvWindow_SetTopItem(void *self, const RuntimeScriptValue
 }
 
 
-
 void RegisterInventoryWindowAPI() {
-	ccAddExternalObjectFunction("InvWindow::ScrollDown^0", Sc_InvWindow_ScrollDown);
-	ccAddExternalObjectFunction("InvWindow::ScrollUp^0", Sc_InvWindow_ScrollUp);
-	ccAddExternalObjectFunction("InvWindow::get_CharacterToUse", Sc_InvWindow_GetCharacterToUse);
-	ccAddExternalObjectFunction("InvWindow::set_CharacterToUse", Sc_InvWindow_SetCharacterToUse);
-	ccAddExternalObjectFunction("InvWindow::geti_ItemAtIndex", Sc_InvWindow_GetItemAtIndex);
-	ccAddExternalObjectFunction("InvWindow::get_ItemCount", Sc_InvWindow_GetItemCount);
-	ccAddExternalObjectFunction("InvWindow::get_ItemHeight", Sc_InvWindow_GetItemHeight);
-	ccAddExternalObjectFunction("InvWindow::set_ItemHeight", Sc_InvWindow_SetItemHeight);
-	ccAddExternalObjectFunction("InvWindow::get_ItemWidth", Sc_InvWindow_GetItemWidth);
-	ccAddExternalObjectFunction("InvWindow::set_ItemWidth", Sc_InvWindow_SetItemWidth);
-	ccAddExternalObjectFunction("InvWindow::get_ItemsPerRow", Sc_InvWindow_GetItemsPerRow);
-	ccAddExternalObjectFunction("InvWindow::get_RowCount", Sc_InvWindow_GetRowCount);
-	ccAddExternalObjectFunction("InvWindow::get_TopItem", Sc_InvWindow_GetTopItem);
-	ccAddExternalObjectFunction("InvWindow::set_TopItem", Sc_InvWindow_SetTopItem);
+	ScFnRegister invwindow_api[] = {
+		{"InvWindow::ScrollDown^0", API_FN_PAIR(InvWindow_ScrollDown)},
+		{"InvWindow::ScrollUp^0", API_FN_PAIR(InvWindow_ScrollUp)},
+		{"InvWindow::get_CharacterToUse", API_FN_PAIR(InvWindow_GetCharacterToUse)},
+		{"InvWindow::set_CharacterToUse", API_FN_PAIR(InvWindow_SetCharacterToUse)},
+		{"InvWindow::geti_ItemAtIndex", API_FN_PAIR(InvWindow_GetItemAtIndex)},
+		{"InvWindow::get_ItemCount", API_FN_PAIR(InvWindow_GetItemCount)},
+		{"InvWindow::get_ItemHeight", API_FN_PAIR(InvWindow_GetItemHeight)},
+		{"InvWindow::set_ItemHeight", API_FN_PAIR(InvWindow_SetItemHeight)},
+		{"InvWindow::get_ItemWidth", API_FN_PAIR(InvWindow_GetItemWidth)},
+		{"InvWindow::set_ItemWidth", API_FN_PAIR(InvWindow_SetItemWidth)},
+		{"InvWindow::get_ItemsPerRow", API_FN_PAIR(InvWindow_GetItemsPerRow)},
+		{"InvWindow::get_RowCount", API_FN_PAIR(InvWindow_GetRowCount)},
+		{"InvWindow::get_TopItem", API_FN_PAIR(InvWindow_GetTopItem)},
+		{"InvWindow::set_TopItem", API_FN_PAIR(InvWindow_SetTopItem)},
+	};
+
+	ccAddExternalFunctions361(invwindow_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/inventory_item.cpp b/engines/ags/engine/ac/inventory_item.cpp
index d99e729cf12..feb095f71ea 100644
--- a/engines/ags/engine/ac/inventory_item.cpp
+++ b/engines/ags/engine/ac/inventory_item.cpp
@@ -201,26 +201,29 @@ RuntimeScriptValue Sc_InventoryItem_GetName_New(void *self, const RuntimeScriptV
 	API_CONST_OBJCALL_OBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetName_New);
 }
 
-
-
 void RegisterInventoryItemAPI() {
-	ccAddExternalStaticFunction("InventoryItem::GetAtScreenXY^2", Sc_GetInvAtLocation);
-	ccAddExternalObjectFunction("InventoryItem::IsInteractionAvailable^1", Sc_InventoryItem_CheckInteractionAvailable);
-	ccAddExternalObjectFunction("InventoryItem::GetName^1", Sc_InventoryItem_GetName);
-	ccAddExternalObjectFunction("InventoryItem::GetProperty^1", Sc_InventoryItem_GetProperty);
-	ccAddExternalObjectFunction("InventoryItem::GetPropertyText^2", Sc_InventoryItem_GetPropertyText);
-	ccAddExternalObjectFunction("InventoryItem::GetTextProperty^1", Sc_InventoryItem_GetTextProperty);
-	ccAddExternalObjectFunction("InventoryItem::SetProperty^2", Sc_InventoryItem_SetProperty);
-	ccAddExternalObjectFunction("InventoryItem::SetTextProperty^2", Sc_InventoryItem_SetTextProperty);
-	ccAddExternalObjectFunction("InventoryItem::RunInteraction^1", Sc_InventoryItem_RunInteraction);
-	ccAddExternalObjectFunction("InventoryItem::SetName^1", Sc_InventoryItem_SetName);
-	ccAddExternalObjectFunction("InventoryItem::get_CursorGraphic", Sc_InventoryItem_GetCursorGraphic);
-	ccAddExternalObjectFunction("InventoryItem::set_CursorGraphic", Sc_InventoryItem_SetCursorGraphic);
-	ccAddExternalObjectFunction("InventoryItem::get_Graphic", Sc_InventoryItem_GetGraphic);
-	ccAddExternalObjectFunction("InventoryItem::set_Graphic", Sc_InventoryItem_SetGraphic);
-	ccAddExternalObjectFunction("InventoryItem::get_ID", Sc_InventoryItem_GetID);
-	ccAddExternalObjectFunction("InventoryItem::get_Name", Sc_InventoryItem_GetName_New);
-	ccAddExternalObjectFunction("InventoryItem::set_Name", Sc_InventoryItem_SetName);
+	ScFnRegister invitem_api[] = {
+		{"InventoryItem::GetAtScreenXY^2", API_FN_PAIR(GetInvAtLocation)},
+
+		{"InventoryItem::IsInteractionAvailable^1", API_FN_PAIR(InventoryItem_CheckInteractionAvailable)},
+		{"InventoryItem::GetName^1", API_FN_PAIR(InventoryItem_GetName)},
+		{"InventoryItem::GetProperty^1", API_FN_PAIR(InventoryItem_GetProperty)},
+		{"InventoryItem::GetPropertyText^2", API_FN_PAIR(InventoryItem_GetPropertyText)},
+		{"InventoryItem::GetTextProperty^1", API_FN_PAIR(InventoryItem_GetTextProperty)},
+		{"InventoryItem::SetProperty^2", API_FN_PAIR(InventoryItem_SetProperty)},
+		{"InventoryItem::SetTextProperty^2", API_FN_PAIR(InventoryItem_SetTextProperty)},
+		{"InventoryItem::RunInteraction^1", API_FN_PAIR(InventoryItem_RunInteraction)},
+		{"InventoryItem::SetName^1", API_FN_PAIR(InventoryItem_SetName)},
+		{"InventoryItem::get_CursorGraphic", API_FN_PAIR(InventoryItem_GetCursorGraphic)},
+		{"InventoryItem::set_CursorGraphic", API_FN_PAIR(InventoryItem_SetCursorGraphic)},
+		{"InventoryItem::get_Graphic", API_FN_PAIR(InventoryItem_GetGraphic)},
+		{"InventoryItem::set_Graphic", API_FN_PAIR(InventoryItem_SetGraphic)},
+		{"InventoryItem::get_ID", API_FN_PAIR(InventoryItem_GetID)},
+		{"InventoryItem::get_Name", API_FN_PAIR(InventoryItem_GetName_New)},
+		{"InventoryItem::set_Name", API_FN_PAIR(InventoryItem_SetName)},
+	};
+
+	ccAddExternalFunctions361(invitem_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/label.cpp b/engines/ags/engine/ac/label.cpp
index db9edc3dc0d..4835f55e03a 100644
--- a/engines/ags/engine/ac/label.cpp
+++ b/engines/ags/engine/ac/label.cpp
@@ -146,19 +146,21 @@ RuntimeScriptValue Sc_Label_SetColor(void *self, const RuntimeScriptValue *param
 	API_OBJCALL_VOID_PINT(GUILabel, Label_SetColor);
 }
 
-
-
 void RegisterLabelAPI() {
-	ccAddExternalObjectFunction("Label::GetText^1", Sc_Label_GetText);
-	ccAddExternalObjectFunction("Label::SetText^1", Sc_Label_SetText);
-	ccAddExternalObjectFunction("Label::get_TextAlignment", Sc_Label_GetTextAlignment);
-	ccAddExternalObjectFunction("Label::set_TextAlignment", Sc_Label_SetTextAlignment);
-	ccAddExternalObjectFunction("Label::get_Font", Sc_Label_GetFont);
-	ccAddExternalObjectFunction("Label::set_Font", Sc_Label_SetFont);
-	ccAddExternalObjectFunction("Label::get_Text", Sc_Label_GetText_New);
-	ccAddExternalObjectFunction("Label::set_Text", Sc_Label_SetText);
-	ccAddExternalObjectFunction("Label::get_TextColor", Sc_Label_GetColor);
-	ccAddExternalObjectFunction("Label::set_TextColor", Sc_Label_SetColor);
+	ScFnRegister label_api[] = {
+		{"Label::GetText^1", API_FN_PAIR(Label_GetText)},
+		{"Label::SetText^1", API_FN_PAIR(Label_SetText)},
+		{"Label::get_TextAlignment", API_FN_PAIR(Label_GetTextAlignment)},
+		{"Label::set_TextAlignment", API_FN_PAIR(Label_SetTextAlignment)},
+		{"Label::get_Font", API_FN_PAIR(Label_GetFont)},
+		{"Label::set_Font", API_FN_PAIR(Label_SetFont)},
+		{"Label::get_Text", API_FN_PAIR(Label_GetText_New)},
+		{"Label::set_Text", API_FN_PAIR(Label_SetText)},
+		{"Label::get_TextColor", API_FN_PAIR(Label_GetColor)},
+		{"Label::set_TextColor", API_FN_PAIR(Label_SetColor)},
+	};
+
+	ccAddExternalFunctions361(label_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp
index ef071a41110..c17c5aeaec1 100644
--- a/engines/ags/engine/ac/listbox.cpp
+++ b/engines/ags/engine/ac/listbox.cpp
@@ -581,48 +581,51 @@ RuntimeScriptValue Sc_ListBox_SetTopItem(void *self, const RuntimeScriptValue *p
 }
 
 
-
 void RegisterListBoxAPI() {
-	ccAddExternalObjectFunction("ListBox::AddItem^1", Sc_ListBox_AddItem);
-	ccAddExternalObjectFunction("ListBox::Clear^0", Sc_ListBox_Clear);
-	ccAddExternalObjectFunction("ListBox::FillDirList^1", Sc_ListBox_FillDirList);
-	ccAddExternalObjectFunction("ListBox::FillSaveGameList^0", Sc_ListBox_FillSaveGameList);
-	ccAddExternalObjectFunction("ListBox::GetItemAtLocation^2", Sc_ListBox_GetItemAtLocation);
-	ccAddExternalObjectFunction("ListBox::GetItemText^2", Sc_ListBox_GetItemText);
-	ccAddExternalObjectFunction("ListBox::InsertItemAt^2", Sc_ListBox_InsertItemAt);
-	ccAddExternalObjectFunction("ListBox::RemoveItem^1", Sc_ListBox_RemoveItem);
-	ccAddExternalObjectFunction("ListBox::ScrollDown^0", Sc_ListBox_ScrollDown);
-	ccAddExternalObjectFunction("ListBox::ScrollUp^0", Sc_ListBox_ScrollUp);
-	ccAddExternalObjectFunction("ListBox::SetItemText^2", Sc_ListBox_SetItemText);
-	ccAddExternalObjectFunction("ListBox::get_Font", Sc_ListBox_GetFont);
-	ccAddExternalObjectFunction("ListBox::set_Font", Sc_ListBox_SetFont);
-	ccAddExternalObjectFunction("ListBox::get_ShowBorder", Sc_ListBox_GetShowBorder);
-	ccAddExternalObjectFunction("ListBox::set_ShowBorder", Sc_ListBox_SetShowBorder);
-	ccAddExternalObjectFunction("ListBox::get_ShowScrollArrows", Sc_ListBox_GetShowScrollArrows);
-	ccAddExternalObjectFunction("ListBox::set_ShowScrollArrows", Sc_ListBox_SetShowScrollArrows);
-	// old "inverted" properties
-	ccAddExternalObjectFunction("ListBox::get_HideBorder", Sc_ListBox_GetHideBorder);
-	ccAddExternalObjectFunction("ListBox::set_HideBorder", Sc_ListBox_SetHideBorder);
-	ccAddExternalObjectFunction("ListBox::get_HideScrollArrows", Sc_ListBox_GetHideScrollArrows);
-	ccAddExternalObjectFunction("ListBox::set_HideScrollArrows", Sc_ListBox_SetHideScrollArrows);
-	//
-	ccAddExternalObjectFunction("ListBox::get_ItemCount", Sc_ListBox_GetItemCount);
-	ccAddExternalObjectFunction("ListBox::geti_Items", Sc_ListBox_GetItems);
-	ccAddExternalObjectFunction("ListBox::seti_Items", Sc_ListBox_SetItemText);
-	ccAddExternalObjectFunction("ListBox::get_RowCount", Sc_ListBox_GetRowCount);
-	ccAddExternalObjectFunction("ListBox::geti_SaveGameSlots", Sc_ListBox_GetSaveGameSlots);
-	ccAddExternalObjectFunction("ListBox::get_SelectedBackColor", Sc_ListBox_GetSelectedBackColor);
-	ccAddExternalObjectFunction("ListBox::set_SelectedBackColor", Sc_ListBox_SetSelectedBackColor);
-	ccAddExternalObjectFunction("ListBox::get_SelectedIndex", Sc_ListBox_GetSelectedIndex);
-	ccAddExternalObjectFunction("ListBox::set_SelectedIndex", Sc_ListBox_SetSelectedIndex);
-	ccAddExternalObjectFunction("ListBox::get_SelectedTextColor", Sc_ListBox_GetSelectedTextColor);
-	ccAddExternalObjectFunction("ListBox::set_SelectedTextColor", Sc_ListBox_SetSelectedTextColor);
-	ccAddExternalObjectFunction("ListBox::get_TextAlignment", Sc_ListBox_GetTextAlignment);
-	ccAddExternalObjectFunction("ListBox::set_TextAlignment", Sc_ListBox_SetTextAlignment);
-	ccAddExternalObjectFunction("ListBox::get_TextColor", Sc_ListBox_GetTextColor);
-	ccAddExternalObjectFunction("ListBox::set_TextColor", Sc_ListBox_SetTextColor);
-	ccAddExternalObjectFunction("ListBox::get_TopItem", Sc_ListBox_GetTopItem);
-	ccAddExternalObjectFunction("ListBox::set_TopItem", Sc_ListBox_SetTopItem);
+	ScFnRegister listbox_api[] = {
+		{"ListBox::AddItem^1", API_FN_PAIR(ListBox_AddItem)},
+		{"ListBox::Clear^0", API_FN_PAIR(ListBox_Clear)},
+		{"ListBox::FillDirList^1", API_FN_PAIR(ListBox_FillDirList)},
+		{"ListBox::FillSaveGameList^0", API_FN_PAIR(ListBox_FillSaveGameList)},
+		{"ListBox::GetItemAtLocation^2", API_FN_PAIR(ListBox_GetItemAtLocation)},
+		{"ListBox::GetItemText^2", API_FN_PAIR(ListBox_GetItemText)},
+		{"ListBox::InsertItemAt^2", API_FN_PAIR(ListBox_InsertItemAt)},
+		{"ListBox::RemoveItem^1", API_FN_PAIR(ListBox_RemoveItem)},
+		{"ListBox::ScrollDown^0", API_FN_PAIR(ListBox_ScrollDown)},
+		{"ListBox::ScrollUp^0", API_FN_PAIR(ListBox_ScrollUp)},
+		{"ListBox::SetItemText^2", API_FN_PAIR(ListBox_SetItemText)},
+		{"ListBox::get_Font", API_FN_PAIR(ListBox_GetFont)},
+		{"ListBox::set_Font", API_FN_PAIR(ListBox_SetFont)},
+		{"ListBox::get_ShowBorder", API_FN_PAIR(ListBox_GetShowBorder)},
+		{"ListBox::set_ShowBorder", API_FN_PAIR(ListBox_SetShowBorder)},
+		{"ListBox::get_ShowScrollArrows", API_FN_PAIR(ListBox_GetShowScrollArrows)},
+		{"ListBox::set_ShowScrollArrows", API_FN_PAIR(ListBox_SetShowScrollArrows)},
+		// old { "inverted" properties
+		{"ListBox::get_HideBorder", API_FN_PAIR(ListBox_GetHideBorder)},
+		{"ListBox::set_HideBorder", API_FN_PAIR(ListBox_SetHideBorder)},
+		{"ListBox::get_HideScrollArrows", API_FN_PAIR(ListBox_GetHideScrollArrows)},
+		{"ListBox::set_HideScrollArrows", API_FN_PAIR(ListBox_SetHideScrollArrows)},
+		//
+		{"ListBox::get_ItemCount", API_FN_PAIR(ListBox_GetItemCount)},
+		{"ListBox::geti_Items", API_FN_PAIR(ListBox_GetItems)},
+		{"ListBox::seti_Items", API_FN_PAIR(ListBox_SetItemText)},
+		{"ListBox::get_RowCount", API_FN_PAIR(ListBox_GetRowCount)},
+		{"ListBox::geti_SaveGameSlots", API_FN_PAIR(ListBox_GetSaveGameSlots)},
+		{"ListBox::get_SelectedBackColor", API_FN_PAIR(ListBox_GetSelectedBackColor)},
+		{"ListBox::set_SelectedBackColor", API_FN_PAIR(ListBox_SetSelectedBackColor)},
+		{"ListBox::get_SelectedIndex", API_FN_PAIR(ListBox_GetSelectedIndex)},
+		{"ListBox::set_SelectedIndex", API_FN_PAIR(ListBox_SetSelectedIndex)},
+		{"ListBox::get_SelectedTextColor", API_FN_PAIR(ListBox_GetSelectedTextColor)},
+		{"ListBox::set_SelectedTextColor", API_FN_PAIR(ListBox_SetSelectedTextColor)},
+		{"ListBox::get_TextAlignment", API_FN_PAIR(ListBox_GetTextAlignment)},
+		{"ListBox::set_TextAlignment", API_FN_PAIR(ListBox_SetTextAlignment)},
+		{"ListBox::get_TextColor", API_FN_PAIR(ListBox_GetTextColor)},
+		{"ListBox::set_TextColor", API_FN_PAIR(ListBox_SetTextColor)},
+		{"ListBox::get_TopItem", API_FN_PAIR(ListBox_GetTopItem)},
+		{"ListBox::set_TopItem", API_FN_PAIR(ListBox_SetTopItem)},
+	};
+
+	ccAddExternalFunctions361(listbox_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/math.cpp b/engines/ags/engine/ac/math.cpp
index b25177f0838..5d4c3c51ba5 100644
--- a/engines/ags/engine/ac/math.cpp
+++ b/engines/ags/engine/ac/math.cpp
@@ -237,26 +237,29 @@ RuntimeScriptValue Sc_Math_GetPi(const RuntimeScriptValue *params, int32_t param
 	API_SCALL_FLOAT(Math_GetPi);
 }
 
-
 void RegisterMathAPI() {
-	ccAddExternalStaticFunction("Maths::ArcCos^1", Sc_Math_ArcCos);
-	ccAddExternalStaticFunction("Maths::ArcSin^1", Sc_Math_ArcSin);
-	ccAddExternalStaticFunction("Maths::ArcTan^1", Sc_Math_ArcTan);
-	ccAddExternalStaticFunction("Maths::ArcTan2^2", Sc_Math_ArcTan2);
-	ccAddExternalStaticFunction("Maths::Cos^1", Sc_Math_Cos);
-	ccAddExternalStaticFunction("Maths::Cosh^1", Sc_Math_Cosh);
-	ccAddExternalStaticFunction("Maths::DegreesToRadians^1", Sc_Math_DegreesToRadians);
-	ccAddExternalStaticFunction("Maths::Exp^1", Sc_Math_Exp);
-	ccAddExternalStaticFunction("Maths::Log^1", Sc_Math_Log);
-	ccAddExternalStaticFunction("Maths::Log10^1", Sc_Math_Log10);
-	ccAddExternalStaticFunction("Maths::RadiansToDegrees^1", Sc_Math_RadiansToDegrees);
-	ccAddExternalStaticFunction("Maths::RaiseToPower^2", Sc_Math_RaiseToPower);
-	ccAddExternalStaticFunction("Maths::Sin^1", Sc_Math_Sin);
-	ccAddExternalStaticFunction("Maths::Sinh^1", Sc_Math_Sinh);
-	ccAddExternalStaticFunction("Maths::Sqrt^1", Sc_Math_Sqrt);
-	ccAddExternalStaticFunction("Maths::Tan^1", Sc_Math_Tan);
-	ccAddExternalStaticFunction("Maths::Tanh^1", Sc_Math_Tanh);
-	ccAddExternalStaticFunction("Maths::get_Pi", Sc_Math_GetPi);
+	ScFnRegister math_api[] = {
+		{"Maths::ArcCos^1", API_FN_PAIR(Math_ArcCos)},
+		{"Maths::ArcSin^1", API_FN_PAIR(Math_ArcSin)},
+		{"Maths::ArcTan^1", API_FN_PAIR(Math_ArcTan)},
+		{"Maths::ArcTan2^2", API_FN_PAIR(Math_ArcTan2)},
+		{"Maths::Cos^1", API_FN_PAIR(Math_Cos)},
+		{"Maths::Cosh^1", API_FN_PAIR(Math_Cosh)},
+		{"Maths::DegreesToRadians^1", API_FN_PAIR(Math_DegreesToRadians)},
+		{"Maths::Exp^1", API_FN_PAIR(Math_Exp)},
+		{"Maths::Log^1", API_FN_PAIR(Math_Log)},
+		{"Maths::Log10^1", API_FN_PAIR(Math_Log10)},
+		{"Maths::RadiansToDegrees^1", API_FN_PAIR(Math_RadiansToDegrees)},
+		{"Maths::RaiseToPower^2", API_FN_PAIR(Math_RaiseToPower)},
+		{"Maths::Sin^1", API_FN_PAIR(Math_Sin)},
+		{"Maths::Sinh^1", API_FN_PAIR(Math_Sinh)},
+		{"Maths::Sqrt^1", API_FN_PAIR(Math_Sqrt)},
+		{"Maths::Tan^1", API_FN_PAIR(Math_Tan)},
+		{"Maths::Tanh^1", API_FN_PAIR(Math_Tanh)},
+		{"Maths::get_Pi", API_FN_PAIR(Math_GetPi)},
+	};
+
+	ccAddExternalFunctions361(math_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 3f080046442..63b5d1a9e1c 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -320,7 +320,7 @@ void Mouse_EnableControl(bool on) {
 	_GP(usetup).mouse_ctrl_enabled = on; // remember setting in config
 }
 
-bool Mouse_IsAutoLocking() {
+bool Mouse_GetAutoLock() {
 	return _GP(usetup).mouse_auto_lock;
 }
 
@@ -565,7 +565,7 @@ RuntimeScriptValue Sc_Mouse_SetControlEnabled(const RuntimeScriptValue *params,
 }
 
 RuntimeScriptValue Sc_Mouse_GetAutoLock(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_BOOL(Mouse_IsAutoLocking);
+	API_SCALL_BOOL(Mouse_GetAutoLock);
 }
 
 RuntimeScriptValue Sc_Mouse_SetAutoLock(const RuntimeScriptValue *params, int32_t param_count) {
@@ -577,40 +577,44 @@ RuntimeScriptValue Sc_Mouse_GetSpeed(const RuntimeScriptValue *params, int32_t p
 }
 
 RuntimeScriptValue Sc_Mouse_SetSpeed(const RuntimeScriptValue *params, int32_t param_count) {
-	ASSERT_VARIABLE_VALUE("Mouse::Speed");
+	ASSERT_PARAM_COUNT("Mouse::Speed", 1);
 	_GP(mouse).SetSpeed(params[0].FValue);
 	return RuntimeScriptValue();
 }
 
 void RegisterMouseAPI() {
-	ccAddExternalStaticFunction("Mouse::ChangeModeGraphic^2", Sc_ChangeCursorGraphic);
-	ccAddExternalStaticFunction("Mouse::ChangeModeHotspot^3", Sc_ChangeCursorHotspot);
-	ccAddExternalStaticFunction("Mouse::ChangeModeView^2", Sc_Mouse_ChangeModeView2);
-	ccAddExternalStaticFunction("Mouse::ChangeModeView^3", Sc_Mouse_ChangeModeView);
-	ccAddExternalStaticFunction("Mouse::Click^1", Sc_Mouse_Click);
-	ccAddExternalStaticFunction("Mouse::DisableMode^1", Sc_disable_cursor_mode);
-	ccAddExternalStaticFunction("Mouse::EnableMode^1", Sc_enable_cursor_mode);
-	ccAddExternalStaticFunction("Mouse::GetModeGraphic^1", Sc_Mouse_GetModeGraphic);
-	ccAddExternalStaticFunction("Mouse::IsButtonDown^1", Sc_IsButtonDown);
-	ccAddExternalStaticFunction("Mouse::IsModeEnabled^1", Sc_IsModeEnabled);
-	ccAddExternalStaticFunction("Mouse::SaveCursorUntilItLeaves^0", Sc_SaveCursorForLocationChange);
-	ccAddExternalStaticFunction("Mouse::SelectNextMode^0", Sc_SetNextCursor);
-	ccAddExternalStaticFunction("Mouse::SelectPreviousMode^0", Sc_SetPreviousCursor);
-	ccAddExternalStaticFunction("Mouse::SetBounds^4", Sc_SetMouseBounds);
-	ccAddExternalStaticFunction("Mouse::SetPosition^2", Sc_SetMousePosition);
-	ccAddExternalStaticFunction("Mouse::Update^0", Sc_RefreshMouse);
-	ccAddExternalStaticFunction("Mouse::UseDefaultGraphic^0", Sc_set_default_cursor);
-	ccAddExternalStaticFunction("Mouse::UseModeGraphic^1", Sc_set_mouse_cursor);
-	ccAddExternalStaticFunction("Mouse::get_AutoLock", Sc_Mouse_GetAutoLock);
-	ccAddExternalStaticFunction("Mouse::set_AutoLock", Sc_Mouse_SetAutoLock);
-	ccAddExternalStaticFunction("Mouse::get_ControlEnabled", Sc_Mouse_GetControlEnabled);
-	ccAddExternalStaticFunction("Mouse::set_ControlEnabled", Sc_Mouse_SetControlEnabled);
-	ccAddExternalStaticFunction("Mouse::get_Mode", Sc_GetCursorMode);
-	ccAddExternalStaticFunction("Mouse::set_Mode", Sc_set_cursor_mode);
-	ccAddExternalStaticFunction("Mouse::get_Speed", Sc_Mouse_GetSpeed);
-	ccAddExternalStaticFunction("Mouse::set_Speed", Sc_Mouse_SetSpeed);
-	ccAddExternalStaticFunction("Mouse::get_Visible", Sc_Mouse_GetVisible);
-	ccAddExternalStaticFunction("Mouse::set_Visible", Sc_Mouse_SetVisible);
+	ScFnRegister mouse_api[] = {
+		{"Mouse::ChangeModeGraphic^2", API_FN_PAIR(ChangeCursorGraphic)},
+		{"Mouse::ChangeModeHotspot^3", API_FN_PAIR(ChangeCursorHotspot)},
+		{"Mouse::ChangeModeView^2", API_FN_PAIR(Mouse_ChangeModeView2)},
+		{"Mouse::ChangeModeView^3", API_FN_PAIR(Mouse_ChangeModeView)},
+		{"Mouse::Click^1", Sc_Mouse_Click},
+		{"Mouse::DisableMode^1", API_FN_PAIR(disable_cursor_mode)},
+		{"Mouse::EnableMode^1", API_FN_PAIR(enable_cursor_mode)},
+		{"Mouse::GetModeGraphic^1", API_FN_PAIR(Mouse_GetModeGraphic)},
+		{"Mouse::IsButtonDown^1", API_FN_PAIR(IsButtonDown)},
+		{"Mouse::IsModeEnabled^1", API_FN_PAIR(IsModeEnabled)},
+		{"Mouse::SaveCursorUntilItLeaves^0", API_FN_PAIR(SaveCursorForLocationChange)},
+		{"Mouse::SelectNextMode^0", API_FN_PAIR(SetNextCursor)},
+		{"Mouse::SelectPreviousMode^0", API_FN_PAIR(SetPreviousCursor)},
+		{"Mouse::SetBounds^4", API_FN_PAIR(SetMouseBounds)},
+		{"Mouse::SetPosition^2", API_FN_PAIR(SetMousePosition)},
+		{"Mouse::Update^0", API_FN_PAIR(RefreshMouse)},
+		{"Mouse::UseDefaultGraphic^0", API_FN_PAIR(set_default_cursor)},
+		{"Mouse::UseModeGraphic^1", API_FN_PAIR(set_mouse_cursor)},
+		{"Mouse::get_AutoLock", API_FN_PAIR(Mouse_GetAutoLock)},
+		{"Mouse::set_AutoLock", API_FN_PAIR(Mouse_SetAutoLock)},
+		{"Mouse::get_ControlEnabled", Sc_Mouse_GetControlEnabled},
+		{"Mouse::set_ControlEnabled", Sc_Mouse_SetControlEnabled},
+		{"Mouse::get_Mode", API_FN_PAIR(GetCursorMode)},
+		{"Mouse::set_Mode", API_FN_PAIR(set_cursor_mode)},
+		{"Mouse::get_Speed", Sc_Mouse_GetSpeed},
+		{"Mouse::set_Speed", Sc_Mouse_SetSpeed},
+		{"Mouse::get_Visible", API_FN_PAIR(Mouse_GetVisible)},
+		{"Mouse::set_Visible", API_FN_PAIR(Mouse_SetVisible)},
+	};
+
+	ccAddExternalFunctions361(mouse_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index c0c5cfda089..456d005b8c5 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -646,33 +646,37 @@ void ScPl_Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, c
 	Overlay_SetText(scover, wii, fontid, clr, scsf_buffer);
 }
 
-
 void RegisterOverlayAPI() {
-	ccAddExternalStaticFunction("Overlay::CreateGraphical^4", Sc_Overlay_CreateGraphical4);
-	ccAddExternalStaticFunction("Overlay::CreateGraphical^5", Sc_Overlay_CreateGraphical);
-	ccAddExternalStaticFunction("Overlay::CreateTextual^106", Sc_Overlay_CreateTextual);
-	ccAddExternalStaticFunction("Overlay::CreateRoomGraphical^5", Sc_Overlay_CreateRoomGraphical);
-	ccAddExternalStaticFunction("Overlay::CreateRoomTextual^106", Sc_Overlay_CreateRoomTextual);
-	ccAddExternalObjectFunction("Overlay::SetText^104", Sc_Overlay_SetText);
-	ccAddExternalObjectFunction("Overlay::Remove^0", Sc_Overlay_Remove);
-	ccAddExternalObjectFunction("Overlay::get_Valid", Sc_Overlay_GetValid);
-	ccAddExternalObjectFunction("Overlay::get_X", Sc_Overlay_GetX);
-	ccAddExternalObjectFunction("Overlay::set_X", Sc_Overlay_SetX);
-	ccAddExternalObjectFunction("Overlay::get_Y", Sc_Overlay_GetY);
-	ccAddExternalObjectFunction("Overlay::set_Y", Sc_Overlay_SetY);
-	ccAddExternalObjectFunction("Overlay::get_Graphic", Sc_Overlay_GetGraphic);
-	ccAddExternalObjectFunction("Overlay::set_Graphic", Sc_Overlay_SetGraphic);
-	ccAddExternalObjectFunction("Overlay::get_InRoom", Sc_Overlay_InRoom);
-	ccAddExternalObjectFunction("Overlay::get_Width", Sc_Overlay_GetWidth);
-	ccAddExternalObjectFunction("Overlay::set_Width", Sc_Overlay_SetWidth);
-	ccAddExternalObjectFunction("Overlay::get_Height", Sc_Overlay_GetHeight);
-	ccAddExternalObjectFunction("Overlay::set_Height", Sc_Overlay_SetHeight);
-	ccAddExternalObjectFunction("Overlay::get_GraphicWidth", Sc_Overlay_GetGraphicWidth);
-	ccAddExternalObjectFunction("Overlay::get_GraphicHeight", Sc_Overlay_GetGraphicHeight);
-	ccAddExternalObjectFunction("Overlay::get_Transparency", Sc_Overlay_GetTransparency);
-	ccAddExternalObjectFunction("Overlay::set_Transparency", Sc_Overlay_SetTransparency);
-	ccAddExternalObjectFunction("Overlay::get_ZOrder", Sc_Overlay_GetZOrder);
-	ccAddExternalObjectFunction("Overlay::set_ZOrder", Sc_Overlay_SetZOrder);
+	ScFnRegister overlay_api[] = {
+		{"Overlay::CreateGraphical^4", API_FN_PAIR(Overlay_CreateGraphical4)},
+		{"Overlay::CreateGraphical^5", API_FN_PAIR(Overlay_CreateGraphical)},
+		{"Overlay::CreateTextual^106", Sc_Overlay_CreateTextual},
+		{"Overlay::CreateRoomGraphical^5", API_FN_PAIR(Overlay_CreateRoomGraphical)},
+		{"Overlay::CreateRoomTextual^106", Sc_Overlay_CreateRoomTextual},
+
+		{"Overlay::SetText^104", Sc_Overlay_SetText},
+		{"Overlay::Remove^0", API_FN_PAIR(Overlay_Remove)},
+		{"Overlay::get_Valid", API_FN_PAIR(Overlay_GetValid)},
+		{"Overlay::get_X", API_FN_PAIR(Overlay_GetX)},
+		{"Overlay::set_X", API_FN_PAIR(Overlay_SetX)},
+		{"Overlay::get_Y", API_FN_PAIR(Overlay_GetY)},
+		{"Overlay::set_Y", API_FN_PAIR(Overlay_SetY)},
+		{"Overlay::get_Graphic", API_FN_PAIR(Overlay_GetGraphic)},
+		{"Overlay::set_Graphic", API_FN_PAIR(Overlay_SetGraphic)},
+		{"Overlay::get_InRoom", API_FN_PAIR(Overlay_InRoom)},
+		{"Overlay::get_Width", API_FN_PAIR(Overlay_GetWidth)},
+		{"Overlay::set_Width", API_FN_PAIR(Overlay_SetWidth)},
+		{"Overlay::get_Height", API_FN_PAIR(Overlay_GetHeight)},
+		{"Overlay::set_Height", API_FN_PAIR(Overlay_SetHeight)},
+		{"Overlay::get_GraphicWidth", API_FN_PAIR(Overlay_GetGraphicWidth)},
+		{"Overlay::get_GraphicHeight", API_FN_PAIR(Overlay_GetGraphicHeight)},
+		{"Overlay::get_Transparency", API_FN_PAIR(Overlay_GetTransparency)},
+		{"Overlay::set_Transparency", API_FN_PAIR(Overlay_SetTransparency)},
+		{"Overlay::get_ZOrder", API_FN_PAIR(Overlay_GetZOrder)},
+		{"Overlay::set_ZOrder", API_FN_PAIR(Overlay_SetZOrder)},
+	};
+
+	ccAddExternalFunctions361(overlay_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/parser.cpp b/engines/ags/engine/ac/parser.cpp
index bc54eaebe62..6699fc8a05f 100644
--- a/engines/ags/engine/ac/parser.cpp
+++ b/engines/ags/engine/ac/parser.cpp
@@ -334,10 +334,14 @@ RuntimeScriptValue Sc_Said(const RuntimeScriptValue *params, int32_t param_count
 
 
 void RegisterParserAPI() {
-	ccAddExternalStaticFunction("Parser::FindWordID^1", Sc_Parser_FindWordID);
-	ccAddExternalStaticFunction("Parser::ParseText^1", Sc_ParseText);
-	ccAddExternalStaticFunction("Parser::SaidUnknownWord^0", Sc_Parser_SaidUnknownWord);
-	ccAddExternalStaticFunction("Parser::Said^1", Sc_Said);
+	ScFnRegister parser_api[] = {
+		{"Parser::FindWordID^1", API_FN_PAIR(Parser_FindWordID)},
+		{"Parser::ParseText^1", API_FN_PAIR(ParseText)},
+		{"Parser::SaidUnknownWord^0", API_FN_PAIR(Parser_SaidUnknownWord)},
+		{"Parser::Said^1", API_FN_PAIR(Said)},
+	};
+
+	ccAddExternalFunctions361(parser_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/region.cpp b/engines/ags/engine/ac/region.cpp
index 3bc33240b55..9ef70593a59 100644
--- a/engines/ags/engine/ac/region.cpp
+++ b/engines/ags/engine/ac/region.cpp
@@ -211,25 +211,29 @@ RuntimeScriptValue Sc_Region_GetTintLuminance(void *self, const RuntimeScriptVal
 	API_OBJCALL_INT(ScriptRegion, Region_GetTintLuminance);
 }
 
-
 void RegisterRegionAPI() {
-	ccAddExternalStaticFunction("Region::GetAtRoomXY^2", Sc_GetRegionAtRoom);
-	ccAddExternalStaticFunction("Region::GetAtScreenXY^2", Sc_GetRegionAtScreen);
-	ccAddExternalStaticFunction("Region::GetDrawingSurface", Sc_Region_GetDrawingSurface);
-	ccAddExternalObjectFunction("Region::Tint^4", Sc_Region_TintNoLum);
-	ccAddExternalObjectFunction("Region::Tint^5", Sc_Region_Tint);
-	ccAddExternalObjectFunction("Region::RunInteraction^1", Sc_Region_RunInteraction);
-	ccAddExternalObjectFunction("Region::get_Enabled", Sc_Region_GetEnabled);
-	ccAddExternalObjectFunction("Region::set_Enabled", Sc_Region_SetEnabled);
-	ccAddExternalObjectFunction("Region::get_ID", Sc_Region_GetID);
-	ccAddExternalObjectFunction("Region::get_LightLevel", Sc_Region_GetLightLevel);
-	ccAddExternalObjectFunction("Region::set_LightLevel", Sc_Region_SetLightLevel);
-	ccAddExternalObjectFunction("Region::get_TintEnabled", Sc_Region_GetTintEnabled);
-	ccAddExternalObjectFunction("Region::get_TintBlue", Sc_Region_GetTintBlue);
-	ccAddExternalObjectFunction("Region::get_TintGreen", Sc_Region_GetTintGreen);
-	ccAddExternalObjectFunction("Region::get_TintRed", Sc_Region_GetTintRed);
-	ccAddExternalObjectFunction("Region::get_TintSaturation", Sc_Region_GetTintSaturation);
-	ccAddExternalObjectFunction("Region::get_TintLuminance", Sc_Region_GetTintLuminance);
+	ScFnRegister region_api[] = {
+		{"Region::GetAtRoomXY^2", API_FN_PAIR(GetRegionAtRoom)},
+		{"Region::GetAtScreenXY^2", API_FN_PAIR(GetRegionAtScreen)},
+		{"Region::GetDrawingSurface", API_FN_PAIR(Region_GetDrawingSurface)},
+
+		{"Region::Tint^4", API_FN_PAIR(Region_TintNoLum)},
+		{"Region::Tint^5", API_FN_PAIR(Region_Tint)},
+		{"Region::RunInteraction^1", API_FN_PAIR(Region_RunInteraction)},
+		{"Region::get_Enabled", API_FN_PAIR(Region_GetEnabled)},
+		{"Region::set_Enabled", API_FN_PAIR(Region_SetEnabled)},
+		{"Region::get_ID", API_FN_PAIR(Region_GetID)},
+		{"Region::get_LightLevel", API_FN_PAIR(Region_GetLightLevel)},
+		{"Region::set_LightLevel", API_FN_PAIR(Region_SetLightLevel)},
+		{"Region::get_TintEnabled", API_FN_PAIR(Region_GetTintEnabled)},
+		{"Region::get_TintBlue", API_FN_PAIR(Region_GetTintBlue)},
+		{"Region::get_TintGreen", API_FN_PAIR(Region_GetTintGreen)},
+		{"Region::get_TintRed", API_FN_PAIR(Region_GetTintRed)},
+		{"Region::get_TintSaturation", API_FN_PAIR(Region_GetTintSaturation)},
+		{"Region::get_TintLuminance", API_FN_PAIR(Region_GetTintLuminance)},
+	};
+
+	ccAddExternalFunctions361(region_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index eb5e8fd72a3..a5530a32420 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1147,24 +1147,28 @@ RuntimeScriptValue Sc_Room_Exists(const RuntimeScriptValue *params, int32_t para
 
 
 void RegisterRoomAPI() {
-	ccAddExternalStaticFunction("Room::GetDrawingSurfaceForBackground^1",   Sc_Room_GetDrawingSurfaceForBackground);
-	ccAddExternalStaticFunction("Room::GetProperty^1",      Sc_Room_GetProperty);
-	ccAddExternalStaticFunction("Room::GetTextProperty^1",  Sc_Room_GetTextProperty);
-	ccAddExternalStaticFunction("Room::SetProperty^2",      Sc_Room_SetProperty);
-	ccAddExternalStaticFunction("Room::SetTextProperty^2",  Sc_Room_SetTextProperty);
-	ccAddExternalStaticFunction("Room::ProcessClick^3",     Sc_RoomProcessClick);
-	ccAddExternalStaticFunction("ProcessClick",             Sc_RoomProcessClick);
-	ccAddExternalStaticFunction("Room::get_BottomEdge",     Sc_Room_GetBottomEdge);
-	ccAddExternalStaticFunction("Room::get_ColorDepth",     Sc_Room_GetColorDepth);
-	ccAddExternalStaticFunction("Room::get_Height",         Sc_Room_GetHeight);
-	ccAddExternalStaticFunction("Room::get_LeftEdge",       Sc_Room_GetLeftEdge);
-	ccAddExternalStaticFunction("Room::geti_Messages",      Sc_Room_GetMessages);
-	ccAddExternalStaticFunction("Room::get_MusicOnLoad",    Sc_Room_GetMusicOnLoad);
-	ccAddExternalStaticFunction("Room::get_ObjectCount",    Sc_Room_GetObjectCount);
-	ccAddExternalStaticFunction("Room::get_RightEdge",      Sc_Room_GetRightEdge);
-	ccAddExternalStaticFunction("Room::get_TopEdge",        Sc_Room_GetTopEdge);
-	ccAddExternalStaticFunction("Room::get_Width",          Sc_Room_GetWidth);
-	ccAddExternalStaticFunction("Room::Exists",             Sc_Room_Exists);
+	ScFnRegister room_api[] = {
+		{"Room::GetDrawingSurfaceForBackground^1", API_FN_PAIR(Room_GetDrawingSurfaceForBackground)},
+		{"Room::GetProperty^1", API_FN_PAIR(Room_GetProperty)},
+		{"Room::GetTextProperty^1", API_FN_PAIR(Room_GetTextProperty)},
+		{"Room::SetProperty^2", API_FN_PAIR(Room_SetProperty)},
+		{"Room::SetTextProperty^2", API_FN_PAIR(Room_SetTextProperty)},
+		{"Room::ProcessClick^3", API_FN_PAIR(RoomProcessClick)},
+		{"ProcessClick", API_FN_PAIR(RoomProcessClick)},
+		{"Room::get_BottomEdge", API_FN_PAIR(Room_GetBottomEdge)},
+		{"Room::get_ColorDepth", API_FN_PAIR(Room_GetColorDepth)},
+		{"Room::get_Height", API_FN_PAIR(Room_GetHeight)},
+		{"Room::get_LeftEdge", API_FN_PAIR(Room_GetLeftEdge)},
+		{"Room::geti_Messages", API_FN_PAIR(Room_GetMessages)},
+		{"Room::get_MusicOnLoad", API_FN_PAIR(Room_GetMusicOnLoad)},
+		{"Room::get_ObjectCount", API_FN_PAIR(Room_GetObjectCount)},
+		{"Room::get_RightEdge", API_FN_PAIR(Room_GetRightEdge)},
+		{"Room::get_TopEdge", API_FN_PAIR(Room_GetTopEdge)},
+		{"Room::get_Width", API_FN_PAIR(Room_GetWidth)},
+		{"Room::Exists", API_FN_PAIR(Room_Exists)},
+	};
+
+	ccAddExternalFunctions361(room_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 34dc58d0c2c..a1737bb61a3 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -199,16 +199,20 @@ RuntimeScriptValue Sc_Screen_RoomToScreenPoint(const RuntimeScriptValue *params,
 }
 
 void RegisterScreenAPI() {
-	ccAddExternalStaticFunction("Screen::get_Height", Sc_Screen_GetScreenHeight);
-	ccAddExternalStaticFunction("Screen::get_Width", Sc_Screen_GetScreenWidth);
-	ccAddExternalStaticFunction("Screen::get_AutoSizeViewportOnRoomLoad", Sc_Screen_GetAutoSizeViewport);
-	ccAddExternalStaticFunction("Screen::set_AutoSizeViewportOnRoomLoad", Sc_Screen_SetAutoSizeViewport);
-	ccAddExternalStaticFunction("Screen::get_Viewport", Sc_Screen_GetViewport);
-	ccAddExternalStaticFunction("Screen::get_ViewportCount", Sc_Screen_GetViewportCount);
-	ccAddExternalStaticFunction("Screen::geti_Viewports", Sc_Screen_GetAnyViewport);
-	ccAddExternalStaticFunction("Screen::ScreenToRoomPoint^2", Sc_Screen_ScreenToRoomPoint2);
-	ccAddExternalStaticFunction("Screen::ScreenToRoomPoint^3", Sc_Screen_ScreenToRoomPoint);
-	ccAddExternalStaticFunction("Screen::RoomToScreenPoint", Sc_Screen_RoomToScreenPoint);
+	ScFnRegister screen_api[] = {
+		{"Screen::get_Height", API_FN_PAIR(Screen_GetScreenHeight)},
+		{"Screen::get_Width", API_FN_PAIR(Screen_GetScreenWidth)},
+		{"Screen::get_AutoSizeViewportOnRoomLoad", API_FN_PAIR(Screen_GetAutoSizeViewport)},
+		{"Screen::set_AutoSizeViewportOnRoomLoad", API_FN_PAIR(Screen_SetAutoSizeViewport)},
+		{"Screen::get_Viewport", API_FN_PAIR(Screen_GetViewport)},
+		{"Screen::get_ViewportCount", API_FN_PAIR(Screen_GetViewportCount)},
+		{"Screen::geti_Viewports", API_FN_PAIR(Screen_GetAnyViewport)},
+		{"Screen::ScreenToRoomPoint^2", API_FN_PAIR(Screen_ScreenToRoomPoint2)},
+		{"Screen::ScreenToRoomPoint^3", API_FN_PAIR(Screen_ScreenToRoomPoint)},
+		{"Screen::RoomToScreenPoint", API_FN_PAIR(Screen_RoomToScreenPoint)},
+	};
+
+	ccAddExternalFunctions361(screen_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index 37dcdb636d5..b1d18c358e2 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -282,34 +282,37 @@ RuntimeScriptValue Sc_Set_GetItemCount(void *self, const RuntimeScriptValue *par
 	API_OBJCALL_INT(ScriptSetBase, Set_GetItemCount);
 }
 
-RuntimeScriptValue Sc_Set_GetItemAsArray(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Set_GetItemsAsArray(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_OBJ(ScriptSetBase, void, _GP(globalDynamicArray), Set_GetItemsAsArray);
 }
 
-
-
 void RegisterContainerAPI() {
-	ccAddExternalStaticFunction("Dictionary::Create", Sc_Dict_Create);
-	ccAddExternalObjectFunction("Dictionary::Clear", Sc_Dict_Clear);
-	ccAddExternalObjectFunction("Dictionary::Contains", Sc_Dict_Contains);
-	ccAddExternalObjectFunction("Dictionary::Get", Sc_Dict_Get);
-	ccAddExternalObjectFunction("Dictionary::Remove", Sc_Dict_Remove);
-	ccAddExternalObjectFunction("Dictionary::Set", Sc_Dict_Set);
-	ccAddExternalObjectFunction("Dictionary::get_CompareStyle", Sc_Dict_GetCompareStyle);
-	ccAddExternalObjectFunction("Dictionary::get_SortStyle", Sc_Dict_GetSortStyle);
-	ccAddExternalObjectFunction("Dictionary::get_ItemCount", Sc_Dict_GetItemCount);
-	ccAddExternalObjectFunction("Dictionary::GetKeysAsArray", Sc_Dict_GetKeysAsArray);
-	ccAddExternalObjectFunction("Dictionary::GetValuesAsArray", Sc_Dict_GetValuesAsArray);
-
-	ccAddExternalStaticFunction("Set::Create", Sc_Set_Create);
-	ccAddExternalObjectFunction("Set::Add", Sc_Set_Add);
-	ccAddExternalObjectFunction("Set::Clear", Sc_Set_Clear);
-	ccAddExternalObjectFunction("Set::Contains", Sc_Set_Contains);
-	ccAddExternalObjectFunction("Set::Remove", Sc_Set_Remove);
-	ccAddExternalObjectFunction("Set::get_CompareStyle", Sc_Set_GetCompareStyle);
-	ccAddExternalObjectFunction("Set::get_SortStyle", Sc_Set_GetSortStyle);
-	ccAddExternalObjectFunction("Set::get_ItemCount", Sc_Set_GetItemCount);
-	ccAddExternalObjectFunction("Set::GetItemsAsArray", Sc_Set_GetItemAsArray);
+	ScFnRegister container_api[] = {
+		// Dictionary
+		{"Dictionary::Create", API_FN_PAIR(Dict_Create)},
+		{"Dictionary::Clear", API_FN_PAIR(Dict_Clear)},
+		{"Dictionary::Contains", API_FN_PAIR(Dict_Contains)},
+		{"Dictionary::Get", API_FN_PAIR(Dict_Get)},
+		{"Dictionary::Remove", API_FN_PAIR(Dict_Remove)},
+		{"Dictionary::Set", API_FN_PAIR(Dict_Set)},
+		{"Dictionary::get_CompareStyle", API_FN_PAIR(Dict_GetCompareStyle)},
+		{"Dictionary::get_SortStyle", API_FN_PAIR(Dict_GetSortStyle)},
+		{"Dictionary::get_ItemCount", API_FN_PAIR(Dict_GetItemCount)},
+		{"Dictionary::GetKeysAsArray", API_FN_PAIR(Dict_GetKeysAsArray)},
+		{"Dictionary::GetValuesAsArray", API_FN_PAIR(Dict_GetValuesAsArray)},
+		// Set
+		{"Set::Create", API_FN_PAIR(Set_Create)},
+		{"Set::Add", API_FN_PAIR(Set_Add)},
+		{"Set::Clear", API_FN_PAIR(Set_Clear)},
+		{"Set::Contains", API_FN_PAIR(Set_Contains)},
+		{"Set::Remove", API_FN_PAIR(Set_Remove)},
+		{"Set::get_CompareStyle", API_FN_PAIR(Set_GetCompareStyle)},
+		{"Set::get_SortStyle", API_FN_PAIR(Set_GetSortStyle)},
+		{"Set::get_ItemCount", API_FN_PAIR(Set_GetItemCount)},
+		{"Set::GetItemsAsArray", API_FN_PAIR(Set_GetItemsAsArray)},
+	};
+
+	ccAddExternalFunctions361(container_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/slider.cpp b/engines/ags/engine/ac/slider.cpp
index 2b7145b2cd6..4bc525aac3f 100644
--- a/engines/ags/engine/ac/slider.cpp
+++ b/engines/ags/engine/ac/slider.cpp
@@ -183,18 +183,22 @@ RuntimeScriptValue Sc_Slider_SetValue(void *self, const RuntimeScriptValue *para
 
 
 void RegisterSliderAPI() {
-	ccAddExternalObjectFunction("Slider::get_BackgroundGraphic", Sc_Slider_GetBackgroundGraphic);
-	ccAddExternalObjectFunction("Slider::set_BackgroundGraphic", Sc_Slider_SetBackgroundGraphic);
-	ccAddExternalObjectFunction("Slider::get_HandleGraphic", Sc_Slider_GetHandleGraphic);
-	ccAddExternalObjectFunction("Slider::set_HandleGraphic", Sc_Slider_SetHandleGraphic);
-	ccAddExternalObjectFunction("Slider::get_HandleOffset", Sc_Slider_GetHandleOffset);
-	ccAddExternalObjectFunction("Slider::set_HandleOffset", Sc_Slider_SetHandleOffset);
-	ccAddExternalObjectFunction("Slider::get_Max", Sc_Slider_GetMax);
-	ccAddExternalObjectFunction("Slider::set_Max", Sc_Slider_SetMax);
-	ccAddExternalObjectFunction("Slider::get_Min", Sc_Slider_GetMin);
-	ccAddExternalObjectFunction("Slider::set_Min", Sc_Slider_SetMin);
-	ccAddExternalObjectFunction("Slider::get_Value", Sc_Slider_GetValue);
-	ccAddExternalObjectFunction("Slider::set_Value", Sc_Slider_SetValue);
+	ScFnRegister slider_api[] = {
+		{"Slider::get_BackgroundGraphic", API_FN_PAIR(Slider_GetBackgroundGraphic)},
+		{"Slider::set_BackgroundGraphic", API_FN_PAIR(Slider_SetBackgroundGraphic)},
+		{"Slider::get_HandleGraphic", API_FN_PAIR(Slider_GetHandleGraphic)},
+		{"Slider::set_HandleGraphic", API_FN_PAIR(Slider_SetHandleGraphic)},
+		{"Slider::get_HandleOffset", API_FN_PAIR(Slider_GetHandleOffset)},
+		{"Slider::set_HandleOffset", API_FN_PAIR(Slider_SetHandleOffset)},
+		{"Slider::get_Max", API_FN_PAIR(Slider_GetMax)},
+		{"Slider::set_Max", API_FN_PAIR(Slider_SetMax)},
+		{"Slider::get_Min", API_FN_PAIR(Slider_GetMin)},
+		{"Slider::set_Min", API_FN_PAIR(Slider_SetMin)},
+		{"Slider::get_Value", API_FN_PAIR(Slider_GetValue)},
+		{"Slider::set_Value", API_FN_PAIR(Slider_SetValue)},
+	};
+
+	ccAddExternalFunctions361(slider_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp
index 6d4bc195e51..acab8b1e366 100644
--- a/engines/ags/engine/ac/speech.cpp
+++ b/engines/ags/engine/ac/speech.cpp
@@ -351,35 +351,41 @@ RuntimeScriptValue Sc_Speech_GetPortraitOverlay(const RuntimeScriptValue *params
 extern RuntimeScriptValue Sc_SetVoiceMode(const RuntimeScriptValue *params, int32_t param_count);
 
 void RegisterSpeechAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*compat_api*/) {
-	ccAddExternalStaticFunction("Speech::get_AnimationStopTimeMargin", Sc_Speech_GetAnimationStopTimeMargin);
-	ccAddExternalStaticFunction("Speech::set_AnimationStopTimeMargin", Sc_Speech_SetAnimationStopTimeMargin);
-	ccAddExternalStaticFunction("Speech::get_CustomPortraitPlacement", Sc_Speech_GetCustomPortraitPlacement);
-	ccAddExternalStaticFunction("Speech::set_CustomPortraitPlacement", Sc_Speech_SetCustomPortraitPlacement);
-	ccAddExternalStaticFunction("Speech::get_DisplayPostTimeMs", Sc_Speech_GetDisplayPostTimeMs);
-	ccAddExternalStaticFunction("Speech::set_DisplayPostTimeMs", Sc_Speech_SetDisplayPostTimeMs);
-	ccAddExternalStaticFunction("Speech::get_GlobalSpeechAnimationDelay", Sc_Speech_GetGlobalSpeechAnimationDelay);
-	ccAddExternalStaticFunction("Speech::set_GlobalSpeechAnimationDelay", Sc_Speech_SetGlobalSpeechAnimationDelay);
-	ccAddExternalStaticFunction("Speech::get_PortraitOverlay", Sc_Speech_GetPortraitOverlay);
-	ccAddExternalStaticFunction("Speech::get_PortraitXOffset", Sc_Speech_GetPortraitXOffset);
-	ccAddExternalStaticFunction("Speech::set_PortraitXOffset", Sc_Speech_SetPortraitXOffset);
-	ccAddExternalStaticFunction("Speech::get_PortraitY", Sc_Speech_GetPortraitY);
-	ccAddExternalStaticFunction("Speech::set_PortraitY", Sc_Speech_SetPortraitY);
-	ccAddExternalStaticFunction("Speech::get_SkipKey", Sc_Speech_GetSkipKey);
-	ccAddExternalStaticFunction("Speech::set_SkipKey", Sc_Speech_SetSkipKey);
-	ccAddExternalStaticFunction("Speech::get_SkipStyle", Sc_Speech_GetSkipStyle);
-	ccAddExternalStaticFunction("Speech::set_SkipStyle", Sc_SetSkipSpeech);
-	ccAddExternalStaticFunction("Speech::get_Style", Sc_Speech_GetStyle);
-	ccAddExternalStaticFunction("Speech::set_Style", Sc_SetSpeechStyle);
-	ccAddExternalStaticFunction("Speech::get_TextAlignment", Sc_Speech_GetTextAlignment);
+	ScFnRegister speech_api[] = {
+		{"Speech::get_AnimationStopTimeMargin", API_FN_PAIR(Speech_GetAnimationStopTimeMargin)},
+		{"Speech::set_AnimationStopTimeMargin", API_FN_PAIR(Speech_SetAnimationStopTimeMargin)},
+		{"Speech::get_CustomPortraitPlacement", API_FN_PAIR(Speech_GetCustomPortraitPlacement)},
+		{"Speech::set_CustomPortraitPlacement", API_FN_PAIR(Speech_SetCustomPortraitPlacement)},
+		{"Speech::get_DisplayPostTimeMs", API_FN_PAIR(Speech_GetDisplayPostTimeMs)},
+		{"Speech::set_DisplayPostTimeMs", API_FN_PAIR(Speech_SetDisplayPostTimeMs)},
+		{"Speech::get_GlobalSpeechAnimationDelay", API_FN_PAIR(Speech_GetGlobalSpeechAnimationDelay)},
+		{"Speech::set_GlobalSpeechAnimationDelay", API_FN_PAIR(Speech_SetGlobalSpeechAnimationDelay)},
+		{"Speech::get_PortraitOverlay", API_FN_PAIR(Speech_GetPortraitOverlay)},
+		{"Speech::get_PortraitXOffset", API_FN_PAIR(Speech_GetPortraitXOffset)},
+		{"Speech::set_PortraitXOffset", API_FN_PAIR(Speech_SetPortraitXOffset)},
+		{"Speech::get_PortraitY", API_FN_PAIR(Speech_GetPortraitY)},
+		{"Speech::set_PortraitY", API_FN_PAIR(Speech_SetPortraitY)},
+		{"Speech::get_SkipKey", API_FN_PAIR(Speech_GetSkipKey)},
+		{"Speech::set_SkipKey", API_FN_PAIR(Speech_SetSkipKey)},
+		{"Speech::get_SkipStyle", Sc_Speech_GetSkipStyle},
+		{"Speech::set_SkipStyle", API_FN_PAIR(SetSkipSpeech)},
+		{"Speech::get_Style", API_FN_PAIR(Speech_GetStyle)},
+		{"Speech::set_Style", API_FN_PAIR(SetSpeechStyle)},
+		{"Speech::get_TextAlignment", API_FN_PAIR(Speech_GetTextAlignment)},
+		{"Speech::get_TextOverlay", API_FN_PAIR(Speech_GetTextOverlay)},
+		{"Speech::get_UseGlobalSpeechAnimationDelay", API_FN_PAIR(Speech_GetUseGlobalSpeechAnimationDelay)},
+		{"Speech::set_UseGlobalSpeechAnimationDelay", API_FN_PAIR(Speech_SetUseGlobalSpeechAnimationDelay)},
+		{"Speech::get_VoiceMode", Sc_Speech_GetVoiceMode},
+		{"Speech::set_VoiceMode", API_FN_PAIR(SetVoiceMode)},
+	};
+
+	ccAddExternalFunctions361(speech_api);
+
+	// Few functions have to be selected based on API level
 	if (base_api < kScriptAPI_v350)
-		ccAddExternalStaticFunction("Speech::set_TextAlignment", Sc_Speech_SetTextAlignment_Old);
+		ccAddExternalStaticFunction361("Speech::set_TextAlignment", API_FN_PAIR(Speech_SetTextAlignment_Old));
 	else
-		ccAddExternalStaticFunction("Speech::set_TextAlignment", Sc_Speech_SetTextAlignment);
-	ccAddExternalStaticFunction("Speech::get_TextOverlay", Sc_Speech_GetTextOverlay);
-	ccAddExternalStaticFunction("Speech::get_UseGlobalSpeechAnimationDelay", Sc_Speech_GetUseGlobalSpeechAnimationDelay);
-	ccAddExternalStaticFunction("Speech::set_UseGlobalSpeechAnimationDelay", Sc_Speech_SetUseGlobalSpeechAnimationDelay);
-	ccAddExternalStaticFunction("Speech::get_VoiceMode", Sc_Speech_GetVoiceMode);
-	ccAddExternalStaticFunction("Speech::set_VoiceMode", Sc_SetVoiceMode);
+		ccAddExternalStaticFunction361("Speech::set_TextAlignment", API_FN_PAIR(Speech_SetTextAlignment));
 
 	/* -- Don't register more unsafe plugin symbols until new plugin interface is designed --*/
 }
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 4b73d893dfa..5ff69ab2551 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -442,26 +442,31 @@ RuntimeScriptValue Sc_String_GetLength(void *self, const RuntimeScriptValue *par
 //=============================================================================
 
 void RegisterStringAPI() {
-	ccAddExternalStaticFunction("String::IsNullOrEmpty^1", Sc_String_IsNullOrEmpty);
-	ccAddExternalObjectFunction("String::Append^1", Sc_String_Append);
-	ccAddExternalObjectFunction("String::AppendChar^1", Sc_String_AppendChar);
-	ccAddExternalObjectFunction("String::CompareTo^2", Sc_String_CompareTo);
-	ccAddExternalObjectFunction("String::Contains^1", Sc_StrContains);
-	ccAddExternalObjectFunction("String::Copy^0", Sc_String_Copy);
-	ccAddExternalObjectFunction("String::EndsWith^2", Sc_String_EndsWith);
-	ccAddExternalStaticFunction("String::Format^101", Sc_String_Format);
-	ccAddExternalObjectFunction("String::IndexOf^1", Sc_StrContains);
-	ccAddExternalObjectFunction("String::LowerCase^0", Sc_String_LowerCase);
-	ccAddExternalObjectFunction("String::Replace^3", Sc_String_Replace);
-	ccAddExternalObjectFunction("String::ReplaceCharAt^2", Sc_String_ReplaceCharAt);
-	ccAddExternalObjectFunction("String::StartsWith^2", Sc_String_StartsWith);
-	ccAddExternalObjectFunction("String::Substring^2", Sc_String_Substring);
-	ccAddExternalObjectFunction("String::Truncate^1", Sc_String_Truncate);
-	ccAddExternalObjectFunction("String::UpperCase^0", Sc_String_UpperCase);
-	ccAddExternalObjectFunction("String::get_AsFloat", Sc_StringToFloat);
-	ccAddExternalObjectFunction("String::get_AsInt", Sc_StringToInt);
-	ccAddExternalObjectFunction("String::geti_Chars", Sc_String_GetChars);
-	ccAddExternalObjectFunction("String::get_Length", Sc_String_GetLength);
+	ScFnRegister string_api[] = {
+		{"String::IsNullOrEmpty^1", API_FN_PAIR(String_IsNullOrEmpty)},
+		{"String::Format^101", Sc_String_Format},
+
+		{"String::Append^1", API_FN_PAIR(String_Append)},
+		{"String::AppendChar^1", API_FN_PAIR(String_AppendChar)},
+		{"String::CompareTo^2", API_FN_PAIR(String_CompareTo)},
+		{"String::Contains^1", API_FN_PAIR(StrContains)},
+		{"String::Copy^0", API_FN_PAIR(String_Copy)},
+		{"String::EndsWith^2", API_FN_PAIR(String_EndsWith)},
+		{"String::IndexOf^1", API_FN_PAIR(StrContains)},
+		{"String::LowerCase^0", API_FN_PAIR(String_LowerCase)},
+		{"String::Replace^3", API_FN_PAIR(String_Replace)},
+		{"String::ReplaceCharAt^2", API_FN_PAIR(String_ReplaceCharAt)},
+		{"String::StartsWith^2", API_FN_PAIR(String_StartsWith)},
+		{"String::Substring^2", API_FN_PAIR(String_Substring)},
+		{"String::Truncate^1", API_FN_PAIR(String_Truncate)},
+		{"String::UpperCase^0", API_FN_PAIR(String_UpperCase)},
+		{"String::get_AsFloat", API_FN_PAIR(StringToFloat)},
+		{"String::get_AsInt", API_FN_PAIR(StringToInt)},
+		{"String::geti_Chars", API_FN_PAIR(String_GetChars)},
+		{"String::get_Length", API_FN_PAIR(String_GetLength)},
+	};
+
+	ccAddExternalFunctions361(string_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index 84e11d31661..f8acbe5eedb 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -53,7 +53,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-bool System_HasInputFocus() {
+bool System_GetHasInputFocus() {
 	return !_G(switched_away);
 }
 
@@ -256,7 +256,7 @@ RuntimeScriptValue Sc_System_GetHardwareAcceleration(const RuntimeScriptValue *p
 }
 
 RuntimeScriptValue Sc_System_GetHasInputFocus(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_BOOL(System_HasInputFocus);
+	API_SCALL_BOOL(System_GetHasInputFocus);
 }
 
 // int ()
@@ -367,36 +367,40 @@ void ScPl_System_Log(CharacterInfo *chaa, int message_type, const char *texx, ..
 }
 
 void RegisterSystemAPI() {
-	ccAddExternalStaticFunction("System::get_AudioChannelCount", Sc_System_GetAudioChannelCount);
-	ccAddExternalStaticFunction("System::geti_AudioChannels", Sc_System_GetAudioChannels);
-	ccAddExternalStaticFunction("System::get_CapsLock", Sc_System_GetCapsLock);
-	ccAddExternalStaticFunction("System::get_ColorDepth", Sc_System_GetColorDepth);
-	ccAddExternalStaticFunction("System::get_Gamma", Sc_System_GetGamma);
-	ccAddExternalStaticFunction("System::set_Gamma", Sc_System_SetGamma);
-	ccAddExternalStaticFunction("System::get_HardwareAcceleration", Sc_System_GetHardwareAcceleration);
-	ccAddExternalStaticFunction("System::get_HasInputFocus", Sc_System_GetHasInputFocus);
-	ccAddExternalStaticFunction("System::get_NumLock", Sc_System_GetNumLock);
-	ccAddExternalStaticFunction("System::get_OperatingSystem", Sc_System_GetOS);
-	ccAddExternalStaticFunction("System::get_RenderAtScreenResolution", Sc_System_GetRenderAtScreenResolution);
-	ccAddExternalStaticFunction("System::set_RenderAtScreenResolution", Sc_System_SetRenderAtScreenResolution);
-	ccAddExternalStaticFunction("System::get_RuntimeInfo", Sc_System_GetRuntimeInfo);
-	ccAddExternalStaticFunction("System::get_ScreenHeight", Sc_System_GetScreenHeight);
-	ccAddExternalStaticFunction("System::get_ScreenWidth", Sc_System_GetScreenWidth);
-	ccAddExternalStaticFunction("System::get_ScrollLock", Sc_System_GetScrollLock);
-	ccAddExternalStaticFunction("System::get_SupportsGammaControl", Sc_System_GetSupportsGammaControl);
-	ccAddExternalStaticFunction("System::get_Version", Sc_System_GetVersion);
-	ccAddExternalStaticFunction("SystemInfo::get_Version", Sc_System_GetVersion);
-	ccAddExternalStaticFunction("System::get_ViewportHeight", Sc_System_GetViewportHeight);
-	ccAddExternalStaticFunction("System::get_ViewportWidth", Sc_System_GetViewportWidth);
-	ccAddExternalStaticFunction("System::get_Volume", Sc_System_GetVolume);
-	ccAddExternalStaticFunction("System::set_Volume", Sc_System_SetVolume);
-	ccAddExternalStaticFunction("System::get_VSync", Sc_System_GetVsync);
-	ccAddExternalStaticFunction("System::set_VSync", Sc_System_SetVsync);
-	ccAddExternalStaticFunction("System::get_Windowed", Sc_System_GetWindowed);
-	ccAddExternalStaticFunction("System::set_Windowed", Sc_System_SetWindowed);
-
-	ccAddExternalStaticFunction("System::SaveConfigToFile", Sc_System_SaveConfigToFile);
-	ccAddExternalStaticFunction("System::Log^102", Sc_System_Log);
+	ScFnRegister system_api[] = {
+		{"System::get_AudioChannelCount", API_FN_PAIR(System_GetAudioChannelCount)},
+		{"System::geti_AudioChannels", API_FN_PAIR(System_GetAudioChannels)},
+		{"System::get_CapsLock", API_FN_PAIR(System_GetCapsLock)},
+		{"System::get_ColorDepth", API_FN_PAIR(System_GetColorDepth)},
+		{"System::get_Gamma", API_FN_PAIR(System_GetGamma)},
+		{"System::set_Gamma", API_FN_PAIR(System_SetGamma)},
+		{"System::get_HardwareAcceleration", API_FN_PAIR(System_GetHardwareAcceleration)},
+		{"System::get_HasInputFocus", API_FN_PAIR(System_GetHasInputFocus)},
+		{"System::get_NumLock", API_FN_PAIR(System_GetNumLock)},
+		{"System::get_OperatingSystem", API_FN_PAIR(System_GetOS)},
+		{"System::get_RenderAtScreenResolution", API_FN_PAIR(System_GetRenderAtScreenResolution)},
+		{"System::set_RenderAtScreenResolution", API_FN_PAIR(System_SetRenderAtScreenResolution)},
+		{"System::get_RuntimeInfo", API_FN_PAIR(System_GetRuntimeInfo)},
+		{"System::get_ScreenHeight", API_FN_PAIR(System_GetScreenHeight)},
+		{"System::get_ScreenWidth", API_FN_PAIR(System_GetScreenWidth)},
+		{"System::get_ScrollLock", API_FN_PAIR(System_GetScrollLock)},
+		{"System::get_SupportsGammaControl", API_FN_PAIR(System_GetSupportsGammaControl)},
+		{"System::get_Version", API_FN_PAIR(System_GetVersion)},
+		{"SystemInfo::get_Version", API_FN_PAIR(System_GetVersion)},
+		{"System::get_ViewportHeight", API_FN_PAIR(System_GetViewportHeight)},
+		{"System::get_ViewportWidth", API_FN_PAIR(System_GetViewportWidth)},
+		{"System::get_Volume", API_FN_PAIR(System_GetVolume)},
+		{"System::set_Volume", API_FN_PAIR(System_SetVolume)},
+		{"System::get_VSync", API_FN_PAIR(System_GetVsync)},
+		{"System::set_VSync", API_FN_PAIR(System_SetVsync)},
+		{"System::get_Windowed", API_FN_PAIR(System_GetWindowed)},
+		{"System::set_Windowed", API_FN_PAIR(System_SetWindowed)},
+
+		{"System::SaveConfigToFile", Sc_System_SaveConfigToFile},
+		{"System::Log^102", Sc_System_Log},
+	};
+
+	ccAddExternalFunctions361(system_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/textbox.cpp b/engines/ags/engine/ac/textbox.cpp
index 7aa110b90ac..943d869d407 100644
--- a/engines/ags/engine/ac/textbox.cpp
+++ b/engines/ags/engine/ac/textbox.cpp
@@ -136,16 +136,20 @@ RuntimeScriptValue Sc_TextBox_SetTextColor(void *self, const RuntimeScriptValue
 
 
 void RegisterTextBoxAPI() {
-	ccAddExternalObjectFunction("TextBox::GetText^1", Sc_TextBox_GetText);
-	ccAddExternalObjectFunction("TextBox::SetText^1", Sc_TextBox_SetText);
-	ccAddExternalObjectFunction("TextBox::get_Font", Sc_TextBox_GetFont);
-	ccAddExternalObjectFunction("TextBox::set_Font", Sc_TextBox_SetFont);
-	ccAddExternalObjectFunction("TextBox::get_ShowBorder", Sc_TextBox_GetShowBorder);
-	ccAddExternalObjectFunction("TextBox::set_ShowBorder", Sc_TextBox_SetShowBorder);
-	ccAddExternalObjectFunction("TextBox::get_Text", Sc_TextBox_GetText_New);
-	ccAddExternalObjectFunction("TextBox::set_Text", Sc_TextBox_SetText);
-	ccAddExternalObjectFunction("TextBox::get_TextColor", Sc_TextBox_GetTextColor);
-	ccAddExternalObjectFunction("TextBox::set_TextColor", Sc_TextBox_SetTextColor);
+	ScFnRegister textbox_api[] = {
+		{"TextBox::GetText^1", API_FN_PAIR(TextBox_GetText)},
+		{"TextBox::SetText^1", API_FN_PAIR(TextBox_SetText)},
+		{"TextBox::get_Font", API_FN_PAIR(TextBox_GetFont)},
+		{"TextBox::set_Font", API_FN_PAIR(TextBox_SetFont)},
+		{"TextBox::get_ShowBorder", API_FN_PAIR(TextBox_GetShowBorder)},
+		{"TextBox::set_ShowBorder", API_FN_PAIR(TextBox_SetShowBorder)},
+		{"TextBox::get_Text", API_FN_PAIR(TextBox_GetText_New)},
+		{"TextBox::set_Text", API_FN_PAIR(TextBox_SetText)},
+		{"TextBox::get_TextColor", API_FN_PAIR(TextBox_GetTextColor)},
+		{"TextBox::set_TextColor", API_FN_PAIR(TextBox_SetTextColor)},
+	};
+
+	ccAddExternalFunctions361(textbox_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index b8a423790e2..a754a7bfb2a 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -247,17 +247,21 @@ RuntimeScriptValue Sc_ViewFrame_GetView(void *self, const RuntimeScriptValue *pa
 
 
 void RegisterViewFrameAPI() {
-	ccAddExternalObjectFunction("ViewFrame::get_Flipped", Sc_ViewFrame_GetFlipped);
-	ccAddExternalObjectFunction("ViewFrame::get_Frame", Sc_ViewFrame_GetFrame);
-	ccAddExternalObjectFunction("ViewFrame::get_Graphic", Sc_ViewFrame_GetGraphic);
-	ccAddExternalObjectFunction("ViewFrame::set_Graphic", Sc_ViewFrame_SetGraphic);
-	ccAddExternalObjectFunction("ViewFrame::get_LinkedAudio", Sc_ViewFrame_GetLinkedAudio);
-	ccAddExternalObjectFunction("ViewFrame::set_LinkedAudio", Sc_ViewFrame_SetLinkedAudio);
-	ccAddExternalObjectFunction("ViewFrame::get_Loop", Sc_ViewFrame_GetLoop);
-	ccAddExternalObjectFunction("ViewFrame::get_Sound", Sc_ViewFrame_GetSound);
-	ccAddExternalObjectFunction("ViewFrame::set_Sound", Sc_ViewFrame_SetSound);
-	ccAddExternalObjectFunction("ViewFrame::get_Speed", Sc_ViewFrame_GetSpeed);
-	ccAddExternalObjectFunction("ViewFrame::get_View", Sc_ViewFrame_GetView);
+	ScFnRegister viewframe_api[] = {
+		{"ViewFrame::get_Flipped", API_FN_PAIR(ViewFrame_GetFlipped)},
+		{"ViewFrame::get_Frame", API_FN_PAIR(ViewFrame_GetFrame)},
+		{"ViewFrame::get_Graphic", API_FN_PAIR(ViewFrame_GetGraphic)},
+		{"ViewFrame::set_Graphic", API_FN_PAIR(ViewFrame_SetGraphic)},
+		{"ViewFrame::get_LinkedAudio", API_FN_PAIR(ViewFrame_GetLinkedAudio)},
+		{"ViewFrame::set_LinkedAudio", API_FN_PAIR(ViewFrame_SetLinkedAudio)},
+		{"ViewFrame::get_Loop", API_FN_PAIR(ViewFrame_GetLoop)},
+		{"ViewFrame::get_Sound", API_FN_PAIR(ViewFrame_GetSound)},
+		{"ViewFrame::set_Sound", API_FN_PAIR(ViewFrame_SetSound)},
+		{"ViewFrame::get_Speed", API_FN_PAIR(ViewFrame_GetSpeed)},
+		{"ViewFrame::get_View", API_FN_PAIR(ViewFrame_GetView)},
+	};
+
+	ccAddExternalFunctions361(viewframe_api);
 }
 
 } // namespace AGS3


Commit: 20706c475f7fb7c1828fe99212d667359db1cf4f
    https://github.com/scummvm/scummvm/commit/20706c475f7fb7c1828fe99212d667359db1cf4f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Complete move to new API registering

This complements the previous commits related to the upstream commit
9221ea54c1a81b9f37829d7fafc90ac2806d4eb1:
Engine: replaced script API registration with cleaner array tables
The benefits are:
1. Cleaner registration view, an array initializer instead of myriads of function calls.
2. Implicit distinction between static and object member functions based on prototype
detection, no need to explicitly pass a parameter or call respective registration function.
3. Enforced function registration for plugins, now combined with the regular registration
in one table entry, so more difficult to skip plugin variant

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/object.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 00a85eaaf4e..0359f8f84e0 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -3105,7 +3105,7 @@ RuntimeScriptValue Sc_Character_SetIdleView(void *self, const RuntimeScriptValue
 	API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetIdleView);
 }
 
-RuntimeScriptValue Sc_Character_HasExplicitLight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_Character_GetHasExplicitLight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_BOOL(CharacterInfo, Character_GetHasExplicitLight);
 }
 
@@ -3631,168 +3631,174 @@ void ScPl_Character_Think(CharacterInfo *chaa, const char *texx, ...) {
 }
 
 void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_api */) {
-	ccAddExternalObjectFunction("Character::AddInventory^2",            Sc_Character_AddInventory);
-	ccAddExternalObjectFunction("Character::AddWaypoint^2",             Sc_Character_AddWaypoint);
-	ccAddExternalObjectFunction("Character::Animate^5",                 Sc_Character_Animate5);
-	ccAddExternalObjectFunction("Character::Animate^6",                 Sc_Character_Animate6);
-	ccAddExternalObjectFunction("Character::Animate^7",                 Sc_Character_Animate);
-	ccAddExternalObjectFunction("Character::ChangeRoom^3",              Sc_Character_ChangeRoom);
-	ccAddExternalObjectFunction("Character::ChangeRoom^4",              Sc_Character_ChangeRoomSetLoop);
-	ccAddExternalObjectFunction("Character::ChangeRoomAutoPosition^2",  Sc_Character_ChangeRoomAutoPosition);
-	ccAddExternalObjectFunction("Character::ChangeView^1",              Sc_Character_ChangeView);
-	ccAddExternalObjectFunction("Character::FaceCharacter^2",           Sc_Character_FaceCharacter);
-	ccAddExternalObjectFunction("Character::FaceDirection^2",           Sc_Character_FaceDirection);
-	ccAddExternalObjectFunction("Character::FaceLocation^3",            Sc_Character_FaceLocation);
-	ccAddExternalObjectFunction("Character::FaceObject^2",              Sc_Character_FaceObject);
-	ccAddExternalObjectFunction("Character::FollowCharacter^3",         Sc_Character_FollowCharacter);
-	ccAddExternalObjectFunction("Character::GetProperty^1",             Sc_Character_GetProperty);
-	ccAddExternalObjectFunction("Character::GetPropertyText^2",         Sc_Character_GetPropertyText);
-	ccAddExternalObjectFunction("Character::GetTextProperty^1",         Sc_Character_GetTextProperty);
-	ccAddExternalObjectFunction("Character::SetProperty^2",             Sc_Character_SetProperty);
-	ccAddExternalObjectFunction("Character::SetTextProperty^2",         Sc_Character_SetTextProperty);
-	ccAddExternalObjectFunction("Character::HasInventory^1",            Sc_Character_HasInventory);
-	ccAddExternalObjectFunction("Character::IsCollidingWithChar^1",     Sc_Character_IsCollidingWithChar);
-	ccAddExternalObjectFunction("Character::IsCollidingWithObject^1",   Sc_Character_IsCollidingWithObject);
-	ccAddExternalObjectFunction("Character::IsInteractionAvailable^1",  Sc_Character_IsInteractionAvailable);
-	ccAddExternalObjectFunction("Character::LockView^1",                Sc_Character_LockView);
-	ccAddExternalObjectFunction("Character::LockView^2",                Sc_Character_LockViewEx);
+	ScFnRegister character_api[] = {
+		{"Character::GetAtRoomXY^2", API_FN_PAIR(GetCharacterAtRoom)},
+		{"Character::GetAtScreenXY^2", API_FN_PAIR(GetCharacterAtScreen)},
+
+		{"Character::AddInventory^2", API_FN_PAIR(Character_AddInventory)},
+		{"Character::AddWaypoint^2", API_FN_PAIR(Character_AddWaypoint)},
+		{"Character::Animate^5", API_FN_PAIR(Character_Animate5)},
+		{"Character::Animate^6", API_FN_PAIR(Character_Animate6)},
+		{"Character::Animate^7", API_FN_PAIR(Character_Animate)},
+		{"Character::ChangeRoom^3", API_FN_PAIR(Character_ChangeRoom)},
+		{"Character::ChangeRoom^4", API_FN_PAIR(Character_ChangeRoomSetLoop)},
+		{"Character::ChangeRoomAutoPosition^2", API_FN_PAIR(Character_ChangeRoomAutoPosition)},
+		{"Character::ChangeView^1", API_FN_PAIR(Character_ChangeView)},
+		{"Character::FaceCharacter^2", API_FN_PAIR(Character_FaceCharacter)},
+		{"Character::FaceDirection^2", API_FN_PAIR(Character_FaceDirection)},
+		{"Character::FaceLocation^3", API_FN_PAIR(Character_FaceLocation)},
+		{"Character::FaceObject^2", API_FN_PAIR(Character_FaceObject)},
+		{"Character::FollowCharacter^3", API_FN_PAIR(Character_FollowCharacter)},
+		{"Character::GetProperty^1", API_FN_PAIR(Character_GetProperty)},
+		{"Character::GetPropertyText^2", API_FN_PAIR(Character_GetPropertyText)},
+		{"Character::GetTextProperty^1", API_FN_PAIR(Character_GetTextProperty)},
+		{"Character::SetProperty^2", API_FN_PAIR(Character_SetProperty)},
+		{"Character::SetTextProperty^2", API_FN_PAIR(Character_SetTextProperty)},
+		{"Character::HasInventory^1", API_FN_PAIR(Character_HasInventory)},
+		{"Character::IsCollidingWithChar^1", API_FN_PAIR(Character_IsCollidingWithChar)},
+		{"Character::IsCollidingWithObject^1", API_FN_PAIR(Character_IsCollidingWithObject)},
+		{"Character::IsInteractionAvailable^1", API_FN_PAIR(Character_IsInteractionAvailable)},
+		{"Character::LockView^1", API_FN_PAIR(Character_LockView)},
+		{"Character::LockView^2", API_FN_PAIR(Character_LockViewEx)},
+		{"Character::LockViewFrame^3", API_FN_PAIR(Character_LockViewFrame)},
+		{"Character::LockViewFrame^4", API_FN_PAIR(Character_LockViewFrameEx)},
+		{"Character::LockViewOffset^3", API_FN_PAIR(Character_LockViewOffset)},
+		{"Character::LockViewOffset^4", API_FN_PAIR(Character_LockViewOffsetEx)},
+		{"Character::LoseInventory^1", API_FN_PAIR(Character_LoseInventory)},
+		{"Character::Move^4", API_FN_PAIR(Character_Move)},
+		{"Character::PlaceOnWalkableArea^0", API_FN_PAIR(Character_PlaceOnWalkableArea)},
+		{"Character::RemoveTint^0", API_FN_PAIR(Character_RemoveTint)},
+		{"Character::RunInteraction^1", API_FN_PAIR(Character_RunInteraction)},
+		{"Character::Say^101", Sc_Character_Say},
+		{"Character::SayAt^4", API_FN_PAIR(Character_SayAt)},
+		{"Character::SayBackground^1", API_FN_PAIR(Character_SayBackground)},
+		{"Character::SetAsPlayer^0", API_FN_PAIR(Character_SetAsPlayer)},
+		{"Character::SetIdleView^2", API_FN_PAIR(Character_SetIdleView)},
+		{"Character::SetLightLevel^1", API_FN_PAIR(Character_SetLightLevel)},
+		{"Character::SetWalkSpeed^2", API_FN_PAIR(Character_SetSpeed)},
+		{"Character::StopMoving^0", API_FN_PAIR(Character_StopMoving)},
+		{"Character::Think^101", Sc_Character_Think},
+		{"Character::Tint^5", API_FN_PAIR(Character_Tint)},
+		{"Character::UnlockView^0", API_FN_PAIR(Character_UnlockView)},
+		{"Character::UnlockView^1", API_FN_PAIR(Character_UnlockViewEx)},
+		{"Character::Walk^4", API_FN_PAIR(Character_Walk)},
+		{"Character::WalkStraight^3", API_FN_PAIR(Character_WalkStraight)},
+
+		{"Character::get_ActiveInventory", API_FN_PAIR(Character_GetActiveInventory)},
+		{"Character::set_ActiveInventory", API_FN_PAIR(Character_SetActiveInventory)},
+		{"Character::get_Animating", API_FN_PAIR(Character_GetAnimating)},
+		{"Character::get_AnimationSpeed", API_FN_PAIR(Character_GetAnimationSpeed)},
+		{"Character::set_AnimationSpeed", API_FN_PAIR(Character_SetAnimationSpeed)},
+		{"Character::get_AnimationVolume", API_FN_PAIR(Character_GetAnimationVolume)},
+		{"Character::set_AnimationVolume", API_FN_PAIR(Character_SetAnimationVolume)},
+		{"Character::get_Baseline", API_FN_PAIR(Character_GetBaseline)},
+		{"Character::set_Baseline", API_FN_PAIR(Character_SetBaseline)},
+		{"Character::get_BlinkInterval", API_FN_PAIR(Character_GetBlinkInterval)},
+		{"Character::set_BlinkInterval", API_FN_PAIR(Character_SetBlinkInterval)},
+		{"Character::get_BlinkView", API_FN_PAIR(Character_GetBlinkView)},
+		{"Character::set_BlinkView", API_FN_PAIR(Character_SetBlinkView)},
+		{"Character::get_BlinkWhileThinking", API_FN_PAIR(Character_GetBlinkWhileThinking)},
+		{"Character::set_BlinkWhileThinking", API_FN_PAIR(Character_SetBlinkWhileThinking)},
+		{"Character::get_BlockingHeight", API_FN_PAIR(Character_GetBlockingHeight)},
+		{"Character::set_BlockingHeight", API_FN_PAIR(Character_SetBlockingHeight)},
+		{"Character::get_BlockingWidth", API_FN_PAIR(Character_GetBlockingWidth)},
+		{"Character::set_BlockingWidth", API_FN_PAIR(Character_SetBlockingWidth)},
+		{"Character::get_Clickable", API_FN_PAIR(Character_GetClickable)},
+		{"Character::set_Clickable", API_FN_PAIR(Character_SetClickable)},
+		{"Character::get_DestinationX", API_FN_PAIR(Character_GetDestinationX)},
+		{"Character::get_DestinationY", API_FN_PAIR(Character_GetDestinationY)},
+		{"Character::get_DiagonalLoops", API_FN_PAIR(Character_GetDiagonalWalking)},
+		{"Character::set_DiagonalLoops", API_FN_PAIR(Character_SetDiagonalWalking)},
+		{"Character::get_Frame", API_FN_PAIR(Character_GetFrame)},
+		{"Character::set_Frame", API_FN_PAIR(Character_SetFrame)},
+		{"Character::get_ID", API_FN_PAIR(Character_GetID)},
+		{"Character::get_IdleView", API_FN_PAIR(Character_GetIdleView)},
+		{"Character::get_IdleAnimationDelay", API_FN_PAIR(Character_GetIdleAnimationDelay)},
+		{"Character::set_IdleAnimationDelay", API_FN_PAIR(Character_SetIdleAnimationDelay)},
+		{"Character::geti_InventoryQuantity", API_FN_PAIR(Character_GetIInventoryQuantity)},
+		{"Character::seti_InventoryQuantity", API_FN_PAIR(Character_SetIInventoryQuantity)},
+		{"Character::get_IgnoreLighting", API_FN_PAIR(Character_GetIgnoreLighting)},
+		{"Character::set_IgnoreLighting", API_FN_PAIR(Character_SetIgnoreLighting)},
+		{"Character::get_IgnoreScaling", API_FN_PAIR(Character_GetIgnoreScaling)},
+		{"Character::set_IgnoreScaling", API_FN_PAIR(Character_SetIgnoreScaling)},
+		{"Character::get_IgnoreWalkbehinds", API_FN_PAIR(Character_GetIgnoreWalkbehinds)},
+		{"Character::set_IgnoreWalkbehinds", API_FN_PAIR(Character_SetIgnoreWalkbehinds)},
+		{"Character::get_Loop", API_FN_PAIR(Character_GetLoop)},
+		{"Character::set_Loop", API_FN_PAIR(Character_SetLoop)},
+		{"Character::get_ManualScaling", API_FN_PAIR(Character_GetIgnoreScaling)},
+		{"Character::set_ManualScaling", API_FN_PAIR(Character_SetManualScaling)},
+		{"Character::get_MovementLinkedToAnimation", API_FN_PAIR(Character_GetMovementLinkedToAnimation)},
+		{"Character::set_MovementLinkedToAnimation", API_FN_PAIR(Character_SetMovementLinkedToAnimation)},
+		{"Character::get_Moving", API_FN_PAIR(Character_GetMoving)},
+		{"Character::get_Name", API_FN_PAIR(Character_GetName)},
+		{"Character::set_Name", API_FN_PAIR(Character_SetName)},
+		{"Character::get_NormalView", API_FN_PAIR(Character_GetNormalView)},
+		{"Character::get_PreviousRoom", API_FN_PAIR(Character_GetPreviousRoom)},
+		{"Character::get_Room", API_FN_PAIR(Character_GetRoom)},
+		{"Character::get_ScaleMoveSpeed", API_FN_PAIR(Character_GetScaleMoveSpeed)},
+		{"Character::set_ScaleMoveSpeed", API_FN_PAIR(Character_SetScaleMoveSpeed)},
+		{"Character::get_ScaleVolume", API_FN_PAIR(Character_GetScaleVolume)},
+		{"Character::set_ScaleVolume", API_FN_PAIR(Character_SetScaleVolume)},
+		{"Character::get_Scaling", API_FN_PAIR(Character_GetScaling)},
+		{"Character::set_Scaling", API_FN_PAIR(Character_SetScaling)},
+		{"Character::get_Solid", API_FN_PAIR(Character_GetSolid)},
+		{"Character::set_Solid", API_FN_PAIR(Character_SetSolid)},
+		{"Character::get_Speaking", API_FN_PAIR(Character_GetSpeaking)},
+		{"Character::get_SpeakingFrame", API_FN_PAIR(Character_GetSpeakingFrame)},
+		{"Character::get_SpeechAnimationDelay", API_FN_PAIR(GetCharacterSpeechAnimationDelay)},
+		{"Character::set_SpeechAnimationDelay", API_FN_PAIR(Character_SetSpeechAnimationDelay)},
+		{"Character::get_SpeechColor", API_FN_PAIR(Character_GetSpeechColor)},
+		{"Character::set_SpeechColor", API_FN_PAIR(Character_SetSpeechColor)},
+		{"Character::get_SpeechView", API_FN_PAIR(Character_GetSpeechView)},
+		{"Character::set_SpeechView", API_FN_PAIR(Character_SetSpeechView)},
+		{"Character::get_Thinking", API_FN_PAIR(Character_GetThinking)},
+		{"Character::get_ThinkingFrame", API_FN_PAIR(Character_GetThinkingFrame)},
+		{"Character::get_ThinkView", API_FN_PAIR(Character_GetThinkView)},
+		{"Character::set_ThinkView", API_FN_PAIR(Character_SetThinkView)},
+		{"Character::get_Transparency", API_FN_PAIR(Character_GetTransparency)},
+		{"Character::set_Transparency", API_FN_PAIR(Character_SetTransparency)},
+		{"Character::get_TurnBeforeWalking", API_FN_PAIR(Character_GetTurnBeforeWalking)},
+		{"Character::set_TurnBeforeWalking", API_FN_PAIR(Character_SetTurnBeforeWalking)},
+		{"Character::get_View", API_FN_PAIR(Character_GetView)},
+		{"Character::get_WalkSpeedX", API_FN_PAIR(Character_GetWalkSpeedX)},
+		{"Character::get_WalkSpeedY", API_FN_PAIR(Character_GetWalkSpeedY)},
+		{"Character::get_X", API_FN_PAIR(Character_GetX)},
+		{"Character::set_X", API_FN_PAIR(Character_SetX)},
+		{"Character::get_x", API_FN_PAIR(Character_GetX)},
+		{"Character::set_x", API_FN_PAIR(Character_SetX)},
+		{"Character::get_Y", API_FN_PAIR(Character_GetY)},
+		{"Character::set_Y", API_FN_PAIR(Character_SetY)},
+		{"Character::get_y", API_FN_PAIR(Character_GetY)},
+		{"Character::set_y", API_FN_PAIR(Character_SetY)},
+		{"Character::get_Z", API_FN_PAIR(Character_GetZ)},
+		{"Character::set_Z", API_FN_PAIR(Character_SetZ)},
+		{"Character::get_z", API_FN_PAIR(Character_GetZ)},
+		{"Character::set_z", API_FN_PAIR(Character_SetZ)},
+		{"Character::get_HasExplicitLight", API_FN_PAIR(Character_GetHasExplicitLight)},
+		{"Character::get_LightLevel", API_FN_PAIR(Character_GetLightLevel)},
+		{"Character::get_TintBlue", API_FN_PAIR(Character_GetTintBlue)},
+		{"Character::get_TintGreen", API_FN_PAIR(Character_GetTintGreen)},
+		{"Character::get_TintRed", API_FN_PAIR(Character_GetTintRed)},
+		{"Character::get_TintSaturation", API_FN_PAIR(Character_GetTintSaturation)},
+		{"Character::get_TintLuminance", API_FN_PAIR(Character_GetTintLuminance)},
+	};
+
+	ccAddExternalFunctions361(character_api);
+
+	// Few functions have to be selected based on API level
 	if (base_api < kScriptAPI_v350) {
-		ccAddExternalObjectFunction("Character::LockViewAligned^3", Sc_Character_LockViewAligned_Old);
-		ccAddExternalObjectFunction("Character::LockViewAligned^4", Sc_Character_LockViewAlignedEx_Old);
+		ccAddExternalObjectFunction361("Character::LockViewAligned^3", API_FN_PAIR(Character_LockViewAligned_Old));
+		ccAddExternalObjectFunction361("Character::LockViewAligned^4", API_FN_PAIR(Character_LockViewAlignedEx_Old));
 	} else {
-		ccAddExternalObjectFunction("Character::LockViewAligned^3", Sc_Character_LockViewAligned);
-		ccAddExternalObjectFunction("Character::LockViewAligned^4", Sc_Character_LockViewAlignedEx);
-	}
-	ccAddExternalObjectFunction("Character::LockViewFrame^3",           Sc_Character_LockViewFrame);
-	ccAddExternalObjectFunction("Character::LockViewFrame^4",           Sc_Character_LockViewFrameEx);
-	ccAddExternalObjectFunction("Character::LockViewOffset^3",          Sc_Character_LockViewOffset);
-	ccAddExternalObjectFunction("Character::LockViewOffset^4",          Sc_Character_LockViewOffsetEx);
-	ccAddExternalObjectFunction("Character::LoseInventory^1",           Sc_Character_LoseInventory);
-	ccAddExternalObjectFunction("Character::Move^4",                    Sc_Character_Move);
-	ccAddExternalObjectFunction("Character::PlaceOnWalkableArea^0",     Sc_Character_PlaceOnWalkableArea);
-	ccAddExternalObjectFunction("Character::RemoveTint^0",              Sc_Character_RemoveTint);
-	ccAddExternalObjectFunction("Character::RunInteraction^1",          Sc_Character_RunInteraction);
-	ccAddExternalObjectFunction("Character::Say^101",                   Sc_Character_Say);
-	ccAddExternalObjectFunction("Character::SayAt^4",                   Sc_Character_SayAt);
-	ccAddExternalObjectFunction("Character::SayBackground^1",           Sc_Character_SayBackground);
-	ccAddExternalObjectFunction("Character::SetAsPlayer^0",             Sc_Character_SetAsPlayer);
-	ccAddExternalObjectFunction("Character::SetIdleView^2",             Sc_Character_SetIdleView);
-	ccAddExternalObjectFunction("Character::SetLightLevel^1",           Sc_Character_SetLightLevel);
-	//ccAddExternalObjectFunction("Character::SetOption^2",             Sc_Character_SetOption);
-	ccAddExternalObjectFunction("Character::SetWalkSpeed^2",            Sc_Character_SetSpeed);
-	ccAddExternalObjectFunction("Character::StopMoving^0",              Sc_Character_StopMoving);
-	ccAddExternalObjectFunction("Character::Think^101",                 Sc_Character_Think);
-	ccAddExternalObjectFunction("Character::Tint^5",                    Sc_Character_Tint);
-	ccAddExternalObjectFunction("Character::UnlockView^0",              Sc_Character_UnlockView);
-	ccAddExternalObjectFunction("Character::UnlockView^1",              Sc_Character_UnlockViewEx);
-	ccAddExternalObjectFunction("Character::Walk^4",                    Sc_Character_Walk);
-	ccAddExternalObjectFunction("Character::WalkStraight^3",            Sc_Character_WalkStraight);
-
-	ccAddExternalStaticFunction("Character::GetAtRoomXY^2",             Sc_GetCharacterAtRoom);
-	ccAddExternalStaticFunction("Character::GetAtScreenXY^2",           Sc_GetCharacterAtScreen);
-
-	ccAddExternalObjectFunction("Character::get_ActiveInventory",       Sc_Character_GetActiveInventory);
-	ccAddExternalObjectFunction("Character::set_ActiveInventory",       Sc_Character_SetActiveInventory);
-	ccAddExternalObjectFunction("Character::get_Animating",             Sc_Character_GetAnimating);
-	ccAddExternalObjectFunction("Character::get_AnimationSpeed",        Sc_Character_GetAnimationSpeed);
-	ccAddExternalObjectFunction("Character::set_AnimationSpeed",        Sc_Character_SetAnimationSpeed);
-	ccAddExternalObjectFunction("Character::get_AnimationVolume",       Sc_Character_GetAnimationVolume);
-	ccAddExternalObjectFunction("Character::set_AnimationVolume",       Sc_Character_SetAnimationVolume);
-	ccAddExternalObjectFunction("Character::get_Baseline",              Sc_Character_GetBaseline);
-	ccAddExternalObjectFunction("Character::set_Baseline",              Sc_Character_SetBaseline);
-	ccAddExternalObjectFunction("Character::get_BlinkInterval",         Sc_Character_GetBlinkInterval);
-	ccAddExternalObjectFunction("Character::set_BlinkInterval",         Sc_Character_SetBlinkInterval);
-	ccAddExternalObjectFunction("Character::get_BlinkView",             Sc_Character_GetBlinkView);
-	ccAddExternalObjectFunction("Character::set_BlinkView",             Sc_Character_SetBlinkView);
-	ccAddExternalObjectFunction("Character::get_BlinkWhileThinking",    Sc_Character_GetBlinkWhileThinking);
-	ccAddExternalObjectFunction("Character::set_BlinkWhileThinking",    Sc_Character_SetBlinkWhileThinking);
-	ccAddExternalObjectFunction("Character::get_BlockingHeight",        Sc_Character_GetBlockingHeight);
-	ccAddExternalObjectFunction("Character::set_BlockingHeight",        Sc_Character_SetBlockingHeight);
-	ccAddExternalObjectFunction("Character::get_BlockingWidth",         Sc_Character_GetBlockingWidth);
-	ccAddExternalObjectFunction("Character::set_BlockingWidth",         Sc_Character_SetBlockingWidth);
-	ccAddExternalObjectFunction("Character::get_Clickable",             Sc_Character_GetClickable);
-	ccAddExternalObjectFunction("Character::set_Clickable",             Sc_Character_SetClickable);
-	ccAddExternalObjectFunction("Character::get_DestinationX",          Sc_Character_GetDestinationX);
-	ccAddExternalObjectFunction("Character::get_DestinationY",          Sc_Character_GetDestinationY);
-	ccAddExternalObjectFunction("Character::get_DiagonalLoops",         Sc_Character_GetDiagonalWalking);
-	ccAddExternalObjectFunction("Character::set_DiagonalLoops",         Sc_Character_SetDiagonalWalking);
-	ccAddExternalObjectFunction("Character::get_Frame",                 Sc_Character_GetFrame);
-	ccAddExternalObjectFunction("Character::set_Frame",                 Sc_Character_SetFrame);
-	if (base_api < kScriptAPI_v341)
-		ccAddExternalObjectFunction("Character::get_HasExplicitTint",       Sc_Character_GetHasExplicitTint_Old);
-	else
-		ccAddExternalObjectFunction("Character::get_HasExplicitTint",       Sc_Character_GetHasExplicitTint);
-	ccAddExternalObjectFunction("Character::get_ID",                    Sc_Character_GetID);
-	ccAddExternalObjectFunction("Character::get_IdleView",              Sc_Character_GetIdleView);
-	ccAddExternalObjectFunction("Character::get_IdleAnimationDelay",    Sc_Character_GetIdleAnimationDelay);
-	ccAddExternalObjectFunction("Character::set_IdleAnimationDelay",    Sc_Character_SetIdleAnimationDelay);
-	ccAddExternalObjectFunction("Character::geti_InventoryQuantity",    Sc_Character_GetIInventoryQuantity);
-	ccAddExternalObjectFunction("Character::seti_InventoryQuantity",    Sc_Character_SetIInventoryQuantity);
-	ccAddExternalObjectFunction("Character::get_IgnoreLighting",        Sc_Character_GetIgnoreLighting);
-	ccAddExternalObjectFunction("Character::set_IgnoreLighting",        Sc_Character_SetIgnoreLighting);
-	ccAddExternalObjectFunction("Character::get_IgnoreScaling",         Sc_Character_GetIgnoreScaling);
-	ccAddExternalObjectFunction("Character::set_IgnoreScaling",         Sc_Character_SetIgnoreScaling);
-	ccAddExternalObjectFunction("Character::get_IgnoreWalkbehinds",     Sc_Character_GetIgnoreWalkbehinds);
-	ccAddExternalObjectFunction("Character::set_IgnoreWalkbehinds",     Sc_Character_SetIgnoreWalkbehinds);
-	ccAddExternalObjectFunction("Character::get_Loop",                  Sc_Character_GetLoop);
-	ccAddExternalObjectFunction("Character::set_Loop",                  Sc_Character_SetLoop);
-	ccAddExternalObjectFunction("Character::get_ManualScaling",         Sc_Character_GetIgnoreScaling);
-	ccAddExternalObjectFunction("Character::set_ManualScaling",         Sc_Character_SetManualScaling);
-	ccAddExternalObjectFunction("Character::get_MovementLinkedToAnimation", Sc_Character_GetMovementLinkedToAnimation);
-	ccAddExternalObjectFunction("Character::set_MovementLinkedToAnimation", Sc_Character_SetMovementLinkedToAnimation);
-	ccAddExternalObjectFunction("Character::get_Moving",                Sc_Character_GetMoving);
-	ccAddExternalObjectFunction("Character::get_Name",                  Sc_Character_GetName);
-	ccAddExternalObjectFunction("Character::set_Name",                  Sc_Character_SetName);
-	ccAddExternalObjectFunction("Character::get_NormalView",            Sc_Character_GetNormalView);
-	ccAddExternalObjectFunction("Character::get_PreviousRoom",          Sc_Character_GetPreviousRoom);
-	ccAddExternalObjectFunction("Character::get_Room",                  Sc_Character_GetRoom);
-	ccAddExternalObjectFunction("Character::get_ScaleMoveSpeed",        Sc_Character_GetScaleMoveSpeed);
-	ccAddExternalObjectFunction("Character::set_ScaleMoveSpeed",        Sc_Character_SetScaleMoveSpeed);
-	ccAddExternalObjectFunction("Character::get_ScaleVolume",           Sc_Character_GetScaleVolume);
-	ccAddExternalObjectFunction("Character::set_ScaleVolume",           Sc_Character_SetScaleVolume);
-	ccAddExternalObjectFunction("Character::get_Scaling",               Sc_Character_GetScaling);
-	ccAddExternalObjectFunction("Character::set_Scaling",               Sc_Character_SetScaling);
-	ccAddExternalObjectFunction("Character::get_Solid",                 Sc_Character_GetSolid);
-	ccAddExternalObjectFunction("Character::set_Solid",                 Sc_Character_SetSolid);
-	ccAddExternalObjectFunction("Character::get_Speaking",              Sc_Character_GetSpeaking);
-	ccAddExternalObjectFunction("Character::get_SpeakingFrame",         Sc_Character_GetSpeakingFrame);
-	ccAddExternalObjectFunction("Character::get_SpeechAnimationDelay",  Sc_GetCharacterSpeechAnimationDelay);
-	ccAddExternalObjectFunction("Character::set_SpeechAnimationDelay",  Sc_Character_SetSpeechAnimationDelay);
-	ccAddExternalObjectFunction("Character::get_SpeechColor",           Sc_Character_GetSpeechColor);
-	ccAddExternalObjectFunction("Character::set_SpeechColor",           Sc_Character_SetSpeechColor);
-	ccAddExternalObjectFunction("Character::get_SpeechView",            Sc_Character_GetSpeechView);
-	ccAddExternalObjectFunction("Character::set_SpeechView",            Sc_Character_SetSpeechView);
-	ccAddExternalObjectFunction("Character::get_Thinking",              Sc_Character_GetThinking);
-	ccAddExternalObjectFunction("Character::get_ThinkingFrame",         Sc_Character_GetThinkingFrame);
-	ccAddExternalObjectFunction("Character::get_ThinkView",             Sc_Character_GetThinkView);
-	ccAddExternalObjectFunction("Character::set_ThinkView",             Sc_Character_SetThinkView);
-	ccAddExternalObjectFunction("Character::get_Transparency",          Sc_Character_GetTransparency);
-	ccAddExternalObjectFunction("Character::set_Transparency",          Sc_Character_SetTransparency);
-	ccAddExternalObjectFunction("Character::get_TurnBeforeWalking",     Sc_Character_GetTurnBeforeWalking);
-	ccAddExternalObjectFunction("Character::set_TurnBeforeWalking",     Sc_Character_SetTurnBeforeWalking);
-	ccAddExternalObjectFunction("Character::get_View",                  Sc_Character_GetView);
-	ccAddExternalObjectFunction("Character::get_WalkSpeedX",            Sc_Character_GetWalkSpeedX);
-	ccAddExternalObjectFunction("Character::get_WalkSpeedY",            Sc_Character_GetWalkSpeedY);
-	ccAddExternalObjectFunction("Character::get_X",                     Sc_Character_GetX);
-	ccAddExternalObjectFunction("Character::set_X",                     Sc_Character_SetX);
-	ccAddExternalObjectFunction("Character::get_x",                     Sc_Character_GetX);
-	ccAddExternalObjectFunction("Character::set_x",                     Sc_Character_SetX);
-	ccAddExternalObjectFunction("Character::get_Y",                     Sc_Character_GetY);
-	ccAddExternalObjectFunction("Character::set_Y",                     Sc_Character_SetY);
-	ccAddExternalObjectFunction("Character::get_y",                     Sc_Character_GetY);
-	ccAddExternalObjectFunction("Character::set_y",                     Sc_Character_SetY);
-	ccAddExternalObjectFunction("Character::get_Z",                     Sc_Character_GetZ);
-	ccAddExternalObjectFunction("Character::set_Z",                     Sc_Character_SetZ);
-	ccAddExternalObjectFunction("Character::get_z",                     Sc_Character_GetZ);
-	ccAddExternalObjectFunction("Character::set_z",                     Sc_Character_SetZ);
-
-	ccAddExternalObjectFunction("Character::get_HasExplicitLight",      Sc_Character_HasExplicitLight);
-	ccAddExternalObjectFunction("Character::get_LightLevel",            Sc_Character_GetLightLevel);
-	ccAddExternalObjectFunction("Character::get_TintBlue",              Sc_Character_GetTintBlue);
-	ccAddExternalObjectFunction("Character::get_TintGreen",             Sc_Character_GetTintGreen);
-	ccAddExternalObjectFunction("Character::get_TintRed",               Sc_Character_GetTintRed);
-	ccAddExternalObjectFunction("Character::get_TintSaturation",        Sc_Character_GetTintSaturation);
-	ccAddExternalObjectFunction("Character::get_TintLuminance",         Sc_Character_GetTintLuminance);
+		ccAddExternalObjectFunction361("Character::LockViewAligned^3", API_FN_PAIR(Character_LockViewAligned));
+		ccAddExternalObjectFunction361("Character::LockViewAligned^4", API_FN_PAIR(Character_LockViewAlignedEx));
+	}
+
+	if (base_api < kScriptAPI_v341) {
+		ccAddExternalObjectFunction361("Character::get_HasExplicitTint", API_FN_PAIR(Character_GetHasExplicitTint_Old));
+	} else {
+		ccAddExternalObjectFunction361("Character::get_HasExplicitTint", API_FN_PAIR(Character_GetHasExplicitTint));
+	}
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 6c719285e77..3a4f683fd64 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1744,65 +1744,67 @@ RuntimeScriptValue Sc_Game_BlockingWaitSkipped(const RuntimeScriptValue *params,
 }
 
 void RegisterGameAPI() {
-	ccAddExternalStaticFunction("Game::IsAudioPlaying^1",                       Sc_Game_IsAudioPlaying);
-	ccAddExternalStaticFunction("Game::SetAudioTypeSpeechVolumeDrop^2",         Sc_Game_SetAudioTypeSpeechVolumeDrop);
-	ccAddExternalStaticFunction("Game::SetAudioTypeVolume^3",                   Sc_Game_SetAudioTypeVolume);
-	ccAddExternalStaticFunction("Game::StopAudio^1",                            Sc_Game_StopAudio);
-	ccAddExternalStaticFunction("Game::ChangeTranslation^1",                    Sc_Game_ChangeTranslation);
-	ccAddExternalStaticFunction("Game::DoOnceOnly^1",                           Sc_Game_DoOnceOnly);
-	ccAddExternalStaticFunction("Game::GetColorFromRGB^3",                      Sc_Game_GetColorFromRGB);
-	ccAddExternalStaticFunction("Game::GetFrameCountForLoop^2",                 Sc_Game_GetFrameCountForLoop);
-	ccAddExternalStaticFunction("Game::GetLocationName^2",                      Sc_Game_GetLocationName);
-	ccAddExternalStaticFunction("Game::GetLoopCountForView^1",                  Sc_Game_GetLoopCountForView);
-	ccAddExternalStaticFunction("Game::GetMODPattern^0",                        Sc_Game_GetMODPattern);
-	ccAddExternalStaticFunction("Game::GetRunNextSettingForLoop^2",             Sc_Game_GetRunNextSettingForLoop);
-	ccAddExternalStaticFunction("Game::GetSaveSlotDescription^1",               Sc_Game_GetSaveSlotDescription);
-	ccAddExternalStaticFunction("Game::GetViewFrame^3",                         Sc_Game_GetViewFrame);
-	ccAddExternalStaticFunction("Game::InputBox^1",                             Sc_Game_InputBox);
-	ccAddExternalStaticFunction("Game::SetSaveGameDirectory^1",                 Sc_Game_SetSaveGameDirectory);
-	ccAddExternalStaticFunction("Game::StopSound^1",                            Sc_StopAllSounds);
-	ccAddExternalStaticFunction("Game::ResetDoOnceOnly",                        Sc_Game_ResetDoOnceOnly);
-	ccAddExternalStaticFunction("Game::get_CharacterCount",                     Sc_Game_GetCharacterCount);
-	ccAddExternalStaticFunction("Game::get_DialogCount",                        Sc_Game_GetDialogCount);
-	ccAddExternalStaticFunction("Game::get_FileName",                           Sc_Game_GetFileName);
-	ccAddExternalStaticFunction("Game::get_FontCount",                          Sc_Game_GetFontCount);
-	ccAddExternalStaticFunction("Game::geti_GlobalMessages",                    Sc_Game_GetGlobalMessages);
-	ccAddExternalStaticFunction("Game::geti_GlobalStrings",                     Sc_Game_GetGlobalStrings);
-	ccAddExternalStaticFunction("Game::seti_GlobalStrings",                     Sc_SetGlobalString);
-	ccAddExternalStaticFunction("Game::get_GUICount",                           Sc_Game_GetGUICount);
-	ccAddExternalStaticFunction("Game::get_IgnoreUserInputAfterTextTimeoutMs",  Sc_Game_GetIgnoreUserInputAfterTextTimeoutMs);
-	ccAddExternalStaticFunction("Game::set_IgnoreUserInputAfterTextTimeoutMs",  Sc_Game_SetIgnoreUserInputAfterTextTimeoutMs);
-	ccAddExternalStaticFunction("Game::get_InSkippableCutscene",                Sc_Game_GetInSkippableCutscene);
-	ccAddExternalStaticFunction("Game::get_InventoryItemCount",                 Sc_Game_GetInventoryItemCount);
-	ccAddExternalStaticFunction("Game::get_MinimumTextDisplayTimeMs",           Sc_Game_GetMinimumTextDisplayTimeMs);
-	ccAddExternalStaticFunction("Game::set_MinimumTextDisplayTimeMs",           Sc_Game_SetMinimumTextDisplayTimeMs);
-	ccAddExternalStaticFunction("Game::get_MouseCursorCount",                   Sc_Game_GetMouseCursorCount);
-	ccAddExternalStaticFunction("Game::get_Name",                               Sc_Game_GetName);
-	ccAddExternalStaticFunction("Game::set_Name",                               Sc_Game_SetName);
-	ccAddExternalStaticFunction("Game::get_NormalFont",                         Sc_Game_GetNormalFont);
-	ccAddExternalStaticFunction("Game::set_NormalFont",                         Sc_SetNormalFont);
-	ccAddExternalStaticFunction("Game::get_SkippingCutscene",                   Sc_Game_GetSkippingCutscene);
-	ccAddExternalStaticFunction("Game::get_SpeechFont",                         Sc_Game_GetSpeechFont);
-	ccAddExternalStaticFunction("Game::set_SpeechFont",                         Sc_SetSpeechFont);
-	ccAddExternalStaticFunction("Game::geti_SpriteWidth",                       Sc_Game_GetSpriteWidth);
-	ccAddExternalStaticFunction("Game::geti_SpriteHeight",                      Sc_Game_GetSpriteHeight);
-	ccAddExternalStaticFunction("Game::get_TextReadingSpeed",                   Sc_Game_GetTextReadingSpeed);
-	ccAddExternalStaticFunction("Game::set_TextReadingSpeed",                   Sc_Game_SetTextReadingSpeed);
-	ccAddExternalStaticFunction("Game::get_TranslationFilename",                Sc_Game_GetTranslationFilename);
-	ccAddExternalStaticFunction("Game::get_UseNativeCoordinates",               Sc_Game_GetUseNativeCoordinates);
-	ccAddExternalStaticFunction("Game::get_ViewCount",                          Sc_Game_GetViewCount);
-	ccAddExternalStaticFunction("Game::get_AudioClipCount",                     Sc_Game_GetAudioClipCount);
-	ccAddExternalStaticFunction("Game::geti_AudioClips",                        Sc_Game_GetAudioClip);
-	ccAddExternalStaticFunction("Game::IsPluginLoaded",                         Sc_Game_IsPluginLoaded);
-	ccAddExternalStaticFunction("Game::ChangeSpeechVox", Sc_Game_ChangeSpeechVox);
-	ccAddExternalStaticFunction("Game::PlayVoiceClip",                          Sc_Game_PlayVoiceClip);
-	ccAddExternalStaticFunction("Game::SimulateKeyPress",                       Sc_Game_SimulateKeyPress);
-	ccAddExternalStaticFunction("Game::get_BlockingWaitSkipped",                Sc_Game_BlockingWaitSkipped);
-	ccAddExternalStaticFunction("Game::get_SpeechVoxFilename", Sc_Game_GetSpeechVoxFilename);
-
-	ccAddExternalStaticFunction("Game::get_Camera",                             Sc_Game_GetCamera);
-	ccAddExternalStaticFunction("Game::get_CameraCount",                        Sc_Game_GetCameraCount);
-	ccAddExternalStaticFunction("Game::geti_Cameras",                           Sc_Game_GetAnyCamera);
+	ScFnRegister game_api[] = {
+		{"Game::IsAudioPlaying^1", API_FN_PAIR(Game_IsAudioPlaying)},
+		{"Game::SetAudioTypeSpeechVolumeDrop^2", API_FN_PAIR(Game_SetAudioTypeSpeechVolumeDrop)},
+		{"Game::SetAudioTypeVolume^3", API_FN_PAIR(Game_SetAudioTypeVolume)},
+		{"Game::StopAudio^1", API_FN_PAIR(Game_StopAudio)},
+		{"Game::ChangeTranslation^1", API_FN_PAIR(Game_ChangeTranslation)},
+		{"Game::DoOnceOnly^1", API_FN_PAIR(Game_DoOnceOnly)},
+		{"Game::GetColorFromRGB^3", API_FN_PAIR(Game_GetColorFromRGB)},
+		{"Game::GetFrameCountForLoop^2", API_FN_PAIR(Game_GetFrameCountForLoop)},
+		{"Game::GetLocationName^2", API_FN_PAIR(Game_GetLocationName)},
+		{"Game::GetLoopCountForView^1", API_FN_PAIR(Game_GetLoopCountForView)},
+		{"Game::GetMODPattern^0", API_FN_PAIR(Game_GetMODPattern)},
+		{"Game::GetRunNextSettingForLoop^2", API_FN_PAIR(Game_GetRunNextSettingForLoop)},
+		{"Game::GetSaveSlotDescription^1", API_FN_PAIR(Game_GetSaveSlotDescription)},
+		{"Game::GetViewFrame^3", API_FN_PAIR(Game_GetViewFrame)},
+		{"Game::InputBox^1", API_FN_PAIR(Game_InputBox)},
+		{"Game::SetSaveGameDirectory^1", API_FN_PAIR(Game_SetSaveGameDirectory)},
+		{"Game::StopSound^1", API_FN_PAIR(StopAllSounds)},
+		{"Game::get_CharacterCount", API_FN_PAIR(Game_GetCharacterCount)},
+		{"Game::get_DialogCount", API_FN_PAIR(Game_GetDialogCount)},
+		{"Game::get_FileName", API_FN_PAIR(Game_GetFileName)},
+		{"Game::get_FontCount", API_FN_PAIR(Game_GetFontCount)},
+		{"Game::geti_GlobalMessages", API_FN_PAIR(Game_GetGlobalMessages)},
+		{"Game::geti_GlobalStrings", API_FN_PAIR(Game_GetGlobalStrings)},
+		{"Game::seti_GlobalStrings", API_FN_PAIR(SetGlobalString)},
+		{"Game::get_GUICount", API_FN_PAIR(Game_GetGUICount)},
+		{"Game::get_IgnoreUserInputAfterTextTimeoutMs", API_FN_PAIR(Game_GetIgnoreUserInputAfterTextTimeoutMs)},
+		{"Game::set_IgnoreUserInputAfterTextTimeoutMs", API_FN_PAIR(Game_SetIgnoreUserInputAfterTextTimeoutMs)},
+		{"Game::get_InSkippableCutscene", API_FN_PAIR(Game_GetInSkippableCutscene)},
+		{"Game::get_InventoryItemCount", API_FN_PAIR(Game_GetInventoryItemCount)},
+		{"Game::get_MinimumTextDisplayTimeMs", API_FN_PAIR(Game_GetMinimumTextDisplayTimeMs)},
+		{"Game::set_MinimumTextDisplayTimeMs", API_FN_PAIR(Game_SetMinimumTextDisplayTimeMs)},
+		{"Game::get_MouseCursorCount", API_FN_PAIR(Game_GetMouseCursorCount)},
+		{"Game::get_Name", API_FN_PAIR(Game_GetName)},
+		{"Game::set_Name", API_FN_PAIR(Game_SetName)},
+		{"Game::get_NormalFont", API_FN_PAIR(Game_GetNormalFont)},
+		{"Game::set_NormalFont", API_FN_PAIR(SetNormalFont)},
+		{"Game::get_SkippingCutscene", API_FN_PAIR(Game_GetSkippingCutscene)},
+		{"Game::get_SpeechFont", API_FN_PAIR(Game_GetSpeechFont)},
+		{"Game::set_SpeechFont", API_FN_PAIR(SetSpeechFont)},
+		{"Game::geti_SpriteWidth", API_FN_PAIR(Game_GetSpriteWidth)},
+		{"Game::geti_SpriteHeight", API_FN_PAIR(Game_GetSpriteHeight)},
+		{"Game::get_TextReadingSpeed", API_FN_PAIR(Game_GetTextReadingSpeed)},
+		{"Game::set_TextReadingSpeed", API_FN_PAIR(Game_SetTextReadingSpeed)},
+		{"Game::get_TranslationFilename", API_FN_PAIR(Game_GetTranslationFilename)},
+		{"Game::get_UseNativeCoordinates", API_FN_PAIR(Game_GetUseNativeCoordinates)},
+		{"Game::get_ViewCount", API_FN_PAIR(Game_GetViewCount)},
+		{"Game::get_AudioClipCount", API_FN_PAIR(Game_GetAudioClipCount)},
+		{"Game::geti_AudioClips", API_FN_PAIR(Game_GetAudioClip)},
+		{"Game::IsPluginLoaded", Sc_Game_IsPluginLoaded},
+		{"Game::ChangeSpeechVox", API_FN_PAIR(Game_ChangeSpeechVox)},
+		{"Game::PlayVoiceClip", Sc_Game_PlayVoiceClip},
+		{"Game::SimulateKeyPress", API_FN_PAIR(Game_SimulateKeyPress)},
+		{"Game::get_BlockingWaitSkipped", API_FN_PAIR(Game_BlockingWaitSkipped)},
+		{"Game::get_SpeechVoxFilename", API_FN_PAIR(Game_GetSpeechVoxFilename)},
+		{"Game::get_Camera", API_FN_PAIR(Game_GetCamera)},
+		{"Game::get_CameraCount", API_FN_PAIR(Game_GetCameraCount)},
+		{"Game::geti_Cameras", API_FN_PAIR(Game_GetAnyCamera)},
+	};
+
+	ccAddExternalFunctions361(game_api);
 }
 
 void RegisterStaticObjects() {
diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index 958b56d1c56..fb6d5acecf1 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -1019,7 +1019,7 @@ RuntimeScriptValue Sc_PlayAmbientSound(const RuntimeScriptValue *params, int32_t
 }
 
 // void (int numb,int playflags)
-RuntimeScriptValue Sc_play_flc_file(const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_PlayFlic(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_VOID_PINT2(PlayFlic);
 }
 
@@ -1054,7 +1054,7 @@ RuntimeScriptValue Sc_PlaySoundEx(const RuntimeScriptValue *params, int32_t para
 }
 
 // void (const char* name, int skip, int flags)
-RuntimeScriptValue Sc_scrPlayVideo(const RuntimeScriptValue *params, int32_t param_count) {
+RuntimeScriptValue Sc_PlayVideo(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_VOID_POBJ_PINT2(PlayVideo, const char);
 }
 
@@ -1887,379 +1887,380 @@ RuntimeScriptValue Sc_SkipWait(const RuntimeScriptValue *params, int32_t param_c
 }
 
 void RegisterGlobalAPI() {
-	ccAddExternalStaticFunction("AbortGame",                Sc_sc_AbortGame);
-	ccAddExternalStaticFunction("AddInventory",             Sc_add_inventory);
-	ccAddExternalStaticFunction("AddInventoryToCharacter",  Sc_AddInventoryToCharacter);
-	ccAddExternalStaticFunction("AnimateButton",            Sc_AnimateButton);
-	ccAddExternalStaticFunction("AnimateCharacter",         Sc_AnimateCharacter4);
-	ccAddExternalStaticFunction("AnimateCharacterEx",       Sc_AnimateCharacter6);
-	ccAddExternalStaticFunction("AnimateObject",            Sc_AnimateObject4);
-	ccAddExternalStaticFunction("AnimateObjectEx",          Sc_AnimateObject6);
-	ccAddExternalStaticFunction("AreCharactersColliding",   Sc_AreCharactersColliding);
-	ccAddExternalStaticFunction("AreCharObjColliding",      Sc_AreCharObjColliding);
-	ccAddExternalStaticFunction("AreObjectsColliding",      Sc_AreObjectsColliding);
-	ccAddExternalStaticFunction("AreThingsOverlapping",     Sc_AreThingsOverlapping);
-	ccAddExternalStaticFunction("CallRoomScript",           Sc_CallRoomScript);
-	ccAddExternalStaticFunction("CDAudio",                  Sc_cd_manager);
-	ccAddExternalStaticFunction("CentreGUI",                Sc_CentreGUI);
-	ccAddExternalStaticFunction("ChangeCharacterView",      Sc_ChangeCharacterView);
-	ccAddExternalStaticFunction("ChangeCursorGraphic",      Sc_ChangeCursorGraphic);
-	ccAddExternalStaticFunction("ChangeCursorHotspot",      Sc_ChangeCursorHotspot);
-	ccAddExternalStaticFunction("ClaimEvent",               Sc_ClaimEvent);
-	ccAddExternalStaticFunction("CreateGraphicOverlay",     Sc_CreateGraphicOverlay);
-	ccAddExternalStaticFunction("CreateTextOverlay",        Sc_CreateTextOverlay);
-	ccAddExternalStaticFunction("CyclePalette",             Sc_CyclePalette);
-	ccAddExternalStaticFunction("Debug",                    Sc_script_debug);
-	ccAddExternalStaticFunction("DeleteSaveSlot",           Sc_DeleteSaveSlot);
-	ccAddExternalStaticFunction("DeleteSprite",             Sc_free_dynamic_sprite);
-	ccAddExternalStaticFunction("DisableCursorMode",        Sc_disable_cursor_mode);
-	ccAddExternalStaticFunction("DisableGroundLevelAreas",  Sc_DisableGroundLevelAreas);
-	ccAddExternalStaticFunction("DisableHotspot",           Sc_DisableHotspot);
-	ccAddExternalStaticFunction("DisableInterface",         Sc_DisableInterface);
-	ccAddExternalStaticFunction("DisableRegion",            Sc_DisableRegion);
-	ccAddExternalStaticFunction("Display",                  Sc_Display);
-	ccAddExternalStaticFunction("DisplayAt",                Sc_DisplayAt);
-	ccAddExternalStaticFunction("DisplayAtY",               Sc_DisplayAtY);
-	ccAddExternalStaticFunction("DisplayMessage",           Sc_DisplayMessage);
-	ccAddExternalStaticFunction("DisplayMessageAtY",        Sc_DisplayMessageAtY);
-	ccAddExternalStaticFunction("DisplayMessageBar",        Sc_DisplayMessageBar);
-	ccAddExternalStaticFunction("DisplaySpeech",            Sc_sc_displayspeech);
-	ccAddExternalStaticFunction("DisplaySpeechAt",          Sc_DisplaySpeechAt);
-	ccAddExternalStaticFunction("DisplaySpeechBackground",  Sc_DisplaySpeechBackground);
-	ccAddExternalStaticFunction("DisplayThought",           Sc_DisplayThought);
-	ccAddExternalStaticFunction("DisplayTopBar",            Sc_DisplayTopBar);
-	ccAddExternalStaticFunction("EnableCursorMode",         Sc_enable_cursor_mode);
-	ccAddExternalStaticFunction("EnableGroundLevelAreas",   Sc_EnableGroundLevelAreas);
-	ccAddExternalStaticFunction("EnableHotspot",            Sc_EnableHotspot);
-	ccAddExternalStaticFunction("EnableInterface",          Sc_EnableInterface);
-	ccAddExternalStaticFunction("EnableRegion",             Sc_EnableRegion);
-	ccAddExternalStaticFunction("EndCutscene",              Sc_EndCutscene);
-	ccAddExternalStaticFunction("FaceCharacter",            Sc_FaceCharacter);
-	ccAddExternalStaticFunction("FaceLocation",             Sc_FaceLocation);
-	ccAddExternalStaticFunction("FadeIn",                   Sc_FadeIn);
-	ccAddExternalStaticFunction("FadeOut",                  Sc_FadeOut);
-	ccAddExternalStaticFunction("FileClose",                Sc_FileClose);
-	ccAddExternalStaticFunction("FileIsEOF",                Sc_FileIsEOF);
-	ccAddExternalStaticFunction("FileIsError",              Sc_FileIsError);
-	// NOTE: FileOpenCMode is a backwards-compatible replacement for old-style global script function FileOpen
-	ccAddExternalStaticFunction("FileOpen",                 Sc_FileOpenCMode);
-	ccAddExternalStaticFunction("FileRead",                 Sc_FileRead);
-	ccAddExternalStaticFunction("FileReadInt",              Sc_FileReadInt);
-	ccAddExternalStaticFunction("FileReadRawChar",          Sc_FileReadRawChar);
-	ccAddExternalStaticFunction("FileReadRawInt",           Sc_FileReadRawInt);
-	ccAddExternalStaticFunction("FileWrite",                Sc_FileWrite);
-	ccAddExternalStaticFunction("FileWriteInt",             Sc_FileWriteInt);
-	ccAddExternalStaticFunction("FileWriteRawChar",         Sc_FileWriteRawChar);
-	ccAddExternalStaticFunction("FileWriteRawLine",         Sc_FileWriteRawLine);
-	ccAddExternalStaticFunction("FindGUIID",                Sc_FindGUIID);
-	ccAddExternalStaticFunction("FlipScreen",               Sc_FlipScreen);
-	ccAddExternalStaticFunction("FloatToInt",               Sc_FloatToInt);
-	ccAddExternalStaticFunction("FollowCharacter",          Sc_FollowCharacter);
-	ccAddExternalStaticFunction("FollowCharacterEx",        Sc_FollowCharacterEx);
-	ccAddExternalStaticFunction("GetBackgroundFrame",       Sc_GetBackgroundFrame);
-	ccAddExternalStaticFunction("GetButtonPic",             Sc_GetButtonPic);
-	ccAddExternalStaticFunction("GetCharacterAt",           Sc_GetCharIDAtScreen);
-	ccAddExternalStaticFunction("GetCharacterProperty",     Sc_GetCharacterProperty);
-	ccAddExternalStaticFunction("GetCharacterPropertyText", Sc_GetCharacterPropertyText);
-	ccAddExternalStaticFunction("GetCurrentMusic",          Sc_GetCurrentMusic);
-	ccAddExternalStaticFunction("GetCursorMode",            Sc_GetCursorMode);
-	ccAddExternalStaticFunction("GetDialogOption",          Sc_GetDialogOption);
-	ccAddExternalStaticFunction("GetGameOption",            Sc_GetGameOption);
-	ccAddExternalStaticFunction("GetGameParameter",         Sc_GetGameParameter);
-	ccAddExternalStaticFunction("GetGameSpeed",             Sc_GetGameSpeed);
-	ccAddExternalStaticFunction("GetGlobalInt",             Sc_GetGlobalInt);
-	ccAddExternalStaticFunction("GetGlobalString",          Sc_GetGlobalString);
-	ccAddExternalStaticFunction("GetGraphicalVariable",     Sc_GetGraphicalVariable);
-	ccAddExternalStaticFunction("GetGUIAt",                 Sc_GetGUIAt);
-	ccAddExternalStaticFunction("GetGUIObjectAt",           Sc_GetGUIObjectAt);
-	ccAddExternalStaticFunction("GetHotspotAt",             Sc_GetHotspotIDAtScreen);
-	ccAddExternalStaticFunction("GetHotspotName",           Sc_GetHotspotName);
-	ccAddExternalStaticFunction("GetHotspotPointX",         Sc_GetHotspotPointX);
-	ccAddExternalStaticFunction("GetHotspotPointY",         Sc_GetHotspotPointY);
-	ccAddExternalStaticFunction("GetHotspotProperty",       Sc_GetHotspotProperty);
-	ccAddExternalStaticFunction("GetHotspotPropertyText",   Sc_GetHotspotPropertyText);
-	ccAddExternalStaticFunction("GetInvAt",                 Sc_GetInvAt);
-	ccAddExternalStaticFunction("GetInvGraphic",            Sc_GetInvGraphic);
-	ccAddExternalStaticFunction("GetInvName",               Sc_GetInvName);
-	ccAddExternalStaticFunction("GetInvProperty",           Sc_GetInvProperty);
-	ccAddExternalStaticFunction("GetInvPropertyText",       Sc_GetInvPropertyText);
-	//ccAddExternalStaticFunction("GetLanguageString",      Sc_GetLanguageString);
-	ccAddExternalStaticFunction("GetLocationName",          Sc_GetLocationName);
-	ccAddExternalStaticFunction("GetLocationType",          Sc_GetLocationType);
-	ccAddExternalStaticFunction("GetMessageText",           Sc_GetMessageText);
-	ccAddExternalStaticFunction("GetMIDIPosition",          Sc_GetMIDIPosition);
-	ccAddExternalStaticFunction("GetMP3PosMillis",          Sc_GetMP3PosMillis);
-	ccAddExternalStaticFunction("GetObjectAt",              Sc_GetObjectIDAtScreen);
-	ccAddExternalStaticFunction("GetObjectBaseline",        Sc_GetObjectBaseline);
-	ccAddExternalStaticFunction("GetObjectGraphic",         Sc_GetObjectGraphic);
-	ccAddExternalStaticFunction("GetObjectName",            Sc_GetObjectName);
-	ccAddExternalStaticFunction("GetObjectProperty",        Sc_GetObjectProperty);
-	ccAddExternalStaticFunction("GetObjectPropertyText",    Sc_GetObjectPropertyText);
-	ccAddExternalStaticFunction("GetObjectX",               Sc_GetObjectX);
-	ccAddExternalStaticFunction("GetObjectY",               Sc_GetObjectY);
-	//  ccAddExternalStaticFunction("GetPalette",           Sc_scGetPal);
-	ccAddExternalStaticFunction("GetPlayerCharacter",       Sc_GetPlayerCharacter);
-	ccAddExternalStaticFunction("GetRawTime",               Sc_GetRawTime);
-	ccAddExternalStaticFunction("GetRegionAt",              Sc_GetRegionIDAtRoom);
-	ccAddExternalStaticFunction("GetRoomProperty",          Sc_Room_GetProperty);
-	ccAddExternalStaticFunction("GetRoomPropertyText",      Sc_GetRoomPropertyText);
-	ccAddExternalStaticFunction("GetSaveSlotDescription",   Sc_GetSaveSlotDescription);
-	ccAddExternalStaticFunction("GetScalingAt",             Sc_GetScalingAt);
-	ccAddExternalStaticFunction("GetSliderValue",           Sc_GetSliderValue);
-	ccAddExternalStaticFunction("GetTextBoxText",           Sc_GetTextBoxText);
-	ccAddExternalStaticFunction("GetTextHeight",            Sc_GetTextHeight);
-	ccAddExternalStaticFunction("GetTextWidth",             Sc_GetTextWidth);
-	ccAddExternalStaticFunction("GetFontHeight",            Sc_GetFontHeight);
-	ccAddExternalStaticFunction("GetFontLineSpacing",       Sc_GetFontLineSpacing);
-	ccAddExternalStaticFunction("GetTime",                  Sc_sc_GetTime);
-	ccAddExternalStaticFunction("GetTranslation",           Sc_get_translation);
-	ccAddExternalStaticFunction("GetTranslationName",       Sc_GetTranslationName);
-	ccAddExternalStaticFunction("GetViewportX",             Sc_GetViewportX);
-	ccAddExternalStaticFunction("GetViewportY",             Sc_GetViewportY);
-	ccAddExternalStaticFunction("GetWalkableAreaAtRoom",    Sc_GetWalkableAreaAtRoom);
-	ccAddExternalStaticFunction("GetWalkableAreaAt",        Sc_GetWalkableAreaAtScreen);
-	ccAddExternalStaticFunction("GetWalkableAreaAtScreen",  Sc_GetWalkableAreaAtScreen);
-	ccAddExternalStaticFunction("GetDrawingSurfaceForWalkableArea", Sc_GetDrawingSurfaceForWalkableArea);
-	ccAddExternalStaticFunction("GetDrawingSurfaceForWalkbehind", Sc_GetDrawingSurfaceForWalkbehind);
-	ccAddExternalStaticFunction("GiveScore",                Sc_GiveScore);
-	ccAddExternalStaticFunction("HasPlayerBeenInRoom",      Sc_HasPlayerBeenInRoom);
-	ccAddExternalStaticFunction("HideMouseCursor",          Sc_HideMouseCursor);
-	ccAddExternalStaticFunction("InputBox",                 Sc_sc_inputbox);
-	ccAddExternalStaticFunction("InterfaceOff",             Sc_InterfaceOff);
-	ccAddExternalStaticFunction("InterfaceOn",              Sc_InterfaceOn);
-	ccAddExternalStaticFunction("IntToFloat",               Sc_IntToFloat);
-	ccAddExternalStaticFunction("InventoryScreen",          Sc_sc_invscreen);
-	ccAddExternalStaticFunction("IsButtonDown",             Sc_IsButtonDown);
-	ccAddExternalStaticFunction("IsChannelPlaying",         Sc_IsChannelPlaying);
-	ccAddExternalStaticFunction("IsGamePaused",             Sc_IsGamePaused);
-	ccAddExternalStaticFunction("IsGUIOn",                  Sc_IsGUIOn);
-	ccAddExternalStaticFunction("IsInteractionAvailable",   Sc_IsInteractionAvailable);
-	ccAddExternalStaticFunction("IsInventoryInteractionAvailable", Sc_IsInventoryInteractionAvailable);
-	ccAddExternalStaticFunction("IsInterfaceEnabled",       Sc_IsInterfaceEnabled);
-	ccAddExternalStaticFunction("IsKeyPressed",             Sc_IsKeyPressed);
-	ccAddExternalStaticFunction("IsMusicPlaying",           Sc_IsMusicPlaying);
-	ccAddExternalStaticFunction("IsMusicVoxAvailable",      Sc_IsMusicVoxAvailable);
-	ccAddExternalStaticFunction("IsObjectAnimating",        Sc_IsObjectAnimating);
-	ccAddExternalStaticFunction("IsObjectMoving",           Sc_IsObjectMoving);
-	ccAddExternalStaticFunction("IsObjectOn",               Sc_IsObjectOn);
-	ccAddExternalStaticFunction("IsOverlayValid",           Sc_IsOverlayValid);
-	ccAddExternalStaticFunction("IsSoundPlaying",           Sc_IsSoundPlaying);
-	ccAddExternalStaticFunction("IsTimerExpired",           Sc_IsTimerExpired);
-	ccAddExternalStaticFunction("IsTranslationAvailable",   Sc_IsTranslationAvailable);
-	ccAddExternalStaticFunction("IsVoxAvailable",           Sc_IsVoxAvailable);
-	ccAddExternalStaticFunction("ListBoxAdd",               Sc_ListBoxAdd);
-	ccAddExternalStaticFunction("ListBoxClear",             Sc_ListBoxClear);
-	ccAddExternalStaticFunction("ListBoxDirList",           Sc_ListBoxDirList);
-	ccAddExternalStaticFunction("ListBoxGetItemText",       Sc_ListBoxGetItemText);
-	ccAddExternalStaticFunction("ListBoxGetNumItems",       Sc_ListBoxGetNumItems);
-	ccAddExternalStaticFunction("ListBoxGetSelected",       Sc_ListBoxGetSelected);
-	ccAddExternalStaticFunction("ListBoxRemove",            Sc_ListBoxRemove);
-	ccAddExternalStaticFunction("ListBoxSaveGameList",      Sc_ListBoxSaveGameList);
-	ccAddExternalStaticFunction("ListBoxSetSelected",       Sc_ListBoxSetSelected);
-	ccAddExternalStaticFunction("ListBoxSetTopItem",        Sc_ListBoxSetTopItem);
-	ccAddExternalStaticFunction("LoadImageFile",            Sc_LoadImageFile);
-	ccAddExternalStaticFunction("LoadSaveSlotScreenshot",   Sc_LoadSaveSlotScreenshot);
-	ccAddExternalStaticFunction("LoseInventory",            Sc_lose_inventory);
-	ccAddExternalStaticFunction("LoseInventoryFromCharacter", Sc_LoseInventoryFromCharacter);
-	ccAddExternalStaticFunction("MergeObject",              Sc_MergeObject);
-	ccAddExternalStaticFunction("MoveCharacter",            Sc_MoveCharacter);
-	ccAddExternalStaticFunction("MoveCharacterBlocking",    Sc_MoveCharacterBlocking);
-	ccAddExternalStaticFunction("MoveCharacterDirect",      Sc_MoveCharacterDirect);
-	ccAddExternalStaticFunction("MoveCharacterPath",        Sc_MoveCharacterPath);
-	ccAddExternalStaticFunction("MoveCharacterStraight",    Sc_MoveCharacterStraight);
-	ccAddExternalStaticFunction("MoveCharacterToHotspot",   Sc_MoveCharacterToHotspot);
-	ccAddExternalStaticFunction("MoveCharacterToObject",    Sc_MoveCharacterToObject);
-	ccAddExternalStaticFunction("MoveObject",               Sc_MoveObject);
-	ccAddExternalStaticFunction("MoveObjectDirect",         Sc_MoveObjectDirect);
-	ccAddExternalStaticFunction("MoveOverlay",              Sc_MoveOverlay);
-	ccAddExternalStaticFunction("MoveToWalkableArea",       Sc_MoveToWalkableArea);
-	ccAddExternalStaticFunction("NewRoom",                  Sc_NewRoom);
-	ccAddExternalStaticFunction("NewRoomEx",                Sc_NewRoomEx);
-	ccAddExternalStaticFunction("NewRoomNPC",               Sc_NewRoomNPC);
-	ccAddExternalStaticFunction("ObjectOff",                Sc_ObjectOff);
-	ccAddExternalStaticFunction("ObjectOn",                 Sc_ObjectOn);
-	ccAddExternalStaticFunction("ParseText",                Sc_ParseText);
-	ccAddExternalStaticFunction("PauseGame",                Sc_PauseGame);
-	ccAddExternalStaticFunction("PlayAmbientSound",         Sc_PlayAmbientSound);
-	ccAddExternalStaticFunction("PlayFlic",                 Sc_play_flc_file);
-	ccAddExternalStaticFunction("PlayMP3File",              Sc_PlayMP3File);
-	ccAddExternalStaticFunction("PlayMusic",                Sc_PlayMusicResetQueue);
-	ccAddExternalStaticFunction("PlayMusicQueued",          Sc_PlayMusicQueued);
-	ccAddExternalStaticFunction("PlaySilentMIDI",           Sc_PlaySilentMIDI);
-	ccAddExternalStaticFunction("PlaySound",                Sc_play_sound);
-	ccAddExternalStaticFunction("PlaySoundEx",              Sc_PlaySoundEx);
-	ccAddExternalStaticFunction("PlayVideo",                Sc_scrPlayVideo);
-	ccAddExternalStaticFunction("QuitGame",                 Sc_QuitGame);
-	ccAddExternalStaticFunction("Random",                   Sc_Rand);
-	ccAddExternalStaticFunction("RawClearScreen",           Sc_RawClear);
-	ccAddExternalStaticFunction("RawDrawCircle",            Sc_RawDrawCircle);
-	ccAddExternalStaticFunction("RawDrawFrameTransparent",  Sc_RawDrawFrameTransparent);
-	ccAddExternalStaticFunction("RawDrawImage",             Sc_RawDrawImage);
-	ccAddExternalStaticFunction("RawDrawImageOffset",       Sc_RawDrawImageOffset);
-	ccAddExternalStaticFunction("RawDrawImageResized",      Sc_RawDrawImageResized);
-	ccAddExternalStaticFunction("RawDrawImageTransparent",  Sc_RawDrawImageTransparent);
-	ccAddExternalStaticFunction("RawDrawLine",              Sc_RawDrawLine);
-	ccAddExternalStaticFunction("RawDrawRectangle",         Sc_RawDrawRectangle);
-	ccAddExternalStaticFunction("RawDrawTriangle",          Sc_RawDrawTriangle);
-	ccAddExternalStaticFunction("RawPrint",                 Sc_RawPrint);
-	ccAddExternalStaticFunction("RawPrintMessageWrapped",   Sc_RawPrintMessageWrapped);
-	ccAddExternalStaticFunction("RawRestoreScreen",         Sc_RawRestoreScreen);
-	ccAddExternalStaticFunction("RawRestoreScreenTinted",   Sc_RawRestoreScreenTinted);
-	ccAddExternalStaticFunction("RawSaveScreen",            Sc_RawSaveScreen);
-	ccAddExternalStaticFunction("RawSetColor",              Sc_RawSetColor);
-	ccAddExternalStaticFunction("RawSetColorRGB",           Sc_RawSetColorRGB);
-	ccAddExternalStaticFunction("RefreshMouse",             Sc_RefreshMouse);
-	ccAddExternalStaticFunction("ReleaseCharacterView",     Sc_ReleaseCharacterView);
-	ccAddExternalStaticFunction("ReleaseViewport",          Sc_ReleaseViewport);
-	ccAddExternalStaticFunction("RemoveObjectTint",         Sc_RemoveObjectTint);
-	ccAddExternalStaticFunction("RemoveOverlay",            Sc_RemoveOverlay);
-	ccAddExternalStaticFunction("RemoveWalkableArea",       Sc_RemoveWalkableArea);
-	ccAddExternalStaticFunction("ResetRoom",                Sc_ResetRoom);
-	ccAddExternalStaticFunction("RestartGame",              Sc_restart_game);
-	ccAddExternalStaticFunction("RestoreGameDialog",        Sc_restore_game_dialog);
-	ccAddExternalStaticFunction("RestoreGameSlot",          Sc_RestoreGameSlot);
-	ccAddExternalStaticFunction("RestoreWalkableArea",      Sc_RestoreWalkableArea);
-	ccAddExternalStaticFunction("RunAGSGame",               Sc_RunAGSGame);
-	ccAddExternalStaticFunction("RunCharacterInteraction",  Sc_RunCharacterInteraction);
-	ccAddExternalStaticFunction("RunDialog",                Sc_RunDialog);
-	ccAddExternalStaticFunction("RunHotspotInteraction",    Sc_RunHotspotInteraction);
-	ccAddExternalStaticFunction("RunInventoryInteraction",  Sc_RunInventoryInteraction);
-	ccAddExternalStaticFunction("RunObjectInteraction",     Sc_RunObjectInteraction);
-	ccAddExternalStaticFunction("RunRegionInteraction",     Sc_RunRegionInteraction);
-	ccAddExternalStaticFunction("Said",                     Sc_Said);
-	ccAddExternalStaticFunction("SaidUnknownWord",          Sc_SaidUnknownWord);
-	ccAddExternalStaticFunction("SaveCursorForLocationChange", Sc_SaveCursorForLocationChange);
-	ccAddExternalStaticFunction("SaveGameDialog",           Sc_save_game_dialog);
-	ccAddExternalStaticFunction("SaveGameSlot",             Sc_save_game);
-	ccAddExternalStaticFunction("SaveScreenShot",           Sc_SaveScreenShot);
-	ccAddExternalStaticFunction("SeekMIDIPosition",         Sc_SeekMIDIPosition);
-	ccAddExternalStaticFunction("SeekMODPattern",           Sc_SeekMODPattern);
-	ccAddExternalStaticFunction("SeekMP3PosMillis",         Sc_SeekMP3PosMillis);
-	ccAddExternalStaticFunction("SetActiveInventory",       Sc_SetActiveInventory);
-	ccAddExternalStaticFunction("SetAmbientTint",           Sc_SetAmbientTint);
-	ccAddExternalStaticFunction("SetAmbientLightLevel",     Sc_SetAmbientLightLevel);
-	ccAddExternalStaticFunction("SetAreaLightLevel",        Sc_SetAreaLightLevel);
-	ccAddExternalStaticFunction("SetAreaScaling",           Sc_SetAreaScaling);
-	ccAddExternalStaticFunction("SetBackgroundFrame",       Sc_SetBackgroundFrame);
-	ccAddExternalStaticFunction("SetButtonPic",             Sc_SetButtonPic);
-	ccAddExternalStaticFunction("SetButtonText",            Sc_SetButtonText);
-	ccAddExternalStaticFunction("SetChannelVolume",         Sc_SetChannelVolume);
-	ccAddExternalStaticFunction("SetCharacterBaseline",     Sc_SetCharacterBaseline);
-	ccAddExternalStaticFunction("SetCharacterClickable",    Sc_SetCharacterClickable);
-	ccAddExternalStaticFunction("SetCharacterFrame",        Sc_SetCharacterFrame);
-	ccAddExternalStaticFunction("SetCharacterIdle",         Sc_SetCharacterIdle);
-	ccAddExternalStaticFunction("SetCharacterIgnoreLight",  Sc_SetCharacterIgnoreLight);
-	ccAddExternalStaticFunction("SetCharacterIgnoreWalkbehinds", Sc_SetCharacterIgnoreWalkbehinds);
-	ccAddExternalStaticFunction("SetCharacterProperty",     Sc_SetCharacterProperty);
-	ccAddExternalStaticFunction("SetCharacterBlinkView",    Sc_SetCharacterBlinkView);
-	ccAddExternalStaticFunction("SetCharacterSpeechView",   Sc_SetCharacterSpeechView);
-	ccAddExternalStaticFunction("SetCharacterSpeed",        Sc_SetCharacterSpeed);
-	ccAddExternalStaticFunction("SetCharacterSpeedEx",      Sc_SetCharacterSpeedEx);
-	ccAddExternalStaticFunction("SetCharacterTransparency", Sc_SetCharacterTransparency);
-	ccAddExternalStaticFunction("SetCharacterView",         Sc_SetCharacterView);
-	ccAddExternalStaticFunction("SetCharacterViewEx",       Sc_SetCharacterViewEx);
-	ccAddExternalStaticFunction("SetCharacterViewOffset",   Sc_SetCharacterViewOffset);
-	ccAddExternalStaticFunction("SetCursorMode",            Sc_set_cursor_mode);
-	ccAddExternalStaticFunction("SetDefaultCursor",         Sc_set_default_cursor);
-	ccAddExternalStaticFunction("SetDialogOption",          Sc_SetDialogOption);
-	ccAddExternalStaticFunction("SetDigitalMasterVolume",   Sc_SetDigitalMasterVolume);
-	ccAddExternalStaticFunction("SetFadeColor",             Sc_SetFadeColor);
-	ccAddExternalStaticFunction("SetFrameSound",            Sc_SetFrameSound);
-	ccAddExternalStaticFunction("SetGameOption",            Sc_SetGameOption);
-	ccAddExternalStaticFunction("SetGameSpeed",             Sc_SetGameSpeed);
-	ccAddExternalStaticFunction("SetGlobalInt",             Sc_SetGlobalInt);
-	ccAddExternalStaticFunction("SetGlobalString",          Sc_SetGlobalString);
-	ccAddExternalStaticFunction("SetGraphicalVariable",     Sc_SetGraphicalVariable);
-	ccAddExternalStaticFunction("SetGUIBackgroundPic",      Sc_SetGUIBackgroundPic);
-	ccAddExternalStaticFunction("SetGUIClickable",          Sc_SetGUIClickable);
-	ccAddExternalStaticFunction("SetGUIObjectEnabled",      Sc_SetGUIObjectEnabled);
-	ccAddExternalStaticFunction("SetGUIObjectPosition",     Sc_SetGUIObjectPosition);
-	ccAddExternalStaticFunction("SetGUIObjectSize",         Sc_SetGUIObjectSize);
-	ccAddExternalStaticFunction("SetGUIPosition",           Sc_SetGUIPosition);
-	ccAddExternalStaticFunction("SetGUISize",               Sc_SetGUISize);
-	ccAddExternalStaticFunction("SetGUITransparency",       Sc_SetGUITransparency);
-	ccAddExternalStaticFunction("SetGUIZOrder",             Sc_SetGUIZOrder);
-	ccAddExternalStaticFunction("SetInvItemName",           Sc_SetInvItemName);
-	ccAddExternalStaticFunction("SetInvItemPic",            Sc_set_inv_item_pic);
-	ccAddExternalStaticFunction("SetInvDimensions",         Sc_SetInvDimensions);
-	ccAddExternalStaticFunction("SetLabelColor",            Sc_SetLabelColor);
-	ccAddExternalStaticFunction("SetLabelFont",             Sc_SetLabelFont);
-	ccAddExternalStaticFunction("SetLabelText",             Sc_SetLabelText);
-	ccAddExternalStaticFunction("SetMouseBounds",           Sc_SetMouseBounds);
-	ccAddExternalStaticFunction("SetMouseCursor",           Sc_set_mouse_cursor);
-	ccAddExternalStaticFunction("SetMousePosition",         Sc_SetMousePosition);
-	ccAddExternalStaticFunction("SetMultitaskingMode",      Sc_SetMultitasking);
-	ccAddExternalStaticFunction("SetMusicMasterVolume",     Sc_SetMusicMasterVolume);
-	ccAddExternalStaticFunction("SetMusicRepeat",           Sc_SetMusicRepeat);
-	ccAddExternalStaticFunction("SetMusicVolume",           Sc_SetMusicVolume);
-	ccAddExternalStaticFunction("SetNextCursorMode",        Sc_SetNextCursor);
-	ccAddExternalStaticFunction("SetNextScreenTransition",  Sc_SetNextScreenTransition);
-	ccAddExternalStaticFunction("SetNormalFont",            Sc_SetNormalFont);
-	ccAddExternalStaticFunction("SetObjectBaseline",        Sc_SetObjectBaseline);
-	ccAddExternalStaticFunction("SetObjectClickable",       Sc_SetObjectClickable);
-	ccAddExternalStaticFunction("SetObjectFrame",           Sc_SetObjectFrame);
-	ccAddExternalStaticFunction("SetObjectGraphic",         Sc_SetObjectGraphic);
-	ccAddExternalStaticFunction("SetObjectIgnoreWalkbehinds", Sc_SetObjectIgnoreWalkbehinds);
-	ccAddExternalStaticFunction("SetObjectPosition",        Sc_SetObjectPosition);
-	ccAddExternalStaticFunction("SetObjectTint",            Sc_SetObjectTint);
-	ccAddExternalStaticFunction("SetObjectTransparency",    Sc_SetObjectTransparency);
-	ccAddExternalStaticFunction("SetObjectView",            Sc_SetObjectView);
-	//  ccAddExternalStaticFunction("SetPalette",           scSetPal);
-	ccAddExternalStaticFunction("SetPalRGB",                Sc_SetPalRGB);
-	ccAddExternalStaticFunction("SetPlayerCharacter",       Sc_SetPlayerCharacter);
-	ccAddExternalStaticFunction("SetRegionTint",            Sc_SetRegionTint);
-	ccAddExternalStaticFunction("SetRestartPoint",          Sc_SetRestartPoint);
-	ccAddExternalStaticFunction("SetScreenTransition",      Sc_SetScreenTransition);
-	ccAddExternalStaticFunction("SetSkipSpeech",            Sc_SetSkipSpeech);
-	ccAddExternalStaticFunction("SetSliderValue",           Sc_SetSliderValue);
-	ccAddExternalStaticFunction("SetSoundVolume",           Sc_SetSoundVolume);
-	ccAddExternalStaticFunction("SetSpeechFont",            Sc_SetSpeechFont);
-	ccAddExternalStaticFunction("SetSpeechStyle",           Sc_SetSpeechStyle);
-	ccAddExternalStaticFunction("SetSpeechVolume",          Sc_SetSpeechVolume);
-	ccAddExternalStaticFunction("SetTalkingColor",          Sc_SetTalkingColor);
-	ccAddExternalStaticFunction("SetTextBoxFont",           Sc_SetTextBoxFont);
-	ccAddExternalStaticFunction("SetTextBoxText",           Sc_SetTextBoxText);
-	ccAddExternalStaticFunction("SetTextOverlay",           Sc_SetTextOverlay);
-	ccAddExternalStaticFunction("SetTextWindowGUI",         Sc_SetTextWindowGUI);
-	ccAddExternalStaticFunction("SetTimer",                 Sc_script_SetTimer);
-	ccAddExternalStaticFunction("SetViewport",              Sc_SetViewport);
-	ccAddExternalStaticFunction("SetVoiceMode",             Sc_SetVoiceMode);
-	ccAddExternalStaticFunction("SetWalkBehindBase",        Sc_SetWalkBehindBase);
-	ccAddExternalStaticFunction("ShakeScreen",              Sc_ShakeScreen);
-	ccAddExternalStaticFunction("ShakeScreenBackground",    Sc_ShakeScreenBackground);
-	ccAddExternalStaticFunction("ShowMouseCursor",          Sc_ShowMouseCursor);
-	ccAddExternalStaticFunction("SkipCutscene",             Sc_SkipCutscene);
-	ccAddExternalStaticFunction("SkipUntilCharacterStops",  Sc_SkipUntilCharacterStops);
-	ccAddExternalStaticFunction("StartCutscene",            Sc_StartCutscene);
-	ccAddExternalStaticFunction("StartRecording",           Sc_scStartRecording);
-	ccAddExternalStaticFunction("StopAmbientSound",         Sc_StopAmbientSound);
-	ccAddExternalStaticFunction("StopChannel",              Sc_stop_and_destroy_channel);
-	ccAddExternalStaticFunction("StopDialog",               Sc_StopDialog);
-	ccAddExternalStaticFunction("StopMoving",               Sc_StopMoving);
-	ccAddExternalStaticFunction("StopMusic",                Sc_scr_StopMusic);
-	ccAddExternalStaticFunction("StopObjectMoving",         Sc_StopObjectMoving);
-	ccAddExternalStaticFunction("StrCat",                   Sc_sc_strcat);
-	ccAddExternalStaticFunction("StrCaseComp",              Sc_stricmp);
-	ccAddExternalStaticFunction("StrComp",                  Sc_strcmp);
-	ccAddExternalStaticFunction("StrContains",              Sc_StrContains);
-	ccAddExternalStaticFunction("StrCopy",                  Sc_sc_strcpy);
-	ccAddExternalStaticFunction("StrFormat",                Sc_sc_sprintf);
-	ccAddExternalStaticFunction("StrGetCharAt",             Sc_StrGetCharAt);
-	ccAddExternalStaticFunction("StringToInt",              Sc_StringToInt);
-	ccAddExternalStaticFunction("StrLen",                   Sc_strlen);
-	ccAddExternalStaticFunction("StrSetCharAt",             Sc_StrSetCharAt);
-	ccAddExternalStaticFunction("StrToLowerCase",           Sc_sc_strlower);
-	ccAddExternalStaticFunction("StrToUpperCase",           Sc_sc_strupper);
-	ccAddExternalStaticFunction("TintScreen",               Sc_TintScreen);
-	ccAddExternalStaticFunction("UnPauseGame",              Sc_UnPauseGame);
-	ccAddExternalStaticFunction("UpdateInventory",          Sc_update_invorder);
-	ccAddExternalStaticFunction("UpdatePalette",            Sc_UpdatePalette);
-	ccAddExternalStaticFunction("Wait",                     Sc_scrWait);
-	ccAddExternalStaticFunction("WaitKey",                  Sc_WaitKey);
-	ccAddExternalStaticFunction("WaitMouse",                Sc_WaitMouse);
-	ccAddExternalStaticFunction("WaitMouseKey",             Sc_WaitMouseKey);
-	ccAddExternalStaticFunction("WaitInput",                Sc_WaitInput);
-	ccAddExternalStaticFunction("SkipWait",                 Sc_SkipWait);
+	ScFnRegister global_api[] = {
+		{"AbortGame", Sc_sc_AbortGame},
+		{"AddInventory", API_FN_PAIR(add_inventory)},
+		{"AddInventoryToCharacter", API_FN_PAIR(AddInventoryToCharacter)},
+		{"AnimateButton", API_FN_PAIR(AnimateButton)},
+		{"AnimateCharacter", API_FN_PAIR(AnimateCharacter4)},
+		{"AnimateCharacterEx", API_FN_PAIR(AnimateCharacter6)},
+		{"AnimateObject", API_FN_PAIR(AnimateObject4)},
+		{"AnimateObjectEx", API_FN_PAIR(AnimateObject6)},
+		{"AreCharactersColliding", API_FN_PAIR(AreCharactersColliding)},
+		{"AreCharObjColliding", API_FN_PAIR(AreCharObjColliding)},
+		{"AreObjectsColliding", API_FN_PAIR(AreObjectsColliding)},
+		{"AreThingsOverlapping", API_FN_PAIR(AreThingsOverlapping)},
+		{"CallRoomScript", API_FN_PAIR(CallRoomScript)},
+		{"CDAudio", API_FN_PAIR(cd_manager)},
+		{"CentreGUI", API_FN_PAIR(CentreGUI)},
+		{"ChangeCharacterView", API_FN_PAIR(ChangeCharacterView)},
+		{"ChangeCursorGraphic", API_FN_PAIR(ChangeCursorGraphic)},
+		{"ChangeCursorHotspot", API_FN_PAIR(ChangeCursorHotspot)},
+		{"ClaimEvent", API_FN_PAIR(ClaimEvent)},
+		{"CreateGraphicOverlay", API_FN_PAIR(CreateGraphicOverlay)},
+		{"CreateTextOverlay", Sc_CreateTextOverlay},
+		{"CyclePalette", API_FN_PAIR(CyclePalette)},
+		{"Debug", API_FN_PAIR(script_debug)},
+		{"DeleteSaveSlot", API_FN_PAIR(DeleteSaveSlot)},
+		{"DeleteSprite", API_FN_PAIR(free_dynamic_sprite)},
+		{"DisableCursorMode", API_FN_PAIR(disable_cursor_mode)},
+		{"DisableGroundLevelAreas", API_FN_PAIR(DisableGroundLevelAreas)},
+		{"DisableHotspot", API_FN_PAIR(DisableHotspot)},
+		{"DisableInterface", API_FN_PAIR(DisableInterface)},
+		{"DisableRegion", API_FN_PAIR(DisableRegion)},
+		{"Display", Sc_Display},
+		{"DisplayAt", Sc_DisplayAt},
+		{"DisplayAtY", API_FN_PAIR(DisplayAtY)},
+		{"DisplayMessage", API_FN_PAIR(DisplayMessage)},
+		{"DisplayMessageAtY", API_FN_PAIR(DisplayMessageAtY)},
+		{"DisplayMessageBar", API_FN_PAIR(DisplayMessageBar)},
+		{"DisplaySpeech", Sc_sc_displayspeech},
+		{"DisplaySpeechAt", API_FN_PAIR(DisplaySpeechAt)},
+		{"DisplaySpeechBackground", API_FN_PAIR(DisplaySpeechBackground)},
+		{"DisplayThought", Sc_DisplayThought},
+		{"DisplayTopBar", Sc_DisplayTopBar},
+		{"EnableCursorMode", API_FN_PAIR(enable_cursor_mode)},
+		{"EnableGroundLevelAreas", API_FN_PAIR(EnableGroundLevelAreas)},
+		{"EnableHotspot", API_FN_PAIR(EnableHotspot)},
+		{"EnableInterface", API_FN_PAIR(EnableInterface)},
+		{"EnableRegion", API_FN_PAIR(EnableRegion)},
+		{"EndCutscene", API_FN_PAIR(EndCutscene)},
+		{"FaceCharacter", API_FN_PAIR(FaceCharacter)},
+		{"FaceLocation", API_FN_PAIR(FaceLocation)},
+		{"FadeIn", API_FN_PAIR(FadeIn)},
+		{"FadeOut", API_FN_PAIR(FadeOut)},
+		{"FileClose", API_FN_PAIR(FileClose)},
+		{"FileIsEOF", API_FN_PAIR(FileIsEOF)},
+		{"FileIsError", API_FN_PAIR(FileIsError)},
+		// NOTE: FileOpenCMode is a backwards-compatible replacement for old-style global script function FileOpen
+		{"FileOpen", API_FN_PAIR(FileOpenCMode)},
+		{"FileRead", API_FN_PAIR(FileRead)},
+		{"FileReadInt", API_FN_PAIR(FileReadInt)},
+		{"FileReadRawChar", API_FN_PAIR(FileReadRawChar)},
+		{"FileReadRawInt", API_FN_PAIR(FileReadRawInt)},
+		{"FileWrite", API_FN_PAIR(FileWrite)},
+		{"FileWriteInt", API_FN_PAIR(FileWriteInt)},
+		{"FileWriteRawChar", API_FN_PAIR(FileWriteRawChar)},
+		{"FileWriteRawLine", API_FN_PAIR(FileWriteRawLine)},
+		{"FindGUIID", API_FN_PAIR(FindGUIID)},
+		{"FlipScreen", API_FN_PAIR(FlipScreen)},
+		{"FloatToInt", API_FN_PAIR(FloatToInt)},
+		{"FollowCharacter", API_FN_PAIR(FollowCharacter)},
+		{"FollowCharacterEx", API_FN_PAIR(FollowCharacterEx)},
+		{"GetBackgroundFrame", API_FN_PAIR(GetBackgroundFrame)},
+		{"GetButtonPic", API_FN_PAIR(GetButtonPic)},
+		{"GetCharacterAt", API_FN_PAIR(GetCharIDAtScreen)},
+		{"GetCharacterProperty", API_FN_PAIR(GetCharacterProperty)},
+		{"GetCharacterPropertyText", API_FN_PAIR(GetCharacterPropertyText)},
+		{"GetCurrentMusic", API_FN_PAIR(GetCurrentMusic)},
+		{"GetCursorMode", API_FN_PAIR(GetCursorMode)},
+		{"GetDialogOption", API_FN_PAIR(GetDialogOption)},
+		{"GetGameOption", API_FN_PAIR(GetGameOption)},
+		{"GetGameParameter", API_FN_PAIR(GetGameParameter)},
+		{"GetGameSpeed", API_FN_PAIR(GetGameSpeed)},
+		{"GetGlobalInt", API_FN_PAIR(GetGlobalInt)},
+		{"GetGlobalString", API_FN_PAIR(GetGlobalString)},
+		{"GetGraphicalVariable", API_FN_PAIR(GetGraphicalVariable)},
+		{"GetGUIAt", API_FN_PAIR(GetGUIAt)},
+		{"GetGUIObjectAt", API_FN_PAIR(GetGUIObjectAt)},
+		{"GetHotspotAt", API_FN_PAIR(GetHotspotIDAtScreen)},
+		{"GetHotspotName", API_FN_PAIR(GetHotspotName)},
+		{"GetHotspotPointX", API_FN_PAIR(GetHotspotPointX)},
+		{"GetHotspotPointY", API_FN_PAIR(GetHotspotPointY)},
+		{"GetHotspotProperty", API_FN_PAIR(GetHotspotProperty)},
+		{"GetHotspotPropertyText", API_FN_PAIR(GetHotspotPropertyText)},
+		{"GetInvAt", API_FN_PAIR(GetInvAt)},
+		{"GetInvGraphic", API_FN_PAIR(GetInvGraphic)},
+		{"GetInvName", API_FN_PAIR(GetInvName)},
+		{"GetInvProperty", API_FN_PAIR(GetInvProperty)},
+		{"GetInvPropertyText", API_FN_PAIR(GetInvPropertyText)},
+		{"GetLocationName", API_FN_PAIR(GetLocationName)},
+		{"GetLocationType", API_FN_PAIR(GetLocationType)},
+		{"GetMessageText", API_FN_PAIR(GetMessageText)},
+		{"GetMIDIPosition", API_FN_PAIR(GetMIDIPosition)},
+		{"GetMP3PosMillis", API_FN_PAIR(GetMP3PosMillis)},
+		{"GetObjectAt", API_FN_PAIR(GetObjectIDAtScreen)},
+		{"GetObjectBaseline", API_FN_PAIR(GetObjectBaseline)},
+		{"GetObjectGraphic", API_FN_PAIR(GetObjectGraphic)},
+		{"GetObjectName", API_FN_PAIR(GetObjectName)},
+		{"GetObjectProperty", API_FN_PAIR(GetObjectProperty)},
+		{"GetObjectPropertyText", API_FN_PAIR(GetObjectPropertyText)},
+		{"GetObjectX", API_FN_PAIR(GetObjectX)},
+		{"GetObjectY", API_FN_PAIR(GetObjectY)},
+		{"GetPlayerCharacter", API_FN_PAIR(GetPlayerCharacter)},
+		{"GetRawTime", API_FN_PAIR(GetRawTime)},
+		{"GetRegionAt", API_FN_PAIR(GetRegionIDAtRoom)},
+		{"GetRoomProperty", API_FN_PAIR(Room_GetProperty)},
+		{"GetRoomPropertyText", API_FN_PAIR(GetRoomPropertyText)},
+		{"GetSaveSlotDescription", API_FN_PAIR(GetSaveSlotDescription)},
+		{"GetScalingAt", API_FN_PAIR(GetScalingAt)},
+		{"GetSliderValue", API_FN_PAIR(GetSliderValue)},
+		{"GetTextBoxText", API_FN_PAIR(GetTextBoxText)},
+		{"GetTextHeight", API_FN_PAIR(GetTextHeight)},
+		{"GetTextWidth", API_FN_PAIR(GetTextWidth)},
+		{"GetFontHeight", API_FN_PAIR(GetFontHeight)},
+		{"GetFontLineSpacing", API_FN_PAIR(GetFontLineSpacing)},
+		{"GetTime", API_FN_PAIR(sc_GetTime)},
+		{"GetTranslation", API_FN_PAIR(get_translation)},
+		{"GetTranslationName", API_FN_PAIR(GetTranslationName)},
+		{"GetViewportX", API_FN_PAIR(GetViewportX)},
+		{"GetViewportY", API_FN_PAIR(GetViewportY)},
+		{"GetWalkableAreaAtRoom", API_FN_PAIR(GetWalkableAreaAtRoom)},
+		{"GetWalkableAreaAt", API_FN_PAIR(GetWalkableAreaAtScreen)},
+		{"GetWalkableAreaAtScreen", API_FN_PAIR(GetWalkableAreaAtScreen)},
+		{"GetDrawingSurfaceForWalkableArea", API_FN_PAIR(GetDrawingSurfaceForWalkableArea)},
+		{"GetDrawingSurfaceForWalkbehind", API_FN_PAIR(GetDrawingSurfaceForWalkbehind)},
+		{"GiveScore", API_FN_PAIR(GiveScore)},
+		{"HasPlayerBeenInRoom", API_FN_PAIR(HasPlayerBeenInRoom)},
+		{"HideMouseCursor", API_FN_PAIR(HideMouseCursor)},
+		{"InputBox", API_FN_PAIR(sc_inputbox)},
+		{"InterfaceOff", API_FN_PAIR(InterfaceOff)},
+		{"InterfaceOn", API_FN_PAIR(InterfaceOn)},
+		{"IntToFloat", API_FN_PAIR(IntToFloat)},
+		{"InventoryScreen", API_FN_PAIR(sc_invscreen)},
+		{"IsButtonDown", API_FN_PAIR(IsButtonDown)},
+		{"IsChannelPlaying", API_FN_PAIR(IsChannelPlaying)},
+		{"IsGamePaused", API_FN_PAIR(IsGamePaused)},
+		{"IsGUIOn", API_FN_PAIR(IsGUIOn)},
+		{"IsInteractionAvailable", API_FN_PAIR(IsInteractionAvailable)},
+		{"IsInventoryInteractionAvailable", API_FN_PAIR(IsInventoryInteractionAvailable)},
+		{"IsInterfaceEnabled", API_FN_PAIR(IsInterfaceEnabled)},
+		{"IsKeyPressed", API_FN_PAIR(IsKeyPressed)},
+		{"IsMusicPlaying", API_FN_PAIR(IsMusicPlaying)},
+		{"IsMusicVoxAvailable", API_FN_PAIR(IsMusicVoxAvailable)},
+		{"IsObjectAnimating", API_FN_PAIR(IsObjectAnimating)},
+		{"IsObjectMoving", API_FN_PAIR(IsObjectMoving)},
+		{"IsObjectOn", API_FN_PAIR(IsObjectOn)},
+		{"IsOverlayValid", API_FN_PAIR(IsOverlayValid)},
+		{"IsSoundPlaying", API_FN_PAIR(IsSoundPlaying)},
+		{"IsTimerExpired", API_FN_PAIR(IsTimerExpired)},
+		{"IsTranslationAvailable", API_FN_PAIR(IsTranslationAvailable)},
+		{"IsVoxAvailable", API_FN_PAIR(IsVoxAvailable)},
+		{"ListBoxAdd", API_FN_PAIR(ListBoxAdd)},
+		{"ListBoxClear", API_FN_PAIR(ListBoxClear)},
+		{"ListBoxDirList", API_FN_PAIR(ListBoxDirList)},
+		{"ListBoxGetItemText", API_FN_PAIR(ListBoxGetItemText)},
+		{"ListBoxGetNumItems", API_FN_PAIR(ListBoxGetNumItems)},
+		{"ListBoxGetSelected", API_FN_PAIR(ListBoxGetSelected)},
+		{"ListBoxRemove", API_FN_PAIR(ListBoxRemove)},
+		{"ListBoxSaveGameList", API_FN_PAIR(ListBoxSaveGameList)},
+		{"ListBoxSetSelected", API_FN_PAIR(ListBoxSetSelected)},
+		{"ListBoxSetTopItem", API_FN_PAIR(ListBoxSetTopItem)},
+		{"LoadImageFile", API_FN_PAIR(LoadImageFile)},
+		{"LoadSaveSlotScreenshot", API_FN_PAIR(LoadSaveSlotScreenshot)},
+		{"LoseInventory", API_FN_PAIR(lose_inventory)},
+		{"LoseInventoryFromCharacter", API_FN_PAIR(LoseInventoryFromCharacter)},
+		{"MergeObject", API_FN_PAIR(MergeObject)},
+		{"MoveCharacter", API_FN_PAIR(MoveCharacter)},
+		{"MoveCharacterBlocking", API_FN_PAIR(MoveCharacterBlocking)},
+		{"MoveCharacterDirect", API_FN_PAIR(MoveCharacterDirect)},
+		{"MoveCharacterPath", API_FN_PAIR(MoveCharacterPath)},
+		{"MoveCharacterStraight", API_FN_PAIR(MoveCharacterStraight)},
+		{"MoveCharacterToHotspot", API_FN_PAIR(MoveCharacterToHotspot)},
+		{"MoveCharacterToObject", API_FN_PAIR(MoveCharacterToObject)},
+		{"MoveObject", API_FN_PAIR(MoveObject)},
+		{"MoveObjectDirect", API_FN_PAIR(MoveObjectDirect)},
+		{"MoveOverlay", API_FN_PAIR(MoveOverlay)},
+		{"MoveToWalkableArea", API_FN_PAIR(MoveToWalkableArea)},
+		{"NewRoom", API_FN_PAIR(NewRoom)},
+		{"NewRoomEx", API_FN_PAIR(NewRoomEx)},
+		{"NewRoomNPC", API_FN_PAIR(NewRoomNPC)},
+		{"ObjectOff", API_FN_PAIR(ObjectOff)},
+		{"ObjectOn", API_FN_PAIR(ObjectOn)},
+		{"ParseText", API_FN_PAIR(ParseText)},
+		{"PauseGame", API_FN_PAIR(PauseGame)},
+		{"PlayAmbientSound", API_FN_PAIR(PlayAmbientSound)},
+		{"PlayFlic", API_FN_PAIR(PlayFlic)},
+		{"PlayMP3File", API_FN_PAIR(PlayMP3File)},
+		{"PlayMusic", API_FN_PAIR(PlayMusicResetQueue)},
+		{"PlayMusicQueued", API_FN_PAIR(PlayMusicQueued)},
+		{"PlaySilentMIDI", API_FN_PAIR(PlaySilentMIDI)},
+		{"PlaySound", API_FN_PAIR(play_sound)},
+		{"PlaySoundEx", API_FN_PAIR(PlaySoundEx)},
+		{"PlayVideo", API_FN_PAIR(PlayVideo)},
+		{"QuitGame", API_FN_PAIR(QuitGame)},
+		{"Random", Sc_Rand},
+		{"RawClearScreen", API_FN_PAIR(RawClear)},
+		{"RawDrawCircle", API_FN_PAIR(RawDrawCircle)},
+		{"RawDrawFrameTransparent", API_FN_PAIR(RawDrawFrameTransparent)},
+		{"RawDrawImage", API_FN_PAIR(RawDrawImage)},
+		{"RawDrawImageOffset", API_FN_PAIR(RawDrawImageOffset)},
+		{"RawDrawImageResized", API_FN_PAIR(RawDrawImageResized)},
+		{"RawDrawImageTransparent", API_FN_PAIR(RawDrawImageTransparent)},
+		{"RawDrawLine", API_FN_PAIR(RawDrawLine)},
+		{"RawDrawRectangle", API_FN_PAIR(RawDrawRectangle)},
+		{"RawDrawTriangle", API_FN_PAIR(RawDrawTriangle)},
+		{"RawPrint", Sc_RawPrint},
+		{"RawPrintMessageWrapped", API_FN_PAIR(RawPrintMessageWrapped)},
+		{"RawRestoreScreen", API_FN_PAIR(RawRestoreScreen)},
+		{"RawRestoreScreenTinted", API_FN_PAIR(RawRestoreScreenTinted)},
+		{"RawSaveScreen", API_FN_PAIR(RawSaveScreen)},
+		{"RawSetColor", API_FN_PAIR(RawSetColor)},
+		{"RawSetColorRGB", API_FN_PAIR(RawSetColorRGB)},
+		{"RefreshMouse", API_FN_PAIR(RefreshMouse)},
+		{"ReleaseCharacterView", API_FN_PAIR(ReleaseCharacterView)},
+		{"ReleaseViewport", API_FN_PAIR(ReleaseViewport)},
+		{"RemoveObjectTint", API_FN_PAIR(RemoveObjectTint)},
+		{"RemoveOverlay", API_FN_PAIR(RemoveOverlay)},
+		{"RemoveWalkableArea", API_FN_PAIR(RemoveWalkableArea)},
+		{"ResetRoom", API_FN_PAIR(ResetRoom)},
+		{"RestartGame", API_FN_PAIR(restart_game)},
+		{"RestoreGameDialog", API_FN_PAIR(restore_game_dialog)},
+		{"RestoreGameSlot", API_FN_PAIR(RestoreGameSlot)},
+		{"RestoreWalkableArea", API_FN_PAIR(RestoreWalkableArea)},
+		{"RunAGSGame", API_FN_PAIR(RunAGSGame)},
+		{"RunCharacterInteraction", API_FN_PAIR(RunCharacterInteraction)},
+		{"RunDialog", API_FN_PAIR(RunDialog)},
+		{"RunHotspotInteraction", API_FN_PAIR(RunHotspotInteraction)},
+		{"RunInventoryInteraction", API_FN_PAIR(RunInventoryInteraction)},
+		{"RunObjectInteraction", API_FN_PAIR(RunObjectInteraction)},
+		{"RunRegionInteraction", API_FN_PAIR(RunRegionInteraction)},
+		{"Said", API_FN_PAIR(Said)},
+		{"SaidUnknownWord", API_FN_PAIR(SaidUnknownWord)},
+		{"SaveCursorForLocationChange", API_FN_PAIR(SaveCursorForLocationChange)},
+		{"SaveGameDialog", API_FN_PAIR(save_game_dialog)},
+		{"SaveGameSlot", API_FN_PAIR(save_game)},
+		{"SaveScreenShot", API_FN_PAIR(SaveScreenShot)},
+		{"SeekMIDIPosition", API_FN_PAIR(SeekMIDIPosition)},
+		{"SeekMODPattern", API_FN_PAIR(SeekMODPattern)},
+		{"SeekMP3PosMillis", API_FN_PAIR(SeekMP3PosMillis)},
+		{"SetActiveInventory", API_FN_PAIR(SetActiveInventory)},
+		{"SetAmbientTint", API_FN_PAIR(SetAmbientTint)},
+		{"SetAmbientLightLevel", API_FN_PAIR(SetAmbientLightLevel)},
+		{"SetAreaLightLevel", API_FN_PAIR(SetAreaLightLevel)},
+		{"SetAreaScaling", API_FN_PAIR(SetAreaScaling)},
+		{"SetBackgroundFrame", API_FN_PAIR(SetBackgroundFrame)},
+		{"SetButtonPic", API_FN_PAIR(SetButtonPic)},
+		{"SetButtonText", API_FN_PAIR(SetButtonText)},
+		{"SetChannelVolume", API_FN_PAIR(SetChannelVolume)},
+		{"SetCharacterBaseline", API_FN_PAIR(SetCharacterBaseline)},
+		{"SetCharacterClickable", API_FN_PAIR(SetCharacterClickable)},
+		{"SetCharacterFrame", API_FN_PAIR(SetCharacterFrame)},
+		{"SetCharacterIdle", API_FN_PAIR(SetCharacterIdle)},
+		{"SetCharacterIgnoreLight", API_FN_PAIR(SetCharacterIgnoreLight)},
+		{"SetCharacterIgnoreWalkbehinds", API_FN_PAIR(SetCharacterIgnoreWalkbehinds)},
+		{"SetCharacterProperty", API_FN_PAIR(SetCharacterProperty)},
+		{"SetCharacterBlinkView", API_FN_PAIR(SetCharacterBlinkView)},
+		{"SetCharacterSpeechView", API_FN_PAIR(SetCharacterSpeechView)},
+		{"SetCharacterSpeed", API_FN_PAIR(SetCharacterSpeed)},
+		{"SetCharacterSpeedEx", API_FN_PAIR(SetCharacterSpeedEx)},
+		{"SetCharacterTransparency", API_FN_PAIR(SetCharacterTransparency)},
+		{"SetCharacterView", API_FN_PAIR(SetCharacterView)},
+		{"SetCharacterViewEx", API_FN_PAIR(SetCharacterViewEx)},
+		{"SetCharacterViewOffset", API_FN_PAIR(SetCharacterViewOffset)},
+		{"SetCursorMode", API_FN_PAIR(set_cursor_mode)},
+		{"SetDefaultCursor", API_FN_PAIR(set_default_cursor)},
+		{"SetDialogOption", API_FN_PAIR(SetDialogOption)},
+		{"SetDigitalMasterVolume", API_FN_PAIR(SetDigitalMasterVolume)},
+		{"SetFadeColor", API_FN_PAIR(SetFadeColor)},
+		{"SetFrameSound", API_FN_PAIR(SetFrameSound)},
+		{"SetGameOption", API_FN_PAIR(SetGameOption)},
+		{"SetGameSpeed", API_FN_PAIR(SetGameSpeed)},
+		{"SetGlobalInt", API_FN_PAIR(SetGlobalInt)},
+		{"SetGlobalString", API_FN_PAIR(SetGlobalString)},
+		{"SetGraphicalVariable", API_FN_PAIR(SetGraphicalVariable)},
+		{"SetGUIBackgroundPic", API_FN_PAIR(SetGUIBackgroundPic)},
+		{"SetGUIClickable", API_FN_PAIR(SetGUIClickable)},
+		{"SetGUIObjectEnabled", API_FN_PAIR(SetGUIObjectEnabled)},
+		{"SetGUIObjectPosition", API_FN_PAIR(SetGUIObjectPosition)},
+		{"SetGUIObjectSize", API_FN_PAIR(SetGUIObjectSize)},
+		{"SetGUIPosition", API_FN_PAIR(SetGUIPosition)},
+		{"SetGUISize", API_FN_PAIR(SetGUISize)},
+		{"SetGUITransparency", API_FN_PAIR(SetGUITransparency)},
+		{"SetGUIZOrder", API_FN_PAIR(SetGUIZOrder)},
+		{"SetInvItemName", API_FN_PAIR(SetInvItemName)},
+		{"SetInvItemPic", API_FN_PAIR(set_inv_item_pic)},
+		{"SetInvDimensions", API_FN_PAIR(SetInvDimensions)},
+		{"SetLabelColor", API_FN_PAIR(SetLabelColor)},
+		{"SetLabelFont", API_FN_PAIR(SetLabelFont)},
+		{"SetLabelText", API_FN_PAIR(SetLabelText)},
+		{"SetMouseBounds", API_FN_PAIR(SetMouseBounds)},
+		{"SetMouseCursor", API_FN_PAIR(set_mouse_cursor)},
+		{"SetMousePosition", API_FN_PAIR(SetMousePosition)},
+		{"SetMultitaskingMode", API_FN_PAIR(SetMultitasking)},
+		{"SetMusicMasterVolume", API_FN_PAIR(SetMusicMasterVolume)},
+		{"SetMusicRepeat", API_FN_PAIR(SetMusicRepeat)},
+		{"SetMusicVolume", API_FN_PAIR(SetMusicVolume)},
+		{"SetNextCursorMode", API_FN_PAIR(SetNextCursor)},
+		{"SetNextScreenTransition", API_FN_PAIR(SetNextScreenTransition)},
+		{"SetNormalFont", API_FN_PAIR(SetNormalFont)},
+		{"SetObjectBaseline", API_FN_PAIR(SetObjectBaseline)},
+		{"SetObjectClickable", API_FN_PAIR(SetObjectClickable)},
+		{"SetObjectFrame", API_FN_PAIR(SetObjectFrame)},
+		{"SetObjectGraphic", API_FN_PAIR(SetObjectGraphic)},
+		{"SetObjectIgnoreWalkbehinds", API_FN_PAIR(SetObjectIgnoreWalkbehinds)},
+		{"SetObjectPosition", API_FN_PAIR(SetObjectPosition)},
+		{"SetObjectTint", API_FN_PAIR(SetObjectTint)},
+		{"SetObjectTransparency", API_FN_PAIR(SetObjectTransparency)},
+		{"SetObjectView", API_FN_PAIR(SetObjectView)},
+		{"SetPalRGB", API_FN_PAIR(SetPalRGB)},
+		{"SetPlayerCharacter", API_FN_PAIR(SetPlayerCharacter)},
+		{"SetRegionTint", API_FN_PAIR(SetRegionTint)},
+		{"SetRestartPoint", API_FN_PAIR(SetRestartPoint)},
+		{"SetScreenTransition", API_FN_PAIR(SetScreenTransition)},
+		{"SetSkipSpeech", API_FN_PAIR(SetSkipSpeech)},
+		{"SetSliderValue", API_FN_PAIR(SetSliderValue)},
+		{"SetSoundVolume", API_FN_PAIR(SetSoundVolume)},
+		{"SetSpeechFont", API_FN_PAIR(SetSpeechFont)},
+		{"SetSpeechStyle", API_FN_PAIR(SetSpeechStyle)},
+		{"SetSpeechVolume", API_FN_PAIR(SetSpeechVolume)},
+		{"SetTalkingColor", API_FN_PAIR(SetTalkingColor)},
+		{"SetTextBoxFont", API_FN_PAIR(SetTextBoxFont)},
+		{"SetTextBoxText", API_FN_PAIR(SetTextBoxText)},
+		{"SetTextOverlay", Sc_SetTextOverlay},
+		{"SetTextWindowGUI", API_FN_PAIR(SetTextWindowGUI)},
+		{"SetTimer", API_FN_PAIR(script_SetTimer)},
+		{"SetViewport", API_FN_PAIR(SetViewport)},
+		{"SetVoiceMode", API_FN_PAIR(SetVoiceMode)},
+		{"SetWalkBehindBase", API_FN_PAIR(SetWalkBehindBase)},
+		{"ShakeScreen", API_FN_PAIR(ShakeScreen)},
+		{"ShakeScreenBackground", API_FN_PAIR(ShakeScreenBackground)},
+		{"ShowMouseCursor", API_FN_PAIR(ShowMouseCursor)},
+		{"SkipCutscene", API_FN_PAIR(SkipCutscene)},
+		{"SkipUntilCharacterStops", API_FN_PAIR(SkipUntilCharacterStops)},
+		{"StartCutscene", API_FN_PAIR(StartCutscene)},
+		{"StartRecording", API_FN_PAIR(scStartRecording)},
+		{"StopAmbientSound", API_FN_PAIR(StopAmbientSound)},
+		{"StopChannel", API_FN_PAIR(stop_and_destroy_channel)},
+		{"StopDialog", API_FN_PAIR(StopDialog)},
+		{"StopMoving", API_FN_PAIR(StopMoving)},
+		{"StopMusic", API_FN_PAIR(scr_StopMusic)},
+		{"StopObjectMoving", API_FN_PAIR(StopObjectMoving)},
+		{"StrCat", Sc_sc_strcat},
+		{"StrCaseComp", Sc_stricmp},
+		{"StrComp", API_FN_PAIR(strcmp)},
+		{"StrContains", API_FN_PAIR(StrContains)},
+		{"StrCopy", Sc_sc_strcpy},
+		{"StrFormat", Sc_sc_sprintf},
+		{"StrGetCharAt", API_FN_PAIR(StrGetCharAt)},
+		{"StringToInt", API_FN_PAIR(StringToInt)},
+		{"StrLen", API_FN_PAIR(strlen)},
+		{"StrSetCharAt", API_FN_PAIR(StrSetCharAt)},
+		{"StrToLowerCase", Sc_sc_strlower},
+		{"StrToUpperCase", Sc_sc_strupper},
+		{"TintScreen", API_FN_PAIR(TintScreen)},
+		{"UnPauseGame", API_FN_PAIR(UnPauseGame)},
+		{"UpdateInventory", API_FN_PAIR(update_invorder)},
+		{"UpdatePalette", API_FN_PAIR(UpdatePalette)},
+		{"Wait", API_FN_PAIR(scrWait)},
+		{"WaitKey", API_FN_PAIR(WaitKey)},
+		{"WaitMouse", API_FN_PAIR(WaitMouse)},
+		{"WaitMouseKey", API_FN_PAIR(WaitMouseKey)},
+		{"WaitInput", API_FN_PAIR(WaitInput)},
+		{"SkipWait", API_FN_PAIR(SkipWait)},
+	};
+
+	ccAddExternalFunctions361(global_api);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index a24d6e0840e..4916b894d63 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -1037,83 +1037,80 @@ RuntimeScriptValue Sc_Object_SetY(void *self, const RuntimeScriptValue *params,
 	API_OBJCALL_VOID_PINT(ScriptObject, Object_SetY);
 }
 
-
-
 void RegisterObjectAPI() {
-	ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate5);
-	ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_Animate6);
-	ccAddExternalObjectFunction("Object::Animate^7", Sc_Object_Animate);
-	ccAddExternalObjectFunction("Object::IsCollidingWithObject^1", Sc_Object_IsCollidingWithObject);
-	ccAddExternalObjectFunction("Object::GetName^1", Sc_Object_GetName);
-	ccAddExternalObjectFunction("Object::GetProperty^1", Sc_Object_GetProperty);
-	ccAddExternalObjectFunction("Object::GetPropertyText^2", Sc_Object_GetPropertyText);
-	ccAddExternalObjectFunction("Object::GetTextProperty^1", Sc_Object_GetTextProperty);
-	ccAddExternalObjectFunction("Object::SetProperty^2", Sc_Object_SetProperty);
-	ccAddExternalObjectFunction("Object::SetTextProperty^2", Sc_Object_SetTextProperty);
-	ccAddExternalObjectFunction("Object::IsInteractionAvailable^1", Sc_Object_IsInteractionAvailable);
-	ccAddExternalObjectFunction("Object::MergeIntoBackground^0", Sc_Object_MergeIntoBackground);
-	ccAddExternalObjectFunction("Object::Move^5", Sc_Object_Move);
-	ccAddExternalObjectFunction("Object::RemoveTint^0", Sc_Object_RemoveTint);
-	ccAddExternalObjectFunction("Object::RunInteraction^1", Sc_Object_RunInteraction);
-	ccAddExternalObjectFunction("Object::SetLightLevel^1", Sc_Object_SetLightLevel);
-	ccAddExternalObjectFunction("Object::SetPosition^2", Sc_Object_SetPosition);
-	ccAddExternalObjectFunction("Object::SetView^3", Sc_Object_SetView);
-	ccAddExternalObjectFunction("Object::StopAnimating^0", Sc_Object_StopAnimating);
-	ccAddExternalObjectFunction("Object::StopMoving^0", Sc_Object_StopMoving);
-	ccAddExternalObjectFunction("Object::Tint^5", Sc_Object_Tint);
-
-	// static
-	ccAddExternalStaticFunction("Object::GetAtRoomXY^2", Sc_GetObjectAtRoom);
-	ccAddExternalStaticFunction("Object::GetAtScreenXY^2", Sc_GetObjectAtScreen);
-
-	ccAddExternalObjectFunction("Object::get_Animating", Sc_Object_GetAnimating);
-	ccAddExternalObjectFunction("Object::get_AnimationVolume", Sc_Object_GetAnimationVolume),
-	ccAddExternalObjectFunction("Object::set_AnimationVolume", Sc_Object_SetAnimationVolume),
-	ccAddExternalObjectFunction("Object::get_Baseline", Sc_Object_GetBaseline);
-	ccAddExternalObjectFunction("Object::set_Baseline", Sc_Object_SetBaseline);
-	ccAddExternalObjectFunction("Object::get_BlockingHeight", Sc_Object_GetBlockingHeight);
-	ccAddExternalObjectFunction("Object::set_BlockingHeight", Sc_Object_SetBlockingHeight);
-	ccAddExternalObjectFunction("Object::get_BlockingWidth", Sc_Object_GetBlockingWidth);
-	ccAddExternalObjectFunction("Object::set_BlockingWidth", Sc_Object_SetBlockingWidth);
-	ccAddExternalObjectFunction("Object::get_Clickable", Sc_Object_GetClickable);
-	ccAddExternalObjectFunction("Object::set_Clickable", Sc_Object_SetClickable);
-	ccAddExternalObjectFunction("Object::get_Frame", Sc_Object_GetFrame);
-	ccAddExternalObjectFunction("Object::get_Graphic", Sc_Object_GetGraphic);
-	ccAddExternalObjectFunction("Object::set_Graphic", Sc_Object_SetGraphic);
-	ccAddExternalObjectFunction("Object::get_ID", Sc_Object_GetID);
-	ccAddExternalObjectFunction("Object::get_IgnoreScaling", Sc_Object_GetIgnoreScaling);
-	ccAddExternalObjectFunction("Object::set_IgnoreScaling", Sc_Object_SetIgnoreScaling);
-	ccAddExternalObjectFunction("Object::get_IgnoreWalkbehinds", Sc_Object_GetIgnoreWalkbehinds);
-	ccAddExternalObjectFunction("Object::set_IgnoreWalkbehinds", Sc_Object_SetIgnoreWalkbehinds);
-	ccAddExternalObjectFunction("Object::get_Loop", Sc_Object_GetLoop);
-	ccAddExternalObjectFunction("Object::get_ManualScaling", Sc_Object_GetIgnoreScaling);
-	ccAddExternalObjectFunction("Object::set_ManualScaling", Sc_Object_SetManualScaling);
-	ccAddExternalObjectFunction("Object::get_Moving", Sc_Object_GetMoving);
-	ccAddExternalObjectFunction("Object::get_Name", Sc_Object_GetName_New);
-	ccAddExternalObjectFunction("Object::set_Name", Sc_Object_SetName);
-	ccAddExternalObjectFunction("Object::get_Scaling", Sc_Object_GetScaling);
-	ccAddExternalObjectFunction("Object::set_Scaling", Sc_Object_SetScaling);
-	ccAddExternalObjectFunction("Object::get_Solid", Sc_Object_GetSolid);
-	ccAddExternalObjectFunction("Object::set_Solid", Sc_Object_SetSolid);
-	ccAddExternalObjectFunction("Object::get_Transparency", Sc_Object_GetTransparency);
-	ccAddExternalObjectFunction("Object::set_Transparency", Sc_Object_SetTransparency);
-	ccAddExternalObjectFunction("Object::get_View", Sc_Object_GetView);
-	ccAddExternalObjectFunction("Object::get_Visible", Sc_Object_GetVisible);
-	ccAddExternalObjectFunction("Object::set_Visible", Sc_Object_SetVisible);
-	ccAddExternalObjectFunction("Object::get_X", Sc_Object_GetX);
-	ccAddExternalObjectFunction("Object::set_X", Sc_Object_SetX);
-	ccAddExternalObjectFunction("Object::get_Y", Sc_Object_GetY);
-	ccAddExternalObjectFunction("Object::set_Y", Sc_Object_SetY);
-
-	ccAddExternalObjectFunction("Object::get_HasExplicitLight", Sc_Object_HasExplicitLight);
-	ccAddExternalObjectFunction("Object::get_HasExplicitTint", Sc_Object_HasExplicitTint);
-	ccAddExternalObjectFunction("Object::get_LightLevel", Sc_Object_GetLightLevel);
-	ccAddExternalObjectFunction("Object::set_LightLevel", Sc_Object_SetLightLevel);
-	ccAddExternalObjectFunction("Object::get_TintBlue", Sc_Object_GetTintBlue);
-	ccAddExternalObjectFunction("Object::get_TintGreen", Sc_Object_GetTintGreen);
-	ccAddExternalObjectFunction("Object::get_TintRed", Sc_Object_GetTintRed);
-	ccAddExternalObjectFunction("Object::get_TintSaturation", Sc_Object_GetTintSaturation);
-	ccAddExternalObjectFunction("Object::get_TintLuminance", Sc_Object_GetTintLuminance);
+	ScFnRegister object_api[] = {
+		{"Object::GetAtRoomXY^2", API_FN_PAIR(GetObjectAtRoom)},
+		{"Object::GetAtScreenXY^2", API_FN_PAIR(GetObjectAtScreen)},
+
+		{"Object::Animate^5", API_FN_PAIR(Object_Animate5)},
+		{"Object::Animate^6", API_FN_PAIR(Object_Animate6)},
+		{"Object::Animate^7", API_FN_PAIR(Object_Animate)},
+		{"Object::IsCollidingWithObject^1", API_FN_PAIR(Object_IsCollidingWithObject)},
+		{"Object::GetName^1", API_FN_PAIR(Object_GetName)},
+		{"Object::GetProperty^1", API_FN_PAIR(Object_GetProperty)},
+		{"Object::GetPropertyText^2", API_FN_PAIR(Object_GetPropertyText)},
+		{"Object::GetTextProperty^1", API_FN_PAIR(Object_GetTextProperty)},
+		{"Object::SetProperty^2", API_FN_PAIR(Object_SetProperty)},
+		{"Object::SetTextProperty^2", API_FN_PAIR(Object_SetTextProperty)},
+		{"Object::IsInteractionAvailable^1", API_FN_PAIR(Object_IsInteractionAvailable)},
+		{"Object::MergeIntoBackground^0", API_FN_PAIR(Object_MergeIntoBackground)},
+		{"Object::Move^5", API_FN_PAIR(Object_Move)},
+		{"Object::RemoveTint^0", API_FN_PAIR(Object_RemoveTint)},
+		{"Object::RunInteraction^1", API_FN_PAIR(Object_RunInteraction)},
+		{"Object::SetLightLevel^1", API_FN_PAIR(Object_SetLightLevel)},
+		{"Object::SetPosition^2", API_FN_PAIR(Object_SetPosition)},
+		{"Object::SetView^3", API_FN_PAIR(Object_SetView)},
+		{"Object::StopAnimating^0", API_FN_PAIR(Object_StopAnimating)},
+		{"Object::StopMoving^0", API_FN_PAIR(Object_StopMoving)},
+		{"Object::Tint^5", API_FN_PAIR(Object_Tint)},
+		{"Object::get_Animating", API_FN_PAIR(Object_GetAnimating)},
+		{"Object::get_Baseline", API_FN_PAIR(Object_GetBaseline)},
+		{"Object::set_Baseline", API_FN_PAIR(Object_SetBaseline)},
+		{"Object::get_BlockingHeight", API_FN_PAIR(Object_GetBlockingHeight)},
+		{"Object::set_BlockingHeight", API_FN_PAIR(Object_SetBlockingHeight)},
+		{"Object::get_BlockingWidth", API_FN_PAIR(Object_GetBlockingWidth)},
+		{"Object::set_BlockingWidth", API_FN_PAIR(Object_SetBlockingWidth)},
+		{"Object::get_Clickable", API_FN_PAIR(Object_GetClickable)},
+		{"Object::set_Clickable", API_FN_PAIR(Object_SetClickable)},
+		{"Object::get_Frame", API_FN_PAIR(Object_GetFrame)},
+		{"Object::get_Graphic", API_FN_PAIR(Object_GetGraphic)},
+		{"Object::set_Graphic", API_FN_PAIR(Object_SetGraphic)},
+		{"Object::get_ID", API_FN_PAIR(Object_GetID)},
+		{"Object::get_IgnoreScaling", API_FN_PAIR(Object_GetIgnoreScaling)},
+		{"Object::set_IgnoreScaling", API_FN_PAIR(Object_SetIgnoreScaling)},
+		{"Object::get_IgnoreWalkbehinds", API_FN_PAIR(Object_GetIgnoreWalkbehinds)},
+		{"Object::set_IgnoreWalkbehinds", API_FN_PAIR(Object_SetIgnoreWalkbehinds)},
+		{"Object::get_Loop", API_FN_PAIR(Object_GetLoop)},
+		{"Object::get_ManualScaling", API_FN_PAIR(Object_GetIgnoreScaling)},
+		{"Object::set_ManualScaling", API_FN_PAIR(Object_SetManualScaling)},
+		{"Object::get_Moving", API_FN_PAIR(Object_GetMoving)},
+		{"Object::get_Name", API_FN_PAIR(Object_GetName_New)},
+		{"Object::set_Name", API_FN_PAIR(Object_SetName)},
+		{"Object::get_Scaling", API_FN_PAIR(Object_GetScaling)},
+		{"Object::set_Scaling", API_FN_PAIR(Object_SetScaling)},
+		{"Object::get_Solid", API_FN_PAIR(Object_GetSolid)},
+		{"Object::set_Solid", API_FN_PAIR(Object_SetSolid)},
+		{"Object::get_Transparency", API_FN_PAIR(Object_GetTransparency)},
+		{"Object::set_Transparency", API_FN_PAIR(Object_SetTransparency)},
+		{"Object::get_View", API_FN_PAIR(Object_GetView)},
+		{"Object::get_Visible", API_FN_PAIR(Object_GetVisible)},
+		{"Object::set_Visible", API_FN_PAIR(Object_SetVisible)},
+		{"Object::get_X", API_FN_PAIR(Object_GetX)},
+		{"Object::set_X", API_FN_PAIR(Object_SetX)},
+		{"Object::get_Y", API_FN_PAIR(Object_GetY)},
+		{"Object::set_Y", API_FN_PAIR(Object_SetY)},
+		{"Object::get_HasExplicitLight", API_FN_PAIR(Object_HasExplicitLight)},
+		{"Object::get_HasExplicitTint", API_FN_PAIR(Object_HasExplicitTint)},
+		{"Object::get_LightLevel", API_FN_PAIR(Object_GetLightLevel)},
+		{"Object::set_LightLevel", API_FN_PAIR(Object_SetLightLevel)},
+		{"Object::get_TintBlue", API_FN_PAIR(Object_GetTintBlue)},
+		{"Object::get_TintGreen", API_FN_PAIR(Object_GetTintGreen)},
+		{"Object::get_TintRed", API_FN_PAIR(Object_GetTintRed)},
+		{"Object::get_TintSaturation", API_FN_PAIR(Object_GetTintSaturation)},
+		{"Object::get_TintLuminance", API_FN_PAIR(Object_GetTintLuminance)},
+	};
+
+	ccAddExternalFunctions361(object_api);
 }
 
 } // namespace AGS3


Commit: 27ce106dac06f86b7a120171da8f2b09a981df7b
    https://github.com/scummvm/scummvm/commit/27ce106dac06f86b7a120171da8f2b09a981df7b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added a template ScFnRegister ctor for easier direct function

>From upstream 78dcba2b8e874c3c313493e04dc0b83fdfb564cb

Changed paths:
    engines/ags/engine/script/script_runtime.h


diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index cde076e1284..a569dab2cfd 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -45,6 +45,12 @@ struct ScFnRegister {
 		: Name(name), Fn(RuntimeScriptValue().SetStaticFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
 	ScFnRegister(const char *name, ScriptAPIObjectFunction *fn, void *plfn = nullptr)
 		: Name(name), Fn(RuntimeScriptValue().SetObjectFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
+	template<typename TPlFn>
+	ScFnRegister(const char *name, ScriptAPIFunction *fn, TPlFn plfn)
+		: Name(name), Fn(RuntimeScriptValue().SetStaticFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
+	template<typename TPlFn>
+	ScFnRegister(const char *name, ScriptAPIObjectFunction *fn, TPlFn plfn)
+		: Name(name), Fn(RuntimeScriptValue().SetObjectFunction(fn)), PlFn(RuntimeScriptValue().SetPluginMethod((Plugins::ScriptContainer *)plfn, nullptr)) {}
 };
 
 // Following functions register engine API symbols for script and plugins.


Commit: 935247b450fb5f6671b5c54b29d099c5c8c97057
    https://github.com/scummvm/scummvm/commit/935247b450fb5f6671b5c54b29d099c5c8c97057
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: reformatted Camera & Viewport api's registration

>From upstream ab9979251d4ce39dca7e28c5f95784fb405b5d00

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


diff --git a/engines/ags/engine/ac/viewport_script.cpp b/engines/ags/engine/ac/viewport_script.cpp
index 64b251e0bb1..1ac469f04d4 100644
--- a/engines/ags/engine/ac/viewport_script.cpp
+++ b/engines/ags/engine/ac/viewport_script.cpp
@@ -519,41 +519,49 @@ RuntimeScriptValue Sc_Viewport_RoomToScreenPoint(void *self, const RuntimeScript
 
 
 void RegisterViewportAPI() {
-	ccAddExternalStaticFunction("Camera::Create", Sc_Camera_Create);
-	ccAddExternalObjectFunction("Camera::Delete", Sc_Camera_Delete);
-	ccAddExternalObjectFunction("Camera::get_X", Sc_Camera_GetX);
-	ccAddExternalObjectFunction("Camera::set_X", Sc_Camera_SetX);
-	ccAddExternalObjectFunction("Camera::get_Y", Sc_Camera_GetY);
-	ccAddExternalObjectFunction("Camera::set_Y", Sc_Camera_SetY);
-	ccAddExternalObjectFunction("Camera::get_Width", Sc_Camera_GetWidth);
-	ccAddExternalObjectFunction("Camera::set_Width", Sc_Camera_SetWidth);
-	ccAddExternalObjectFunction("Camera::get_Height", Sc_Camera_GetHeight);
-	ccAddExternalObjectFunction("Camera::set_Height", Sc_Camera_SetHeight);
-	ccAddExternalObjectFunction("Camera::get_AutoTracking", Sc_Camera_GetAutoTracking);
-	ccAddExternalObjectFunction("Camera::set_AutoTracking", Sc_Camera_SetAutoTracking);
-	ccAddExternalObjectFunction("Camera::SetAt", Sc_Camera_SetAt);
-	ccAddExternalObjectFunction("Camera::SetSize", Sc_Camera_SetSize);
-
-	ccAddExternalStaticFunction("Viewport::Create", Sc_Viewport_Create);
-	ccAddExternalObjectFunction("Viewport::Delete", Sc_Viewport_Delete);
-	ccAddExternalObjectFunction("Viewport::get_X", Sc_Viewport_GetX);
-	ccAddExternalObjectFunction("Viewport::set_X", Sc_Viewport_SetX);
-	ccAddExternalObjectFunction("Viewport::get_Y", Sc_Viewport_GetY);
-	ccAddExternalObjectFunction("Viewport::set_Y", Sc_Viewport_SetY);
-	ccAddExternalObjectFunction("Viewport::get_Width", Sc_Viewport_GetWidth);
-	ccAddExternalObjectFunction("Viewport::set_Width", Sc_Viewport_SetWidth);
-	ccAddExternalObjectFunction("Viewport::get_Height", Sc_Viewport_GetHeight);
-	ccAddExternalObjectFunction("Viewport::set_Height", Sc_Viewport_SetHeight);
-	ccAddExternalObjectFunction("Viewport::get_Camera", Sc_Viewport_GetCamera);
-	ccAddExternalObjectFunction("Viewport::set_Camera", Sc_Viewport_SetCamera);
-	ccAddExternalObjectFunction("Viewport::get_Visible", Sc_Viewport_GetVisible);
-	ccAddExternalObjectFunction("Viewport::set_Visible", Sc_Viewport_SetVisible);
-	ccAddExternalObjectFunction("Viewport::get_ZOrder", Sc_Viewport_GetZOrder);
-	ccAddExternalObjectFunction("Viewport::set_ZOrder", Sc_Viewport_SetZOrder);
-	ccAddExternalStaticFunction("Viewport::GetAtScreenXY", Sc_Viewport_GetAtScreenXY);
-	ccAddExternalObjectFunction("Viewport::SetPosition", Sc_Viewport_SetPosition);
-	ccAddExternalObjectFunction("Viewport::ScreenToRoomPoint", Sc_Viewport_ScreenToRoomPoint);
-	ccAddExternalObjectFunction("Viewport::RoomToScreenPoint", Sc_Viewport_RoomToScreenPoint);
+	ScFnRegister camera_api[] = {
+		{"Camera::Create", API_FN_PAIR(Camera_Create)},
+		{"Camera::Delete", API_FN_PAIR(Camera_Delete)},
+		{"Camera::get_X", API_FN_PAIR(Camera_GetX)},
+		{"Camera::set_X", API_FN_PAIR(Camera_SetX)},
+		{"Camera::get_Y", API_FN_PAIR(Camera_GetY)},
+		{"Camera::set_Y", API_FN_PAIR(Camera_SetY)},
+		{"Camera::get_Width", API_FN_PAIR(Camera_GetWidth)},
+		{"Camera::set_Width", API_FN_PAIR(Camera_SetWidth)},
+		{"Camera::get_Height", API_FN_PAIR(Camera_GetHeight)},
+		{"Camera::set_Height", API_FN_PAIR(Camera_SetHeight)},
+		{"Camera::get_AutoTracking", API_FN_PAIR(Camera_GetAutoTracking)},
+		{"Camera::set_AutoTracking", API_FN_PAIR(Camera_SetAutoTracking)},
+		{"Camera::SetAt", API_FN_PAIR(Camera_SetAt)},
+		{"Camera::SetSize", API_FN_PAIR(Camera_SetSize)},
+	};
+
+	ccAddExternalFunctions361(camera_api);
+
+	ScFnRegister viewport_api[] = {
+		{"Viewport::Create", API_FN_PAIR(Viewport_Create)},
+		{"Viewport::Delete", API_FN_PAIR(Viewport_Delete)},
+		{"Viewport::get_X", API_FN_PAIR(Viewport_GetX)},
+		{"Viewport::set_X", API_FN_PAIR(Viewport_SetX)},
+		{"Viewport::get_Y", API_FN_PAIR(Viewport_GetY)},
+		{"Viewport::set_Y", API_FN_PAIR(Viewport_SetY)},
+		{"Viewport::get_Width", API_FN_PAIR(Viewport_GetWidth)},
+		{"Viewport::set_Width", API_FN_PAIR(Viewport_SetWidth)},
+		{"Viewport::get_Height", API_FN_PAIR(Viewport_GetHeight)},
+		{"Viewport::set_Height", API_FN_PAIR(Viewport_SetHeight)},
+		{"Viewport::get_Camera", API_FN_PAIR(Viewport_GetCamera)},
+		{"Viewport::set_Camera", API_FN_PAIR(Viewport_SetCamera)},
+		{"Viewport::get_Visible", API_FN_PAIR(Viewport_GetVisible)},
+		{"Viewport::set_Visible", API_FN_PAIR(Viewport_SetVisible)},
+		{"Viewport::get_ZOrder", API_FN_PAIR(Viewport_GetZOrder)},
+		{"Viewport::set_ZOrder", API_FN_PAIR(Viewport_SetZOrder)},
+		{"Viewport::GetAtScreenXY", API_FN_PAIR(Viewport_GetAtScreenXY)},
+		{"Viewport::SetPosition", API_FN_PAIR(Viewport_SetPosition)},
+		{"Viewport::ScreenToRoomPoint", API_FN_PAIR(Viewport_ScreenToRoomPoint)},
+		{"Viewport::RoomToScreenPoint", API_FN_PAIR(Viewport_RoomToScreenPoint)},
+	};
+
+	ccAddExternalFunctions361(viewport_api);
 }
 
 } // namespace AGS3


Commit: 795dacac07e625906301aef7acb2cf230c8d1223
    https://github.com/scummvm/scummvm/commit/795dacac07e625906301aef7acb2cf230c8d1223
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: inherit StaticObject managers from ICCDynamicObject

This removes an excessive separation between "static managers" and "dynamic managers",
and let use the single ICCDynamicObject interface when working with both kinds.
Simplifies RuntimeScriptValue and StaticArray classes.

Picked out most common implementation into CCBasicObject, which is inherited by
AGSCCDynamicObject and AGSCCStaticObject.

>From upstream 781632881dd64af2b57b789617da15107bca9451

Changed paths:
  A engines/ags/engine/ac/dynobj/cc_static_array.cpp
  A engines/ags/engine/ac/dynobj/cc_static_array.h
  A engines/ags/engine/ac/dynobj/script_game.cpp
  A engines/ags/engine/ac/dynobj/script_game.h
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/script_file.cpp
    engines/ags/engine/ac/dynobj/script_file.h
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/engine/script/script_api.h
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h
    engines/ags/engine/script/system_imports.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/module.mk


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index 2cd4a19bb6c..e7ba292ce78 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -21,74 +21,75 @@
 
 #include "ags/shared/core/types.h"
 #include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
-#include "ags/shared/ac/common.h"               // quit()
 #include "ags/shared/util/memory_stream.h"
 
 namespace AGS3 {
 
 using namespace AGS::Shared;
 
-// *** The script serialization routines for built-in types
-
-int AGSCCDynamicObject::Dispose(const char *address, bool force) {
-	// cannot be removed from memory
-	return 0;
+int CCBasicObject::Dispose(const char * /*address*/, bool /*force*/) {
+	return 0; // cannot be removed from memory
 }
 
-int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize) {
-	// If the required space is larger than the provided buffer,
-	// then return negated required space, notifying the caller that a larger buffer is necessary
-	size_t req_size = CalcSerializeSize(address);
-	if (bufsize < 0 || req_size > static_cast<size_t>(bufsize))
-		return -(static_cast<int32_t>(req_size));
-
-	MemoryStream mems(reinterpret_cast<uint8_t *>(buffer), bufsize, kStream_Write);
-	Serialize(address, &mems);
-	return static_cast<int32_t>(mems.GetPosition());
+int CCBasicObject::Serialize(const char * /*address*/, char * /*buffer*/, int /*bufsize*/) {
+	return 0; // does not save data
 }
 
-const char *AGSCCDynamicObject::GetFieldPtr(const char *address, intptr_t offset) {
+const char *CCBasicObject::GetFieldPtr(const char *address, intptr_t offset) {
 	return address + offset;
 }
 
-void AGSCCDynamicObject::Read(const char *address, intptr_t offset, void *dest, int size) {
+void CCBasicObject::Read(const char *address, intptr_t offset, void *dest, int size) {
 	memcpy(dest, address + offset, size);
 }
 
-uint8_t AGSCCDynamicObject::ReadInt8(const char *address, intptr_t offset) {
+uint8_t CCBasicObject::ReadInt8(const char *address, intptr_t offset) {
 	return *(const uint8_t *)(address + offset);
 }
 
-int16_t AGSCCDynamicObject::ReadInt16(const char *address, intptr_t offset) {
+int16_t CCBasicObject::ReadInt16(const char *address, intptr_t offset) {
 	return *(const int16_t *)(address + offset);
 }
 
-int32_t AGSCCDynamicObject::ReadInt32(const char *address, intptr_t offset) {
+int32_t CCBasicObject::ReadInt32(const char *address, intptr_t offset) {
 	return *(const int32_t *)(address + offset);
 }
 
-float AGSCCDynamicObject::ReadFloat(const char *address, intptr_t offset) {
+float CCBasicObject::ReadFloat(const char *address, intptr_t offset) {
 	return *(const float *)(address + offset);
 }
 
-void AGSCCDynamicObject::Write(const char *address, intptr_t offset, void *src, int size) {
+void CCBasicObject::Write(const char *address, intptr_t offset, void *src, int size) {
 	memcpy((void *)(const_cast<char *>(address) + offset), src, size);
 }
 
-void AGSCCDynamicObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
+void CCBasicObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
 	*(uint8_t *)(const_cast<char *>(address) + offset) = val;
 }
 
-void AGSCCDynamicObject::WriteInt16(const char *address, intptr_t offset, int16_t val) {
+void CCBasicObject::WriteInt16(const char *address, intptr_t offset, int16_t val) {
 	*(int16_t *)(const_cast<char *>(address) + offset) = val;
 }
 
-void AGSCCDynamicObject::WriteInt32(const char *address, intptr_t offset, int32_t val) {
+void CCBasicObject::WriteInt32(const char *address, intptr_t offset, int32_t val) {
 	*(int32_t *)(const_cast<char *>(address) + offset) = val;
 }
 
-void AGSCCDynamicObject::WriteFloat(const char *address, intptr_t offset, float val) {
+void CCBasicObject::WriteFloat(const char *address, intptr_t offset, float val) {
 	*(float *)(const_cast<char *>(address) + offset) = val;
 }
 
+
+int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize) {
+	// If the required space is larger than the provided buffer,
+	// then return negated required space, notifying the caller that a larger buffer is necessary
+	size_t req_size = CalcSerializeSize(address);
+	if (bufsize < 0 || req_size > static_cast<size_t>(bufsize))
+		return -(static_cast<int32_t>(req_size));
+
+	MemoryStream mems(reinterpret_cast<uint8_t *>(buffer), bufsize, kStream_Write);
+	Serialize(address, &mems);
+	return static_cast<int32_t>(mems.GetPosition());
+}
+
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index 32c9afce03a..63f12685807 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -21,16 +21,19 @@
 
 //=============================================================================
 //
-// The common implementation for ICCDynamicObject interface.
-// Intended to be used as a parent class for majority of the
-// dynamic object managers.
+// This is a collection of common implementations of the ICCDynamicObject
+// interface. Intended to be used as parent classes for majority of the
+// script object managers.
 //
-// Basic implementation of:
-// * Serialization from a raw buffer; provides a virtual function that
-//   accepts Stream, to be implemented in children instead.
-// * Provides Unserialize interface that accepts Stream.
-// * Data Read/Write methods that treat the contents of the object as
-//   a raw byte buffer.
+// CCBasicObject: parent for managers that treat object contents as raw
+// byte buffer.
+//
+// AGSCCDynamicObject, extends CCBasicObject: parent for built-in dynamic
+// object managers; provides simplier serialization methods working with
+// streams instead of a raw memory buffer.
+//
+// AGSCCStaticObject, extends CCBasicObject: a formal stub, intended as
+// a parent for built-in static object managers.
 //
 //=============================================================================
 
@@ -43,30 +46,53 @@ namespace AGS3 {
 
 namespace AGS { namespace Shared { class Stream; } }
 
-struct AGSCCDynamicObject : ICCDynamicObject {
-protected:
-	virtual ~AGSCCDynamicObject() {}
+// CCBasicObject: basic implementation of the script object interface,
+// intended to be used as a parent for object/manager classes that do not
+// require specific implementation.
+// * Dispose ignored, never deletes any data on its own;
+// * Serialization skipped, does not save or load anything;
+// * Provides default implementation for reading and writing data fields,
+//   treats the contents of an object as a raw byte buffer.
+struct CCBasicObject : ICCDynamicObject {
 public:
-	// default implementation
-	int Dispose(const char *address, bool force) override;
+	virtual ~CCBasicObject() = default;
 
-	// TODO: pass savegame format version
-	int Serialize(const char *address, char *buffer, int bufsize) override;
-	// Try unserializing the object from the given input stream
-	virtual void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) = 0;
+	// Dispose the object
+	int Dispose(const char * /*address*/, bool /*force*/) override;
+	// Serialize the object into BUFFER (which is BUFSIZE bytes)
+	// return number of bytes used
+	int Serialize(const char * /*address*/, char * /*buffer*/, int /*bufsize*/) override;
 
-	// Legacy support for reading and writing object values by their relative offset
+	//
+	// Legacy support for reading and writing object fields by their relative offset
+	//
 	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
+	void Read(const char *address, intptr_t offset, void *dest, int size) override;
 	uint8_t ReadInt8(const char *address, intptr_t offset) override;
 	int16_t ReadInt16(const char *address, intptr_t offset) override;
 	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
+	float ReadFloat(const char *address, intptr_t offset) override;
+	void Write(const char *address, intptr_t offset, void *src, int size) override;
+	void WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
+	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
+	void WriteInt32(const char *address, intptr_t offset, int32_t val) override;
+	void WriteFloat(const char *address, intptr_t offset, float val) override;
+};
+
+
+// AGSCCDynamicObject: standard parent implementation for the built-in
+// script objects/manager.
+// * Serialization from a raw buffer; provides a virtual function that
+//   accepts Stream, to be implemented in children instead.
+// * Provides Unserialize interface that accepts Stream.
+struct AGSCCDynamicObject : public CCBasicObject {
+public:
+	virtual ~AGSCCDynamicObject() = default;
+
+	// TODO: pass savegame format version
+	int Serialize(const char *address, char *buffer, int bufsize) override;
+	// Try unserializing the object from the given input stream
+	virtual void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) = 0;
 
 protected:
 	// Savegame serialization
@@ -76,6 +102,18 @@ protected:
 	virtual void Serialize(const char *address, AGS::Shared::Stream *out) = 0;
 };
 
+// CCStaticObject is a base class for managing static global objects in script.
+// The static objects can never be disposed, and do not support serialization
+// through ICCDynamicObject interface.
+struct AGSCCStaticObject : CCBasicObject {
+public:
+	virtual ~AGSCCStaticObject() = default;
+
+	const char *GetType() override { return "StaticObject"; }
+};
+
+// extern AGSCCStaticObject GlobalStaticManager;
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.cpp b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
new file mode 100644
index 00000000000..ea5a6ca2855
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
@@ -0,0 +1,92 @@
+/* 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 "ags/engine/ac/dynobj/cc_static_array.h"
+#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+
+namespace AGS3 {
+
+void CCStaticArray::Create(ICCDynamicObject *mgr, int elem_legacy_size, int elem_real_size, int elem_count) {
+	_mgr = mgr;
+	_elemLegacySize = elem_legacy_size;
+	_elemRealSize = elem_real_size;
+	_elemCount = elem_count;
+}
+
+const char *CCStaticArray::GetElementPtr(const char *address, intptr_t legacy_offset) {
+	return address + (legacy_offset / _elemLegacySize) * _elemRealSize;
+}
+
+const char *CCStaticArray::GetFieldPtr(const char *address, intptr_t offset) {
+	return GetElementPtr(address, offset);
+}
+
+void CCStaticArray::Read(const char *address, intptr_t offset, void *dest, int size) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->Read(el_ptr, offset % _elemLegacySize, dest, size);
+}
+
+uint8_t CCStaticArray::ReadInt8(const char *address, intptr_t offset) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->ReadInt8(el_ptr, offset % _elemLegacySize);
+}
+
+int16_t CCStaticArray::ReadInt16(const char *address, intptr_t offset) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->ReadInt16(el_ptr, offset % _elemLegacySize);
+}
+
+int32_t CCStaticArray::ReadInt32(const char *address, intptr_t offset) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->ReadInt32(el_ptr, offset % _elemLegacySize);
+}
+
+float CCStaticArray::ReadFloat(const char *address, intptr_t offset) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->ReadFloat(el_ptr, offset % _elemLegacySize);
+}
+
+void CCStaticArray::Write(const char *address, intptr_t offset, void *src, int size) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->Write(el_ptr, offset % _elemLegacySize, src, size);
+}
+
+void CCStaticArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->WriteInt8(el_ptr, offset % _elemLegacySize, val);
+}
+
+void CCStaticArray::WriteInt16(const char *address, intptr_t offset, int16_t val) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->WriteInt16(el_ptr, offset % _elemLegacySize, val);
+}
+
+void CCStaticArray::WriteInt32(const char *address, intptr_t offset, int32_t val) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->WriteInt32(el_ptr, offset % _elemLegacySize, val);
+}
+
+void CCStaticArray::WriteFloat(const char *address, intptr_t offset, float val) {
+	const char *el_ptr = GetElementPtr(address, offset);
+	return _mgr->WriteFloat(el_ptr, offset % _elemLegacySize, val);
+}
+
+} // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.h b/engines/ags/engine/ac/dynobj/cc_static_array.h
new file mode 100644
index 00000000000..0d1cad5e803
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.h
@@ -0,0 +1,78 @@
+/* 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/>.
+ *
+ */
+
+//=============================================================================
+//
+// CCStaticArray manages access to an array of script objects,
+// where an element's size counted by script's bytecode may differ from the
+// real element size in the engine's memory.
+// The purpose of this is to remove size restriction from the engine's structs
+// exposed to scripts.
+// NOTE: on the other hand, similar effect could be achieved by separating
+// object data into two or more structs, where "base" structs are stored in
+// the exposed arrays (part of API), while extending structs are stored
+// separately. This is more an issue of engine data design.
+//
+//=============================================================================
+
+#ifndef AGS_ENGINE_AC_DYNOBJ_STATIC_ARRAY_H
+#define AGS_ENGINE_AC_DYNOBJ_STATIC_ARRAY_H
+
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+
+namespace AGS3 {
+
+
+struct CCStaticArray : public AGSCCStaticObject {
+public:
+	~CCStaticArray() override {}
+
+	void Create(ICCDynamicObject *mgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/);
+
+	inline ICCDynamicObject *GetObjectManager() const {
+		return _mgr;
+	}
+
+	// Legacy support for reading and writing object values by their relative offset
+	virtual const char *GetElementPtr(const char *address, intptr_t legacy_offset);
+
+	const char *GetFieldPtr(const char *address, intptr_t offset) override;
+	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
+	uint8_t ReadInt8(const char *address, intptr_t offset) override;
+	int16_t ReadInt16(const char *address, intptr_t offset) override;
+	int32_t ReadInt32(const char *address, intptr_t offset) override;
+	float   ReadFloat(const char *address, intptr_t offset) override;
+	void    Write(const char *address, intptr_t offset, void *src, int size) override;
+	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
+	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
+	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
+	void    WriteFloat(const char *address, intptr_t offset, float val) override;
+
+private:
+	ICCDynamicObject	*_mgr;
+	int                 _elemLegacySize;
+	int                 _elemRealSize;
+	int                 _elemCount;
+};
+
+} // namespace AGS3
+
+#endif
diff --git a/engines/ags/engine/ac/dynobj/script_file.cpp b/engines/ags/engine/ac/dynobj/script_file.cpp
index a83eaccd81d..54a47c31464 100644
--- a/engines/ags/engine/ac/dynobj/script_file.cpp
+++ b/engines/ags/engine/ac/dynobj/script_file.cpp
@@ -63,43 +63,4 @@ sc_File::sc_File() {
 	handle = 0;
 }
 
-
-const char *sc_File::GetFieldPtr(const char *address, intptr_t offset) {
-	return address;
-}
-
-void sc_File::Read(const char *address, intptr_t offset, void *dest, int size) {
-}
-
-uint8_t sc_File::ReadInt8(const char *address, intptr_t offset) {
-	return 0;
-}
-
-int16_t sc_File::ReadInt16(const char *address, intptr_t offset) {
-	return 0;
-}
-
-int32_t sc_File::ReadInt32(const char *address, intptr_t offset) {
-	return 0;
-}
-
-float sc_File::ReadFloat(const char *address, intptr_t offset) {
-	return 0.0;
-}
-
-void sc_File::Write(const char *address, intptr_t offset, void *src, int size) {
-}
-
-void sc_File::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-}
-
-void sc_File::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-}
-
-void sc_File::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-}
-
-void sc_File::WriteFloat(const char *address, intptr_t offset, float val) {
-}
-
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_file.h b/engines/ags/engine/ac/dynobj/script_file.h
index 5179c8634d8..aa0f8aeebea 100644
--- a/engines/ags/engine/ac/dynobj/script_file.h
+++ b/engines/ags/engine/ac/dynobj/script_file.h
@@ -22,7 +22,7 @@
 #ifndef AGS_ENGINE_DYNOBJ__SCRIPTFILE_H
 #define AGS_ENGINE_DYNOBJ__SCRIPTFILE_H
 
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
 #include "ags/shared/util/file.h"
 
 namespace AGS3 {
@@ -33,7 +33,7 @@ using namespace AGS; // FIXME later
 #define scFileWrite  2
 #define scFileAppend 3
 
-struct sc_File final : ICCDynamicObject {
+struct sc_File final : CCBasicObject {
 	int32_t             handle;
 
 	static const Shared::FileOpenMode fopenModes[];
@@ -49,19 +49,6 @@ struct sc_File final : ICCDynamicObject {
 	void Close();
 
 	sc_File();
-
-	// Legacy support for reading and writing object values by their relative offset
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_game.cpp b/engines/ags/engine/ac/dynobj/script_game.cpp
new file mode 100644
index 00000000000..8665dffc8a1
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/script_game.cpp
@@ -0,0 +1,39 @@
+/* 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 "ags/engine/ac/dynobj/script_game.h"
+#include "ags/engine/ac/game.h"
+#include "ags/engine/ac/game_state.h"
+
+
+namespace AGS3 {
+
+void StaticGame::WriteInt32(const char *address, intptr_t offset, int32_t val) {
+	if (offset == 4 * sizeof(int32_t)) { // game.debug_mode
+		set_debug_mode(val != 0);
+	} else if (offset == 99 * sizeof(int32_t) || offset == 112 * sizeof(int32_t)) { // game.text_align, game.speech_text_align
+		*(int32_t *)(address + offset) = ReadScriptAlignment(val);
+	} else {
+		*(int32_t *)(address + offset) = val;
+	}
+}
+
+} // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_game.h b/engines/ags/engine/ac/dynobj/script_game.h
new file mode 100644
index 00000000000..2b8f675c3fa
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/script_game.h
@@ -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/>.
+ *
+ */
+
+#ifndef AGS_ENGINE_AC_DYNOBJ_AGS_SCRIPT_GAME_H
+#define AGS_ENGINE_AC_DYNOBJ_AGS_SCRIPT_GAME_H
+
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+
+namespace AGS3 {
+
+// Wrapper around script's "Game" struct, managing access to its variables
+struct StaticGame : public AGSCCStaticObject {
+	const char *GetType() override { return "Game"; }
+	void WriteInt32(const char *address, intptr_t offset, int32_t val) override;
+};
+
+} // namespace AGS3
+
+#endif
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 3a4f683fd64..c337685133c 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -57,6 +57,7 @@
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
 #include "ags/engine/ac/dynobj/script_camera.h"
+#include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/debugging/debugger.h"
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 83e129ecf4c..0db19f870c6 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -34,8 +34,6 @@
 #include "ags/engine/ac/dynobj/all_dynamic_classes.h"
 #include "ags/engine/ac/dynobj/all_script_classes.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
-#include "ags/engine/ac/statobj/ags_static_object.h"
-#include "ags/engine/ac/statobj/static_array.h"
 #include "ags/shared/ac/view.h"
 #include "ags/shared/core/asset_manager.h"
 #include "ags/engine/debugging/debug_log.h"
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index e0a18fb6e5a..b4bfb697eee 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -38,8 +38,6 @@
 #include "ags/shared/util/text_stream_writer.h"
 #include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/ac/dynobj/script_user_object.h"
-#include "ags/engine/ac/statobj/ags_static_object.h"
-#include "ags/engine/ac/statobj/static_array.h"
 #include "ags/engine/ac/sys_events.h"
 #include "ags/shared/util/memory.h"
 #include "ags/shared/util/string_utils.h" // linux strnicmp definition
@@ -1033,8 +1031,9 @@ int ccInstance::Run(int32_t curpc) {
 			const char *address;
 			switch (reg1.Type) {
 			case kScValStaticArray:
-				CC_ERROR_IF_RETCODE(!reg1.StcArr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
-				address = reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue);
+				// FIXME: return manager type from interface?
+				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
+				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
@@ -1070,8 +1069,9 @@ int ccInstance::Run(int32_t curpc) {
 
 			switch (reg1.Type) {
 			case kScValStaticArray:
-				CC_ERROR_IF_RETCODE(!reg1.StcArr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
-				address = (const char *)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue);
+				// FIXME: return manager type from interface?
+				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
+				address = (char *)reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
@@ -1293,13 +1293,12 @@ int ccInstance::Run(int32_t curpc) {
 				registers[SREG_OP] = reg1;
 				break;
 			case kScValStaticArray:
-				if (reg1.StcArr->GetDynamicManager()) {
-					registers[SREG_OP].SetDynamicObject(
-					    (char *)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue),
-					    reg1.StcArr->GetDynamicManager());
-					break;
-				}
-			// fall through
+				// FIXME: return manager type from interface?
+				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_CALLOBJ argument is not a dynamic object");
+				registers[SREG_OP].SetDynamicObject(
+					(char *)reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue),
+					reg1.ArrMgr->GetObjectManager());
+				break;
 			default:
 				cc_error("internal error: SCMD_CALLOBJ argument is not an object of built-in or user-defined type");
 				return -1;
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index d2bb3200ece..5c2b236ca41 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -22,7 +22,6 @@
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_object.h"
-#include "ags/engine/ac/statobj/static_object.h"
 #include "ags/shared/util/memory.h"
 
 namespace AGS3 {
@@ -49,9 +48,8 @@ uint8_t RuntimeScriptValue::ReadByte() const {
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
-		return this->StcMgr->ReadInt8(this->Ptr, this->IValue);
 	case kScValDynamicObject:
-		return this->DynMgr->ReadInt8(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt8(this->Ptr, this->IValue);
 	default:
 		return *((uint8_t *)this->GetPtrWithOffset());
 	}
@@ -73,9 +71,8 @@ int16_t RuntimeScriptValue::ReadInt16() const {
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
-		return this->StcMgr->ReadInt16(this->Ptr, this->IValue);
 	case kScValDynamicObject:
-		return this->DynMgr->ReadInt16(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt16(this->Ptr, this->IValue);
 
 	default:
 		return *((int16_t *)this->GetPtrWithOffset());
@@ -98,9 +95,8 @@ int32_t RuntimeScriptValue::ReadInt32() const {
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
-		return this->StcMgr->ReadInt32(this->Ptr, this->IValue);
 	case kScValDynamicObject:
-		return this->DynMgr->ReadInt32(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt32(this->Ptr, this->IValue);
 	default:
 		return *((int32_t *)this->GetPtrWithOffset());
 	}
@@ -118,10 +114,8 @@ void RuntimeScriptValue::WriteByte(uint8_t val) {
 		break;
 	case kScValStaticObject:
 	case kScValStaticArray:
-		this->StcMgr->WriteInt8(this->Ptr, this->IValue, val);
-		break;
 	case kScValDynamicObject:
-		this->DynMgr->WriteInt8(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt8(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((uint8_t *)this->GetPtrWithOffset()) = val;
@@ -147,10 +141,8 @@ void RuntimeScriptValue::WriteInt16(int16_t val) {
 		break;
 	case kScValStaticObject:
 	case kScValStaticArray:
-		this->StcMgr->WriteInt16(this->Ptr, this->IValue, val);
-		break;
 	case kScValDynamicObject:
-		this->DynMgr->WriteInt16(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt16(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int16_t *)this->GetPtrWithOffset()) = val;
@@ -176,10 +168,8 @@ void RuntimeScriptValue::WriteInt32(int32_t val) {
 		break;
 	case kScValStaticObject:
 	case kScValStaticArray:
-		this->StcMgr->WriteInt32(this->Ptr, this->IValue, val);
-		break;
 	case kScValDynamicObject:
-		this->DynMgr->WriteInt32(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt32(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int32_t *)this->GetPtrWithOffset()) = val;
@@ -195,10 +185,8 @@ RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
 	}
 
 	if (Ptr) {
-		if (Type == kScValDynamicObject)
-			Ptr = const_cast<char *>(DynMgr->GetFieldPtr(Ptr, IValue));
-		else if (Type == kScValStaticObject)
-			Ptr = const_cast<char *>(StcMgr->GetFieldPtr(Ptr, IValue));
+		if (Type == kScValDynamicObject || Type == kScValStaticObject)
+			Ptr = const_cast<char *>(ObjMgr->GetFieldPtr(Ptr, IValue));
 		else
 			Ptr += IValue;
 		IValue = 0;
@@ -219,10 +207,8 @@ intptr_t RuntimeScriptValue::GetDirectPtr() const {
 		temp_val = temp_val->RValue;
 		ival += temp_val->IValue;
 	}
-	if (temp_val->Type == kScValDynamicObject)
-		return (intptr_t)temp_val->DynMgr->GetFieldPtr(temp_val->Ptr, ival);
-	else if (temp_val->Type == kScValStaticObject)
-		return (intptr_t)temp_val->StcMgr->GetFieldPtr(temp_val->Ptr, ival);
+	if (temp_val->Type == kScValDynamicObject || temp_val->Type == kScValStaticObject)
+		return (intptr_t)temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
 	else
 		return (intptr_t)(temp_val->Ptr + ival);
 }
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 160c3514811..42d7bb5964c 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -29,8 +29,7 @@
 #define AGS_ENGINE_SCRIPT_RUNTIME_SCRIPT_VALUE_H
 
 #include "ags/engine/ac/dynobj/cc_dynamic_object.h"
-#include "ags/engine/ac/statobj/static_object.h"
-#include "ags/engine/ac/statobj/static_array.h"
+#include "ags/engine/ac/dynobj/cc_static_array.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/shared/util/memory.h"
 
@@ -100,10 +99,9 @@ public:
 	// a separation between Script*, Dynamic* and game entity classes.
 	// Once those classes are merged, it will no longer be needed.
 	union {
-		void *MgrPtr;// generic object manager pointer
-		ICCStaticObject *StcMgr;// static object manager
-		StaticArray *StcArr;// static array manager
-		ICCDynamicObject *DynMgr;// dynamic object manager
+		void			 *MgrPtr;  // generic object manager pointer
+		ICCDynamicObject *ObjMgr;  // script object manager
+		CCStaticArray	 *ArrMgr;  // static array manager
 	};
 	// The "real" size of data, either one stored in I/FValue,
 	// or the one referenced by Ptr. Used for calculating stack
@@ -219,21 +217,21 @@ public:
 		Size = 4;
 		return *this;
 	}
-	inline RuntimeScriptValue &SetStaticObject(void *object, ICCStaticObject *manager) {
+	inline RuntimeScriptValue &SetStaticObject(void *object, ICCDynamicObject *manager) {
 		Type = kScValStaticObject;
 		methodName.clear();
 		IValue = 0;
 		Ptr = (char *)object;
-		StcMgr = manager;
+		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
-	inline RuntimeScriptValue &SetStaticArray(void *object, StaticArray *manager) {
+	inline RuntimeScriptValue &SetStaticArray(void *object, CCStaticArray *manager) {
 		Type = kScValStaticArray;
 		methodName.clear();
 		IValue = 0;
 		Ptr = (char *)object;
-		StcArr = manager;
+		ArrMgr = manager;
 		Size = 4;
 		return *this;
 	}
@@ -242,7 +240,7 @@ public:
 		methodName.clear();
 		IValue = 0;
 		Ptr = (char *)object;
-		DynMgr = manager;
+		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
@@ -251,7 +249,7 @@ public:
 		methodName.clear();
 		IValue = 0;
 		Ptr = (char *)object;
-		DynMgr = manager;
+		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
@@ -259,7 +257,7 @@ public:
 		Type = type;
 		IValue = 0;
 		Ptr = (char *)object;
-		DynMgr = manager;
+		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
@@ -345,9 +343,8 @@ public:
 		}
 		case kScValStaticObject:
 		case kScValStaticArray:
-			return RuntimeScriptValue().SetInt32(this->StcMgr->ReadInt32(this->Ptr, this->IValue));
 		case kScValDynamicObject:
-			return RuntimeScriptValue().SetInt32(this->DynMgr->ReadInt32(this->Ptr, this->IValue));
+			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32(this->Ptr, this->IValue));
 		default:
 			return RuntimeScriptValue().SetInt32(*(int32_t *)this->GetPtrWithOffset());
 		}
@@ -392,12 +389,9 @@ public:
 			break;
 		}
 		case kScValStaticObject:
-		case kScValStaticArray: {
-			this->StcMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
-			break;
-		}
+		case kScValStaticArray:
 		case kScValDynamicObject: {
-			this->DynMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
+			this->ObjMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
 			break;
 		}
 		default: {
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index a366efe19d2..b9ad5baed8f 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -32,7 +32,6 @@
 //include <stdarg.h>
 #include "ags/shared/core/types.h"
 #include "ags/engine/ac/runtime_defines.h"
-#include "ags/engine/ac/statobj/ags_static_object.h"
 #include "ags/shared/debugging/out.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index 686a29779b0..5846a994b32 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -21,7 +21,6 @@
 
 #include "ags/engine/script/script_runtime.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
-#include "ags/engine/ac/statobj/static_object.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/system_imports.h"
 #include "ags/globals.h"
@@ -63,11 +62,11 @@ bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *i
 	return _GP(simp).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager) {
+bool ccAddExternalStaticObject(const String &name, void *ptr, ICCDynamicObject *manager) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticObject(ptr, manager), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr) {
+bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *array_mgr) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticArray(ptr, array_mgr), nullptr) != UINT32_MAX;
 }
 
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index a569dab2cfd..2b7048df29d 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -28,9 +28,7 @@
 
 namespace AGS3 {
 
-struct ICCStaticObject;
 struct ICCDynamicObject;
-struct StaticArray;
 
 using AGS::Shared::String;
 
@@ -64,8 +62,9 @@ bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer
 // Register a function, exported from a plugin. Requires direct function pointer only.
 bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *sc);
 // Register engine objects for script's access.
-bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager);
-bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr);
+// TODO: get manager type from the interface!
+bool ccAddExternalStaticObject(const String &name, void *ptr, ICCDynamicObject *manager);
+bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *array_mgr);
 bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager);
 // Register script own functions (defined in the linked scripts)
 bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst);
diff --git a/engines/ags/engine/script/system_imports.h b/engines/ags/engine/script/system_imports.h
index 56436c5b785..0f86e7a9749 100644
--- a/engines/ags/engine/script/system_imports.h
+++ b/engines/ags/engine/script/system_imports.h
@@ -28,7 +28,6 @@
 namespace AGS3 {
 
 struct ICCDynamicObject;
-struct ICCStaticObject;
 
 using AGS::Shared::String;
 
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index f0e87561bd4..cbb5420c505 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -65,10 +65,12 @@
 #include "ags/engine/ac/dynobj/cc_object.h"
 #include "ags/engine/ac/dynobj/cc_region.h"
 #include "ags/engine/ac/dynobj/cc_serializer.h"
+#include "ags/engine/ac/dynobj/cc_static_array.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
 #include "ags/engine/ac/dynobj/script_audio_channel.h"
 #include "ags/engine/ac/dynobj/script_dialog.h"
 #include "ags/engine/ac/dynobj/script_dialog_options_rendering.h"
+#include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
 #include "ags/engine/ac/dynobj/script_hotspot.h"
 #include "ags/engine/ac/dynobj/script_inv_item.h"
@@ -76,7 +78,6 @@
 #include "ags/engine/ac/dynobj/script_region.h"
 #include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/ac/dynobj/script_system.h"
-#include "ags/engine/ac/statobj/static_array.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/engine/debugging/log_file.h"
 #include "ags/engine/debugging/message_buffer.h"
@@ -119,8 +120,8 @@ Globals::Globals() {
 	// ags_plugin.cpp globals
 	_glVirtualScreenWrap = new AGS::Shared::Bitmap();
 
-	// ags_static_object.cpp globals
-	_GlobalStaticManager = new AGSStaticObject();
+	// cc_ags_dynamic_object.cpp globals
+	_GlobalStaticManager = new AGSCCStaticObject();
 	_GameStaticManager = new StaticGame();
 
 	// asset_manager.cpp globals
@@ -253,13 +254,13 @@ Globals::Globals() {
 	_saveGameDirectory = AGS::Shared::SAVE_FOLDER_PREFIX;
 
 	// game_init.cpp globals
-	_StaticCharacterArray = new StaticArray();
-	_StaticObjectArray = new StaticArray();
-	_StaticGUIArray = new StaticArray();
-	_StaticHotspotArray = new StaticArray();
-	_StaticRegionArray = new StaticArray();
-	_StaticInventoryArray = new StaticArray();
-	_StaticDialogArray = new StaticArray();
+	_StaticCharacterArray = new CCStaticArray();
+	_StaticObjectArray = new CCStaticArray();
+	_StaticGUIArray = new CCStaticArray();
+	_StaticHotspotArray = new CCStaticArray();
+	_StaticRegionArray = new CCStaticArray();
+	_StaticInventoryArray = new CCStaticArray();
+	_StaticDialogArray = new CCStaticArray();
 
 	// game_run.cpp globals
 	_fps = std::numeric_limits<float>::quiet_NaN();
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 95196eded14..f5a70bf82ed 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -109,9 +109,9 @@ class SplitLines;
 class TTFFontRenderer;
 class WFNFontRenderer;
 
+struct AGSCCStaticObject;
 struct AGSDeSerializer;
 struct AGSPlatformDriver;
-struct AGSStaticObject;
 struct AmbientSound;
 struct AnimatingGUIButton;
 struct CachedActSpsData;
@@ -177,6 +177,7 @@ struct SOUNDCLIP;
 struct SpeechLipSyncLine;
 struct SpriteListEntry;
 struct StaticArray;
+struct CCStaticArray;
 struct StaticGame;
 struct SystemImports;
 struct TopBarSettings;
@@ -302,8 +303,8 @@ public:
 	 * @{
 	 */
 
-	AGSStaticObject *_GlobalStaticManager;
-	StaticGame      *_GameStaticManager;
+	AGSCCStaticObject *_GlobalStaticManager;
+	StaticGame        *_GameStaticManager;
 
 	/**@}*/
 
@@ -807,13 +808,13 @@ public:
 	 * @{
 	 */
 
-	StaticArray *_StaticCharacterArray;
-	StaticArray *_StaticObjectArray;
-	StaticArray *_StaticGUIArray;
-	StaticArray *_StaticHotspotArray;
-	StaticArray *_StaticRegionArray;
-	StaticArray *_StaticInventoryArray;
-	StaticArray *_StaticDialogArray;
+	CCStaticArray *_StaticCharacterArray;
+	CCStaticArray *_StaticObjectArray;
+	CCStaticArray *_StaticGUIArray;
+	CCStaticArray *_StaticHotspotArray;
+	CCStaticArray *_StaticRegionArray;
+	CCStaticArray *_StaticInventoryArray;
+	CCStaticArray *_StaticDialogArray;
 
 	/**@}*/
 
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 2734cfa69df..c1021770876 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -207,6 +207,7 @@ MODULE_OBJS = \
 	engine/ac/dynobj/cc_object.o \
 	engine/ac/dynobj/cc_region.o \
 	engine/ac/dynobj/cc_serializer.o \
+	engine/ac/dynobj/cc_static_array.o \
 	engine/ac/dynobj/dynobj_manager.o \
 	engine/ac/dynobj/managed_object_pool.o \
 	engine/ac/dynobj/script_camera.o \
@@ -216,14 +217,13 @@ MODULE_OBJS = \
 	engine/ac/dynobj/script_drawing_surface.o \
 	engine/ac/dynobj/script_dynamic_sprite.o \
 	engine/ac/dynobj/script_file.o \
+	engine/ac/dynobj/script_game.o \
 	engine/ac/dynobj/script_overlay.o \
 	engine/ac/dynobj/script_set.o \
 	engine/ac/dynobj/script_string.o \
 	engine/ac/dynobj/script_user_object.o \
 	engine/ac/dynobj/script_viewport.o \
 	engine/ac/dynobj/script_view_frame.o \
-	engine/ac/statobj/ags_static_object.o \
-	engine/ac/statobj/static_array.o \
 	engine/debugging/debug.o \
 	engine/debugging/file_based_ags_debugger.o \
 	engine/debugging/log_file.o \


Commit: 5cb2873a83c5c1e2d018c9bb0e1fdbf38f412aff
    https://github.com/scummvm/scummvm/commit/5cb2873a83c5c1e2d018c9bb0e1fdbf38f412aff
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Remove leftover files

This complements 1300d346e91d751feb3cfed51af76d214de8eaa5

Changed paths:
  R engines/ags/engine/ac/statobj/ags_static_object.cpp
  R engines/ags/engine/ac/statobj/ags_static_object.h
  R engines/ags/engine/ac/statobj/static_array.cpp
  R engines/ags/engine/ac/statobj/static_array.h
  R engines/ags/engine/ac/statobj/static_object.h


diff --git a/engines/ags/engine/ac/statobj/ags_static_object.cpp b/engines/ags/engine/ac/statobj/ags_static_object.cpp
deleted file mode 100644
index f2021249536..00000000000
--- a/engines/ags/engine/ac/statobj/ags_static_object.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/* 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 "ags/shared/ac/game_setup_struct.h"
-#include "ags/engine/ac/statobj/ags_static_object.h"
-#include "ags/engine/ac/game.h"
-#include "ags/engine/ac/game_state.h"
-#include "ags/globals.h"
-
-namespace AGS3 {
-
-using namespace AGS::Shared;
-
-const char *AGSStaticObject::GetFieldPtr(const char *address, intptr_t offset) {
-	return address + offset;
-}
-
-void AGSStaticObject::Read(const char *address, intptr_t offset, void *dest, int size) {
-	memcpy(dest, address + offset, size);
-}
-
-uint8_t AGSStaticObject::ReadInt8(const char *address, intptr_t offset) {
-	return *(const uint8_t *)(address + offset);
-}
-
-int16_t AGSStaticObject::ReadInt16(const char *address, intptr_t offset) {
-	return *(const int16_t *)(address + offset);
-}
-
-int32_t AGSStaticObject::ReadInt32(const char *address, intptr_t offset) {
-	return *(const int32_t *)(address + offset);
-}
-
-float AGSStaticObject::ReadFloat(const char *address, intptr_t offset) {
-	return *(const float *)(address + offset);
-}
-
-void AGSStaticObject::Write(const char *address, intptr_t offset, void *src, int size) {
-	memcpy((void *)(const_cast<char *>(address) + offset), src, size);
-}
-
-void AGSStaticObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-	*(uint8_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void AGSStaticObject::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	*(int16_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void AGSStaticObject::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	*(int32_t *)(const_cast<char *>(address) + offset) = val;
-}
-
-void AGSStaticObject::WriteFloat(const char *address, intptr_t offset, float val) {
-	*(float *)(const_cast<char *>(address) + offset) = val;
-}
-
-
-void StaticGame::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	if (offset == 4 * sizeof(int32_t)) {
-		set_debug_mode(val != 0);
-	} else if (offset == 57 * sizeof(int32_t)) {
-		_GP(play).inv_top = val;
-		GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
-	} else if (offset == 99 * sizeof(int32_t) || offset == 112 * sizeof(int32_t)) {
-		*(int32_t *)(const_cast<char *>(address) + offset) = ReadScriptAlignment(val);
-	} else {
-		*(int32_t *)(const_cast<char *>(address) + offset) = val;
-	}
-}
-
-
-} // namespace AGS3
diff --git a/engines/ags/engine/ac/statobj/ags_static_object.h b/engines/ags/engine/ac/statobj/ags_static_object.h
deleted file mode 100644
index 15b32b783a5..00000000000
--- a/engines/ags/engine/ac/statobj/ags_static_object.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* 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 AGS_ENGINE_AC_STATOBJ_AGS_STATIC_OBJECT_H
-#define AGS_ENGINE_AC_STATOBJ_AGS_STATIC_OBJECT_H
-
-#include "ags/engine/ac/statobj/static_object.h"
-
-namespace AGS3 {
-
-struct AGSStaticObject : public ICCStaticObject {
-	~AGSStaticObject() override {}
-
-	// Legacy support for reading and writing object values by their relative offset
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
-};
-
-// Wrapper around script's "Game" struct, managing access to its variables
-struct StaticGame : public AGSStaticObject {
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-};
-
-} // namespace AGS3
-
-#endif
diff --git a/engines/ags/engine/ac/statobj/static_array.cpp b/engines/ags/engine/ac/statobj/static_array.cpp
deleted file mode 100644
index 62bb0bc94db..00000000000
--- a/engines/ags/engine/ac/statobj/static_array.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/* 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 "ags/engine/ac/statobj/static_array.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
-
-namespace AGS3 {
-
-void StaticArray::Create(int elem_legacy_size, int elem_real_size, int elem_count) {
-	_staticMgr = nullptr;
-	_dynamicMgr = nullptr;
-	_elemLegacySize = elem_legacy_size;
-	_elemRealSize = elem_real_size;
-	_elemCount = elem_count;
-}
-
-void StaticArray::Create(ICCStaticObject *stcmgr, int elem_legacy_size, int elem_real_size, int elem_count) {
-	_staticMgr = stcmgr;
-	_dynamicMgr = nullptr;
-	_elemLegacySize = elem_legacy_size;
-	_elemRealSize = elem_real_size;
-	_elemCount = elem_count;
-}
-
-void StaticArray::Create(ICCDynamicObject *dynmgr, int elem_legacy_size, int elem_real_size, int elem_count) {
-	_staticMgr = nullptr;
-	_dynamicMgr = dynmgr;
-	_elemLegacySize = elem_legacy_size;
-	_elemRealSize = elem_real_size;
-	_elemCount = elem_count;
-}
-
-const char *StaticArray::GetElementPtr(const char *address, intptr_t legacy_offset) {
-	return address + (legacy_offset / _elemLegacySize) * _elemRealSize;
-}
-
-char *StaticArray::GetElementPtr(char *address, intptr_t legacy_offset) {
-	return address + (legacy_offset / _elemLegacySize) * _elemRealSize;
-}
-
-const char *StaticArray::GetFieldPtr(const char *address, intptr_t offset) {
-	return GetElementPtr(address, offset);
-}
-
-void StaticArray::Read(const char *address, intptr_t offset, void *dest, int size) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->Read(el_ptr, offset % _elemLegacySize, dest, size);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->Read(el_ptr, offset % _elemLegacySize, dest, size);
-	}
-	memcpy(dest, el_ptr + offset % _elemLegacySize, size);
-}
-
-uint8_t StaticArray::ReadInt8(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->ReadInt8(el_ptr, offset % _elemLegacySize);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->ReadInt8(el_ptr, offset % _elemLegacySize);
-	}
-
-	return *(const uint8_t *)(el_ptr + offset % _elemLegacySize);
-}
-
-int16_t StaticArray::ReadInt16(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->ReadInt16(el_ptr, offset % _elemLegacySize);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->ReadInt16(el_ptr, offset % _elemLegacySize);
-	}
-
-	return *(const uint16_t *)(el_ptr + offset % _elemLegacySize);
-}
-
-int32_t StaticArray::ReadInt32(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->ReadInt32(el_ptr, offset % _elemLegacySize);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->ReadInt32(el_ptr, offset % _elemLegacySize);
-	}
-
-	return *(const uint32_t *)(el_ptr + offset % _elemLegacySize);
-}
-
-float StaticArray::ReadFloat(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->ReadFloat(el_ptr, offset % _elemLegacySize);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->ReadFloat(el_ptr, offset % _elemLegacySize);
-	}
-
-	return *(const float *)(el_ptr + offset % _elemLegacySize);
-}
-
-void StaticArray::Write(const char *address, intptr_t offset, void *src, int size) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->Write(el_ptr, offset % _elemLegacySize, src, size);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->Write(el_ptr, offset % _elemLegacySize, src, size);
-	} else {
-		memcpy((void *)(const_cast<char *>(el_ptr) + offset % _elemLegacySize), src, size);
-	}
-}
-
-void StaticArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->WriteInt8(el_ptr, offset % _elemLegacySize, val);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->WriteInt8(el_ptr, offset % _elemLegacySize, val);
-	} else {
-		*(uint8_t *)(const_cast<char *>(el_ptr) + offset % _elemLegacySize) = val;
-	}
-}
-
-void StaticArray::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->WriteInt16(el_ptr, offset % _elemLegacySize, val);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->WriteInt16(el_ptr, offset % _elemLegacySize, val);
-	} else {
-		*(uint16_t *)(const_cast<char *>(el_ptr) + offset % _elemLegacySize) = val;
-	}
-}
-
-void StaticArray::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->WriteInt32(el_ptr, offset % _elemLegacySize, val);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->WriteInt32(el_ptr, offset % _elemLegacySize, val);
-	} else {
-		*(uint32_t *)(const_cast<char *>(el_ptr) + offset % _elemLegacySize) = val;
-	}
-}
-
-void StaticArray::WriteFloat(const char *address, intptr_t offset, float val) {
-	const char *el_ptr = GetElementPtr(address, offset);
-	if (_staticMgr) {
-		return _staticMgr->WriteFloat(el_ptr, offset % _elemLegacySize, val);
-	} else if (_dynamicMgr) {
-		return _dynamicMgr->WriteFloat(el_ptr, offset % _elemLegacySize, val);
-	} else {
-		*(float *)(const_cast<char *>(el_ptr) + offset % _elemLegacySize) = val;
-	}
-}
-
-} // namespace AGS3
diff --git a/engines/ags/engine/ac/statobj/static_array.h b/engines/ags/engine/ac/statobj/static_array.h
deleted file mode 100644
index 54fdaadb8db..00000000000
--- a/engines/ags/engine/ac/statobj/static_array.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* 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 AGS_ENGINE_AC_STATOBJ_STATIC_ARRAY_H
-#define AGS_ENGINE_AC_STATOBJ_STATIC_ARRAY_H
-
-#include "ags/engine/ac/statobj/static_object.h"
-
-namespace AGS3 {
-
-struct ICCDynamicObject;
-
-struct StaticArray : public ICCStaticObject {
-public:
-	~StaticArray() override {}
-
-	void Create(int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/);
-	void Create(ICCStaticObject *stcmgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/);
-	void Create(ICCDynamicObject *dynmgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/);
-
-	inline ICCStaticObject *GetStaticManager() const {
-		return _staticMgr;
-	}
-	inline ICCDynamicObject *GetDynamicManager() const {
-		return _dynamicMgr;
-	}
-	// Legacy support for reading and writing object values by their relative offset
-	virtual const char *GetElementPtr(const char *address, intptr_t legacy_offset);
-	virtual char *GetElementPtr(char *address, intptr_t legacy_offset);
-
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
-
-private:
-	ICCStaticObject *_staticMgr;
-	ICCDynamicObject *_dynamicMgr;
-	int                 _elemLegacySize;
-	int                 _elemRealSize;
-	int                 _elemCount;
-};
-
-} // namespace AGS3
-
-#endif
diff --git a/engines/ags/engine/ac/statobj/static_object.h b/engines/ags/engine/ac/statobj/static_object.h
deleted file mode 100644
index 2480a7e2e1f..00000000000
--- a/engines/ags/engine/ac/statobj/static_object.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* 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/>.
- *
- */
-
-//=============================================================================
-//
-// A stub class for "managing" static global objects exported to script.
-// This may be temporary solution (oh no, not again :P) that could be
-// replaced by the use of dynamic objects in the future.
-//
-//=============================================================================
-
-#ifndef AGS_ENGINE_AC_STATOBJ_STATICOBJECT_H
-#define AGS_ENGINE_AC_STATOBJ_STATICOBJECT_H
-
-#include "ags/shared/core/types.h"
-
-namespace AGS3 {
-
-struct ICCStaticObject {
-	virtual ~ICCStaticObject() {}
-
-	// Legacy support for reading and writing object values by their relative offset
-	virtual const char *GetFieldPtr(const char *address, intptr_t offset) = 0;
-	virtual void    Read(const char *address, intptr_t offset, void *dest, int size) = 0;
-	virtual uint8_t ReadInt8(const char *address, intptr_t offset) = 0;
-	virtual int16_t ReadInt16(const char *address, intptr_t offset) = 0;
-	virtual int32_t ReadInt32(const char *address, intptr_t offset) = 0;
-	virtual float   ReadFloat(const char *address, intptr_t offset) = 0;
-	virtual void    Write(const char *address, intptr_t offset, void *src, int size) = 0;
-	virtual void    WriteInt8(const char *address, intptr_t offset, uint8_t val) = 0;
-	virtual void    WriteInt16(const char *address, intptr_t offset, int16_t val) = 0;
-	virtual void    WriteInt32(const char *address, intptr_t offset, int32_t val) = 0;
-	virtual void    WriteFloat(const char *address, intptr_t offset, float val) = 0;
-};
-
-} // namespace AGS3
-
-#endif


Commit: 87caf7fd93021dc93b7b0a629d35b1d89723f241
    https://github.com/scummvm/scummvm/commit/87caf7fd93021dc93b7b0a629d35b1d89723f241
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidy CCStaticArray

>From upstream 30c56a93d7e66974e3f86c687c2fe169f7de6b81

Changed paths:
    engines/ags/engine/ac/dynobj/cc_static_array.cpp
    engines/ags/engine/ac/dynobj/cc_static_array.h


diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.cpp b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
index ea5a6ca2855..c09a60d7ca2 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
@@ -24,15 +24,15 @@
 
 namespace AGS3 {
 
-void CCStaticArray::Create(ICCDynamicObject *mgr, int elem_legacy_size, int elem_real_size, int elem_count) {
+void CCStaticArray::Create(ICCDynamicObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count) {
 	_mgr = mgr;
-	_elemLegacySize = elem_legacy_size;
-	_elemRealSize = elem_real_size;
+	_elemScriptSize = elem_script_size;
+	_elemMemSize = elem_mem_size;
 	_elemCount = elem_count;
 }
 
 const char *CCStaticArray::GetElementPtr(const char *address, intptr_t legacy_offset) {
-	return address + (legacy_offset / _elemLegacySize) * _elemRealSize;
+	return address + (legacy_offset / _elemScriptSize) * _elemMemSize;
 }
 
 const char *CCStaticArray::GetFieldPtr(const char *address, intptr_t offset) {
@@ -41,52 +41,52 @@ const char *CCStaticArray::GetFieldPtr(const char *address, intptr_t offset) {
 
 void CCStaticArray::Read(const char *address, intptr_t offset, void *dest, int size) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->Read(el_ptr, offset % _elemLegacySize, dest, size);
+	return _mgr->Read(el_ptr, offset % _elemScriptSize, dest, size);
 }
 
 uint8_t CCStaticArray::ReadInt8(const char *address, intptr_t offset) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->ReadInt8(el_ptr, offset % _elemLegacySize);
+	return _mgr->ReadInt8(el_ptr, offset % _elemScriptSize);
 }
 
 int16_t CCStaticArray::ReadInt16(const char *address, intptr_t offset) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->ReadInt16(el_ptr, offset % _elemLegacySize);
+	return _mgr->ReadInt16(el_ptr, offset % _elemScriptSize);
 }
 
 int32_t CCStaticArray::ReadInt32(const char *address, intptr_t offset) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->ReadInt32(el_ptr, offset % _elemLegacySize);
+	return _mgr->ReadInt32(el_ptr, offset % _elemScriptSize);
 }
 
 float CCStaticArray::ReadFloat(const char *address, intptr_t offset) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->ReadFloat(el_ptr, offset % _elemLegacySize);
+	return _mgr->ReadFloat(el_ptr, offset % _elemScriptSize);
 }
 
 void CCStaticArray::Write(const char *address, intptr_t offset, void *src, int size) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->Write(el_ptr, offset % _elemLegacySize, src, size);
+	return _mgr->Write(el_ptr, offset % _elemScriptSize, src, size);
 }
 
 void CCStaticArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->WriteInt8(el_ptr, offset % _elemLegacySize, val);
+	return _mgr->WriteInt8(el_ptr, offset % _elemScriptSize, val);
 }
 
 void CCStaticArray::WriteInt16(const char *address, intptr_t offset, int16_t val) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->WriteInt16(el_ptr, offset % _elemLegacySize, val);
+	return _mgr->WriteInt16(el_ptr, offset % _elemScriptSize, val);
 }
 
 void CCStaticArray::WriteInt32(const char *address, intptr_t offset, int32_t val) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->WriteInt32(el_ptr, offset % _elemLegacySize, val);
+	return _mgr->WriteInt32(el_ptr, offset % _elemScriptSize, val);
 }
 
 void CCStaticArray::WriteFloat(const char *address, intptr_t offset, float val) {
 	const char *el_ptr = GetElementPtr(address, offset);
-	return _mgr->WriteFloat(el_ptr, offset % _elemLegacySize, val);
+	return _mgr->WriteFloat(el_ptr, offset % _elemScriptSize, val);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.h b/engines/ags/engine/ac/dynobj/cc_static_array.h
index 0d1cad5e803..aa5772f4129 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.h
@@ -45,7 +45,7 @@ struct CCStaticArray : public AGSCCStaticObject {
 public:
 	~CCStaticArray() override {}
 
-	void Create(ICCDynamicObject *mgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/);
+	void Create(ICCDynamicObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count = SIZE_MAX /*unknown*/);
 
 	inline ICCDynamicObject *GetObjectManager() const {
 		return _mgr;
@@ -67,10 +67,10 @@ public:
 	void    WriteFloat(const char *address, intptr_t offset, float val) override;
 
 private:
-	ICCDynamicObject	*_mgr;
-	int                 _elemLegacySize;
-	int                 _elemRealSize;
-	int                 _elemCount;
+	ICCDynamicObject *_mgr = nullptr;
+	size_t			 _elemScriptSize = 0u;
+	size_t			 _elemMemSize = 0u;
+	size_t			 _elemCount = 0u;
 };
 
 } // namespace AGS3


Commit: 70dd8cb1f866d139047148521b775ca49bf7f41f
    https://github.com/scummvm/scummvm/commit/70dd8cb1f866d139047148521b775ca49bf7f41f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in RuntimeScriptValue store generic ptr as void*

>From upstream 9d11b497907419ce9bf0ea47eb585bd748a3e027

Changed paths:
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/engine/script/script_api.cpp
    engines/ags/engine/script/script_api.h


diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index fb6d5acecf1..f6097f6c022 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -291,7 +291,7 @@ RuntimeScriptValue Sc_DisplayThought(const RuntimeScriptValue *params, int32_t p
 // void (int ypos, int ttexcol, int backcol, char *title, char*texx, ...)
 RuntimeScriptValue Sc_DisplayTopBar(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(DisplayTopBar, 5);
-	DisplayTopBar(params[0].IValue, params[1].IValue, params[2].IValue, params[3].Ptr, scsf_buffer);
+	DisplayTopBar(params[0].IValue, params[1].IValue, params[2].IValue, params[3].CStr, scsf_buffer);
 	return RuntimeScriptValue((int32_t)0);
 }
 
@@ -1798,7 +1798,7 @@ RuntimeScriptValue Sc_sc_strcpy(const RuntimeScriptValue *params, int32_t param_
 // void (char*destt, const char*texx, ...);
 RuntimeScriptValue Sc_sc_sprintf(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(_sc_sprintf, 2);
-	_sc_strcpy(params[0].Ptr, scsf_buffer);
+	_sc_strcpy(params[0].CStr, scsf_buffer);
 	return params[0];
 }
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index b4bfb697eee..e00d52ce264 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -995,8 +995,8 @@ int ccInstance::Run(int32_t curpc) {
 			// TODO: test reg[MAR] type here;
 			// That might be dynamic object, but also a non-managed dynamic array, "allocated"
 			// on global or local memspace (buffer)
-			const char *arr_ptr = registers[SREG_MAR].GetPtrWithOffset();
-			const auto &hdr = CCDynamicArray::GetHeader(arr_ptr);
+			void *arr_ptr = registers[SREG_MAR].GetPtrWithOffset();
+			const auto &hdr = CCDynamicArray::GetHeader((const char *)arr_ptr);
 			if ((reg1.IValue < 0) ||
 				(static_cast<uint32_t>(reg1.IValue) >= hdr.TotalSize)) {
 				int elem_count = hdr.ElemCount & (~ARRAY_MANAGED_TYPE_FLAG);
@@ -1033,11 +1033,11 @@ int ccInstance::Run(int32_t curpc) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
-				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
+				address = reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
-				address = reg1.Ptr;
+				address = (char *)reg1.Ptr;
 				break;
 			case kScValPluginArg:
 				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
@@ -1071,11 +1071,11 @@ int ccInstance::Run(int32_t curpc) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
-				address = (char *)reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
+				address = (char *)reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
-				address = reg1.Ptr;
+				address = (char *)reg1.Ptr;
 				break;
 			case kScValPluginArg:
 				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
@@ -1166,7 +1166,7 @@ int ccInstance::Run(int32_t curpc) {
 			int32_t instId = codeOp.Instruction.InstanceId;
 			// determine the offset into the code of the instance we want
 			runningInst = _G(loadedInstances)[instId];
-			intptr_t callAddr = reg1.Ptr - (char *)&runningInst->code[0];
+			intptr_t callAddr = reg1.PtrU8 - reinterpret_cast<uint8_t *>(&runningInst->code[0]);
 			if (callAddr % sizeof(intptr_t) != 0) {
 				cc_error("call address not aligned");
 				return -1;
@@ -1296,8 +1296,7 @@ int ccInstance::Run(int32_t curpc) {
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_CALLOBJ argument is not a dynamic object");
 				registers[SREG_OP].SetDynamicObject(
-					(char *)reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue),
-					reg1.ArrMgr->GetObjectManager());
+					(char *)reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue), reg1.ArrMgr->GetObjectManager());
 				break;
 			default:
 				cc_error("internal error: SCMD_CALLOBJ argument is not an object of built-in or user-defined type");
@@ -1436,7 +1435,7 @@ int ccInstance::Run(int32_t curpc) {
 				cc_error("No string class implementation set, but opcode was used");
 				return -1;
 			} else {
-				const char *ptr = (const char *)reg1.GetDirectPtr();
+				const char *ptr = reinterpret_cast<const char *>(reg1.GetDirectPtr());
 				reg1.SetDynamicObject(
 					_G(stringClassImpl)->CreateString(ptr).second,
 					&_GP(myScriptStringImpl));
@@ -1450,8 +1449,8 @@ int ccInstance::Run(int32_t curpc) {
 				cc_error("!Null pointer referenced");
 				return -1;
 			} else {
-				const char *ptr1 = (const char *)reg1.GetDirectPtr();
-				const char *ptr2 = (const char *)reg2.GetDirectPtr();
+				const char *ptr1 = reinterpret_cast<const char *>(reg1.GetDirectPtr());
+				const char *ptr2 = reinterpret_cast<const char *>(reg2.GetDirectPtr());
 				reg1.SetInt32AsBool(strcmp(ptr1, ptr2) == 0);
 			}
 			break;
@@ -1463,8 +1462,8 @@ int ccInstance::Run(int32_t curpc) {
 				cc_error("!Null pointer referenced");
 				return -1;
 			} else {
-				const char *ptr1 = (const char *)reg1.GetDirectPtr();
-				const char *ptr2 = (const char *)reg2.GetDirectPtr();
+				const char *ptr1 = reinterpret_cast<const char *>(reg1.GetDirectPtr());
+				const char *ptr2 = reinterpret_cast<const char *>(reg2.GetDirectPtr());
 				reg1.SetInt32AsBool(strcmp(ptr1, ptr2) != 0);
 			}
 			break;
@@ -1687,7 +1686,7 @@ bool ccInstance::_Create(PScript scri, ccInstance *joined) {
 		if (etype == EXPORT_FUNCTION) {
 			// NOTE: unfortunately, there seems to be no way to know if
 			// that's an extender function that expects object pointer
-			exports[i].SetCodePtr((char *)((intptr_t)eaddr * sizeof(intptr_t) + (char *)(&code[0])));
+			exports[i].SetCodePtr(((intptr_t)eaddr * sizeof(intptr_t) + reinterpret_cast<uint8_t *>(&code[0])));
 		} else if (etype == EXPORT_DATA) {
 			ScriptVariable *gl_var = FindGlobalVar(eaddr);
 			if (gl_var) {
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index 5c2b236ca41..b8f0c0a3871 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -42,14 +42,14 @@ uint8_t RuntimeScriptValue::ReadByte() const {
 	case kScValStackPtr:
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			return *(uint8_t *)(RValue->GetPtrWithOffset() + this->IValue);
+			return *(uint8_t *)(GetRValuePtrWithOffset());
 		} else {
 			return static_cast<uint8_t>(RValue->IValue);
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt8(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt8((const char *)this->Ptr, this->IValue);
 	default:
 		return *((uint8_t *)this->GetPtrWithOffset());
 	}
@@ -59,20 +59,20 @@ int16_t RuntimeScriptValue::ReadInt16() const {
 	switch (this->Type) {
 	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
-			return *(int16_t *)(RValue->GetPtrWithOffset() + this->IValue);
+			return *(int16_t *)(GetRValuePtrWithOffset());
 		} else {
 			return static_cast<int16_t>(RValue->IValue);
 		}
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			return Memory::ReadInt16LE(RValue->GetPtrWithOffset() + this->IValue);
+			return Memory::ReadInt16LE(GetRValuePtrWithOffset());
 		} else {
 			return static_cast<int16_t>(RValue->IValue);
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt16(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt16((const char *)this->Ptr, this->IValue);
 
 	default:
 		return *((int16_t *)this->GetPtrWithOffset());
@@ -83,20 +83,20 @@ int32_t RuntimeScriptValue::ReadInt32() const {
 	switch (this->Type) {
 	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
-			return *(int32_t *)(RValue->GetPtrWithOffset() + this->IValue);
+			return *(int32_t *)(GetRValuePtrWithOffset());
 		} else {
 			return static_cast<int32_t>(RValue->IValue);
 		}
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			return Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue);
+			return Memory::ReadInt32LE(GetRValuePtrWithOffset());
 		} else {
 			return static_cast<uint32_t>(RValue->IValue);
 		}
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt32(this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt32((const char *)this->Ptr, this->IValue);
 	default:
 		return *((int32_t *)this->GetPtrWithOffset());
 	}
@@ -107,7 +107,7 @@ void RuntimeScriptValue::WriteByte(uint8_t val) {
 	case kScValStackPtr:
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			*(uint8_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
+			*(uint8_t *)(GetRValuePtrWithOffset()) = val;
 		} else {
 			RValue->SetUInt8(val); // set RValue as int
 		}
@@ -115,7 +115,7 @@ void RuntimeScriptValue::WriteByte(uint8_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt8(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt8((const char *)this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((uint8_t *)this->GetPtrWithOffset()) = val;
@@ -127,14 +127,14 @@ void RuntimeScriptValue::WriteInt16(int16_t val) {
 	switch (this->Type) {
 	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
-			*(int16_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
+			*(int16_t *)(GetRValuePtrWithOffset()) = val;
 		} else {
 			RValue->SetInt16(val); // set RValue as int
 		}
 		break;
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			Memory::WriteInt16LE(RValue->GetPtrWithOffset() + this->IValue, val);
+			Memory::WriteInt16LE(GetRValuePtrWithOffset(), val);
 		} else {
 			RValue->SetInt16(val); // set RValue as int
 		}
@@ -142,7 +142,7 @@ void RuntimeScriptValue::WriteInt16(int16_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt16(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt16((const char *)this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int16_t *)this->GetPtrWithOffset()) = val;
@@ -154,14 +154,14 @@ void RuntimeScriptValue::WriteInt32(int32_t val) {
 	switch (this->Type) {
 	case kScValStackPtr:
 		if (RValue->Type == kScValData) {
-			*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue) = val;
+			*(int32_t *)(GetRValuePtrWithOffset()) = val;
 		} else {
 			RValue->SetInt32(val); // set RValue as int
 		}
 		break;
 	case kScValGlobalVar:
 		if (RValue->Type == kScValData) {
-			Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, val);
+			Memory::WriteInt32LE(GetRValuePtrWithOffset(), val);
 		} else {
 			RValue->SetInt32(val); // set RValue as int
 		}
@@ -169,7 +169,7 @@ void RuntimeScriptValue::WriteInt32(int32_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt32(this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt32((const char *)this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int32_t *)this->GetPtrWithOffset()) = val;
@@ -186,9 +186,9 @@ RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
 
 	if (Ptr) {
 		if (Type == kScValDynamicObject || Type == kScValStaticObject)
-			Ptr = const_cast<char *>(ObjMgr->GetFieldPtr(Ptr, IValue));
+			Ptr = const_cast<char *>(ObjMgr->GetFieldPtr((const char *)Ptr, IValue));
 		else
-			Ptr += IValue;
+			Ptr = PtrU8 + IValue;
 		IValue = 0;
 	}
 	return *this;
@@ -208,9 +208,9 @@ intptr_t RuntimeScriptValue::GetDirectPtr() const {
 		ival += temp_val->IValue;
 	}
 	if (temp_val->Type == kScValDynamicObject || temp_val->Type == kScValStaticObject)
-		return (intptr_t)temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
+		return (intptr_t)temp_val->ObjMgr->GetFieldPtr((const char *)temp_val->Ptr, ival);
 	else
-		return (intptr_t)(temp_val->Ptr + ival);
+		return (intptr_t)(temp_val->PtrU8 + ival);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 42d7bb5964c..1e891cf1351 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -90,7 +90,9 @@ public:
 	// Pointer is used for storing... pointers - to objects, arrays,
 	// functions and stack entries (other RSV)
 	union {
-		char *Ptr;   // generic data pointer
+		void	*Ptr;	// generic data pointer
+		uint8_t *PtrU8;	// byte buffer pointer
+		char	*CStr;	// char buffer pointer
 		RuntimeScriptValue *RValue;// access ptr as a pointer to Runtime Value
 		ScriptAPIFunction *SPfn;  // access ptr as a pointer to Script API Static Function
 		ScriptAPIObjectFunction *ObjPfn; // access ptr as a pointer to Script API Object Function
@@ -114,6 +116,7 @@ public:
 	inline bool IsValid() const {
 		return Type != kScValUndefined;
 	}
+
 	inline bool IsNull() const {
 		return Ptr == nullptr && IValue == 0;
 	}
@@ -121,14 +124,20 @@ public:
 	inline bool GetAsBool() const {
 		return !IsNull();
 	}
-	inline char *GetPtrWithOffset() const {
-		return Ptr + IValue;
+
+	inline void *GetPtrWithOffset() const {
+		return PtrU8 + IValue;
+	}
+
+	inline void *GetRValuePtrWithOffset() const {
+		return static_cast<uint8_t *>(RValue->GetPtrWithOffset()) + this->IValue;
 	}
 
 	inline RuntimeScriptValue &Invalidate() {
 		*this = RuntimeScriptValue();
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetUInt8(uint8_t val) {
 		Type = kScValInteger;
 		methodName.clear();
@@ -138,6 +147,7 @@ public:
 		Size = 1;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetInt16(int16_t val) {
 		Type = kScValInteger;
 		methodName.clear();
@@ -147,6 +157,7 @@ public:
 		Size = 2;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetInt32(int32_t val) {
 		Type = kScValInteger;
 		methodName.clear();
@@ -156,6 +167,7 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetFloat(float val) {
 		Type = kScValFloat;
 		methodName.clear();
@@ -165,12 +177,15 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetInt32AsBool(bool val) {
 		return SetInt32(val ? 1 : 0);
 	}
+
 	inline RuntimeScriptValue &SetFloatAsBool(bool val) {
 		return SetFloat(val ? 1.0F : 0.0F);
 	}
+
 	inline RuntimeScriptValue &SetPluginArgument(int32_t val) {
 		Type = kScValPluginArg;
 		methodName.clear();
@@ -180,6 +195,7 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetStackPtr(RuntimeScriptValue *stack_entry) {
 		Type = kScValStackPtr;
 		methodName.clear();
@@ -189,7 +205,8 @@ public:
 		Size = 4;
 		return *this;
 	}
-	inline RuntimeScriptValue &SetData(char *data, int size) {
+
+	inline RuntimeScriptValue &SetData(void *data, int size) {
 		Type = kScValData;
 		methodName.clear();
 		IValue = 0;
@@ -198,6 +215,7 @@ public:
 		Size = size;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetGlobalVar(RuntimeScriptValue *glvar_value) {
 		Type = kScValGlobalVar;
 		methodName.clear();
@@ -207,6 +225,7 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	// TODO: size?
 	inline RuntimeScriptValue &SetStringLiteral(const char *str) {
 		Type = kScValStringLiteral;
@@ -217,50 +236,56 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetStaticObject(void *object, ICCDynamicObject *manager) {
 		Type = kScValStaticObject;
 		methodName.clear();
 		IValue = 0;
-		Ptr = (char *)object;
+		Ptr = object;
 		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetStaticArray(void *object, CCStaticArray *manager) {
 		Type = kScValStaticArray;
 		methodName.clear();
 		IValue = 0;
-		Ptr = (char *)object;
+		Ptr = object;
 		ArrMgr = manager;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetDynamicObject(void *object, ICCDynamicObject *manager) {
 		Type = kScValDynamicObject;
 		methodName.clear();
 		IValue = 0;
-		Ptr = (char *)object;
+		Ptr = object;
 		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetPluginObject(void *object, ICCDynamicObject *manager) {
 		Type = kScValPluginObject;
 		methodName.clear();
 		IValue = 0;
-		Ptr = (char *)object;
+		Ptr = object;
 		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetDynamicObject(ScriptValueType type, void *object, ICCDynamicObject *manager) {
 		Type = type;
 		IValue = 0;
-		Ptr = (char *)object;
+		Ptr = object;
 		ObjMgr = manager;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetStaticFunction(ScriptAPIFunction *pfn) {
 		Type = kScValStaticFunction;
 		methodName.clear();
@@ -270,15 +295,17 @@ public:
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetPluginMethod(Plugins::ScriptContainer *sc, const Common::String &method) {
 		Type = kScValPluginFunction;
 		methodName = method;
-		Ptr = (char *)sc;
+		Ptr = sc;
 		MgrPtr = nullptr;
 		IValue = 0;
 		Size = 4;
 		return *this;
 	}
+
 	inline RuntimeScriptValue &SetObjectFunction(ScriptAPIObjectFunction *pfn) {
 		Type = kScValObjectFunction;
 		methodName.clear();
@@ -288,7 +315,8 @@ public:
 		Size = 4;
 		return *this;
 	}
-	inline RuntimeScriptValue &SetCodePtr(char *ptr) {
+
+	inline RuntimeScriptValue &SetCodePtr(void *ptr) {
 		Type = kScValCodePtr;
 		methodName.clear();
 		IValue = 0;
@@ -324,7 +352,7 @@ public:
 			switch (RValue->Type) {
 			case kScValData:
 				// read from the stack memory buffer
-				return RuntimeScriptValue().SetInt32(*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue));
+				return RuntimeScriptValue().SetInt32(*(int32_t *)(GetRValuePtrWithOffset()));
 			default:
 				// return the stack entry itself
 				return *RValue;
@@ -335,7 +363,7 @@ public:
 			switch (RValue->Type) {
 			case kScValData:
 				// read from the global memory buffer
-				return RuntimeScriptValue().SetInt32(AGS::Shared::Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue));
+				return RuntimeScriptValue().SetInt32(AGS::Shared::Memory::ReadInt32LE(GetRValuePtrWithOffset()));
 			default:
 				// return the gvar entry itself
 				return *RValue;
@@ -344,7 +372,7 @@ public:
 		case kScValStaticObject:
 		case kScValStaticArray:
 		case kScValDynamicObject:
-			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32(this->Ptr, this->IValue));
+			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32((const char *)this->Ptr, this->IValue));
 		default:
 			return RuntimeScriptValue().SetInt32(*(int32_t *)this->GetPtrWithOffset());
 		}
@@ -361,7 +389,7 @@ public:
 			switch (RValue->Type) {
 			case kScValData:
 				// write into the stack memory buffer
-				*(int32_t *)(RValue->GetPtrWithOffset() + this->IValue) = rval.IValue;
+				*(int32_t *)(GetRValuePtrWithOffset()) = rval.IValue;
 				break;
 			default:
 				// write into the stack entry
@@ -379,7 +407,7 @@ public:
 			switch (RValue->Type) {
 			case kScValData:
 				// write into the global memory buffer
-				AGS::Shared::Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, rval.IValue);
+				AGS::Shared::Memory::WriteInt32LE(GetRValuePtrWithOffset(), rval.IValue);
 				break;
 			default:
 				// write into the gvar entry
@@ -391,7 +419,7 @@ public:
 		case kScValStaticObject:
 		case kScValStaticArray:
 		case kScValDynamicObject: {
-			this->ObjMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
+			this->ObjMgr->WriteInt32((const char *)this->Ptr, this->IValue, rval.IValue);
 			break;
 		}
 		default: {
diff --git a/engines/ags/engine/script/script_api.cpp b/engines/ags/engine/script/script_api.cpp
index 16e442fe9cc..fbbcf634134 100644
--- a/engines/ags/engine/script/script_api.cpp
+++ b/engines/ags/engine/script/script_api.cpp
@@ -65,7 +65,7 @@ inline const char *GetArgPtr(const RuntimeScriptValue *sc_args, va_list *varg_pt
 	if (varg_ptr)
 		return va_arg(*varg_ptr, const char *);
 	else
-		return sc_args[arg_idx].Ptr;
+		return reinterpret_cast<const char *>(sc_args[arg_idx].Ptr);
 }
 
 
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index b9ad5baed8f..ac2c3f537b9 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -83,12 +83,12 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 #define API_SCALL_SCRIPT_SPRINTF(FUNCTION, PARAM_COUNT) \
 	ASSERT_PARAM_COUNT(FUNCTION, PARAM_COUNT); \
 	char ScSfBuffer[STD_BUFFER_SIZE]; \
-	const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].Ptr), params + PARAM_COUNT, param_count - PARAM_COUNT)
+	const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].CStr), params + PARAM_COUNT, param_count - PARAM_COUNT)
 
 #define API_OBJCALL_SCRIPT_SPRINTF(METHOD, PARAM_COUNT) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, PARAM_COUNT); \
 	char ScSfBuffer[STD_BUFFER_SIZE]; \
-	const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].Ptr), params + PARAM_COUNT, param_count - PARAM_COUNT)
+	const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].CStr), params + PARAM_COUNT, param_count - PARAM_COUNT)
 
 //-----------------------------------------------------------------------------
 // Calls to ScriptSprintf without translation
@@ -96,7 +96,7 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 #define API_SCALL_SCRIPT_SPRINTF_PURE(FUNCTION, PARAM_COUNT) \
     ASSERT_PARAM_COUNT(FUNCTION, PARAM_COUNT); \
     char ScSfBuffer[STD_BUFFER_SIZE]; \
-    const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, params[PARAM_COUNT - 1].Ptr, params + PARAM_COUNT, param_count - PARAM_COUNT)
+    const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, params[PARAM_COUNT - 1].CStr, params + PARAM_COUNT, param_count - PARAM_COUNT)
 
 
 //-----------------------------------------------------------------------------
@@ -527,7 +527,7 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 
 #define API_OBJCALL_INT_PINT_POBJ(CLASS, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
-	return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, params[0].IValue, params[1].Ptr))
+	return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, params[0].IValue, params[1].CStr))
 
 #define API_OBJCALL_INT_PINT2(CLASS, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \


Commit: 54b8b1d873797e31d1a3584c7927aae50633e2f3
    https://github.com/scummvm/scummvm/commit/54b8b1d873797e31d1a3584c7927aae50633e2f3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: changed script object data pointers from char* to void*

This seems to be far more appropriate.
>From upstream a495538a5e0106a655e4e04cf547be8ec9284d7c

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_channel.h
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.h
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.h
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/cc_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui.h
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.h
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.h
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.h
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_object.h
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_region.h
    engines/ags/engine/ac/dynobj/cc_static_array.cpp
    engines/ags/engine/ac/dynobj/cc_static_array.h
    engines/ags/engine/ac/dynobj/dynobj_manager.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_camera.h
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_date_time.h
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_dict.h
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
    engines/ags/engine/ac/dynobj/script_file.cpp
    engines/ags/engine/ac/dynobj/script_file.h
    engines/ags/engine/ac/dynobj/script_game.cpp
    engines/ags/engine/ac/dynobj/script_game.h
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_overlay.h
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_set.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.h
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/dynobj/script_viewport.h
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/plugins/ags_controller/ags_controller.cpp
    engines/ags/plugins/ags_galaxy_steam/ags_galaxy_steam.cpp
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/ags_plugin.h
    engines/ags/plugins/ags_sock/ags_sock.cpp
    engines/ags/plugins/ags_sprite_video/ags_sprite_video.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index e7ba292ce78..a675e3c10bd 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -27,60 +27,59 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-int CCBasicObject::Dispose(const char * /*address*/, bool /*force*/) {
+int CCBasicObject::Dispose(void * /*address*/, bool /*force*/) {
 	return 0; // cannot be removed from memory
 }
 
-int CCBasicObject::Serialize(const char * /*address*/, char * /*buffer*/, int /*bufsize*/) {
+int CCBasicObject::Serialize(void * /*address*/, uint8_t * /*buffer*/, int /*bufsize*/) {
 	return 0; // does not save data
 }
 
-const char *CCBasicObject::GetFieldPtr(const char *address, intptr_t offset) {
-	return address + offset;
+void *CCBasicObject::GetFieldPtr(void *address, intptr_t offset) {
+	return static_cast<uint8_t *>(address) + offset;
 }
 
-void CCBasicObject::Read(const char *address, intptr_t offset, void *dest, int size) {
-	memcpy(dest, address + offset, size);
+void CCBasicObject::Read(void *address, intptr_t offset, uint8_t *dest, size_t size) {
+	memcpy(dest, static_cast<uint8_t *>(address) + offset, size);
 }
 
-uint8_t CCBasicObject::ReadInt8(const char *address, intptr_t offset) {
-	return *(const uint8_t *)(address + offset);
+uint8_t CCBasicObject::ReadInt8(void *address, intptr_t offset) {
+	return *(uint8_t *)(static_cast<uint8_t *>(address) + offset);
 }
 
-int16_t CCBasicObject::ReadInt16(const char *address, intptr_t offset) {
-	return *(const int16_t *)(address + offset);
+int16_t CCBasicObject::ReadInt16(void *address, intptr_t offset) {
+	return *(int16_t *)(static_cast<uint8_t *>(address) + offset);
 }
 
-int32_t CCBasicObject::ReadInt32(const char *address, intptr_t offset) {
-	return *(const int32_t *)(address + offset);
+int32_t CCBasicObject::ReadInt32(void *address, intptr_t offset) {
+	return *(int32_t *)(static_cast<uint8_t *>(address) + offset);
 }
 
-float CCBasicObject::ReadFloat(const char *address, intptr_t offset) {
-	return *(const float *)(address + offset);
+float CCBasicObject::ReadFloat(void *address, intptr_t offset) {
+	return *(float *)(static_cast<uint8_t *>(address) + offset);
 }
 
-void CCBasicObject::Write(const char *address, intptr_t offset, void *src, int size) {
-	memcpy((void *)(const_cast<char *>(address) + offset), src, size);
+void CCBasicObject::Write(void *address, intptr_t offset, const uint8_t *src, size_t size) {
+	memcpy(static_cast<uint8_t *>(address) + offset, src, size);
 }
 
-void CCBasicObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-	*(uint8_t *)(const_cast<char *>(address) + offset) = val;
+void CCBasicObject::WriteInt8(void *address, intptr_t offset, uint8_t val) {
+	*(uint8_t *)(static_cast<uint8_t *>(address) + offset) = val;
 }
 
-void CCBasicObject::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	*(int16_t *)(const_cast<char *>(address) + offset) = val;
+void CCBasicObject::WriteInt16(void *address, intptr_t offset, int16_t val) {
+	*(int16_t *)(static_cast<uint8_t *>(address) + offset) = val;
 }
 
-void CCBasicObject::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	*(int32_t *)(const_cast<char *>(address) + offset) = val;
+void CCBasicObject::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	*(int32_t *)(static_cast<uint8_t *>(address) + offset) = val;
 }
 
-void CCBasicObject::WriteFloat(const char *address, intptr_t offset, float val) {
-	*(float *)(const_cast<char *>(address) + offset) = val;
+void CCBasicObject::WriteFloat(void *address, intptr_t offset, float val) {
+	*(float *)(static_cast<uint8_t *>(address) + offset) = val;
 }
 
-
-int AGSCCDynamicObject::Serialize(const char *address, char *buffer, int bufsize) {
+int AGSCCDynamicObject::Serialize(void *address, uint8_t *buffer, int bufsize) {
 	// If the required space is larger than the provided buffer,
 	// then return negated required space, notifying the caller that a larger buffer is necessary
 	size_t req_size = CalcSerializeSize(address);
diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index 63f12685807..cf0358f1a91 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -53,30 +53,30 @@ namespace AGS { namespace Shared { class Stream; } }
 // * Serialization skipped, does not save or load anything;
 // * Provides default implementation for reading and writing data fields,
 //   treats the contents of an object as a raw byte buffer.
-struct CCBasicObject : ICCDynamicObject {
+struct CCBasicObject : public ICCDynamicObject {
 public:
 	virtual ~CCBasicObject() = default;
 
 	// Dispose the object
-	int Dispose(const char * /*address*/, bool /*force*/) override;
+	int Dispose(void * /*address*/, bool /*force*/) override;
 	// Serialize the object into BUFFER (which is BUFSIZE bytes)
 	// return number of bytes used
-	int Serialize(const char * /*address*/, char * /*buffer*/, int /*bufsize*/) override;
+	int Serialize(void * /*address*/, uint8_t * /*buffer*/, int /*bufsize*/) override;
 
 	//
 	// Legacy support for reading and writing object fields by their relative offset
 	//
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float ReadFloat(const char *address, intptr_t offset) override;
-	void Write(const char *address, intptr_t offset, void *src, int size) override;
-	void WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void WriteFloat(const char *address, intptr_t offset, float val) override;
+	void *GetFieldPtr(void *address, intptr_t offset) override;
+	void Read(void *address, intptr_t offset, uint8_t *dest, size_t size) override;
+	uint8_t ReadInt8(void *address, intptr_t offset) override;
+	int16_t ReadInt16(void *address, intptr_t offset) override;
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	float ReadFloat(void *address, intptr_t offset) override;
+	void Write(void *address, intptr_t offset, const uint8_t *src, size_t size) override;
+	void WriteInt8(void *address, intptr_t offset, uint8_t val) override;
+	void WriteInt16(void *address, intptr_t offset, int16_t val) override;
+	void WriteInt32(void *address, intptr_t offset, int32_t val) override;
+	void WriteFloat(void *address, intptr_t offset, float val) override;
 };
 
 
@@ -90,30 +90,28 @@ public:
 	virtual ~AGSCCDynamicObject() = default;
 
 	// TODO: pass savegame format version
-	int Serialize(const char *address, char *buffer, int bufsize) override;
+	int Serialize(void *address, uint8_t *buffer, int bufsize) override;
 	// Try unserializing the object from the given input stream
 	virtual void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) = 0;
 
 protected:
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	virtual size_t CalcSerializeSize(const char *address) = 0;
+	virtual size_t CalcSerializeSize(void *address) = 0;
 	// Write object data into the provided stream
-	virtual void Serialize(const char *address, AGS::Shared::Stream *out) = 0;
+	virtual void Serialize(void *address, AGS::Shared::Stream *out) = 0;
 };
 
 // CCStaticObject is a base class for managing static global objects in script.
 // The static objects can never be disposed, and do not support serialization
 // through ICCDynamicObject interface.
-struct AGSCCStaticObject : CCBasicObject {
+struct AGSCCStaticObject : public CCBasicObject {
 public:
 	virtual ~AGSCCStaticObject() = default;
 
 	const char *GetType() override { return "StaticObject"; }
 };
 
-// extern AGSCCStaticObject GlobalStaticManager;
-
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index 8918a4bd10c..d76c85f8451 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -34,11 +34,11 @@ const char *CCAudioChannel::GetType() {
 	return "AudioChannel";
 }
 
-size_t CCAudioChannel::CalcSerializeSize(const char * /*address*/) {
+size_t CCAudioChannel::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void CCAudioChannel::Serialize(const char *address, Stream *out) {
+void CCAudioChannel::Serialize(void *address, Stream *out) {
 	const ScriptAudioChannel *ach = (const ScriptAudioChannel *)address;
 	out->WriteInt32(ach->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.h b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
index 1aae5ee3abe..f7688b63f20 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
@@ -31,9 +31,9 @@ struct CCAudioChannel final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index 3098b9230d2..a7868d7df90 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -33,11 +33,11 @@ const char *CCAudioClip::GetType() {
 	return "AudioClip";
 }
 
-size_t CCAudioClip::CalcSerializeSize(const char * /*address*/) {
+size_t CCAudioClip::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void CCAudioClip::Serialize(const char *address, Stream *out) {
+void CCAudioClip::Serialize(void *address, Stream *out) {
 	const ScriptAudioClip *ach = (const ScriptAudioClip *)address;
 	out->WriteInt32(ach->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.h b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
index 4afadee80a5..fa05437dea2 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
@@ -31,9 +31,9 @@ struct CCAudioClip final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 3f7864e7870..7b18cd370dd 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -39,13 +39,13 @@ const char *CCCharacter::GetType() {
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-size_t CCCharacter::CalcSerializeSize(const char * /*address*/) {
+size_t CCCharacter::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCCharacter::Serialize(const char *address, Stream *out) {
+void CCCharacter::Serialize(void *address, Stream *out) {
 	const CharacterInfo *chaa = (const CharacterInfo *)address;
 	out->WriteInt32(chaa->index_id);
 }
@@ -55,8 +55,9 @@ void CCCharacter::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, &_GP(game).chars[num], this);
 }
 
-void CCCharacter::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	*(int16_t *)(const_cast<char *>(address) + offset) = val;
+void CCCharacter::WriteInt16(void *address, intptr_t offset, int16_t val) {
+	uint8_t *data = static_cast<uint8_t *>(address);
+	*(int16_t *)(data + offset) = val;
 
 	// Detect when a game directly modifies the inventory, which causes the displayed
 	// and actual inventory to diverge since 2.70. Force an update of the displayed
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index d25e3052f98..e9917218e5b 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -33,12 +33,12 @@ struct CCCharacter final : AGSCCDynamicObject {
 
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
-	void WriteInt16(const char *address, intptr_t offset, int16_t val) override;
+	void WriteInt16(void *address, intptr_t offset, int16_t val) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index 4bdd8281a4a..bdc9939118b 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -36,13 +36,13 @@ const char *CCDialog::GetType() {
 	return "Dialog";
 }
 
-size_t CCDialog::CalcSerializeSize(const char * /*address*/) {
+size_t CCDialog::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCDialog::Serialize(const char *address, Stream *out) {
+void CCDialog::Serialize(void *address, Stream *out) {
 	const ScriptDialog *shh = (const ScriptDialog *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h
index eaaf00e19e5..3d141cc6bf1 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.h
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.h
@@ -34,9 +34,9 @@ struct CCDialog final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 3d939ca0382..2b214a319c7 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -35,7 +35,7 @@ const char *CCDynamicArray::GetType() {
 	return TypeName;
 }
 
-int CCDynamicArray::Dispose(const char *address, bool force) {
+int CCDynamicArray::Dispose(void *address, bool force) {
 	// If it's an array of managed objects, release their ref counts;
 	// except if this array is forcefully removed from the managed pool,
 	// in which case just ignore these.
@@ -53,16 +53,16 @@ int CCDynamicArray::Dispose(const char *address, bool force) {
 		}
 	}
 
-	delete[] (address - MemHeaderSz);
+	delete[] (static_cast<uint8_t *>(address) - MemHeaderSz);
 	return 1;
 }
 
-size_t CCDynamicArray::CalcSerializeSize(const char *address) {
+size_t CCDynamicArray::CalcSerializeSize(void *address) {
 	const Header &hdr = GetHeader(address);
 	return hdr.TotalSize + FileHeaderSz;
 }
 
-void CCDynamicArray::Serialize(const char *address, AGS::Shared::Stream *out) {
+void CCDynamicArray::Serialize(void *address, AGS::Shared::Stream *out) {
 	const Header &hdr = GetHeader(address);
 	out->WriteInt32(hdr.ElemCount);
 	out->WriteInt32(hdr.TotalSize);
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 3e12815c7a6..6897ab3267c 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -41,13 +41,13 @@ public:
 		uint32_t TotalSize = 0u;
 	};
 
-	inline static const Header &GetHeader(const char *address) {
-		return reinterpret_cast<const Header &>(*(address - MemHeaderSz));
+	inline static const Header &GetHeader(void *address) {
+		return reinterpret_cast<const Header &>(*(static_cast<uint8_t *>(address) - MemHeaderSz));
 	}
 
 	// return the type name of the object
 	const char *GetType() override;
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
 	// Create managed array object and return a pointer to the beginning of a buffer
 	DynObjectRef Create(int numElements, int elementSize, bool isManagedType);
@@ -60,9 +60,9 @@ private:
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 // Helper functions for setting up dynamic arrays.
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
index 57d91933d55..3467ed830f9 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
@@ -45,9 +45,9 @@ class Stream;
 typedef std::pair<int32_t, void *> DynObjectRef;
 
 
-// OBJECT-BASED SCRIPTING RUNTIME FUNCTIONS
-// interface
 struct ICCDynamicObject {
+	// WARNING: The first section of this interface is also a part of the AGS plugin API!
+
 	// when a ref count reaches 0, this is called with the address
 	// of the object. Return 1 to remove the object from memory, 0 to
 	// leave it
@@ -55,13 +55,13 @@ struct ICCDynamicObject {
 	// to other managed objects or game resources (instead of disposing these too).
 	// TODO: it might be better to rewrite the managed pool and remove this flag at all,
 	// because it makes the use of this interface prone to mistakes.
-	virtual int Dispose(const char *address, bool force = false) = 0;
+	virtual int Dispose(void *address, bool force = false) = 0;
 	// return the type name of the object
 	virtual const char *GetType() = 0;
 	// serialize the object into BUFFER (which is BUFSIZE bytes)
 	// return number of bytes used
 	// TODO: pass savegame format version
-	virtual int Serialize(const char *address, char *buffer, int bufsize) = 0;
+	virtual int Serialize(void *address, uint8_t *buffer, int bufsize) = 0;
 
 	// Legacy support for reading and writing object values by their relative offset.
 	// WARNING: following were never a part of plugin API, therefore these methods
@@ -76,17 +76,17 @@ struct ICCDynamicObject {
 	// necessary, because byte-code does not contain any distinct operation for this case.
 	// The worst thing here is that with the current byte-code structure we can never tell whether
 	// offset 0 means getting pointer to whole object or a pointer to its first field.
-	virtual const char *GetFieldPtr(const char *address, intptr_t offset) = 0;
-	virtual void    Read(const char *address, intptr_t offset, void *dest, int size) = 0;
-	virtual uint8_t ReadInt8(const char *address, intptr_t offset) = 0;
-	virtual int16_t ReadInt16(const char *address, intptr_t offset) = 0;
-	virtual int32_t ReadInt32(const char *address, intptr_t offset) = 0;
-	virtual float   ReadFloat(const char *address, intptr_t offset) = 0;
-	virtual void    Write(const char *address, intptr_t offset, void *src, int size) = 0;
-	virtual void    WriteInt8(const char *address, intptr_t offset, uint8_t val) = 0;
-	virtual void    WriteInt16(const char *address, intptr_t offset, int16_t val) = 0;
-	virtual void    WriteInt32(const char *address, intptr_t offset, int32_t val) = 0;
-	virtual void    WriteFloat(const char *address, intptr_t offset, float val) = 0;
+	virtual void	*GetFieldPtr(void *address, intptr_t offset) = 0;
+	virtual void 	Read(void *address, intptr_t offset, uint8_t *dest, size_t size) = 0;
+	virtual uint8_t ReadInt8(void *address, intptr_t offset) = 0;
+	virtual int16_t ReadInt16(void *address, intptr_t offset) = 0;
+	virtual int32_t ReadInt32(void *address, intptr_t offset) = 0;
+	virtual float	ReadFloat(void *address, intptr_t offset) = 0;
+	virtual void 	Write(void *address, intptr_t offset, const uint8_t *src, size_t size) = 0;
+	virtual void 	WriteInt8(void *address, intptr_t offset, uint8_t val) = 0;
+	virtual void 	WriteInt16(void *address, intptr_t offset, int16_t val) = 0;
+	virtual void 	WriteInt32(void *address, intptr_t offset, int32_t val) = 0;
+	virtual void 	WriteFloat(void *address, intptr_t offset, float val) = 0;
 
 protected:
 	ICCDynamicObject() {}
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index d7035573d28..6fcc5a0f508 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -34,13 +34,13 @@ const char *CCGUI::GetType() {
 	return "GUI";
 }
 
-size_t CCGUI::CalcSerializeSize(const char * /*address*/) {
+size_t CCGUI::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCGUI::Serialize(const char *address, Stream *out) {
+void CCGUI::Serialize(void *address, Stream *out) {
 	const ScriptGUI *shh = (const ScriptGUI *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h
index 24bceda2133..6bee137b80b 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui.h
@@ -34,9 +34,9 @@ struct CCGUI final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index c0d1f1c90bf..da5fad874e3 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -36,13 +36,13 @@ const char *CCGUIObject::GetType() {
 	return "GUIObject";
 }
 
-size_t CCGUIObject::CalcSerializeSize(const char * /*address*/) {
+size_t CCGUIObject::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t) * 2;
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCGUIObject::Serialize(const char *address, Stream *out) {
+void CCGUIObject::Serialize(void *address, Stream *out) {
 	const GUIObject *guio = (const GUIObject *)address;
 	out->WriteInt32(guio->ParentId);
 	out->WriteInt32(guio->Id);
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.h b/engines/ags/engine/ac/dynobj/cc_gui_object.h
index daec1cec26d..006c4f2545a 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.h
@@ -34,9 +34,9 @@ struct CCGUIObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index 75279b3747b..78bad86e2af 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -36,13 +36,13 @@ const char *CCHotspot::GetType() {
 	return "Hotspot";
 }
 
-size_t CCHotspot::CalcSerializeSize(const char * /*address*/) {
+size_t CCHotspot::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCHotspot::Serialize(const char *address, Stream *out) {
+void CCHotspot::Serialize(void *address, Stream *out) {
 	const ScriptHotspot *shh = (const ScriptHotspot *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h
index a8d3fccd452..7acc3290746 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.h
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h
@@ -34,9 +34,9 @@ struct CCHotspot final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index 41e80685b65..c1cd51e0d42 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -35,13 +35,13 @@ const char *CCInventory::GetType() {
 	return "Inventory";
 }
 
-size_t CCInventory::CalcSerializeSize(const char * /*address*/) {
+size_t CCInventory::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCInventory::Serialize(const char *address, Stream *out) {
+void CCInventory::Serialize(void *address, Stream *out) {
 	const ScriptInvItem *shh = (const ScriptInvItem *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h
index 961e1a76332..195361d3d1e 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.h
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.h
@@ -34,9 +34,9 @@ struct CCInventory final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index 0fc40b7e090..e8ccff254ad 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -36,13 +36,13 @@ const char *CCObject::GetType() {
 	return "Object";
 }
 
-size_t CCObject::CalcSerializeSize(const char * /*address*/) {
+size_t CCObject::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCObject::Serialize(const char *address, Stream *out) {
+void CCObject::Serialize(void *address, Stream *out) {
 	const ScriptObject *shh = (const ScriptObject *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h
index 7d3335ec022..8f8c8a1203d 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_object.h
@@ -34,9 +34,9 @@ struct CCObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index 6850ba26912..9e3f99d7997 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -36,13 +36,13 @@ const char *CCRegion::GetType() {
 	return "Region";
 }
 
-size_t CCRegion::CalcSerializeSize(const char * /*address*/) {
+size_t CCRegion::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCRegion::Serialize(const char *address, Stream *out) {
+void CCRegion::Serialize(void *address, Stream *out) {
 	const ScriptRegion *shh = (const ScriptRegion *)address;
 	out->WriteInt32(shh->id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h
index 6d9f64c7e18..da6b2999211 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.h
+++ b/engines/ags/engine/ac/dynobj/cc_region.h
@@ -34,9 +34,9 @@ struct CCRegion final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.cpp b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
index c09a60d7ca2..70d926f9f89 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
@@ -31,61 +31,57 @@ void CCStaticArray::Create(ICCDynamicObject *mgr, size_t elem_script_size, size_
 	_elemCount = elem_count;
 }
 
-const char *CCStaticArray::GetElementPtr(const char *address, intptr_t legacy_offset) {
-	return address + (legacy_offset / _elemScriptSize) * _elemMemSize;
-}
-
-const char *CCStaticArray::GetFieldPtr(const char *address, intptr_t offset) {
+void *CCStaticArray::GetFieldPtr(void *address, intptr_t offset) {
 	return GetElementPtr(address, offset);
 }
 
-void CCStaticArray::Read(const char *address, intptr_t offset, void *dest, int size) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::Read(void *address, intptr_t offset, uint8_t *dest, size_t size) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->Read(el_ptr, offset % _elemScriptSize, dest, size);
 }
 
-uint8_t CCStaticArray::ReadInt8(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
+uint8_t CCStaticArray::ReadInt8(void *address, intptr_t offset) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->ReadInt8(el_ptr, offset % _elemScriptSize);
 }
 
-int16_t CCStaticArray::ReadInt16(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
+int16_t CCStaticArray::ReadInt16(void *address, intptr_t offset) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->ReadInt16(el_ptr, offset % _elemScriptSize);
 }
 
-int32_t CCStaticArray::ReadInt32(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
+int32_t CCStaticArray::ReadInt32(void *address, intptr_t offset) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->ReadInt32(el_ptr, offset % _elemScriptSize);
 }
 
-float CCStaticArray::ReadFloat(const char *address, intptr_t offset) {
-	const char *el_ptr = GetElementPtr(address, offset);
+float CCStaticArray::ReadFloat(void *address, intptr_t offset) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->ReadFloat(el_ptr, offset % _elemScriptSize);
 }
 
-void CCStaticArray::Write(const char *address, intptr_t offset, void *src, int size) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::Write(void *address, intptr_t offset, const uint8_t *src, size_t size) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->Write(el_ptr, offset % _elemScriptSize, src, size);
 }
 
-void CCStaticArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::WriteInt8(void *address, intptr_t offset, uint8_t val) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->WriteInt8(el_ptr, offset % _elemScriptSize, val);
 }
 
-void CCStaticArray::WriteInt16(const char *address, intptr_t offset, int16_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::WriteInt16(void *address, intptr_t offset, int16_t val) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->WriteInt16(el_ptr, offset % _elemScriptSize, val);
 }
 
-void CCStaticArray::WriteInt32(const char *address, intptr_t offset, int32_t val) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->WriteInt32(el_ptr, offset % _elemScriptSize, val);
 }
 
-void CCStaticArray::WriteFloat(const char *address, intptr_t offset, float val) {
-	const char *el_ptr = GetElementPtr(address, offset);
+void CCStaticArray::WriteFloat(void *address, intptr_t offset, float val) {
+	void *el_ptr = GetElementPtr(address, offset);
 	return _mgr->WriteFloat(el_ptr, offset % _elemScriptSize, val);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.h b/engines/ags/engine/ac/dynobj/cc_static_array.h
index aa5772f4129..985c8dd5fbc 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.h
@@ -52,19 +52,21 @@ public:
 	}
 
 	// Legacy support for reading and writing object values by their relative offset
-	virtual const char *GetElementPtr(const char *address, intptr_t legacy_offset);
+	inline void *GetElementPtr(void *address, intptr_t legacy_offset) {
+		return static_cast<uint8_t *>(address) + (legacy_offset / _elemScriptSize) * _elemMemSize;
+	}
 
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
+	void	*GetFieldPtr(void *address, intptr_t offset) override;
+	void 	Read(void *address, intptr_t offset, uint8_t *dest, size_t size) override;
+	uint8_t ReadInt8(void *address, intptr_t offset) override;
+	int16_t ReadInt16(void *address, intptr_t offset) override;
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	float 	ReadFloat(void *address, intptr_t offset) override;
+	void 	Write(void *address, intptr_t offset, const uint8_t *src, size_t size) override;
+	void 	WriteInt8(void *address, intptr_t offset, uint8_t val) override;
+	void 	WriteInt16(void *address, intptr_t offset, int16_t val) override;
+	void 	WriteInt32(void *address, intptr_t offset, int32_t val) override;
+	void 	WriteFloat(void *address, intptr_t offset, float val) override;
 
 private:
 	ICCDynamicObject *_mgr = nullptr;
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index 6b91d72fd9d..80577a46ef9 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -54,8 +54,8 @@ void ccSetStringClassImpl(ICCStringClass *theClass) {
 
 // register a memory handle for the object and allow script
 // pointers to point to it
-int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
-	int32_t handl = _GP(pool).AddObject((const char *)object, callback, obj_type);
+int32_t ccRegisterManagedObject(void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+	int32_t handl = _GP(pool).AddObject(object, callback, obj_type);
 
 	ManagedObjectLog("Register managed object type '%s' handle=%d addr=%08X",
 	                 ((callback == NULL) ? "(unknown)" : callback->GetType()), handl, object);
@@ -64,13 +64,13 @@ int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback,
 }
 
 // register a de-serialized object
-int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
-	return _GP(pool).AddUnserializedObject((const char *)object, callback, obj_type, index);
+int32_t ccRegisterUnserializedObject(int index, void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+	return _GP(pool).AddUnserializedObject(object, callback, obj_type, index);
 }
 
 // unregister a particular object
-int ccUnRegisterManagedObject(const void *object) {
-	return _GP(pool).RemoveObject((const char *)object);
+int ccUnRegisterManagedObject(void *object) {
+	return _GP(pool).RemoveObject(object);
 }
 
 // remove all registered objects
@@ -94,12 +94,12 @@ void ccAttemptDisposeObject(int32_t handle) {
 }
 
 // translate between object handles and memory addresses
-int32_t ccGetObjectHandleFromAddress(const void *address) {
+int32_t ccGetObjectHandleFromAddress(void *address) {
 	// set to null
 	if (address == nullptr)
 		return 0;
 
-	int32_t handl = _GP(pool).AddressToHandle((const char *)address);
+	int32_t handl = _GP(pool).AddressToHandle(address);
 
 	ManagedObjectLog("Line %d WritePtr: %08X to %d", _G(currentline), address, handl);
 
@@ -110,11 +110,11 @@ int32_t ccGetObjectHandleFromAddress(const void *address) {
 	return handl;
 }
 
-const char *ccGetObjectAddressFromHandle(int32_t handle) {
+void *ccGetObjectAddressFromHandle(int32_t handle) {
 	if (handle == 0) {
 		return nullptr;
 	}
-	const char *addr = _GP(pool).HandleToAddress(handle);
+	void *addr = _GP(pool).HandleToAddress(handle);
 
 	ManagedObjectLog("Line %d ReadPtr: %d to %08X", _G(currentline), handle, addr);
 
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index 0d10b70cf2a..576c175dbc3 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -49,11 +49,11 @@ using namespace AGS; // FIXME later
 extern void  ccSetStringClassImpl(ICCStringClass *theClass);
 // register a memory handle for the object and allow script
 // pointers to point to it
-extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterManagedObject(void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
 // register a de-serialized object
-extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterUnserializedObject(int index, void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
 // unregister a particular object
-extern int   ccUnRegisterManagedObject(const void *object);
+extern int   ccUnRegisterManagedObject(void *object);
 // remove all registered objects
 extern void  ccUnregisterAllObjects();
 // serialize all objects to disk
@@ -63,10 +63,8 @@ extern int   ccUnserializeAllObjects(Shared::Stream *in, ICCObjectReader *callba
 // dispose the object if RefCount==0
 extern void  ccAttemptDisposeObject(int32_t handle);
 // translate between object handles and memory addresses
-extern int32_t ccGetObjectHandleFromAddress(const void *address);
-// TODO: not sure if it makes any sense whatsoever to use "const char*"
-// in these functions, might as well change to char* or just void*.
-extern const char *ccGetObjectAddressFromHandle(int32_t handle);
+extern int32_t ccGetObjectHandleFromAddress(void *address);
+extern void *ccGetObjectAddressFromHandle(int32_t handle);
 extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, ICCDynamicObject *&manager);
 
 extern int ccAddObjectReference(int32_t handle);
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 93c3fda41e2..7463a4d59d6 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -95,7 +95,7 @@ int32_t ManagedObjectPool::SubRef(int32_t handle) {
 	return newRefCount;
 }
 
-int32_t ManagedObjectPool::AddressToHandle(const char *addr) {
+int32_t ManagedObjectPool::AddressToHandle(void *addr) {
 	if (addr == nullptr) {
 		return 0;
 	}
@@ -107,7 +107,7 @@ int32_t ManagedObjectPool::AddressToHandle(const char *addr) {
 }
 
 // this function is called often (whenever a pointer is used)
-const char *ManagedObjectPool::HandleToAddress(int32_t handle) {
+void *ManagedObjectPool::HandleToAddress(int32_t handle) {
 	if (handle < 1 || (size_t)handle >= objects.size()) {
 		return nullptr;
 	}
@@ -126,12 +126,12 @@ ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, voi
 		return kScValUndefined;
 	}
 	auto &o = objects[handle];
-	object = const_cast<char *>(o.addr);  // WARNING: This strips the const from the char* pointer.
+	object = (void *)(o.addr); // WARNING: This strips the const from the char* pointer.
 	manager = o.callback;
 	return o.obj_type;
 }
 
-int ManagedObjectPool::RemoveObject(const char *address) {
+int ManagedObjectPool::RemoveObject(void *address) {
 	if (address == nullptr) {
 		return 0;
 	}
@@ -165,7 +165,7 @@ void ManagedObjectPool::RunGarbageCollection() {
 	ManagedObjectLog("Ran garbage collection");
 }
 
-int ManagedObjectPool::Add(int handle, const char *address, ICCDynamicObject *callback, ScriptValueType obj_type)
+int ManagedObjectPool::Add(int handle, void *address, ICCDynamicObject *callback, ScriptValueType obj_type)
 {
     auto &o = objects[handle];
     assert(!o.isUsed());
@@ -177,7 +177,7 @@ int ManagedObjectPool::Add(int handle, const char *address, ICCDynamicObject *ca
     return handle;
 }
 
-int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type) {
+int ManagedObjectPool::AddObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type) {
 	int32_t handle;
 
 	if (!available_ids.empty()) {
@@ -194,7 +194,7 @@ int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback
 	return Add(handle, address, callback, obj_type);
 }
 
-int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle) {
+int ManagedObjectPool::AddUnserializedObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle) {
 	if (handle < 0) {
 		cc_error("Attempt to assign invalid handle: %d", handle);
 		return 0;
@@ -211,7 +211,7 @@ void ManagedObjectPool::WriteToDisk(Stream *out) {
 	// use this opportunity to clean up any non-referenced pointers
 	RunGarbageCollection();
 
-	std::vector<char> serializeBuffer;
+	std::vector<uint8_t> serializeBuffer;
 	serializeBuffer.resize(SERIALIZE_BUFFER_SIZE);
 
 	out->WriteInt32(OBJECT_CACHE_MAGIC_NUMBER);
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index 23e5a0e8661..fe3b2286f1a 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -41,7 +41,7 @@ class Stream;
 using namespace AGS; // FIXME later
 
 struct Pointer_Hash {
-	uint operator()(const char *v) const {
+	uint operator()(void *v) const {
 		return static_cast<uint>(reinterpret_cast<uintptr>(v));
 	}
 };
@@ -53,9 +53,7 @@ private:
 	struct ManagedObject {
 		ScriptValueType obj_type;
 		int32_t handle;
-		// TODO: this makes no sense having this as "const char*",
-		// void* will be proper (and in all related functions)
-		const char *addr;
+		void *addr;
 		ICCDynamicObject *callback;
 		int refCount;
 
@@ -64,10 +62,9 @@ private:
 		}
 
 		ManagedObject() : obj_type(kScValUndefined), handle(0), addr(nullptr),
-			callback(nullptr), refCount(0) {
-		}
+			callback(nullptr), refCount(0) {}
 		ManagedObject(ScriptValueType theType, int32_t theHandle,
-		              const char *theAddr, ICCDynamicObject *theCallback)
+		              void *theAddr, ICCDynamicObject *theCallback)
 			: obj_type(theType), handle(theHandle), addr(theAddr),
 			  callback(theCallback), refCount(0) {
 		}
@@ -78,9 +75,9 @@ private:
 	int32_t nextHandle{}; // TODO: manage nextHandle's going over INT32_MAX !
 	std::queue<int32_t> available_ids;
 	std::vector<ManagedObject> objects;
-	std::unordered_map<const char *, int32_t, Pointer_Hash> handleByAddress;
+	std::unordered_map<void *, int32_t, Pointer_Hash> handleByAddress;
 
-	int Add(int handle, const char *address, ICCDynamicObject *callback, ScriptValueType obj_type);
+	int Add(int handle, void *address, ICCDynamicObject *callback, ScriptValueType obj_type);
 	int Remove(ManagedObject &o, bool force = false);
 	void RunGarbageCollection();
 
@@ -89,19 +86,19 @@ public:
 	int32_t AddRef(int32_t handle);
 	int CheckDispose(int32_t handle);
 	int32_t SubRef(int32_t handle);
-	int32_t AddressToHandle(const char *addr);
-	const char *HandleToAddress(int32_t handle);
+	int32_t AddressToHandle(void *addr);
+	void *HandleToAddress(int32_t handle);
 	ScriptValueType HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager);
-	int RemoveObject(const char *address);
+	int RemoveObject(void *address);
 	void RunGarbageCollectionIfAppropriate();
-	int AddObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type);
-	int AddUnserializedObject(const char *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle);
+	int AddObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type);
+	int AddUnserializedObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle);
 	void WriteToDisk(Shared::Stream *out);
 	int ReadFromDisk(Shared::Stream *in, ICCObjectReader *reader);
 	void reset();
 	ManagedObjectPool();
 
-	const char *disableDisposeForObject{ nullptr };
+	void *disableDisposeForObject{ nullptr };
 };
 
 // Extreme(!!) verbosity managed memory pool log
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index 32647b59098..eb4cf8cbc35 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -37,18 +37,18 @@ const char *ScriptCamera::GetType() {
 	return "Camera2";
 }
 
-int ScriptCamera::Dispose(const char *address, bool force) {
+int ScriptCamera::Dispose(void *address, bool force) {
 	// Note that ScriptCamera is a reference to actual Camera object,
 	// and this deletes the reference, while camera may remain in GameState.
 	delete this;
 	return 1;
 }
 
-size_t ScriptCamera::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptCamera::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptCamera::Serialize(const char *address, Stream *out) {
+void ScriptCamera::Serialize(void *address, Stream *out) {
 	out->WriteInt32(_id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_camera.h b/engines/ags/engine/ac/dynobj/script_camera.h
index 557fa867b20..781c6827f80 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.h
+++ b/engines/ags/engine/ac/dynobj/script_camera.h
@@ -44,13 +44,13 @@ public:
 	}
 
 	const char *GetType() override;
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 
 private:
 	int _id = -1; // index of camera in the game state array
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index 27eadd63cb3..733238bbfc7 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -27,7 +27,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-int ScriptDateTime::Dispose(const char *address, bool force) {
+int ScriptDateTime::Dispose(void *address, bool force) {
 	// always dispose a DateTime
 	delete this;
 	return 1;
@@ -37,11 +37,11 @@ const char *ScriptDateTime::GetType() {
 	return "DateTime";
 }
 
-size_t ScriptDateTime::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptDateTime::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t) * 7;
 }
 
-void ScriptDateTime::Serialize(const char *address, Stream *out) {
+void ScriptDateTime::Serialize(void *address, Stream *out) {
 	out->WriteInt32(year);
 	out->WriteInt32(month);
 	out->WriteInt32(day);
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.h b/engines/ags/engine/ac/dynobj/script_date_time.h
index e4ca7a750e3..3691b3bab5d 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.h
+++ b/engines/ags/engine/ac/dynobj/script_date_time.h
@@ -31,7 +31,7 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 	int hour, minute, second;
 	int rawUnixTime;
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -39,9 +39,9 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index 51a279ac288..e417bc01ad1 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -32,13 +32,13 @@ const char *ScriptDialogOptionsRendering::GetType() {
 	return "DialogOptionsRendering";
 }
 
-size_t ScriptDialogOptionsRendering::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptDialogOptionsRendering::CalcSerializeSize(void * /*address*/) {
 	return 0;
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void ScriptDialogOptionsRendering::Serialize(const char *address, Stream *out) {
+void ScriptDialogOptionsRendering::Serialize(void *address, Stream *out) {
 }
 
 void ScriptDialogOptionsRendering::Unserialize(int index, Stream *in, size_t data_sz) {
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
index 9006b476db5..63f5d02c7d8 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
@@ -49,9 +49,9 @@ struct ScriptDialogOptionsRendering final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index efc5dfecde5..5e4f02e9b54 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -24,7 +24,7 @@
 
 namespace AGS3 {
 
-int ScriptDictBase::Dispose(const char *address, bool force) {
+int ScriptDictBase::Dispose(void *address, bool force) {
 	Clear();
 	delete this;
 	return 1;
@@ -34,11 +34,11 @@ const char *ScriptDictBase::GetType() {
 	return "StringDictionary";
 }
 
-size_t ScriptDictBase::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptDictBase::CalcSerializeSize(void * /*address*/) {
 	return CalcContainerSize();
 }
 
-void ScriptDictBase::Serialize(const char *address, Stream *out) {
+void ScriptDictBase::Serialize(void *address, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
 	SerializeContainer(out);
diff --git a/engines/ags/engine/ac/dynobj/script_dict.h b/engines/ags/engine/ac/dynobj/script_dict.h
index 7e6b3e15a4d..e6daaad4a2f 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.h
+++ b/engines/ags/engine/ac/dynobj/script_dict.h
@@ -48,7 +48,7 @@ using namespace AGS::Shared;
 
 class ScriptDictBase : public AGSCCDynamicObject {
 public:
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -65,9 +65,9 @@ public:
 	virtual void GetValues(std::vector<const char *> &buf) const = 0;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 
 private:
 	virtual size_t CalcContainerSize() = 0;
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index 55069c648ed..ff05fbff560 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -63,7 +63,7 @@ void ScriptDrawingSurface::FinishedDrawing() {
 	modified = 1;
 }
 
-int ScriptDrawingSurface::Dispose(const char *address, bool force) {
+int ScriptDrawingSurface::Dispose(void *address, bool force) {
 
 	// dispose the drawing surface
 	DrawingSurface_Release(this);
@@ -75,11 +75,11 @@ const char *ScriptDrawingSurface::GetType() {
 	return "DrawingSurface";
 }
 
-size_t ScriptDrawingSurface::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptDrawingSurface::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t) * 9;
 }
 
-void ScriptDrawingSurface::Serialize(const char *address, Stream *out) {
+void ScriptDrawingSurface::Serialize(void *address, Stream *out) {
 	// pack mask type in the last byte of a negative integer
 	// note: (-1) is reserved for "unused", for backward compatibility
 	if (roomMaskType > 0)
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index 8feef052766..4c50f54e5bf 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -45,7 +45,7 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 	int hasAlphaChannel;
 	//Shared::Bitmap* abufBackup;
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 	AGS::Shared::Bitmap *GetBitmapSurface();
@@ -61,9 +61,9 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index 4d32b4d91d6..eca57d9b413 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -28,7 +28,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-int ScriptDynamicSprite::Dispose(const char *address, bool force) {
+int ScriptDynamicSprite::Dispose(void *address, bool force) {
 	// always dispose
 	if ((slot) && (!force))
 		free_dynamic_sprite(slot);
@@ -41,11 +41,11 @@ const char *ScriptDynamicSprite::GetType() {
 	return "DynamicSprite";
 }
 
-size_t ScriptDynamicSprite::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptDynamicSprite::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptDynamicSprite::Serialize(const char *address, Stream *out) {
+void ScriptDynamicSprite::Serialize(void *address, Stream *out) {
 	out->WriteInt32(slot);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
index b4b7a4f8ebc..fad13a8c9a1 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
@@ -29,7 +29,7 @@ namespace AGS3 {
 struct ScriptDynamicSprite final : AGSCCDynamicObject {
 	int slot;
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -38,9 +38,9 @@ struct ScriptDynamicSprite final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_file.cpp b/engines/ags/engine/ac/dynobj/script_file.cpp
index 54a47c31464..08d9762a0c9 100644
--- a/engines/ags/engine/ac/dynobj/script_file.cpp
+++ b/engines/ags/engine/ac/dynobj/script_file.cpp
@@ -30,7 +30,7 @@ const Shared::FileOpenMode sc_File::fopenModes[] =
 const Shared::FileWorkMode sc_File::fworkModes[] =
 { Shared::kFile_Read/*CHECKME, was undefined*/, Shared::kFile_Read, Shared::kFile_Write, Shared::kFile_Write };
 
-int sc_File::Dispose(const char *address, bool force) {
+int sc_File::Dispose(void *address, bool force) {
 	Close();
 	delete this;
 	return 1;
@@ -40,7 +40,7 @@ const char *sc_File::GetType() {
 	return "File";
 }
 
-int sc_File::Serialize(const char *address, char *buffer, int bufsize) {
+int sc_File::Serialize(void *address, uint8_t *buffer, int bufsize) {
 	// we cannot serialize an open file, so it will get closed
 	return 0;
 }
diff --git a/engines/ags/engine/ac/dynobj/script_file.h b/engines/ags/engine/ac/dynobj/script_file.h
index aa0f8aeebea..2b3ed740a01 100644
--- a/engines/ags/engine/ac/dynobj/script_file.h
+++ b/engines/ags/engine/ac/dynobj/script_file.h
@@ -39,11 +39,11 @@ struct sc_File final : CCBasicObject {
 	static const Shared::FileOpenMode fopenModes[];
 	static const Shared::FileWorkMode fworkModes[];
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 
 	const char *GetType() override;
 
-	int Serialize(const char *address, char *buffer, int bufsize) override;
+	int Serialize(void *address, uint8_t *buffer, int bufsize) override;
 
 	int OpenFile(const char *filename, int mode);
 	void Close();
diff --git a/engines/ags/engine/ac/dynobj/script_game.cpp b/engines/ags/engine/ac/dynobj/script_game.cpp
index 8665dffc8a1..16849b83e1d 100644
--- a/engines/ags/engine/ac/dynobj/script_game.cpp
+++ b/engines/ags/engine/ac/dynobj/script_game.cpp
@@ -26,13 +26,14 @@
 
 namespace AGS3 {
 
-void StaticGame::WriteInt32(const char *address, intptr_t offset, int32_t val) {
+void StaticGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	uint8_t *data = static_cast<uint8_t *>(address);
 	if (offset == 4 * sizeof(int32_t)) { // game.debug_mode
 		set_debug_mode(val != 0);
 	} else if (offset == 99 * sizeof(int32_t) || offset == 112 * sizeof(int32_t)) { // game.text_align, game.speech_text_align
-		*(int32_t *)(address + offset) = ReadScriptAlignment(val);
+		*(int32_t *)(data + offset) = ReadScriptAlignment(val);
 	} else {
-		*(int32_t *)(address + offset) = val;
+		*(int32_t *)(data + offset) = val;
 	}
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_game.h b/engines/ags/engine/ac/dynobj/script_game.h
index 2b8f675c3fa..7118dacfe42 100644
--- a/engines/ags/engine/ac/dynobj/script_game.h
+++ b/engines/ags/engine/ac/dynobj/script_game.h
@@ -29,7 +29,7 @@ namespace AGS3 {
 // Wrapper around script's "Game" struct, managing access to its variables
 struct StaticGame : public AGSCCStaticObject {
 	const char *GetType() override { return "Game"; }
-	void WriteInt32(const char *address, intptr_t offset, int32_t val) override;
+	void WriteInt32(void *address, intptr_t offset, int32_t val) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 6da5b41a3dc..92a84c19019 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -33,7 +33,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-int ScriptOverlay::Dispose(const char *address, bool force) {
+int ScriptOverlay::Dispose(void * /*address*/, bool force) {
 	// since the managed object is being deleted, remove the
 	// reference so it doesn't try and dispose something else
 	// with that handle later
@@ -59,11 +59,11 @@ const char *ScriptOverlay::GetType() {
 	return "Overlay";
 }
 
-size_t ScriptOverlay::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptOverlay::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t) * 4;
 }
 
-void ScriptOverlay::Serialize(const char *address, Stream *out) {
+void ScriptOverlay::Serialize(void * /*address*/, Stream *out) {
 	out->WriteInt32(overlayId);
 	out->WriteInt32(0); // unused (was text window x padding)
 	out->WriteInt32(0); // unused (was text window y padding)
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.h b/engines/ags/engine/ac/dynobj/script_overlay.h
index 1a40d4bcbd5..15edfc49763 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.h
+++ b/engines/ags/engine/ac/dynobj/script_overlay.h
@@ -29,7 +29,7 @@ namespace AGS3 {
 struct ScriptOverlay final : AGSCCDynamicObject {
 	int overlayId = -1;
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 	void Remove();
@@ -37,9 +37,9 @@ struct ScriptOverlay final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index 479de145300..122a224925d 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -25,7 +25,7 @@
 
 namespace AGS3 {
 
-int ScriptSetBase::Dispose(const char *address, bool force) {
+int ScriptSetBase::Dispose(void * /*address*/, bool force) {
 	Clear();
 	delete this;
 	return 1;
@@ -35,11 +35,11 @@ const char *ScriptSetBase::GetType() {
 	return "StringSet";
 }
 
-size_t ScriptSetBase::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptSetBase::CalcSerializeSize(void * /*address*/) {
 	return CalcContainerSize();
 }
 
-void ScriptSetBase::Serialize(const char *address, Stream *out) {
+void ScriptSetBase::Serialize(void * /*address*/, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
 	SerializeContainer(out);
diff --git a/engines/ags/engine/ac/dynobj/script_set.h b/engines/ags/engine/ac/dynobj/script_set.h
index 31e62a7d7b6..f7be112c12c 100644
--- a/engines/ags/engine/ac/dynobj/script_set.h
+++ b/engines/ags/engine/ac/dynobj/script_set.h
@@ -47,7 +47,7 @@ using namespace AGS::Shared;
 
 class ScriptSetBase : public AGSCCDynamicObject {
 public:
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -63,9 +63,9 @@ public:
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	virtual size_t CalcSerializeSize(const char *address) override;
+	virtual size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 
 private:
 	virtual size_t CalcContainerSize() = 0;
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index 8c66ba8fde3..ab076be776d 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -32,7 +32,7 @@ DynObjectRef ScriptString::CreateString(const char *fromText) {
 	return CreateNewScriptStringObj(fromText);
 }
 
-int ScriptString::Dispose(const char *address, bool force) {
+int ScriptString::Dispose(void * /*address*/, bool force) {
 	// always dispose
 	if (_text) {
 		free(_text);
@@ -46,11 +46,11 @@ const char *ScriptString::GetType() {
 	return "String";
 }
 
-size_t ScriptString::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptString::CalcSerializeSize(void * /*address*/) {
 	return _len + 1 + sizeof(int32_t);
 }
 
-void ScriptString::Serialize(const char * /*address*/, Stream *out) {
+void ScriptString::Serialize(void * /*address*/, Stream *out) {
 	const auto *cstr = _text ? _text : "";
 	out->WriteInt32(_len);
 	out->Write(cstr, _len + 1);
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index 3840d48815c..af9db4fe3f4 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -27,7 +27,7 @@
 namespace AGS3 {
 
 struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -42,9 +42,9 @@ struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 
 private:
 	// TODO: the preallocated text buffer may be assigned externally;
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index e500a1894e6..d6ac7538304 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -46,7 +46,7 @@ ScriptUserObject::~ScriptUserObject() {
 	return suo;
 }
 
-void ScriptUserObject::Create(const char *data, Stream *in, size_t size) {
+void ScriptUserObject::Create(const uint8_t *data, Stream *in, size_t size) {
 	delete[] _data;
 	_data = nullptr;
 
@@ -62,16 +62,16 @@ void ScriptUserObject::Create(const char *data, Stream *in, size_t size) {
 	}
 }
 
-int ScriptUserObject::Dispose(const char *address, bool force) {
+int ScriptUserObject::Dispose(void * /*address*/, bool force) {
 	delete this;
 	return 1;
 }
 
-size_t ScriptUserObject::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptUserObject::CalcSerializeSize(void * /*address*/) {
 	return _size;
 }
 
-void ScriptUserObject::Serialize(const char * /*address*/, AGS::Shared::Stream *out) {
+void ScriptUserObject::Serialize(void * /*address*/, AGS::Shared::Stream *out) {
 	out->Write(_data, _size);
 }
 
@@ -80,47 +80,47 @@ void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, this, this);
 }
 
-const char *ScriptUserObject::GetFieldPtr(const char *address, intptr_t offset) {
+void *ScriptUserObject::GetFieldPtr(void * /*address*/, intptr_t offset) {
 	return _data + offset;
 }
 
-void ScriptUserObject::Read(const char *address, intptr_t offset, void *dest, int size) {
+void ScriptUserObject::Read(void * /*address*/, intptr_t offset, uint8_t *dest, size_t size) {
 	memcpy(dest, _data + offset, size);
 }
 
-uint8_t ScriptUserObject::ReadInt8(const char *address, intptr_t offset) {
+uint8_t ScriptUserObject::ReadInt8(void * /*address*/, intptr_t offset) {
 	return *(uint8_t *)(_data + offset);
 }
 
-int16_t ScriptUserObject::ReadInt16(const char *address, intptr_t offset) {
+int16_t ScriptUserObject::ReadInt16(void * /*address*/, intptr_t offset) {
 	return *(int16_t *)(_data + offset);
 }
 
-int32_t ScriptUserObject::ReadInt32(const char *address, intptr_t offset) {
+int32_t ScriptUserObject::ReadInt32(void * /*address*/, intptr_t offset) {
 	return *(int32_t *)(_data + offset);
 }
 
-float ScriptUserObject::ReadFloat(const char *address, intptr_t offset) {
+float ScriptUserObject::ReadFloat(void * /*address*/, intptr_t offset) {
 	return *(float *)(_data + offset);
 }
 
-void ScriptUserObject::Write(const char *address, intptr_t offset, void *src, int size) {
+void ScriptUserObject::Write(void * /*address*/, intptr_t offset, const uint8_t *src, size_t size) {
 	memcpy((void *)(_data + offset), src, size);
 }
 
-void ScriptUserObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) {
+void ScriptUserObject::WriteInt8(void * /*address*/, intptr_t offset, uint8_t val) {
 	*(uint8_t *)(_data + offset) = val;
 }
 
-void ScriptUserObject::WriteInt16(const char *address, intptr_t offset, int16_t val) {
+void ScriptUserObject::WriteInt16(void * /*address*/, intptr_t offset, int16_t val) {
 	*(int16_t *)(_data + offset) = val;
 }
 
-void ScriptUserObject::WriteInt32(const char *address, intptr_t offset, int32_t val) {
+void ScriptUserObject::WriteInt32(void * /*address*/, intptr_t offset, int32_t val) {
 	*(int32_t *)(_data + offset) = val;
 }
 
-void ScriptUserObject::WriteFloat(const char *address, intptr_t offset, float val) {
+void ScriptUserObject::WriteFloat(void * /*address*/, intptr_t offset, float val) {
 	*(float *)(_data + offset) = val;
 }
 
@@ -128,8 +128,8 @@ void ScriptUserObject::WriteFloat(const char *address, intptr_t offset, float va
 // Allocates managed struct containing two ints: X and Y
 ScriptUserObject *ScriptStructHelpers::CreatePoint(int x, int y) {
 	ScriptUserObject *suo = ScriptUserObject::CreateManaged(sizeof(int32_t) * 2);
-	suo->WriteInt32((const char *)suo, 0, x);
-	suo->WriteInt32((const char *)suo, sizeof(int32_t), y);
+	suo->WriteInt32(suo, 0, x);
+	suo->WriteInt32(suo, sizeof(int32_t), y);
 	return suo;
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index a2fdf92af53..703287c5b32 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -44,25 +44,25 @@ protected:
 
 public:
 	static ScriptUserObject *CreateManaged(size_t size);
-	void Create(const char *data, AGS::Shared::Stream *in, size_t size);
+	void Create(const uint8_t *data, AGS::Shared::Stream *in, size_t size);
 
 	// return the type name of the object
 	const char *GetType() override;
-	int Dispose(const char *address, bool force) override;
+	int  Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 	// Support for reading and writing object values by their relative offset
-	const char *GetFieldPtr(const char *address, intptr_t offset) override;
-	void    Read(const char *address, intptr_t offset, void *dest, int size) override;
-	uint8_t ReadInt8(const char *address, intptr_t offset) override;
-	int16_t ReadInt16(const char *address, intptr_t offset) override;
-	int32_t ReadInt32(const char *address, intptr_t offset) override;
-	float   ReadFloat(const char *address, intptr_t offset) override;
-	void    Write(const char *address, intptr_t offset, void *src, int size) override;
-	void    WriteInt8(const char *address, intptr_t offset, uint8_t val) override;
-	void    WriteInt16(const char *address, intptr_t offset, int16_t val) override;
-	void    WriteInt32(const char *address, intptr_t offset, int32_t val) override;
-	void    WriteFloat(const char *address, intptr_t offset, float val) override;
+	void	*GetFieldPtr(void *address, intptr_t offset) override;
+	void 	Read(void *address, intptr_t offset, uint8_t *dest, size_t size) override;
+	uint8_t ReadInt8(void *address, intptr_t offset) override;
+	int16_t ReadInt16(void *address, intptr_t offset) override;
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	float	ReadFloat(void *address, intptr_t offset) override;
+	void	Write(void *address, intptr_t offset, const uint8_t *src, size_t size) override;
+	void	WriteInt8(void *address, intptr_t offset, uint8_t val) override;
+	void	WriteInt16(void *address, intptr_t offset, int16_t val) override;
+	void	WriteInt32(void *address, intptr_t offset, int32_t val) override;
+	void	WriteFloat(void *address, intptr_t offset, float val) override;
 
 private:
 	// NOTE: we use signed int for Size at the moment, because the managed
@@ -76,9 +76,9 @@ private:
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index b7b204f8a3e..a56bfdb8dfa 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -27,7 +27,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-int ScriptViewFrame::Dispose(const char *address, bool force) {
+int ScriptViewFrame::Dispose(void * /*address*/, bool force) {
 	// always dispose a ViewFrame
 	delete this;
 	return 1;
@@ -37,11 +37,11 @@ const char *ScriptViewFrame::GetType() {
 	return "ViewFrame";
 }
 
-size_t ScriptViewFrame::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptViewFrame::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t) * 3;
 }
 
-void ScriptViewFrame::Serialize(const char *address, Stream *out) {
+void ScriptViewFrame::Serialize(void * /*address*/, Stream *out) {
 	out->WriteInt32(view);
 	out->WriteInt32(loop);
 	out->WriteInt32(frame);
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.h b/engines/ags/engine/ac/dynobj/script_view_frame.h
index 690df07d175..a10a2bad97d 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.h
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.h
@@ -29,7 +29,7 @@ namespace AGS3 {
 struct ScriptViewFrame final : AGSCCDynamicObject {
 	int view, loop, frame;
 
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	const char *GetType() override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
@@ -38,9 +38,9 @@ struct ScriptViewFrame final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index 5c79d313801..10d2b8d7ac7 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -37,18 +37,18 @@ const char *ScriptViewport::GetType() {
 	return "Viewport2";
 }
 
-int ScriptViewport::Dispose(const char *address, bool force) {
+int ScriptViewport::Dispose(void * /*address*/, bool force) {
 	// Note that ScriptViewport is a reference to actual Viewport object,
 	// and this deletes the reference, while viewport may remain in GameState.
 	delete this;
 	return 1;
 }
 
-size_t ScriptViewport::CalcSerializeSize(const char * /*address*/) {
+size_t ScriptViewport::CalcSerializeSize(void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptViewport::Serialize(const char *address, Stream *out) {
+void ScriptViewport::Serialize(void * /*address*/, Stream *out) {
 	out->WriteInt32(_id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.h b/engines/ags/engine/ac/dynobj/script_viewport.h
index c5c6f4fb3ac..0b4276fbfdc 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.h
+++ b/engines/ags/engine/ac/dynobj/script_viewport.h
@@ -43,14 +43,14 @@ public:
 	}
 
 	const char *GetType() override;
-	int Dispose(const char *address, bool force) override;
+	int Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(const char *address) override;
+	size_t CalcSerializeSize(void *address) override;
 	// Write object data into the provided stream
-	void Serialize(const char *address, AGS::Shared::Stream *out) override;
+	void Serialize(void *address, AGS::Shared::Stream *out) override;
 
 private:
 	int _id = -1; // index of viewport in the game state array
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 456d005b8c5..cd1ae273918 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -342,7 +342,7 @@ static void dispose_overlay(ScreenOverlay &over) {
 	over.ddb = nullptr;
 	// invalidate script object and dispose it if there are no more refs
 	if (over.associatedOverlayHandle > 0) {
-		ScriptOverlay *scover = (ScriptOverlay *)const_cast<char *>(ccGetObjectAddressFromHandle(over.associatedOverlayHandle));
+		ScriptOverlay *scover = (ScriptOverlay *)ccGetObjectAddressFromHandle(over.associatedOverlayHandle);
 		if (scover) scover->overlayId = -1;
 		ccAttemptDisposeObject(over.associatedOverlayHandle);
 	}
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index e00d52ce264..7891b032a09 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -996,7 +996,7 @@ int ccInstance::Run(int32_t curpc) {
 			// That might be dynamic object, but also a non-managed dynamic array, "allocated"
 			// on global or local memspace (buffer)
 			void *arr_ptr = registers[SREG_MAR].GetPtrWithOffset();
-			const auto &hdr = CCDynamicArray::GetHeader((const char *)arr_ptr);
+			const auto &hdr = CCDynamicArray::GetHeader(arr_ptr);
 			if ((reg1.IValue < 0) ||
 				(static_cast<uint32_t>(reg1.IValue) >= hdr.TotalSize)) {
 				int elem_count = hdr.ElemCount & (~ARRAY_MANAGED_TYPE_FLAG);
@@ -1028,16 +1028,16 @@ int ccInstance::Run(int32_t curpc) {
 		case SCMD_MEMWRITEPTR: {
 			const auto &reg1 = registers[codeOp.Arg1i()];
 			int32_t handle = registers[SREG_MAR].ReadInt32();
-			const char *address;
+			void *address;
 			switch (reg1.Type) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
-				address = reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue);
+				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
-				address = (char *)reg1.Ptr;
+				address = reg1.Ptr;
 				break;
 			case kScValPluginArg:
 				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
@@ -1064,22 +1064,22 @@ int ccInstance::Run(int32_t curpc) {
 			break;
 		}
 		case SCMD_MEMINITPTR: {
-			const char *address;
+			void *address;
 			const auto &reg1 = registers[codeOp.Arg1i()];
 
 			switch (reg1.Type) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
-				address = (char *)reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue);
+				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
 			case kScValDynamicObject:
 			case kScValPluginObject:
-				address = (char *)reg1.Ptr;
+				address = reg1.Ptr;
 				break;
 			case kScValPluginArg:
 				// FIXME: plugin API is currently strictly 32-bit, so this may break on 64-bit systems
-				address = Int32ToPtr<char>(reg1.IValue);
+				address = Int32ToPtr<uint8_t>(reg1.IValue);
 				break;
 			default:
 				// There's one possible case when the reg1 is 0, which means writing nullptr
@@ -1111,7 +1111,7 @@ int ccInstance::Run(int32_t curpc) {
 			// Note: we might be freeing a dynamic array which contains the DisableDispose
 			// object, that will be handled inside the recursive call to SubRef.
 			// CHECKME!! what type of data may reg1 point to?
-			_GP(pool).disableDisposeForObject = (const char *)registers[SREG_AX].Ptr;
+			_GP(pool).disableDisposeForObject = registers[SREG_AX].Ptr;
 			ccReleaseObjectReference(handle);
 			_GP(pool).disableDisposeForObject = nullptr;
 			registers[SREG_MAR].WriteInt32(0);
@@ -1295,8 +1295,7 @@ int ccInstance::Run(int32_t curpc) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_CALLOBJ argument is not a dynamic object");
-				registers[SREG_OP].SetDynamicObject(
-					(char *)reg1.ArrMgr->GetElementPtr((char *)reg1.Ptr, reg1.IValue), reg1.ArrMgr->GetObjectManager());
+				registers[SREG_OP].SetDynamicObject(reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue), reg1.ArrMgr->GetObjectManager());
 				break;
 			default:
 				cc_error("internal error: SCMD_CALLOBJ argument is not an object of built-in or user-defined type");
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index b8f0c0a3871..0ed9827b8ba 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -49,7 +49,7 @@ uint8_t RuntimeScriptValue::ReadByte() const {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt8((const char *)this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt8(this->Ptr, this->IValue);
 	default:
 		return *((uint8_t *)this->GetPtrWithOffset());
 	}
@@ -72,7 +72,7 @@ int16_t RuntimeScriptValue::ReadInt16() const {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt16((const char *)this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt16(this->Ptr, this->IValue);
 
 	default:
 		return *((int16_t *)this->GetPtrWithOffset());
@@ -96,7 +96,7 @@ int32_t RuntimeScriptValue::ReadInt32() const {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		return this->ObjMgr->ReadInt32((const char *)this->Ptr, this->IValue);
+		return this->ObjMgr->ReadInt32(this->Ptr, this->IValue);
 	default:
 		return *((int32_t *)this->GetPtrWithOffset());
 	}
@@ -115,7 +115,7 @@ void RuntimeScriptValue::WriteByte(uint8_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt8((const char *)this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt8(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((uint8_t *)this->GetPtrWithOffset()) = val;
@@ -142,7 +142,7 @@ void RuntimeScriptValue::WriteInt16(int16_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt16((const char *)this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt16(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int16_t *)this->GetPtrWithOffset()) = val;
@@ -169,7 +169,7 @@ void RuntimeScriptValue::WriteInt32(int32_t val) {
 	case kScValStaticObject:
 	case kScValStaticArray:
 	case kScValDynamicObject:
-		this->ObjMgr->WriteInt32((const char *)this->Ptr, this->IValue, val);
+		this->ObjMgr->WriteInt32(this->Ptr, this->IValue, val);
 		break;
 	default:
 		*((int32_t *)this->GetPtrWithOffset()) = val;
@@ -186,7 +186,7 @@ RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
 
 	if (Ptr) {
 		if (Type == kScValDynamicObject || Type == kScValStaticObject)
-			Ptr = const_cast<char *>(ObjMgr->GetFieldPtr((const char *)Ptr, IValue));
+			Ptr = ObjMgr->GetFieldPtr(Ptr, IValue);
 		else
 			Ptr = PtrU8 + IValue;
 		IValue = 0;
@@ -208,7 +208,7 @@ intptr_t RuntimeScriptValue::GetDirectPtr() const {
 		ival += temp_val->IValue;
 	}
 	if (temp_val->Type == kScValDynamicObject || temp_val->Type == kScValStaticObject)
-		return (intptr_t)temp_val->ObjMgr->GetFieldPtr((const char *)temp_val->Ptr, ival);
+		return (intptr_t)temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
 	else
 		return (intptr_t)(temp_val->PtrU8 + ival);
 }
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 1e891cf1351..8b287d2df8f 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -372,7 +372,7 @@ public:
 		case kScValStaticObject:
 		case kScValStaticArray:
 		case kScValDynamicObject:
-			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32((const char *)this->Ptr, this->IValue));
+			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32(this->Ptr, this->IValue));
 		default:
 			return RuntimeScriptValue().SetInt32(*(int32_t *)this->GetPtrWithOffset());
 		}
@@ -419,7 +419,7 @@ public:
 		case kScValStaticObject:
 		case kScValStaticArray:
 		case kScValDynamicObject: {
-			this->ObjMgr->WriteInt32((const char *)this->Ptr, this->IValue, rval.IValue);
+			this->ObjMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
 			break;
 		}
 		default: {
diff --git a/engines/ags/plugins/ags_controller/ags_controller.cpp b/engines/ags/plugins/ags_controller/ags_controller.cpp
index 2109607d2a7..878d32ea411 100644
--- a/engines/ags/plugins/ags_controller/ags_controller.cpp
+++ b/engines/ags/plugins/ags_controller/ags_controller.cpp
@@ -31,7 +31,7 @@ namespace AGSController {
 struct Controller : public IAGSScriptManagedObject {
 public:
 
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		return true;
 	}
 
@@ -39,7 +39,7 @@ public:
 		return "Controller";
 	};
 
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
diff --git a/engines/ags/plugins/ags_galaxy_steam/ags_galaxy_steam.cpp b/engines/ags/plugins/ags_galaxy_steam/ags_galaxy_steam.cpp
index e53a1d5302e..debdba72961 100644
--- a/engines/ags/plugins/ags_galaxy_steam/ags_galaxy_steam.cpp
+++ b/engines/ags/plugins/ags_galaxy_steam/ags_galaxy_steam.cpp
@@ -32,7 +32,7 @@ struct SteamData : public IAGSScriptManagedObject {
 public:
 	Common::String steamLanguage = "english";
 
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
@@ -41,7 +41,7 @@ public:
 		return "SteamData";
 	};
 
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index e160c5a840b..54a8515b78c 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -642,8 +642,8 @@ void IAGSEngine::QueueGameScriptFunction(const char *name, int32 globalScript, i
 	_G(curscript)->run_another(name, globalScript ? kScInstGame : kScInstRoom, numArgs, params);
 }
 
-int IAGSEngine::RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback) {
-	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
+int IAGSEngine::RegisterManagedObject(void *object, IAGSScriptManagedObject *callback) {
+	_GP(GlobalReturnValue).SetPluginObject(object, (ICCDynamicObject *)callback);
 	return ccRegisterManagedObject(object, (ICCDynamicObject *)callback, kScValPluginObject);
 }
 
@@ -664,12 +664,12 @@ void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectR
 	_G(numPluginReaders)++;
 }
 
-void IAGSEngine::RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback) {
-	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
+void IAGSEngine::RegisterUnserializedObject(int key, void *object, IAGSScriptManagedObject *callback) {
+	_GP(GlobalReturnValue).SetPluginObject(object, (ICCDynamicObject *)callback);
 	ccRegisterUnserializedObject(key, object, (ICCDynamicObject *)callback, kScValPluginObject);
 }
 
-int IAGSEngine::GetManagedObjectKeyByAddress(const char *address) {
+int IAGSEngine::GetManagedObjectKeyByAddress(void *address) {
 	return ccGetObjectHandleFromAddress(address);
 }
 
@@ -688,11 +688,11 @@ const char *IAGSEngine::CreateScriptString(const char *fromText) {
 	return string;
 }
 
-int IAGSEngine::IncrementManagedObjectRefCount(const char *address) {
+int IAGSEngine::IncrementManagedObjectRefCount(void *address) {
 	return ccAddObjectReference(GetManagedObjectKeyByAddress(address));
 }
 
-int IAGSEngine::DecrementManagedObjectRefCount(const char *address) {
+int IAGSEngine::DecrementManagedObjectRefCount(void *address) {
 	return ccReleaseObjectReference(GetManagedObjectKeyByAddress(address));
 }
 
diff --git a/engines/ags/plugins/ags_plugin.h b/engines/ags/plugins/ags_plugin.h
index 0a8684b5be3..0ed14d222ed 100644
--- a/engines/ags/plugins/ags_plugin.h
+++ b/engines/ags/plugins/ags_plugin.h
@@ -273,12 +273,12 @@ public:
 	// when a ref count reaches 0, this is called with the address
 	// of the object. Return 1 to remove the object from memory, 0 to
 	// leave it
-	virtual int Dispose(const char *address, bool force) = 0;
+	virtual int Dispose(void *address, bool force) = 0;
 	// return the type name of the object
 	virtual const char *GetType() = 0;
 	// serialize the object into BUFFER (which is BUFSIZE bytes)
 	// return number of bytes used
-	virtual int Serialize(const char *address, char *buffer, int bufsize) = 0;
+	virtual int Serialize(void *address, char *buffer, int bufsize) = 0;
 protected:
 	IAGSScriptManagedObject() {
 	}
@@ -516,17 +516,17 @@ public:
 	// run the specified script function whenever script engine is available
 	AGSIFUNC(void)   QueueGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1 = 0, long arg2 = 0);
 	// register a new dynamic managed script object
-	AGSIFUNC(int)    RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback);
+	AGSIFUNC(int)    RegisterManagedObject(void *object, IAGSScriptManagedObject *callback);
 	// add an object reader for the specified object type
 	AGSIFUNC(void)   AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader);
 	// register an un-serialized managed script object
-	AGSIFUNC(void)   RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback);
+	AGSIFUNC(void)   RegisterUnserializedObject(int key, void *object, IAGSScriptManagedObject *callback);
 
 	// *** BELOW ARE INTERFACE VERSION 16 AND ABOVE ONLY
 	// get the address of a managed object based on its key
 	AGSIFUNC(void *)  GetManagedObjectAddressByKey(int key);
 	// get managed object's key from its address
-	AGSIFUNC(int)    GetManagedObjectKeyByAddress(const char *address);
+	AGSIFUNC(int)    GetManagedObjectKeyByAddress(void *address);
 
 	// *** BELOW ARE INTERFACE VERSION 17 AND ABOVE ONLY
 	// create a new script string
@@ -534,9 +534,9 @@ public:
 
 	// *** BELOW ARE INTERFACE VERSION 18 AND ABOVE ONLY
 	// increment reference count
-	AGSIFUNC(int)    IncrementManagedObjectRefCount(const char *address);
+	AGSIFUNC(int)    IncrementManagedObjectRefCount(void *address);
 	// decrement reference count
-	AGSIFUNC(int)    DecrementManagedObjectRefCount(const char *address);
+	AGSIFUNC(int)    DecrementManagedObjectRefCount(void *address);
 	// set mouse position
 	AGSIFUNC(void)   SetMousePosition(int32 x, int32 y);
 	// simulate the mouse being clicked
diff --git a/engines/ags/plugins/ags_sock/ags_sock.cpp b/engines/ags/plugins/ags_sock/ags_sock.cpp
index f42d274fc0e..6f72061d927 100644
--- a/engines/ags/plugins/ags_sock/ags_sock.cpp
+++ b/engines/ags/plugins/ags_sock/ags_sock.cpp
@@ -30,14 +30,14 @@ namespace AGSSock {
 
 struct SockData : public IAGSScriptManagedObject, public Common::Array<byte> {
 public:
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
 	const char *GetType() override {
 		return "SockData";
 	};
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
@@ -48,14 +48,14 @@ public:
 	Common::String _address;
 	Common::String _ip;
 
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
 	const char *GetType() override {
 		return "SockAddr";
 	};
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
@@ -73,14 +73,14 @@ public:
 	bool _valid = false;
 	Common::String _errorString;
 
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
 	const char *GetType() override {
 		return "Socket";
 	};
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
diff --git a/engines/ags/plugins/ags_sprite_video/ags_sprite_video.cpp b/engines/ags/plugins/ags_sprite_video/ags_sprite_video.cpp
index d100b52697c..3df6233fc3f 100644
--- a/engines/ags/plugins/ags_sprite_video/ags_sprite_video.cpp
+++ b/engines/ags/plugins/ags_sprite_video/ags_sprite_video.cpp
@@ -35,28 +35,28 @@ char video_filename[200];
 
 struct D3D : public IAGSScriptManagedObject {
 public:
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
 	const char *GetType() override {
 		return "D3D";
 	};
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };
 
 struct D3DVideo : public IAGSScriptManagedObject {
 public:
-	int Dispose(const char *address, bool force) override {
+	int Dispose(void *address, bool force) override {
 		delete this;
 		return true;
 	}
 	const char *GetType() override {
 		return "D3DVideo";
 	};
-	int Serialize(const char *address, char *buffer, int bufsize) override {
+	int Serialize(void *address, char *buffer, int bufsize) override {
 		return 0;
 	}
 };


Commit: ac0422354a01626f010f8c2e0f9fd0a387bece2f
    https://github.com/scummvm/scummvm/commit/ac0422354a01626f010f8c2e0f9fd0a387bece2f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: renamed ICCDynamicObject to IScriptObject

because this interface is now used for both Dynamic and
Static object managers.
>From upstream 1bb14f7a2bae43534edf950c040bcd4a1be5f7df

Changed paths:
  A engines/ags/engine/ac/dynobj/cc_script_object.h
  R engines/ags/engine/ac/dynobj/cc_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_serializer.h
    engines/ags/engine/ac/dynobj/cc_static_array.cpp
    engines/ags/engine/ac/dynobj/cc_static_array.h
    engines/ags/engine/ac/dynobj/dynobj_manager.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h
    engines/ags/engine/ac/script_containers.cpp
    engines/ags/engine/ac/speech.cpp
    engines/ags/engine/ac/string.h
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h
    engines/ags/engine/script/system_imports.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index cf0358f1a91..74bccc552f0 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -21,7 +21,7 @@
 
 //=============================================================================
 //
-// This is a collection of common implementations of the ICCDynamicObject
+// This is a collection of common implementations of the IScriptObject
 // interface. Intended to be used as parent classes for majority of the
 // script object managers.
 //
@@ -40,7 +40,7 @@
 #ifndef AGS_ENGINE_AC_DYNOBJ_CCDYNAMIC_OBJECT_H
 #define AGS_ENGINE_AC_DYNOBJ_CCDYNAMIC_OBJECT_H
 
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 
 namespace AGS3 {
 
@@ -53,7 +53,7 @@ namespace AGS { namespace Shared { class Stream; } }
 // * Serialization skipped, does not save or load anything;
 // * Provides default implementation for reading and writing data fields,
 //   treats the contents of an object as a raw byte buffer.
-struct CCBasicObject : public ICCDynamicObject {
+struct CCBasicObject : public IScriptObject {
 public:
 	virtual ~CCBasicObject() = default;
 
@@ -104,7 +104,7 @@ protected:
 
 // CCStaticObject is a base class for managing static global objects in script.
 // The static objects can never be disposed, and do not support serialization
-// through ICCDynamicObject interface.
+// through IScriptObject interface.
 struct AGSCCStaticObject : public CCBasicObject {
 public:
 	virtual ~AGSCCStaticObject() = default;
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_script_object.h
similarity index 85%
rename from engines/ags/engine/ac/dynobj/cc_dynamic_object.h
rename to engines/ags/engine/ac/dynobj/cc_script_object.h
index 3467ed830f9..da8e2316379 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_script_object.h
@@ -21,12 +21,16 @@
 
 //=============================================================================
 //
-// Managed script object interface.
+// IScriptObject: script managed object interface.
+// Provides interaction with a object which allocation and lifetime is
+// managed by the engine and/or the managed pool rather than the script VM.
+// These may be both static objects existing throughout the game, and
+// dynamic objects allocated by the script command.
 //
 //=============================================================================
 
-#ifndef AGS_ENGINE_AC_DYNOBJ_CC_DYNAMIC_OBJECT_H
-#define AGS_ENGINE_AC_DYNOBJ_CC_DYNAMIC_OBJECT_H
+#ifndef AGS_ENGINE_AC_DYNOBJ_CC_SCRIPT_OBJECT_H
+#define AGS_ENGINE_AC_DYNOBJ_CC_SCRIPT_OBJECT_H
 
 #include "common/std/utility.h"
 #include "ags/shared/core/types.h"
@@ -45,7 +49,7 @@ class Stream;
 typedef std::pair<int32_t, void *> DynObjectRef;
 
 
-struct ICCDynamicObject {
+struct IScriptObject {
 	// WARNING: The first section of this interface is also a part of the AGS plugin API!
 
 	// when a ref count reaches 0, this is called with the address
@@ -89,16 +93,19 @@ struct ICCDynamicObject {
 	virtual void 	WriteFloat(void *address, intptr_t offset, float val) = 0;
 
 protected:
-	ICCDynamicObject() {}
-	virtual ~ICCDynamicObject() {}
+	IScriptObject() {}
+	virtual ~IScriptObject() {}
 };
 
+// The interface of a script object deserializer.
+// WARNING: a part of the plugin API.
 struct ICCObjectReader {
 	virtual ~ICCObjectReader() {}
 	// TODO: pass savegame format version
-	virtual void Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) = 0;
+	virtual void Unserialize(int32_t handle, const char *objectType, const char *serializedData, int dataSize) = 0;
 };
 
+// The interface of a dynamic String allocator.
 struct ICCStringClass {
 	virtual ~ICCStringClass() {}
 	virtual DynObjectRef CreateString(const char *fromText) = 0;
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.h b/engines/ags/engine/ac/dynobj/cc_serializer.h
index 756adc791b9..3c7d2bbfb03 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.h
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.h
@@ -22,7 +22,7 @@
 #ifndef AGS_ENGINE_AC_DYNOBJ_SERIALIZER_H
 #define AGS_ENGINE_AC_DYNOBJ_SERIALIZER_H
 
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.cpp b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
index 70d926f9f89..dc27bf4c2f5 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
@@ -20,11 +20,10 @@
  */
 
 #include "ags/engine/ac/dynobj/cc_static_array.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
 
 namespace AGS3 {
 
-void CCStaticArray::Create(ICCDynamicObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count) {
+void CCStaticArray::Create(IScriptObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count) {
 	_mgr = mgr;
 	_elemScriptSize = elem_script_size;
 	_elemMemSize = elem_mem_size;
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.h b/engines/ags/engine/ac/dynobj/cc_static_array.h
index 985c8dd5fbc..eec5b74bb5e 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.h
@@ -45,9 +45,9 @@ struct CCStaticArray : public AGSCCStaticObject {
 public:
 	~CCStaticArray() override {}
 
-	void Create(ICCDynamicObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count = SIZE_MAX /*unknown*/);
+	void Create(IScriptObject *mgr, size_t elem_script_size, size_t elem_mem_size, size_t elem_count = SIZE_MAX /*unknown*/);
 
-	inline ICCDynamicObject *GetObjectManager() const {
+	inline IScriptObject *GetObjectManager() const {
 		return _mgr;
 	}
 
@@ -69,7 +69,7 @@ public:
 	void 	WriteFloat(void *address, intptr_t offset, float val) override;
 
 private:
-	ICCDynamicObject *_mgr = nullptr;
+	IScriptObject	 *_mgr = nullptr;
 	size_t			 _elemScriptSize = 0u;
 	size_t			 _elemMemSize = 0u;
 	size_t			 _elemCount = 0u;
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index 80577a46ef9..83d0d2d0551 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -35,7 +35,6 @@
 
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/core/platform.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
 #include "ags/engine/ac/dynobj/managed_object_pool.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/shared/script/cc_common.h"
@@ -54,7 +53,7 @@ void ccSetStringClassImpl(ICCStringClass *theClass) {
 
 // register a memory handle for the object and allow script
 // pointers to point to it
-int32_t ccRegisterManagedObject(void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+int32_t ccRegisterManagedObject(void *object, IScriptObject *callback, ScriptValueType obj_type) {
 	int32_t handl = _GP(pool).AddObject(object, callback, obj_type);
 
 	ManagedObjectLog("Register managed object type '%s' handle=%d addr=%08X",
@@ -64,7 +63,7 @@ int32_t ccRegisterManagedObject(void *object, ICCDynamicObject *callback, Script
 }
 
 // register a de-serialized object
-int32_t ccRegisterUnserializedObject(int index, void *object, ICCDynamicObject *callback, ScriptValueType obj_type) {
+int32_t ccRegisterUnserializedObject(int index, void *object, IScriptObject *callback, ScriptValueType obj_type) {
 	return _GP(pool).AddUnserializedObject(object, callback, obj_type, index);
 }
 
@@ -125,7 +124,7 @@ void *ccGetObjectAddressFromHandle(int32_t handle) {
 	return addr;
 }
 
-ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, ICCDynamicObject *&manager) {
+ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, IScriptObject *&manager) {
 	if (handle == 0) {
 		object = nullptr;
 		manager = nullptr;
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index 576c175dbc3..08039a5b19b 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -32,7 +32,7 @@
 
 #include "ags/shared/core/types.h"
 #include "ags/engine/script/runtime_script_value.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 
 namespace AGS3 {
 
@@ -49,9 +49,9 @@ using namespace AGS; // FIXME later
 extern void  ccSetStringClassImpl(ICCStringClass *theClass);
 // register a memory handle for the object and allow script
 // pointers to point to it
-extern int32_t ccRegisterManagedObject(void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterManagedObject(void *object, IScriptObject *, ScriptValueType obj_type = kScValDynamicObject);
 // register a de-serialized object
-extern int32_t ccRegisterUnserializedObject(int index, void *object, ICCDynamicObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterUnserializedObject(int index, void *object, IScriptObject *, ScriptValueType obj_type = kScValDynamicObject);
 // unregister a particular object
 extern int   ccUnRegisterManagedObject(void *object);
 // remove all registered objects
@@ -65,7 +65,7 @@ extern void  ccAttemptDisposeObject(int32_t handle);
 // translate between object handles and memory addresses
 extern int32_t ccGetObjectHandleFromAddress(void *address);
 extern void *ccGetObjectAddressFromHandle(int32_t handle);
-extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, ICCDynamicObject *&manager);
+extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, IScriptObject *&manager);
 
 extern int ccAddObjectReference(int32_t handle);
 extern int ccReleaseObjectReference(int32_t handle);
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 7463a4d59d6..e43e0895038 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -119,7 +119,7 @@ void *ManagedObjectPool::HandleToAddress(int32_t handle) {
 }
 
 // this function is called often (whenever a pointer is used)
-ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager) {
+ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, void *&object, IScriptObject *&manager) {
 	if ((handle < 0 || (size_t)handle >= objects.size()) || !objects[handle].isUsed()) {
 		object = nullptr;
 		manager = nullptr;
@@ -165,7 +165,7 @@ void ManagedObjectPool::RunGarbageCollection() {
 	ManagedObjectLog("Ran garbage collection");
 }
 
-int ManagedObjectPool::Add(int handle, void *address, ICCDynamicObject *callback, ScriptValueType obj_type)
+int ManagedObjectPool::Add(int handle, void *address, IScriptObject *callback, ScriptValueType obj_type)
 {
     auto &o = objects[handle];
     assert(!o.isUsed());
@@ -177,7 +177,7 @@ int ManagedObjectPool::Add(int handle, void *address, ICCDynamicObject *callback
     return handle;
 }
 
-int ManagedObjectPool::AddObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type) {
+int ManagedObjectPool::AddObject(void *address, IScriptObject *callback, ScriptValueType obj_type) {
 	int32_t handle;
 
 	if (!available_ids.empty()) {
@@ -194,7 +194,7 @@ int ManagedObjectPool::AddObject(void *address, ICCDynamicObject *callback, Scri
 	return Add(handle, address, callback, obj_type);
 }
 
-int ManagedObjectPool::AddUnserializedObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle) {
+int ManagedObjectPool::AddUnserializedObject(void *address, IScriptObject *callback, ScriptValueType obj_type, int handle) {
 	if (handle < 0) {
 		cc_error("Attempt to assign invalid handle: %d", handle);
 		return 0;
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index fe3b2286f1a..25dabf2be07 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -28,7 +28,7 @@
 
 #include "ags/shared/core/platform.h"
 #include "ags/engine/script/runtime_script_value.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"   // ICCDynamicObject
+#include "ags/engine/ac/dynobj/cc_script_object.h"   // IScriptObject
 
 namespace AGS3 {
 
@@ -54,7 +54,7 @@ private:
 		ScriptValueType obj_type;
 		int32_t handle;
 		void *addr;
-		ICCDynamicObject *callback;
+		IScriptObject *callback;
 		int refCount;
 
 		bool isUsed() const {
@@ -64,7 +64,7 @@ private:
 		ManagedObject() : obj_type(kScValUndefined), handle(0), addr(nullptr),
 			callback(nullptr), refCount(0) {}
 		ManagedObject(ScriptValueType theType, int32_t theHandle,
-		              void *theAddr, ICCDynamicObject *theCallback)
+		              void *theAddr, IScriptObject *theCallback)
 			: obj_type(theType), handle(theHandle), addr(theAddr),
 			  callback(theCallback), refCount(0) {
 		}
@@ -77,7 +77,7 @@ private:
 	std::vector<ManagedObject> objects;
 	std::unordered_map<void *, int32_t, Pointer_Hash> handleByAddress;
 
-	int Add(int handle, void *address, ICCDynamicObject *callback, ScriptValueType obj_type);
+	int Add(int handle, void *address, IScriptObject *callback, ScriptValueType obj_type);
 	int Remove(ManagedObject &o, bool force = false);
 	void RunGarbageCollection();
 
@@ -88,11 +88,11 @@ public:
 	int32_t SubRef(int32_t handle);
 	int32_t AddressToHandle(void *addr);
 	void *HandleToAddress(int32_t handle);
-	ScriptValueType HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager);
+	ScriptValueType HandleToAddressAndManager(int32_t handle, void *&object, IScriptObject *&manager);
 	int RemoveObject(void *address);
 	void RunGarbageCollectionIfAppropriate();
-	int AddObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type);
-	int AddUnserializedObject(void *address, ICCDynamicObject *callback, ScriptValueType obj_type, int handle);
+	int AddObject(void *address, IScriptObject *callback, ScriptValueType obj_type);
+	int AddUnserializedObject(void *address, IScriptObject *callback, ScriptValueType obj_type, int handle);
 	void WriteToDisk(Shared::Stream *out);
 	int ReadFromDisk(Shared::Stream *in, ICCObjectReader *reader);
 	void reset();
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index b1d18c358e2..7391d8c3cbc 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -28,7 +28,7 @@
 #include "ags/shared/ac/common.h" // quit
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 #include "ags/engine/ac/dynobj/script_dict.h"
 #include "ags/engine/ac/dynobj/script_set.h"
 #include "ags/engine/ac/dynobj/script_string.h"
diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp
index acab8b1e366..02dac0e3ab9 100644
--- a/engines/ags/engine/ac/speech.cpp
+++ b/engines/ags/engine/ac/speech.cpp
@@ -28,7 +28,7 @@
 #include "ags/engine/ac/game_state.h"
 #include "ags/engine/ac/global_audio.h"
 #include "ags/engine/ac/global_display.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index bb25538782c..95025169023 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -23,7 +23,7 @@
 #define AGS_ENGINE_AC_STRING_H
 
 //include <stdarg.h>
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 7891b032a09..293e6f39b9c 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1019,7 +1019,7 @@ int ccInstance::Run(int32_t curpc) {
 			// FIXME: make pool return a ready RuntimeScriptValue with these set?
 			// or another struct, which may be assigned to RSV
 			void *object;
-			ICCDynamicObject *manager;
+			IScriptObject *manager;
 			ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(handle, object, manager);
 			reg1.SetDynamicObject(obj_type, object, manager);
 			ASSERT_CC_ERROR();
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index 0ed9827b8ba..9bd5ae15a41 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -21,7 +21,7 @@
 
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/runtime_script_value.h"
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 #include "ags/shared/util/memory.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 8b287d2df8f..3b3e4a4af9b 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -28,7 +28,7 @@
 #ifndef AGS_ENGINE_SCRIPT_RUNTIME_SCRIPT_VALUE_H
 #define AGS_ENGINE_SCRIPT_RUNTIME_SCRIPT_VALUE_H
 
-#include "ags/engine/ac/dynobj/cc_dynamic_object.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 #include "ags/engine/ac/dynobj/cc_static_array.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/shared/util/memory.h"
@@ -102,7 +102,7 @@ public:
 	// Once those classes are merged, it will no longer be needed.
 	union {
 		void			 *MgrPtr;  // generic object manager pointer
-		ICCDynamicObject *ObjMgr;  // script object manager
+		IScriptObject	 *ObjMgr;  // script object manager
 		CCStaticArray	 *ArrMgr;  // static array manager
 	};
 	// The "real" size of data, either one stored in I/FValue,
@@ -237,7 +237,7 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetStaticObject(void *object, ICCDynamicObject *manager) {
+	inline RuntimeScriptValue &SetStaticObject(void *object, IScriptObject *manager) {
 		Type = kScValStaticObject;
 		methodName.clear();
 		IValue = 0;
@@ -257,7 +257,7 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetDynamicObject(void *object, ICCDynamicObject *manager) {
+	inline RuntimeScriptValue &SetDynamicObject(void *object, IScriptObject *manager) {
 		Type = kScValDynamicObject;
 		methodName.clear();
 		IValue = 0;
@@ -267,7 +267,7 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetPluginObject(void *object, ICCDynamicObject *manager) {
+	inline RuntimeScriptValue &SetPluginObject(void *object, IScriptObject *manager) {
 		Type = kScValPluginObject;
 		methodName.clear();
 		IValue = 0;
@@ -277,7 +277,7 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetDynamicObject(ScriptValueType type, void *object, ICCDynamicObject *manager) {
+	inline RuntimeScriptValue &SetDynamicObject(ScriptValueType type, void *object, IScriptObject *manager) {
 		Type = type;
 		IValue = 0;
 		Ptr = object;
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index 5846a994b32..f9e651f5540 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -62,7 +62,7 @@ bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *i
 	return _GP(simp).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalStaticObject(const String &name, void *ptr, ICCDynamicObject *manager) {
+bool ccAddExternalStaticObject(const String &name, void *ptr, IScriptObject *manager) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticObject(ptr, manager), nullptr) != UINT32_MAX;
 }
 
@@ -70,7 +70,7 @@ bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *arra
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticArray(ptr, array_mgr), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager) {
+bool ccAddExternalDynamicObject(const String &name, void *ptr, IScriptObject *manager) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetDynamicObject(ptr, manager), nullptr) != UINT32_MAX;
 }
 
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index 2b7048df29d..36f68e110fd 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -28,7 +28,7 @@
 
 namespace AGS3 {
 
-struct ICCDynamicObject;
+struct IScriptObject;
 
 using AGS::Shared::String;
 
@@ -63,9 +63,9 @@ bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer
 bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *sc);
 // Register engine objects for script's access.
 // TODO: get manager type from the interface!
-bool ccAddExternalStaticObject(const String &name, void *ptr, ICCDynamicObject *manager);
+bool ccAddExternalStaticObject(const String &name, void *ptr, IScriptObject *manager);
 bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *array_mgr);
-bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager);
+bool ccAddExternalDynamicObject(const String &name, void *ptr, IScriptObject *manager);
 // Register script own functions (defined in the linked scripts)
 bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst);
 // Remove the script access to a variable or function in your program
diff --git a/engines/ags/engine/script/system_imports.h b/engines/ags/engine/script/system_imports.h
index 0f86e7a9749..3936eb36c85 100644
--- a/engines/ags/engine/script/system_imports.h
+++ b/engines/ags/engine/script/system_imports.h
@@ -27,7 +27,7 @@
 
 namespace AGS3 {
 
-struct ICCDynamicObject;
+struct IScriptObject;
 
 using AGS::Shared::String;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 54a8515b78c..8dc8d1f65f5 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -643,8 +643,8 @@ void IAGSEngine::QueueGameScriptFunction(const char *name, int32 globalScript, i
 }
 
 int IAGSEngine::RegisterManagedObject(void *object, IAGSScriptManagedObject *callback) {
-	_GP(GlobalReturnValue).SetPluginObject(object, (ICCDynamicObject *)callback);
-	return ccRegisterManagedObject(object, (ICCDynamicObject *)callback, kScValPluginObject);
+	_GP(GlobalReturnValue).SetPluginObject(object, (IScriptObject *)callback);
+	return ccRegisterManagedObject(object, (IScriptObject *)callback, kScValPluginObject);
 }
 
 void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader) {
@@ -665,8 +665,8 @@ void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectR
 }
 
 void IAGSEngine::RegisterUnserializedObject(int key, void *object, IAGSScriptManagedObject *callback) {
-	_GP(GlobalReturnValue).SetPluginObject(object, (ICCDynamicObject *)callback);
-	ccRegisterUnserializedObject(key, object, (ICCDynamicObject *)callback, kScValPluginObject);
+	_GP(GlobalReturnValue).SetPluginObject(object, (IScriptObject *)callback);
+	ccRegisterUnserializedObject(key, object, (IScriptObject *)callback, kScValPluginObject);
 }
 
 int IAGSEngine::GetManagedObjectKeyByAddress(void *address) {
@@ -675,7 +675,7 @@ int IAGSEngine::GetManagedObjectKeyByAddress(void *address) {
 
 void *IAGSEngine::GetManagedObjectAddressByKey(int key) {
 	void *object;
-	ICCDynamicObject *manager;
+	IScriptObject *manager;
 	ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager);
 	_GP(GlobalReturnValue).SetDynamicObject(obj_type, object, manager);
 	return object;


Commit: f13f7cfac2d496e555d6f1590aca39cc096c0fbe
    https://github.com/scummvm/scummvm/commit/f13f7cfac2d496e555d6f1590aca39cc096c0fbe
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: rem the distinction between static/dynamic objects in script VM

>From upstream 04450b2a41f0e0f107a4ea85dab375f439b4ffae

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h
    engines/ags/engine/script/script_api.h
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 0359f8f84e0..27d01b20b4d 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2035,7 +2035,7 @@ void setup_player_character(int charid) {
 	_G(playerchar) = &_GP(game).chars[charid];
 	_G(sc_PlayerCharPtr) = ccGetObjectHandleFromAddress((char *)_G(playerchar));
 	if (_G(loaded_game_file_version) < kGameVersion_270) {
-		ccAddExternalDynamicObject("player", _G(playerchar), &_GP(ccDynamicCharacter));
+		ccAddExternalScriptObject("player", _G(playerchar), &_GP(ccDynamicCharacter));
 	}
 }
 
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 09a2b1dd714..0a62ddead0d 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -382,7 +382,7 @@ bool get_custom_dialog_options_dimensions(int dlgnum) {
 	_GP(ccDialogOptionsRendering).Reset();
 	_GP(ccDialogOptionsRendering).dialogID = dlgnum;
 
-	_GP(getDialogOptionsDimensionsFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+	_GP(getDialogOptionsDimensionsFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 	run_function_on_non_blocking_thread(&_GP(getDialogOptionsDimensionsFunc));
 
 	if ((_GP(ccDialogOptionsRendering).width > 0) &&
@@ -603,7 +603,7 @@ void DialogOptions::Show() {
 
 	// Close custom dialog options
 	if (usingCustomRendering) {
-		_GP(runDialogOptionCloseFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+		_GP(runDialogOptionCloseFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 		run_function_on_non_blocking_thread(&_GP(runDialogOptionCloseFunc));
 	}
 }
@@ -633,7 +633,7 @@ void DialogOptions::Redraw() {
 		_G(dialogOptionsRenderingSurface)->hasAlphaChannel = _GP(ccDialogOptionsRendering).hasAlphaChannel;
 		options_surface_has_alpha = _G(dialogOptionsRenderingSurface)->hasAlphaChannel != 0;
 
-		_GP(renderDialogOptionsFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+		_GP(renderDialogOptionsFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 		run_function_on_non_blocking_thread(&_GP(renderDialogOptionsFunc));
 
 		if (!_GP(ccDialogOptionsRendering).surfaceAccessed)
@@ -811,7 +811,7 @@ bool DialogOptions::Run() {
 	}
 
 	if (new_custom_render) {
-		_GP(runDialogOptionRepExecFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+		_GP(runDialogOptionRepExecFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 		run_function_on_non_blocking_thread(&_GP(runDialogOptionRepExecFunc));
 	}
 
@@ -832,7 +832,7 @@ bool DialogOptions::Run() {
 		if ((_G(mousex) >= dirtyx) && (_G(mousey) >= dirtyy) &&
 			(_G(mousex) < dirtyx + tempScrn->GetWidth()) &&
 			(_G(mousey) < dirtyy + tempScrn->GetHeight())) {
-			_GP(getDialogOptionUnderCursorFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+			_GP(getDialogOptionUnderCursorFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			run_function_on_non_blocking_thread(&_GP(getDialogOptionUnderCursorFunc));
 
 			if (!_GP(getDialogOptionUnderCursorFunc).atLeastOneImplementationExists)
@@ -872,7 +872,7 @@ bool DialogOptions::Run() {
 		!_GP(play).IsIgnoringInput()) {
 		if (mouseison < 0 && !new_custom_render) {
 			if (usingCustomRendering) {
-				_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+				_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 				_GP(runDialogOptionMouseClickHandlerFunc).params[1].SetInt32(mbut);
 				run_function_on_non_blocking_thread(&_GP(runDialogOptionMouseClickHandlerFunc));
 
@@ -887,7 +887,7 @@ bool DialogOptions::Run() {
 			// they clicked the text box
 			parserActivated = 1;
 		} else if (new_custom_render) {
-			_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+			_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			_GP(runDialogOptionMouseClickHandlerFunc).params[1].SetInt32(mbut);
 			run_function_on_non_blocking_thread(&_GP(runDialogOptionMouseClickHandlerFunc));
 		} else if (usingCustomRendering) {
@@ -901,7 +901,7 @@ bool DialogOptions::Run() {
 
 	if (usingCustomRendering) {
 		if (mwheelz != 0) {
-			_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+			_GP(runDialogOptionMouseClickHandlerFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			_GP(runDialogOptionMouseClickHandlerFunc).params[1].SetInt32((mwheelz < 0) ? 9 : 8);
 			run_function_on_non_blocking_thread(&_GP(runDialogOptionMouseClickHandlerFunc));
 
@@ -996,14 +996,14 @@ bool DialogOptions::RunKey(const KeyInput &ki) {
 	} else if (new_custom_render) {
 		if (old_keyhandle || (ki.UChar == 0)) {
 			// "dialog_options_key_press"
-			_GP(runDialogOptionKeyPressHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+			_GP(runDialogOptionKeyPressHandlerFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			_GP(runDialogOptionKeyPressHandlerFunc).params[1].SetInt32(AGSKeyToScriptKey(ki.Key));
 			_GP(runDialogOptionKeyPressHandlerFunc).params[2].SetInt32(ki.Mod);
 			run_function_on_non_blocking_thread(&_GP(runDialogOptionKeyPressHandlerFunc));
 		}
 		if (!old_keyhandle && (ki.UChar > 0)) {
 			// "dialog_options_text_input"
-			_GP(runDialogOptionTextInputHandlerFunc).params[0].SetDynamicObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
+			_GP(runDialogOptionTextInputHandlerFunc).params[0].SetScriptObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
 			_GP(runDialogOptionTextInputHandlerFunc).params[1].SetInt32(ki.UChar);
 			run_function_on_non_blocking_thread(&_GP(runDialogOptionKeyPressHandlerFunc));
 		}
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index 08039a5b19b..be8897155b3 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -49,9 +49,9 @@ using namespace AGS; // FIXME later
 extern void  ccSetStringClassImpl(ICCStringClass *theClass);
 // register a memory handle for the object and allow script
 // pointers to point to it
-extern int32_t ccRegisterManagedObject(void *object, IScriptObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterManagedObject(void *object, IScriptObject *, ScriptValueType obj_type = kScValScriptObject);
 // register a de-serialized object
-extern int32_t ccRegisterUnserializedObject(int index, void *object, IScriptObject *, ScriptValueType obj_type = kScValDynamicObject);
+extern int32_t ccRegisterUnserializedObject(int index, void *object, IScriptObject *, ScriptValueType obj_type = kScValScriptObject);
 // unregister a particular object
 extern int   ccUnRegisterManagedObject(void *object);
 // remove all registered objects
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index c337685133c..04cc177585d 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1809,12 +1809,12 @@ void RegisterGameAPI() {
 }
 
 void RegisterStaticObjects() {
-	ccAddExternalStaticObject("game", &_GP(play), &_GP(GameStaticManager));
-	ccAddExternalStaticObject("gs_globals", &_GP(play).globalvars[0], &_GP(GlobalStaticManager));
-	ccAddExternalStaticObject("mouse", &_GP(scmouse), &_GP(GlobalStaticManager));
-	ccAddExternalStaticObject("palette", &_G(palette)[0], &_GP(GlobalStaticManager));
-	ccAddExternalStaticObject("system", &_GP(scsystem), &_GP(GlobalStaticManager));
-	ccAddExternalStaticObject("savegameindex", &_GP(play).filenumbers[0], &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("game", &_GP(play), &_GP(GameStaticManager));
+	ccAddExternalScriptObject("gs_globals", &_GP(play).globalvars[0], &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("mouse", &_GP(scmouse), &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("palette", &_G(palette)[0], &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("system", &_GP(scsystem), &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("savegameindex", &_GP(play).filenumbers[0], &_GP(GlobalStaticManager));
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index e6514f9fb47..0ae4c75e76f 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -307,7 +307,7 @@ void remove_popup_interface(int ifacenum) {
 void process_interface_click(int ifce, int btn, int mbut) {
 	if (btn < 0) {
 		// click on GUI background
-		RuntimeScriptValue params[]{ RuntimeScriptValue().SetDynamicObject(&_GP(scrGui)[ifce], &_GP(ccDynamicGUI)),
+		RuntimeScriptValue params[]{ RuntimeScriptValue().SetScriptObject(&_GP(scrGui)[ifce], &_GP(ccDynamicGUI)),
 					RuntimeScriptValue().SetInt32(mbut) };
 		QueueScriptFunction(kScInstGame, _GP(guis)[ifce].OnClickHandler.GetCStr(), 2, params);
 		return;
@@ -335,11 +335,11 @@ void process_interface_click(int ifce, int btn, int mbut) {
 			(!_G(gameinst)->GetSymbolAddress(theObj->EventHandlers[0].GetCStr()).IsNull())) {
 			// control-specific event handler
 			if (theObj->GetEventArgs(0).FindChar(',') != String::NoIndex) {
-				RuntimeScriptValue params[]{ RuntimeScriptValue().SetDynamicObject(theObj, &_GP(ccDynamicGUIObject)),
+				RuntimeScriptValue params[]{ RuntimeScriptValue().SetScriptObject(theObj, &_GP(ccDynamicGUIObject)),
 					RuntimeScriptValue().SetInt32(mbut) };
 				QueueScriptFunction(kScInstGame, theObj->EventHandlers[0].GetCStr(), 2, params);
 			} else {
-				RuntimeScriptValue params[]{ RuntimeScriptValue().SetDynamicObject(theObj, &_GP(ccDynamicGUIObject)) };
+				RuntimeScriptValue params[]{ RuntimeScriptValue().SetScriptObject(theObj, &_GP(ccDynamicGUIObject)) };
 				QueueScriptFunction(kScInstGame, theObj->EventHandlers[0].GetCStr(), 1, params);
 			}
 		} else {
@@ -420,7 +420,7 @@ void export_gui_controls(int ee) {
 	for (int ff = 0; ff < _GP(guis)[ee].GetControlCount(); ff++) {
 		GUIObject *guio = _GP(guis)[ee].GetControl(ff);
 		if (!guio->Name.IsEmpty())
-			ccAddExternalDynamicObject(guio->Name, guio, &_GP(ccDynamicGUIObject));
+			ccAddExternalScriptObject(guio->Name, guio, &_GP(ccDynamicGUIObject));
 		ccRegisterManagedObject(guio, &_GP(ccDynamicGUIObject));
 	}
 }
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index cd1ae273918..d8af7d25191 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -530,14 +530,14 @@ RuntimeScriptValue Sc_Overlay_CreateTextual(const RuntimeScriptValue *params, in
 	API_SCALL_SCRIPT_SPRINTF(Overlay_CreateTextual, 6);
 	ScriptOverlay *overlay = Overlay_CreateTextual(params[0].IValue, params[1].IValue, params[2].IValue,
 												   params[3].IValue, params[4].IValue, scsf_buffer);
-	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
+	return RuntimeScriptValue().SetScriptObject(overlay, overlay);
 }
 
 RuntimeScriptValue Sc_Overlay_CreateRoomTextual(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(Overlay_CreateRoomTextual, 6);
 	ScriptOverlay *overlay = Overlay_CreateRoomTextual(params[0].IValue, params[1].IValue, params[2].IValue,
 													   params[3].IValue, params[4].IValue, scsf_buffer);
-	return RuntimeScriptValue().SetDynamicObject(overlay, overlay);
+	return RuntimeScriptValue().SetScriptObject(overlay, overlay);
 }
 
 // void (ScriptOverlay *scover, int wii, int fontid, int clr, char*texx, ...)
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index a5530a32420..ef83570e51a 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -631,14 +631,14 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 		// export the object's script object
 		if (_GP(thisroom).Objects[cc].ScriptName.IsEmpty())
 			continue;
-		ccAddExternalDynamicObject(_GP(thisroom).Objects[cc].ScriptName, &_G(scrObj)[cc], &_GP(ccDynamicObject));
+		ccAddExternalScriptObject(_GP(thisroom).Objects[cc].ScriptName, &_G(scrObj)[cc], &_GP(ccDynamicObject));
 	}
 
 	for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) {
 		if (_GP(thisroom).Hotspots[cc].ScriptName.IsEmpty())
 			continue;
 
-		ccAddExternalDynamicObject(_GP(thisroom).Hotspots[cc].ScriptName, &_G(scrHotspot)[cc], &_GP(ccDynamicHotspot));
+		ccAddExternalScriptObject(_GP(thisroom).Hotspots[cc].ScriptName, &_G(scrHotspot)[cc], &_GP(ccDynamicHotspot));
 	}
 
 	_G(our_eip) = 210;
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 5ff69ab2551..db9ea91aa07 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -378,7 +378,7 @@ RuntimeScriptValue Sc_String_EndsWith(void *self, const RuntimeScriptValue *para
 // const char* (const char *texx, ...)
 RuntimeScriptValue Sc_String_Format(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_SCRIPT_SPRINTF(String_Format, 1);
-	return RuntimeScriptValue().SetDynamicObject(const_cast<char *>(CreateNewScriptString(scsf_buffer)), &_GP(myScriptStringImpl));
+	return RuntimeScriptValue().SetScriptObject(const_cast<char *>(CreateNewScriptString(scsf_buffer)), &_GP(myScriptStringImpl));
 }
 
 // const char* (const char *thisString)
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 0db19f870c6..7d387ff4b23 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -98,7 +98,7 @@ void InitAndRegisterAudioObjects(GameSetupStruct &game) {
 		// between game versions, for now.
 		game.audioClips[i].id = i;
 		ccRegisterManagedObject(&game.audioClips[i], &_GP(ccDynamicAudioClip));
-		ccAddExternalDynamicObject(game.audioClips[i].scriptName, &game.audioClips[i], &_GP(ccDynamicAudioClip));
+		ccAddExternalScriptObject(game.audioClips[i].scriptName, &game.audioClips[i], &_GP(ccDynamicAudioClip));
 	}
 }
 
@@ -121,7 +121,7 @@ void InitAndRegisterCharacters(GameSetupStruct &game) {
 		ccRegisterManagedObject(&game.chars[i], &_GP(ccDynamicCharacter));
 
 		// export the character's script object
-		ccAddExternalDynamicObject(game.chars[i].scrname, &game.chars[i], &_GP(ccDynamicCharacter));
+		ccAddExternalScriptObject(game.chars[i].scrname, &game.chars[i], &_GP(ccDynamicCharacter));
 	}
 }
 
@@ -134,7 +134,7 @@ void InitAndRegisterDialogs(GameSetupStruct &game) {
 		ccRegisterManagedObject(&_GP(scrDialog)[i], &_GP(ccDynamicDialog));
 
 		if (!game.dialogScriptNames[i].IsEmpty())
-			ccAddExternalDynamicObject(game.dialogScriptNames[i], &_GP(scrDialog)[i], &_GP(ccDynamicDialog));
+			ccAddExternalScriptObject(game.dialogScriptNames[i], &_GP(scrDialog)[i], &_GP(ccDynamicDialog));
 	}
 }
 
@@ -163,7 +163,7 @@ HError InitAndRegisterGUI(GameSetupStruct &game) {
 		// export all the GUI's controls
 		export_gui_controls(i);
 		_GP(scrGui)[i].id = i;
-		ccAddExternalDynamicObject(_GP(guis)[i].Name, &_GP(scrGui)[i], &_GP(ccDynamicGUI));
+		ccAddExternalScriptObject(_GP(guis)[i].Name, &_GP(scrGui)[i], &_GP(ccDynamicGUI));
 		ccRegisterManagedObject(&_GP(scrGui)[i], &_GP(ccDynamicGUI));
 	}
 	return HError::None();
@@ -177,7 +177,7 @@ void InitAndRegisterInvItems(GameSetupStruct &game) {
 		ccRegisterManagedObject(&_G(scrInv)[i], &_GP(ccDynamicInv));
 
 		if (!game.invScriptNames[i].IsEmpty())
-			ccAddExternalDynamicObject(game.invScriptNames[i], &_G(scrInv)[i], &_GP(ccDynamicInv));
+			ccAddExternalScriptObject(game.invScriptNames[i], &_G(scrInv)[i], &_GP(ccDynamicInv));
 	}
 }
 
@@ -245,7 +245,7 @@ HError InitAndRegisterGameEntities(GameSetupStruct &game) {
 
 	setup_player_character(game.playercharacter);
 	if (_G(loaded_game_file_version) >= kGameVersion_270)
-		ccAddExternalStaticObject("player", &_G(sc_PlayerCharPtr), &_GP(GlobalStaticManager));
+		ccAddExternalScriptObject("player", &_G(sc_PlayerCharPtr), &_GP(GlobalStaticManager));
 	return HError::None();
 }
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 293e6f39b9c..c2d45c75601 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -421,7 +421,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	// Allow to pass less parameters if script callback has less declared args
 	numargs = MIN(numargs, export_args);
 	// object pointer needs to start zeroed
-	registers[SREG_OP].SetDynamicObject(nullptr, nullptr);
+	registers[SREG_OP].SetScriptObject(nullptr, nullptr);
 	registers[SREG_SP].SetStackPtr(&stack[0]);
 	stackdata_ptr = stackdata;
 	// NOTE: Pushing parameters to stack in reverse order
@@ -1021,7 +1021,7 @@ int ccInstance::Run(int32_t curpc) {
 			void *object;
 			IScriptObject *manager;
 			ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(handle, object, manager);
-			reg1.SetDynamicObject(obj_type, object, manager);
+			reg1.SetScriptObject(obj_type, object, manager);
 			ASSERT_CC_ERROR();
 			break;
 		}
@@ -1035,7 +1035,7 @@ int ccInstance::Run(int32_t curpc) {
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: MEMWRITEPTR argument is not a dynamic object");
 				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
-			case kScValDynamicObject:
+			case kScValScriptObject:
 			case kScValPluginObject:
 				address = reg1.Ptr;
 				break;
@@ -1073,7 +1073,7 @@ int ccInstance::Run(int32_t curpc) {
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
 				address = reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue);
 				break;
-			case kScValDynamicObject:
+			case kScValScriptObject:
 			case kScValPluginObject:
 				address = reg1.Ptr;
 				break;
@@ -1281,8 +1281,7 @@ int ccInstance::Run(int32_t curpc) {
 			}
 			switch (reg1.Type) {
 			// This might be a static object, passed to the user-defined extender function
-			case kScValStaticObject:
-			case kScValDynamicObject:
+			case kScValScriptObject:
 			case kScValPluginObject:
 			case kScValPluginArg:
 			// This might be an object of USER-DEFINED type, calling its MEMBER-FUNCTION.
@@ -1295,7 +1294,7 @@ int ccInstance::Run(int32_t curpc) {
 			case kScValStaticArray:
 				// FIXME: return manager type from interface?
 				// CC_ERROR_IF_RETCODE(!reg1.ArrMgr->GetDynamicManager(), "internal error: SCMD_CALLOBJ argument is not a dynamic object");
-				registers[SREG_OP].SetDynamicObject(reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue), reg1.ArrMgr->GetObjectManager());
+				registers[SREG_OP].SetScriptObject(reg1.ArrMgr->GetElementPtr(reg1.Ptr, reg1.IValue), reg1.ArrMgr->GetObjectManager());
 				break;
 			default:
 				cc_error("internal error: SCMD_CALLOBJ argument is not an object of built-in or user-defined type");
@@ -1331,7 +1330,7 @@ int ccInstance::Run(int32_t curpc) {
 				return -1;
 			}
 			DynObjectRef ref = _GP(globalDynamicArray).Create(numElements, arg_elsize, arg_managed);
-			reg1.SetDynamicObject(ref.second, &_GP(globalDynamicArray));
+			reg1.SetScriptObject(ref.second, &_GP(globalDynamicArray));
 			break;
 		}
 		case SCMD_NEWUSEROBJECT: {
@@ -1342,7 +1341,7 @@ int ccInstance::Run(int32_t curpc) {
 				return -1;
 			}
 			ScriptUserObject *suo = ScriptUserObject::CreateManaged(arg_size);
-			reg1.SetDynamicObject(suo, suo);
+			reg1.SetScriptObject(suo, suo);
 			break;
 		}
 		case SCMD_FADD: {
@@ -1435,7 +1434,7 @@ int ccInstance::Run(int32_t curpc) {
 				return -1;
 			} else {
 				const char *ptr = reinterpret_cast<const char *>(reg1.GetDirectPtr());
-				reg1.SetDynamicObject(
+				reg1.SetScriptObject(
 					_G(stringClassImpl)->CreateString(ptr).second,
 					&_GP(myScriptStringImpl));
 			}
@@ -1564,8 +1563,7 @@ void ccInstance::DumpInstruction(const ScriptOperation &op) const {
 				debugN(" %p", (void *)arg.GetPtrWithOffset());
 				break;
 			case kScValStaticArray:
-			case kScValStaticObject:
-			case kScValDynamicObject:
+			case kScValScriptObject:
 			case kScValStaticFunction:
 			case kScValObjectFunction:
 			case kScValPluginFunction:
@@ -1825,7 +1823,7 @@ bool ccInstance::CreateGlobalVars(const ccScript *scri) {
 				return false;
 			}
 			// TODO: register this explicitly as a string instead (can do this later)
-			glvar.RValue.SetStaticObject(globaldata + data_addr, &_GP(GlobalStaticManager));
+			glvar.RValue.SetScriptObject(globaldata + data_addr, &_GP(GlobalStaticManager));
 		}
 		break;
 		default:
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index 9bd5ae15a41..804869550b7 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -46,9 +46,8 @@ uint8_t RuntimeScriptValue::ReadByte() const {
 		} else {
 			return static_cast<uint8_t>(RValue->IValue);
 		}
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		return this->ObjMgr->ReadInt8(this->Ptr, this->IValue);
 	default:
 		return *((uint8_t *)this->GetPtrWithOffset());
@@ -69,9 +68,8 @@ int16_t RuntimeScriptValue::ReadInt16() const {
 		} else {
 			return static_cast<int16_t>(RValue->IValue);
 		}
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		return this->ObjMgr->ReadInt16(this->Ptr, this->IValue);
 
 	default:
@@ -93,9 +91,8 @@ int32_t RuntimeScriptValue::ReadInt32() const {
 		} else {
 			return static_cast<uint32_t>(RValue->IValue);
 		}
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		return this->ObjMgr->ReadInt32(this->Ptr, this->IValue);
 	default:
 		return *((int32_t *)this->GetPtrWithOffset());
@@ -112,9 +109,8 @@ void RuntimeScriptValue::WriteByte(uint8_t val) {
 			RValue->SetUInt8(val); // set RValue as int
 		}
 		break;
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		this->ObjMgr->WriteInt8(this->Ptr, this->IValue, val);
 		break;
 	default:
@@ -139,9 +135,8 @@ void RuntimeScriptValue::WriteInt16(int16_t val) {
 			RValue->SetInt16(val); // set RValue as int
 		}
 		break;
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		this->ObjMgr->WriteInt16(this->Ptr, this->IValue, val);
 		break;
 	default:
@@ -166,9 +161,8 @@ void RuntimeScriptValue::WriteInt32(int32_t val) {
 			RValue->SetInt32(val); // set RValue as int
 		}
 		break;
-	case kScValStaticObject:
 	case kScValStaticArray:
-	case kScValDynamicObject:
+	case kScValScriptObject:
 		this->ObjMgr->WriteInt32(this->Ptr, this->IValue, val);
 		break;
 	default:
@@ -185,7 +179,7 @@ RuntimeScriptValue &RuntimeScriptValue::DirectPtr() {
 	}
 
 	if (Ptr) {
-		if (Type == kScValDynamicObject || Type == kScValStaticObject)
+		if (Type == kScValScriptObject)
 			Ptr = ObjMgr->GetFieldPtr(Ptr, IValue);
 		else
 			Ptr = PtrU8 + IValue;
@@ -207,7 +201,7 @@ intptr_t RuntimeScriptValue::GetDirectPtr() const {
 		temp_val = temp_val->RValue;
 		ival += temp_val->IValue;
 	}
-	if (temp_val->Type == kScValDynamicObject || temp_val->Type == kScValStaticObject)
+	if (temp_val->Type == kScValScriptObject)
 		return (intptr_t)temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
 	else
 		return (intptr_t)(temp_val->PtrU8 + ival);
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 3b3e4a4af9b..04338cb47fe 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -42,22 +42,21 @@ enum ScriptValueType {
 	kScValInteger,      // as strictly 32-bit integer (for integer math)
 	kScValFloat,        // as float (for floating point math), 32-bit
 	kScValPluginArg,    // an 32-bit value, passed to a script function when called
-	// directly by plugin; is allowed to represent object pointer
+						// directly by plugin; is allowed to represent object pointer
 	kScValStackPtr,     // as a pointer to stack entry
 	kScValData,         // as a container for randomly sized data (usually array)
 	kScValGlobalVar,    // as a pointer to script variable; used only for global vars,
-	// as pointer to local vars must have StackPtr type so that the
-	// stack allocation could work
+						// as pointer to local vars must have StackPtr type so that the
+						// stack allocation could work
 	kScValStringLiteral,// as a pointer to literal string (array of chars)
-	kScValStaticObject, // as a pointer to static global script object
 	kScValStaticArray,  // as a pointer to static global array (of static or dynamic objects)
-	kScValDynamicObject,// as a pointer to managed script object
+	kScValScriptObject, // as a pointer to managed script object
 	kScValPluginObject, // as a pointer to object managed by plugin (similar to
-	// kScValDynamicObject, but has backward-compatible limitations)
+						// kScValScriptObject, but has backward-compatible limitations)
 	kScValStaticFunction,// as a pointer to static function
 	kScValPluginFunction,// temporary workaround for plugins (unsafe function ptr)
 	kScValObjectFunction,// as a pointer to object member function, gets object pointer as
-	// first parameter
+						 // first parameter
 	kScValCodePtr       // as a pointer to element in byte-code array
 };
 
@@ -237,16 +236,6 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetStaticObject(void *object, IScriptObject *manager) {
-		Type = kScValStaticObject;
-		methodName.clear();
-		IValue = 0;
-		Ptr = object;
-		ObjMgr = manager;
-		Size = 4;
-		return *this;
-	}
-
 	inline RuntimeScriptValue &SetStaticArray(void *object, CCStaticArray *manager) {
 		Type = kScValStaticArray;
 		methodName.clear();
@@ -257,8 +246,8 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetDynamicObject(void *object, IScriptObject *manager) {
-		Type = kScValDynamicObject;
+	inline RuntimeScriptValue &SetScriptObject(void *object, IScriptObject *manager) {
+		Type = kScValScriptObject;
 		methodName.clear();
 		IValue = 0;
 		Ptr = object;
@@ -277,7 +266,7 @@ public:
 		return *this;
 	}
 
-	inline RuntimeScriptValue &SetDynamicObject(ScriptValueType type, void *object, IScriptObject *manager) {
+	inline RuntimeScriptValue &SetScriptObject(ScriptValueType type, void *object, IScriptObject *manager) {
 		Type = type;
 		IValue = 0;
 		Ptr = object;
@@ -369,9 +358,8 @@ public:
 				return *RValue;
 			}
 		}
-		case kScValStaticObject:
 		case kScValStaticArray:
-		case kScValDynamicObject:
+		case kScValScriptObject:
 			return RuntimeScriptValue().SetInt32(this->ObjMgr->ReadInt32(this->Ptr, this->IValue));
 		default:
 			return RuntimeScriptValue().SetInt32(*(int32_t *)this->GetPtrWithOffset());
@@ -416,9 +404,8 @@ public:
 			}
 			break;
 		}
-		case kScValStaticObject:
 		case kScValStaticArray:
-		case kScValDynamicObject: {
+		case kScValScriptObject: {
 			this->ObjMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue);
 			break;
 		}
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index ac2c3f537b9..c1ec7202322 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -306,107 +306,107 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 
 #define API_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \
 	(void)params; (void)param_count; \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(), &RET_MGR)
 
 #define API_CONST_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION()), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION()), &RET_MGR)
 
 #define API_SCALL_OBJ_PINT(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue), &RET_MGR)
 
 #define API_CONST_SCALL_OBJ_PINT(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue)), &RET_MGR)
 
 #define API_SCALL_OBJ_POBJ_PINT_PBOOL(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 3); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].GetAsBool()), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].GetAsBool()), &RET_MGR)
 
 #define API_SCALL_OBJ_PINT2(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue), &RET_MGR)
 
 #define API_CONST_SCALL_OBJ_PINT2(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue)), &RET_MGR)
 
 #define API_SCALL_OBJ_PINT3_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 4); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr), &RET_MGR)
 
 #define API_SCALL_OBJ_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr), &RET_MGR)
 
 #define API_CONST_SCALL_OBJ_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr)), &RET_MGR)
 
 #define API_SCALL_OBJAUTO(RET_CLASS, FUNCTION) \
 	(void)params; (void)param_count; \
 	RET_CLASS* ret_obj = FUNCTION(); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT2(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT3(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 3); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT4(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 4); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT5(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 5); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT2_PBOOL(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 3); \
 	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].GetAsBool()); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT3_PBOOL(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 4); \
 	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].GetAsBool()); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PINT3_PBOOL2(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 5); \
 	RET_CLASS *ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].GetAsBool(), params[4].GetAsBool()); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_PBOOL2(RET_CLASS, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
 	RET_CLASS* ret_obj = FUNCTION(params[0].GetAsBool(), params[1].GetAsBool()); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_POBJ(RET_CLASS, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
 	RET_CLASS* ret_obj = FUNCTION((P1CLASS*)params[0].Ptr); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_POBJ_PINT(RET_CLASS, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
 	RET_CLASS* ret_obj = (RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_SCALL_OBJAUTO_POBJ_PINT4(RET_CLASS, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 5); \
 	RET_CLASS* ret_obj = FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 
 #define API_SCALL_STOBJ_POBJ2(RET_CLASS, FUNCTION, P1CLASS, P2CLASS) \
@@ -575,70 +575,70 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 
 #define API_OBJCALL_OBJ_PINT_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
-	return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, params[0].IValue, (P1CLASS*)params[1].Ptr), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)METHOD((CLASS*)self, params[0].IValue, (P1CLASS*)params[1].Ptr), &RET_MGR)
 
 #define API_OBJCALL_OBJ_POBJ2_PINT(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
-	return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].IValue), &RET_MGR)
 
 #define API_OBJCALL_OBJ_POBJ2_PBOOL(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
-	return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool()), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool()), &RET_MGR)
 
 #define API_CONST_OBJCALL_OBJ_POBJ2_PBOOL(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool())), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool())), &RET_MGR)
 
 #define API_OBJCALL_OBJ(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_SELF(METHOD); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self), &RET_MGR)
 
 #define API_CONST_OBJCALL_OBJ(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_SELF(METHOD); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self)), &RET_MGR)
 
 #define API_OBJCALL_OBJ_PINT(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue), &RET_MGR)
 
 #define API_CONST_OBJCALL_OBJ_PINT(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue)), &RET_MGR)
 
 #define API_OBJCALL_OBJ_PINT2(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue), &RET_MGR)
 
 #define API_CONST_OBJCALL_OBJ_PINT2(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue)), &RET_MGR)
 
 #define API_OBJCALL_OBJ_PINT3(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue), &RET_MGR)
 
 #define API_OBJCALL_OBJ_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr), &RET_MGR)
 
 #define API_CONST_OBJCALL_OBJ_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetDynamicObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)), &RET_MGR)
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)), &RET_MGR)
 
 #define API_OBJCALL_OBJAUTO(CLASS, RET_CLASS, METHOD) \
 	ASSERT_SELF(METHOD); \
 	RET_CLASS* ret_obj = METHOD((CLASS*)self); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_OBJCALL_OBJAUTO_PINT2_PBOOL(CLASS, RET_CLASS, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
 	RET_CLASS* ret_obj = METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].GetAsBool()); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 #define API_OBJCALL_OBJAUTO_POBJ(CLASS, RET_CLASS, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
 	RET_CLASS* ret_obj = METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr); \
-	return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj)
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj)
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index f9e651f5540..f01bcd1595c 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -62,16 +62,12 @@ bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *i
 	return _GP(simp).add(name, RuntimeScriptValue().SetPluginMethod(instance, name), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalStaticObject(const String &name, void *ptr, IScriptObject *manager) {
-	return _GP(simp).add(name, RuntimeScriptValue().SetStaticObject(ptr, manager), nullptr) != UINT32_MAX;
-}
-
 bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *array_mgr) {
 	return _GP(simp).add(name, RuntimeScriptValue().SetStaticArray(ptr, array_mgr), nullptr) != UINT32_MAX;
 }
 
-bool ccAddExternalDynamicObject(const String &name, void *ptr, IScriptObject *manager) {
-	return _GP(simp).add(name, RuntimeScriptValue().SetDynamicObject(ptr, manager), nullptr) != UINT32_MAX;
+bool ccAddExternalScriptObject(const String &name, void *ptr, IScriptObject *manager) {
+	return _GP(simp).add(name, RuntimeScriptValue().SetScriptObject(ptr, manager), nullptr) != UINT32_MAX;
 }
 
 bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst) {
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index 36f68e110fd..d8463e045eb 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -62,10 +62,8 @@ bool ccAddExternalFunctionForPlugin(const String &name, Plugins::ScriptContainer
 // Register a function, exported from a plugin. Requires direct function pointer only.
 bool ccAddExternalPluginFunction(const String &name, Plugins::ScriptContainer *sc);
 // Register engine objects for script's access.
-// TODO: get manager type from the interface!
-bool ccAddExternalStaticObject(const String &name, void *ptr, IScriptObject *manager);
 bool ccAddExternalStaticArray(const String &name, void *ptr, CCStaticArray *array_mgr);
-bool ccAddExternalDynamicObject(const String &name, void *ptr, IScriptObject *manager);
+bool ccAddExternalScriptObject(const String &name, void *ptr, IScriptObject *manager);
 // Register script own functions (defined in the linked scripts)
 bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst);
 // Remove the script access to a variable or function in your program
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 8dc8d1f65f5..eeec1e877a4 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -677,14 +677,14 @@ void *IAGSEngine::GetManagedObjectAddressByKey(int key) {
 	void *object;
 	IScriptObject *manager;
 	ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager);
-	_GP(GlobalReturnValue).SetDynamicObject(obj_type, object, manager);
+	_GP(GlobalReturnValue).SetScriptObject(obj_type, object, manager);
 	return object;
 }
 
 const char *IAGSEngine::CreateScriptString(const char *fromText) {
 	const char *string = CreateNewScriptString(fromText);
 	// Should be still standard dynamic object, because not managed by plugin
-	_GP(GlobalReturnValue).SetDynamicObject(const_cast<char *>(string), &_GP(myScriptStringImpl));
+	_GP(GlobalReturnValue).SetScriptObject(const_cast<char *>(string), &_GP(myScriptStringImpl));
 	return string;
 }
 


Commit: 3bf65728263c88e9a0d6a3b40c9f7ff6da5058ed
    https://github.com/scummvm/scummvm/commit/3bf65728263c88e9a0d6a3b40c9f7ff6da5058ed
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented proper manager for GameState script struct

>From upstream 8360a2c354b51542eb68ae5f5fcd9153de1cdb83

Changed paths:
    engines/ags/engine/ac/dynobj/cc_static_array.cpp
    engines/ags/engine/ac/dynobj/script_game.cpp
    engines/ags/engine/ac/dynobj/script_game.h
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.cpp b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
index dc27bf4c2f5..3c4b0b2d8cc 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/engine/ac/dynobj/cc_static_array.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/ac/dynobj/script_game.cpp b/engines/ags/engine/ac/dynobj/script_game.cpp
index 16849b83e1d..d5531a42826 100644
--- a/engines/ags/engine/ac/dynobj/script_game.cpp
+++ b/engines/ags/engine/ac/dynobj/script_game.cpp
@@ -22,18 +22,420 @@
 #include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_state.h"
-
+#include "ags/shared/script/cc_common.h" // cc_error
+#include "ags/globals.h"
 
 namespace AGS3 {
 
-void StaticGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
-	uint8_t *data = static_cast<uint8_t *>(address);
-	if (offset == 4 * sizeof(int32_t)) { // game.debug_mode
+int32_t CCScriptGame::ReadInt32(void *address, intptr_t offset) {
+	const int index = offset / sizeof(int32_t);
+	if (index >= 5 && index < 5 + MAXGLOBALVARS)
+		return _GP(play).globalvars[index - 5];
+
+	switch (index) {
+	case 0:
+		return _GP(play).score;
+	case 1:
+		return _GP(play).usedmode;
+	case 2:
+		return _GP(play).disabled_user_interface;
+	case 3:
+		return _GP(play).gscript_timer;
+	case 4:
+		return _GP(play).debug_mode;
+	// 5 -> 54: _GP(play).globalvars
+	case 55:
+		return _GP(play).messagetime;
+	case 56:
+		return _GP(play).usedinv;
+	case 57:
+		return _GP(play).inv_top;
+	case 58:
+		return _GP(play).inv_numdisp;
+	case 59:
+		return _GP(play).inv_numorder;
+	case 60:
+		return _GP(play).inv_numinline;
+	case 61:
+		return _GP(play).text_speed;
+	case 62:
+		return _GP(play).sierra_inv_color;
+	case 63:
+		return _GP(play).talkanim_speed;
+	case 64:
+		return _GP(play).inv_item_wid;
+	case 65:
+		return _GP(play).inv_item_hit;
+	case 66:
+		return _GP(play).speech_text_shadow;
+	case 67:
+		return _GP(play).swap_portrait_side;
+	case 68:
+		return _GP(play).speech_textwindow_gui;
+	case 69:
+		return _GP(play).follow_change_room_timer;
+	case 70:
+		return _GP(play).totalscore;
+	case 71:
+		return _GP(play).skip_display;
+	case 72:
+		return _GP(play).no_multiloop_repeat;
+	case 73:
+		return _GP(play).roomscript_finished;
+	case 74:
+		return _GP(play).used_inv_on;
+	case 75:
+		return _GP(play).no_textbg_when_voice;
+	case 76:
+		return _GP(play).max_dialogoption_width;
+	case 77:
+		return _GP(play).no_hicolor_fadein;
+	case 78:
+		return _GP(play).bgspeech_game_speed;
+	case 79:
+		return _GP(play).bgspeech_stay_on_display;
+	case 80:
+		return _GP(play).unfactor_speech_from_textlength;
+	case 81:
+		return _GP(play).mp3_loop_before_end;
+	case 82:
+		return _GP(play).speech_music_drop;
+	case 83:
+		return _GP(play).in_cutscene;
+	case 84:
+		return _GP(play).fast_forward;
+	case 85:
+		return _GP(play).room_width;
+	case 86:
+		return _GP(play).room_height;
+	case 87:
+		return _GP(play).game_speed_modifier;
+	case 88:
+		return _GP(play).score_sound;
+	case 89:
+		return _GP(play).takeover_data;
+	case 90:
+		return 0; // _GP(play).replay_hotkey
+	case 91:
+		return _GP(play).dialog_options_x;
+	case 92:
+		return _GP(play).dialog_options_y;
+	case 93:
+		return _GP(play).narrator_speech;
+	case 94:
+		return _GP(play).ambient_sounds_persist;
+	case 95:
+		return _GP(play).lipsync_speed;
+	case 96:
+		return _GP(play).close_mouth_speech_time;
+	case 97:
+		return _GP(play).disable_antialiasing;
+	case 98:
+		return _GP(play).text_speed_modifier;
+	case 99:
+		return _GP(play).text_align;
+	case 100:
+		return _GP(play).speech_bubble_width;
+	case 101:
+		return _GP(play).min_dialogoption_width;
+	case 102:
+		return _GP(play).disable_dialog_parser;
+	case 103:
+		return _GP(play).anim_background_speed;
+	case 104:
+		return _GP(play).top_bar_backcolor;
+	case 105:
+		return _GP(play).top_bar_textcolor;
+	case 106:
+		return _GP(play).top_bar_bordercolor;
+	case 107:
+		return _GP(play).top_bar_borderwidth;
+	case 108:
+		return _GP(play).top_bar_ypos;
+	case 109:
+		return _GP(play).screenshot_width;
+	case 110:
+		return _GP(play).screenshot_height;
+	case 111:
+		return _GP(play).top_bar_font;
+	case 112:
+		return _GP(play).speech_text_align;
+	case 113:
+		return _GP(play).auto_use_walkto_points;
+	case 114:
+		return _GP(play).inventory_greys_out;
+	case 115:
+		return _GP(play).skip_speech_specific_key;
+	case 116:
+		return _GP(play).abort_key;
+	case 117:
+		return _GP(play).fade_to_red;
+	case 118:
+		return _GP(play).fade_to_green;
+	case 119:
+		return _GP(play).fade_to_blue;
+	case 120:
+		return _GP(play).show_single_dialog_option;
+	case 121:
+		return _GP(play).keep_screen_during_instant_transition;
+	case 122:
+		return _GP(play).read_dialog_option_colour;
+	case 123:
+		return _GP(play).stop_dialog_at_end;
+	case 124:
+		return _GP(play).speech_portrait_placement;
+	case 125:
+		return _GP(play).speech_portrait_x;
+	case 126:
+		return _GP(play).speech_portrait_y;
+	case 127:
+		return _GP(play).speech_display_post_time_ms;
+	case 128:
+		return _GP(play).dialog_options_highlight_color;
+	default:
+		cc_error("ScriptGame: unsupported variable offset %d", offset);
+		return 0;
+	}
+}
+
+void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	const int index = offset / sizeof(int32_t);
+	if (index >= 5 && index < 5 + MAXGLOBALVARS) {
+		_GP(play).globalvars[index - 5] = val;
+		return;
+	}
+
+	switch (index) {
+	case 0:
+		_GP(play).score = val;
+		break;
+	case 1:
+		_GP(play).usedmode = val;
+		break;
+	case 2:
+		_GP(play).disabled_user_interface = val;
+		break;
+	case 3:
+		_GP(play).gscript_timer = val;
+		break;
+	case 4:
 		set_debug_mode(val != 0);
-	} else if (offset == 99 * sizeof(int32_t) || offset == 112 * sizeof(int32_t)) { // game.text_align, game.speech_text_align
-		*(int32_t *)(data + offset) = ReadScriptAlignment(val);
-	} else {
-		*(int32_t *)(data + offset) = val;
+		break; // _GP(play).debug_mode
+	// 5 -> 54: _GP(play).globalvars
+	case 55:
+		_GP(play).messagetime = val;
+		break;
+	case 56:
+		_GP(play).usedinv = val;
+		break;
+	case 57:
+		_GP(play).inv_top = val;
+		break;
+	case 58:
+		_GP(play).inv_numdisp = val;
+		break;
+	case 59:
+		_GP(play).inv_numorder = val;
+		break;
+	case 60:
+		_GP(play).inv_numinline = val;
+		break;
+	case 61:
+		_GP(play).text_speed = val;
+		break;
+	case 62:
+		_GP(play).sierra_inv_color = val;
+		break;
+	case 63:
+		_GP(play).talkanim_speed = val;
+		break;
+	case 64:
+		_GP(play).inv_item_wid = val;
+		break;
+	case 65:
+		_GP(play).inv_item_hit = val;
+		break;
+	case 66:
+		_GP(play).speech_text_shadow = val;
+		break;
+	case 67:
+		_GP(play).swap_portrait_side = val;
+		break;
+	case 68:
+		_GP(play).speech_textwindow_gui = val;
+		break;
+	case 69:
+		_GP(play).follow_change_room_timer = val;
+		break;
+	case 70:
+		_GP(play).totalscore = val;
+		break;
+	case 71:
+		_GP(play).skip_display = val;
+		break;
+	case 72:
+		_GP(play).no_multiloop_repeat = val;
+		break;
+	case 73:
+		_GP(play).roomscript_finished = val;
+		break;
+	case 74:
+		_GP(play).used_inv_on = val;
+		break;
+	case 75:
+		_GP(play).no_textbg_when_voice = val;
+		break;
+	case 76:
+		_GP(play).max_dialogoption_width = val;
+		break;
+	case 77:
+		_GP(play).no_hicolor_fadein = val;
+		break;
+	case 78:
+		_GP(play).bgspeech_game_speed = val;
+		break;
+	case 79:
+		_GP(play).bgspeech_stay_on_display = val;
+		break;
+	case 80:
+		_GP(play).unfactor_speech_from_textlength = val;
+		break;
+	case 81:
+		_GP(play).mp3_loop_before_end = val;
+		break;
+	case 82:
+		_GP(play).speech_music_drop = val;
+		break;
+	case 83: // _GP(play).in_cutscene
+	case 84: // _GP(play).fast_forward;
+	case 85: // _GP(play).room_width;
+	case 86: // _GP(play).room_height;
+		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
+		break;
+	case 87:
+		_GP(play).game_speed_modifier = val;
+		break;
+	case 88:
+		_GP(play).score_sound = val;
+		break;
+	case 89:
+		_GP(play).takeover_data = val;
+		break;
+	case 90:
+		break; // _GP(play).replay_hotkey
+	case 91:
+		_GP(play).dialog_options_x = val;
+		break;
+	case 92:
+		_GP(play).dialog_options_y = val;
+		break;
+	case 93:
+		_GP(play).narrator_speech = val;
+		break;
+	case 94:
+		_GP(play).ambient_sounds_persist = val;
+		break;
+	case 95:
+		_GP(play).lipsync_speed = val;
+		break;
+	case 96:
+		_GP(play).close_mouth_speech_time = val;
+		break;
+	case 97:
+		_GP(play).disable_antialiasing = val;
+		break;
+	case 98:
+		_GP(play).text_speed_modifier = val;
+		break;
+	case 99:
+		_GP(play).text_align = ReadScriptAlignment(val);
+		break;
+	case 100:
+		_GP(play).speech_bubble_width = val;
+		break;
+	case 101:
+		_GP(play).min_dialogoption_width = val;
+		break;
+	case 102:
+		_GP(play).disable_dialog_parser = val;
+		break;
+	case 103:
+		_GP(play).anim_background_speed = val;
+		break;
+	case 104:
+		_GP(play).top_bar_backcolor = val;
+		break;
+	case 105:
+		_GP(play).top_bar_textcolor = val;
+		break;
+	case 106:
+		_GP(play).top_bar_bordercolor = val;
+		break;
+	case 107:
+		_GP(play).top_bar_borderwidth = val;
+		break;
+	case 108:
+		_GP(play).top_bar_ypos = val;
+		break;
+	case 109:
+		_GP(play).screenshot_width = val;
+		break;
+	case 110:
+		_GP(play).screenshot_height = val;
+		break;
+	case 111:
+		_GP(play).top_bar_font = val;
+		break;
+	case 112:
+		_GP(play).speech_text_align = ReadScriptAlignment(val);
+		break;
+	case 113:
+		_GP(play).auto_use_walkto_points = val;
+		break;
+	case 114:
+		_GP(play).inventory_greys_out = val;
+		break;
+	case 115:
+		_GP(play).skip_speech_specific_key = val;
+		break;
+	case 116:
+		_GP(play).abort_key = val;
+		break;
+	case 117: // _GP(play).fade_to_red;
+	case 118: // _GP(play).fade_to_green;
+	case 119: // _GP(play).fade_to_blue;
+		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
+		break;
+	case 120:
+		_GP(play).show_single_dialog_option = val;
+		break;
+	case 121:
+		_GP(play).keep_screen_during_instant_transition = val;
+		break;
+	case 122:
+		_GP(play).read_dialog_option_colour = val;
+		break;
+	case 123:
+		_GP(play).stop_dialog_at_end = val;
+		break;
+	case 124:
+		_GP(play).speech_portrait_placement = val;
+		break;
+	case 125:
+		_GP(play).speech_portrait_x = val;
+		break;
+	case 126:
+		_GP(play).speech_portrait_y = val;
+		break;
+	case 127:
+		_GP(play).speech_display_post_time_ms = val;
+		break;
+	case 128:
+		_GP(play).dialog_options_highlight_color = val;
+		break;
+	default:
+		cc_error("ScriptGame: unsupported variable offset %d", offset);
+		break;
 	}
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_game.h b/engines/ags/engine/ac/dynobj/script_game.h
index 7118dacfe42..808ddd40298 100644
--- a/engines/ags/engine/ac/dynobj/script_game.h
+++ b/engines/ags/engine/ac/dynobj/script_game.h
@@ -19,6 +19,12 @@
  *
  */
 
+//=============================================================================
+//
+// Wrapper around script "GameState" struct, managing access to its variables.
+//
+//=============================================================================
+
 #ifndef AGS_ENGINE_AC_DYNOBJ_AGS_SCRIPT_GAME_H
 #define AGS_ENGINE_AC_DYNOBJ_AGS_SCRIPT_GAME_H
 
@@ -26,9 +32,8 @@
 
 namespace AGS3 {
 
-// Wrapper around script's "Game" struct, managing access to its variables
-struct StaticGame : public AGSCCStaticObject {
-	const char *GetType() override { return "Game"; }
+struct CCScriptGame : public AGSCCStaticObject {
+	int32_t ReadInt32(void *address, intptr_t offset) override;
 	void WriteInt32(void *address, intptr_t offset, int32_t val) override;
 };
 
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index cbb5420c505..0fee4f8a714 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -122,7 +122,7 @@ Globals::Globals() {
 
 	// cc_ags_dynamic_object.cpp globals
 	_GlobalStaticManager = new AGSCCStaticObject();
-	_GameStaticManager = new StaticGame();
+	_GameStaticManager = new CCScriptGame();
 
 	// asset_manager.cpp globals
 	_AssetMgr = new std::unique_ptr<Shared::AssetManager>();
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index f5a70bf82ed..76dfd1c5e9a 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -35,6 +35,7 @@
 #include "ags/shared/font/wfn_font.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/script/cc_script.h"
+#include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/event.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/walk_behind.h"
@@ -304,7 +305,7 @@ public:
 	 */
 
 	AGSCCStaticObject *_GlobalStaticManager;
-	StaticGame        *_GameStaticManager;
+	CCScriptGame	  *_GameStaticManager;
 
 	/**@}*/
 


Commit: dd43bf35d6a067de208d06d00dc251ef0368ae7d
    https://github.com/scummvm/scummvm/commit/dd43bf35d6a067de208d06d00dc251ef0368ae7d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented proper manager for ScriptMouse script struct

>From upstream 487acd23845fda7be5ad29b17631878b7e1a7433

Changed paths:
  A engines/ags/engine/ac/dynobj/script_mouse.cpp
    engines/ags/engine/ac/dynobj/script_mouse.h
    engines/ags/engine/ac/game.cpp
    engines/ags/module.mk


diff --git a/engines/ags/engine/ac/dynobj/script_mouse.cpp b/engines/ags/engine/ac/dynobj/script_mouse.cpp
new file mode 100644
index 00000000000..705259955ec
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/script_mouse.cpp
@@ -0,0 +1,51 @@
+/* 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 "ags/engine/ac/dynobj/script_mouse.h"
+#include "ags/shared/script/cc_common.h" // cc_error
+
+namespace AGS3 {
+
+int32_t ScriptMouse::ReadInt32(void *address, intptr_t offset) {
+	switch (offset) {
+	case 0:
+		return x;
+	case 4:
+		return y;
+	default:
+		cc_error("ScriptMouse: unsupported variable offset %d", offset);
+		return 0;
+	}
+}
+
+void ScriptMouse::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	switch (offset) {
+	case 0:
+	case 4:
+		cc_error("ScriptMouse: attempt to write readonly variable at offset %d", offset);
+		break;
+	default:
+		cc_error("ScriptMouse: unsupported variable offset %d", offset);
+		break;
+	}
+}
+
+} // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_mouse.h b/engines/ags/engine/ac/dynobj/script_mouse.h
index 0768f22dd86..22baf4e6f9a 100644
--- a/engines/ags/engine/ac/dynobj/script_mouse.h
+++ b/engines/ags/engine/ac/dynobj/script_mouse.h
@@ -19,14 +19,24 @@
  *
  */
 
+//=============================================================================
+//
+// Wrapper around script "Mouse" struct, managing access to its variables.
+//
+//=============================================================================
 #ifndef AGS_ENGINE_DYNOBJ__SCRIPTMOUSE_H
 #define AGS_ENGINE_DYNOBJ__SCRIPTMOUSE_H
 
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+
 namespace AGS3 {
 
-// The text script's "mouse" struct
-struct ScriptMouse {
-	int x, y;
+struct ScriptMouse : public AGSCCStaticObject {
+	int x;
+	int y;
+
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	void WriteInt32(void *address, intptr_t offset, int32_t val) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 04cc177585d..0e8d001abf1 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1811,7 +1811,7 @@ void RegisterGameAPI() {
 void RegisterStaticObjects() {
 	ccAddExternalScriptObject("game", &_GP(play), &_GP(GameStaticManager));
 	ccAddExternalScriptObject("gs_globals", &_GP(play).globalvars[0], &_GP(GlobalStaticManager));
-	ccAddExternalScriptObject("mouse", &_GP(scmouse), &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("mouse", &_GP(scmouse), &_GP(scmouse));
 	ccAddExternalScriptObject("palette", &_G(palette)[0], &_GP(GlobalStaticManager));
 	ccAddExternalScriptObject("system", &_GP(scsystem), &_GP(GlobalStaticManager));
 	ccAddExternalScriptObject("savegameindex", &_GP(play).filenumbers[0], &_GP(GlobalStaticManager));
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index c1021770876..ba45da4d464 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -218,6 +218,7 @@ MODULE_OBJS = \
 	engine/ac/dynobj/script_dynamic_sprite.o \
 	engine/ac/dynobj/script_file.o \
 	engine/ac/dynobj/script_game.o \
+	engine/ac/dynobj/script_mouse.o \
 	engine/ac/dynobj/script_overlay.o \
 	engine/ac/dynobj/script_set.o \
 	engine/ac/dynobj/script_string.o \


Commit: 16cb64b0806398a68c920328987127dd042310ae
    https://github.com/scummvm/scummvm/commit/16cb64b0806398a68c920328987127dd042310ae
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented proper manager for ScriptSystem script struct

>From upstream 7d309dad0eaeddca1612fbaacb8cc466b672e141

Changed paths:
  A engines/ags/engine/ac/dynobj/script_system.cpp
    engines/ags/engine/ac/dynobj/script_system.h
    engines/ags/engine/ac/game.cpp
    engines/ags/module.mk


diff --git a/engines/ags/engine/ac/dynobj/script_system.cpp b/engines/ags/engine/ac/dynobj/script_system.cpp
new file mode 100644
index 00000000000..42fb76099d9
--- /dev/null
+++ b/engines/ags/engine/ac/dynobj/script_system.cpp
@@ -0,0 +1,73 @@
+/* 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 "ags/engine/ac/dynobj/script_system.h"
+#include "ags/shared/script/cc_common.h" // cc_error
+
+namespace AGS3 {
+
+int32_t ScriptSystem::ReadInt32(void *address, intptr_t offset) {
+	const int index = offset / sizeof(int32_t);
+	switch (index) {
+	case 0:
+		return width;
+	case 1:
+		return height;
+	case 2:
+		return coldepth;
+	case 3:
+		return os;
+	case 4:
+		return windowed;
+	case 5:
+		return vsync;
+	case 6:
+		return viewport_width;
+	case 7:
+		return viewport_height;
+	default:
+		cc_error("ScriptSystem: unsupported variable offset %d", offset);
+		return 0;
+	}
+}
+
+void ScriptSystem::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	const int index = offset / sizeof(int32_t);
+	switch (index) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+	case 6:
+	case 7:
+		cc_error("ScriptSystem: attempt to write readonly variable at offset %d", offset);
+		break;
+	case 5:
+		vsync = val;
+		break;
+	default:
+		cc_error("ScriptSystem: unsupported variable offset %d", offset);
+		break;
+	}
+}
+
+} // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_system.h b/engines/ags/engine/ac/dynobj/script_system.h
index 7e212cc357d..928ba1c9865 100644
--- a/engines/ags/engine/ac/dynobj/script_system.h
+++ b/engines/ags/engine/ac/dynobj/script_system.h
@@ -19,7 +19,8 @@
  *
  */
 
-// ScriptSystem is a readable/writeable struct which has been exposed to
+// Wrapper around script "System" struct, managing access to its variables.
+// ScriptSystem is a readable/writeable struct which had been exposed to
 // script in older versions of API (deprecated).
 // WARNING: it *MUST* keep its size exact to avoid breaking address offsets
 // when running old scripts. In case of emergency you may use its reserved
@@ -29,10 +30,11 @@
 #ifndef AGS_ENGINE_DYNOBJ_SCRIPT_SYSTEM_H
 #define AGS_ENGINE_DYNOBJ_SCRIPT_SYSTEM_H
 
+#include "ags/engine/ac/dynobj/cc_ags_dynamic_object.h"
+
 namespace AGS3 {
 
-// The text script's "system" struct
-struct ScriptSystem {
+struct ScriptSystem : AGSCCStaticObject {
 	int width = 0; // game screen width
 	int height = 0; // game screen height
 	int coldepth = 0; // game's color depth, in bits per pixel (8, 16, 32)
@@ -43,6 +45,9 @@ struct ScriptSystem {
 	int viewport_height = 0; // game viewport height (normal or letterboxed)
 	char aci_version[10]{}; // engine version string (informational)
 	int reserved[5]{}; // reserved fields
+
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	void	WriteInt32(void *address, intptr_t offset, int32_t val) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 0e8d001abf1..55a33f90861 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1813,7 +1813,7 @@ void RegisterStaticObjects() {
 	ccAddExternalScriptObject("gs_globals", &_GP(play).globalvars[0], &_GP(GlobalStaticManager));
 	ccAddExternalScriptObject("mouse", &_GP(scmouse), &_GP(scmouse));
 	ccAddExternalScriptObject("palette", &_G(palette)[0], &_GP(GlobalStaticManager));
-	ccAddExternalScriptObject("system", &_GP(scsystem), &_GP(GlobalStaticManager));
+	ccAddExternalScriptObject("system", &_GP(scsystem), &_GP(scsystem));
 	ccAddExternalScriptObject("savegameindex", &_GP(play).filenumbers[0], &_GP(GlobalStaticManager));
 }
 
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index ba45da4d464..d1d230b5403 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -222,6 +222,7 @@ MODULE_OBJS = \
 	engine/ac/dynobj/script_overlay.o \
 	engine/ac/dynobj/script_set.o \
 	engine/ac/dynobj/script_string.o \
+	engine/ac/dynobj/script_system.o \
 	engine/ac/dynobj/script_user_object.o \
 	engine/ac/dynobj/script_viewport.o \
 	engine/ac/dynobj/script_view_frame.o \


Commit: cd3a4d8287a5af7bd4535e3b224e87b71b3209fb
    https://github.com/scummvm/scummvm/commit/cd3a4d8287a5af7bd4535e3b224e87b71b3209fb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented proper manager for CCCharacter script struct

>From upstream a8eb1e173a87320d7fb6d4b29183c484fe80cc7c

Changed paths:
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h


diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 7b18cd370dd..931fe507578 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -24,7 +24,7 @@
 #include "ags/shared/ac/character_info.h"
 #include "ags/engine/ac/global_character.h"
 #include "ags/shared/ac/game_setup_struct.h"
-#include "ags/shared/ac/game_version.h"
+#include "ags/shared/script/cc_common.h" // cc_error
 #include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
@@ -55,18 +55,315 @@ void CCCharacter::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, &_GP(game).chars[num], this);
 }
 
+uint8_t CCCharacter::ReadInt8(void *address, intptr_t offset) {
+	const CharacterInfo *ci = static_cast<CharacterInfo *>(address);
+	const int on_offset = 28 * sizeof(int32_t) /* first var group */
+						  + 301 * sizeof(int16_t) /* inventory */ + sizeof(int16_t) * 2 /* two shorts */ + 40 /* name */ + 20 /* scrname */;
+	if (offset == on_offset)
+		return ci->on;
+	cc_error("ScriptCharacter: unsupported 'char' variable offset %d", offset);
+	return 0;
+}
+
+void CCCharacter::WriteInt8(void *address, intptr_t offset, uint8_t val) {
+	CharacterInfo *ci = static_cast<CharacterInfo *>(address);
+	const int on_offset = 28 * sizeof(int32_t) /* first var group */
+						  + 301 * sizeof(int16_t) /* inventory */ + sizeof(int16_t) * 2 /* two shorts */ + 40 /* name */ + 20 /* scrname */;
+	if (offset == on_offset)
+		ci->on = val;
+	else
+		cc_error("ScriptCharacter: unsupported 'char' variable offset %d", offset);
+}
+
+int16_t CCCharacter::ReadInt16(void *address, intptr_t offset) {
+	const CharacterInfo *ci = static_cast<CharacterInfo *>(address);
+
+	// Handle inventory fields
+	const int invoffset = 112;
+	if (offset >= invoffset && offset < (invoffset + MAX_INV * sizeof(short))) {
+		return ci->inv[(offset - invoffset) / sizeof(short)];
+	}
+
+	switch (offset) {
+	// +9 int32 = 36
+	case 36:
+		return ci->following;
+	case 38:
+		return ci->followinfo;
+	// 40 +1 int32 = 44
+	case 44:
+		return ci->idletime;
+	case 46:
+		return ci->idleleft;
+	case 48:
+		return ci->transparency;
+	case 50:
+		return ci->baseline;
+	// 52 +3 int32 = 64
+	case 64:
+		return ci->blinkview;
+	case 66:
+		return ci->blinkinterval;
+	case 68:
+		return ci->blinktimer;
+	case 70:
+		return ci->blinkframe;
+	case 72:
+		return ci->walkspeed_y;
+	case 74:
+		return ci->pic_yoffs;
+	// 76 +2 int32 = 84
+	case 84:
+		return ci->speech_anim_speed;
+	case 86:
+		return ci->idle_anim_speed;
+	case 88:
+		return ci->blocking_width;
+	case 90:
+		return ci->blocking_height;
+	// 92 +1 int32 = 96
+	case 96:
+		return ci->pic_xoffs;
+	case 98:
+		return ci->walkwaitcounter;
+	case 100:
+		return ci->loop;
+	case 102:
+		return ci->frame;
+	case 104:
+		return ci->walking;
+	case 106:
+		return ci->animating;
+	case 108:
+		return ci->walkspeed;
+	case 110:
+		return ci->animspeed;
+	// 112 +301 int16 = 714 (skip inventory)
+	case 714:
+		return ci->actx;
+	case 716:
+		return ci->acty;
+	default:
+		cc_error("ScriptCharacter: unsupported 'short' variable offset %d", offset);
+		return 0;
+	}
+}
+
 void CCCharacter::WriteInt16(void *address, intptr_t offset, int16_t val) {
-	uint8_t *data = static_cast<uint8_t *>(address);
-	*(int16_t *)(data + offset) = val;
+	CharacterInfo *ci = static_cast<CharacterInfo *>(address);
 
 	// Detect when a game directly modifies the inventory, which causes the displayed
 	// and actual inventory to diverge since 2.70. Force an update of the displayed
-	// inventory for older games that reply on the previous behaviour.
-	if (_G(loaded_game_file_version) < kGameVersion_270) {
-		const int invoffset = 112;
-		if (offset >= invoffset && offset < (intptr_t)(invoffset + MAX_INV * sizeof(short))) {
-			update_invorder();
-		}
+	// inventory for older games that rely on this behaviour.
+	const int invoffset = 112;
+	if (offset >= invoffset && offset < (invoffset + MAX_INV * sizeof(short))) {
+		ci->inv[(offset - invoffset) / sizeof(short)] = val;
+		update_invorder();
+		return;
+	}
+
+	// TODO: for safety, find out which of the following fields
+	// must be readonly, and add assertions for them, i.e.:
+	// cc_error("ScriptCharacter: attempt to write readonly 'short' variable at offset %d", offset);
+	switch (offset) {
+	// +9 int32 = 36
+	case 36:
+		ci->following = val;
+		break;
+	case 38:
+		ci->followinfo = val;
+		break;
+	// 40 +1 int32 = 44
+	case 44:
+		ci->idletime = val;
+		break;
+	case 46:
+		ci->idleleft = val;
+		break;
+	case 48:
+		ci->transparency = val;
+		break;
+	case 50:
+		ci->baseline = val;
+		break;
+	// 52 +3 int32 = 64
+	case 64:
+		ci->blinkview = val;
+		break;
+	case 66:
+		ci->blinkinterval = val;
+		break;
+	case 68:
+		ci->blinktimer = val;
+		break;
+	case 70:
+		ci->blinkframe = val;
+		break;
+	case 72:
+		ci->walkspeed_y = val;
+		break;
+	case 74:
+		ci->pic_yoffs = val;
+		break;
+	// 76 +2 int32 = 84
+	case 84:
+		ci->speech_anim_speed = val;
+		break;
+	case 86:
+		ci->idle_anim_speed = val;
+		break;
+	case 88:
+		ci->blocking_width = val;
+		break;
+	case 90:
+		ci->blocking_height = val;
+		break;
+	// 92 +1 int32 = 96
+	case 96:
+		ci->pic_xoffs = val;
+		break;
+	case 98:
+		ci->walkwaitcounter = val;
+		break;
+	case 100:
+		ci->loop = val;
+		break;
+	case 102:
+		ci->frame = val;
+		break;
+	case 104:
+		ci->walking = val;
+		break;
+	case 106:
+		ci->animating = val;
+		break;
+	case 108:
+		ci->walkspeed = val;
+		break;
+	case 110:
+		ci->animspeed = val;
+		break;
+	// 112 +301 int16 = 714 (skip inventory)
+	case 714:
+		ci->actx = val;
+		break;
+	case 716:
+		ci->acty = val;
+		break;
+	default:
+		cc_error("ScriptCharacter: unsupported 'short' variable offset %d", offset);
+		break;
+	}
+}
+
+int32_t CCCharacter::ReadInt32(void *address, intptr_t offset) {
+	const CharacterInfo *ci = static_cast<CharacterInfo *>(address);
+
+	switch (offset) {
+	case 0:
+		return ci->defview;
+	case 4:
+		return ci->talkview;
+	case 8:
+		return ci->view;
+	case 12:
+		return ci->room;
+	case 16:
+		return ci->prevroom;
+	case 20:
+		return ci->x;
+	case 24:
+		return ci->y;
+	case 28:
+		return ci->wait;
+	case 32:
+		return ci->flags;
+	// 36 +2 int16 = 40
+	case 40:
+		return ci->idleview;
+	// 44 +4 int16 = 52
+	case 52:
+		return ci->activeinv;
+	case 56:
+		return ci->talkcolor;
+	case 60:
+		return ci->thinkview;
+	// 64 +6 int16 = 76
+	case 76:
+		return ci->z;
+	case 80:
+		return ci->walkwait;
+	// 84 +4 int16 = 100
+	case 92:
+		return ci->index_id;
+	default:
+		cc_error("ScriptCharacter: unsupported 'int' variable offset %d", offset);
+		return 0;
+	}
+}
+
+void CCCharacter::WriteInt32(void *address, intptr_t offset, int32_t val) {
+	CharacterInfo *ci = static_cast<CharacterInfo *>(address);
+
+	// TODO: for safety, find out which of the following fields
+	// must be readonly, and add assertions for them, i.e.:
+	// cc_error("ScriptCharacter: attempt to write readonly 'int' variable at offset %d", offset);
+	switch (offset) {
+	case 0:
+		ci->defview = val;
+		break;
+	case 4:
+		ci->talkview = val;
+		break;
+	case 8:
+		ci->view = val;
+		break;
+	case 12:
+		ci->room = val;
+		break;
+	case 16:
+		ci->prevroom = val;
+		break;
+	case 20:
+		ci->x = val;
+		break;
+	case 24:
+		ci->y = val;
+		break;
+	case 28:
+		ci->wait = val;
+		break;
+	case 32:
+		ci->flags = val;
+		break;
+	// 36 +2 int16 = 40
+	case 40:
+		ci->idleview = val;
+		break;
+	// 44 +4 int16 = 52
+	case 52:
+		ci->activeinv = val;
+		break;
+	case 56:
+		ci->talkcolor = val;
+		break;
+	case 60:
+		ci->thinkview = val;
+		break;
+	// 64 +6 int16 = 76
+	case 76:
+		ci->z = val;
+		break;
+	case 80:
+		ci->walkwait = val;
+		break;
+	// 84 +4 int16 = 100
+	case 92:
+		ci->index_id = val;
+		break;
+	default:
+		cc_error("ScriptCharacter: unsupported 'int' variable offset %d", offset);
+		break;
 	}
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index e9917218e5b..a97c88a7de1 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -19,6 +19,13 @@
  *
  */
 
+//=============================================================================
+//
+// Wrapper around script "Character" struct, managing access to its variables.
+// Assumes object data contains CharacterInfo object.
+//
+//=============================================================================
+
 #ifndef AGS_ENGINE_AC_DYNOBJ_CC_CHARACTER_H
 #define AGS_ENGINE_AC_DYNOBJ_CC_CHARACTER_H
 
@@ -27,13 +34,18 @@
 namespace AGS3 {
 
 struct CCCharacter final : AGSCCDynamicObject {
-
+public:
 	// return the type name of the object
 	const char *GetType() override;
-
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
+	uint8_t ReadInt8(void *address, intptr_t offset) override;
+	int16_t ReadInt16(void *address, intptr_t offset) override;
+	int32_t ReadInt32(void *address, intptr_t offset) override;
+	void WriteInt8(void *address, intptr_t offset, uint8_t val) override;
 	void WriteInt16(void *address, intptr_t offset, int16_t val) override;
+	void WriteInt32(void *address, intptr_t offset, int32_t val) override;
+
 protected:
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize(void *address) override;


Commit: 63f08f13a08eab0db75c4f5843eac871b0cb8f2b
    https://github.com/scummvm/scummvm/commit/63f08f13a08eab0db75c4f5843eac871b0cb8f2b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed data type in ScriptUserObject

>From upstream 174da0b00eb9355acd381c51524a07187a8a1222

Changed paths:
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h


diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index d6ac7538304..18eaf956e29 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -52,7 +52,7 @@ void ScriptUserObject::Create(const uint8_t *data, Stream *in, size_t size) {
 
 	_size = size;
 	if (_size > 0) {
-		_data = new char[size];
+		_data = new uint8_t[size];
 		if (data)
 			memcpy(_data, data, _size);
 		else if (in)
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index 703287c5b32..1ebc65d9a8d 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -71,8 +71,8 @@ private:
 	// enough. Since this interface is also a part of Plugin API, we would
 	// need more significant change to program before we could use different
 	// approach.
-	int32_t _size = 0;
-	char *_data = nullptr;
+	int32_t  _size = 0;
+	uint8_t *_data = nullptr;
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes


Commit: e6c2b159a76aca7417b25e65903953bbf63c9617
    https://github.com/scummvm/scummvm/commit/e6c2b159a76aca7417b25e65903953bbf63c9617
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: small fixes in ManagedObjectPool

>From upstream 84949acf29bd95111c32facac536d0cac84e6782

Changed paths:
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp


diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index e43e0895038..326c6798f73 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -76,7 +76,7 @@ int ManagedObjectPool::CheckDispose(int32_t handle) {
 }
 
 int32_t ManagedObjectPool::SubRef(int32_t handle) {
-	if (handle < 0 || (size_t)handle >= objects.size()) {
+	if (handle < 1 || (size_t)handle >= objects.size()) {
 		return 0;
 	}
 	auto &o = objects[handle];
@@ -120,7 +120,7 @@ void *ManagedObjectPool::HandleToAddress(int32_t handle) {
 
 // this function is called often (whenever a pointer is used)
 ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, void *&object, IScriptObject *&manager) {
-	if ((handle < 0 || (size_t)handle >= objects.size()) || !objects[handle].isUsed()) {
+	if ((handle < 1 || (size_t)handle >= objects.size()) || !objects[handle].isUsed()) {
 		object = nullptr;
 		manager = nullptr;
 		return kScValUndefined;
@@ -195,7 +195,7 @@ int ManagedObjectPool::AddObject(void *address, IScriptObject *callback, ScriptV
 }
 
 int ManagedObjectPool::AddUnserializedObject(void *address, IScriptObject *callback, ScriptValueType obj_type, int handle) {
-	if (handle < 0) {
+	if (handle < 1) {
 		cc_error("Attempt to assign invalid handle: %d", handle);
 		return 0;
 	}


Commit: 1847279274cf7365a52c3a26247b04b8f317d918
    https://github.com/scummvm/scummvm/commit/1847279274cf7365a52c3a26247b04b8f317d918
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: small code clarification in walk_character() + more comments

There are no functional changes done here.
Partially from upstream 08fe531d3466eabc35d6de8fcb1b8514cdebae1a

Changed paths:
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/room.h


diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index ef83570e51a..25a17509d73 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1014,11 +1014,12 @@ void croom_ptr_clear() {
 	_G(objs) = nullptr;
 }
 
-
+// coordinate conversion (data) ---> game ---> (room mask)
 int room_to_mask_coord(int coord) {
 	return coord * _GP(game).GetDataUpscaleMult() / _GP(thisroom).MaskResolution;
 }
 
+// coordinate conversion (room mask) ---> game ---> (data)
 int mask_to_room_coord(int coord) {
 	return coord * _GP(thisroom).MaskResolution / _GP(game).GetDataUpscaleMult();
 }
@@ -1031,9 +1032,9 @@ void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_st
 
 	// If speed is independent from MaskResolution...
 	if ((_GP(game).options[OPT_WALKSPEEDABSOLUTE] != 0) && _GP(game).GetDataUpscaleMult() > 1) {
-		for (int i = from_step; i <= to_step; i++) { // ...so they are not multiplied by MaskResolution factor when converted to room coords
-			ml->xpermove[i] = ml->xpermove[i] / _GP(game).GetDataUpscaleMult();
-			ml->ypermove[i] = ml->ypermove[i] / _GP(game).GetDataUpscaleMult();
+		for (int i = from_step; i <= to_step; i++) { // ...we still need to convert from game to data coords
+			ml->xpermove[i] = game_to_data_coord(ml->xpermove[i]);
+			ml->ypermove[i] = game_to_data_coord(ml->ypermove[i]);
 		}
 	}
 
diff --git a/engines/ags/engine/ac/room.h b/engines/ags/engine/ac/room.h
index 1820845b7f2..7468a46fbf5 100644
--- a/engines/ags/engine/ac/room.h
+++ b/engines/ags/engine/ac/room.h
@@ -69,14 +69,16 @@ void  on_background_frame_change();
 // Clear the current room pointer if room status is no longer valid
 void  croom_ptr_clear();
 
-// These functions convert coordinates between data resolution and region mask.
-// In hi-res games region masks are 1:2 (or smaller) of the room size.
-// In legacy games with low-res data resolution there's additional conversion
-// between data and room coordinates.
+// Following functions convert coordinates between room resolution and region mask.
+// Region masks can be 1:N of the room size: 1:1, 1:2 etc.
+// In contemporary games this is simply multiplying or dividing on mask resolution.
+// In legacy upscale mode (and generally pre-3.* high-res games) things are more
+// complicated, as first we need to make an additional conversion between data coords
+// and upscale game coordinates.
 //
-// coordinate conversion data ---> room ---> mask
+// coordinate conversion (data) ---> game ---> (room mask)
 extern int room_to_mask_coord(int coord);
-// coordinate conversion mask ---> room ---> data
+// coordinate conversion (room mask) ---> game ---> (data)
 extern int mask_to_room_coord(int coord);
 
 struct MoveList;


Commit: 771872e3386026086782975a3be39a473fc5618d
    https://github.com/scummvm/scummvm/commit/771872e3386026086782975a3be39a473fc5618d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: do not store temp load data in GameSetupStruct

>From upstream 53d3e30563f8be5e3c1a08872a7d290761746729

Changed paths:
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_setup_struct_base.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index d0af068bfb3..c9a29a3d9cf 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -206,9 +206,9 @@ static void ReadMoveList_Aligned(Stream *in) {
 	}
 }
 
-static void ReadGameSetupStructBase_Aligned(Stream *in, GameDataVersion data_ver) {
+static void ReadGameSetupStructBase_Aligned(Stream *in, GameDataVersion data_ver, GameSetupStruct::SerializeInfo &info) {
 	AlignedStream align_s(in, Shared::kAligned_Read);
-	_GP(game).GameSetupStructBase::ReadFromFile(&align_s, data_ver);
+	_GP(game).GameSetupStructBase::ReadFromFile(&align_s, data_ver, info);
 }
 
 static void ReadCharacterExtras_Aligned(Stream *in) {
@@ -476,12 +476,8 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 		int ViewCount = _GP(game).numviews;
 	} objwas;
 
-	ReadGameSetupStructBase_Aligned(in, data_ver);
-
-	// Delete unneeded data
-	// TODO: reorganize this (may be solved by optimizing safe format too)
-	delete[] _GP(game).load_messages;
-	_GP(game).load_messages = nullptr;
+	GameSetupStruct::SerializeInfo info;
+	ReadGameSetupStructBase_Aligned(in, data_ver, info);
 
 	if (!AssertGameContent(err, objwas.CharacterCount, _GP(game).numcharacters, "Characters") ||
 		!AssertGameContent(err, objwas.DialogCount, _GP(game).numdialog, "Dialogs") ||
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 59179a627c4..6fcd0e952ff 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -190,10 +190,8 @@ void GameSetupStruct::read_interaction_scripts(Shared::Stream *in, GameDataVersi
 }
 
 void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
-	if (load_dictionary) {
-		dict.reset(new WordsDictionary());
-		read_dictionary(dict.get(), in);
-	}
+	dict.reset(new WordsDictionary());
+	read_dictionary(dict.get(), in);
 }
 
 void GameSetupStruct::ReadMouseCursors_Aligned(Stream *in) {
@@ -226,7 +224,7 @@ void GameSetupStruct::read_lipsync(Shared::Stream *in, GameDataVersion data_ver)
 		in->ReadArray(&lipSyncFrameLetters[0][0], MAXLIPSYNCFRAMES, 50);
 }
 
-void GameSetupStruct::read_messages(Shared::Stream *in, GameDataVersion data_ver) {
+void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int> &load_messages, GameDataVersion data_ver) {
 	char mbuf[GLOBALMESLENGTH];
 	for (int i = 0; i < MAXGLOBALMES; ++i) {
 		if (!load_messages[i])
@@ -246,8 +244,6 @@ void GameSetupStruct::read_messages(Shared::Stream *in, GameDataVersion data_ver
 		}
 		messages[i] = mbuf;
 	}
-	delete[] load_messages;
-	load_messages = nullptr;
 }
 
 void GameSetupStruct::ReadCharacters_Aligned(Stream *in, bool is_save) {
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 82780cec921..96ac646ff0e 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -19,9 +19,16 @@
  *
  */
 
+//=============================================================================
+//
+// GameSetupStruct is a contemporary main game data.
+//
+//=============================================================================
+
 #ifndef AGS_SHARED_AC_GAME_SETUP_STRUCT_H
 #define AGS_SHARED_AC_GAME_SETUP_STRUCT_H
 
+#include "common/std/array.h"
 #include "common/std/vector.h"
 #include "ags/shared/ac/audio_clip_type.h"
 #include "ags/shared/ac/character_info.h" // TODO: constants to separate header
@@ -148,7 +155,7 @@ struct GameSetupStruct : public GameSetupStructBase {
 	// Part 2
 	void read_characters(Shared::Stream *in);
 	void read_lipsync(Shared::Stream *in, GameDataVersion data_ver);
-	void read_messages(Shared::Stream *in, GameDataVersion data_ver);
+	void read_messages(Shared::Stream *in, const std::array<int> &load_messages, GameDataVersion data_ver);
 
 	void ReadCharacters_Aligned(Shared::Stream *in, bool is_save);
 	void WriteCharacters_Aligned(Shared::Stream *out);
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index a2be1a8ae26..4e2b7d0041a 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -52,11 +52,6 @@ GameSetupStructBase::GameSetupStructBase()
 	, default_lipsync_frame(0)
 	, invhotdotsprite(0)
 	, dict(nullptr)
-	, globalscript(nullptr)
-	, compiled_script(nullptr)
-	, load_messages(nullptr)
-	, load_dictionary(false)
-	, load_compiled_script(false)
 	, _resolutionType(kGameResolution_Undefined)
 	, _dataUpscaleMult(1)
 	, _screenUpscaleMult(1) {
@@ -75,13 +70,7 @@ void GameSetupStructBase::Free() {
 	for (int i = 0; i < MAXGLOBALMES; ++i) {
 		messages[i].Free();
 	}
-	delete[] load_messages;
-	load_messages = nullptr;
 	dict.reset();
-	delete globalscript;
-	globalscript = nullptr;
-	delete compiled_script;
-	compiled_script = nullptr;
 	chars.clear();
 
 	numcharacters = 0;
@@ -142,7 +131,7 @@ void GameSetupStructBase::OnResolutionSet() {
 	_relativeUIMult = IsLegacyHiRes() ? HIRES_COORD_MULTIPLIER : 1;
 }
 
-void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver) {
+void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, SerializeInfo &info) {
 	StrUtil::ReadCStrCount(gamename, in, GAME_NAME_LENGTH);
 	in->ReadArrayOfInt32(options, MAX_OPTIONS);
 	if (game_ver < kGameVersion_340_4) { // TODO: this should probably be possible to deduce script API level
@@ -180,18 +169,15 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver) {
 	default_lipsync_frame = in->ReadInt32();
 	invhotdotsprite = in->ReadInt32();
 	in->ReadArrayOfInt32(reserved, NUM_INTS_RESERVED);
-	load_messages = new int32_t[MAXGLOBALMES];
-	in->ReadArrayOfInt32(load_messages, MAXGLOBALMES);
+	in->ReadArrayOfInt32(&info.HasMessages.front(), MAXGLOBALMES);
 
-	// - GameSetupStruct::read_words_dictionary() checks load_dictionary
-	// - load_game_file() checks load_compiled_script
-	load_dictionary = in->ReadInt32() != 0;
-	in->ReadInt32(); // globalscript
-	in->ReadInt32(); // chars
-	load_compiled_script = in->ReadInt32() != 0;
+	info.HasWordsDict = in->ReadInt32() != 0;
+	in->ReadInt32(); // globalscript (dummy 32-bit pointer value)
+	in->ReadInt32(); // chars (dummy 32-bit pointer value)
+	info.HasCCScript = in->ReadInt32() != 0;
 }
 
-void GameSetupStructBase::WriteToFile(Stream *out) const {
+void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) const {
 	out->Write(gamename, GAME_NAME_LENGTH);
 	out->WriteArrayOfInt32(options, MAX_OPTIONS);
 	out->Write(&paluses[0], sizeof(paluses));
@@ -225,9 +211,9 @@ void GameSetupStructBase::WriteToFile(Stream *out) const {
 		out->WriteInt32(!messages[i].IsEmpty() ? 1 : 0);
 	}
 	out->WriteInt32(dict ? 1 : 0);
-	out->WriteInt32(0); // globalscript
-	out->WriteInt32(0); // chars
-	out->WriteInt32(compiled_script ? 1 : 0);
+	out->WriteInt32(0); // globalscript (dummy 32-bit pointer value)
+	out->WriteInt32(0); // chars  (dummy 32-bit pointer value)
+	out->WriteInt32(info.HasCCScript ? 1 : 0);
 }
 
 Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox) {
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 7b39e37822a..f9f9c25c4c6 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -30,6 +30,7 @@
 #define AGS_SHARED_AC_GAME_SETUP_STRUCT_BASE_H
 
 #include "ags/lib/allegro.h" // RGB
+#include "common/std/array.h"
 #include "common/std/memory.h"
 #include "common/std/vector.h"
 #include "ags/shared/ac/game_version.h"
@@ -81,18 +82,7 @@ struct GameSetupStructBase {
 	int32_t           reserved[NUM_INTS_RESERVED];
 	String			  messages[MAXGLOBALMES];
 	std::unique_ptr<WordsDictionary> dict;
-	char *globalscript;
 	std::vector<CharacterInfo> chars;
-	ccScript *compiled_script;
-
-	// TODO: refactor to not have this as struct members
-	int32_t *load_messages;
-	bool load_dictionary;
-	bool load_compiled_script;
-	// [IKM] 2013-03-30
-	// NOTE: it looks like nor 'globalscript', not 'compiled_script' are used
-	// to store actual script data anytime; 'ccScript* _GP(gamescript)' global
-	// pointer is used for that instead.
 
 	GameSetupStructBase();
 	GameSetupStructBase(GameSetupStructBase &&gss) = default;
@@ -105,8 +95,20 @@ struct GameSetupStructBase {
 	void SetDefaultResolution(Size game_res);
 	void SetGameResolution(GameResolutionType type);
 	void SetGameResolution(Size game_res);
-	void ReadFromFile(Shared::Stream *in, GameDataVersion game_ver);
-	void WriteToFile(Shared::Stream *out) const;
+
+	// Tells whether the serialized game data contains certain components
+	struct SerializeInfo {
+		bool HasCCScript = false;
+		bool HasWordsDict = false;
+		std::array<int> HasMessages;
+
+		SerializeInfo() {
+			HasMessages.resize(MAXGLOBALMES);
+		}
+	};
+
+	void ReadFromFile(Shared::Stream *in, GameDataVersion game_ver, SerializeInfo &info);
+	void WriteToFile(Shared::Stream *out, const SerializeInfo &info) const;
 
 	//
 	// ** On game resolution.
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 7780c3d47bd..c8b174749d6 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -758,9 +758,10 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	//-------------------------------------------------------------------------
 	// The classic data section.
 	//-------------------------------------------------------------------------
+	GameSetupStruct::SerializeInfo sinfo;
 	{
 		AlignedStream align_s(in, Shared::kAligned_Read);
-		game.GameSetupStructBase::ReadFromFile(&align_s, data_ver);
+		game.GameSetupStructBase::ReadFromFile(&align_s, data_ver, sinfo);
 	}
 
 	Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename);
@@ -780,9 +781,10 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	if (!err)
 		return err;
 	game.read_interaction_scripts(in, data_ver);
-	game.read_words_dictionary(in);
+	if (sinfo.HasWordsDict)
+		game.read_words_dictionary(in);
 
-	if (game.load_compiled_script) {
+	if (sinfo.HasCCScript) {
 		ents.GlobalScript.reset(ccScript::CreateFromStream(in));
 		if (!ents.GlobalScript)
 			return new MainGameFileError(kMGFErr_CreateGlobalScriptFailed, cc_get_error().ErrorString);
@@ -804,7 +806,7 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 
 	game.read_characters(in);
 	game.read_lipsync(in, data_ver);
-	game.read_messages(in, data_ver);
+	game.read_messages(in, sinfo.HasMessages, data_ver);
 
 	ReadDialogs(ents.Dialogs, ents.OldDialogScripts, ents.OldDialogSources, ents.OldSpeechLines,
 		in, data_ver, game.numdialog);
@@ -868,13 +870,11 @@ HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver
 }
 
 void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver) {
+	GameSetupStruct::SerializeInfo sinfo;
 	{
 		AlignedStream align_s(in, Shared::kAligned_Read);
-		_GP(game).ReadFromFile(&align_s, data_ver);
+		_GP(game).ReadFromFile(&align_s, data_ver, sinfo);
 	}
-	// Discard game messages we do not need here
-	delete[] _GP(game).load_messages;
-	_GP(game).load_messages = nullptr;
 	_GP(game).read_savegame_info(in, data_ver);
 }
 


Commit: c00522563a3e13e2d5ba209acbef3883f1290c49
    https://github.com/scummvm/scummvm/commit/c00522563a3e13e2d5ba209acbef3883f1290c49
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store ScriptUserObject in memory similar to DynamicArrays

I realized this recently that SUOs were not registered in the script exports
properly. In AGS standards the object having a memory buffer should
register that buffer's address directly, so that it could be read and written
to directly. That is how strings, arrays, and any other structs are exported.
This also how the engine plugins can access the data if they use script API
to retrieve it.

There have not been any script API working with custom structs earlier,
but there is since 3.5.0 (few functions returning managed Point struct).
Plugins won't be able to use them correctly without this fix.

>From upstream 9658c71528676acb386e2c638e782cc4e7d2afe5

Changed paths:
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/cc_script_object.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/engine/ac/script_containers.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 2b214a319c7..62e7c0c28af 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -70,7 +70,7 @@ void CCDynamicArray::Serialize(void *address, AGS::Shared::Stream *out) {
 }
 
 void CCDynamicArray::Unserialize(int index, Stream *in, size_t data_sz) {
-	char *new_arr = new char[(data_sz - FileHeaderSz) + MemHeaderSz];
+	uint8_t *new_arr = new uint8_t[(data_sz - FileHeaderSz) + MemHeaderSz];
 	Header &hdr = reinterpret_cast<Header &>(*new_arr);
 	hdr.ElemCount = in->ReadInt32();
 	hdr.TotalSize = in->ReadInt32();
@@ -78,35 +78,34 @@ void CCDynamicArray::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, &new_arr[MemHeaderSz], this);
 }
 
-DynObjectRef CCDynamicArray::Create(int numElements, int elementSize, bool isManagedType) {
-	char *new_arr = new char[numElements * elementSize + MemHeaderSz];
+/* static */ DynObjectRef CCDynamicArray::Create(int numElements, int elementSize, bool isManagedType) {
+	uint8_t *new_arr = new uint8_t[numElements * elementSize + MemHeaderSz];
 	memset(new_arr, 0, numElements * elementSize + MemHeaderSz);
 	Header &hdr = reinterpret_cast<Header &>(*new_arr);
 	hdr.ElemCount = numElements | (ARRAY_MANAGED_TYPE_FLAG * isManagedType);
 	hdr.TotalSize = elementSize * numElements;
 	void *obj_ptr = &new_arr[MemHeaderSz];
-	// TODO: investigate if it's possible to register real object ptr directly
-	int32_t handle = ccRegisterManagedObject(obj_ptr, this);
+	int32_t handle = ccRegisterManagedObject(obj_ptr, &_GP(globalDynamicArray));
 	if (handle == 0) {
 		delete[] new_arr;
-		return DynObjectRef(0, nullptr);
+		return DynObjectRef();
 	}
-	return DynObjectRef(handle, obj_ptr);
+	return DynObjectRef(handle, obj_ptr, &_GP(globalDynamicArray));
 }
 
 DynObjectRef DynamicArrayHelpers::CreateStringArray(const std::vector<const char *> items) {
 	// NOTE: we need element size of "handle" for array of managed pointers
 	DynObjectRef arr = _GP(globalDynamicArray).Create(items.size(), sizeof(int32_t), true);
-	if (!arr.second)
+	if (!arr.Obj)
 		return arr;
 	// Create script strings and put handles into array
-	int32_t *slots = static_cast<int32_t *>(arr.second);
+	int32_t *slots = static_cast<int32_t *>(arr.Obj);
 	for (auto s : items) {
 		DynObjectRef str = _G(stringClassImpl)->CreateString(s);
 		// We must add reference count, because the string is going to be saved
 		// within another object (array), not returned to script directly
-		ccAddObjectReference(str.first);
-		*(slots++) = str.first;
+		ccAddObjectReference(str.Handle);
+		*(slots++) = str.Handle;
 	}
 
 	return arr;
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 6897ab3267c..3bfb9be6850 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -41,16 +41,20 @@ public:
 		uint32_t TotalSize = 0u;
 	};
 
+	CCDynamicArray() = default;
+	~CCDynamicArray() = default;
+
 	inline static const Header &GetHeader(void *address) {
 		return reinterpret_cast<const Header &>(*(static_cast<uint8_t *>(address) - MemHeaderSz));
 	}
 
+	// Create managed array object and return a pointer to the beginning of a buffer
+	static DynObjectRef Create(int numElements, int elementSize, bool isManagedType);
+
 	// return the type name of the object
 	const char *GetType() override;
 	int Dispose(void *address, bool force) override;
-	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz);
-	// Create managed array object and return a pointer to the beginning of a buffer
-	DynObjectRef Create(int numElements, int elementSize, bool isManagedType);
+	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
 private:
 	// The size of the array's header in memory, prepended to the element data
diff --git a/engines/ags/engine/ac/dynobj/cc_script_object.h b/engines/ags/engine/ac/dynobj/cc_script_object.h
index da8e2316379..ebdc083b0f8 100644
--- a/engines/ags/engine/ac/dynobj/cc_script_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_script_object.h
@@ -44,10 +44,18 @@ class Stream;
 } // namespace Shared
 } // namespace AGS
 
+struct IScriptObject;
 
-// A pair of managed handle and abstract object pointer
-typedef std::pair<int32_t, void *> DynObjectRef;
+// A convenience struct for grouping handle and dynamic object
+struct DynObjectRef {
+	const int Handle = 0;
+	void *const Obj = nullptr;
+	IScriptObject *const Mgr = nullptr;
 
+	DynObjectRef() = default;
+	DynObjectRef(int handle, void *obj, IScriptObject *mgr)
+		: Handle(handle), Obj(obj), Mgr(mgr) {}
+};
 
 struct IScriptObject {
 	// WARNING: The first section of this interface is also a part of the AGS plugin API!
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 18eaf956e29..b343319f368 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -35,102 +35,51 @@ const char *ScriptUserObject::GetType() {
 	return TypeName;
 }
 
-ScriptUserObject::~ScriptUserObject() {
-	delete[] _data;
-}
-
-/* static */ ScriptUserObject *ScriptUserObject::CreateManaged(size_t size) {
-	ScriptUserObject *suo = new ScriptUserObject();
-	suo->Create(nullptr, nullptr, size);
-	ccRegisterManagedObject(suo, suo);
-	return suo;
-}
-
-void ScriptUserObject::Create(const uint8_t *data, Stream *in, size_t size) {
-	delete[] _data;
-	_data = nullptr;
-
-	_size = size;
-	if (_size > 0) {
-		_data = new uint8_t[size];
-		if (data)
-			memcpy(_data, data, _size);
-		else if (in)
-			in->Read(_data, _size);
-		else
-			memset(_data, 0, _size);
+/* static */ DynObjectRef ScriptUserObject::Create(size_t size) {
+	uint8_t *new_data = new uint8_t[size + MemHeaderSz];
+	memset(new_data, 0, size + MemHeaderSz);
+	Header &hdr = reinterpret_cast<Header &>(*new_data);
+	hdr.Size = size;
+	void *obj_ptr = &new_data[MemHeaderSz];
+	int32_t handle = ccRegisterManagedObject(obj_ptr, &globalDynamicStruct);
+	if (handle == 0) {
+		delete[] new_data;
+		return DynObjectRef();
 	}
+	return DynObjectRef(handle, obj_ptr, &globalDynamicStruct);
 }
 
-int ScriptUserObject::Dispose(void * /*address*/, bool force) {
-	delete this;
+int ScriptUserObject::Dispose(void *address, bool /*force*/) {
+	delete[] (static_cast<uint8_t *>(address) - MemHeaderSz);
 	return 1;
 }
 
-size_t ScriptUserObject::CalcSerializeSize(void * /*address*/) {
-	return _size;
+size_t ScriptUserObject::CalcSerializeSize(void *address) {
+	const Header &hdr = GetHeader(address);
+	return hdr.Size + FileHeaderSz;
 }
 
-void ScriptUserObject::Serialize(void * /*address*/, AGS::Shared::Stream *out) {
-	out->Write(_data, _size);
+void ScriptUserObject::Serialize(void *address, AGS::Shared::Stream *out) {
+	const Header &hdr = GetHeader(address);
+	// NOTE: we only write the data, no header at the moment
+	out->Write(address, hdr.Size);
 }
 
 void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
-	Create(nullptr, in, data_sz);
-	ccRegisterUnserializedObject(index, this, this);
-}
-
-void *ScriptUserObject::GetFieldPtr(void * /*address*/, intptr_t offset) {
-	return _data + offset;
-}
-
-void ScriptUserObject::Read(void * /*address*/, intptr_t offset, uint8_t *dest, size_t size) {
-	memcpy(dest, _data + offset, size);
-}
-
-uint8_t ScriptUserObject::ReadInt8(void * /*address*/, intptr_t offset) {
-	return *(uint8_t *)(_data + offset);
-}
-
-int16_t ScriptUserObject::ReadInt16(void * /*address*/, intptr_t offset) {
-	return *(int16_t *)(_data + offset);
-}
-
-int32_t ScriptUserObject::ReadInt32(void * /*address*/, intptr_t offset) {
-	return *(int32_t *)(_data + offset);
-}
-
-float ScriptUserObject::ReadFloat(void * /*address*/, intptr_t offset) {
-	return *(float *)(_data + offset);
-}
-
-void ScriptUserObject::Write(void * /*address*/, intptr_t offset, const uint8_t *src, size_t size) {
-	memcpy((void *)(_data + offset), src, size);
-}
-
-void ScriptUserObject::WriteInt8(void * /*address*/, intptr_t offset, uint8_t val) {
-	*(uint8_t *)(_data + offset) = val;
-}
-
-void ScriptUserObject::WriteInt16(void * /*address*/, intptr_t offset, int16_t val) {
-	*(int16_t *)(_data + offset) = val;
-}
-
-void ScriptUserObject::WriteInt32(void * /*address*/, intptr_t offset, int32_t val) {
-	*(int32_t *)(_data + offset) = val;
-}
-
-void ScriptUserObject::WriteFloat(void * /*address*/, intptr_t offset, float val) {
-	*(float *)(_data + offset) = val;
+	uint8_t *new_data = new uint8_t[(data_sz - FileHeaderSz) + MemHeaderSz];
+	Header &hdr = reinterpret_cast<Header &>(*new_data);
+	in->Read(new_data + MemHeaderSz, data_sz - FileHeaderSz);
+	ccRegisterUnserializedObject(index, &new_data[MemHeaderSz], this);
 }
 
+ScriptUserObject globalDynamicStruct;
 
 // Allocates managed struct containing two ints: X and Y
 ScriptUserObject *ScriptStructHelpers::CreatePoint(int x, int y) {
-	ScriptUserObject *suo = ScriptUserObject::CreateManaged(sizeof(int32_t) * 2);
-	suo->WriteInt32(suo, 0, x);
-	suo->WriteInt32(suo, sizeof(int32_t), y);
-	return suo;
+	DynObjectRef ref = ScriptUserObject::Create(sizeof(int32_t) * 2);
+	ref.Mgr->WriteInt32(ref.Obj, 0, x);
+	ref.Mgr->WriteInt32(ref.Obj, sizeof(int32_t), y);
+	return static_cast<ScriptUserObject *>(ref.Obj);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index 1ebc65d9a8d..c192f1db1a9 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -21,7 +21,7 @@
 
 //=============================================================================
 //
-// Managed object, which size and contents are defined by user script
+// ScriptUserObject is a dynamic (managed) struct manager.
 //
 //=============================================================================
 
@@ -37,42 +37,36 @@ struct ScriptUserObject final : AGSCCDynamicObject {
 public:
 	static const char *TypeName;
 
+	struct Header {
+		uint32_t Size = 0u;
+		// NOTE: we use signed int for Size at the moment, because the managed
+		// object interface's Serialize() function requires the object to return
+		// negative value of size in case the provided buffer was not large
+		// enough. Since this interface is also a part of Plugin API, we would
+		// need more significant change to program before we could use different
+		// approach.
+	};
+
 	ScriptUserObject() = default;
+	~ScriptUserObject() = default;
 
-protected:
-	virtual ~ScriptUserObject();
+	inline static const Header &GetHeader(void *address) {
+		return reinterpret_cast<const Header &>(*(static_cast<uint8_t *>(address) - MemHeaderSz));
+	}
 
-public:
-	static ScriptUserObject *CreateManaged(size_t size);
-	void Create(const uint8_t *data, AGS::Shared::Stream *in, size_t size);
+	// Create managed struct object and return a pointer to the beginning of a buffer
+	static DynObjectRef Create(size_t size);
 
 	// return the type name of the object
 	const char *GetType() override;
 	int  Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
-	// Support for reading and writing object values by their relative offset
-	void	*GetFieldPtr(void *address, intptr_t offset) override;
-	void 	Read(void *address, intptr_t offset, uint8_t *dest, size_t size) override;
-	uint8_t ReadInt8(void *address, intptr_t offset) override;
-	int16_t ReadInt16(void *address, intptr_t offset) override;
-	int32_t ReadInt32(void *address, intptr_t offset) override;
-	float	ReadFloat(void *address, intptr_t offset) override;
-	void	Write(void *address, intptr_t offset, const uint8_t *src, size_t size) override;
-	void	WriteInt8(void *address, intptr_t offset, uint8_t val) override;
-	void	WriteInt16(void *address, intptr_t offset, int16_t val) override;
-	void	WriteInt32(void *address, intptr_t offset, int32_t val) override;
-	void	WriteFloat(void *address, intptr_t offset, float val) override;
-
 private:
-	// NOTE: we use signed int for Size at the moment, because the managed
-	// object interface's Serialize() function requires the object to return
-	// negative value of size in case the provided buffer was not large
-	// enough. Since this interface is also a part of Plugin API, we would
-	// need more significant change to program before we could use different
-	// approach.
-	int32_t  _size = 0;
-	uint8_t *_data = nullptr;
+	// The size of the array's header in memory, prepended to the element data
+	static const size_t MemHeaderSz = sizeof(Header);
+	// The size of the serialized header
+	static const size_t FileHeaderSz = sizeof(uint32_t) * 0; // no header serialized
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
@@ -81,6 +75,7 @@ private:
 	void Serialize(void *address, AGS::Shared::Stream *out) override;
 };
 
+extern ScriptUserObject globalDynamicStruct;
 
 // Helper functions for setting up custom managed structs based on ScriptUserObject.
 namespace ScriptStructHelpers {
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index 7391d8c3cbc..f6f5da74283 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -118,7 +118,7 @@ void *Dict_GetKeysAsArray(ScriptDictBase *dic) {
 	if (items.size() == 0)
 		return nullptr;
 	DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items);
-	return arr.second;
+	return arr.Obj;
 }
 
 void *Dict_GetValuesAsArray(ScriptDictBase *dic) {
@@ -127,7 +127,7 @@ void *Dict_GetValuesAsArray(ScriptDictBase *dic) {
 	if (items.size() == 0)
 		return nullptr;
 	DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items);
-	return arr.second;
+	return arr.Obj;
 }
 
 RuntimeScriptValue Sc_Dict_Create(const RuntimeScriptValue *params, int32_t param_count) {
@@ -247,7 +247,7 @@ void *Set_GetItemsAsArray(ScriptSetBase *set) {
 	if (items.size() == 0)
 		return nullptr;
 	DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items);
-	return arr.second;
+	return arr.Obj;
 }
 
 RuntimeScriptValue Sc_Set_Create(const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index db9ea91aa07..326c877d43a 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -246,11 +246,11 @@ int StrContains(const char *s1, const char *s2) {
 //=============================================================================
 
 const char *CreateNewScriptString(const String &fromText) {
-	return (const char *)CreateNewScriptStringObj(fromText.GetCStr(), true).second;
+	return (const char *)CreateNewScriptStringObj(fromText.GetCStr(), true).Obj;
 }
 
 const char *CreateNewScriptString(const char *fromText, bool reAllocate) {
-	return (const char *)CreateNewScriptStringObj(fromText, reAllocate).second;
+	return (const char *)CreateNewScriptStringObj(fromText, reAllocate).Obj;
 }
 
 DynObjectRef CreateNewScriptStringObj(const String &fromText) {
@@ -268,9 +268,9 @@ DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) {
 	int32_t handle = ccRegisterManagedObject(obj_ptr, str);
 	if (handle == 0) {
 		delete str;
-		return DynObjectRef(0, nullptr);
+		return DynObjectRef();
 	}
-	return DynObjectRef(handle, obj_ptr);
+	return DynObjectRef(handle, obj_ptr, str);
 }
 
 size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index c2d45c75601..ccb2b2b5965 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1329,8 +1329,8 @@ int ccInstance::Run(int32_t curpc) {
 				cc_error("invalid size for dynamic array; requested: %d, range: 1..%d", numElements, INT32_MAX);
 				return -1;
 			}
-			DynObjectRef ref = _GP(globalDynamicArray).Create(numElements, arg_elsize, arg_managed);
-			reg1.SetScriptObject(ref.second, &_GP(globalDynamicArray));
+			DynObjectRef ref = CCDynamicArray::Create(numElements, arg_elsize, arg_managed);
+			reg1.SetScriptObject(ref.Obj, &_GP(globalDynamicArray));
 			break;
 		}
 		case SCMD_NEWUSEROBJECT: {
@@ -1340,8 +1340,8 @@ int ccInstance::Run(int32_t curpc) {
 				cc_error("Invalid size for user object; requested: %d (or %d), range: 0..%d", arg_size, arg_size, INT_MAX);
 				return -1;
 			}
-			ScriptUserObject *suo = ScriptUserObject::CreateManaged(arg_size);
-			reg1.SetScriptObject(suo, suo);
+			DynObjectRef ref = ScriptUserObject::Create(arg_size);
+			reg1.SetScriptObject(ref.Obj, ref.Mgr);
 			break;
 		}
 		case SCMD_FADD: {
@@ -1435,7 +1435,7 @@ int ccInstance::Run(int32_t curpc) {
 			} else {
 				const char *ptr = reinterpret_cast<const char *>(reg1.GetDirectPtr());
 				reg1.SetScriptObject(
-					_G(stringClassImpl)->CreateString(ptr).second,
+					_G(stringClassImpl)->CreateString(ptr).Obj,
 					&_GP(myScriptStringImpl));
 			}
 			break;


Commit: 60c4c2d484cbb83b58a6460aa22678215e12e564
    https://github.com/scummvm/scummvm/commit/60c4c2d484cbb83b58a6460aa22678215e12e564
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix ResetDoOnceOnly registration

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 55a33f90861..e3ab8caf1c6 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1763,6 +1763,7 @@ void RegisterGameAPI() {
 		{"Game::InputBox^1", API_FN_PAIR(Game_InputBox)},
 		{"Game::SetSaveGameDirectory^1", API_FN_PAIR(Game_SetSaveGameDirectory)},
 		{"Game::StopSound^1", API_FN_PAIR(StopAllSounds)},
+		{"Game::ResetDoOnceOnly", API_FN_PAIR(Game_ResetDoOnceOnly)},
 		{"Game::get_CharacterCount", API_FN_PAIR(Game_GetCharacterCount)},
 		{"Game::get_DialogCount", API_FN_PAIR(Game_GetDialogCount)},
 		{"Game::get_FileName", API_FN_PAIR(Game_GetFileName)},


Commit: 09ffee9ed76763665f7748f987f26ee38d6c5552
    https://github.com/scummvm/scummvm/commit/09ffee9ed76763665f7748f987f26ee38d6c5552
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store ccInstances in shared_ptr, tidy freeing of instances (take two)

After the updates to cc_instance this should now work

>From upstream b1e4f75141ce88b3146f55ecd9d7a95378428072
and 49e8aa0b566dea4ce025f9530a9f2392dbe67a33

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index e3ab8caf1c6..61e82777d7c 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -373,36 +373,9 @@ void unload_game() {
 
 	// Free all script instances and script modules
 	ccInstance::FreeInstanceStack();
-	delete _G(gameinstFork);
-	delete _G(gameinst);
-	_G(gameinstFork) = nullptr;
-	_G(gameinst) = nullptr;
-	_GP(gamescript).reset();
-
-	delete _G(dialogScriptsInst);
-	_G(dialogScriptsInst) = nullptr;
-	_GP(dialogScriptsScript).reset();
-
-	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		delete _GP(moduleInstFork)[i];
-		delete _GP(moduleInst)[i];
-		_GP(scriptModules)[i].reset();
-	}
 
-	_GP(moduleInstFork).resize(0);
-	_GP(moduleInst).resize(0);
-	_GP(scriptModules).resize(0);
-	_GP(repExecAlways).moduleHasFunction.resize(0);
-	_GP(lateRepExecAlways).moduleHasFunction.resize(0);
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(0);
-	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(0);
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(0);
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(0);
-	_G(numScriptModules) = 0;
+	FreeAllScriptInstances();
+	FreeGlobalScripts();
 
 	_GP(charextra).clear();
 	_GP(mls).clear();
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 25a17509d73..ea7bf77d781 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -265,10 +265,7 @@ void unload_old_room() {
 	if (_G(croom) == nullptr) ;
 	else if (_G(roominst) != nullptr) {
 		save_room_data_segment();
-		delete _G(roominstFork);
-		delete _G(roominst);
-		_G(roominstFork) = nullptr;
-		_G(roominst) = nullptr;
+		FreeRoomScriptInstance();
 	} else _G(croom)->tsdatasize = 0;
 	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
 	_GP(play).bg_frame = 0;
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 7d387ff4b23..309b1d13da6 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -396,25 +396,6 @@ void InitGameResolution(GameSetupStruct &game, GameDataVersion data_ver) {
 	_GP(scsystem).viewport_height = game_to_data_coord(_GP(play).GetMainViewport().GetHeight());
 }
 
-void AllocScriptModules() {
-	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
-	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
-	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
-	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
-	for (auto &val : _GP(moduleRepExecAddr)) {
-		val.Invalidate();
-	}
-}
-
 HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver) {
 	GameSetupStruct &game = ents.Game;
 	const ScriptAPIVersion base_api = (ScriptAPIVersion)game.options[OPT_BASESCRIPTAPI];
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 96a01928b3a..40a2e7cad9a 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -356,26 +356,12 @@ void DoBeforeRestore(PreservedParams &pp) {
 
 	// preserve script data sizes and cleanup scripts
 	pp.GlScDataSize = _G(gameinst)->globaldatasize;
-	delete _G(gameinstFork);
-	delete _G(gameinst);
-	_G(gameinstFork) = nullptr;
-	_G(gameinst) = nullptr;
 	pp.ScMdDataSize.resize(_G(numScriptModules));
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		pp.ScMdDataSize[i] = _GP(moduleInst)[i]->globaldatasize;
-		delete _GP(moduleInstFork)[i];
-		delete _GP(moduleInst)[i];
-		_GP(moduleInstFork)[i] = nullptr;
-		_GP(moduleInst)[i] = nullptr;
 	}
 
-	delete _G(roominstFork);
-	delete _G(roominst);
-	_G(roominstFork) = nullptr;
-	_G(roominst) = nullptr;
-
-	delete _G(dialogScriptsInst);
-	_G(dialogScriptsInst) = nullptr;
+	FreeAllScriptInstances();
 
 	// reset saved room states
 	resetRoomStatuses();
@@ -481,6 +467,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 
 	update_gui_zorder();
 
+	AllocScriptModules();
 	if (create_global_script()) {
 		return new SavegameError(kSvgErr_GameObjectInitFailed,
 		                         String::FromFormat("Unable to recreate global script: %s", cc_get_error().ErrorString.GetCStr()));
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index ccb2b2b5965..c5737e6e226 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -211,18 +211,16 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-ccInstance *ccInstance::CreateFromScript(PScript scri) {
+PInstance ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	ccInstance *cinst = new ccInstance();
+	std::shared_ptr<ccInstance> cinst(new ccInstance());
 	if (!cinst->_Create(scri, joined)) {
-		delete cinst;
 		return nullptr;
 	}
-
 	return cinst;
 }
 
@@ -265,7 +263,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-ccInstance *ccInstance::Fork() {
+PInstance ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index d74d2e6f485..48b71f5b077 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -109,6 +109,10 @@ struct ScriptPosition {
 	int32_t         Line;
 };
 
+
+struct ccInstance;
+typedef std::shared_ptr<ccInstance> PInstance;
+
 // Running instance of the script
 struct ccInstance {
 public:
@@ -161,14 +165,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static ccInstance *CreateFromScript(PScript script);
-	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
+	static PInstance CreateFromScript(PScript script);
+	static PInstance CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	ccInstance *Fork();
+	PInstance Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index 72d9f4a7298..e5971ec5621 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -101,7 +101,7 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 
 void ExecutingScript::init() {
 	inst = nullptr;
-	forked = 0;
+	forked = false;
 	numanother = 0;
 	numPostScriptActions = 0;
 
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index c7f77af13e1..420c4880b82 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,7 +59,7 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	ccInstance *inst;
+	PInstance inst;
 	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
 	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
 	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
@@ -68,7 +68,7 @@ struct ExecutingScript {
 	int  numPostScriptActions;
 	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
 	int  numanother;
-	int8 forked;
+	bool forked;
 
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index e9233c0c991..d5476b206c0 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -90,18 +90,18 @@ void run_function_on_non_blocking_thread(NonBlockingScriptFunction *funcToRun) {
 	// run modules
 	// modules need a forkedinst for this to work
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i], funcToRun, funcToRun->moduleHasFunction[i]);
+		funcToRun->moduleHasFunction[i] = DoRunScriptFuncCantBlock(_GP(moduleInstFork)[i].get(), funcToRun, funcToRun->moduleHasFunction[i]);
 
 		if (room_changes_was != _GP(play).room_changes)
 			return;
 	}
 
-	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork), funcToRun, funcToRun->globalScriptHasFunction);
+	funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(_G(gameinstFork).get(), funcToRun, funcToRun->globalScriptHasFunction);
 
 	if (room_changes_was != _GP(play).room_changes || _G(abort_engine))
 		return;
 
-	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork), funcToRun, funcToRun->roomHasFunction);
+	funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(_G(roominstFork).get(), funcToRun, funcToRun->roomHasFunction);
 }
 
 int run_interaction_event(const ObjectEvent &obj_evt, Interaction *nint, int evnt, int chkAny, bool isInv) {
@@ -198,29 +198,31 @@ int create_global_script() {
 
 	ccSetOption(SCOPT_AUTOIMPORT, 1);
 
-	std::vector<ccInstance *> instances_for_resolving;
+	// NOTE: this function assumes that the module lists have their elements preallocated!
+
+	std::vector<PInstance> all_insts; // gather all to resolve exports below
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
-		_GP(moduleInst)[i] = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
-		if (_GP(moduleInst)[i] == nullptr)
+		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
+		if (!inst)
 			return kscript_create_error;
-		instances_for_resolving.push_back(_GP(moduleInst)[i]);
+		_GP(moduleInst)[i] = inst;
+		all_insts.push_back(inst);
 	}
 
 	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
-	if (_G(gameinst) == nullptr)
+	if (!_G(gameinst))
 		return kscript_create_error;
-	instances_for_resolving.push_back(_G(gameinst));
+	all_insts.push_back(_G(gameinst));
 
 	if (_GP(dialogScriptsScript) != nullptr) {
 		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
-		if (_G(dialogScriptsInst) == nullptr)
+		if (!_G(dialogScriptsInst))
 			return kscript_create_error;
-		instances_for_resolving.push_back(_G(dialogScriptsInst));
+		all_insts.push_back(_G(dialogScriptsInst));
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
-	for (size_t instance_idx = 0; instance_idx < instances_for_resolving.size(); instance_idx++) {
-		auto inst = instances_for_resolving[instance_idx];
+	for (auto &inst : all_insts) {
 		if (!inst->ResolveScriptImports(inst->instanceof.get()))
 			return kscript_create_error;
 		if (!inst->ResolveImportFixups(inst->instanceof.get()))
@@ -230,10 +232,11 @@ int create_global_script() {
 	// Create the forks for 'repeatedly_execute_always' after resolving
 	// because they copy their respective originals including the resolve information
 	for (size_t module_idx = 0; module_idx < _G(numScriptModules); module_idx++) {
-		_GP(moduleInstFork)[module_idx] = _GP(moduleInst)[module_idx]->Fork();
-		if (_GP(moduleInstFork)[module_idx] == nullptr)
+		auto fork = _GP(moduleInst)[module_idx]->Fork();
+		if (!fork)
 			return kscript_create_error;
 
+		_GP(moduleInstFork)[module_idx] = fork;
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
@@ -246,12 +249,12 @@ int create_global_script() {
 }
 
 void cancel_all_scripts() {
-	for (int aa = 0; aa < _G(num_scripts); aa++) {
-		if (_G(scripts)[aa].forked)
-			_G(scripts)[aa].inst->AbortAndDestroy();
-		else
-			_G(scripts)[aa].inst->Abort();
-		_G(scripts)[aa].numanother = 0;
+	for (int i = 0; i < _G(num_scripts); ++i) {
+		auto &sc = _G(scripts)[i];
+		if (sc.inst) {
+			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
+		}
+		sc.numanother = 0;
 	}
 	_G(num_scripts) = 0;
 	// in case the script is running on non-blocking thread (rep-exec-always etc)
@@ -260,7 +263,7 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
 		return _G(gameinst);
 	else if (sc_inst == kScInstRoom)
@@ -302,7 +305,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(ccInstance *sci, const char **tsname) {
+static int PrepareTextScript(PInstance sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -322,7 +325,7 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
 		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
 			quit("unable to fork instance for secondary script");
-		_G(scripts)[_G(num_scripts)].forked = 1;
+		_G(scripts)[_G(num_scripts)].forked = true;
 	}
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
@@ -336,7 +339,7 @@ static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -440,12 +443,70 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	ccInstance *sci = GetScriptInstanceByType(sc_inst);
+	PInstance sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
 }
 
+void AllocScriptModules() {
+	// NOTE: this preallocation possibly required to safeguard some algorithms
+	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
+	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
+	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(renderDialogOptionsFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.resize(_G(numScriptModules), true);
+	for (auto &val : _GP(moduleRepExecAddr)) {
+		val.Invalidate();
+	}
+}
+
+void FreeAllScriptInstances() {
+	FreeRoomScriptInstance();
+
+	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
+	// or bad things will happen; TODO: investigate and make this less fragile
+	_G(gameinstFork).reset();
+	_G(gameinst).reset();
+	_G(dialogScriptsInst).reset();
+	_GP(moduleInstFork).clear();
+	_GP(moduleInst).clear();
+}
+
+void FreeRoomScriptInstance() {
+	// NOTE: don't know why, but Forks must be deleted prior to primary inst,
+	// or bad things will happen; TODO: investigate and make this less fragile
+	_G(roominstFork).reset();
+	_G(roominst).reset();
+}
+
+void FreeGlobalScripts() {
+	_G(numScriptModules) = 0;
+
+	_GP(gamescript).reset();
+	_GP(scriptModules).clear();
+	_GP(dialogScriptsScript).reset();
+
+	_GP(repExecAlways).moduleHasFunction.clear();
+	_GP(lateRepExecAlways).moduleHasFunction.clear();
+	_GP(getDialogOptionsDimensionsFunc).moduleHasFunction.clear();
+	_GP(renderDialogOptionsFunc).moduleHasFunction.clear();
+	_GP(getDialogOptionUnderCursorFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionMouseClickHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionKeyPressHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionTextInputHandlerFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionRepExecFunc).moduleHasFunction.clear();
+	_GP(runDialogOptionCloseFunc).moduleHasFunction.clear();
+}
+
 String GetScriptName(ccInstance *sci) {
 	// TODO: have script name a ccScript's member?
 	// TODO: check script modules too?
@@ -480,8 +541,7 @@ void post_script_cleanup() {
 	ExecutingScript copyof;
 	if (_G(num_scripts) > 0) {
 		copyof = _G(scripts)[_G(num_scripts) - 1];
-		if (_G(scripts)[_G(num_scripts) - 1].forked)
-			delete _G(scripts)[_G(num_scripts) - 1].inst;
+		_G(scripts)[_G(num_scripts) - 1].inst.reset();
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
@@ -568,7 +628,6 @@ void post_script_cleanup() {
 		if ((_G(displayed_room) != old_room_number) || (_G(load_new_game)))
 			break;
 	}
-	copyof.numanother = 0;
 
 }
 
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index 826d8a2ea00..bf428e434c7 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -22,6 +22,7 @@
 #ifndef AGS_ENGINE_SCRIPT_SCRIPT_H
 #define AGS_ENGINE_SCRIPT_SCRIPT_H
 
+#include "common/std/memory.h"
 #include "common/std/vector.h"
 
 #include "ags/engine/script/cc_instance.h"
@@ -86,12 +87,12 @@ void    run_unhandled_event(const ObjectEvent &obj_evt, int evnt);
 int     create_global_script();
 void    cancel_all_scripts();
 
-ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
+PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -105,6 +106,16 @@ int     RunScriptFunctionInRoom(const char *tsname, size_t param_count = 0,
 int     RunScriptFunctionAuto(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 
+// Preallocates script module instances
+void	AllocScriptModules();
+// Delete all the script instance objects
+void	FreeAllScriptInstances();
+// Delete only the current room script instance
+void	FreeRoomScriptInstance();
+// Deletes all the global scripts and modules;
+// this frees all of the bytecode and runtime script memory.
+void	FreeGlobalScripts();
+
 String  GetScriptName(ccInstance *sci);
 
 //=============================================================================
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 0fee4f8a714..9ee6b72a463 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -370,8 +370,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<ccInstance *>();
-	_moduleInstFork = new std::vector<ccInstance *>();
+	_moduleInst = new std::vector<PInstance>();
+	_moduleInstFork = new std::vector<PInstance>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 76dfd1c5e9a..a8d447d3f19 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1255,9 +1255,11 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	ccInstance *_gameinst = nullptr, *_roominst = nullptr;
-	ccInstance *_dialogScriptsInst = nullptr;
-	ccInstance *_gameinstFork = nullptr, *_roominstFork = nullptr;
+	PInstance _gameinst;
+	PInstance _roominst;
+	PInstance _dialogScriptsInst;
+	PInstance _gameinstFork;
+	PInstance _roominstFork;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1279,8 +1281,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<ccInstance *> *_moduleInst;
-	std::vector<ccInstance *> *_moduleInstFork;
+	std::vector<PInstance> *_moduleInst;
+	std::vector<PInstance> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index eeec1e877a4..c9c0784e65c 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -605,7 +605,7 @@ int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int
 	if (_G(inside_script))
 		return -300;
 
-	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 815041ec6a4c2bbb7c0c9f21797f7ed3961ffaca
    https://github.com/scummvm/scummvm/commit/815041ec6a4c2bbb7c0c9f21797f7ed3961ffaca
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store game's ccInstances as unique_ptrs instead

The script handling part of the engine appeared to be too fragile and dependent on
 a precise timing of **deleting** a ccInstance.
For example, restoring a saved game may execute in the midst of the call to
RunScriptFunction(), at the "post script cleanup" stage, while there are still references
to ccInstance on the engine's call stack (and few other places). If anything prevents
 ccInstance's disposal at this moment, that would in turn lead to logical errors due to
  the game scripts "instances" counting. The most outstanding consequence is that the
   script exports are kept pointing into the deleted memory and not re-registered after the save is restored.

Overall, the script handling part of the engine would benefit from a major refactor,
 with a goal to make it safe and consistent, but this is not something I might do at the moment.

>From upstream 1f6abf4b678f77825c710d4ae18b57ff8f882c93

Changed paths:
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h
    engines/ags/engine/script/executing_script.cpp
    engines/ags/engine/script/executing_script.h
    engines/ags/engine/script/script.cpp
    engines/ags/engine/script/script.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 0a62ddead0d..debe9ec583f 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -175,7 +175,7 @@ int run_dialog_script(int dialogID, int offse, int optionIndex) {
 		char func_name[100];
 		snprintf(func_name, sizeof(func_name), "_run_dialog%d", dialogID);
 		RuntimeScriptValue params[]{ optionIndex };
-		RunScriptFunction(_G(dialogScriptsInst), func_name, 1, params);
+		RunScriptFunction(_G(dialogScriptsInst).get(), func_name, 1, params);
 		result = _G(dialogScriptsInst)->returnValue;
 	} else {
 		// old dialog format
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index f308b4242da..c28570fd491 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -60,7 +60,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 	int toret;
 
 	if (includeRoom && _G(roominst)) {
-		toret = RunScriptFunction(_G(roominst), tsname, numParams, params);
+		toret = RunScriptFunction(_G(roominst).get(), tsname, numParams, params);
 		if (_G(abort_engine))
 			return -1;
 
@@ -72,7 +72,7 @@ int run_claimable_event(const char *tsname, bool includeRoom, int numParams, con
 
 	// run script modules
 	for (auto &module_inst : _GP(moduleInst)) {
-		toret = RunScriptFunction(module_inst, tsname, numParams, params);
+		toret = RunScriptFunction(module_inst.get(), tsname, numParams, params);
 
 		if (_G(eventClaimed) == EVENT_CLAIMED) {
 			_G(eventClaimed) = eventClaimedOldValue;
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index ea7bf77d781..a9b2b0e14eb 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -680,7 +680,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 			StopMoving(cc);
 	}
 
-	_G(roominst) = nullptr;
+	_G(roominst).reset();
 	if (_G(debug_flags) & DBG_NOSCRIPT) ;
 	else if (_GP(thisroom).CompiledScript != nullptr) {
 		compile_room_script();
@@ -961,7 +961,7 @@ void check_new_room() {
 void compile_room_script() {
 	cc_clear_error();
 
-	_G(roominst) = ccInstance::CreateFromScript(_GP(thisroom).CompiledScript);
+	_G(roominst).reset(ccInstance::CreateFromScript(_GP(thisroom).CompiledScript));
 	if (cc_has_error() || (_G(roominst) == nullptr)) {
 		quitprintf("Unable to create local script:\n%s", cc_get_error().ErrorString.GetCStr());
 	}
@@ -972,7 +972,7 @@ void compile_room_script() {
 	if (!_G(roominst)->ResolveImportFixups(_G(roominst)->instanceof.get()))
 		quitprintf("Unable to resolve import fixups in room script:\n%s", cc_get_error().ErrorString.GetCStr());
 
-	_G(roominstFork) = _G(roominst)->Fork();
+	_G(roominstFork).reset(_G(roominst)->Fork());
 	if (_G(roominstFork) == nullptr)
 		quitprintf("Unable to create forked room instance:\n%s", cc_get_error().ErrorString.GetCStr());
 
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index c5737e6e226..57548184739 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -211,13 +211,13 @@ void ccInstance::FreeInstanceStack() {
 	_GP(InstThreads).clear();
 }
 
-PInstance ccInstance::CreateFromScript(PScript scri) {
+ccInstance *ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-PInstance ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
-	std::shared_ptr<ccInstance> cinst(new ccInstance());
+	ccInstance *cinst = new ccInstance();
 	if (!cinst->_Create(scri, joined)) {
 		return nullptr;
 	}
@@ -263,7 +263,7 @@ ccInstance::~ccInstance() {
 	Free();
 }
 
-PInstance ccInstance::Fork() {
+ccInstance *ccInstance::Fork() {
 	return CreateEx(instanceof, this);
 }
 
@@ -1715,6 +1715,8 @@ bool ccInstance::_Create(PScript scri, ccInstance *joined) {
 }
 
 void ccInstance::Free() {
+	// When the base script has no more "instances",
+	// remove all script exports
 	if (instanceof != nullptr) {
 		instanceof->instances--;
 		if (instanceof->instances == 0) {
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index 48b71f5b077..b99ccea90e9 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -110,9 +110,6 @@ struct ScriptPosition {
 };
 
 
-struct ccInstance;
-typedef std::shared_ptr<ccInstance> PInstance;
-
 // Running instance of the script
 struct ccInstance {
 public:
@@ -165,14 +162,14 @@ public:
 	// when destroying all script instances, e.g. on game quit.
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
-	static PInstance CreateFromScript(PScript script);
-	static PInstance CreateEx(PScript scri, ccInstance *joined);
+	static ccInstance *CreateFromScript(PScript script);
+	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
 	~ccInstance();
 	// Create a runnable instance of the same script, sharing global memory
-	PInstance Fork();
+	ccInstance *Fork();
 	// Specifies that when the current function returns to the script, it
 	// will stop and return from CallInstance
 	void    Abort();
diff --git a/engines/ags/engine/script/executing_script.cpp b/engines/ags/engine/script/executing_script.cpp
index e5971ec5621..58a20f974c2 100644
--- a/engines/ags/engine/script/executing_script.cpp
+++ b/engines/ags/engine/script/executing_script.cpp
@@ -99,20 +99,4 @@ void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_
 		script.Params[p] = params[p];
 }
 
-void ExecutingScript::init() {
-	inst = nullptr;
-	forked = false;
-	numanother = 0;
-	numPostScriptActions = 0;
-
-	memset(postScriptActions, 0, sizeof(postScriptActions));
-	memset(postScriptActionNames, 0, sizeof(postScriptActionNames));
-	memset(postScriptSaveSlotDescription, 0, sizeof(postScriptSaveSlotDescription));
-	memset(postScriptActionData, 0, sizeof(postScriptActionData));
-}
-
-ExecutingScript::ExecutingScript() {
-	init();
-}
-
 } // namespace AGS3
diff --git a/engines/ags/engine/script/executing_script.h b/engines/ags/engine/script/executing_script.h
index 420c4880b82..5e6ab762ea0 100644
--- a/engines/ags/engine/script/executing_script.h
+++ b/engines/ags/engine/script/executing_script.h
@@ -59,21 +59,21 @@ struct QueuedScript {
 };
 
 struct ExecutingScript {
-	PInstance inst;
-	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS];
-	const char *postScriptActionNames[MAX_QUEUED_ACTIONS];
-	ScriptPosition  postScriptActionPositions[MAX_QUEUED_ACTIONS];
-	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC];
-	int  postScriptActionData[MAX_QUEUED_ACTIONS];
-	int  numPostScriptActions;
-	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS];
-	int  numanother;
-	bool forked;
+	ccInstance *inst = nullptr;
+	// owned fork; CHECKME: this seem unused in the current engine
+	std::unique_ptr<ccInstance> forkedInst{};
+	PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS]{};
+	const char *postScriptActionNames[MAX_QUEUED_ACTIONS]{};
+	ScriptPosition postScriptActionPositions[MAX_QUEUED_ACTIONS]{};
+	char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][MAX_QUEUED_ACTION_DESC]{};
+	int postScriptActionData[MAX_QUEUED_ACTIONS]{};
+	int numPostScriptActions = 0;
+	QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS]{};
+	int numanother = 0;
 
+	ExecutingScript() = default;
 	int queue_action(PostScriptAction act, int data, const char *aname);
 	void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue *params);
-	void init();
-	ExecutingScript();
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index d5476b206c0..0b9b888f012 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -59,7 +59,7 @@ static char scfunctionname[MAX_FUNCTION_NAME_LEN + 1];
 int run_dialog_request(int parmtr) {
 	_GP(play).stop_dialog_at_end = DIALOG_RUNNING;
 	RuntimeScriptValue params[]{ parmtr };
-	RunScriptFunction(_G(gameinst), "dialog_request", 1, params);
+	RunScriptFunction(_G(gameinst).get(), "dialog_request", 1, params);
 
 	if (_GP(play).stop_dialog_at_end == DIALOG_STOP) {
 		_GP(play).stop_dialog_at_end = DIALOG_NONE;
@@ -200,25 +200,25 @@ int create_global_script() {
 
 	// NOTE: this function assumes that the module lists have their elements preallocated!
 
-	std::vector<PInstance> all_insts; // gather all to resolve exports below
+	std::vector<ccInstance *> all_insts; // gather all to resolve exports below
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		auto inst = ccInstance::CreateFromScript(_GP(scriptModules)[i]);
 		if (!inst)
 			return kscript_create_error;
-		_GP(moduleInst)[i] = inst;
+		_GP(moduleInst)[i].reset(inst);
 		all_insts.push_back(inst);
 	}
 
-	_G(gameinst) = ccInstance::CreateFromScript(_GP(gamescript));
+	_G(gameinst).reset(ccInstance::CreateFromScript(_GP(gamescript)));
 	if (!_G(gameinst))
 		return kscript_create_error;
-	all_insts.push_back(_G(gameinst));
+	all_insts.push_back(_G(gameinst).get());
 
-	if (_GP(dialogScriptsScript) != nullptr) {
-		_G(dialogScriptsInst) = ccInstance::CreateFromScript(_GP(dialogScriptsScript));
+	if (_GP(dialogScriptsScript)) {
+		_G(dialogScriptsInst).reset(ccInstance::CreateFromScript(_GP(dialogScriptsScript)));
 		if (!_G(dialogScriptsInst))
 			return kscript_create_error;
-		all_insts.push_back(_G(dialogScriptsInst));
+		all_insts.push_back(_G(dialogScriptsInst).get());
 	}
 
 	// Resolve the script imports after all the scripts have been loaded
@@ -236,11 +236,11 @@ int create_global_script() {
 		if (!fork)
 			return kscript_create_error;
 
-		_GP(moduleInstFork)[module_idx] = fork;
+		_GP(moduleInstFork)[module_idx].reset(fork);
 		_GP(moduleRepExecAddr)[module_idx] = _GP(moduleInst)[module_idx]->GetSymbolAddress(REP_EXEC_NAME);
 	}
 
-	_G(gameinstFork) = _G(gameinst)->Fork();
+	_G(gameinstFork).reset(_G(gameinst)->Fork());
 	if (_G(gameinstFork) == nullptr)
 		return kscript_create_error;
 
@@ -252,7 +252,7 @@ void cancel_all_scripts() {
 	for (int i = 0; i < _G(num_scripts); ++i) {
 		auto &sc = _G(scripts)[i];
 		if (sc.inst) {
-			(sc.forked) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
+			(sc.forkedInst) ? sc.inst->AbortAndDestroy() : sc.inst->Abort();
 		}
 		sc.numanother = 0;
 	}
@@ -263,11 +263,11 @@ void cancel_all_scripts() {
 		inst->Abort();
 }
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst) {
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) {
 	if (sc_inst == kScInstGame)
-		return _G(gameinst);
+		return _G(gameinst).get();
 	else if (sc_inst == kScInstRoom)
-		return _G(roominst);
+		return _G(roominst).get();
 	return nullptr;
 }
 
@@ -305,7 +305,7 @@ static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction
 	return (hasTheFunc);
 }
 
-static int PrepareTextScript(PInstance sci, const char **tsname) {
+static int PrepareTextScript(ccInstance *sci, const char **tsname) {
 	cc_clear_error();
 	// FIXME: try to make it so this function is not called with NULL sci
 	if (sci == nullptr) return -1;
@@ -317,16 +317,19 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 		cc_error("script is already in execution");
 		return -3;
 	}
-	_G(scripts)[_G(num_scripts)].init();
-	_G(scripts)[_G(num_scripts)].inst = sci;
+	ExecutingScript exscript;
 	// CHECKME: this conditional block will never run, because
 	// function would have quit earlier (deprecated functionality?)
 	if (sci->IsBeingRun()) {
-		_G(scripts)[_G(num_scripts)].inst = sci->Fork();
-		if (_G(scripts)[_G(num_scripts)].inst == nullptr)
+		auto fork = sci->Fork();
+		if (!fork)
 			quit("unable to fork instance for secondary script");
-		_G(scripts)[_G(num_scripts)].forked = true;
+		exscript.forkedInst.reset(fork);
+		exscript.inst = fork;
+	} else {
+		exscript.inst = sci;
 	}
+	_G(scripts)[_G(num_scripts)] = std::move(exscript);
 	_G(curscript) = &_G(scripts)[_G(num_scripts)];
 	_G(num_scripts)++;
 	if (_G(num_scripts) >= MAX_SCRIPT_AT_ONCE)
@@ -339,7 +342,7 @@ static int PrepareTextScript(PInstance sci, const char **tsname) {
 	return 0;
 }
 
-int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
+int RunScriptFunction(ccInstance *sci, const char *tsname, size_t numParam, const RuntimeScriptValue *params) {
 	int oldRestoreCount = _G(gameHasBeenRestored);
 	// TODO: research why this is really necessary, and refactor to avoid such hacks!
 	// First, save the current ccError state
@@ -385,8 +388,8 @@ int RunScriptFunction(PInstance sci, const char *tsname, size_t numParam, const
 
 void RunScriptFunctionInModules(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
 	for (size_t i = 0; i < _G(numScriptModules); ++i)
-		RunScriptFunction(_GP(moduleInst)[i], tsname, param_count, params);
-	RunScriptFunction(_G(gameinst), tsname, param_count, params);
+		RunScriptFunction(_GP(moduleInst)[i].get(), tsname, param_count, params);
+	RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
 }
 
 int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -394,7 +397,7 @@ int RunScriptFunctionInRoom(const char *tsname, size_t param_count, const Runtim
 	// identified by having no parameters;
 	// TODO: this is a hack, this should be defined either by function type, or as an arg
 	const bool strict_room_event = (param_count == 0);
-	int toret = RunScriptFunction(_G(roominst), tsname, param_count, params);
+	int toret = RunScriptFunction(_G(roominst).get(), tsname, param_count, params);
 	// If it's a obligatory room event, and return code means missing function - error
 	if (strict_room_event && (toret == -18))
 		quitprintf("RunScriptFunction: error %d (%s) trying to run '%s'   (Room %d)",
@@ -408,13 +411,13 @@ static int RunUnclaimableEvent(const char *tsname) {
 	const int restore_game_count_was = _G(gameHasBeenRestored);
 	for (size_t i = 0; i < _G(numScriptModules); ++i) {
 		if (!_GP(moduleRepExecAddr)[i].IsNull())
-			RunScriptFunction(_GP(moduleInst)[i], tsname);
+			RunScriptFunction(_GP(moduleInst)[i].get(), tsname);
 		// Break on room change or save restoration
 		if ((room_changes_was != _GP(play).room_changes) ||
 			(restore_game_count_was != _G(gameHasBeenRestored)))
 			return 0;
 	}
-	return RunScriptFunction(_G(gameinst), tsname);
+	return RunScriptFunction(_G(gameinst).get(), tsname);
 }
 
 static int RunClaimableEvent(const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -424,7 +427,7 @@ static int RunClaimableEvent(const char *tsname, size_t param_count, const Runti
 	// Break on event claim
 	if (eventWasClaimed)
 		return toret;
-	return RunScriptFunction(_G(gameinst), tsname, param_count, params);
+	return RunScriptFunction(_G(gameinst).get(), tsname, param_count, params);
 }
 
 int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t param_count, const RuntimeScriptValue *params) {
@@ -443,7 +446,7 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 		return RunClaimableEvent(tsname, param_count, params);
 	}
 	// Else run on the single chosen script instance
-	PInstance sci = GetScriptInstanceByType(sc_inst);
+	ccInstance *sci = GetScriptInstanceByType(sc_inst);
 	if (!sci)
 		return 0;
 	return RunScriptFunction(sci, tsname, param_count, params);
@@ -451,8 +454,8 @@ int RunScriptFunctionAuto(ScriptInstType sc_inst, const char *tsname, size_t par
 
 void AllocScriptModules() {
 	// NOTE: this preallocation possibly required to safeguard some algorithms
-	_GP(moduleInst).resize(_G(numScriptModules), nullptr);
-	_GP(moduleInstFork).resize(_G(numScriptModules), nullptr);
+	_GP(moduleInst).resize(_G(numScriptModules));
+	_GP(moduleInstFork).resize(_G(numScriptModules));
 	_GP(moduleRepExecAddr).resize(_G(numScriptModules));
 	_GP(repExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
 	_GP(lateRepExecAlways).moduleHasFunction.resize(_G(numScriptModules), true);
@@ -539,9 +542,9 @@ void post_script_cleanup() {
 		quit(cc_get_error().ErrorString);
 
 	ExecutingScript copyof;
-	if (_G(num_scripts) > 0) {
-		copyof = _G(scripts)[_G(num_scripts) - 1];
-		_G(scripts)[_G(num_scripts) - 1].inst.reset();
+	if (_G(num_scripts) > 0) {  // save until the end of function
+		copyof = std::move(_G(scripts)[_G(num_scripts) - 1]);
+		copyof.forkedInst.reset(); // don't need it further
 		_G(num_scripts)--;
 	}
 	_G(inside_script)--;
diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h
index bf428e434c7..47fb910df85 100644
--- a/engines/ags/engine/script/script.h
+++ b/engines/ags/engine/script/script.h
@@ -87,12 +87,12 @@ void    run_unhandled_event(const ObjectEvent &obj_evt, int evnt);
 int     create_global_script();
 void    cancel_all_scripts();
 
-PInstance GetScriptInstanceByType(ScriptInstType sc_inst);
+ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst);
 // Queues a script function to be run either called by the engine or from another script
 void    QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Try to run a script function on a given script instance
-int     RunScriptFunction(PInstance sci, const char *tsname, size_t param_count = 0,
+int     RunScriptFunction(ccInstance *sci, const char *tsname, size_t param_count = 0,
 	const RuntimeScriptValue *params = nullptr);
 // Run a script function in all the regular script modules, in order, where available
 // includes globalscript, but not the current room script.
@@ -135,6 +135,19 @@ void    can_run_delayed_command();
 bool    get_script_position(ScriptPosition &script_pos);
 String  cc_get_callstack(int max_lines = INT_MAX);
 
+// [ikm] we keep ccInstances saved in unique_ptrs globally for now
+// (e.g. as opposed to shared_ptrs), because the script handling part of the
+// engine is quite fragile and prone to errors whenever the instance is not
+// **deleted** in precise time. This is related to:
+// - ccScript's "instances" counting, which affects script exports reg/unreg;
+// - loadedInstances array.
+// One of the examples is the save restoration, that may occur in the midst
+// of a post-script cleanup process, whilst the engine's stack still has
+// references to the ccInstances that are going to be deleted on cleanup.
+// Ideally, this part of the engine should be refactored awhole with a goal
+// to make it safe and consistent.
+typedef std::unique_ptr<ccInstance> UInstance;
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 9ee6b72a463..1c00fd2515b 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -370,8 +370,8 @@ Globals::Globals() {
 	_runDialogOptionCloseFunc = new NonBlockingScriptFunction("dialog_options_close", 1);
 	_scsystem = new ScriptSystem();
 	_scriptModules = new std::vector<PScript>();
-	_moduleInst = new std::vector<PInstance>();
-	_moduleInstFork = new std::vector<PInstance>();
+	_moduleInst = new std::vector<UInstance>();
+	_moduleInstFork = new std::vector<UInstance>();
 	_moduleRepExecAddr = new std::vector<RuntimeScriptValue>();
 
 	// script_runtime.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index a8d447d3f19..0d80a18bdbd 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1255,11 +1255,11 @@ public:
 
 	PScript *_gamescript;
 	PScript *_dialogScriptsScript;
-	PInstance _gameinst;
-	PInstance _roominst;
-	PInstance _dialogScriptsInst;
-	PInstance _gameinstFork;
-	PInstance _roominstFork;
+	UInstance _gameinst;
+	UInstance _roominst;
+	UInstance _dialogScriptsInst;
+	UInstance _gameinstFork;
+	UInstance _roominstFork;
 
 	int _num_scripts = 0;
 	int _post_script_cleanup_stack = 0;
@@ -1281,8 +1281,8 @@ public:
 	ScriptSystem *_scsystem;
 
 	std::vector<PScript> *_scriptModules;
-	std::vector<PInstance> *_moduleInst;
-	std::vector<PInstance> *_moduleInstFork;
+	std::vector<UInstance> *_moduleInst;
+	std::vector<UInstance> *_moduleInstFork;
 	std::vector<RuntimeScriptValue> *_moduleRepExecAddr;
 	size_t _numScriptModules = 0;
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index c9c0784e65c..6836f167b3c 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -596,16 +596,18 @@ int IAGSEngine::IsSpriteAlphaBlended(int32 slot) {
 void IAGSEngine::DisableSound() {
 	shutdown_sound();
 }
+
 int IAGSEngine::CanRunScriptFunctionNow() {
 	if (_G(inside_script))
 		return 0;
 	return 1;
 }
+
 int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2, long arg3) {
 	if (_G(inside_script))
 		return -300;
 
-	PInstance toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
+	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
 
 	RuntimeScriptValue params[]{
 		   RuntimeScriptValue().SetPluginArgument(arg1),


Commit: 961b06447f50493422c7347a3d548cff42809133
    https://github.com/scummvm/scummvm/commit/961b06447f50493422c7347a3d548cff42809133
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use internal interface alias for plugin's object readers

The idea is to have similar interface internally in the engine, without having to refer to the
 plugin's API; at least to keep things consistent with the rest of interfaces for now.
 (The whole engine interface system may need to be reorganized.)

* Renamed existing ICCObjectReader to ICCObjectCollectionReader, an interface that supports
 multiple object types.
* ICCObjectReader is now an internal engine's alias for plugin's IAGSManagedObjectReader
(but it may be used for the internal purposes too if a need arises).
* Moved PluginObjectReader declaration, save 1 small h/cpp pair.
* Store pluginReaders in a std::vector, remove MAX_PLUGIN_OBJECT_READERS as redundant.

>From upstream 49d037671e4daaa909a7a98e19456e0f29f8e3cb

Changed paths:
  R engines/ags/plugins/plugin_object_reader.cpp
  R engines/ags/plugins/plugin_object_reader.h
    engines/ags/engine/ac/dynobj/cc_script_object.h
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/cc_serializer.h
    engines/ags/engine/ac/dynobj/dynobj_manager.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/engine/ac/dynobj/managed_object_pool.h
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/runtime_defines.h
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/module.mk
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/plugin_engine.h


diff --git a/engines/ags/engine/ac/dynobj/cc_script_object.h b/engines/ags/engine/ac/dynobj/cc_script_object.h
index ebdc083b0f8..40b6c3b43ea 100644
--- a/engines/ags/engine/ac/dynobj/cc_script_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_script_object.h
@@ -105,12 +105,18 @@ protected:
 	virtual ~IScriptObject() {}
 };
 
-// The interface of a script object deserializer.
+// The interface of a script objects deserializer that handles multiple types.
+struct ICCObjectCollectionReader {
+	virtual ~ICCObjectCollectionReader() {}
+	// TODO: pass savegame format version
+	virtual void Unserialize(int32_t handle, const char *objectType, const char *serializedData, int dataSize) = 0;
+};
+
+// The interface of a script objects deserializer that handles a single type.
 // WARNING: a part of the plugin API.
 struct ICCObjectReader {
 	virtual ~ICCObjectReader() {}
-	// TODO: pass savegame format version
-	virtual void Unserialize(int32_t handle, const char *objectType, const char *serializedData, int dataSize) = 0;
+	virtual void Unserialize(int32_t handle, const char *serializedData, int dataSize) = 0;
 };
 
 // The interface of a dynamic String allocator.
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index 4a5aced3516..22172fc19f6 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -32,8 +32,7 @@
 #include "ags/engine/ac/dynobj/script_viewport.h"
 #include "ags/engine/ac/game.h"
 #include "ags/engine/debugging/debug_log.h"
-#include "ags/plugins/ags_plugin.h"
-#include "ags/plugins/plugin_object_reader.h"
+#include "ags/plugins/plugin_engine.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
@@ -118,11 +117,15 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 		Viewport_Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "Camera2") == 0) {
 		Camera_Unserialize(index, &mems, data_sz);
-	} else if (!unserialize_audio_script_object(index, objectType, &mems, data_sz)) {
+	} else if (strcmp(objectType, "AudioChannel") == 0) {
+		_GP(ccDynamicAudio).Unserialize(index, &mems, data_sz);
+	} else if (strcmp(objectType, "AudioClip") == 0) {
+		_GP(ccDynamicAudioClip).Unserialize(index, &mems, data_sz);
+	} else {
 		// check if the type is read by a plugin
-		for (int ii = 0; ii < _G(numPluginReaders); ii++) {
-			if (strcmp(objectType, _G(pluginReaders)[ii].type) == 0) {
-				_G(pluginReaders)[ii].reader->Unserialize(index, serializedData, dataSize);
+		for (const auto &pr : _GP(pluginReaders)) {
+			if (pr.Type == objectType) {
+				pr.Reader->Unserialize(index, serializedData, dataSize);
 				return;
 			}
 		}
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.h b/engines/ags/engine/ac/dynobj/cc_serializer.h
index 3c7d2bbfb03..1f2cb973b19 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.h
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.h
@@ -26,7 +26,7 @@
 
 namespace AGS3 {
 
-struct AGSDeSerializer : ICCObjectReader {
+struct AGSDeSerializer : ICCObjectCollectionReader {
 	void Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) override;
 };
 
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index 83d0d2d0551..40a19203e16 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -83,7 +83,7 @@ void ccSerializeAllObjects(Stream *out) {
 }
 
 // un-serialise all objects (will remove all currently registered ones)
-int ccUnserializeAllObjects(Stream *in, ICCObjectReader *callback) {
+int ccUnserializeAllObjects(Stream *in, ICCObjectCollectionReader *callback) {
 	return _GP(pool).ReadFromDisk(in, callback);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index be8897155b3..ccc192d3dfe 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -59,7 +59,7 @@ extern void  ccUnregisterAllObjects();
 // serialize all objects to disk
 extern void  ccSerializeAllObjects(Shared::Stream *out);
 // un-serialise all objects (will remove all currently registered ones)
-extern int   ccUnserializeAllObjects(Shared::Stream *in, ICCObjectReader *callback);
+extern int   ccUnserializeAllObjects(Shared::Stream *in, ICCObjectCollectionReader *callback);
 // dispose the object if RefCount==0
 extern void  ccAttemptDisposeObject(int32_t handle);
 // translate between object handles and memory addresses
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index 326c6798f73..f49de17ed3f 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -252,7 +252,7 @@ void ManagedObjectPool::WriteToDisk(Stream *out) {
 	}
 }
 
-int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) {
+int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectCollectionReader *reader) {
 	if (in->ReadInt32() != OBJECT_CACHE_MAGIC_NUMBER) {
 		cc_error("Data was not written by ccSeralize");
 		return -1;
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.h b/engines/ags/engine/ac/dynobj/managed_object_pool.h
index 25dabf2be07..1186a17e4d3 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.h
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.h
@@ -94,7 +94,7 @@ public:
 	int AddObject(void *address, IScriptObject *callback, ScriptValueType obj_type);
 	int AddUnserializedObject(void *address, IScriptObject *callback, ScriptValueType obj_type, int handle);
 	void WriteToDisk(Shared::Stream *out);
-	int ReadFromDisk(Shared::Stream *in, ICCObjectReader *reader);
+	int ReadFromDisk(Shared::Stream *in, ICCObjectCollectionReader *reader);
 	void reset();
 	ManagedObjectPool();
 
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 61e82777d7c..5cf5490ae21 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1317,17 +1317,6 @@ void get_message_text(int msnum, char *buffer, char giveErr) {
 	replace_tokens(get_translation(_GP(thisroom).Messages[msnum].GetCStr()), buffer, maxlen);
 }
 
-bool unserialize_audio_script_object(int index, const char *objectType, Stream *in, size_t data_sz) {
-	if (strcmp(objectType, "AudioChannel") == 0) {
-		_GP(ccDynamicAudio).Unserialize(index, in, data_sz);
-	} else if (strcmp(objectType, "AudioClip") == 0) {
-		_GP(ccDynamicAudioClip).Unserialize(index, in, data_sz);
-	} else {
-		return false;
-	}
-	return true;
-}
-
 void game_sprite_updated(int sprnum) {
 	// update the shared texture (if exists)
 	_G(gfxDriver)->UpdateSharedDDB(sprnum, _GP(spriteset)[sprnum], (_GP(game).SpriteInfos[sprnum].Flags & SPF_ALPHACHANNEL) != 0, false);
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index f6e83df25e1..b0ad46316c8 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -208,8 +208,6 @@ void replace_tokens(const char *srcmes, char *destm, int maxlen = 99999);
 const char *get_global_message(int msnum);
 void get_message_text(int msnum, char *buffer, char giveErr = 1);
 
-bool unserialize_audio_script_object(int index, const char *objectType, AGS::Shared::Stream *in, size_t data_sz);
-
 // Notifies the game objects that certain sprite was updated.
 // This make them update their render states, caches, and so on.
 void game_sprite_updated(int sprnum);
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index a47f8f9a31d..1964c005019 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -159,8 +159,6 @@ const int LegacyRoomVolumeFactor = 30;
 #define TURNING_AROUND     1000
 #define TURNING_BACKWARDS 10000
 
-#define MAX_PLUGIN_OBJECT_READERS 50
-
 #define LOCTYPE_HOTSPOT 1
 #define LOCTYPE_CHAR 2
 #define LOCTYPE_OBJ  3
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 1c00fd2515b..5aba5353cb5 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -98,7 +98,7 @@
 #include "ags/engine/script/system_imports.h"
 #include "common/std/limits.h"
 #include "ags/plugins/ags_plugin.h"
-#include "ags/plugins/plugin_object_reader.h"
+#include "ags/plugins/plugin_engine.h"
 #include "ags/plugins/core/core.h"
 #include "common/file.h"
 
@@ -119,6 +119,7 @@ Globals::Globals() {
 
 	// ags_plugin.cpp globals
 	_glVirtualScreenWrap = new AGS::Shared::Bitmap();
+	_pluginReaders = new std::vector<PluginObjectReader>();
 
 	// cc_ags_dynamic_object.cpp globals
 	_GlobalStaticManager = new AGSCCStaticObject();
@@ -341,7 +342,6 @@ Globals::Globals() {
 	_plugins->reserve(MAXPLUGINS);
 
 	// plugin_object_reader.cpp globals
-	_pluginReaders = new PluginObjectReader[MAX_PLUGIN_OBJECT_READERS];
 
 	// room.cpp globals
 	_rgb_table = new RGB_MAP();
@@ -403,6 +403,7 @@ Globals::~Globals() {
 
 	// ags_plugin.cpp globals
 	delete _glVirtualScreenWrap;
+	delete _pluginReaders;
 
 	// ags_static_object.cpp globals
 	delete _GlobalStaticManager;
@@ -596,7 +597,6 @@ Globals::~Globals() {
 	delete _plugins;
 
 	// plugin_object_reader.cpp globals
-	delete[] _pluginReaders;
 
 	// room.cpp globals
 	delete _rgb_table;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 0d80a18bdbd..552647f4949 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -295,6 +295,7 @@ public:
 	 */
 
 	AGS::Shared::Bitmap *_glVirtualScreenWrap;
+	std::vector<PluginObjectReader> *_pluginReaders;
 
 	/**@}*/
 
@@ -1185,9 +1186,6 @@ public:
 	 * @{
 	 */
 
-	PluginObjectReader *_pluginReaders;
-	int _numPluginReaders = 0;
-
 	/**@}*/
 
 	/**
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index d1d230b5403..845e77b1bdf 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -284,7 +284,6 @@ MODULE_OBJS = \
 	engine/script/system_imports.o \
 	plugins/ags_plugin.o \
 	plugins/plugin_base.o \
-	plugins/plugin_object_reader.o \
 	plugins/core/core.o \
 	plugins/core/audio_channel.o \
 	plugins/core/audio_clip.o \
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 6836f167b3c..977c04cc420 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -65,7 +65,6 @@
 #include "ags/engine/main/engine.h"
 #include "ags/engine/media/audio/audio_system.h"
 #include "ags/plugins/plugin_engine.h"
-#include "ags/plugins/plugin_object_reader.h"
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/engine/script/script.h"
 #include "ags/engine/script/script_runtime.h"
@@ -650,20 +649,15 @@ int IAGSEngine::RegisterManagedObject(void *object, IAGSScriptManagedObject *cal
 }
 
 void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader) {
-	if (_G(numPluginReaders) >= MAX_PLUGIN_OBJECT_READERS)
-		quit("Plugin error: IAGSEngine::AddObjectReader: Too many object readers added");
-
 	if ((typeName == nullptr) || (typeName[0] == 0))
 		quit("Plugin error: IAGSEngine::AddObjectReader: invalid name for type");
 
-	for (int ii = 0; ii < _G(numPluginReaders); ii++) {
-		if (strcmp(_G(pluginReaders)[ii].type, typeName) == 0)
-			quitprintf("Plugin error: IAGSEngine::AddObjectReader: type '%s' has been registered already", typeName);
+	for (const auto &pr : _GP(pluginReaders)) {
+		if (pr.Type == typeName)
+			quitprintf("Plugin error: IAGSEngine::AddObjectReader: type '%s' has been registered already", pr.Type.GetCStr());
 	}
 
-	_G(pluginReaders)[_G(numPluginReaders)].reader = reader;
-	_G(pluginReaders)[_G(numPluginReaders)].type = typeName;
-	_G(numPluginReaders)++;
+	_GP(pluginReaders).push_back(PluginObjectReader(typeName, reinterpret_cast<ICCObjectReader*>(reader)));
 }
 
 void IAGSEngine::RegisterUnserializedObject(int key, void *object, IAGSScriptManagedObject *callback) {
@@ -848,8 +842,7 @@ Engine::GameInitError pl_register_plugins(const std::vector<PluginInfo> &infos)
 		// AGS Editor currently saves plugin names in game data with
 		// ".dll" extension appended; we need to take care of that
 		const String name_ext = ".dll";
-		if (name.GetLength() <= name_ext.GetLength() || name.GetLength() > PLUGIN_FILENAME_MAX + name_ext.GetLength() ||
-		        name.CompareRightNoCase(name_ext, name_ext.GetLength())) {
+		if (name.GetLength() <= name_ext.GetLength() || name.CompareRightNoCase(name_ext, name_ext.GetLength())) {
 			return kGameInitErr_PluginNameInvalid;
 		}
 		// remove ".dll" from plugin's name
diff --git a/engines/ags/plugins/plugin_engine.h b/engines/ags/plugins/plugin_engine.h
index ed710583fdf..0b0d5a91d94 100644
--- a/engines/ags/plugins/plugin_engine.h
+++ b/engines/ags/plugins/plugin_engine.h
@@ -31,6 +31,7 @@
 #include "common/std/vector.h"
 #include "ags/engine/game/game_init.h"
 #include "ags/shared/game/plugin_info.h"
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
@@ -43,6 +44,19 @@ class Stream;
 
 using namespace AGS; // FIXME later
 
+
+//
+// PluginObjectReader is a managed object unserializer registered by plugin.
+//
+struct PluginObjectReader {
+	const Shared::String Type;
+	ICCObjectReader *const Reader = nullptr;
+
+	PluginObjectReader(const Shared::String &type, ICCObjectReader *reader)
+		: Type(type), Reader(reader) {}
+};
+
+
 void pl_stop_plugins();
 void pl_startup_plugins();
 NumberPtr pl_run_plugin_hooks(int event, NumberPtr data);
diff --git a/engines/ags/plugins/plugin_object_reader.cpp b/engines/ags/plugins/plugin_object_reader.cpp
deleted file mode 100644
index 4e24933c83c..00000000000
--- a/engines/ags/plugins/plugin_object_reader.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/* 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 "ags/plugins/plugin_object_reader.h"
-#include "ags/engine/ac/runtime_defines.h"
-
-namespace AGS3 {
-
-} // namespace AGS3
diff --git a/engines/ags/plugins/plugin_object_reader.h b/engines/ags/plugins/plugin_object_reader.h
deleted file mode 100644
index 4fdfb2d071d..00000000000
--- a/engines/ags/plugins/plugin_object_reader.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* 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 AGS_PLUGINS_PLUGIN_OBJECT_READER_H
-#define AGS_PLUGINS_PLUGIN_OBJECT_READER_H
-
-namespace AGS3 {
-
-class IAGSManagedObjectReader;
-
-struct PluginObjectReader {
-	IAGSManagedObjectReader *reader;
-	const char *type;
-};
-
-} // namespace AGS3
-
-#endif


Commit: ee82074a477684eed843c5266cac03e9f41d47ef
    https://github.com/scummvm/scummvm/commit/ee82074a477684eed843c5266cac03e9f41d47ef
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: further tidy up around agsplugin.h/cpp

* Moved plugin API event constants to a separate header agsplugin_evts.h.
Engine should not include whole plugin API, this is unnecessary, and may
 create naming conflicts.
* Moved SimulateMouseClick to mouse.cpp.
* Minor cleanup in agsplugin.cpp

>From upstream 510293c1fd277d853544534423ceea27b8ae3e2d

Changed paths:
  A engines/ags/plugins/ags_plugin_evts.h
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/ac/global_translation.cpp
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/mouse.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/screen.cpp
    engines/ags/engine/ac/sprite.cpp
    engines/ags/engine/ac/sys_events.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/globals.h
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/ags_plugin.h
    engines/ags/plugins/plugin_engine.h
    engines/ags/shared/ac/keycode.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 0d4c2a56ff3..693812fd2b8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -61,7 +61,7 @@
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_object.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/gfx/gfx_util.h"
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index c28570fd491..d749a402687 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -34,7 +34,7 @@
 #include "ags/engine/main/game_run.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/gfx/bitmap.h"
diff --git a/engines/ags/engine/ac/global_translation.cpp b/engines/ags/engine/ac/global_translation.cpp
index 578e285f158..7adcc725331 100644
--- a/engines/ags/engine/ac/global_translation.cpp
+++ b/engines/ags/engine/ac/global_translation.cpp
@@ -26,7 +26,7 @@
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/translation.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/util/memory.h"
 #include "ags/engine/ac/string.h"
diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 63b5d1a9e1c..10a54f27efb 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -312,6 +312,10 @@ int IsModeEnabled(int which) {
 	       (_GP(game).mcurs[which].flags & MCF_DISABLED) == 0;
 }
 
+void SimulateMouseClick(int button_id) {
+	_G(simulatedClick) = static_cast<eAGSMouseButton>(button_id);
+}
+
 void Mouse_EnableControl(bool on) {
 	bool should_control_mouse =
 	    _GP(usetup).mouse_ctrl_when == kMouseCtrl_Always ||
@@ -553,7 +557,7 @@ RuntimeScriptValue Sc_Mouse_SetVisible(const RuntimeScriptValue *params, int32_t
 }
 
 RuntimeScriptValue Sc_Mouse_Click(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT(PluginSimulateMouseClick);
+	API_SCALL_VOID_PINT(SimulateMouseClick);
 }
 
 RuntimeScriptValue Sc_Mouse_GetControlEnabled(const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/mouse.h b/engines/ags/engine/ac/mouse.h
index 4e443bcba20..858cc13122c 100644
--- a/engines/ags/engine/ac/mouse.h
+++ b/engines/ags/engine/ac/mouse.h
@@ -56,6 +56,7 @@ void disable_cursor_mode(int modd);
 
 // Try to enable or disable mouse speed control by the engine
 void Mouse_EnableControl(bool on);
+void SimulateMouseClick(int button_id);
 
 //=============================================================================
 
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index a9b2b0e14eb..8ef93a15bab 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -59,7 +59,7 @@
 #include "ags/shared/debugging/out.h"
 #include "ags/shared/game/room_version.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/script.h"
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index a1737bb61a3..d22bfbadb31 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -31,7 +31,7 @@
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gfx/graphics_driver.h"
diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp
index 938b88534b2..a6716d075f6 100644
--- a/engines/ags/engine/ac/sprite.cpp
+++ b/engines/ags/engine/ac/sprite.cpp
@@ -25,7 +25,7 @@
 #include "ags/engine/ac/sprite.h"
 #include "ags/engine/ac/system.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/shared/gfx/bitmap.h"
diff --git a/engines/ags/engine/ac/sys_events.cpp b/engines/ags/engine/ac/sys_events.cpp
index 15607380a52..0b120ca8f59 100644
--- a/engines/ags/engine/ac/sys_events.cpp
+++ b/engines/ags/engine/ac/sys_events.cpp
@@ -184,9 +184,9 @@ bool ags_misbuttondown(eAGSMouseButton but) {
 }
 
 eAGSMouseButton ags_mgetbutton() {
-	if (_G(pluginSimulatedClick) > kMouseNone) {
-		eAGSMouseButton mbut = _G(pluginSimulatedClick);
-		_G(pluginSimulatedClick) = kMouseNone;
+	if (_G(simulatedClick) > kMouseNone) {
+		eAGSMouseButton mbut = _G(simulatedClick);
+		_G(simulatedClick) = kMouseNone;
 		return mbut;
 	}
 	return mgetbutton();
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 40a2e7cad9a..016688a9d81 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -57,7 +57,7 @@
 #include "ags/engine/main/update.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/engine/platform/base/sys_main.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/script/cc_common.h"
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index b2373684b35..e3f4c27777b 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -66,7 +66,7 @@
 #include "ags/engine/main/update.h"
 #include "ags/engine/media/audio/audio_system.h"
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/plugins/ags_plugin.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/engine/script/script_runtime.h"
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 552647f4949..529a7ed7258 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1339,7 +1339,7 @@ public:
 	long _pl_file_handle = -1;
 	AGS::Shared::Stream *_pl_file_stream = nullptr;
 
-	eAGSMouseButton _pluginSimulatedClick = kMouseNone;
+	eAGSMouseButton _simulatedClick = kMouseNone;
 	int _mouse_z_was = 0;
 
 	/**@}*/
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 977c04cc420..a132e9997e3 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -25,7 +25,6 @@
 #include "ags/lib/allegro.h"
 #include "common/std/vector.h"
 #include "ags/shared/core/platform.h"
-#include "ags/plugins/ags_plugin.h"
 #include "ags/plugins/core/core.h"
 #include "ags/shared/ac/common.h"
 #include "ags/shared/ac/view.h"
@@ -77,10 +76,16 @@
 #include "ags/shared/util/wgt2_allg.h"
 #include "ags/globals.h"
 
+// hide internal constants conflicting with plugin API
+#undef OBJF_NOINTERACT
+#undef OBJF_NOWALKBEHINDS
+
+#include "ags/plugins/ags_plugin.h"
+
+
 namespace AGS3 {
 
 using namespace AGS::Shared;
-using namespace AGS::Shared::Memory;
 using namespace AGS::Engine;
 
 const int PLUGIN_API_VERSION = 26;
@@ -89,7 +94,7 @@ const int PLUGIN_API_VERSION = 26;
 // we can reuse the same handle.
 
 void PluginSimulateMouseClick(int pluginButtonID) {
-	_G(pluginSimulatedClick) = static_cast<eAGSMouseButton>(pluginButtonID);
+	_G(simulatedClick) = static_cast<eAGSMouseButton>(pluginButtonID);
 }
 
 void IAGSEngine::AbortGame(const char *reason) {
@@ -698,7 +703,7 @@ void IAGSEngine::SetMousePosition(int32 x, int32 y) {
 }
 
 void IAGSEngine::SimulateMouseClick(int32 button) {
-	PluginSimulateMouseClick(button);
+	SimulateMouseClick(button);
 }
 
 int IAGSEngine::GetMovementPathWaypointCount(int32 pathId) {
diff --git a/engines/ags/plugins/ags_plugin.h b/engines/ags/plugins/ags_plugin.h
index 0ed14d222ed..e3d62ee48d0 100644
--- a/engines/ags/plugins/ags_plugin.h
+++ b/engines/ags/plugins/ags_plugin.h
@@ -21,9 +21,9 @@
 
 //=============================================================================
 //
-// AGS Plugin interface header file
+// AGS Plugin interface header file.
 //
-// #define THIS_IS_THE_PLUGIN beforehand if including from the plugin
+// #define THIS_IS_THE_PLUGIN beforehand if including from the plugin.
 //
 //=============================================================================
 
@@ -35,6 +35,7 @@
 #include "ags/shared/font/ags_font_renderer.h"
 #include "ags/shared/util/string.h"
 #include "ags/plugins/plugin_base.h"
+#include "ags/plugins/ags_plugin_evts.h"
 #include "ags/engine/util/library_scummvm.h"
 
 namespace AGS3 {
@@ -221,39 +222,6 @@ public:
 	AGSIFUNC(void) UnregisterScriptHeader(const char *header);
 };
 
-
-// Below are interface 3 and later
-#define AGSE_KEYPRESS        1
-#define AGSE_MOUSECLICK      2
-#define AGSE_POSTSCREENDRAW  4
-// Below are interface 4 and later
-#define AGSE_PRESCREENDRAW   8
-// Below are interface 5 and later
-#define AGSE_SAVEGAME        0x10
-#define AGSE_RESTOREGAME     0x20
-// Below are interface 6 and later
-#define AGSE_PREGUIDRAW      0x40
-#define AGSE_LEAVEROOM       0x80
-#define AGSE_ENTERROOM       0x100
-#define AGSE_TRANSITIONIN    0x200
-#define AGSE_TRANSITIONOUT   0x400
-// Below are interface 12 and later
-#define AGSE_FINALSCREENDRAW 0x800
-#define AGSE_TRANSLATETEXT   0x1000
-// Below are interface 13 and later
-#define AGSE_SCRIPTDEBUG     0x2000
-#define AGSE_AUDIODECODE     0x4000 // obsolete, no longer supported
-// Below are interface 18 and later
-#define AGSE_SPRITELOAD      0x8000
-// Below are interface 21 and later
-#define AGSE_PRERENDER       0x10000
-// Below are interface 24 and later
-#define AGSE_PRESAVEGAME     0x20000
-#define AGSE_POSTRESTOREGAME 0x40000
-// Below are interface 26 and later
-#define AGSE_POSTROOMDRAW    0x80000
-#define AGSE_TOOHIGH         0x100000
-
 // GetFontType font types
 #define FNT_INVALID 0
 #define FNT_SCI     1
diff --git a/engines/ags/plugins/ags_plugin_evts.h b/engines/ags/plugins/ags_plugin_evts.h
new file mode 100644
index 00000000000..a5c100ed208
--- /dev/null
+++ b/engines/ags/plugins/ags_plugin_evts.h
@@ -0,0 +1,70 @@
+/* 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/>.
+ *
+ */
+
+//=============================================================================
+//
+// AGS Plugin interface header file.
+// Engine events definition.
+//
+// If you are writing a plugin, include agsplugin.h instead.
+//
+//=============================================================================
+
+#ifndef AGS_PLUGINS_AGS_PLUGIN_EVTS_H
+#define AGS_PLUGINS_AGS_PLUGIN_EVTS_H
+
+namespace AGS3 {
+
+// Below are interface 3 and later
+#define AGSE_KEYPRESS        1
+#define AGSE_MOUSECLICK      2
+#define AGSE_POSTSCREENDRAW  4
+// Below are interface 4 and later
+#define AGSE_PRESCREENDRAW   8
+// Below are interface 5 and later
+#define AGSE_SAVEGAME        0x10
+#define AGSE_RESTOREGAME     0x20
+// Below are interface 6 and later
+#define AGSE_PREGUIDRAW      0x40
+#define AGSE_LEAVEROOM       0x80
+#define AGSE_ENTERROOM       0x100
+#define AGSE_TRANSITIONIN    0x200
+#define AGSE_TRANSITIONOUT   0x400
+// Below are interface 12 and later
+#define AGSE_FINALSCREENDRAW 0x800
+#define AGSE_TRANSLATETEXT   0x1000
+// Below are interface 13 and later
+#define AGSE_SCRIPTDEBUG     0x2000
+#define AGSE_AUDIODECODE     0x4000 // obsolete, no longer supported
+// Below are interface 18 and later
+#define AGSE_SPRITELOAD      0x8000
+// Below are interface 21 and later
+#define AGSE_PRERENDER       0x10000
+// Below are interface 24 and later
+#define AGSE_PRESAVEGAME     0x20000
+#define AGSE_POSTRESTOREGAME 0x40000
+// Below are interface 26 and later
+#define AGSE_POSTROOMDRAW    0x80000
+#define AGSE_TOOHIGH         0x100000
+
+} // namespace AGS3
+
+#endif
diff --git a/engines/ags/plugins/plugin_engine.h b/engines/ags/plugins/plugin_engine.h
index 0b0d5a91d94..8454b89ef57 100644
--- a/engines/ags/plugins/plugin_engine.h
+++ b/engines/ags/plugins/plugin_engine.h
@@ -29,6 +29,7 @@
 #define AGS_ENGINE_PLUGIN_PLUGIN_ENGINE_H
 
 #include "common/std/vector.h"
+#include "ags/engine/ac/dynobj/cc_script_object.h"
 #include "ags/engine/game/game_init.h"
 #include "ags/shared/game/plugin_info.h"
 #include "ags/shared/util/string.h"
@@ -72,6 +73,8 @@ bool pl_any_want_hook(int event);
 void pl_set_file_handle(long data, AGS::Shared::Stream *stream);
 void pl_clear_file_handle();
 
+bool RegisterPluginStubs(const char* name);
+
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/shared/ac/keycode.h b/engines/ags/shared/ac/keycode.h
index d2a4cdfa31d..1ea2d62b2b0 100644
--- a/engines/ags/shared/ac/keycode.h
+++ b/engines/ags/shared/ac/keycode.h
@@ -292,7 +292,7 @@ struct KeyInput {
 };
 
 // AGS own mouse button codes;
-// These correspond to MouseButton enum in script API (sans special values)
+// These correspond to MouseButton enum in script and plugin API (sans special values)
 enum eAGSMouseButton
 {
 	kMouseNone = 0,


Commit: 1527b38a5cc4f7ef99e9a3f253848b9f65ccb955
    https://github.com/scummvm/scummvm/commit/1527b38a5cc4f7ef99e9a3f253848b9f65ccb955
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: SpriteCache uses proper callbacks instead of hardcoded externs

>From upstream 9354aaf7d105158418dfbc1ba67e75adcd9f89d4

Changed paths:
  R engines/ags/engine/ac/sprite_cache_engine.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/sprite.cpp
    engines/ags/engine/ac/sprite.h
    engines/ags/globals.cpp
    engines/ags/module.mk
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 5cf5490ae21..85d6c10aa07 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -51,6 +51,7 @@
 #include "ags/engine/ac/sys_events.h"
 #include "ags/engine/ac/rich_game_media.h"
 #include "ags/engine/ac/room_status.h"
+#include "ags/engine/ac/sprite.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/string.h"
 #include "ags/engine/ac/translation.h"
diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp
index a6716d075f6..01f18af6bd5 100644
--- a/engines/ags/engine/ac/sprite.cpp
+++ b/engines/ags/engine/ac/sprite.cpp
@@ -27,7 +27,6 @@
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/plugins/ags_plugin_evts.h"
 #include "ags/plugins/plugin_engine.h"
-#include "ags/shared/ac/sprite_cache.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gfx/graphics_driver.h"
 
@@ -36,13 +35,12 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-void get_new_size_for_sprite(int ee, int ww, int hh, int &newwid, int &newhit) {
-	newwid = ww;
-	newhit = hh;
-	const SpriteInfo &spinfo = _GP(game).SpriteInfos[ee];
-	if (!_GP(game).AllowRelativeRes() || !spinfo.IsRelativeRes())
-		return;
-	ctx_data_to_game_size(newwid, newhit, spinfo.IsLegacyHiRes());
+Size get_new_size_for_sprite(const Size &size, const uint32_t sprite_flags) {
+	if (!_GP(game).AllowRelativeRes() || ((sprite_flags & SPF_VAR_RESOLUTION) == 0))
+		return size;
+	Size newsz = size;
+	ctx_data_to_game_size(newsz.Width, newsz.Height, ((sprite_flags & SPF_HIRES) != 0));
+	return newsz;
 }
 
 // from is a 32-bit RGBA image, to is a 15/16/24-bit destination image
@@ -94,66 +92,44 @@ Bitmap *remove_alpha_channel(Bitmap *from) {
 	return to;
 }
 
-void pre_save_sprite(Bitmap * /*image*/) {
-	// not used, we don't save
-}
-
-// these vars are global to help with debugging
-Bitmap *tmpdbl, *curspr;
-int newwid, newhit;
-void initialize_sprite(int ee) {
-
-	if ((ee < 0) || ((size_t)ee > _GP(spriteset).GetSpriteSlotCount()))
-		quit("initialize_sprite: invalid sprite number");
-
-	if ((_GP(spriteset)[ee] == nullptr) && (ee > 0)) {
-		// replace empty sprites with blue cups, to avoid crashes
-		_GP(spriteset).RemapSpriteToSprite0(ee);
-	} else if (_GP(spriteset)[ee] == nullptr) {
-		_GP(game).SpriteInfos[ee].Width = 0;
-		_GP(game).SpriteInfos[ee].Height = 0;
-	} else {
-		// stretch sprites to correct resolution
-		int oldeip = _G(our_eip);
-		_G(our_eip) = 4300;
-
-		if (_GP(game).SpriteInfos[ee].Flags & SPF_HADALPHACHANNEL) {
-			// we stripped the alpha channel out last time, put
-			// it back so that we can remove it properly again
-			_GP(game).SpriteInfos[ee].Flags |= SPF_ALPHACHANNEL;
-		}
+Bitmap *initialize_sprite(sprkey_t index, Bitmap *image, uint32_t &sprite_flags) {
+	int oldeip = _G(our_eip);
+	_G(our_eip) = 4300;
 
-		curspr = _GP(spriteset)[ee];
-		get_new_size_for_sprite(ee, curspr->GetWidth(), curspr->GetHeight(), newwid, newhit);
-
-		_G(eip_guinum) = ee;
-		_G(eip_guiobj) = newwid;
+	if (sprite_flags & SPF_HADALPHACHANNEL) {
+		// we stripped the alpha channel out last time, put
+		// it back so that we can remove it properly again
+		// CHECKME: find out what does this mean, and explain properly
+		sprite_flags |= SPF_ALPHACHANNEL;
+	}
 
-		if ((newwid != curspr->GetWidth()) || (newhit != curspr->GetHeight())) {
-			tmpdbl = BitmapHelper::CreateTransparentBitmap(newwid, newhit, curspr->GetColorDepth());
-			if (tmpdbl == nullptr)
-				quit("Not enough memory to load sprite graphics");
-			tmpdbl->StretchBlt(curspr, RectWH(0, 0, tmpdbl->GetWidth(), tmpdbl->GetHeight()), Shared::kBitmap_Transparency);
-			delete curspr;
-			_GP(spriteset).SubstituteBitmap(ee, tmpdbl);
-		}
+	// stretch sprites to correct resolution
+	Size newsz = get_new_size_for_sprite(image->GetSize(), sprite_flags);
 
-		_GP(game).SpriteInfos[ee].Width = _GP(spriteset)[ee]->GetWidth();
-		_GP(game).SpriteInfos[ee].Height = _GP(spriteset)[ee]->GetHeight();
+	_G(eip_guinum) = index;
+	_G(eip_guiobj) = newsz.Width;
 
-		_GP(spriteset).SubstituteBitmap(ee, PrepareSpriteForUse(_GP(spriteset)[ee], (_GP(game).SpriteInfos[ee].Flags & SPF_ALPHACHANNEL) != 0));
+	Bitmap *use_bmp = image;
+	if (newsz != image->GetSize()) {
+		use_bmp = new Bitmap(newsz.Width, newsz.Height, image->GetColorDepth());
+		use_bmp->StretchBlt(image, RectWH(0, 0, use_bmp->GetWidth(), use_bmp->GetHeight()));
+		delete image;
+	}
 
-		if (_GP(game).GetColorDepth() < 32) {
-			_GP(game).SpriteInfos[ee].Flags &= ~SPF_ALPHACHANNEL;
-			// save the fact that it had one for the next time this
-			// is re-loaded from disk
-			_GP(game).SpriteInfos[ee].Flags |= SPF_HADALPHACHANNEL;
-		}
+	use_bmp = PrepareSpriteForUse(use_bmp, (sprite_flags & SPF_ALPHACHANNEL) != 0);
+	if (_GP(game).GetColorDepth() < 32) {
+		sprite_flags &= ~SPF_ALPHACHANNEL;
+		// save the fact that it had one for the next time this is re-loaded from disk
+		// CHECKME: find out what does this mean, and explain properly
+		sprite_flags |= SPF_HADALPHACHANNEL;
+	}
 
-		pl_run_plugin_hooks(AGSE_SPRITELOAD, ee);
+	_G(our_eip) = oldeip;
+	return use_bmp;
+}
 
-		_G(our_eip) = oldeip;
-	}
+void post_init_sprite(sprkey_t index) {
+	pl_run_plugin_hooks(AGSE_SPRITELOAD, index);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/sprite.h b/engines/ags/engine/ac/sprite.h
index 17183b356bf..9d144cd9873 100644
--- a/engines/ags/engine/ac/sprite.h
+++ b/engines/ags/engine/ac/sprite.h
@@ -22,14 +22,21 @@
 #ifndef AGS_ENGINE_AC_SPRITE_H
 #define AGS_ENGINE_AC_SPRITE_H
 
+#include "ags/shared/ac/sprite_cache.h"
+#include "ags/shared/gfx/bitmap.h"
+
 namespace AGS3 {
 
-void get_new_size_for_sprite(int ee, int ww, int hh, int &newwid, int &newhit);
 // Converts from 32-bit RGBA image, to a 15/16/24-bit destination image,
 // replacing more than half-translucent alpha pixels with transparency mask pixels.
 Shared::Bitmap *remove_alpha_channel(Shared::Bitmap *from);
-void pre_save_sprite(Shared::Bitmap *bitmap);
-void initialize_sprite(int ee);
+Size get_new_size_for_sprite(const Size &size, const uint32_t sprite_flags);
+// Initializes a loaded sprite for use in the game, adjusts the sprite flags.
+// Returns a resulting bitmap, which may be a new or old bitmap; or null on failure.
+// Original bitmap **gets deleted** if a new bitmap had to be created,
+// or if failed to properly initialize one.
+Shared::Bitmap *initialize_sprite(Shared::sprkey_t index, Shared::Bitmap *image, uint32_t &sprite_flags);
+void post_init_sprite(Shared::sprkey_t index);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/sprite_cache_engine.cpp b/engines/ags/engine/ac/sprite_cache_engine.cpp
deleted file mode 100644
index fdcefb8e871..00000000000
--- a/engines/ags/engine/ac/sprite_cache_engine.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/* 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/>.
- *
- */
-
-//=============================================================================
-//
-// Implementation from sprcache.cpp specific to Engine runtime
-//
-//=============================================================================
-
-#include "ags/shared/ac/game_struct_defines.h"
-#include "ags/shared/ac/sprite_cache.h"
-#include "ags/shared/util/compress.h"
-
-namespace AGS3 {
-
-//=============================================================================
-// Engine-specific implementation split out of sprcache.cpp
-//=============================================================================
-
-void AGS::Shared::SpriteCache::InitNullSpriteParams(sprkey_t index) {
-	// make it a blue cup, to avoid crashes
-	_sprInfos[index].Width = _sprInfos[0].Width;
-	_sprInfos[index].Height = _sprInfos[0].Height;
-	_spriteData[index].Image = nullptr;
-	_spriteData[index].Size = _spriteData[0].Size;
-	_spriteData[index].Flags = SPRCACHEFLAG_REMAPPED;
-}
-
-} // namespace AGS3
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 5aba5353cb5..3ad8c132ddc 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -51,6 +51,7 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/route_finder_jps.h"
 #include "ags/engine/ac/screen_overlay.h"
+#include "ags/engine/ac/sprite.h"
 #include "ags/engine/ac/sprite_list_entry.h"
 #include "ags/engine/ac/top_bar_settings.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
@@ -236,7 +237,14 @@ Globals::Globals() {
 	_guis = new std::vector<AGS::Shared::GUIMain>();
 	_play = new GameState();
 	_game = new GameSetupStruct();
-	_spriteset = new AGS::Shared::SpriteCache(_game->SpriteInfos);
+
+	AGS::Shared::SpriteCache::Callbacks spritecallbacks = {
+		get_new_size_for_sprite,
+		initialize_sprite,
+		post_init_sprite,
+		nullptr};
+	_spriteset = new AGS::Shared::SpriteCache(_game->SpriteInfos, spritecallbacks);
+
 	_thisroom = new AGS::Shared::RoomStruct();
 	_troom = new RoomStatus();
 	_usetup = new GameSetup();
diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 845e77b1bdf..56b0a24df92 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -183,7 +183,6 @@ MODULE_OBJS = \
 	engine/ac/slider.o \
 	engine/ac/speech.o \
 	engine/ac/sprite.o \
-	engine/ac/sprite_cache_engine.o \
 	engine/ac/string.o \
 	engine/ac/system.o \
 	engine/ac/sys_events.o \
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 41399c40631..021c47309fd 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -39,10 +39,6 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-// [IKM] We have to forward-declare these because their implementations are in the Engine
-extern void initialize_sprite(int);
-extern void pre_save_sprite(Bitmap *image);
-extern void get_new_size_for_sprite(int, int, int, int &, int &);
 
 // High-verbosity sprite cache log
 #if DEBUG_SPRITECACHE
@@ -60,9 +56,13 @@ SpriteInfo::SpriteInfo()
 namespace AGS {
 namespace Shared {
 
-SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos)
+SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks)
 	: _sprInfos(sprInfos), _maxCacheSize(DEFAULTCACHESIZE_KB * 1024u),
-	_cacheSize(0u), _lockedSize(0u) {
+	  _cacheSize(0u), _lockedSize(0u) {
+	_callbacks.AdjustSize = (callbacks.AdjustSize) ? callbacks.AdjustSize : DummyAdjustSize;
+	_callbacks.InitSprite = (callbacks.InitSprite) ? callbacks.InitSprite : DummyInitSprite;
+	_callbacks.PostInitSprite = (callbacks.PostInitSprite) ? callbacks.PostInitSprite : DummyPostInitSprite;
+	_callbacks.PrewriteSprite = (callbacks.PrewriteSprite) ? callbacks.PrewriteSprite : DummyPrewriteSprite;
 }
 
 SpriteCache::~SpriteCache() {
@@ -134,15 +134,6 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 	RemapSpriteToSprite0(index);
 }
 
-void SpriteCache::SubstituteBitmap(sprkey_t index, Bitmap *sprite) {
-	if (!DoesSpriteExist(index)) {
-		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SubstituteBitmap: attempt to set for non-existing sprite %d", index);
-		return;
-	}
-	_spriteData[index].Image = sprite;
-	SprCacheLog("SubstituteBitmap: %d", index);
-}
-
 Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return nullptr;
@@ -335,27 +326,25 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
 			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
 			err ? "Sprite does not exist." : err->FullMessage().GetCStr());
-		RemapSpriteToSprite0(index);
+		InitNullSpriteParams(index);
+		return 0;
+	}
+
+	// Let the external user convert this sprite's image for their needs
+	image = _callbacks.InitSprite(index, image, _sprInfos[index].Flags);
+	if (!image) {
+		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
+					  "LoadSprite: failed to initialize sprite %d, remapping to sprite 0.", index);
+		InitNullSpriteParams(index);
 		return 0;
 	}
 
-	// update the stored width/height
+	// save the stored sprite info
 	_sprInfos[index].Width = image->GetWidth();
 	_sprInfos[index].Height = image->GetHeight();
 	_spriteData[index].Image = image;
-
-	// Stop it adding the sprite to the used list just because it's loaded
-	// TODO: this messy hack is required, because initialize_sprite calls operator[]
-	// which puts the sprite to the MRU list.
-	_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
-
-	// TODO: this is ugly: asks the engine to convert the sprite using its own knowledge.
-	// And engine assigns new bitmap using SpriteCache::SubstituteBitmap().
-	// Perhaps change to the callback function pointer?
-	initialize_sprite(index);
-
-	if (index != 0)  // leave sprite 0 locked
-		_spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED;
+	if (index == 0) // keep sprite 0 locked
+		_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
 
 	const size_t size = _sprInfos[index].Width * _sprInfos[index].Height *
 		_spriteData[index].Image->GetBPP();
@@ -364,6 +353,12 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 	_spriteData[index].Size = size;
 	_cacheSize += size;
 	SprCacheLog("Loaded %d, size now %zu KB", index, _cacheSize / 1024);
+
+	// Let the external user to react to the new sprite;
+	// note that this callback is allowed to modify the sprite's pixels,
+	// but not its size or flags.
+	_callbacks.PostInitSprite(index);
+
 	return size;
 }
 
@@ -377,15 +372,20 @@ void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 	SprCacheLog("RemapSpriteToSprite0: %d", index);
 }
 
+void SpriteCache::InitNullSpriteParams(sprkey_t index) {
+	assert(index >= 0);
+	if (index > 0) {
+		RemapSpriteToSprite0(index);
+	} else {
+		_sprInfos[index] = SpriteInfo();
+		_spriteData[index] = SpriteData();
+	}
+}
+
 int SpriteCache::SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
 	std::vector<std::pair<bool, Bitmap *>> sprites;
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
-		// NOTE: this is a horrible hack:
-		// because Editor expects slightly different RGB order, it swaps colors
-		// when loading them (call to initialize_sprite), so here we basically
-		// unfix that fix to save the data in a way that engine will expect.
-		// TODO: perhaps adjust the editor to NOT need this?!
-		pre_save_sprite(_spriteData[i].Image);
+		_callbacks.PrewriteSprite(_spriteData[i].Image);
 		sprites.push_back(std::make_pair(DoesSpriteExist(i), _spriteData[i].Image));
 	}
 	return SaveSpriteFile(filename, sprites, &_file, store_flags, compress, index);
@@ -408,11 +408,12 @@ HError SpriteCache::InitFile(const String &filename, const String &sprindex_file
 		if (!metrics[i].IsNull()) {
 			// Existing sprite
 			_spriteData[i].Flags = SPRCACHEFLAG_ISASSET;
-			get_new_size_for_sprite(i, metrics[i].Width, metrics[i].Height, _sprInfos[i].Width, _sprInfos[i].Height);
+			Size newsz = _callbacks.AdjustSize(Size(metrics[i].Width, metrics[i].Height), _sprInfos[i].Flags);
+			_sprInfos[i].Width = newsz.Width;
+			_sprInfos[i].Height = newsz.Height;
 		} else {
 			// Handle empty slot: remap to sprite 0
-			if (i > 0) // FIXME: optimize
-				InitNullSpriteParams(i);
+			InitNullSpriteParams(i);
 		}
 	}
 	return HError::None();
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 6a62f8fdc7c..7a830582bff 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -89,7 +89,19 @@ public:
 	static const sprkey_t MAX_SPRITE_INDEX = INT32_MAX - 1;
 	static const size_t   MAX_SPRITE_SLOTS = INT32_MAX;
 
-	SpriteCache(std::vector<SpriteInfo> &sprInfos);
+	typedef Size (*PfnAdjustSpriteSize)(const Size &size, const uint32_t sprite_flags);
+	typedef Bitmap *(*PfnInitSprite)(sprkey_t index, Bitmap *image, uint32_t &sprite_flags);
+	typedef void (*PfnPostInitSprite)(sprkey_t index);
+	typedef void (*PfnPrewriteSprite)(Bitmap *image);
+
+	struct Callbacks {
+		PfnAdjustSpriteSize AdjustSize;
+		PfnInitSprite InitSprite;
+		PfnPostInitSprite PostInitSprite;
+		PfnPrewriteSprite PrewriteSprite;
+	};
+
+	SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks);
 	~SpriteCache();
 
 	// Loads sprite reference information and inits sprite stream
@@ -126,8 +138,6 @@ public:
 	size_t      GetSpriteSlotCount() const;
 	// Loads sprite and and locks in memory (so it cannot get removed implicitly)
 	void        Precache(sprkey_t index);
-	// Remap the given index to the sprite 0
-	void        RemapSpriteToSprite0(sprkey_t index);
 	// Unregisters sprite from the bank and returns the bitmap
 	Bitmap		*RemoveSprite(sprkey_t index);
 	// Deletes particular sprite, marks slot as unused
@@ -143,8 +153,6 @@ public:
 	// Assigns new sprite for the given index, remapping it to sprite 0;
 	// optionally marks it as an asset placeholder
 	void        SetEmptySprite(sprkey_t index, bool as_asset);
-	// Assigns new bitmap for the *registered* sprite without changing its properties
-	void        SubstituteBitmap(sprkey_t index, Shared::Bitmap *);
 	// Sets max cache size in bytes
 	void        SetMaxCacheSize(size_t size);
 
@@ -154,6 +162,8 @@ public:
 private:
 	// Load sprite from game resource
 	size_t      LoadSprite(sprkey_t index);
+	// Remap the given index to the sprite 0
+	void        RemapSpriteToSprite0(sprkey_t index);
 	// Gets the index of a sprite which data is used for the given slot;
 	// in case of remapped sprite this will return the one given sprite is remapped to
 	sprkey_t    GetDataIndex(sprkey_t index);
@@ -161,6 +171,15 @@ private:
 	void        DisposeOldest();
 	// Keep disposing oldest elements until cache has at least the given free space
 	void        FreeMem(size_t space);
+	// Initialize the empty sprite slot
+	void 		InitNullSpriteParams(sprkey_t index);
+	//
+    // Dummy no-op variants for callbacks
+    //
+	static Size   DummyAdjustSize(const Size &size, const uint32_t) { return size; }
+	static Bitmap *DummyInitSprite(sprkey_t, Bitmap *image, uint32_t &) { return image; }
+	static void   DummyPostInitSprite(sprkey_t) { /* do nothing */ }
+	static void   DummyPrewriteSprite(Bitmap *) { /* do nothing */ }
 
 	// Information required for the sprite streaming
 	struct SpriteData {
@@ -187,6 +206,7 @@ private:
 	// Array of sprite references
 	std::vector<SpriteData> _spriteData;
 
+	Callbacks _callbacks;
 	SpriteFile _file;
 
 	size_t _maxCacheSize;  // cache size limit
@@ -198,8 +218,6 @@ private:
 	// that were last time used long ago.
 	std::list<sprkey_t> _mru;
 
-	// Initialize the empty sprite slot
-	void        InitNullSpriteParams(sprkey_t index);
 };
 
 } // namespace Shared


Commit: 2fbeea18401367bfe9a362a6083aa45c84dd17ff
    https://github.com/scummvm/scummvm/commit/2fbeea18401367bfe9a362a6083aa45c84dd17ff
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: SpriteCache: SetSprite() disposes old sprite in the same slot itself

>From upstream 68f1e1a141e53c525b26e8b8f32f81eaa590d299

Changed paths:
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 2e900764d3f..1293f9e711a 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -111,8 +111,6 @@ void DynamicSprite_Resize(ScriptDynamicSprite *sds, int width, int height) {
 					   RectWH(0, 0, _GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height),
 					   RectWH(0, 0, width, height));
 
-	delete sprite;  // FIXME: instead do this while replacing sprite with existing id
-
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
@@ -130,7 +128,6 @@ void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction) {
 
 	// AGS script FlipDirection corresponds to internal GraphicFlip
 	newPic->FlipBlt(sprite, 0, 0, static_cast<GraphicFlip>(direction));
-	delete sprite;  // FIXME: instead do this while replacing sprite with existing id
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
@@ -179,8 +176,6 @@ void DynamicSprite_ChangeCanvasSize(ScriptDynamicSprite *sds, int width, int hei
 	// blit it into the enlarged image
 	newPic->Blit(sprite, 0, 0, x, y, sprite->GetWidth(), sprite->GetHeight());
 
-	delete sprite; // FIXME: instead do this while replacing sprite with existing id
-
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
@@ -203,8 +198,6 @@ void DynamicSprite_Crop(ScriptDynamicSprite *sds, int x1, int y1, int width, int
 	// blit it cropped
 	newPic->Blit(sprite, x1, y1, 0, 0, newPic->GetWidth(), newPic->GetHeight());
 
-	delete sprite; // FIXME: instead do this while replacing sprite with existing id
-
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
@@ -246,8 +239,6 @@ void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int he
 	newPic->RotateBlt(sprite, width / 2 + width % 2, height / 2,
 					  sprite->GetWidth() / 2, sprite->GetHeight() / 2, itofix(angle));
 
-	delete sprite; // FIXME: instead do this while replacing sprite with existing id
-
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
@@ -259,7 +250,6 @@ void DynamicSprite_Tint(ScriptDynamicSprite *sds, int red, int green, int blue,
 
 	tint_image(newPic, source, red, green, blue, saturation, (luminance * 25) / 10);
 
-	delete source;  // FIXME: instead do this while replacing sprite with existing id
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 021c47309fd..64268802970 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -94,10 +94,8 @@ void SpriteCache::Reset() {
 	_file.Close();
 	// TODO: find out if it's safe to simply always delete _spriteData.Image with array element
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
-		if (_spriteData[i].Image) {
-			delete _spriteData[i].Image;
-			_spriteData[i].Image = nullptr;
-		}
+		delete _spriteData[i].Image;
+		_spriteData[i].Image = nullptr;
 	}
 	_spriteData.clear();
 	_mru.clear();
@@ -114,6 +112,7 @@ bool SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite, int flags) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign nullptr to index %d", index);
 		return false;
 	}
+	delete _spriteData[index].Image; // delete old sprite, if was present
 	_spriteData[index].Image = sprite;
 	_spriteData[index].Flags = SPRCACHEFLAG_LOCKED; // NOT from asset file
 	_spriteData[index].Size = 0;
@@ -129,6 +128,7 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetEmptySprite: unable to use index %d", index);
 		return;
 	}
+	delete _spriteData[index].Image; // delete old sprite, if was present
 	if (as_asset)
 		_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
 	RemapSpriteToSprite0(index);
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 7a830582bff..b3c83e05ffe 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -147,11 +147,13 @@ public:
 	void        DisposeAll();
 	// Deletes all data and resets cache to the clear state
 	void        Reset();
-	// Assigns new sprite for the given index; this sprite won't be auto disposed
-	// flags are SPF_* constants that define sprite's behavior in game.
+	// Assigns new sprite for the given index; this sprite won't be auto disposed.
+	// *Deletes* the previous sprite if one was found at the same index.
+	// "flags" are SPF_* constants that define sprite's behavior in game.
 	bool        SetSprite(sprkey_t index, Shared::Bitmap *, int flags = 0);
 	// Assigns new sprite for the given index, remapping it to sprite 0;
-	// optionally marks it as an asset placeholder
+	// optionally marks it as an asset placeholder.
+	// *Deletes* the previous sprite if one was found at the same index.
 	void        SetEmptySprite(sprkey_t index, bool as_asset);
 	// Sets max cache size in bytes
 	void        SetMaxCacheSize(size_t size);


Commit: 7edb641ff2594722eea3aa94e87e30109fac5f74
    https://github.com/scummvm/scummvm/commit/7edb641ff2594722eea3aa94e87e30109fac5f74
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: SpriteCache: store bitmaps in unique_ptr, tidy storage code a little

>From upstream ccffbf65b7ea6c85f074ee8f42066102df95c93e

Changed paths:
    engines/ags/engine/ac/room.cpp
    engines/ags/shared/ac/game_struct_defines.h
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 8ef93a15bab..08e5359b515 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -903,7 +903,7 @@ void new_room(int newnum, CharacterInfo *forchar) {
 
 	if (_GP(usetup).clear_cache_on_room_change) {
 		// Delete all cached sprites
-		_GP(spriteset).DisposeAll();
+		_GP(spriteset).DisposeAllCached();
 	}
 
 	load_new_room(newnum, forchar);
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 79dfa305bdc..7d5e4b7582a 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -231,11 +231,12 @@ enum GameGuiAlphaRenderingStyle {
 
 // General information about sprite (properties, size)
 struct SpriteInfo {
-	uint32_t Flags;
-	int      Width;
-	int      Height;
+	int Width  = 0;
+	int Height = 0;
+	uint32_t Flags = 0u; // SPF_* flags
 
-	SpriteInfo();
+	SpriteInfo() = default;
+	SpriteInfo(int w, int h, uint32_t flags) : Width(w), Height(h), Flags(flags) {}
 
 	inline Size GetResolution() const { return Size(Width, Height); }
 
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 64268802970..3bde2127948 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -39,6 +39,14 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+// Tells that the sprite is found in the game resources.
+#define SPRCACHEFLAG_ISASSET  0x01
+// Tells that the sprite is assigned externally and cannot be autodisposed.
+#define SPRCACHEFLAG_EXTERNAL 0x02
+// Tells that the sprite index was remapped to the placeholder (sprite 0).
+#define SPRCACHEFLAG_REMAP0	  0x04
+// Locked sprites are ones that should not be freed when out of cache space.
+#define SPRCACHEFLAG_LOCKED	  0x08
 
 // High-verbosity sprite cache log
 #if DEBUG_SPRITECACHE
@@ -47,12 +55,6 @@ using namespace AGS::Shared;
 #define SprCacheLog(...)
 #endif
 
-SpriteInfo::SpriteInfo()
-	: Flags(0)
-	, Width(0)
-	, Height(0) {
-}
-
 namespace AGS {
 namespace Shared {
 
@@ -92,11 +94,6 @@ void SpriteCache::SetMaxCacheSize(size_t size) {
 
 void SpriteCache::Reset() {
 	_file.Close();
-	// TODO: find out if it's safe to simply always delete _spriteData.Image with array element
-	for (size_t i = 0; i < _spriteData.size(); ++i) {
-		delete _spriteData[i].Image;
-		_spriteData[i].Image = nullptr;
-	}
 	_spriteData.clear();
 	_mru.clear();
 	_cacheSize = 0;
@@ -112,13 +109,10 @@ bool SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite, int flags) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign nullptr to index %d", index);
 		return false;
 	}
-	delete _spriteData[index].Image; // delete old sprite, if was present
-	_spriteData[index].Image = sprite;
-	_spriteData[index].Flags = SPRCACHEFLAG_LOCKED; // NOT from asset file
-	_spriteData[index].Size = 0;
-	_sprInfos[index].Flags = flags;
-	_sprInfos[index].Width = sprite->GetWidth();
-	_sprInfos[index].Height = sprite->GetHeight();
+
+	// Assign sprite with 0 size, as it will not be included into the cache size
+	_spriteData[index] = SpriteData(sprite, 0, SPRCACHEFLAG_EXTERNAL | SPRCACHEFLAG_LOCKED);
+	_sprInfos[index] = SpriteInfo(flags, sprite->GetWidth(), sprite->GetHeight());
 	SprCacheLog("SetSprite: (external) %d", index);
 	return true;
 }
@@ -128,7 +122,6 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetEmptySprite: unable to use index %d", index);
 		return;
 	}
-	delete _spriteData[index].Image; // delete old sprite, if was present
 	if (as_asset)
 		_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
 	RemapSpriteToSprite0(index);
@@ -137,7 +130,7 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return nullptr;
-	Bitmap *image = _spriteData[index].Image;
+	Bitmap *image = _spriteData[index].Image.release();
 	InitNullSpriteParams(index);
 	SprCacheLog("RemoveSprite: %d", index);
 	return image;
@@ -147,7 +140,6 @@ void SpriteCache::DisposeSprite(sprkey_t index) {
 	assert(index >= 0); // out of positive range indexes are valid to fail
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return;
-	delete _spriteData[index].Image;
 	InitNullSpriteParams(index);
 	SprCacheLog("RemoveAndDispose: %d", index);
 }
@@ -186,10 +178,12 @@ bool SpriteCache::SpriteData::IsAssetSprite() const {
 	return (Flags & SPRCACHEFLAG_ISASSET) != 0; // found in game resources
 }
 
+bool SpriteCache::SpriteData::IsRemapped() const {
+	return (Flags & SPRCACHEFLAG_REMAP0) != 0; // was remapped to placeholder (sprite 0)
+}
+
 bool SpriteCache::SpriteData::IsExternalSprite() const {
-	return (Image != nullptr) &&  // HAS loaded bitmap
-		((Flags & SPRCACHEFLAG_ISASSET) == 0) && // AND NOT found in game resources
-		((Flags & SPRCACHEFLAG_REMAPPED) == 0); // AND was NOT remapped to another sprite
+	return (Flags & SPRCACHEFLAG_EXTERNAL) != 0; // assigned externally
 }
 
 bool SpriteCache::SpriteData::IsLocked() const {
@@ -211,7 +205,7 @@ Bitmap *SpriteCache::operator [] (sprkey_t index) {
 
 	// Externally added sprite or locked sprite, don't put it into MRU list
 	if (_spriteData[index].IsExternalSprite() || _spriteData[index].IsLocked())
-		return _spriteData[index].Image;
+		return _spriteData[index].Image.get();
 
 	if (_spriteData[index].Image) {
 		// Move to the beginning of the MRU list
@@ -221,7 +215,7 @@ Bitmap *SpriteCache::operator [] (sprkey_t index) {
 		LoadSprite(index);
 		_spriteData[index].MruIt = _mru.insert(_mru.begin(), index);
 	}
-	return _spriteData[index].Image;
+	return _spriteData[index].Image.get();
 }
 
 void SpriteCache::FreeMem(size_t space) {
@@ -229,7 +223,7 @@ void SpriteCache::FreeMem(size_t space) {
 		DisposeOldest();
 		if (tries > 1000) { // ???
 			Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: STUCK IN FREE_UP_MEM; RESETTING CACHE");
-			DisposeAll();
+			DisposeAllCached();
 		}
 	}
 }
@@ -248,8 +242,7 @@ void SpriteCache::DisposeOldest() {
 	// assert(_spriteData[sprnum].IsAssetSprite());
 
 	if (!_spriteData[sprnum].IsAssetSprite()) {
-		if (!(_spriteData[sprnum].Flags & SPRCACHEFLAG_REMAPPED))
-			Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SpriteCache::DisposeOldest: in MRU list sprite %d is external or does not exist", sprnum);
+		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SpriteCache::DisposeOldest: in MRU list sprite %d is external or does not exist", sprnum);
 		_mru.erase(it);
 		// std::list::erase() invalidates iterators to the erased item.
 		// But our implementation does not.
@@ -260,8 +253,7 @@ void SpriteCache::DisposeOldest() {
 	// NOTE: locked sprites may still occur in MRU list
 	if (!_spriteData[sprnum].IsLocked()) {
 		_cacheSize -= _spriteData[sprnum].Size;
-		delete _spriteData[*it].Image;
-		_spriteData[sprnum].Image = nullptr;
+		_spriteData[sprnum].Image.reset();
 		SprCacheLog("DisposeOldest: disposed %d, size now %d KB", sprnum, _cacheSize / 1024);
 	}
 	// Remove from the mru list
@@ -271,13 +263,12 @@ void SpriteCache::DisposeOldest() {
 	_spriteData[sprnum].MruIt._node = nullptr;
 }
 
-void SpriteCache::DisposeAll() {
+void SpriteCache::DisposeAllCached() {
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
-		if (!_spriteData[i].IsLocked() && // not locked
+		if (!_spriteData[i].IsLocked() &&   // not locked
 			_spriteData[i].IsAssetSprite()) // sprite from game resource
 		{
-			delete _spriteData[i].Image;
-			_spriteData[i].Image = nullptr;
+			_spriteData[i].Image.reset();
 		}
 	}
 	_cacheSize = _lockedSize;
@@ -290,12 +281,12 @@ void SpriteCache::Precache(sprkey_t index) {
 	if (!_spriteData[index].IsAssetSprite())
 		return; // cannot precache a non-asset sprite
 
-	soff_t sprSize = 0;
+	size_t size = 0;
 
 	if (_spriteData[index].Image == nullptr) {
-		sprSize = LoadSprite(index);
+		size = LoadSprite(index);
 	} else if (!_spriteData[index].IsLocked()) {
-		sprSize = _spriteData[index].Size;
+		size = _spriteData[index].Size;
 		// Remove locked sprite from the MRU list
 		_mru.erase(_spriteData[index].MruIt);
 		// std::list::erase() invalidates iterators to the erased item.
@@ -304,20 +295,21 @@ void SpriteCache::Precache(sprkey_t index) {
 	}
 
 	// make sure locked sprites can't fill the cache
-	_maxCacheSize += sprSize;
-	_lockedSize += sprSize;
+	_maxCacheSize += size;
+	_lockedSize += size;
 	_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
 	SprCacheLog("Precached %d", index);
 }
 
 sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
-	return (_spriteData[index].Flags & SPRCACHEFLAG_REMAPPED) == 0 ? index : 0;
+	return (_spriteData[index].Flags & SPRCACHEFLAG_REMAP0) == 0 ? index : 0;
 }
 
 size_t SpriteCache::LoadSprite(sprkey_t index) {
 	assert((index >= 0) && ((size_t)index < _spriteData.size()));
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return 0;
+	assert((_spriteData[index].Flags & SPRCACHEFLAG_ISASSET) != 0);
 
 	sprkey_t load_index = GetDataIndex(index);
 	Bitmap *image;
@@ -342,15 +334,12 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 	// save the stored sprite info
 	_sprInfos[index].Width = image->GetWidth();
 	_sprInfos[index].Height = image->GetHeight();
-	_spriteData[index].Image = image;
-	if (index == 0) // keep sprite 0 locked
-		_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
-
-	const size_t size = _sprInfos[index].Width * _sprInfos[index].Height *
-		_spriteData[index].Image->GetBPP();
 	// Clear up space before adding to cache
+	const size_t size = image->GetWidth() * image->GetHeight() * image->GetBPP();
 	FreeMem(size);
-	_spriteData[index].Size = size;
+	_spriteData[index] = SpriteData(image, size, SPRCACHEFLAG_ISASSET);
+	if (index == 0) // keep sprite 0 locked
+		_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
 	_cacheSize += size;
 	SprCacheLog("Loaded %d, size now %zu KB", index, _cacheSize / 1024);
 
@@ -363,12 +352,12 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 }
 
 void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
-	_sprInfos[index].Flags = _sprInfos[0].Flags;
-	_sprInfos[index].Width = _sprInfos[0].Width;
-	_sprInfos[index].Height = _sprInfos[0].Height;
-	_spriteData[index].Image = nullptr;
+	assert((index > 0) && ((size_t)index < _spriteData.size()));
+	_sprInfos[index] = _sprInfos[0];
+
+	_spriteData[index].Image.reset();
 	_spriteData[index].Size = _spriteData[0].Size;
-	_spriteData[index].Flags |= SPRCACHEFLAG_REMAPPED;
+	_spriteData[index].Flags |= SPRCACHEFLAG_REMAP0;
 	SprCacheLog("RemapSpriteToSprite0: %d", index);
 }
 
@@ -385,8 +374,8 @@ void SpriteCache::InitNullSpriteParams(sprkey_t index) {
 int SpriteCache::SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
 	std::vector<std::pair<bool, Bitmap *>> sprites;
 	for (size_t i = 0; i < _spriteData.size(); ++i) {
-		_callbacks.PrewriteSprite(_spriteData[i].Image);
-		sprites.push_back(std::make_pair(DoesSpriteExist(i), _spriteData[i].Image));
+		_callbacks.PrewriteSprite(_spriteData[i].Image.get());
+		sprites.push_back(std::make_pair(DoesSpriteExist(i), _spriteData[i].Image.get()));
 	}
 	return SaveSpriteFile(filename, sprites, &_file, store_flags, compress, index);
 }
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index b3c83e05ffe..d2267381876 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -46,6 +46,7 @@
 #include "common/std/list.h"
 #include "ags/shared/ac/sprite_file.h"
 #include "ags/shared/core/platform.h"
+#include "ags/shared/gfx/bitmap.h"
 #include "ags/shared/util/error.h"
 #include "ags/shared/util/geometry.h"
 
@@ -64,13 +65,6 @@ typedef AGS::Shared::HError HAGSError;
 
 struct SpriteInfo;
 
-// Tells that the sprite is found in the game resources.
-#define SPRCACHEFLAG_ISASSET        0x01
-// Tells that the sprite index was remapped to another existing sprite.
-#define SPRCACHEFLAG_REMAPPED       0x02
-// Locked sprites are ones that should not be freed when out of cache space.
-#define SPRCACHEFLAG_LOCKED         0x04
-
 // Max size of the sprite cache, in bytes
 #if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS
 #define DEFAULTCACHESIZE_KB (32 * 1024)
@@ -105,9 +99,9 @@ public:
 	~SpriteCache();
 
 	// Loads sprite reference information and inits sprite stream
-	HError      InitFile(const Shared::String &filename, const Shared::String &sprindex_filename);
+	HError      InitFile(const String &filename, const String &sprindex_filename);
 	// Saves current cache contents to the file
-	int         SaveToFile(const Shared::String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index);
+	int         SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index);
 	// Closes an active sprite file stream
 	void        DetachFile();
 
@@ -142,15 +136,15 @@ public:
 	Bitmap		*RemoveSprite(sprkey_t index);
 	// Deletes particular sprite, marks slot as unused
 	void		DisposeSprite(sprkey_t index);
-	// Deletes all loaded (non-locked, non-external) images from the cache;
+	// Deletes all loaded asset (non-locked, non-external) images from the cache;
 	// this keeps all the auxiliary sprite information intact
-	void        DisposeAll();
+	void        DisposeAllCached();
 	// Deletes all data and resets cache to the clear state
 	void        Reset();
 	// Assigns new sprite for the given index; this sprite won't be auto disposed.
 	// *Deletes* the previous sprite if one was found at the same index.
 	// "flags" are SPF_* constants that define sprite's behavior in game.
-	bool        SetSprite(sprkey_t index, Shared::Bitmap *, int flags = 0);
+	bool        SetSprite(sprkey_t index, Bitmap *, int flags = 0);
 	// Assigns new sprite for the given index, remapping it to sprite 0;
 	// optionally marks it as an asset placeholder.
 	// *Deletes* the previous sprite if one was found at the same index.
@@ -159,7 +153,7 @@ public:
 	void        SetMaxCacheSize(size_t size);
 
 	// Loads (if it's not in cache yet) and returns bitmap by the sprite index
-	Shared::Bitmap *operator[](sprkey_t index);
+	Bitmap *operator[](sprkey_t index);
 
 private:
 	// Load sprite from game resource
@@ -185,18 +179,25 @@ private:
 
 	// Information required for the sprite streaming
 	struct SpriteData {
-		size_t          Size = 0; // to track cache size
-		uint32_t        Flags = 0;
-		// TODO: investigate if we may safely use unique_ptr here
-		// (some of these bitmaps may be assigned from outside of the cache)
-		Shared::Bitmap *Image = nullptr; // actual bitmap
+		size_t	 Size  = 0;			   // to track cache size, 0 = means don't track
+		uint32_t Flags = 0;			   // SPRCACHEFLAG* flags
+		std::unique_ptr<Bitmap> Image; // actual bitmap
+
 		// MRU list reference
 		std::list<sprkey_t>::iterator MruIt;
 
+		SpriteData() = default;
+		SpriteData(SpriteData &&other) = default;
+		SpriteData(Bitmap *image, size_t size, uint32_t flags) : Size(size), Flags(flags), Image(image) {}
+
+		SpriteData &operator=(SpriteData &&other) = default;
+
 		// Tells if there actually is a registered sprite in this slot
 		bool DoesSpriteExist() const;
 		// Tells if there's a game resource corresponding to this slot
 		bool IsAssetSprite() const;
+		// Tells if a sprite is remapped to placeholder (e.g. failed to load)
+		bool IsRemapped() const;
 		// Tells if sprite was added externally, not loaded from game resources
 		bool IsExternalSprite() const;
 		// Tells if sprite is locked and should not be disposed by cache logic


Commit: ee6cf385c94557820aa1a0a82cdffdb011c10499
    https://github.com/scummvm/scummvm/commit/ee6cf385c94557820aa1a0a82cdffdb011c10499
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: SpriteCache: corrected handling of "remapped" sprites

Remapped sprites are the sprites that were supposed to be present in the game
assets (according to the sprite index), but failed to load for whatever reasons.
The idea is that whenever a remapped sprite is requested, sprite cache should
return a placeholder sprite (sprite 0) instead.

This commit corrects the logic of this, where it would use remapped sprite index
one step earlier. This should fix incorrect duplicating addition of same "bad" sprite
index to the MRU list, and also saves little redundant actions where the LoadSprite
would reload the sprite 0 unnecessarily per each such request.

>From upstream eca2e2d3b47a7fcf235690ee0ebda9058a662c91

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 3bde2127948..99b03e1a550 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -206,12 +206,14 @@ Bitmap *SpriteCache::operator [] (sprkey_t index) {
 	// Externally added sprite or locked sprite, don't put it into MRU list
 	if (_spriteData[index].IsExternalSprite() || _spriteData[index].IsLocked())
 		return _spriteData[index].Image.get();
-
+	// Resolve potentially remapped sprites
+	index = GetDataIndex(index);
+	// Either use ready image, or load one from assets
 	if (_spriteData[index].Image) {
 		// Move to the beginning of the MRU list
 		_mru.splice(_mru.begin(), _mru, _spriteData[index].MruIt);
 	} else {
-		// Sprite exists in file but is not in mem, load it
+		// Sprite exists in file but is not in mem, load it and add to MRU list
 		LoadSprite(index);
 		_spriteData[index].MruIt = _mru.insert(_mru.begin(), index);
 	}
@@ -311,9 +313,8 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 		return 0;
 	assert((_spriteData[index].Flags & SPRCACHEFLAG_ISASSET) != 0);
 
-	sprkey_t load_index = GetDataIndex(index);
 	Bitmap *image;
-	HError err = _file.LoadSprite(load_index, image);
+	HError err = _file.LoadSprite(index, image);
 	if (!image) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
 			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,


Commit: e81c85c91766ccd0ea4e58dccd4be7ebd50e7ef0
    https://github.com/scummvm/scummvm/commit/e81c85c91766ccd0ea4e58dccd4be7ebd50e7ef0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: removed wrong cast to (char*)

>From upstream e4fa97f8a7042b282a0a6ab504e731f1c32a7d93

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 27d01b20b4d..37fbf86cb76 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2033,7 +2033,7 @@ int wantMoveNow(CharacterInfo *chi, CharacterExtras *chex) {
 void setup_player_character(int charid) {
 	_GP(game).playercharacter = charid;
 	_G(playerchar) = &_GP(game).chars[charid];
-	_G(sc_PlayerCharPtr) = ccGetObjectHandleFromAddress((char *)_G(playerchar));
+	_G(sc_PlayerCharPtr) = ccGetObjectHandleFromAddress(_G(playerchar));
 	if (_G(loaded_game_file_version) < kGameVersion_270) {
 		ccAddExternalScriptObject("player", _G(playerchar), &_GP(ccDynamicCharacter));
 	}
diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index f49de17ed3f..d37632eb4b1 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -235,7 +235,7 @@ void ManagedObjectPool::WriteToDisk(Stream *out) {
 		// handle
 		out->WriteInt32(o.handle);
 		// write the type of the object
-		StrUtil::WriteCStr(const_cast<char *>(o.callback->GetType()), out);
+		StrUtil::WriteCStr(o.callback->GetType(), out);
 		// now write the object data
 		int bytesWritten = o.callback->Serialize(o.addr, &serializeBuffer.front(), serializeBuffer.size());
 		if ((bytesWritten < 0) && ((size_t)(-bytesWritten) > serializeBuffer.size())) {


Commit: 59b44ac4961c0c135383019903f9f563f91a25c8
    https://github.com/scummvm/scummvm/commit/59b44ac4961c0c135383019903f9f563f91a25c8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Remove accidental duplication in ResourceCache

Changed paths:
    engines/ags/shared/util/resource_cache.h


diff --git a/engines/ags/shared/util/resource_cache.h b/engines/ags/shared/util/resource_cache.h
index 90abaacb0ab..174ce51b1f3 100644
--- a/engines/ags/shared/util/resource_cache.h
+++ b/engines/ags/shared/util/resource_cache.h
@@ -353,309 +353,6 @@ private:
 	TValue _dummy;
 };
 
-} // namespace Common
-} // namespace AGS
-
-#endif // __AGS_CN_UTIL__RESOURCECACHE_H#include <list>
-#include "util/string.h"
-#include <unordered_map>
-
-namespace AGS {
-namespace Common {
-
-template<typename TKey, typename TValue,
-		 typename TSize = size_t, typename HashFn = std::hash<TKey> >
-class ResourceCache {
-public:
-	// Flags determine management rules for the particular item
-	enum ItemFlags {
-		// Locked items are temporarily saved from disposal when freeing cache space;
-		// they still count towards the cache size though.
-		kCacheItem_Locked = 0x0001,
-		// External items are managed strictly by the external user;
-		// do not count towards the cache size, do not prevent caching normal items.
-		// They cannot be locked or released (considered permanently locked),
-		// only removed by request.
-		kCacheItem_External = 0x0002,
-	};
-
-	ResourceCache(TSize max_size = 0u)
-		: _maxSize(max_size), _sectionLocked(_mru.end()) {}
-
-	// Get the MRU cache size limit
-	inline size_t GetMaxCacheSize() const { return _maxSize; }
-	// Get the current total MRU cache size
-	inline size_t GetCacheSize() const { return _cacheSize; }
-	// Get the summed size of locked items (included in total cache size)
-	inline size_t GetLockedSize() const { return _lockedSize; }
-	// Get the summed size of external items (excluded from total cache size)
-	inline size_t GetExternalSize() const { return _externalSize; }
-
-	// Set the MRU cache size limit
-	void SetMaxCacheSize(TSize size = 0u) {
-		_maxSize = size;
-		FreeMem(0u); // makes sure it does not exceed max size
-	}
-
-	// Tells if particular key is in the cache
-	bool Exists(const TKey &key) const {
-		return _storage.find(key) != _storage.end();
-	}
-
-	// Gets the item with the given key if it exists;
-	// reorders the item as recently used.
-	const TValue &Get(const TKey &key) {
-		auto it = _storage.find(key);
-		if (it == _storage.end())
-			return _dummy; // no such key
-
-		// Unless locked, move the item ref to the beginning of the MRU list
-		const auto &item = it->second;
-		if ((item.Flags & kCacheItem_Locked) == 0)
-			_mru.splice(_mru.begin(), _mru, item.MruIt);
-		return item.Value;
-	}
-
-	// Add particular item into the cache, disposes existing item if such key is already taken.
-	// If a new item will exceed the cache size limit, cache will remove oldest items
-	// in order to free mem.
-	void Put(const TKey &key, const TValue &value, uint32_t flags = 0u) {
-		if (_maxSize == 0)
-			return; // cache is disabled
-		auto it = _storage.find(key);
-		if (it != _storage.end()) {
-			// Remove previous cached item
-			RemoveImpl(it);
-		}
-		PutImpl(key, TValue(value), flags); // make a temp local copy for safe std::move
-	}
-
-	void Put(const TKey &key, TValue &&value, uint32_t flags = 0u) {
-		if (_maxSize == 0)
-			return; // cache is disabled
-		auto it = _storage.find(key);
-		if (it != _storage.end()) {
-			// Remove previous cached item
-			RemoveImpl(it);
-		}
-		PutImpl(key, std::move(value), flags);
-	}
-
-	// Locks the item with the given key,
-	// temporarily excluding it from MRU disposal rules
-	void Lock(const TKey &key) {
-		auto it = _storage.find(key);
-		if (it == _storage.end())
-			return; // no such key
-		auto &item = it->second;
-		if ((item.Flags & kCacheItem_Locked) != 0)
-			return; // already locked
-
-		// Lock item and move to the locked section
-		item.Flags |= kCacheItem_Locked;
-		_mru.splice(_sectionLocked, _mru, item.MruIt); // CHECKME: TEST!!
-		_sectionLocked = item.MruIt;
-		_lockedSize += item.Size;
-	}
-
-	// Releases (unlocks) the item with the given key,
-	// adds it back to MRU disposal rules
-	void Release(const TKey &key) {
-		auto it = _storage.find(key);
-		if (it == _storage.end())
-			return; // no such key
-
-		auto &item = it->second;
-		if ((item.Flags & kCacheItem_External) != 0)
-			return; // never release external data, must be removed by user
-		if ((item.Flags & kCacheItem_Locked) == 0)
-			return; // not locked
-
-		// Unlock, and move the item to the beginning of the MRU list
-		item.Flags &= ~kCacheItem_Locked;
-		if (_sectionLocked == item.MruIt)
-			_sectionLocked = std::next(item.MruIt);
-		_mru.splice(_mru.begin(), _mru, item.MruIt); // CHECKME: TEST!!
-		_lockedSize -= item.Size;
-	}
-
-	// Deletes the cached item
-	void Dispose(const TKey &key) {
-		auto it = _storage.find(key);
-		if (it == _storage.end())
-			return; // no such key
-		RemoveImpl(it);
-	}
-
-	// Removes the item from the cache and returns to the caller.
-	TValue Remove(const TKey &key) {
-		auto it = _storage.find(key);
-		if (it == _storage.end())
-			return TValue(); // no such key
-		TValue value = std::move(it->second.Value);
-		RemoveImpl(it);
-		return value;
-	}
-
-	// Disposes all items that are not locked or external
-	void DisposeFreeItems() {
-		for (auto mru_it = _sectionLocked; mru_it != _mru.end(); ++mru_it) {
-			auto it = _storage.find(*mru_it);
-			assert(it != _storage.end());
-			auto &item = it->second;
-			_cacheSize -= item.Size;
-			_storage.erase(it);
-			_mru.erase(mru_it);
-		}
-	}
-
-	// Clear the cache, dispose all items
-	void Clear() {
-		_storage.clear();
-		_mru.clear();
-		_sectionLocked = _mru.end();
-		_cacheSize = 0u;
-		_lockedSize = 0u;
-		_externalSize = 0u;
-	}
-
-protected:
-	struct TItem;
-	// MRU list type
-	typedef std::list<TKey> TMruList;
-	// MRU list reference type
-	typedef typename TMruList::iterator TMruIt;
-	// Storage type
-	typedef std::unordered_map<TKey, TItem, HashFn> TStorage;
-
-	struct TItem {
-		TMruIt MruIt; // MRU list reference
-		TValue Value;
-		TSize Size = 0u;
-		uint32_t Flags = 0u; // flags determine management rules for this item
-
-		TItem() = default;
-		TItem(const TItem &item) = default;
-		TItem(TItem &&item) = default;
-		TItem(const TMruIt &mru_it, const TValue &value, const TSize size, uint32_t flags)
-			: MruIt(mru_it), Value(value), Size(size), Flags(flags) {}
-		TItem(const TMruIt &mru_it, TValue &&value, const TSize size, uint32_t flags)
-			: MruIt(mru_it), Value(std::move(value)), Size(size), Flags(flags) {}
-		TItem &operator=(const TItem &item) = default;
-		TItem &operator=(TItem &&item) = default;
-	};
-
-	// Calculates item size; expects to return 0 if an item is invalid
-	// and should not be added to the cache.
-	virtual TSize CalcSize(const TValue &item) = 0;
-
-private:
-	// Add particular item into the cache.
-	// If a new item will exceed the cache size limit, cache will remove oldest items
-	// in order to free mem.
-	void PutImpl(const TKey &key, TValue &&value, uint32_t flags) {
-		// Request item's size, and test if it's a valid item
-		TSize size = CalcSize(value);
-		if (size == 0u)
-			return; // invalid item
-
-		if ((flags & kCacheItem_External) == 0) {
-			// clear up space before adding
-			if (_cacheSize + size > _maxSize)
-				FreeMem(size);
-			_cacheSize += size;
-		} else {
-			// always mark external data as locked, easier to handle
-			flags |= kCacheItem_Locked;
-			_externalSize += size;
-		}
-
-		// Prepare a MRU slot, then add an item
-		TMruIt mru_it = _mru.end();
-		// only normal items are added to MRU at all
-		if ((flags & kCacheItem_External) == 0) {
-			if ((flags & kCacheItem_Locked) == 0) {
-				// normal item, add to the list
-				mru_it = _mru.insert(_mru.begin(), key);
-			} else {
-				// locked item, add to the dedicated list section
-				mru_it = _mru.insert(_sectionLocked, key);
-				_sectionLocked = mru_it;
-				_lockedSize += size;
-			}
-		}
-		TItem item = TItem(mru_it, std::move(value), size, flags);
-		_storage[key] = std::move(item);
-	}
-	// Removes the item from the container
-	void RemoveImpl(typename TStorage::iterator it) {
-		auto &item = it->second;
-		// normal items are removed from MRU, and discounted from cache size
-		if ((item.Flags & kCacheItem_External) == 0) {
-			TMruIt mru_it = item.MruIt;
-			if (_sectionLocked == mru_it)
-				_sectionLocked = std::next(mru_it);
-			_cacheSize -= item.Size;
-			if ((item.Flags & kCacheItem_Locked) != 0)
-				_lockedSize -= item.Size;
-			_mru.erase(mru_it);
-		} else {
-			_externalSize -= item.Size;
-		}
-		_storage.erase(it);
-	}
-	// Remove the oldest (least recently used) item in cache
-	void DisposeOldest() {
-		assert(_mru.begin() != _sectionLocked);
-		if (_mru.begin() == _sectionLocked)
-			return;
-		// Remove from the storage and mru list
-		auto mru_it = std::prev(_sectionLocked);
-		auto it = _storage.find(*mru_it);
-		assert(it != _storage.end());
-		auto &item = it->second;
-		assert((item.Flags & (kCacheItem_Locked | kCacheItem_External)) == 0);
-		_cacheSize -= item.Size;
-		_storage.erase(it);
-		_mru.erase(mru_it);
-	}
-	// Keep disposing oldest elements until cache has at least the given free space
-	void FreeMem(size_t space) {
-		// TODO: consider sprite cache's behavior where it would just clear
-		// whole cache in case disposing one by one were taking too much iterations
-		while ((_mru.begin() != _sectionLocked) && (_cacheSize + space > _maxSize)) {
-			DisposeOldest();
-		}
-	}
-
-	// Size of tracked data stored in this cache;
-	// note that this is an abstract value, which may or not refer to an
-	// actual size in bytes, and depends on the implementation.
-	TSize _cacheSize = 0u;
-	// Size of data locked (forbidden from disposal),
-	// this size is *included* in _cacheSize; provided for stats.
-	TSize _lockedSize = 0u;
-	// Size of the external data, that is - data that does not count towards
-	// cache limit, and which is not our reponsibility; provided for stats.
-	TSize _externalSize = 0u;
-	// Maximal size of tracked data.
-	// When the inserted item increases the cache size past this limit,
-	// the cache will try to free the space by removing oldest items.
-	// "External" data does not count towards this limit.
-	TSize _maxSize = 0u;
-	// MRU list: the way to track which items were used recently.
-	// When clearing up space for new items, cache first deletes the items
-	// that were last time used long ago.
-	TMruList _mru;
-	// A locked section border iterator, points to the *last* locked item
-	// starting from the end of the list, or equals _mru.end() if there's none.
-	TMruIt _sectionLocked;
-	// Key-to-mru lookup map
-	TStorage _storage;
-	// Dummy value, return in case of a missing key
-	TValue _dummy;
-};
-
 } // namespace Shared
 } // namespace AGS
 } // namespace AGS3


Commit: b498b14eccaaa07b49042b00c069b4ced68a800e
    https://github.com/scummvm/scummvm/commit/b498b14eccaaa07b49042b00c069b4ced68a800e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: save global draw method args in DrawState struct

Avoid query gfxDriver's properties all the time, instead save them once in init_draw_method() and use saved values.

>From upstream db535512af65119d50ae3d6fb06c221d56f42d85

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character.h
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_object.h
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/object.h
    engines/ags/engine/ac/walk_behind.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/gfx_driver_base.cpp
    engines/ags/engine/gfx/gfx_driver_base.h
    engines/ags/engine/gfx/graphics_driver.h
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 37fbf86cb76..733cbafd9df 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -500,13 +500,13 @@ int Character_IsCollidingWithObject(CharacterInfo *chin, ScriptObject *objid) {
 	if (_G(objs)[objid->id].on != 1)
 		return 0;
 
-	Bitmap *checkblk = GetObjectImage(objid->id, nullptr);
+	Bitmap *checkblk = GetObjectImage(objid->id);
 	int objWidth = checkblk->GetWidth();
 	int objHeight = checkblk->GetHeight();
 	int o1x = _G(objs)[objid->id].x;
 	int o1y = _G(objs)[objid->id].y - game_to_data_coord(objHeight);
 
-	Bitmap *charpic = GetCharacterImage(chin->index_id, nullptr);
+	Bitmap *charpic = GetCharacterImage(chin->index_id);
 
 	int charWidth = charpic->GetWidth();
 	int charHeight = charpic->GetHeight();
@@ -2103,16 +2103,13 @@ int GetCharacterFrameVolume(CharacterInfo *chi) {
 	return frame_vol;
 }
 
-Bitmap *GetCharacterImage(int charid, int *isFlipped) {
-	if (!_G(gfxDriver)->HasAcceleratedTransform()) {
-		Bitmap *actsp = get_cached_character_image(charid);
-		if (actsp) {
-			// the cached image is pre-flipped, so no longer register the image as such
-			if (isFlipped)
-				*isFlipped = 0;
-			return actsp;
-		}
-	}
+Bitmap *GetCharacterImage(int charid, bool *is_original) {
+	// NOTE: the cached image will only be present in software render mode
+	Bitmap *actsp = get_cached_character_image(charid);
+	if (is_original)
+		*is_original = !actsp; // no cached means we use original sprite
+	if (actsp)
+		return actsp;
 	CharacterInfo *chin = &_GP(game).chars[charid];
 	int sppic = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
 	return _GP(spriteset)[sppic];
@@ -2199,11 +2196,15 @@ int is_pos_on_character(int xx, int yy) {
 		int yyy = _GP(charextra)[cc].GetEffectiveY(chin) - game_to_data_coord(usehit);
 
 		int mirrored = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE;
-		Bitmap *theImage = GetCharacterImage(cc, &mirrored);
+
+		bool is_original;
+		Bitmap *theImage = GetCharacterImage(cc, &is_original);
+		if (!is_original)
+			mirrored = 0; // transformed image is already flipped
 
 		if (is_pos_in_sprite(xx, yy, xxx, yyy, theImage,
 		                     game_to_data_coord(usewid),
-		                     game_to_data_coord(usehit), mirrored) == FALSE)
+		                     game_to_data_coord(usehit), mirrored, is_original) == FALSE)
 			continue;
 
 		int use_base = chin->get_baseline();
diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h
index 2927b3a64cc..83fd4957934 100644
--- a/engines/ags/engine/ac/character.h
+++ b/engines/ags/engine/ac/character.h
@@ -209,7 +209,7 @@ void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int
 int  wantMoveNow(CharacterInfo *chi, CharacterExtras *chex);
 void setup_player_character(int charid);
 int  GetCharacterFrameVolume(CharacterInfo *chi);
-Shared::Bitmap *GetCharacterImage(int charid, int *isFlipped);
+Shared::Bitmap *GetCharacterImage(int charid, bool *is_original = nullptr);
 CharacterInfo *GetCharacterAtScreen(int xx, int yy);
 // Deduces room object's scale, accounting for both manual scaling and the room region effects;
 // calculates resulting sprite size.
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 693812fd2b8..1e0de687ad0 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -77,7 +77,22 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-int _places_r = 3, _places_g = 2, _places_b = 3;
+// TODO: refactor the draw unit into a virtual interface with
+// two implementations: for software and video-texture render,
+// instead of checking whether the current method is "software".
+struct DrawState {
+	// Whether we should use software rendering methods
+	// (aka raw draw), as opposed to video texture transform & fx
+	bool SoftwareRender = false;
+	// Whether we should redraw whole game screen each frame
+	bool FullFrameRedraw = false;
+	// Walk-behinds representation
+	WalkBehindMethodEnum WalkBehindMethod = DrawAsSeparateSprite;
+	// Whether there are currently remnants of a on-screen effect
+	bool ScreenIsDirty = false;
+};
+
+DrawState drawstate;
 
 ObjTexture::ObjTexture(ObjTexture &&o) {
 	*this = std::move(o);
@@ -363,11 +378,16 @@ int MakeColor(int color_index) {
 }
 
 void init_draw_method() {
-	if (_G(gfxDriver)->HasAcceleratedTransform()) {
-		_G(walkBehindMethod) = DrawAsSeparateSprite;
-		create_blank_image(_GP(game).GetColorDepth());
+	drawstate.SoftwareRender = !_G(gfxDriver)->HasAcceleratedTransform();
+	drawstate.FullFrameRedraw = _G(gfxDriver)->RequiresFullRedrawEachFrame();
+
+	if (drawstate.SoftwareRender) {
+		drawstate.SoftwareRender = true;
+		drawstate.WalkBehindMethod = DrawOverCharSprite;
 	} else {
-		_G(walkBehindMethod) = DrawOverCharSprite;
+		drawstate.WalkBehindMethod = DrawAsSeparateSprite;
+		create_blank_image(_GP(game).GetColorDepth());
+		// texturecache.SetMaxCacheSize(usetup.TextureCacheSize);
 	}
 
 	on_mainviewport_changed();
@@ -453,7 +473,7 @@ void clear_drawobj_cache() {
 }
 
 void on_mainviewport_changed() {
-	if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
+	if (!drawstate.FullFrameRedraw) {
 		const auto &view = _GP(play).GetMainViewport();
 		set_invalidrects_globaloffs(view.Left, view.Top);
 		// the black background region covers whole game screen
@@ -508,12 +528,18 @@ void sync_roomview(Viewport *view) {
 }
 
 void init_room_drawdata() {
+	if (_G(displayed_room) < 0)
+		return; // not loaded yet
+
+	if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
+		walkbehinds_generate_sprites();
+	}
 	// Update debug overlays, if any were on
 	debug_draw_room_mask(_G(debugRoomMask));
 	debug_draw_movelist(_G(debugMoveListChar));
 
 	// Following data is only updated for software renderer
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	// Make sure all frame buffers are created for software drawing
 	int view_count = _GP(play).GetRoomViewportCount();
@@ -523,7 +549,7 @@ void init_room_drawdata() {
 }
 
 void on_roomviewport_created(int index) {
-	if (!_G(gfxDriver) || _G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (!_G(gfxDriver) || drawstate.FullFrameRedraw)
 		return;
 	if ((size_t)index < _GP(CameraDrawData).size())
 		return;
@@ -531,14 +557,14 @@ void on_roomviewport_created(int index) {
 }
 
 void on_roomviewport_deleted(int index) {
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	_GP(CameraDrawData).erase(_GP(CameraDrawData).begin() + index);
 	delete_invalid_regions(index);
 }
 
 void on_roomviewport_changed(Viewport *view) {
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	if (!view->IsVisible() || view->GetCamera() == nullptr)
 		return;
@@ -556,7 +582,7 @@ void on_roomviewport_changed(Viewport *view) {
 }
 
 void detect_roomviewport_overlaps(size_t z_index) {
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	// Find out if we overlap or are overlapped by anything;
 	const auto &viewports = _GP(play).GetRoomViewportsZOrdered();
@@ -580,7 +606,7 @@ void detect_roomviewport_overlaps(size_t z_index) {
 }
 
 void on_roomcamera_changed(Camera *cam) {
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	if (cam->HasChangedSize()) {
 		auto viewrefs = cam->GetLinkedViewports();
@@ -623,11 +649,11 @@ void reset_objcache_for_sprite(int sprnum, bool deleted) {
 }
 
 void mark_screen_dirty() {
-	_G(screen_is_dirty) = true;
+	drawstate.ScreenIsDirty = true;
 }
 
 bool is_screen_dirty() {
-	return _G(screen_is_dirty);
+	return drawstate.ScreenIsDirty;
 }
 
 void invalidate_screen() {
@@ -680,11 +706,10 @@ static void render_black_borders() {
 }
 
 void render_to_screen() {
-	const bool full_frame_rend = _G(gfxDriver)->RequiresFullRedrawEachFrame();
 	// Stage: final plugin callback (still drawn on game screen
 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-										_GP(play).GetGlobalTransform(full_frame_rend), (GraphicFlip)_GP(play).screen_flipped);
+										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped);
 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
 		_G(gfxDriver)->EndSpriteBatch();
 	}
@@ -704,7 +729,7 @@ void render_to_screen() {
 	while (!succeeded && !_G(want_exit) && !_G(abort_engine)) {
 		//     try
 		//     {
-		if (full_frame_rend) {
+		if (drawstate.FullFrameRedraw) {
 			_G(gfxDriver)->Render();
 		}
 		else {
@@ -848,7 +873,7 @@ static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zo
 	sprite.x = x;
 	sprite.y = y;
 
-	if (_G(walkBehindMethod) == DrawAsSeparateSprite)
+	if (drawstate.WalkBehindMethod == DrawAsSeparateSprite)
 		sprite.takesPriorityIfEqual = !isWalkBehind;
 	else
 		sprite.takesPriorityIfEqual = isWalkBehind;
@@ -1181,7 +1206,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 								 ObjTexture &actsp,			// object texture to draw upon
 								 bool optimize_by_position, // allow to optimize walk-behind merging using object's pos
 								 bool force_software) {
-	const bool use_hw_transform = !force_software && _G(gfxDriver)->HasAcceleratedTransform();
+	const bool use_hw_transform = !force_software && !drawstate.SoftwareRender;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
@@ -1217,7 +1242,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// NOTE: we need cached bitmap if:
 	// * it's a software renderer, otherwise
 	// * the walk-behind method is DrawOverCharSprite
-	if ((use_hw_transform) && (_G(walkBehindMethod) != DrawOverCharSprite)) {
+	if ((use_hw_transform) && (drawstate.WalkBehindMethod != DrawOverCharSprite)) {
 		// HW acceleration
 		const bool is_texture_intact = objsav.sppic == specialpic;
 		objsav.sppic = specialpic;
@@ -1236,8 +1261,8 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// Software mode below
 	//
 
-	if ((!use_hw_transform) && (_G(gfxDriver)->HasAcceleratedTransform())) {
-		// They want to draw it in software mode with the D3D driver, so force a redraw
+	if ((!use_hw_transform) && (!drawstate.SoftwareRender)) {
+		// They want to draw it in software mode with the hw driver, so force a redraw
 		objsav.sppic = INT32_MIN;
 	}
 
@@ -1253,7 +1278,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 			(objsav.zoom == objsrc.zoom) &&
 			(objsav.mirrored == is_mirrored)) {
 		// the image is the same, we can use it cached!
-		if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
+		if ((drawstate.WalkBehindMethod != DrawOverCharSprite) &&
 			(actsp.Bmp != nullptr))
 			return true;
 		// Check if the X & Y co-ords are the same, too -- if so, there
@@ -1329,13 +1354,13 @@ void prepare_and_add_object_gfx(const ObjectCache &objsav, ObjTexture &actsp, bo
 	// This potentially may edit actsp's raw bitmap if actsp_modified is set.
 	if (use_walkbehinds) {
 		// Only merge sprite with the walk-behinds in software mode
-		if ((_G(walkBehindMethod) == DrawOverCharSprite) && (actsp_modified)) {
+		if ((drawstate.WalkBehindMethod == DrawOverCharSprite) && (actsp_modified)) {
 			walkbehinds_cropout(actsp.Bmp.get(), atx, aty, usebasel);
 		}
 	} else {
 		// Ignore walk-behinds by shifting baseline to a larger value
 		// CHECKME: may this fail if WB somehow got larger than room baseline?
-		if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
+		if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
 			usebasel += _GP(thisroom).Height;
 		}
 	}
@@ -1393,7 +1418,7 @@ bool construct_object_gfx(int objid, bool force_software) {
 void prepare_objects_for_drawing() {
 	_G(our_eip) = 32;
 
-	const bool hw_accel = _G(gfxDriver)->HasAcceleratedTransform();
+	const bool hw_accel = !drawstate.SoftwareRender;
 
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		const RoomObject &obj = _G(objs)[objid];
@@ -1500,7 +1525,7 @@ bool construct_char_gfx(int charid, bool force_software) {
 
 void prepare_characters_for_drawing() {
 	_G(our_eip) = 33;
-	const bool hw_accel = _G(gfxDriver)->HasAcceleratedTransform();
+	const bool hw_accel = !drawstate.SoftwareRender;
 
 	// draw characters
 	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
@@ -1569,9 +1594,9 @@ void prepare_room_sprites() {
 			recycle_ddb_bitmap(_G(roomBackgroundBmp), _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false, true);
 
 	}
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
+	if (drawstate.FullFrameRedraw) {
 		if (_G(current_background_is_dirty) || _G(walkBehindsCachedForBgNum) != _GP(play).bg_frame) {
-			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
+			if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
 				walkbehinds_generate_sprites();
 			}
 		}
@@ -1589,7 +1614,7 @@ void prepare_room_sprites() {
 		if ((_G(debug_flags) & DBG_NODRAWSPRITES) == 0) {
 			_G(our_eip) = 34;
 
-			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
+			if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
 				for (size_t wb = 1 /* 0 is "no area" */;
 					(wb < MAX_WALK_BEHINDS) && (wb < (size_t)_GP(walkbehindobj).size()); ++wb) {
 					const auto &wbobj = _GP(walkbehindobj)[wb];
@@ -1621,7 +1646,7 @@ void prepare_room_sprites() {
 
 // Draws the black surface behind (or rather between) the room viewports
 void draw_preroom_background() {
-	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
+	if (drawstate.FullFrameRedraw)
 		return;
 	update_black_invreg_and_reset(_G(gfxDriver)->GetMemoryBackBuffer());
 }
@@ -1918,7 +1943,7 @@ static void construct_room_view() {
 		const SpriteTransform view_trans(view_rc.Left, view_rc.Top, view_sx, view_sy);
 		const SpriteTransform cam_trans(-cam_rc.Left, -cam_rc.Top);
 
-		if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
+		if (drawstate.FullFrameRedraw) {
 			// For hw renderer we draw everything as a sprite stack;
 			// viewport-camera pair is done as 2 nested scene nodes,
 			// where first defines how camera's image translates into the viewport on screen,
@@ -1972,7 +1997,7 @@ static void construct_ui_view() {
 // Prepares overlay textures;
 // but does not put them on screen yet - that's done in respective construct_*_view functions
 static void construct_overlays() {
-	const bool is_software_mode = !_G(gfxDriver)->HasAcceleratedTransform();
+	const bool is_software_mode = drawstate.SoftwareRender;
 	auto &overs = get_overlays();
 	if (_GP(overlaybmp).size() < overs.size()) {
 		_GP(overlaybmp).resize(overs.size());
@@ -1984,7 +2009,7 @@ static void construct_overlays() {
 		if (over.transparency == 255) continue; // skip fully transparent
 
 		bool has_changed = over.HasChanged();
-		if (over.IsRoomLayer() && (_G(walkBehindMethod) == DrawOverCharSprite)) {
+		if (over.IsRoomLayer() && (drawstate.WalkBehindMethod == DrawOverCharSprite)) {
 			Point pos = get_overlay_position(over);
 			has_changed |= (pos.X != _GP(screenovercache)[i].X || pos.Y != _GP(screenovercache)[i].Y);
 			_GP(screenovercache)[i].X = pos.X; _GP(screenovercache)[i].Y = pos.Y;
@@ -1996,7 +2021,7 @@ static void construct_overlays() {
 				transform_sprite(over.GetImage(), over.HasAlphaChannel(), _GP(overlaybmp)[i], Size(over.scaleWidth, over.scaleHeight)) :
 				over.GetImage();
 
-			if ((_G(walkBehindMethod) == DrawOverCharSprite) && over.IsRoomLayer()) {
+			if ((drawstate.WalkBehindMethod == DrawOverCharSprite) && over.IsRoomLayer()) {
 				if (use_bmp != _GP(overlaybmp)[i].get()) {
 					recycle_bitmap(_GP(overlaybmp)[i], use_bmp->GetColorDepth(), use_bmp->GetWidth(), use_bmp->GetHeight(), true);
 					_GP(overlaybmp)[i]->Blit(use_bmp);
@@ -2046,16 +2071,15 @@ void construct_game_scene(bool full_redraw) {
 		_GP(play).UpdateRoomCameras();
 
 	// Begin with the parent scene node, defining global offset and flip
-	bool full_frame_rend = _G(gfxDriver)->RequiresFullRedrawEachFrame();
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-									_GP(play).GetGlobalTransform(full_frame_rend),
+									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
 									(GraphicFlip)_GP(play).screen_flipped);
 
 	// Stage: room viewports
 	if (_GP(play).screen_is_faded_out == 0 && _GP(play).complete_overlay_on == 0) {
 		if (_G(displayed_room) >= 0) {
 			construct_room_view();
-		} else if (!full_frame_rend) {
+		} else if (!drawstate.FullFrameRedraw) {
 			// black it out so we don't get cursor trails
 			// TODO: this is possible to do with dirty rects system now too (it can paint black rects outside of room viewport)
 			_G(gfxDriver)->GetMemoryBackBuffer()->Fill(0);
@@ -2074,9 +2098,8 @@ void construct_game_scene(bool full_redraw) {
 }
 
 void construct_game_screen_overlay(bool draw_mouse) {
-	const bool full_frame_rend = _G(gfxDriver)->RequiresFullRedrawEachFrame();
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-									_GP(play).GetGlobalTransform(full_frame_rend),
+									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
 									(GraphicFlip)_GP(play).screen_flipped);
 	if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) {
 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
@@ -2096,7 +2119,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 	_G(gfxDriver)->EndSpriteBatch();
 
 	// For hardware-accelerated renderers: legacy letterbox and global screen fade effect
-	if (full_frame_rend) {
+	if (drawstate.FullFrameRedraw) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 		// Stage: legacy letterbox mode borders
 		if (_GP(play).screen_is_faded_out == 0)
@@ -2143,7 +2166,7 @@ void debug_draw_room_mask(RoomAreaMask mask) {
 
 	// Software mode scaling
 	// note we don't use transparency in software mode - may be slow in hi-res games
-	if (!_G(gfxDriver)->HasAcceleratedTransform() &&
+	if (drawstate.SoftwareRender &&
 		(mask != kRoomAreaWalkBehind) &&
 		(bmp->GetSize() != Size(_GP(thisroom).Width, _GP(thisroom).Height))) {
 		recycle_bitmap(_GP(debugRoomMaskObj).Bmp,
@@ -2165,7 +2188,7 @@ void update_room_debug() {
 	if (_G(debugRoomMask) == kRoomAreaWalkable) {
 		Bitmap *bmp = prepare_walkable_areas(-1);
 		// Software mode scaling
-		if (!_G(gfxDriver)->HasAcceleratedTransform() && (_GP(thisroom).MaskResolution > 1)) {
+		if (drawstate.SoftwareRender && (_GP(thisroom).MaskResolution > 1)) {
 			recycle_bitmap(_GP(debugRoomMaskObj).Bmp,
 				bmp->GetColorDepth(), _GP(thisroom).Width, _GP(thisroom).Height);
 			_GP(debugRoomMaskObj).Bmp->StretchBlt(bmp, RectWH(0, 0, _GP(thisroom).Width, _GP(thisroom).Height));
@@ -2176,13 +2199,13 @@ void update_room_debug() {
 		_GP(debugRoomMaskObj).Ddb->SetStretch(_GP(thisroom).Width, _GP(thisroom).Height);
 	}
 	if (_G(debugMoveListChar) >= 0) {
-		const int mult = _G(gfxDriver)->HasAcceleratedTransform() ? _GP(thisroom).MaskResolution : 1;
-		if (_G(gfxDriver)->HasAcceleratedTransform())
+		const int mult = drawstate.SoftwareRender ? 1 : _GP(thisroom).MaskResolution;
+		if (drawstate.SoftwareRender)
 			recycle_bitmap(_GP(debugMoveListObj).Bmp, _GP(game).GetColorDepth(),
-				_GP(thisroom).WalkAreaMask->GetWidth(), _GP(thisroom).WalkAreaMask->GetHeight(), true);
+				_GP(thisroom).Width, _GP(thisroom).Height, true);
 		else
 			recycle_bitmap(_GP(debugMoveListObj).Bmp, _GP(game).GetColorDepth(),
-				_GP(thisroom).Width, _GP(thisroom).Height, true);
+				_GP(thisroom).WalkAreaMask->GetWidth(), _GP(thisroom).WalkAreaMask->GetHeight(), true);
 
 		if (_GP(game).chars[_G(debugMoveListChar)].walking > 0) {
 			int mlsnum = _GP(game).chars[_G(debugMoveListChar)].walking;
@@ -2222,7 +2245,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 	// TODO: extraBitmap is a hack, used to place an additional gui element
 	// on top of the screen. Normally this should be a part of the game UI stage.
 	if (extraBitmap != nullptr) {
-		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), _GP(play).GetGlobalTransform(_G(gfxDriver)->RequiresFullRedrawEachFrame()),
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), _GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
 										(GraphicFlip)_GP(play).screen_flipped);
 		invalidate_sprite(extraX, extraY, extraBitmap, false);
 		_G(gfxDriver)->DrawSprite(extraX, extraY, extraBitmap);
@@ -2240,7 +2263,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 		}
 	}
 
-	_G(screen_is_dirty) = false;
+	drawstate.ScreenIsDirty = false;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 85d6c10aa07..acc5f33a303 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -864,7 +864,7 @@ void save_game(int slotn, const char *descript) {
 	// WORKAROUND: AGS originally only creates savegames if the game flags
 	// that it supports it. But we want it all the time for ScummVM GMM,
 	// unless explicitly disabled through gameflag
-	if ((/*_GP(game).options[OPT_SAVESCREENSHOT] != 0*/ true) && _G(saveThumbnail))
+	if ((/*_GP(game).options[OPT_SAVESCREENSHOT] != 0*/ true) && _G(saveThumbnail) && slotn != 999)
 		screenShot.reset(create_savegame_screenshot());
 
 	std::unique_ptr<Stream> out(StartSavegame(nametouse, descript, screenShot.get()));
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 8e901ab83a3..997e7b2237b 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -75,10 +75,13 @@ int GetObjectIDAtRoom(int roomx, int roomy) {
 		if (_G(objs)[aa].view != RoomObject::NoView)
 			isflipped = _GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE;
 
-		Bitmap *theImage = GetObjectImage(aa, &isflipped);
+		bool is_original;
+		Bitmap *theImage = GetObjectImage(aa, &is_original);
+		if (!is_original)
+			isflipped = 0; // transformed image is already flipped
 
 		if (is_pos_in_sprite(roomx, roomy, xxx, yyy - spHeight, theImage,
-		                     spWidth, spHeight, isflipped) == FALSE)
+		                     spWidth, spHeight, isflipped, is_original) == FALSE)
 			continue;
 
 		int usebasel = _G(objs)[aa].get_baseline();
@@ -517,16 +520,13 @@ void GetObjectPropertyText(int item, const char *property, char *bufer) {
 	get_text_property(_GP(thisroom).Objects[item].Properties, _G(croom)->objProps[item], property, bufer);
 }
 
-Bitmap *GetObjectImage(int obj, int *isFlipped) {
-	if (!_G(gfxDriver)->HasAcceleratedTransform()) {
-		Bitmap *actsp = get_cached_object_image(obj);
-		if (actsp) {
-			// the cached image is pre-flipped, so no longer register the image as such
-			if (isFlipped)
-				*isFlipped = 0;
-			return actsp;
-		}
-	}
+Bitmap *GetObjectImage(int obj, bool *is_original) {
+	// NOTE: the cached image will only be present in software render mode
+	Bitmap *actsp = get_cached_object_image(obj);
+	if (is_original)
+		*is_original = !actsp; // no cached means we use original sprite
+	if (actsp)
+		return actsp;
 	return _GP(spriteset)[_G(objs)[obj].num];
 }
 
diff --git a/engines/ags/engine/ac/global_object.h b/engines/ags/engine/ac/global_object.h
index 4fc3aa1e377..7d136f97588 100644
--- a/engines/ags/engine/ac/global_object.h
+++ b/engines/ags/engine/ac/global_object.h
@@ -79,7 +79,7 @@ int  AreThingsOverlapping(int thing1, int thing2);
 int  GetObjectProperty(int hss, const char *property);
 void GetObjectPropertyText(int item, const char *property, char *bufer);
 
-Shared::Bitmap *GetObjectImage(int obj, int *isFlipped);
+Shared::Bitmap *GetObjectImage(int obj, bool *is_original = nullptr);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 4916b894d63..c0baa9c1a9f 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -550,8 +550,9 @@ int isposinbox(int mmx, int mmy, int lf, int tp, int rt, int bt) {
 }
 
 // xx,yy is the position in room co-ordinates that we are checking
-// arx,ary is the sprite x/y co-ordinates
-int is_pos_in_sprite(int xx, int yy, int arx, int ary, Bitmap *sprit, int spww, int sphh, int flipped) {
+// arx,ary,spww,sphh are the sprite's bounding box
+// bitmap_original tells whether bitmap is an original sprite, or transformed version
+int is_pos_in_sprite(int xx, int yy, int arx, int ary, Bitmap *sprit, int spww, int sphh, int flipped, bool bitmap_original) {
 	if (spww == 0) spww = game_to_data_coord(sprit->GetWidth()) - 1;
 	if (sphh == 0) sphh = game_to_data_coord(sprit->GetHeight()) - 1;
 
@@ -563,10 +564,9 @@ int is_pos_in_sprite(int xx, int yy, int arx, int ary, Bitmap *sprit, int spww,
 		int xpos = data_to_game_coord(xx - arx);
 		int ypos = data_to_game_coord(yy - ary);
 
-		if (_G(gfxDriver)->HasAcceleratedTransform()) {
-			// hardware acceleration, so the sprite in memory will not have
-			// been stretched, it will be original size. Thus, adjust our
-			// calculations to compensate
+		if (bitmap_original) {
+			// Bitmap has original sprite's resolution,
+			// thus adjust our calculations to compensate
 			data_to_game_coords(&spww, &sphh);
 
 			if (spww != sprit->GetWidth())
diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h
index 219bad051e5..f9fa0769c24 100644
--- a/engines/ags/engine/ac/object.h
+++ b/engines/ags/engine/ac/object.h
@@ -109,7 +109,10 @@ void    update_object_scale(int &res_zoom, int &res_width, int &res_height,
 void    move_object(int objj, int tox, int toy, int spee, int ignwal);
 void    get_object_blocking_rect(int objid, int *x1, int *y1, int *width, int *y2);
 int     isposinbox(int mmx, int mmy, int lf, int tp, int rt, int bt);
-int     is_pos_in_sprite(int xx, int yy, int arx, int ary, Shared::Bitmap *sprit, int spww, int sphh, int flipped = 0);
+// xx,yy is the position in room co-ordinates that we are checking
+// arx,ary,spww,sphh are the sprite's bounding box (including sprite scaling);
+// bitmap_original tells whether bitmap is an original sprite, or transformed version
+int     is_pos_in_sprite(int xx, int yy, int arx, int ary, Shared::Bitmap *sprit, int spww, int sphh, int flipped, bool bitmap_original);
 // X and Y co-ordinates must be in native format
 // X and Y are ROOM coordinates
 int     check_click_on_object(int roomx, int roomy, int mood);
diff --git a/engines/ags/engine/ac/walk_behind.cpp b/engines/ags/engine/ac/walk_behind.cpp
index b577f80c8fe..3a193e91399 100644
--- a/engines/ags/engine/ac/walk_behind.cpp
+++ b/engines/ags/engine/ac/walk_behind.cpp
@@ -159,10 +159,6 @@ void walkbehinds_recalc() {
 			}
 		}
 	}
-
-	if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
-		walkbehinds_generate_sprites();
-	}
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 101e0aab110..56ad587656b 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -163,6 +163,12 @@ public:
 	const char *GetDriverID() override {
 		return "Software";
 	}
+
+	bool RequiresFullRedrawEachFrame() override { return false; }
+	bool HasAcceleratedTransform() override { return false; }
+	bool UsesMemoryBackBuffer() override { return true; }
+	bool ShouldReleaseRenderTargets() override { return false; }
+
 	const char *GetDriverName() override {
 		return "ScummVM 2D renderer";
 	}
@@ -220,15 +226,6 @@ public:
 	void UseSmoothScaling(bool /*enabled*/) override {}
 	bool DoesSupportVsyncToggle() override;
 	void RenderSpritesAtScreenResolution(bool /*enabled*/, int /*supersampling*/) override {}
-	bool RequiresFullRedrawEachFrame() override {
-		return false;
-	}
-	bool HasAcceleratedTransform() override {
-		return false;
-	}
-	bool UsesMemoryBackBuffer() override {
-		return true;
-	}
 	Bitmap *GetMemoryBackBuffer() override;
 	void SetMemoryBackBuffer(Bitmap *backBuffer) override;
 	Bitmap *GetStageBackBuffer(bool mark_dirty) override;
diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 1e4612f4e3e..743705bff9a 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -166,13 +166,6 @@ VideoMemoryGraphicsDriver::~VideoMemoryGraphicsDriver() {
 	DestroyAllStageScreens();
 }
 
-bool VideoMemoryGraphicsDriver::UsesMemoryBackBuffer() {
-	// Although we do use ours, we do not let engine draw upon it;
-	// only plugin handling are allowed to request our mem buffer.
-	// TODO: find better workaround?
-	return false;
-}
-
 Bitmap *VideoMemoryGraphicsDriver::GetMemoryBackBuffer() {
 	return nullptr;
 }
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 256dcd43ae3..b76e9f22216 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -239,7 +239,13 @@ public:
 	VideoMemoryGraphicsDriver();
 	~VideoMemoryGraphicsDriver() override;
 
-	bool UsesMemoryBackBuffer() override;
+	bool RequiresFullRedrawEachFrame() override { return true; }
+	bool HasAcceleratedTransform() override { return true; }
+	// NOTE: although we do use ours, we do not let engine draw upon it;
+	// only plugin handling are allowed to request our mem buffer
+	// for compatibility reasons.
+	bool UsesMemoryBackBuffer() override { return false; }
+
 	Bitmap *GetMemoryBackBuffer() override;
 	void SetMemoryBackBuffer(Bitmap *backBuffer) override;
 	Bitmap *GetStageBackBuffer(bool mark_dirty) override;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index c71cdd35d8c..4056943f701 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -101,6 +101,17 @@ public:
 	virtual const char *GetDriverID() = 0;
 	// Gets graphic driver's "friendly name"
 	virtual const char *GetDriverName() = 0;
+
+	// Tells if this gfx driver has to redraw whole scene each time
+	virtual bool RequiresFullRedrawEachFrame() = 0;
+	// Tells if this gfx driver uses GPU to transform sprites
+	virtual bool HasAcceleratedTransform() = 0;
+	// Tells if this gfx driver draws on a virtual screen before rendering on real screen.
+	virtual bool UsesMemoryBackBuffer() = 0;
+	// Tells if this gfx driver requires releasing render targets
+	// in case of display mode change or reset.
+	virtual bool ShouldReleaseRenderTargets() = 0;
+
 	virtual void SetTintMethod(TintMethod method) = 0;
 	// Initialize given display mode
 	virtual bool SetDisplayMode(const DisplayMode &mode) = 0;
@@ -238,9 +249,7 @@ public:
 	// These matrixes will be filled in accordance to the renderer's compatible format;
 	// returns false if renderer does not use matrixes (not a 3D renderer).
 	virtual bool GetStageMatrixes(RenderMatrixes &rm) = 0;
-	virtual bool RequiresFullRedrawEachFrame() = 0;
-	virtual bool HasAcceleratedTransform() = 0;
-	virtual bool UsesMemoryBackBuffer() = 0;
+
 	virtual ~IGraphicsDriver() {}
 };
 
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 3ad8c132ddc..19870777f0d 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -197,7 +197,7 @@ Globals::Globals() {
 	_overlaybmp = new std::vector<std::unique_ptr<Shared::Bitmap> >();
 	_debugRoomMaskObj =  new ObjTexture();
 	_debugMoveListObj = new ObjTexture();
-
+	_debugConsoleBuffer = new AGS::Shared::Bitmap();
 	_maincoltable = new COLOR_MAP();
 	_palette = new color[256];
 	for (int i = 0; i < PALETTE_COUNT; ++i)
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 529a7ed7258..9b8ca1f6e4a 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -605,7 +605,9 @@ public:
 	ObjTexture *_debugMoveListObj;
 	RoomAreaMask _debugRoomMask = kRoomAreaNone;
 	int _debugMoveListChar = -1;
-
+	// For in-game "console" surface
+	AGS::Shared::Bitmap *_debugConsoleBuffer;
+	// Whether room bg was modified
 	bool _current_background_is_dirty = false;
 	// Room background sprite
 	AGS::Engine::IDriverDependantBitmap *_roomBackgroundBmp = nullptr;
@@ -613,7 +615,6 @@ public:
 	bool _screen_is_dirty = false;
 	AGS::Shared::Bitmap *_raw_saved_screen = nullptr;
 	AGS::Shared::Bitmap **_dynamicallyCreatedSurfaces = nullptr;
-	int _places_r = 3, _places_g = 2, _places_b = 3;
 	color *_palette;
 	COLOR_MAP *_maincoltable;
 
@@ -1398,7 +1399,6 @@ public:
 	int _walkBehindRight[MAX_WALK_BEHINDS], _walkBehindBottom[MAX_WALK_BEHINDS];
 	AGS::Engine::IDriverDependantBitmap *_walkBehindBitmap[MAX_WALK_BEHINDS];
 	int _walkBehindsCachedForBgNum = 0;
-	WalkBehindMethodEnum _walkBehindMethod = DrawOverCharSprite;
 	int _walk_behind_baselines_changed = 0;
 	Rect _walkBehindAABB[MAX_WALK_BEHINDS]; // WB bounding box
 	std::vector<WalkBehindColumn> _walkBehindCols; // precalculated WB positions


Commit: e407658475fb6772dcf70b741fa1fbb87d9d5e30
    https://github.com/scummvm/scummvm/commit/e407658475fb6772dcf70b741fa1fbb87d9d5e30
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in hw draw mode always assume walk-behinds are sprites

>From upstream a2cfb4b3708aab7ef0911772fa83921420485999
and
b1e3a485c3e8500923aa9395fb5af69e288d3c0a

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 1e0de687ad0..da49193f07a 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1239,10 +1239,8 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 
 	actsp.SpriteID = pic; // for texture sharing
 
-	// NOTE: we need cached bitmap if:
-	// * it's a software renderer, otherwise
-	// * the walk-behind method is DrawOverCharSprite
-	if ((use_hw_transform) && (drawstate.WalkBehindMethod != DrawOverCharSprite)) {
+	// Hardware accelerated mode: always use original sprite and apply texture transform
+	if (use_hw_transform) {
 		// HW acceleration
 		const bool is_texture_intact = objsav.sppic == specialpic;
 		objsav.sppic = specialpic;
@@ -1260,9 +1258,8 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	//
 	// Software mode below
 	//
-
-	if ((!use_hw_transform) && (!drawstate.SoftwareRender)) {
-		// They want to draw it in software mode with the hw driver, so force a redraw
+	// They want to draw it in software mode with the hw driver, so force a redraw (???)
+	if (!drawstate.SoftwareRender) {
 		objsav.sppic = INT32_MIN;
 	}
 
@@ -1300,10 +1297,8 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	const int src_sprwidth = sprite->GetWidth();
 	const int src_sprheight = sprite->GetHeight();
 	bool actsps_used = false;
-	if (!use_hw_transform) {
-		// draw the base sprite, scaled and flipped as appropriate
-		actsps_used = scale_and_flip_sprite(actsp, pic, scale_size.Width, scale_size.Height, is_mirrored);
-	}
+	// draw the base sprite, scaled and flipped as appropriate
+	actsps_used = scale_and_flip_sprite(actsp, pic, scale_size.Width, scale_size.Height, is_mirrored);
 	if (!actsps_used) {
 		// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
 		recycle_bitmap(actsp.Bmp, coldept, src_sprwidth, src_sprheight);
@@ -1311,7 +1306,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 
 	// apply tints or lightenings where appropriate, else just copy
 	// the source bitmap
-	if (!use_hw_transform && ((tint_level > 0) || (light_level != 0))) {
+	if ((tint_level > 0) || (light_level != 0)) {
 		// direct read from source bitmap, where possible
 		Bitmap *blit_from = nullptr;
 		if (!actsps_used)
@@ -1998,6 +1993,8 @@ static void construct_ui_view() {
 // but does not put them on screen yet - that's done in respective construct_*_view functions
 static void construct_overlays() {
 	const bool is_software_mode = drawstate.SoftwareRender;
+	const bool crop_walkbehinds = (drawstate.WalkBehindMethod == DrawOverCharSprite);
+
 	auto &overs = get_overlays();
 	if (_GP(overlaybmp).size() < overs.size()) {
 		_GP(overlaybmp).resize(overs.size());
@@ -2009,26 +2006,30 @@ static void construct_overlays() {
 		if (over.transparency == 255) continue; // skip fully transparent
 
 		bool has_changed = over.HasChanged();
-		if (over.IsRoomLayer() && (drawstate.WalkBehindMethod == DrawOverCharSprite)) {
+		// If walk behinds are drawn over the cached object sprite, then check if positions were updated
+		if (crop_walkbehinds && over.IsRoomLayer()) {
 			Point pos = get_overlay_position(over);
 			has_changed |= (pos.X != _GP(screenovercache)[i].X || pos.Y != _GP(screenovercache)[i].Y);
 			_GP(screenovercache)[i].X = pos.X; _GP(screenovercache)[i].Y = pos.Y;
 		}
 
 		if (has_changed) {
-			// For software mode - prepare transformed bitmap if necessary
-			Bitmap *use_bmp = is_software_mode ?
-				transform_sprite(over.GetImage(), over.HasAlphaChannel(), _GP(overlaybmp)[i], Size(over.scaleWidth, over.scaleHeight)) :
-				over.GetImage();
-
-			if ((drawstate.WalkBehindMethod == DrawOverCharSprite) && over.IsRoomLayer()) {
-				if (use_bmp != _GP(overlaybmp)[i].get()) {
-					recycle_bitmap(_GP(overlaybmp)[i], use_bmp->GetColorDepth(), use_bmp->GetWidth(), use_bmp->GetHeight(), true);
-					_GP(overlaybmp)[i]->Blit(use_bmp);
+			// For software mode - prepare transformed bitmap if necessary;
+			// for hardware-accelerated - use the sprite ID if possible, to avoid redundant sprite load
+			Bitmap *use_bmp = nullptr;
+			if (is_software_mode) {
+				use_bmp = transform_sprite(over.GetImage(), over.HasAlphaChannel(), _GP(overlaybmp)[i], Size(over.scaleWidth, over.scaleHeight));
+				if (crop_walkbehinds && over.IsRoomLayer()) {
+					if (use_bmp != _GP(overlaybmp)[i].get()) {
+						recycle_bitmap(_GP(overlaybmp)[i], use_bmp->GetColorDepth(), use_bmp->GetWidth(), use_bmp->GetHeight(), true);
+						_GP(overlaybmp)[i]->Blit(use_bmp);
+					}
+					Point pos = get_overlay_position(over);
+					walkbehinds_cropout(_GP(overlaybmp)[i].get(), pos.X, pos.Y, over.zorder);
+					use_bmp = _GP(overlaybmp)[i].get();
 				}
-				Point pos = get_overlay_position(over);
-				walkbehinds_cropout(_GP(overlaybmp)[i].get(), pos.X, pos.Y, over.zorder);
-				use_bmp = _GP(overlaybmp)[i].get();
+			} else if (over.GetSpriteNum() < 0) {
+				use_bmp = over.GetImage();
 			}
 
 			over.ddb = recycle_ddb_sprite(over.ddb, over.GetSpriteNum(), use_bmp, over.HasAlphaChannel());


Commit: ffe86114e4b3dfc8e55212793c1df5d06dafeb0b
    https://github.com/scummvm/scummvm/commit/ffe86114e4b3dfc8e55212793c1df5d06dafeb0b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Complete "query gfxdriver for texture mem" commit

Completes a7f392a8f08ec14c48297ee05dd0bd0edb765df8

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index da49193f07a..d7055876b2f 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -387,7 +387,13 @@ void init_draw_method() {
 	} else {
 		drawstate.WalkBehindMethod = DrawAsSeparateSprite;
 		create_blank_image(_GP(game).GetColorDepth());
-		// texturecache.SetMaxCacheSize(usetup.TextureCacheSize);
+		size_t tx_cache_size = _GP(usetup).TextureCacheSize * 1024;
+		// If graphics driver can report available texture memory,
+		// then limit the setting by, let's say, 66% of it (we use it for other things)
+		size_t avail_tx_mem = _G(gfxDriver)->GetAvailableTextureMemory();
+		if (avail_tx_mem > 0)
+			tx_cache_size = std::min<size_t>(tx_cache_size, avail_tx_mem * 0.66);
+		// texturecache.SetMaxCacheSize(tx_cache_size);
 	}
 
 	on_mainviewport_changed();


Commit: df504348ed6fa0b43cc75052158c7060df639498
    https://github.com/scummvm/scummvm/commit/df504348ed6fa0b43cc75052158c7060df639498
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: Restore leftover bindings

>From upstream
720114e8a873dc69bf54de4148d2da77ae6561c6
c68e2b02ebddcd33184270cd95b66eb34521e1d1

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


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index d7962f060e6..9b7c19dc1da 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -736,6 +736,7 @@ void RegisterFileAPI() {
 		{"File::Delete^1", API_FN_PAIR(File_Delete)},
 		{"File::Exists^1", API_FN_PAIR(File_Exists)},
 		{"File::Open^2", API_FN_PAIR(sc_OpenFile)},
+		{"File::ResolvePath^1", API_FN_PAIR(File_ResolvePath)},
 
 		{"File::Close^0", API_FN_PAIR(File_Close)},
 		{"File::ReadInt^0", API_FN_PAIR(File_ReadInt)},
@@ -754,6 +755,7 @@ void RegisterFileAPI() {
 		{"File::get_EOF", API_FN_PAIR(File_GetEOF)},
 		{"File::get_Error", API_FN_PAIR(File_GetError)},
 		{"File::get_Position", API_FN_PAIR(File_GetPosition)},
+		{"File::get_Path", API_FN_PAIR(File_GetPath)},
 	};
 
 	ccAddExternalFunctions361(file_api);


Commit: dcd7be5b2d005b142f4aeb6a3ee87c532cec94e1
    https://github.com/scummvm/scummvm/commit/dcd7be5b2d005b142f4aeb6a3ee87c532cec94e1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed sprInfo init in SpriteCache::SetSprite()

>From upstream 9bb30c7973c138bee3efc3ba86e0f10175b9d1f7

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 99b03e1a550..1b5f280bc19 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -112,7 +112,7 @@ bool SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite, int flags) {
 
 	// Assign sprite with 0 size, as it will not be included into the cache size
 	_spriteData[index] = SpriteData(sprite, 0, SPRCACHEFLAG_EXTERNAL | SPRCACHEFLAG_LOCKED);
-	_sprInfos[index] = SpriteInfo(flags, sprite->GetWidth(), sprite->GetHeight());
+	_sprInfos[index] = SpriteInfo(sprite->GetWidth(), sprite->GetHeight(), flags);
 	SprCacheLog("SetSprite: (external) %d", index);
 	return true;
 }


Commit: 57464f3dcef2e76d9050488c375d8bc63d0d00a4
    https://github.com/scummvm/scummvm/commit/57464f3dcef2e76d9050488c375d8bc63d0d00a4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Complete "object interaction events take obj pointer and mode as params" commit

This completes cb6cce267d75ccdbca452873693a1ccadf0f688b

Changed paths:
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_hotspot.cpp
    engines/ags/engine/ac/global_inventory_item.cpp
    engines/ags/engine/ac/global_object.cpp
    engines/ags/engine/ac/global_region.cpp
    engines/ags/engine/script/script.cpp


diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 9f54edfdbba..94c2ce0534a 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -41,10 +41,12 @@
 #include "ags/engine/ac/properties.h"
 #include "ags/engine/ac/screen_overlay.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/cc_character.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/engine/main/game_run.h"
 #include "ags/engine/script/script.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -388,7 +390,8 @@ void RunCharacterInteraction(int cc, int mood) {
 		_GP(play).usedinv = _G(playerchar)->activeinv;
 	}
 
-	const auto obj_evt = ObjectEvent("character%d", cc);
+	const auto obj_evt = ObjectEvent("character%d", cc,
+									 RuntimeScriptValue().SetScriptObject(&_GP(game).chars[cc], &_GP(ccDynamicCharacter)), mood);
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		if ((evnt >= 0) &&
 			run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), evnt, anyclick_evt) < 0)
diff --git a/engines/ags/engine/ac/global_hotspot.cpp b/engines/ags/engine/ac/global_hotspot.cpp
index 05245976cfe..24e9dfb1883 100644
--- a/engines/ags/engine/ac/global_hotspot.cpp
+++ b/engines/ags/engine/ac/global_hotspot.cpp
@@ -33,9 +33,11 @@
 #include "ags/engine/ac/properties.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/cc_hotspot.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/engine/script/script.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -118,9 +120,8 @@ void RunHotspotInteraction(int hotspothere, int mood) {
 	else if ((mood != MODE_WALK) && (_GP(play).check_interaction_only == 0))
 		MoveCharacterToHotspot(_GP(game).playercharacter, hotspothere);
 
-	// can't use the setevent functions because this ProcessClick is only
-	// executed once in a eventlist
-	const auto obj_evt = ObjectEvent("hotspot%d", hotspothere);
+	const auto obj_evt = ObjectEvent("hotspot%d", hotspothere,
+									 RuntimeScriptValue().SetScriptObject(&_G(scrHotspot)[hotspothere], &_GP(ccDynamicHotspot)), mood);
 
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		if ((evnt >= 0) &&
diff --git a/engines/ags/engine/ac/global_inventory_item.cpp b/engines/ags/engine/ac/global_inventory_item.cpp
index 2d5c19fe8da..5acdc0703c2 100644
--- a/engines/ags/engine/ac/global_inventory_item.cpp
+++ b/engines/ags/engine/ac/global_inventory_item.cpp
@@ -30,9 +30,11 @@
 #include "ags/engine/ac/inv_window.h"
 #include "ags/engine/ac/properties.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/cc_inventory.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_inv.h"
 #include "ags/engine/script/script.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -116,7 +118,9 @@ void RunInventoryInteraction(int iit, int mood) {
 	if (evnt < 0) // on any non-supported mode - use "other-click"
 		evnt = otherclick_evt;
 
-	auto obj_evt = ObjectEvent("inventory%d", iit);
+	const auto obj_evt = ObjectEvent("inventory%d", iit,
+									 RuntimeScriptValue().SetScriptObject(&_G(scrInv)[iit], &_GP(ccDynamicInv)), mood);
+
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		run_interaction_script(obj_evt, _GP(game).invScripts[iit].get(), evnt);
 	} else {
diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp
index 997e7b2237b..3a8850e5b9c 100644
--- a/engines/ags/engine/ac/global_object.cpp
+++ b/engines/ags/engine/ac/global_object.cpp
@@ -37,6 +37,7 @@
 #include "ags/engine/ac/room_object.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/ac/dynobj/cc_object.h"
 #include "ags/engine/ac/view_frame.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/main/game_run.h"
@@ -433,7 +434,9 @@ void RunObjectInteraction(int aa, int mood) {
 		_GP(play).usedinv = _G(playerchar)->activeinv;
 	}
 
-	const auto obj_evt = ObjectEvent("object%d", aa);
+	const auto obj_evt = ObjectEvent("object%d", aa,
+									 RuntimeScriptValue().SetScriptObject(&_G(scrObj)[aa], &_GP(ccDynamicObject)), mood);
+
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		if ((evnt >= 0) &&
 			run_interaction_script(obj_evt, _GP(thisroom).Objects[aa].EventHandlers.get(), evnt, anyclick_evt) < 0)
diff --git a/engines/ags/engine/ac/global_region.cpp b/engines/ags/engine/ac/global_region.cpp
index f78844dea26..dce06e561ea 100644
--- a/engines/ags/engine/ac/global_region.cpp
+++ b/engines/ags/engine/ac/global_region.cpp
@@ -26,6 +26,7 @@
 #include "ags/engine/ac/region.h"
 #include "ags/engine/ac/room.h"
 #include "ags/engine/ac/room_status.h"
+#include "ags/engine/ac/dynobj/cc_region.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/gfx/bitmap.h"
@@ -145,7 +146,9 @@ void RunRegionInteraction(int regnum, int mood) {
 
 	// NOTE: for Regions the mode has specific meanings (NOT verbs):
 	// 0 - stands on region, 1 - walks onto region, 2 - walks off region
-	const auto obj_evt = ObjectEvent("region%d", regnum);
+	const auto obj_evt = ObjectEvent("region%d", regnum,
+									 RuntimeScriptValue().SetScriptObject(&_G(scrRegion)[regnum], &_GP(ccDynamicRegion)), mood);
+
 	if (_G(loaded_game_file_version) > kGameVersion_272) {
 		run_interaction_script(obj_evt, _GP(thisroom).Regions[regnum].EventHandlers.get(), mood);
 	} else {
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index 0b9b888f012..de2f73fd1d1 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -164,7 +164,7 @@ int run_interaction_script(const ObjectEvent &obj_evt, InteractionScripts *nint,
 		return -1;
 	}
 
-	int room_was = _GP(play).room_changes;
+	const int room_was = _GP(play).room_changes;
 
 	// TODO: find a way to generalize all the following hard-coded behavior
 


Commit: d4cc4a8808161ee27c07eb2230521fb7be9a3673
    https://github.com/scummvm/scummvm/commit/d4cc4a8808161ee27c07eb2230521fb7be9a3673
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: proper handling of Hotspot WalkOn/MouseOver event args

>From upstream 8fee5f33c0070f9f9a86f29470de53caea823d26

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


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index d749a402687..de60efbd82d 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -30,6 +30,8 @@
 #include "ags/engine/ac/gui.h"
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/ac/screen.h"
+#include "ags/engine/ac/dynobj/script_hotspot.h"
+#include "ags/engine/ac/dynobj/cc_hotspot.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/main/game_run.h"
 #include "ags/shared/script/cc_common.h"
@@ -141,13 +143,14 @@ void process_event(const EventHappened *evp) {
 		ObjectEvent obj_evt;
 
 		if (evp->data1 == EVB_HOTSPOT) {
-
-			if (_GP(thisroom).Hotspots[evp->data2].EventHandlers != nullptr)
-				scriptPtr = _GP(thisroom).Hotspots[evp->data2].EventHandlers;
+			const int hotspot_id = evp->data2;
+			if (_GP(thisroom).Hotspots[hotspot_id].EventHandlers != nullptr)
+				scriptPtr = _GP(thisroom).Hotspots[hotspot_id].EventHandlers;
 			else
-				evpt = &_G(croom)->intrHotspot[evp->data2];
+				evpt = &_G(croom)->intrHotspot[hotspot_id];
 
-			obj_evt = ObjectEvent("hotspot%d", evp->data2);
+			obj_evt = ObjectEvent("hotspot%d", hotspot_id,
+								  RuntimeScriptValue().SetScriptObject(&_G(scrHotspot)[hotspot_id], &_GP(ccDynamicHotspot)));
 			// Debug::Printf("Running hotspot interaction for hotspot %d, event %d", evp->data2, evp->data3);
 		} else if (evp->data1 == EVB_ROOM) {
 


Commit: d7c2c0a49094e41149674613c889c346b8fb7675
    https://github.com/scummvm/scummvm/commit/d7c2c0a49094e41149674613c889c346b8fb7675
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: ScriptAPI: add Object.AnimationVolume bindings

This completes 094efb1c332edb1580d25a7922890fe42fc77e92

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


diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index c0baa9c1a9f..54b88c217b2 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -1064,6 +1064,8 @@ void RegisterObjectAPI() {
 		{"Object::StopMoving^0", API_FN_PAIR(Object_StopMoving)},
 		{"Object::Tint^5", API_FN_PAIR(Object_Tint)},
 		{"Object::get_Animating", API_FN_PAIR(Object_GetAnimating)},
+		{"Object::get_AnimationVolume", API_FN_PAIR(Object_GetAnimationVolume)},
+		{"Object::set_AnimationVolume", API_FN_PAIR(Object_SetAnimationVolume)},
 		{"Object::get_Baseline", API_FN_PAIR(Object_GetBaseline)},
 		{"Object::set_Baseline", API_FN_PAIR(Object_SetBaseline)},
 		{"Object::get_BlockingHeight", API_FN_PAIR(Object_GetBlockingHeight)},


Commit: 20fd74c0649ef3c5a66b94a01c6599efe94d7d16
    https://github.com/scummvm/scummvm/commit/20fd74c0649ef3c5a66b94a01c6599efe94d7d16
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Marked few more game script variables as readonly

Completes 84c3fbbe77bcf32e8352a61a7fce0f466e77c245

Changed paths:
    engines/ags/engine/ac/dynobj/script_game.cpp


diff --git a/engines/ags/engine/ac/dynobj/script_game.cpp b/engines/ags/engine/ac/dynobj/script_game.cpp
index d5531a42826..8571487edbb 100644
--- a/engines/ags/engine/ac/dynobj/script_game.cpp
+++ b/engines/ags/engine/ac/dynobj/script_game.cpp
@@ -231,14 +231,10 @@ void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	case 57:
 		_GP(play).inv_top = val;
 		break;
-	case 58:
-		_GP(play).inv_numdisp = val;
-		break;
-	case 59:
-		_GP(play).inv_numorder = val;
-		break;
-	case 60:
-		_GP(play).inv_numinline = val;
+	case 58: // play.inv_numdisp
+	case 59: // play.inv_numorder
+	case 60: // play.inv_numinline
+		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
 		break;
 	case 61:
 		_GP(play).text_speed = val;


Commit: 455b9609d0b38e7b0bf89c097ddb2ac13568040c
    https://github.com/scummvm/scummvm/commit/455b9609d0b38e7b0bf89c097ddb2ac13568040c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: implemented Type.GetByName() for all applicable types

>From upstream 29602688ec766cdc0b8fb7aa761f73292365651b

Changed paths:
    engines/ags/engine/ac/audio_clip.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/ac/hotspot.cpp
    engines/ags/engine/ac/inventory_item.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/script/script_runtime.cpp
    engines/ags/engine/script/script_runtime.h


diff --git a/engines/ags/engine/ac/audio_clip.cpp b/engines/ags/engine/ac/audio_clip.cpp
index 0278c3cefd6..68895da30c8 100644
--- a/engines/ags/engine/ac/audio_clip.cpp
+++ b/engines/ags/engine/ac/audio_clip.cpp
@@ -27,6 +27,7 @@
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/shared/core/asset_manager.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
+#include "ags/engine/ac/dynobj/cc_audio_clip.h"
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/globals.h"
 
@@ -90,6 +91,14 @@ ScriptAudioChannel *AudioClip_PlayOnChannel(ScriptAudioClip *clip, int chan, int
 //
 //=============================================================================
 
+ScriptAudioClip *AudioClip_GetByName(const char *name) {
+	return static_cast<ScriptAudioClip *>(ccGetScriptObjectAddress(name, _GP(ccDynamicAudioClip).GetType()));
+}
+
+RuntimeScriptValue Sc_AudioClip_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptAudioClip, _GP(ccDynamicAudioClip), AudioClip_GetByName, const char);
+}
+
 RuntimeScriptValue Sc_AudioClip_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetID);
 }
@@ -135,6 +144,7 @@ RuntimeScriptValue Sc_AudioClip_PlayOnChannel(void *self, const RuntimeScriptVal
 
 void RegisterAudioClipAPI() {
 	ScFnRegister audioclip_api[] = {
+		{"AudioClip::GetByName", API_FN_PAIR(AudioClip_GetByName)},
 		{"AudioClip::Play^2", API_FN_PAIR(AudioClip_Play)},
 		{"AudioClip::PlayFrom^3", API_FN_PAIR(AudioClip_PlayFrom)},
 		{"AudioClip::PlayQueued^2", API_FN_PAIR(AudioClip_PlayQueued)},
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 733cbafd9df..1710741b8a0 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2897,6 +2897,14 @@ PViewport FindNearestViewport(int charid) {
 //
 //=============================================================================
 
+CharacterInfo *Character_GetByName(const char *name) {
+	return static_cast<CharacterInfo *>(ccGetScriptObjectAddress(name, _GP(ccDynamicCharacter).GetType()));
+}
+
+RuntimeScriptValue Sc_Character_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(CharacterInfo, _GP(ccDynamicCharacter), Character_GetByName, const char);
+}
+
 // void | CharacterInfo *chaa, ScriptInvItem *invi, int addIndex
 RuntimeScriptValue Sc_Character_AddInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_AddInventory, ScriptInvItem);
@@ -3138,12 +3146,6 @@ RuntimeScriptValue Sc_Character_GetTintLuminance(void *self, const RuntimeScript
 	API_OBJCALL_INT(CharacterInfo, Character_GetTintLuminance);
 }
 
-/*
-RuntimeScriptValue Sc_Character_SetOption(void *self, const RuntimeScriptValue *params, int32_t param_count)
-{
-}
-*/
-
 // void (CharacterInfo *chaa, int xspeed, int yspeed)
 RuntimeScriptValue Sc_Character_SetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetSpeed);
@@ -3635,6 +3637,7 @@ void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_
 	ScFnRegister character_api[] = {
 		{"Character::GetAtRoomXY^2", API_FN_PAIR(GetCharacterAtRoom)},
 		{"Character::GetAtScreenXY^2", API_FN_PAIR(GetCharacterAtScreen)},
+		{"Character::GetByName", API_FN_PAIR(Character_GetByName)},
 
 		{"Character::AddInventory^2", API_FN_PAIR(Character_AddInventory)},
 		{"Character::AddWaypoint^2", API_FN_PAIR(Character_AddWaypoint)},
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index debe9ec583f..ad209480efb 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -63,6 +63,7 @@
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
+#include "ags/engine/ac/dynobj/cc_dialog.h"
 #include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/ags.h"
 #include "ags/globals.h"
@@ -1181,6 +1182,14 @@ void do_conversation(int dlgnum) {
 //
 //=============================================================================
 
+ScriptDialog *Dialog_GetByName(const char *name) {
+	return static_cast<ScriptDialog *>(ccGetScriptObjectAddress(name, _GP(ccDynamicDialog).GetType()));
+}
+
+RuntimeScriptValue Sc_Dialog_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptDialog, _GP(ccDynamicDialog), Dialog_GetByName, const char);
+}
+
 // int (ScriptDialog *sd)
 RuntimeScriptValue Sc_Dialog_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptDialog, Dialog_GetID);
@@ -1232,6 +1241,7 @@ RuntimeScriptValue Sc_Dialog_Start(void *self, const RuntimeScriptValue *params,
 
 void RegisterDialogAPI() {
 	ScFnRegister dialog_api[] = {
+		{"Dialog::GetByName", API_FN_PAIR(Dialog_GetByName)},
 		{"Dialog::get_ID", API_FN_PAIR(Dialog_GetID)},
 		{"Dialog::get_OptionCount", API_FN_PAIR(Dialog_GetOptionCount)},
 		{"Dialog::get_ShowTextParser", API_FN_PAIR(Dialog_GetShowTextParser)},
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 0ae4c75e76f..e8e361fea09 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -604,6 +604,14 @@ void gui_on_mouse_down(const int guin, const int mbut) {
 //
 //=============================================================================
 
+ScriptGUI *GUI_GetByName(const char *name) {
+	return static_cast<ScriptGUI *>(ccGetScriptObjectAddress(name, _GP(ccDynamicGUI).GetType()));
+}
+
+RuntimeScriptValue Sc_GUI_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptGUI, _GP(ccDynamicGUI), GUI_GetByName, const char);
+}
+
 // void GUI_Centre(ScriptGUI *sgui)
 RuntimeScriptValue Sc_GUI_Centre(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID(ScriptGUI, GUI_Centre);
@@ -792,6 +800,7 @@ RuntimeScriptValue Sc_GUI_GetShown(void *self, const RuntimeScriptValue *params,
 void RegisterGUIAPI() {
 	ScFnRegister gui_api[] = {
 		{"GUI::GetAtScreenXY^2", API_FN_PAIR(GetGUIAtLocation)},
+		{"GUI::GetByName", API_FN_PAIR(GUI_GetByName)},
 		{"GUI::ProcessClick^3", API_FN_PAIR(GUI_ProcessClick)},
 
 		{"GUI::Centre^0", API_FN_PAIR(GUI_Centre)},
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index e4db002d29f..dac8ffe23bf 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -224,6 +224,14 @@ void GUIControl_SetTransparency(GUIObject *guio, int trans) {
 //
 //=============================================================================
 
+GUIObject *GUIControl_GetByName(const char *name) {
+	return static_cast<GUIObject *>(ccGetScriptObjectAddress(name, _GP(ccDynamicGUIObject).GetType()));
+}
+
+RuntimeScriptValue Sc_GUIControl_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(GUIObject, _GP(ccDynamicGUIObject), GUIControl_GetByName, const char);
+}
+
 // void (GUIObject *guio)
 RuntimeScriptValue Sc_GUIControl_BringToFront(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID(GUIObject, GUIControl_BringToFront);
@@ -378,6 +386,7 @@ RuntimeScriptValue Sc_GUIControl_SetTransparency(void *self, const RuntimeScript
 void RegisterGUIControlAPI() {
 	ScFnRegister guicontrol_api[] = {
 		{"GUIControl::GetAtScreenXY^2", API_FN_PAIR(GetGUIControlAtLocation)},
+		{"GUIControl::GetByName", API_FN_PAIR(GUIControl_GetByName)},
 
 		{"GUIControl::BringToFront^0", API_FN_PAIR(GUIControl_BringToFront)},
 		{"GUIControl::SendToBack^0", API_FN_PAIR(GUIControl_SendToBack)},
diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp
index 828684565c6..41f9057a410 100644
--- a/engines/ags/engine/ac/hotspot.cpp
+++ b/engines/ags/engine/ac/hotspot.cpp
@@ -145,6 +145,14 @@ int get_hotspot_at(int xpp, int ypp) {
 //
 //=============================================================================
 
+ScriptHotspot *Hotspot_GetByName(const char *name) {
+	return static_cast<ScriptHotspot *>(ccGetScriptObjectAddress(name, _GP(ccDynamicHotspot).GetType()));
+}
+
+RuntimeScriptValue Sc_Hotspot_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptHotspot, _GP(ccDynamicHotspot), Hotspot_GetByName, const char);
+}
+
 RuntimeScriptValue Sc_GetHotspotAtRoom(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_OBJ_PINT2(ScriptHotspot, _GP(ccDynamicHotspot), GetHotspotAtRoom);
 }
@@ -233,6 +241,7 @@ void RegisterHotspotAPI() {
 	ScFnRegister hotspot_api[] = {
 		{"Hotspot::GetAtRoomXY^2", API_FN_PAIR(GetHotspotAtRoom)},
 		{"Hotspot::GetAtScreenXY^2", API_FN_PAIR(GetHotspotAtScreen)},
+		{"Hotspot::GetByName", API_FN_PAIR(Hotspot_GetByName)},
 		{"Hotspot::GetDrawingSurface", API_FN_PAIR(Hotspot_GetDrawingSurface)},
 
 		{"Hotspot::GetName^1", API_FN_PAIR(Hotspot_GetName)},
diff --git a/engines/ags/engine/ac/inventory_item.cpp b/engines/ags/engine/ac/inventory_item.cpp
index feb095f71ea..1ec5208af5c 100644
--- a/engines/ags/engine/ac/inventory_item.cpp
+++ b/engines/ags/engine/ac/inventory_item.cpp
@@ -123,6 +123,14 @@ void set_inv_item_cursorpic(int invItemId, int piccy) {
 //
 //=============================================================================
 
+ScriptInvItem *InventoryItem_GetByName(const char *name) {
+	return static_cast<ScriptInvItem *>(ccGetScriptObjectAddress(name, _GP(ccDynamicInv).GetType()));
+}
+
+RuntimeScriptValue Sc_InventoryItem_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptInvItem, _GP(ccDynamicInv), InventoryItem_GetByName, const char);
+}
+
 // ScriptInvItem *(int xx, int yy)
 RuntimeScriptValue Sc_GetInvAtLocation(const RuntimeScriptValue *params, int32_t param_count) {
 	API_SCALL_OBJ_PINT2(ScriptInvItem, _GP(ccDynamicInv), GetInvAtLocation);
@@ -204,6 +212,7 @@ RuntimeScriptValue Sc_InventoryItem_GetName_New(void *self, const RuntimeScriptV
 void RegisterInventoryItemAPI() {
 	ScFnRegister invitem_api[] = {
 		{"InventoryItem::GetAtScreenXY^2", API_FN_PAIR(GetInvAtLocation)},
+		{"InventoryItem::GetByName", API_FN_PAIR(InventoryItem_GetByName)},
 
 		{"InventoryItem::IsInteractionAvailable^1", API_FN_PAIR(InventoryItem_CheckInteractionAvailable)},
 		{"InventoryItem::GetName^1", API_FN_PAIR(InventoryItem_GetName)},
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 54b88c217b2..d290f9a126d 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -718,6 +718,14 @@ bool CycleViewAnim(int view, uint16_t &o_loop, uint16_t &o_frame, bool forwards,
 //
 //=============================================================================
 
+ScriptObject *Object_GetByName(const char *name) {
+	return static_cast<ScriptObject *>(ccGetScriptObjectAddress(name, _GP(ccDynamicObject).GetType()));
+}
+
+RuntimeScriptValue Sc_Object_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_OBJ_POBJ(ScriptObject, _GP(ccDynamicObject), Object_GetByName, const char);
+}
+
 // void (ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction)
 RuntimeScriptValue Sc_Object_Animate5(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_VOID_PINT5(ScriptObject, Object_Animate5);
@@ -1041,6 +1049,7 @@ void RegisterObjectAPI() {
 	ScFnRegister object_api[] = {
 		{"Object::GetAtRoomXY^2", API_FN_PAIR(GetObjectAtRoom)},
 		{"Object::GetAtScreenXY^2", API_FN_PAIR(GetObjectAtScreen)},
+		{"Object::GetByName", API_FN_PAIR(Object_GetByName)},
 
 		{"Object::Animate^5", API_FN_PAIR(Object_Animate5)},
 		{"Object::Animate^6", API_FN_PAIR(Object_Animate6)},
diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp
index f01bcd1595c..eabef048631 100644
--- a/engines/ags/engine/script/script_runtime.cpp
+++ b/engines/ags/engine/script/script_runtime.cpp
@@ -105,9 +105,20 @@ Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name) {
 	return Plugins::PluginMethod();
 }
 
+void *ccGetScriptObjectAddress(const String &name, const String &type) {
+	const auto *imp = _GP(simp).getByName(name);
+	if (!imp)
+		return nullptr;
+	if (imp->Value.Type != kScValScriptObject && imp->Value.Type != kScValPluginObject)
+		return nullptr;
+	if (type != imp->Value.ObjMgr->GetType())
+		return nullptr;
+	return imp->Value.Ptr;
+}
+
 void ccSetScriptAliveTimer(unsigned sys_poll_timeout, unsigned abort_timeout, unsigned abort_loops) {
-	 ccInstance::SetExecTimeout(sys_poll_timeout, abort_timeout, abort_loops);
- }
+	ccInstance::SetExecTimeout(sys_poll_timeout, abort_timeout, abort_loops);
+}
 
 void ccNotifyScriptStillAlive() {
 	ccInstance *cur_inst = ccInstance::GetCurrentInstance();
diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h
index d8463e045eb..d96b0f5227b 100644
--- a/engines/ags/engine/script/script_runtime.h
+++ b/engines/ags/engine/script/script_runtime.h
@@ -87,6 +87,8 @@ inline void ccAddExternalFunctions361(const ScFnRegister (&arr)[N]) {
 void *ccGetSymbolAddress(const String &name);
 // Get a registered symbol's direct pointer; this is used solely for plugins
 Plugins::PluginMethod ccGetSymbolAddressForPlugin(const String &name);
+// Get a registered Script Object, optionally restricting to the given type name
+void *ccGetScriptObjectAddress(const String &name, const String &type);
 
 // DEBUG HOOK
 typedef void (*new_line_hook_type)(ccInstance *, int);


Commit: c6e115e6975498c9ff82293449fb1d49aefd9e09
    https://github.com/scummvm/scummvm/commit/c6e115e6975498c9ff82293449fb1d49aefd9e09
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: added readonly ScriptName property to all applicable types

>From upstream a19c52bae9d8513bcb68924c0c4f11717dde90e7

Changed paths:
    engines/ags/engine/ac/audio_clip.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/ac/hotspot.cpp
    engines/ags/engine/ac/inventory_item.cpp
    engines/ags/engine/ac/object.cpp


diff --git a/engines/ags/engine/ac/audio_clip.cpp b/engines/ags/engine/ac/audio_clip.cpp
index 68895da30c8..fc7cc4c0ef0 100644
--- a/engines/ags/engine/ac/audio_clip.cpp
+++ b/engines/ags/engine/ac/audio_clip.cpp
@@ -25,9 +25,11 @@
 #include "ags/engine/ac/audio_channel.h"
 #include "ags/shared/ac/common.h"
 #include "ags/shared/ac/game_setup_struct.h"
+#include "ags/engine/ac/string.h"
 #include "ags/shared/core/asset_manager.h"
 #include "ags/engine/ac/dynobj/cc_audio_channel.h"
 #include "ags/engine/ac/dynobj/cc_audio_clip.h"
+#include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/globals.h"
 
@@ -39,6 +41,10 @@ int AudioClip_GetID(ScriptAudioClip *clip) {
 	return clip->id;
 }
 
+const char *AudioClip_GetScriptName(ScriptAudioClip *clip) {
+	return CreateNewScriptString(clip->scriptName);
+}
+
 int AudioClip_GetFileType(ScriptAudioClip *clip) {
 	return clip->fileType;
 }
@@ -103,6 +109,10 @@ RuntimeScriptValue Sc_AudioClip_GetID(void *self, const RuntimeScriptValue *para
 	API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetID);
 }
 
+RuntimeScriptValue Sc_AudioClip_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptAudioClip, const char, _GP(myScriptStringImpl), AudioClip_GetScriptName);
+}
+
 // int | ScriptAudioClip *clip
 RuntimeScriptValue Sc_AudioClip_GetFileType(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetFileType);
@@ -153,6 +163,7 @@ void RegisterAudioClipAPI() {
 		{"AudioClip::get_ID", API_FN_PAIR(AudioClip_GetID)},
 		{"AudioClip::get_FileType", API_FN_PAIR(AudioClip_GetFileType)},
 		{"AudioClip::get_IsAvailable", API_FN_PAIR(AudioClip_GetIsAvailable)},
+		{"AudioClip::get_ScriptName", API_FN_PAIR(AudioClip_GetScriptName)},
 		{"AudioClip::get_Type", API_FN_PAIR(AudioClip_GetType)},
 	};
 
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 1710741b8a0..c4d99136bee 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1199,6 +1199,10 @@ int Character_GetID(CharacterInfo *chaa) {
 
 }
 
+const char *Character_GetScriptName(CharacterInfo *chin) {
+	return CreateNewScriptString(chin->scrname);
+}
+
 int Character_GetFrame(CharacterInfo *chaa) {
 	return chaa->frame;
 }
@@ -3333,6 +3337,10 @@ RuntimeScriptValue Sc_Character_GetID(void *self, const RuntimeScriptValue *para
 	API_OBJCALL_INT(CharacterInfo, Character_GetID);
 }
 
+RuntimeScriptValue Sc_Character_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetScriptName);
+}
+
 // int (CharacterInfo *chaa)
 RuntimeScriptValue Sc_Character_GetIdleView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(CharacterInfo, Character_GetIdleView);
@@ -3745,6 +3753,7 @@ void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_
 		{"Character::set_ScaleVolume", API_FN_PAIR(Character_SetScaleVolume)},
 		{"Character::get_Scaling", API_FN_PAIR(Character_GetScaling)},
 		{"Character::set_Scaling", API_FN_PAIR(Character_SetScaling)},
+		{"Character::get_ScriptName", API_FN_PAIR(Character_GetScriptName)},
 		{"Character::get_Solid", API_FN_PAIR(Character_GetSolid)},
 		{"Character::set_Solid", API_FN_PAIR(Character_SetSolid)},
 		{"Character::get_Speaking", API_FN_PAIR(Character_GetSpeaking)},
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index ad209480efb..3affc2a252e 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -146,6 +146,10 @@ int Dialog_GetID(ScriptDialog *sd) {
 	return sd->id;
 }
 
+const char *Dialog_GetScriptName(ScriptDialog *sd) {
+	return CreateNewScriptString(_GP(game).dialogScriptNames[sd->id]);
+}
+
 //=============================================================================
 
 #define RUN_DIALOG_STAY          -1
@@ -1195,6 +1199,10 @@ RuntimeScriptValue Sc_Dialog_GetID(void *self, const RuntimeScriptValue *params,
 	API_OBJCALL_INT(ScriptDialog, Dialog_GetID);
 }
 
+RuntimeScriptValue Sc_Dialog_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptDialog, const char, _GP(myScriptStringImpl), Dialog_GetScriptName);
+}
+
 // int (ScriptDialog *sd)
 RuntimeScriptValue Sc_Dialog_GetOptionCount(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptDialog, Dialog_GetOptionCount);
@@ -1244,6 +1252,7 @@ void RegisterDialogAPI() {
 		{"Dialog::GetByName", API_FN_PAIR(Dialog_GetByName)},
 		{"Dialog::get_ID", API_FN_PAIR(Dialog_GetID)},
 		{"Dialog::get_OptionCount", API_FN_PAIR(Dialog_GetOptionCount)},
+		{"Dialog::get_ScriptName", API_FN_PAIR(Dialog_GetScriptName)},
 		{"Dialog::get_ShowTextParser", API_FN_PAIR(Dialog_GetShowTextParser)},
 		{"Dialog::DisplayOptions^1", API_FN_PAIR(Dialog_DisplayOptions)},
 		{"Dialog::GetOptionState^1", API_FN_PAIR(Dialog_GetOptionState)},
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index e8e361fea09..16af4dad80f 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -37,6 +37,7 @@
 #include "ags/engine/ac/inv_window.h"
 #include "ags/engine/ac/mouse.h"
 #include "ags/engine/ac/runtime_defines.h"
+#include "ags/engine/ac/string.h"
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/dynobj/cc_gui_object.h"
 #include "ags/engine/ac/dynobj/script_gui.h"
@@ -53,6 +54,7 @@
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/ac/dynobj/cc_gui.h"
 #include "ags/engine/ac/dynobj/cc_gui_object.h"
+#include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/shared/util/string_compat.h"
 #include "ags/shared/debugging/out.h"
@@ -167,6 +169,10 @@ int GUI_GetID(ScriptGUI *tehgui) {
 	return tehgui->id;
 }
 
+const char *GUI_GetScriptName(ScriptGUI *tehgui) {
+	return CreateNewScriptString(_GP(guis)[tehgui->id].Name);
+}
+
 GUIObject *GUI_GetiControls(ScriptGUI *tehgui, int idx) {
 	if ((idx < 0) || (idx >= _GP(guis)[tehgui->id].GetControlCount()))
 		return nullptr;
@@ -701,6 +707,10 @@ RuntimeScriptValue Sc_GUI_GetID(void *self, const RuntimeScriptValue *params, in
 	API_OBJCALL_INT(ScriptGUI, GUI_GetID);
 }
 
+RuntimeScriptValue Sc_GUI_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptGUI, const char, _GP(myScriptStringImpl), GUI_GetScriptName);
+}
+
 RuntimeScriptValue Sc_GUI_GetPopupYPos(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptGUI, GUI_GetPopupYPos);
 }
@@ -824,6 +834,7 @@ void RegisterGUIAPI() {
 		{"GUI::get_PopupStyle", API_FN_PAIR(GUI_GetPopupStyle)},
 		{"GUI::get_PopupYPos", API_FN_PAIR(GUI_GetPopupYPos)},
 		{"GUI::set_PopupYPos", API_FN_PAIR(GUI_SetPopupYPos)},
+		{"GUI::get_ScriptName", API_FN_PAIR(GUI_GetScriptName)},
 		{"TextWindowGUI::get_TextColor", API_FN_PAIR(GUI_GetTextColor)},
 		{"TextWindowGUI::set_TextColor", API_FN_PAIR(GUI_SetTextColor)},
 		{"TextWindowGUI::get_TextPadding", API_FN_PAIR(GUI_GetTextPadding)},
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index dac8ffe23bf..e7c8dee2cef 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -23,6 +23,7 @@
 #include "ags/engine/ac/gui_control.h"
 #include "ags/engine/ac/global_gui.h"
 #include "ags/engine/ac/mouse.h"
+#include "ags/engine/ac/string.h"
 #include "ags/engine/debugging/debug_log.h"
 #include "ags/shared/gui/gui_button.h"
 #include "ags/shared/gui/gui_inv.h"
@@ -34,6 +35,7 @@
 #include "ags/engine/script/runtime_script_value.h"
 #include "ags/engine/ac/dynobj/cc_gui.h"
 #include "ags/engine/ac/dynobj/cc_gui_object.h"
+#include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/shared/debugging/out.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
@@ -97,6 +99,10 @@ int GUIControl_GetID(GUIObject *guio) {
 	return guio->Id;
 }
 
+const char *GUIControl_GetScriptName(GUIObject *guio) {
+	return CreateNewScriptString(guio->Name);
+}
+
 ScriptGUI *GUIControl_GetOwningGUI(GUIObject *guio) {
 	return &_GP(scrGui)[guio->ParentId];
 }
@@ -322,6 +328,10 @@ RuntimeScriptValue Sc_GUIControl_GetID(void *self, const RuntimeScriptValue *par
 	API_OBJCALL_INT(GUIObject, GUIControl_GetID);
 }
 
+RuntimeScriptValue Sc_GUIControl_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(GUIObject, const char, _GP(myScriptStringImpl), GUIControl_GetScriptName);
+}
+
 // ScriptGUI* (GUIObject *guio)
 RuntimeScriptValue Sc_GUIControl_GetOwningGUI(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_OBJ(GUIObject, ScriptGUI, _GP(ccDynamicGUI), GUIControl_GetOwningGUI);
@@ -406,6 +416,7 @@ void RegisterGUIControlAPI() {
 		{"GUIControl::set_Height", API_FN_PAIR(GUIControl_SetHeight)},
 		{"GUIControl::get_ID", API_FN_PAIR(GUIControl_GetID)},
 		{"GUIControl::get_OwningGUI", API_FN_PAIR(GUIControl_GetOwningGUI)},
+		{"GUIControl::get_ScriptName", API_FN_PAIR(GUIControl_GetScriptName)},
 		{"GUIControl::get_Visible", API_FN_PAIR(GUIControl_GetVisible)},
 		{"GUIControl::set_Visible", API_FN_PAIR(GUIControl_SetVisible)},
 		{"GUIControl::get_Width", API_FN_PAIR(GUIControl_GetWidth)},
diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp
index 41f9057a410..7b2a22f44be 100644
--- a/engines/ags/engine/ac/hotspot.cpp
+++ b/engines/ags/engine/ac/hotspot.cpp
@@ -65,6 +65,10 @@ int Hotspot_GetID(ScriptHotspot *hss) {
 	return hss->id;
 }
 
+const char *Hotspot_GetScriptName(ScriptHotspot *hss) {
+	return CreateNewScriptString(_GP(thisroom).Hotspots[hss->id].ScriptName);
+}
+
 int Hotspot_GetWalkToX(ScriptHotspot *hss) {
 	return GetHotspotPointX(hss->id);
 }
@@ -218,6 +222,10 @@ RuntimeScriptValue Sc_Hotspot_GetID(void *self, const RuntimeScriptValue *params
 	API_OBJCALL_INT(ScriptHotspot, Hotspot_GetID);
 }
 
+RuntimeScriptValue Sc_Hotspot_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetScriptName);
+}
+
 // const char* (ScriptHotspot *hss)
 RuntimeScriptValue Sc_Hotspot_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_CONST_OBJCALL_OBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetName_New);
@@ -257,6 +265,7 @@ void RegisterHotspotAPI() {
 		{"Hotspot::get_ID", API_FN_PAIR(Hotspot_GetID)},
 		{"Hotspot::get_Name", API_FN_PAIR(Hotspot_GetName_New)},
 		{"Hotspot::set_Name", API_FN_PAIR(Hotspot_SetName)},
+		{"Hotspot::get_ScriptName", API_FN_PAIR(Hotspot_GetScriptName)},
 		{"Hotspot::get_WalkToX", API_FN_PAIR(Hotspot_GetWalkToX)},
 		{"Hotspot::get_WalkToY", API_FN_PAIR(Hotspot_GetWalkToY)},
 	};
diff --git a/engines/ags/engine/ac/inventory_item.cpp b/engines/ags/engine/ac/inventory_item.cpp
index 1ec5208af5c..e12be61e4e0 100644
--- a/engines/ags/engine/ac/inventory_item.cpp
+++ b/engines/ags/engine/ac/inventory_item.cpp
@@ -59,6 +59,10 @@ int InventoryItem_GetID(ScriptInvItem *scii) {
 	return scii->id;
 }
 
+const char *InventoryItem_GetScriptName(ScriptInvItem *scii) {
+	return CreateNewScriptString(_GP(game).invScriptNames[scii->id]);
+}
+
 ScriptInvItem *GetInvAtLocation(int xx, int yy) {
 	int hsnum = GetInvAt(xx, yy);
 	if (hsnum <= 0)
@@ -204,6 +208,10 @@ RuntimeScriptValue Sc_InventoryItem_GetID(void *self, const RuntimeScriptValue *
 	API_OBJCALL_INT(ScriptInvItem, InventoryItem_GetID);
 }
 
+RuntimeScriptValue Sc_InventoryItem_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetScriptName);
+}
+
 // const char* (ScriptInvItem *invitem)
 RuntimeScriptValue Sc_InventoryItem_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_CONST_OBJCALL_OBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetName_New);
@@ -230,6 +238,7 @@ void RegisterInventoryItemAPI() {
 		{"InventoryItem::get_ID", API_FN_PAIR(InventoryItem_GetID)},
 		{"InventoryItem::get_Name", API_FN_PAIR(InventoryItem_GetName_New)},
 		{"InventoryItem::set_Name", API_FN_PAIR(InventoryItem_SetName)},
+		{"InventoryItem::get_ScriptName", API_FN_PAIR(InventoryItem_GetScriptName)},
 	};
 
 	ccAddExternalFunctions361(invitem_api);
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index d290f9a126d..6c0f4904e7c 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -396,6 +396,10 @@ int Object_GetID(ScriptObject *objj) {
 	return objj->id;
 }
 
+const char *Object_GetScriptName(ScriptObject *objj) {
+	return CreateNewScriptString(_GP(thisroom).Objects[objj->id].ScriptName);
+}
+
 void Object_SetIgnoreWalkbehinds(ScriptObject *chaa, int clik) {
 	SetObjectIgnoreWalkbehinds(chaa->id, clik);
 }
@@ -939,6 +943,10 @@ RuntimeScriptValue Sc_Object_GetID(void *self, const RuntimeScriptValue *params,
 	API_OBJCALL_INT(ScriptObject, Object_GetID);
 }
 
+RuntimeScriptValue Sc_Object_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_OBJ(ScriptObject, const char, _GP(myScriptStringImpl), Object_GetScriptName);
+}
+
 // int (ScriptObject *objj)
 RuntimeScriptValue Sc_Object_GetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptObject, Object_GetIgnoreScaling);
@@ -1099,6 +1107,7 @@ void RegisterObjectAPI() {
 		{"Object::set_Name", API_FN_PAIR(Object_SetName)},
 		{"Object::get_Scaling", API_FN_PAIR(Object_GetScaling)},
 		{"Object::set_Scaling", API_FN_PAIR(Object_SetScaling)},
+		{"Object::get_ScriptName", API_FN_PAIR(Object_GetScriptName)},
 		{"Object::get_Solid", API_FN_PAIR(Object_GetSolid)},
 		{"Object::set_Solid", API_FN_PAIR(Object_SetSolid)},
 		{"Object::get_Transparency", API_FN_PAIR(Object_GetTransparency)},


Commit: d48716b7112691d7c8882b805882e1841f893b65
    https://github.com/scummvm/scummvm/commit/d48716b7112691d7c8882b805882e1841f893b65
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed ScriptUserObject de-serialization saves garbage into Size

Was broken by an oversight in 9658c71
>From upstream f3dff32a20bfd162a7d4f3b8f0ba019512bab473

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/script_user_object.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
index a675e3c10bd..5ed1714c591 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.cpp
@@ -83,6 +83,7 @@ int AGSCCDynamicObject::Serialize(void *address, uint8_t *buffer, int bufsize) {
 	// If the required space is larger than the provided buffer,
 	// then return negated required space, notifying the caller that a larger buffer is necessary
 	size_t req_size = CalcSerializeSize(address);
+	assert(req_size <= INT32_MAX); // dynamic object API does not support size > int32
 	if (bufsize < 0 || req_size > static_cast<size_t>(bufsize))
 		return -(static_cast<int32_t>(req_size));
 
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index 22172fc19f6..ba797e21ee6 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -50,6 +50,7 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 	// classes registered by plugin cannot, because streams are not (yet)
 	// part of the plugin API.
 	size_t data_sz = static_cast<size_t>(dataSize);
+	assert(data_sz <= INT32_MAX); // dynamic object API does not support size > int32
 	MemoryStream mems(reinterpret_cast<const uint8_t *>(serializedData), dataSize);
 
 	// TODO: consider this: there are object types that are part of the
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index b343319f368..3072a0fe6b1 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -68,6 +68,7 @@ void ScriptUserObject::Serialize(void *address, AGS::Shared::Stream *out) {
 void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
 	uint8_t *new_data = new uint8_t[(data_sz - FileHeaderSz) + MemHeaderSz];
 	Header &hdr = reinterpret_cast<Header &>(*new_data);
+	hdr.Size = data_sz - FileHeaderSz;
 	in->Read(new_data + MemHeaderSz, data_sz - FileHeaderSz);
 	ccRegisterUnserializedObject(index, &new_data[MemHeaderSz], this);
 }


Commit: d83adaac04c5f010b55b93feb82cd3ba36a86bd4
    https://github.com/scummvm/scummvm/commit/d83adaac04c5f010b55b93feb82cd3ba36a86bd4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: warn of write into readonly script variable, instead of error

Some older games may to try to write there, although
it's not clear whether that had any effect in the original engine.
>From upstream 09a13f8dc0f2fc9e58f5eb0103066197e598cb73

Changed paths:
    engines/ags/engine/ac/dynobj/script_game.cpp
    engines/ags/engine/ac/dynobj/script_mouse.cpp
    engines/ags/engine/ac/dynobj/script_system.cpp


diff --git a/engines/ags/engine/ac/dynobj/script_game.cpp b/engines/ags/engine/ac/dynobj/script_game.cpp
index 8571487edbb..d45892b2653 100644
--- a/engines/ags/engine/ac/dynobj/script_game.cpp
+++ b/engines/ags/engine/ac/dynobj/script_game.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_state.h"
@@ -234,7 +235,7 @@ void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	case 58: // play.inv_numdisp
 	case 59: // play.inv_numorder
 	case 60: // play.inv_numinline
-		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
+		debug_script_warn("ScriptGame: attempt to write in readonly variable at offset %d, value %d", offset, val);
 		break;
 	case 61:
 		_GP(play).text_speed = val;
@@ -306,7 +307,7 @@ void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	case 84: // _GP(play).fast_forward;
 	case 85: // _GP(play).room_width;
 	case 86: // _GP(play).room_height;
-		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
+		debug_script_warn("ScriptGame: attempt to write in readonly variable at offset %d, value %d", offset, val);
 		break;
 	case 87:
 		_GP(play).game_speed_modifier = val;
@@ -400,7 +401,7 @@ void CCScriptGame::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	case 117: // _GP(play).fade_to_red;
 	case 118: // _GP(play).fade_to_green;
 	case 119: // _GP(play).fade_to_blue;
-		cc_error("ScriptGame: attempt to write readonly variable at offset %d", offset);
+		debug_script_warn("ScriptGame: attempt to write in readonly variable at offset %d, value %d", offset, val);
 		break;
 	case 120:
 		_GP(play).show_single_dialog_option = val;
diff --git a/engines/ags/engine/ac/dynobj/script_mouse.cpp b/engines/ags/engine/ac/dynobj/script_mouse.cpp
index 705259955ec..7bf318287ed 100644
--- a/engines/ags/engine/ac/dynobj/script_mouse.cpp
+++ b/engines/ags/engine/ac/dynobj/script_mouse.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/ac/dynobj/script_mouse.h"
 #include "ags/shared/script/cc_common.h" // cc_error
 
@@ -40,7 +41,7 @@ void ScriptMouse::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	switch (offset) {
 	case 0:
 	case 4:
-		cc_error("ScriptMouse: attempt to write readonly variable at offset %d", offset);
+		debug_script_warn("ScriptMouse: attempt to write in readonly variable at offset %d, value", offset, val);
 		break;
 	default:
 		cc_error("ScriptMouse: unsupported variable offset %d", offset);
diff --git a/engines/ags/engine/ac/dynobj/script_system.cpp b/engines/ags/engine/ac/dynobj/script_system.cpp
index 42fb76099d9..00c182c3f17 100644
--- a/engines/ags/engine/ac/dynobj/script_system.cpp
+++ b/engines/ags/engine/ac/dynobj/script_system.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/ac/dynobj/script_system.h"
 #include "ags/shared/script/cc_common.h" // cc_error
 
@@ -59,7 +60,7 @@ void ScriptSystem::WriteInt32(void *address, intptr_t offset, int32_t val) {
 	case 4:
 	case 6:
 	case 7:
-		cc_error("ScriptSystem: attempt to write readonly variable at offset %d", offset);
+		debug_script_warn("ScriptSystem: attempt to write in readonly variable at offset %d, value %d", offset, val);
 		break;
 	case 5:
 		vsync = val;


Commit: 80dcc60a786c95e5349d0403462ad913d51974af
    https://github.com/scummvm/scummvm/commit/80dcc60a786c95e5349d0403462ad913d51974af
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: ScriptAPI: added formatting support to a few text displaying functions

This refers to:
* DisplayAtY();
* DrawingSurface.DrawStringWrapped();
* Character.SayAt();
* Character.SayBackground();

>From upstream e90f366b5abc2f7a89f16a40f24ccdc63fbe3601

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index c4d99136bee..41bf479d0e5 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -3098,14 +3098,16 @@ RuntimeScriptValue Sc_Character_Say(void *self, const RuntimeScriptValue *params
 	return RuntimeScriptValue((int32_t)0);
 }
 
-// void (CharacterInfo *chaa, int x, int y, int width, const char *texx)
 RuntimeScriptValue Sc_Character_SayAt(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT3_POBJ(CharacterInfo, Character_SayAt, const char);
+	API_OBJCALL_SCRIPT_SPRINTF(Character_SayAt, 4);
+	Character_SayAt((CharacterInfo *)self, params[0].IValue, params[1].IValue, params[2].IValue, scsf_buffer);
+	return RuntimeScriptValue((int32_t)0);
 }
 
-// ScriptOverlay* (CharacterInfo *chaa, const char *texx)
 RuntimeScriptValue Sc_Character_SayBackground(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_OBJAUTO_POBJ(CharacterInfo, ScriptOverlay, Character_SayBackground, const char);
+	API_OBJCALL_SCRIPT_SPRINTF(Character_SayBackground, 1);
+	auto *ret_obj = Character_SayBackground((CharacterInfo *)self, scsf_buffer);
+	return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj);
 }
 
 // void (CharacterInfo *chaa)
@@ -3629,13 +3631,21 @@ RuntimeScriptValue Sc_Character_SetZ(void *self, const RuntimeScriptValue *param
 //
 //=============================================================================
 
-// void (CharacterInfo *chaa, const char *texx, ...)
 void ScPl_Character_Say(CharacterInfo *chaa, const char *texx, ...) {
 	API_PLUGIN_SCRIPT_SPRINTF(texx);
 	Character_Say(chaa, scsf_buffer);
 }
 
-// void (CharacterInfo *chaa, const char *texx, ...)
+void ScPl_Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx, ...) {
+	API_PLUGIN_SCRIPT_SPRINTF(texx);
+	Character_SayAt(chaa, x, y, width, scsf_buffer);
+}
+
+ScriptOverlay *ScPl_Character_SayBackground(CharacterInfo *chaa, const char *texx, ...) {
+	API_PLUGIN_SCRIPT_SPRINTF(texx);
+	return Character_SayBackground(chaa, scsf_buffer);
+}
+
 void ScPl_Character_Think(CharacterInfo *chaa, const char *texx, ...) {
 	API_PLUGIN_SCRIPT_SPRINTF(texx);
 	Character_Think(chaa, scsf_buffer);
@@ -3682,8 +3692,12 @@ void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_
 		{"Character::RemoveTint^0", API_FN_PAIR(Character_RemoveTint)},
 		{"Character::RunInteraction^1", API_FN_PAIR(Character_RunInteraction)},
 		{"Character::Say^101", Sc_Character_Say},
+		// old non-variadic variants
 		{"Character::SayAt^4", API_FN_PAIR(Character_SayAt)},
 		{"Character::SayBackground^1", API_FN_PAIR(Character_SayBackground)},
+		// newer variadic variants
+		{"Character::SayAt^104", Sc_Character_SayAt},
+		{"Character::SayBackground^101", Sc_Character_SayBackground},
 		{"Character::SetAsPlayer^0", API_FN_PAIR(Character_SetAsPlayer)},
 		{"Character::SetIdleView^2", API_FN_PAIR(Character_SetIdleView)},
 		{"Character::SetLightLevel^1", API_FN_PAIR(Character_SetLightLevel)},
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index c2ab462abc9..e466939c392 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -480,7 +480,10 @@ RuntimeScriptValue Sc_DrawingSurface_DrawStringWrapped_Old(void *self, const Run
 }
 
 RuntimeScriptValue Sc_DrawingSurface_DrawStringWrapped(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_OBJCALL_VOID_PINT5_POBJ(ScriptDrawingSurface, DrawingSurface_DrawStringWrapped, const char);
+	API_OBJCALL_SCRIPT_SPRINTF(DrawingSurface_DrawString, 6);
+	DrawingSurface_DrawStringWrapped((ScriptDrawingSurface *)self, params[0].IValue, params[1].IValue, params[2].IValue,
+									 params[3].IValue, params[4].IValue, scsf_buffer);
+	return RuntimeScriptValue((int32_t)0);
 }
 
 // void (ScriptDrawingSurface* target, ScriptDrawingSurface* source, int translev)
@@ -547,6 +550,11 @@ RuntimeScriptValue Sc_DrawingSurface_GetWidth(void *self, const RuntimeScriptVal
 //
 //=============================================================================
 
+void ScPl_DrawingSurface_DrawStringWrapped(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int alignment, const char *msg, ...) {
+	API_PLUGIN_SCRIPT_SPRINTF(msg);
+	DrawingSurface_DrawStringWrapped(sds, xx, yy, wid, font, alignment, scsf_buffer);
+}
+
 void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*compat_api */) {
 	ScFnRegister drawsurf_api[] = {
 		{"DrawingSurface::Clear^1", API_FN_PAIR(DrawingSurface_Clear)},
@@ -575,10 +583,13 @@ void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion /*com
 	ccAddExternalFunctions361(drawsurf_api);
 
 	// Few functions have to be selected based on API level
-	if (base_api < kScriptAPI_v350)
+	if (base_api < kScriptAPI_v350) {
 		ccAddExternalObjectFunction361("DrawingSurface::DrawStringWrapped^6", API_FN_PAIR(DrawingSurface_DrawStringWrapped_Old));
-	else
+	}
+	else { // old non-variadic and new variadic variants
 		ccAddExternalObjectFunction361("DrawingSurface::DrawStringWrapped^6", API_FN_PAIR(DrawingSurface_DrawStringWrapped));
+		ccAddExternalObjectFunction361("DrawingSurface::DrawStringWrapped^106", Sc_DrawingSurface_DrawStringWrapped, (void *)ScPl_DrawingSurface_DrawStringWrapped);
+	}
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index f6097f6c022..ff475b1fec3 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -246,7 +246,9 @@ RuntimeScriptValue Sc_DisplayAt(const RuntimeScriptValue *params, int32_t param_
 
 // void  (int ypos, char *texx)
 RuntimeScriptValue Sc_DisplayAtY(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_PINT_POBJ(DisplayAtY, const char);
+	API_SCALL_SCRIPT_SPRINTF(DisplayAtY, 2);
+	DisplayAtY(params[0].IValue, scsf_buffer);
+	return RuntimeScriptValue((int32_t)0);
 }
 
 // void (int msnum)
@@ -1920,7 +1922,10 @@ void RegisterGlobalAPI() {
 		{"DisableRegion", API_FN_PAIR(DisableRegion)},
 		{"Display", Sc_Display},
 		{"DisplayAt", Sc_DisplayAt},
-		{"DisplayAtY", API_FN_PAIR(DisplayAtY)},
+		// CHECKME: this function was non-variadic prior to 3.6.1, but AGS compiler does
+		// not produce "name^argnum" symbol id for non-member functions for some reason :/
+		// do we have to do anything about this here? like, test vs script API version...
+		{"DisplayAtY", Sc_DisplayAtY},
 		{"DisplayMessage", API_FN_PAIR(DisplayMessage)},
 		{"DisplayMessageAtY", API_FN_PAIR(DisplayMessageAtY)},
 		{"DisplayMessageBar", API_FN_PAIR(DisplayMessageBar)},


Commit: 24df476bfa297fe6147288f4170ea582a6562c9e
    https://github.com/scummvm/scummvm/commit/24df476bfa297fe6147288f4170ea582a6562c9e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added some const modifiers in dynamic object methods

>From upstream 7f7fbf5f06e0ee5c19d5d810d0b5f411b0f33b8e

Changed paths:
    engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
    engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
    engines/ags/engine/ac/dynobj/cc_audio_channel.h
    engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
    engines/ags/engine/ac/dynobj/cc_audio_clip.h
    engines/ags/engine/ac/dynobj/cc_character.cpp
    engines/ags/engine/ac/dynobj/cc_character.h
    engines/ags/engine/ac/dynobj/cc_dialog.cpp
    engines/ags/engine/ac/dynobj/cc_dialog.h
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_dynamic_array.h
    engines/ags/engine/ac/dynobj/cc_gui.cpp
    engines/ags/engine/ac/dynobj/cc_gui.h
    engines/ags/engine/ac/dynobj/cc_gui_object.cpp
    engines/ags/engine/ac/dynobj/cc_gui_object.h
    engines/ags/engine/ac/dynobj/cc_hotspot.cpp
    engines/ags/engine/ac/dynobj/cc_hotspot.h
    engines/ags/engine/ac/dynobj/cc_inventory.cpp
    engines/ags/engine/ac/dynobj/cc_inventory.h
    engines/ags/engine/ac/dynobj/cc_object.cpp
    engines/ags/engine/ac/dynobj/cc_object.h
    engines/ags/engine/ac/dynobj/cc_region.cpp
    engines/ags/engine/ac/dynobj/cc_region.h
    engines/ags/engine/ac/dynobj/script_camera.cpp
    engines/ags/engine/ac/dynobj/script_camera.h
    engines/ags/engine/ac/dynobj/script_date_time.cpp
    engines/ags/engine/ac/dynobj/script_date_time.h
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
    engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
    engines/ags/engine/ac/dynobj/script_dict.cpp
    engines/ags/engine/ac/dynobj/script_dict.h
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.h
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
    engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
    engines/ags/engine/ac/dynobj/script_overlay.cpp
    engines/ags/engine/ac/dynobj/script_overlay.h
    engines/ags/engine/ac/dynobj/script_set.cpp
    engines/ags/engine/ac/dynobj/script_set.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/engine/ac/dynobj/script_view_frame.cpp
    engines/ags/engine/ac/dynobj/script_view_frame.h
    engines/ags/engine/ac/dynobj/script_viewport.cpp
    engines/ags/engine/ac/dynobj/script_viewport.h


diff --git a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
index 74bccc552f0..3db089bd2ae 100644
--- a/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_ags_dynamic_object.h
@@ -97,9 +97,9 @@ public:
 protected:
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	virtual size_t CalcSerializeSize(void *address) = 0;
+	virtual size_t CalcSerializeSize(const void *address) = 0;
 	// Write object data into the provided stream
-	virtual void Serialize(void *address, AGS::Shared::Stream *out) = 0;
+	virtual void Serialize(const void *address, AGS::Shared::Stream *out) = 0;
 };
 
 // CCStaticObject is a base class for managing static global objects in script.
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
index d76c85f8451..3be1cf3c537 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.cpp
@@ -34,12 +34,12 @@ const char *CCAudioChannel::GetType() {
 	return "AudioChannel";
 }
 
-size_t CCAudioChannel::CalcSerializeSize(void * /*address*/) {
+size_t CCAudioChannel::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void CCAudioChannel::Serialize(void *address, Stream *out) {
-	const ScriptAudioChannel *ach = (const ScriptAudioChannel *)address;
+void CCAudioChannel::Serialize(const void *address, Stream *out) {
+	const ScriptAudioChannel *ach = static_cast<const ScriptAudioChannel *>(address);
 	out->WriteInt32(ach->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_channel.h b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
index f7688b63f20..3333845c3e6 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_channel.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_channel.h
@@ -31,9 +31,9 @@ struct CCAudioChannel final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
index a7868d7df90..e813d05ff97 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.cpp
@@ -33,12 +33,12 @@ const char *CCAudioClip::GetType() {
 	return "AudioClip";
 }
 
-size_t CCAudioClip::CalcSerializeSize(void * /*address*/) {
+size_t CCAudioClip::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void CCAudioClip::Serialize(void *address, Stream *out) {
-	const ScriptAudioClip *ach = (const ScriptAudioClip *)address;
+void CCAudioClip::Serialize(const void *address, Stream *out) {
+	const ScriptAudioClip *ach = static_cast<const ScriptAudioClip *>(address);
 	out->WriteInt32(ach->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_audio_clip.h b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
index fa05437dea2..95d9896db8d 100644
--- a/engines/ags/engine/ac/dynobj/cc_audio_clip.h
+++ b/engines/ags/engine/ac/dynobj/cc_audio_clip.h
@@ -31,9 +31,9 @@ struct CCAudioClip final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index 931fe507578..c641ec39a09 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -39,14 +39,14 @@ const char *CCCharacter::GetType() {
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-size_t CCCharacter::CalcSerializeSize(void * /*address*/) {
+size_t CCCharacter::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCCharacter::Serialize(void *address, Stream *out) {
-	const CharacterInfo *chaa = (const CharacterInfo *)address;
+void CCCharacter::Serialize(const void *address, Stream *out) {
+	const CharacterInfo *chaa = static_cast<const CharacterInfo *>(address);
 	out->WriteInt32(chaa->index_id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h
index a97c88a7de1..3a5cf68b64c 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.h
+++ b/engines/ags/engine/ac/dynobj/cc_character.h
@@ -48,9 +48,9 @@ public:
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
index bdc9939118b..4a24da82de7 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp
@@ -36,14 +36,14 @@ const char *CCDialog::GetType() {
 	return "Dialog";
 }
 
-size_t CCDialog::CalcSerializeSize(void * /*address*/) {
+size_t CCDialog::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCDialog::Serialize(void *address, Stream *out) {
-	const ScriptDialog *shh = (const ScriptDialog *)address;
+void CCDialog::Serialize(const void *address, Stream *out) {
+	const ScriptDialog *shh = static_cast<const ScriptDialog *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h
index 3d141cc6bf1..5ebb0c48d5e 100644
--- a/engines/ags/engine/ac/dynobj/cc_dialog.h
+++ b/engines/ags/engine/ac/dynobj/cc_dialog.h
@@ -34,9 +34,9 @@ struct CCDialog final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 62e7c0c28af..00adb7c6d96 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -57,12 +57,12 @@ int CCDynamicArray::Dispose(void *address, bool force) {
 	return 1;
 }
 
-size_t CCDynamicArray::CalcSerializeSize(void *address) {
+size_t CCDynamicArray::CalcSerializeSize(const void *address) {
 	const Header &hdr = GetHeader(address);
 	return hdr.TotalSize + FileHeaderSz;
 }
 
-void CCDynamicArray::Serialize(void *address, AGS::Shared::Stream *out) {
+void CCDynamicArray::Serialize(const void *address, AGS::Shared::Stream *out) {
 	const Header &hdr = GetHeader(address);
 	out->WriteInt32(hdr.ElemCount);
 	out->WriteInt32(hdr.TotalSize);
diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
index 3bfb9be6850..b74ec94eac7 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.h
@@ -44,8 +44,8 @@ public:
 	CCDynamicArray() = default;
 	~CCDynamicArray() = default;
 
-	inline static const Header &GetHeader(void *address) {
-		return reinterpret_cast<const Header &>(*(static_cast<uint8_t *>(address) - MemHeaderSz));
+	inline static const Header &GetHeader(const void *address) {
+		return reinterpret_cast<const Header &>(*(static_cast<const uint8_t *>(address) - MemHeaderSz));
 	}
 
 	// Create managed array object and return a pointer to the beginning of a buffer
@@ -64,9 +64,9 @@ private:
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 // Helper functions for setting up dynamic arrays.
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp
index 6fcc5a0f508..32192c173c4 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp
@@ -34,14 +34,14 @@ const char *CCGUI::GetType() {
 	return "GUI";
 }
 
-size_t CCGUI::CalcSerializeSize(void * /*address*/) {
+size_t CCGUI::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCGUI::Serialize(void *address, Stream *out) {
-	const ScriptGUI *shh = (const ScriptGUI *)address;
+void CCGUI::Serialize(const void *address, Stream *out) {
+	const ScriptGUI *shh = static_cast<const ScriptGUI *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h
index 6bee137b80b..83f6bf23656 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui.h
@@ -34,9 +34,9 @@ struct CCGUI final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
index da5fad874e3..d4e60047050 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.cpp
@@ -36,14 +36,14 @@ const char *CCGUIObject::GetType() {
 	return "GUIObject";
 }
 
-size_t CCGUIObject::CalcSerializeSize(void * /*address*/) {
+size_t CCGUIObject::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t) * 2;
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCGUIObject::Serialize(void *address, Stream *out) {
-	const GUIObject *guio = (const GUIObject *)address;
+void CCGUIObject::Serialize(const void *address, Stream *out) {
+	const GUIObject *guio = static_cast<const GUIObject *>(address);
 	out->WriteInt32(guio->ParentId);
 	out->WriteInt32(guio->Id);
 }
diff --git a/engines/ags/engine/ac/dynobj/cc_gui_object.h b/engines/ags/engine/ac/dynobj/cc_gui_object.h
index 006c4f2545a..7e449c7f2b9 100644
--- a/engines/ags/engine/ac/dynobj/cc_gui_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_gui_object.h
@@ -34,9 +34,9 @@ struct CCGUIObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
index 78bad86e2af..300f106f262 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp
@@ -36,14 +36,14 @@ const char *CCHotspot::GetType() {
 	return "Hotspot";
 }
 
-size_t CCHotspot::CalcSerializeSize(void * /*address*/) {
+size_t CCHotspot::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCHotspot::Serialize(void *address, Stream *out) {
-	const ScriptHotspot *shh = (const ScriptHotspot *)address;
+void CCHotspot::Serialize(const void *address, Stream *out) {
+	const ScriptHotspot *shh = static_cast<const ScriptHotspot *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h
index 7acc3290746..297c88934c5 100644
--- a/engines/ags/engine/ac/dynobj/cc_hotspot.h
+++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h
@@ -34,9 +34,9 @@ struct CCHotspot final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
index c1cd51e0d42..eb498e46747 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp
@@ -35,14 +35,14 @@ const char *CCInventory::GetType() {
 	return "Inventory";
 }
 
-size_t CCInventory::CalcSerializeSize(void * /*address*/) {
+size_t CCInventory::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCInventory::Serialize(void *address, Stream *out) {
-	const ScriptInvItem *shh = (const ScriptInvItem *)address;
+void CCInventory::Serialize(const void *address, Stream *out) {
+	const ScriptInvItem *shh = static_cast<const ScriptInvItem *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h
index 195361d3d1e..da920c8a652 100644
--- a/engines/ags/engine/ac/dynobj/cc_inventory.h
+++ b/engines/ags/engine/ac/dynobj/cc_inventory.h
@@ -34,9 +34,9 @@ struct CCInventory final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp
index e8ccff254ad..c175c84df21 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_object.cpp
@@ -36,14 +36,14 @@ const char *CCObject::GetType() {
 	return "Object";
 }
 
-size_t CCObject::CalcSerializeSize(void * /*address*/) {
+size_t CCObject::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCObject::Serialize(void *address, Stream *out) {
-	const ScriptObject *shh = (const ScriptObject *)address;
+void CCObject::Serialize(const void *address, Stream *out) {
+	const ScriptObject *shh = static_cast<const ScriptObject *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h
index 8f8c8a1203d..72d37183a49 100644
--- a/engines/ags/engine/ac/dynobj/cc_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_object.h
@@ -34,9 +34,9 @@ struct CCObject final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp
index 9e3f99d7997..e93b397783d 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_region.cpp
@@ -36,14 +36,14 @@ const char *CCRegion::GetType() {
 	return "Region";
 }
 
-size_t CCRegion::CalcSerializeSize(void * /*address*/) {
+size_t CCRegion::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void CCRegion::Serialize(void *address, Stream *out) {
-	const ScriptRegion *shh = (const ScriptRegion *)address;
+void CCRegion::Serialize(const void *address, Stream *out) {
+	const ScriptRegion *shh = static_cast<const ScriptRegion *>(address);
 	out->WriteInt32(shh->id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h
index da6b2999211..62622488a40 100644
--- a/engines/ags/engine/ac/dynobj/cc_region.h
+++ b/engines/ags/engine/ac/dynobj/cc_region.h
@@ -34,9 +34,9 @@ struct CCRegion final : AGSCCDynamicObject {
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_camera.cpp b/engines/ags/engine/ac/dynobj/script_camera.cpp
index eb4cf8cbc35..ef8cf554179 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.cpp
+++ b/engines/ags/engine/ac/dynobj/script_camera.cpp
@@ -44,11 +44,11 @@ int ScriptCamera::Dispose(void *address, bool force) {
 	return 1;
 }
 
-size_t ScriptCamera::CalcSerializeSize(void * /*address*/) {
+size_t ScriptCamera::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptCamera::Serialize(void *address, Stream *out) {
+void ScriptCamera::Serialize(const void *address, Stream *out) {
 	out->WriteInt32(_id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_camera.h b/engines/ags/engine/ac/dynobj/script_camera.h
index 781c6827f80..64b7636f3e5 100644
--- a/engines/ags/engine/ac/dynobj/script_camera.h
+++ b/engines/ags/engine/ac/dynobj/script_camera.h
@@ -48,9 +48,9 @@ public:
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 
 private:
 	int _id = -1; // index of camera in the game state array
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.cpp b/engines/ags/engine/ac/dynobj/script_date_time.cpp
index 733238bbfc7..f2c83c6e4cb 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.cpp
+++ b/engines/ags/engine/ac/dynobj/script_date_time.cpp
@@ -37,11 +37,11 @@ const char *ScriptDateTime::GetType() {
 	return "DateTime";
 }
 
-size_t ScriptDateTime::CalcSerializeSize(void * /*address*/) {
+size_t ScriptDateTime::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t) * 7;
 }
 
-void ScriptDateTime::Serialize(void *address, Stream *out) {
+void ScriptDateTime::Serialize(const void *address, Stream *out) {
 	out->WriteInt32(year);
 	out->WriteInt32(month);
 	out->WriteInt32(day);
diff --git a/engines/ags/engine/ac/dynobj/script_date_time.h b/engines/ags/engine/ac/dynobj/script_date_time.h
index 3691b3bab5d..4b668cc7d22 100644
--- a/engines/ags/engine/ac/dynobj/script_date_time.h
+++ b/engines/ags/engine/ac/dynobj/script_date_time.h
@@ -39,9 +39,9 @@ struct ScriptDateTime final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
index e417bc01ad1..22a4e418cbc 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.cpp
@@ -32,13 +32,13 @@ const char *ScriptDialogOptionsRendering::GetType() {
 	return "DialogOptionsRendering";
 }
 
-size_t ScriptDialogOptionsRendering::CalcSerializeSize(void * /*address*/) {
+size_t ScriptDialogOptionsRendering::CalcSerializeSize(const void * /*address*/) {
 	return 0;
 }
 
 // serialize the object into BUFFER (which is BUFSIZE bytes)
 // return number of bytes used
-void ScriptDialogOptionsRendering::Serialize(void *address, Stream *out) {
+void ScriptDialogOptionsRendering::Serialize(const void *address, Stream *out) {
 }
 
 void ScriptDialogOptionsRendering::Unserialize(int index, Stream *in, size_t data_sz) {
diff --git a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
index 63f5d02c7d8..852c0a6c7e8 100644
--- a/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
+++ b/engines/ags/engine/ac/dynobj/script_dialog_options_rendering.h
@@ -49,9 +49,9 @@ struct ScriptDialogOptionsRendering final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dict.cpp b/engines/ags/engine/ac/dynobj/script_dict.cpp
index 5e4f02e9b54..91193d582d5 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dict.cpp
@@ -34,11 +34,11 @@ const char *ScriptDictBase::GetType() {
 	return "StringDictionary";
 }
 
-size_t ScriptDictBase::CalcSerializeSize(void * /*address*/) {
+size_t ScriptDictBase::CalcSerializeSize(const void * /*address*/) {
 	return CalcContainerSize();
 }
 
-void ScriptDictBase::Serialize(void *address, Stream *out) {
+void ScriptDictBase::Serialize(const void *address, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
 	SerializeContainer(out);
diff --git a/engines/ags/engine/ac/dynobj/script_dict.h b/engines/ags/engine/ac/dynobj/script_dict.h
index e6daaad4a2f..764a5a0f024 100644
--- a/engines/ags/engine/ac/dynobj/script_dict.h
+++ b/engines/ags/engine/ac/dynobj/script_dict.h
@@ -65,9 +65,9 @@ public:
 	virtual void GetValues(std::vector<const char *> &buf) const = 0;
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 
 private:
 	virtual size_t CalcContainerSize() = 0;
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index ff05fbff560..d25f3944a93 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -75,11 +75,11 @@ const char *ScriptDrawingSurface::GetType() {
 	return "DrawingSurface";
 }
 
-size_t ScriptDrawingSurface::CalcSerializeSize(void * /*address*/) {
+size_t ScriptDrawingSurface::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t) * 9;
 }
 
-void ScriptDrawingSurface::Serialize(void *address, Stream *out) {
+void ScriptDrawingSurface::Serialize(const void *address, Stream *out) {
 	// pack mask type in the last byte of a negative integer
 	// note: (-1) is reserved for "unused", for backward compatibility
 	if (roomMaskType > 0)
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.h b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
index 4c50f54e5bf..6b2026ebfb0 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.h
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.h
@@ -61,9 +61,9 @@ struct ScriptDrawingSurface final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
index eca57d9b413..8b6906dd73d 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.cpp
@@ -41,11 +41,11 @@ const char *ScriptDynamicSprite::GetType() {
 	return "DynamicSprite";
 }
 
-size_t ScriptDynamicSprite::CalcSerializeSize(void * /*address*/) {
+size_t ScriptDynamicSprite::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptDynamicSprite::Serialize(void *address, Stream *out) {
+void ScriptDynamicSprite::Serialize(const void *address, Stream *out) {
 	out->WriteInt32(slot);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
index fad13a8c9a1..429219d5c0c 100644
--- a/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynobj/script_dynamic_sprite.h
@@ -38,9 +38,9 @@ struct ScriptDynamicSprite final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.cpp b/engines/ags/engine/ac/dynobj/script_overlay.cpp
index 92a84c19019..46dc1c76757 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.cpp
+++ b/engines/ags/engine/ac/dynobj/script_overlay.cpp
@@ -59,11 +59,11 @@ const char *ScriptOverlay::GetType() {
 	return "Overlay";
 }
 
-size_t ScriptOverlay::CalcSerializeSize(void * /*address*/) {
+size_t ScriptOverlay::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t) * 4;
 }
 
-void ScriptOverlay::Serialize(void * /*address*/, Stream *out) {
+void ScriptOverlay::Serialize(const void * /*address*/, Stream *out) {
 	out->WriteInt32(overlayId);
 	out->WriteInt32(0); // unused (was text window x padding)
 	out->WriteInt32(0); // unused (was text window y padding)
diff --git a/engines/ags/engine/ac/dynobj/script_overlay.h b/engines/ags/engine/ac/dynobj/script_overlay.h
index 15edfc49763..7ded246091e 100644
--- a/engines/ags/engine/ac/dynobj/script_overlay.h
+++ b/engines/ags/engine/ac/dynobj/script_overlay.h
@@ -37,9 +37,9 @@ struct ScriptOverlay final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_set.cpp b/engines/ags/engine/ac/dynobj/script_set.cpp
index 122a224925d..5b0ed525546 100644
--- a/engines/ags/engine/ac/dynobj/script_set.cpp
+++ b/engines/ags/engine/ac/dynobj/script_set.cpp
@@ -35,11 +35,11 @@ const char *ScriptSetBase::GetType() {
 	return "StringSet";
 }
 
-size_t ScriptSetBase::CalcSerializeSize(void * /*address*/) {
+size_t ScriptSetBase::CalcSerializeSize(const void * /*address*/) {
 	return CalcContainerSize();
 }
 
-void ScriptSetBase::Serialize(void * /*address*/, Stream *out) {
+void ScriptSetBase::Serialize(const void * /*address*/, Stream *out) {
 	out->WriteInt32(IsSorted());
 	out->WriteInt32(IsCaseSensitive());
 	SerializeContainer(out);
diff --git a/engines/ags/engine/ac/dynobj/script_set.h b/engines/ags/engine/ac/dynobj/script_set.h
index f7be112c12c..b2b6829e02e 100644
--- a/engines/ags/engine/ac/dynobj/script_set.h
+++ b/engines/ags/engine/ac/dynobj/script_set.h
@@ -63,9 +63,9 @@ public:
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	virtual size_t CalcSerializeSize(void *address) override;
+	virtual size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 
 private:
 	virtual size_t CalcContainerSize() = 0;
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index ab076be776d..a74bf627903 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -46,11 +46,11 @@ const char *ScriptString::GetType() {
 	return "String";
 }
 
-size_t ScriptString::CalcSerializeSize(void * /*address*/) {
+size_t ScriptString::CalcSerializeSize(const void * /*address*/) {
 	return _len + 1 + sizeof(int32_t);
 }
 
-void ScriptString::Serialize(void * /*address*/, Stream *out) {
+void ScriptString::Serialize(const void * /*address*/, Stream *out) {
 	const auto *cstr = _text ? _text : "";
 	out->WriteInt32(_len);
 	out->Write(cstr, _len + 1);
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index af9db4fe3f4..548b5d00f9e 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -42,9 +42,9 @@ struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 
 private:
 	// TODO: the preallocated text buffer may be assigned externally;
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 3072a0fe6b1..35a62f76d0e 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -54,12 +54,12 @@ int ScriptUserObject::Dispose(void *address, bool /*force*/) {
 	return 1;
 }
 
-size_t ScriptUserObject::CalcSerializeSize(void *address) {
+size_t ScriptUserObject::CalcSerializeSize(const void *address) {
 	const Header &hdr = GetHeader(address);
 	return hdr.Size + FileHeaderSz;
 }
 
-void ScriptUserObject::Serialize(void *address, AGS::Shared::Stream *out) {
+void ScriptUserObject::Serialize(const void *address, AGS::Shared::Stream *out) {
 	const Header &hdr = GetHeader(address);
 	// NOTE: we only write the data, no header at the moment
 	out->Write(address, hdr.Size);
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index c192f1db1a9..19aef8a4ff8 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -50,8 +50,8 @@ public:
 	ScriptUserObject() = default;
 	~ScriptUserObject() = default;
 
-	inline static const Header &GetHeader(void *address) {
-		return reinterpret_cast<const Header &>(*(static_cast<uint8_t *>(address) - MemHeaderSz));
+	inline static const Header &GetHeader(const void *address) {
+		return reinterpret_cast<const Header &>(*(static_cast<const uint8_t *>(address) - MemHeaderSz));
 	}
 
 	// Create managed struct object and return a pointer to the beginning of a buffer
@@ -70,9 +70,9 @@ private:
 
 	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 extern ScriptUserObject globalDynamicStruct;
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.cpp b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
index a56bfdb8dfa..42851c3b1ff 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.cpp
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.cpp
@@ -37,11 +37,11 @@ const char *ScriptViewFrame::GetType() {
 	return "ViewFrame";
 }
 
-size_t ScriptViewFrame::CalcSerializeSize(void * /*address*/) {
+size_t ScriptViewFrame::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t) * 3;
 }
 
-void ScriptViewFrame::Serialize(void * /*address*/, Stream *out) {
+void ScriptViewFrame::Serialize(const void * /*address*/, Stream *out) {
 	out->WriteInt32(view);
 	out->WriteInt32(loop);
 	out->WriteInt32(frame);
diff --git a/engines/ags/engine/ac/dynobj/script_view_frame.h b/engines/ags/engine/ac/dynobj/script_view_frame.h
index a10a2bad97d..63ced07c3b5 100644
--- a/engines/ags/engine/ac/dynobj/script_view_frame.h
+++ b/engines/ags/engine/ac/dynobj/script_view_frame.h
@@ -38,9 +38,9 @@ struct ScriptViewFrame final : AGSCCDynamicObject {
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.cpp b/engines/ags/engine/ac/dynobj/script_viewport.cpp
index 10d2b8d7ac7..5c12bff51ed 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.cpp
+++ b/engines/ags/engine/ac/dynobj/script_viewport.cpp
@@ -44,11 +44,11 @@ int ScriptViewport::Dispose(void * /*address*/, bool force) {
 	return 1;
 }
 
-size_t ScriptViewport::CalcSerializeSize(void * /*address*/) {
+size_t ScriptViewport::CalcSerializeSize(const void * /*address*/) {
 	return sizeof(int32_t);
 }
 
-void ScriptViewport::Serialize(void * /*address*/, Stream *out) {
+void ScriptViewport::Serialize(const void * /*address*/, Stream *out) {
 	out->WriteInt32(_id);
 }
 
diff --git a/engines/ags/engine/ac/dynobj/script_viewport.h b/engines/ags/engine/ac/dynobj/script_viewport.h
index 0b4276fbfdc..75bbc93adc5 100644
--- a/engines/ags/engine/ac/dynobj/script_viewport.h
+++ b/engines/ags/engine/ac/dynobj/script_viewport.h
@@ -48,9 +48,9 @@ public:
 
 protected:
 	// Calculate and return required space for serialization, in bytes
-	size_t CalcSerializeSize(void *address) override;
+	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
-	void Serialize(void *address, AGS::Shared::Stream *out) override;
+	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 
 private:
 	int _id = -1; // index of viewport in the game state array


Commit: 964b85b632107930a21fdd4707dc237bbbe0d2fe
    https://github.com/scummvm/scummvm/commit/964b85b632107930a21fdd4707dc237bbbe0d2fe
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add RoomOptions comments

Partially from upstream 5ec52bab21bb1e1dfb8e519d39e8bac5a906bb3f

Changed paths:
    engines/ags/shared/game/room_struct.h


diff --git a/engines/ags/shared/game/room_struct.h b/engines/ags/shared/game/room_struct.h
index e1b47c6308d..93edf4cf1a9 100644
--- a/engines/ags/shared/game/room_struct.h
+++ b/engines/ags/shared/game/room_struct.h
@@ -125,8 +125,10 @@ typedef std::shared_ptr<Bitmap> PBitmap;
 // Various room options
 struct RoomOptions {
 	// Index of the startup music in the room
+	// this is a deprecated option, used before 3.2.* with old audio API.
 	int  StartupMusic;
-	// If saving and loading game is disabled in the room
+	// If saving and loading game is disabled in the room;
+	// this is a deprecated option that affects only built-in save/load dialogs
 	bool SaveLoadDisabled;
 	// If player character is turned off in the room
 	bool PlayerCharOff;
@@ -134,7 +136,7 @@ struct RoomOptions {
 	int  PlayerView;
 	// Room's music volume modifier
 	RoomVolumeMod MusicVolume;
-	// A collection of boolean options
+	// A collection of RoomFlags
 	int  Flags;
 
 	RoomOptions();


Commit: dd0195163fb62beb401940393a53dc2922b2f446
    https://github.com/scummvm/scummvm/commit/dd0195163fb62beb401940393a53dc2922b2f446
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: removed excessive header guidialoginternaldefs.h

>From upstream 55c81e95261e59a359d51009242e139604c76a4e

Changed paths:
  R engines/ags/engine/gui/gui_dialog_internal_defs.h
    engines/ags/engine/gui/csci_dialog.h
    engines/ags/engine/gui/gui_dialog_defines.h
    engines/ags/engine/gui/my_label.cpp
    engines/ags/engine/gui/my_listbox.cpp
    engines/ags/engine/gui/my_push_button.cpp
    engines/ags/engine/gui/my_textbox.cpp
    engines/ags/engine/gui/new_control.cpp


diff --git a/engines/ags/engine/gui/csci_dialog.h b/engines/ags/engine/gui/csci_dialog.h
index 175b8eaf038..74e1b9dd415 100644
--- a/engines/ags/engine/gui/csci_dialog.h
+++ b/engines/ags/engine/gui/csci_dialog.h
@@ -29,7 +29,7 @@
 #define AGS_ENGINE_GUI_CSCI_DIALOG_H
 
 #include "ags/shared/core/types.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/gui/gui_dialog_defines.h b/engines/ags/engine/gui/gui_dialog_defines.h
index 4dbc26b502c..a9a80ba8f20 100644
--- a/engines/ags/engine/gui/gui_dialog_defines.h
+++ b/engines/ags/engine/gui/gui_dialog_defines.h
@@ -19,10 +19,17 @@
  *
  */
 
+//=============================================================================
+//
+// Constants for built-in GUI dialogs.
+//
+//=============================================================================
+
 #ifndef AGS_ENGINE_GUI_GUI_DIALOG_DEFINES_H
 #define AGS_ENGINE_GUI_GUI_DIALOG_DEFINES_H
 
 #include "ags/engine/ac/game_setup.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -39,6 +46,8 @@ namespace AGS3 {
 #define MSG_PLAYBUTTON   994    // "Play"
 #define MSG_QUITDIALOG   995    // "Do you want to quit?"
 
+#define TEXT_HT _GP(usetup).textheight
+
 /*#define COL251 26
 #define COL252 28
 #define COL253 29
diff --git a/engines/ags/engine/gui/gui_dialog_internal_defs.h b/engines/ags/engine/gui/gui_dialog_internal_defs.h
deleted file mode 100644
index 8735012db9b..00000000000
--- a/engines/ags/engine/gui/gui_dialog_internal_defs.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/* 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 AGS_ENGINE_GUI_GUI_DIALOG_INTERNAL_DEFS_H
-#define AGS_ENGINE_GUI_GUI_DIALOG_INTERNAL_DEFS_H
-
-#include "ags/engine/gui/gui_dialog_defines.h"
-#include "ags/globals.h"
-
-#define _export
-#ifdef WINAPI
-#undef WINAPI
-#endif
-#define WINAPI
-#define TEXT_HT _GP(usetup).textheight
-
-#endif
diff --git a/engines/ags/engine/gui/my_label.cpp b/engines/ags/engine/gui/my_label.cpp
index d6a9cdcb6a3..7b6810a3263 100644
--- a/engines/ags/engine/gui/my_label.cpp
+++ b/engines/ags/engine/gui/my_label.cpp
@@ -19,13 +19,12 @@
  *
  */
 
+#include "ags/engine/gui/my_label.h"
 #include "ags/engine/ac/display.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/engine/ac/string.h"
 #include "ags/shared/font/fonts.h"
-#include "ags/shared/gui/gui_defines.h"
-#include "ags/engine/gui/my_label.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
diff --git a/engines/ags/engine/gui/my_listbox.cpp b/engines/ags/engine/gui/my_listbox.cpp
index 9fbbf700d3d..b0fea752abb 100644
--- a/engines/ags/engine/gui/my_listbox.cpp
+++ b/engines/ags/engine/gui/my_listbox.cpp
@@ -19,19 +19,18 @@
  *
  */
 
-//include <string.h>
+#include "ags/engine/gui/my_listbox.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/keycode.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gui/gui_dialog.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
-#include "ags/engine/gui/my_listbox.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
 
 namespace AGS3 {
 
-using AGS::Shared::Bitmap;
+using namespace AGS::Shared;
 
 MyListBox::MyListBox(int xx, int yy, int wii, int hii) {
 	x = xx;
diff --git a/engines/ags/engine/gui/my_push_button.cpp b/engines/ags/engine/gui/my_push_button.cpp
index a7d82d6db89..42cfd2cbf56 100644
--- a/engines/ags/engine/gui/my_push_button.cpp
+++ b/engines/ags/engine/gui/my_push_button.cpp
@@ -19,16 +19,17 @@
  *
  */
 
+#include "ags/engine/gui/my_push_button.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/sys_events.h"
+#include "ags/engine/ac/timer.h"
 #include "ags/shared/font/fonts.h"
-#include "ags/engine/gui/my_push_button.h"
+#include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gui/gui_dialog.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
 #include "ags/engine/main/game_run.h"
-#include "ags/shared/gfx/bitmap.h"
+
 #include "ags/engine/platform/base/ags_platform_driver.h"
-#include "ags/engine/ac/timer.h"
 
 namespace AGS3 {
 
diff --git a/engines/ags/engine/gui/my_textbox.cpp b/engines/ags/engine/gui/my_textbox.cpp
index 10b3ed19c10..9057887af97 100644
--- a/engines/ags/engine/gui/my_textbox.cpp
+++ b/engines/ags/engine/gui/my_textbox.cpp
@@ -19,16 +19,17 @@
  *
  */
 
+#include "ags/engine/gui/my_textbox.h"
 #include "ags/shared/ac/keycode.h"
 #include "ags/shared/font/fonts.h"
-#include "ags/engine/gui/my_textbox.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
 #include "ags/shared/gfx/bitmap.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
+
 #include "ags/globals.h"
 
 namespace AGS3 {
 
-using AGS::Shared::Bitmap;
+using namespace AGS::Shared;
 
 MyTextBox::MyTextBox(int xx, int yy, int wii, const char *tee) {
 	x = xx;
diff --git a/engines/ags/engine/gui/new_control.cpp b/engines/ags/engine/gui/new_control.cpp
index 2fbeee56b42..03bfedaca13 100644
--- a/engines/ags/engine/gui/new_control.cpp
+++ b/engines/ags/engine/gui/new_control.cpp
@@ -21,7 +21,7 @@
 
 #include "ags/engine/gui/new_control.h"
 #include "ags/engine/gui/gui_dialog.h"
-#include "ags/engine/gui/gui_dialog_internal_defs.h"
+#include "ags/engine/gui/gui_dialog_defines.h"
 #include "ags/globals.h"
 
 namespace AGS3 {


Commit: 73e50455abf8bcae3fb5f83ec722c5c80cc538e2
    https://github.com/scummvm/scummvm/commit/73e50455abf8bcae3fb5f83ec722c5c80cc538e2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: precache only player walk view

>From upstream 20c7e57eecfec7b53a3067fd59de50c448a4d977

Changed paths:
    engines/ags/engine/ac/view_frame.cpp
    engines/ags/engine/ac/view_frame.h
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index a754a7bfb2a..6d27b26812f 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -107,11 +107,12 @@ int ViewFrame_GetFrame(ScriptViewFrame *svf) {
 
 //=============================================================================
 
-void precache_view(int view) {
+void precache_view(int view, int max_loops) {
 	if (view < 0)
 		return;
 
-	for (int i = 0; i < _GP(views)[view].numLoops; i++) {
+	max_loops = std::min(_GP(views)[view].numLoops, max_loops);
+	for (int i = 0; i < max_loops; i++) {
 		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; j++)
 			_GP(spriteset).Precache(_GP(views)[view].loops[i].frames[j].pic);
 	}
diff --git a/engines/ags/engine/ac/view_frame.h b/engines/ags/engine/ac/view_frame.h
index f29b0417256..6e4685856c1 100644
--- a/engines/ags/engine/ac/view_frame.h
+++ b/engines/ags/engine/ac/view_frame.h
@@ -50,7 +50,7 @@ int  ViewFrame_GetView(ScriptViewFrame *svf);
 int  ViewFrame_GetLoop(ScriptViewFrame *svf);
 int  ViewFrame_GetFrame(ScriptViewFrame *svf);
 
-void precache_view(int view);
+void precache_view(int view, int max_loops = INT_MAX);
 // Calculate the frame sound volume from different factors;
 // pass scale as 100 if volume scaling is disabled
 // NOTE: historically scales only in 0-100 range :/
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 89d84a41092..2b03801aff4 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -560,7 +560,7 @@ void engine_init_game_settings() {
 	}
 	// may as well preload the character gfx
 	if (_G(playerchar)->view >= 0)
-		precache_view(_G(playerchar)->view);
+		precache_view(_G(playerchar)->view, Character_GetDiagonalWalking(_G(playerchar)) ? 8 : 4);
 
 	_G(our_eip) = -6;
 


Commit: 6c5c0f17f3d2969e7c496ccfb61506e1e1a9f0d2
    https://github.com/scummvm/scummvm/commit/6c5c0f17f3d2969e7c496ccfb61506e1e1a9f0d2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use templates in gfxdriverbase getpixel functions

>From upstream cd643de30cbd31eb8fb15c504d8bd2a0f7326678

Changed paths:
    engines/ags/engine/gfx/gfx_driver_base.cpp


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 743705bff9a..8d5f41f40ec 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -360,49 +360,40 @@ void VideoMemoryGraphicsDriver::DestroyFxPool() {
 	_fxIndex = 0;
 }
 
-
-#define algetr32(c) getr32(c)
-#define algetg32(c) getg32(c)
-#define algetb32(c) getb32(c)
-#define algeta32(c) geta32(c)
-
-#define algetr16(c) getr16(c)
-#define algetg16(c) getg16(c)
-#define algetb16(c) getb16(c)
-
-#define algetr8(c)  getr8(c)
-#define algetg8(c)  getg8(c)
-#define algetb8(c)  getb8(c)
-
-
-__inline void get_pixel_if_not_transparent8(const unsigned char *pixel, unsigned char *red, unsigned char *green, unsigned char *blue, unsigned char *divisor) {
-	if (pixel[0] != MASK_COLOR_8) {
-		*red += algetr8(pixel[0]);
-		*green += algetg8(pixel[0]);
-		*blue += algetb8(pixel[0]);
-		divisor[0]++;
-	}
+template <typename T> T algetr(const T);
+template <typename T> T algetg(const T);
+template <typename T> T algetb(const T);
+template <typename T> T algeta(const T);
+
+template <> uint8_t algetr(const uint8_t c) { return getr8(c); }
+template <> uint8_t algetg(const uint8_t c) { return getg8(c); }
+template <> uint8_t algetb(const uint8_t c) { return getb8(c); }
+template <> uint8_t algeta(const uint8_t c) { return 0xFF; }
+
+template <> uint16_t algetr(const uint16_t c) { return getr16(c); }
+template <> uint16_t algetg(const uint16_t c) { return getg16(c); }
+template <> uint16_t algetb(const uint16_t c) { return getb16(c); }
+template <> uint16_t algeta(const uint16_t c) { return 0xFF; }
+
+template <> uint32_t algetr(const uint32_t c) { return getr32(c); }
+template <> uint32_t algetg(const uint32_t c) { return getg32(c); }
+template <> uint32_t algetb(const uint32_t c) { return getb32(c); }
+template <> uint32_t algeta(const uint32_t c) { return geta32(c); }
+
+template <typename T> bool is_color_mask(const T);
+template <> bool is_color_mask(const uint8_t c) { return c == MASK_COLOR_8;}
+template <> bool is_color_mask(const uint16_t c) { return c == MASK_COLOR_16;}
+template <> bool is_color_mask(const uint32_t c) { return c == MASK_COLOR_32;}
+
+template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red, T *green, T *blue, T *divisor) {
+    if (!is_color_mask<T>(pixel[0])) {
+        *red += algetr<T>(pixel[0]);
+        *green += algetg<T>(pixel[0]);
+        *blue += algetb<T>(pixel[0]);
+        divisor[0]++;
+    }
 }
 
-__inline void get_pixel_if_not_transparent16(const unsigned short *pixel, unsigned short *red, unsigned short *green, unsigned short *blue, unsigned short *divisor) {
-	if (pixel[0] != MASK_COLOR_16) {
-		*red += algetr16(pixel[0]);
-		*green += algetg16(pixel[0]);
-		*blue += algetb16(pixel[0]);
-		divisor[0]++;
-	}
-}
-
-__inline void get_pixel_if_not_transparent32(const unsigned int *pixel, unsigned int *red, unsigned int *green, unsigned int *blue, unsigned int *divisor) {
-	if (pixel[0] != MASK_COLOR_32) {
-		*red += algetr32(pixel[0]);
-		*green += algetg32(pixel[0]);
-		*blue += algetb32(pixel[0]);
-		divisor[0]++;
-	}
-}
-
-
 #define VMEMCOLOR_RGBA(r,g,b,a) \
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
@@ -421,7 +412,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 
 			for (int x = 0; x < tile->width; x++) {
 				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
-				if (*srcData == MASK_COLOR_8) {
+				if (is_color_mask<uint8_t>(*srcData)) {
 					if (!usingLinearFiltering)
 						memPtrLong[x] = 0;
 					// set to transparent, but use the colour from the neighbouring
@@ -429,13 +420,13 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					else {
 						unsigned char red = 0, green = 0, blue = 0, divisor = 0;
 						if (x > 0)
-							get_pixel_if_not_transparent8(&srcData[-1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint8_t>(&srcData[-1], &red, &green, &blue, &divisor);
 						if (x < tile->width - 1)
-							get_pixel_if_not_transparent8(&srcData[1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint8_t>(&srcData[1], &red, &green, &blue, &divisor);
 						if (y > 0)
-							get_pixel_if_not_transparent8((const unsigned char *)&scanline_before[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint8_t>((const unsigned char *)&scanline_before[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
 						if (y < tile->height - 1)
-							get_pixel_if_not_transparent8((const unsigned char *)&scanline_after[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint8_t>((const unsigned char *)&scanline_after[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
 						if (divisor > 0)
 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
 						else
@@ -443,7 +434,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					}
 					lastPixelWasTransparent = true;
 				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF);
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint8_t>(*srcData), algetg<uint8_t>(*srcData), algetb<uint8_t>(*srcData), 0xFF);
 					if (lastPixelWasTransparent) {
 						// update the colour of the previous tranparent pixel, to
 						// stop black outlines when linear filtering
@@ -465,7 +456,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 
 			for (int x = 0; x < tile->width; x++) {
 				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
-				if (*srcData == MASK_COLOR_16) {
+				if (is_color_mask<uint16_t>(*srcData)) {
 					if (!usingLinearFiltering)
 						memPtrLong[x] = 0;
 					// set to transparent, but use the colour from the neighbouring
@@ -473,13 +464,13 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					else {
 						unsigned short red = 0, green = 0, blue = 0, divisor = 0;
 						if (x > 0)
-							get_pixel_if_not_transparent16(&srcData[-1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint16_t>(&srcData[-1], &red, &green, &blue, &divisor);
 						if (x < tile->width - 1)
-							get_pixel_if_not_transparent16(&srcData[1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint16_t>(&srcData[1], &red, &green, &blue, &divisor);
 						if (y > 0)
-							get_pixel_if_not_transparent16((const unsigned short *)&scanline_before[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint16_t>((const unsigned short *)&scanline_before[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
 						if (y < tile->height - 1)
-							get_pixel_if_not_transparent16((const unsigned short *)&scanline_after[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint16_t>((const unsigned short *)&scanline_after[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
 						if (divisor > 0)
 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
 						else
@@ -487,7 +478,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					}
 					lastPixelWasTransparent = true;
 				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF);
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint16_t>(*srcData), algetg<uint16_t>(*srcData), algetb<uint16_t>(*srcData), 0xFF);
 					if (lastPixelWasTransparent) {
 						// update the colour of the previous tranparent pixel, to
 						// stop black outlines when linear filtering
@@ -509,7 +500,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 
 			for (int x = 0; x < tile->width; x++) {
 				const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-				if (*srcData == MASK_COLOR_32) {
+				if (is_color_mask<uint32_t>(*srcData)) {
 					if (!usingLinearFiltering)
 						memPtrLong[x] = 0;
 					// set to transparent, but use the colour from the neighbouring
@@ -517,13 +508,13 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					else {
 						unsigned int red = 0, green = 0, blue = 0, divisor = 0;
 						if (x > 0)
-							get_pixel_if_not_transparent32(&srcData[-1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint32_t>(&srcData[-1], &red, &green, &blue, &divisor);
 						if (x < tile->width - 1)
-							get_pixel_if_not_transparent32(&srcData[1], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint32_t>(&srcData[1], &red, &green, &blue, &divisor);
 						if (y > 0)
-							get_pixel_if_not_transparent32((const unsigned int *)&scanline_before[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint32_t>((const unsigned int *)&scanline_before[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
 						if (y < tile->height - 1)
-							get_pixel_if_not_transparent32((const unsigned int *)&scanline_after[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
+							get_pixel_if_not_transparent<uint32_t>((const unsigned int *)&scanline_after[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
 						if (divisor > 0)
 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
 						else
@@ -531,9 +522,9 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 					}
 					lastPixelWasTransparent = true;
 				} else if (has_alpha) {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData));
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), algeta<uint32_t>(*srcData));
 				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF);
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), 0xFF);
 					if (lastPixelWasTransparent) {
 						// update the colour of the previous tranparent pixel, to
 						// stop black outlines when linear filtering
@@ -562,7 +553,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 
 			for (int x = 0; x < tile->width; x++) {
 				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF);
+				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint8_t>(*srcData), algetg<uint8_t>(*srcData), algetb<uint8_t>(*srcData), 0xFF);
 			}
 
 			dst_ptr += dst_pitch;
@@ -575,7 +566,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 
 			for (int x = 0; x < tile->width; x++) {
 				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF);
+				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint16_t>(*srcData), algetg<uint16_t>(*srcData), algetb<uint16_t>(*srcData), 0xFF);
 			}
 
 			dst_ptr += dst_pitch;
@@ -589,7 +580,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 
 				for (int x = 0; x < tile->width; x++) {
 					const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData));
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), algeta<uint32_t>(*srcData));
 				}
 				dst_ptr += dst_pitch;
 			}
@@ -600,7 +591,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 
 				for (int x = 0; x < tile->width; x++) {
 					const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF);
+					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), 0xFF);
 				}
 				dst_ptr += dst_pitch;
 			}


Commit: 941df46f904dbc59d43e8067e0fbfb2cc9a07e06
    https://github.com/scummvm/scummvm/commit/941df46f904dbc59d43e8067e0fbfb2cc9a07e06
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: refactor video to mem using templates

>From upstream e0cfa4cdf307bcf941de0e956acd0c05443e4933

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 8d5f41f40ec..85a4c126c90 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -397,147 +397,111 @@ template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red,
 #define VMEMCOLOR_RGBA(r,g,b,a) \
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
-void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-												 uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
-	const int src_depth = bitmap->GetColorDepth();
-	bool lastPixelWasTransparent = false;
-	switch (src_depth) {
-	case 8: {
-		for (int y = 0; y < tile->height; y++) {
-			lastPixelWasTransparent = false;
-			const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + tile->y - 1) : nullptr;
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-			const uint8_t *scanline_after = (y < tile->height - 1) ? bitmap->GetScanLine(y + tile->y + 1) : nullptr;
-			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
+template<typename T>
+void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
+	const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+	uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
 
-			for (int x = 0; x < tile->width; x++) {
-				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
-				if (is_color_mask<uint8_t>(*srcData)) {
-					if (!usingLinearFiltering)
+	bool lastPixelWasTransparent = false;
+	for (int y = 0; y < tile->height; y++) {
+		lastPixelWasTransparent = false;
+		const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + tile->y - 1) : nullptr;
+		const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
+		const uint8_t *scanline_after = (y < tile->height - 1) ? bitmap->GetScanLine(y + tile->y + 1) : nullptr;
+		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
+
+		for (int x = 0; x < tile->width; x++) {
+			auto *srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
+
+			if (is_color_mask<T>(*srcData)) {
+				if (!usingLinearFiltering)
+					memPtrLong[x] = 0;
+				// set to transparent, but use the colour from the neighbouring
+				// pixel to stop the linear filter doing black outlines
+				else {
+					T red = 0, green = 0, blue = 0, divisor = 0;
+					if (x > 0)
+						get_pixel_if_not_transparent<T>(&srcData[-1], &red, &green, &blue, &divisor);
+					if (x < tile->width - 1)
+						get_pixel_if_not_transparent<T>(&srcData[1], &red, &green, &blue, &divisor);
+					if (y > 0)
+						get_pixel_if_not_transparent<T>(
+							(const T *)&scanline_before[(x + tile->x) * sizeof(T)], &red, &green,
+							&blue, &divisor);
+					if (y < tile->height - 1)
+						get_pixel_if_not_transparent<T>(
+							(const T *)&scanline_after[(x + tile->x) * sizeof(T)], &red, &green,
+							&blue, &divisor);
+					if (divisor > 0)
+						memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
+					else
 						memPtrLong[x] = 0;
-					// set to transparent, but use the colour from the neighbouring
-					// pixel to stop the linear filter doing black outlines
-					else {
-						unsigned char red = 0, green = 0, blue = 0, divisor = 0;
-						if (x > 0)
-							get_pixel_if_not_transparent<uint8_t>(&srcData[-1], &red, &green, &blue, &divisor);
-						if (x < tile->width - 1)
-							get_pixel_if_not_transparent<uint8_t>(&srcData[1], &red, &green, &blue, &divisor);
-						if (y > 0)
-							get_pixel_if_not_transparent<uint8_t>((const unsigned char *)&scanline_before[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
-						if (y < tile->height - 1)
-							get_pixel_if_not_transparent<uint8_t>((const unsigned char *)&scanline_after[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
-						if (divisor > 0)
-							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
-						else
-							memPtrLong[x] = 0;
-					}
-					lastPixelWasTransparent = true;
-				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint8_t>(*srcData), algetg<uint8_t>(*srcData), algetb<uint8_t>(*srcData), 0xFF);
-					if (lastPixelWasTransparent) {
-						// update the colour of the previous tranparent pixel, to
-						// stop black outlines when linear filtering
-						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
-						lastPixelWasTransparent = false;
-					}
+				}
+				lastPixelWasTransparent = true;
+			} else if (has_alpha) {
+				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData),
+											   algeta<T>(*srcData));
+			} else {
+				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData),
+											   0xFF);
+				if (lastPixelWasTransparent) {
+					// update the colour of the previous tranparent pixel, to
+					// stop black outlines when linear filtering
+					memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
+					lastPixelWasTransparent = false;
 				}
 			}
-			dst_ptr += dst_pitch;
 		}
-	} break;
-	case 16: {
+		dst_ptr += dst_pitch;
+	}
+}
+
+void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+												 uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
+	const int src_depth = bitmap->GetColorDepth();
+	switch (src_depth) {
+	case 8:
+		BitmapToVideoMemImpl<uint8_t>(bitmap, false, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		break;
+	case 16:
+		BitmapToVideoMemImpl<uint16_t>(bitmap, false, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		break;
+	case 32:
+		BitmapToVideoMemImpl<uint32_t>(bitmap, has_alpha, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		break;
+	default:
+		break;
+	}
+}
+
+template<typename T>
+void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(
+	const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+	uint8_t *dst_ptr, const int dst_pitch) {
+	if (has_alpha) {
 		for (int y = 0; y < tile->height; y++) {
-			lastPixelWasTransparent = false;
-			const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + tile->y - 1) : nullptr;
 			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-			const uint8_t *scanline_after = (y < tile->height - 1) ? bitmap->GetScanLine(y + tile->y + 1) : nullptr;
 			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
 
 			for (int x = 0; x < tile->width; x++) {
-				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
-				if (is_color_mask<uint16_t>(*srcData)) {
-					if (!usingLinearFiltering)
-						memPtrLong[x] = 0;
-					// set to transparent, but use the colour from the neighbouring
-					// pixel to stop the linear filter doing black outlines
-					else {
-						unsigned short red = 0, green = 0, blue = 0, divisor = 0;
-						if (x > 0)
-							get_pixel_if_not_transparent<uint16_t>(&srcData[-1], &red, &green, &blue, &divisor);
-						if (x < tile->width - 1)
-							get_pixel_if_not_transparent<uint16_t>(&srcData[1], &red, &green, &blue, &divisor);
-						if (y > 0)
-							get_pixel_if_not_transparent<uint16_t>((const unsigned short *)&scanline_before[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
-						if (y < tile->height - 1)
-							get_pixel_if_not_transparent<uint16_t>((const unsigned short *)&scanline_after[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
-						if (divisor > 0)
-							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
-						else
-							memPtrLong[x] = 0;
-					}
-					lastPixelWasTransparent = true;
-				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint16_t>(*srcData), algetg<uint16_t>(*srcData), algetb<uint16_t>(*srcData), 0xFF);
-					if (lastPixelWasTransparent) {
-						// update the colour of the previous tranparent pixel, to
-						// stop black outlines when linear filtering
-						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
-						lastPixelWasTransparent = false;
-					}
-				}
+				auto srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
+				memPtrLong[x] = VMEMCOLOR_RGBA(
+					algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData), algeta<T>(*srcData));
 			}
 			dst_ptr += dst_pitch;
 		}
-	} break;
-	case 32: {
+	} else {
 		for (int y = 0; y < tile->height; y++) {
-			lastPixelWasTransparent = false;
-			const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + tile->y - 1) : nullptr;
 			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-			const uint8_t *scanline_after = (y < tile->height - 1) ? bitmap->GetScanLine(y + tile->y + 1) : nullptr;
 			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
 
 			for (int x = 0; x < tile->width; x++) {
-				const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-				if (is_color_mask<uint32_t>(*srcData)) {
-					if (!usingLinearFiltering)
-						memPtrLong[x] = 0;
-					// set to transparent, but use the colour from the neighbouring
-					// pixel to stop the linear filter doing black outlines
-					else {
-						unsigned int red = 0, green = 0, blue = 0, divisor = 0;
-						if (x > 0)
-							get_pixel_if_not_transparent<uint32_t>(&srcData[-1], &red, &green, &blue, &divisor);
-						if (x < tile->width - 1)
-							get_pixel_if_not_transparent<uint32_t>(&srcData[1], &red, &green, &blue, &divisor);
-						if (y > 0)
-							get_pixel_if_not_transparent<uint32_t>((const unsigned int *)&scanline_before[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
-						if (y < tile->height - 1)
-							get_pixel_if_not_transparent<uint32_t>((const unsigned int *)&scanline_after[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
-						if (divisor > 0)
-							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
-						else
-							memPtrLong[x] = 0;
-					}
-					lastPixelWasTransparent = true;
-				} else if (has_alpha) {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), algeta<uint32_t>(*srcData));
-				} else {
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), 0xFF);
-					if (lastPixelWasTransparent) {
-						// update the colour of the previous tranparent pixel, to
-						// stop black outlines when linear filtering
-						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
-						lastPixelWasTransparent = false;
-					}
-				}
+				auto srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
+				memPtrLong[x] = VMEMCOLOR_RGBA(
+					algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData), 0xFF);
 			}
 			dst_ptr += dst_pitch;
 		}
-	} break;
-	default:
-		break;
 	}
 }
 
@@ -546,57 +510,15 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 	const int src_depth = bitmap->GetColorDepth();
 
 	switch (src_depth) {
-	case 8: {
-		for (int y = 0; y < tile->height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-			for (int x = 0; x < tile->width; x++) {
-				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint8_t>(*srcData), algetg<uint8_t>(*srcData), algetb<uint8_t>(*srcData), 0xFF);
-			}
-
-			dst_ptr += dst_pitch;
-		}
-	} break;
-	case 16: {
-		for (int y = 0; y < tile->height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-			for (int x = 0; x < tile->width; x++) {
-				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint16_t>(*srcData), algetg<uint16_t>(*srcData), algetb<uint16_t>(*srcData), 0xFF);
-			}
-
-			dst_ptr += dst_pitch;
-		}
-	} break;
-	case 32: {
-		if (has_alpha) {
-			for (int y = 0; y < tile->height; y++) {
-				const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-				unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-				for (int x = 0; x < tile->width; x++) {
-					const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), algeta<uint32_t>(*srcData));
-				}
-				dst_ptr += dst_pitch;
-			}
-		} else {
-			for (int y = 0; y < tile->height; y++) {
-				const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-				unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-				for (int x = 0; x < tile->width; x++) {
-					const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
-					memPtrLong[x] = VMEMCOLOR_RGBA(algetr<uint32_t>(*srcData), algetg<uint32_t>(*srcData), algetb<uint32_t>(*srcData), 0xFF);
-				}
-				dst_ptr += dst_pitch;
-			}
-		}
-	} break;
+	case 8:
+		BitmapToVideoMemOpaqueImpl<uint8_t>(bitmap, false, tile, dst_ptr, dst_pitch);
+		break;
+	case 16:
+		BitmapToVideoMemOpaqueImpl<uint16_t>(bitmap, false, tile, dst_ptr, dst_pitch);
+		break;
+	case 32:
+		BitmapToVideoMemOpaqueImpl<uint32_t>(bitmap, has_alpha, tile, dst_ptr, dst_pitch);
+		break;
 	default:
 		break;
 	}
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index b76e9f22216..8149f7da45f 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -350,6 +350,19 @@ private:
 	std::vector<ScreenFx> _fxPool;
 	size_t _fxIndex; // next free pool item
 
+	// specialized method to convert bitmap to video memory depending on bit depth
+	template<typename T>
+	void
+	BitmapToVideoMemImpl(
+		const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+		uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
+
+	template<typename T>
+	void
+	BitmapToVideoMemOpaqueImpl(
+		const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+		uint8_t *dst_ptr, const int dst_pitch);
+
 	// Texture short-term cache:
 	// - caches textures while they are in the immediate use;
 	// - this lets to share same texture data among multiple sprites on screen.


Commit: 9e85642f3099b4f0bcebd1133c78facbb0bdce66
    https://github.com/scummvm/scummvm/commit/9e85642f3099b4f0bcebd1133c78facbb0bdce66
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in btvm functions with alpha compile time

>From upstream 19a9f54e41127b74856f45677c22885bb4b94898

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 85a4c126c90..ea17296cb3e 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -397,9 +397,9 @@ template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red,
 #define VMEMCOLOR_RGBA(r,g,b,a) \
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
-template<typename T>
+template<typename T, bool HasAlpha>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
-	const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+	const Bitmap *bitmap, const TextureTile *tile,
 	uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
 
 	bool lastPixelWasTransparent = false;
@@ -438,7 +438,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
 						memPtrLong[x] = 0;
 				}
 				lastPixelWasTransparent = true;
-			} else if (has_alpha) {
+			} else if (HasAlpha) {
 				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData),
 											   algeta<T>(*srcData));
 			} else {
@@ -461,24 +461,28 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 	const int src_depth = bitmap->GetColorDepth();
 	switch (src_depth) {
 	case 8:
-		BitmapToVideoMemImpl<uint8_t>(bitmap, false, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		BitmapToVideoMemImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
 		break;
 	case 16:
-		BitmapToVideoMemImpl<uint16_t>(bitmap, false, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		BitmapToVideoMemImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
 		break;
 	case 32:
-		BitmapToVideoMemImpl<uint32_t>(bitmap, has_alpha, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		if (has_alpha) {
+			BitmapToVideoMemImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		} else {
+			BitmapToVideoMemImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		}
 		break;
 	default:
 		break;
 	}
 }
 
-template<typename T>
+template<typename T, bool HasAlpha>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(
-	const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+	const Bitmap *bitmap, const TextureTile *tile,
 	uint8_t *dst_ptr, const int dst_pitch) {
-	if (has_alpha) {
+	if (HasAlpha) {
 		for (int y = 0; y < tile->height; y++) {
 			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
 			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
@@ -511,13 +515,17 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, con
 
 	switch (src_depth) {
 	case 8:
-		BitmapToVideoMemOpaqueImpl<uint8_t>(bitmap, false, tile, dst_ptr, dst_pitch);
+		BitmapToVideoMemOpaqueImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
 	case 16:
-		BitmapToVideoMemOpaqueImpl<uint16_t>(bitmap, false, tile, dst_ptr, dst_pitch);
+		BitmapToVideoMemOpaqueImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
 	case 32:
-		BitmapToVideoMemOpaqueImpl<uint32_t>(bitmap, has_alpha, tile, dst_ptr, dst_pitch);
+		if (has_alpha) {
+			BitmapToVideoMemOpaqueImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch);
+		} else {
+			BitmapToVideoMemOpaqueImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch);
+		}
 		break;
 	default:
 		break;
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 8149f7da45f..b5e5d75bf8e 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -351,16 +351,16 @@ private:
 	size_t _fxIndex; // next free pool item
 
 	// specialized method to convert bitmap to video memory depending on bit depth
-	template<typename T>
+	template<typename T, bool HasAlpha>
 	void
 	BitmapToVideoMemImpl(
-		const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+		const Bitmap *bitmap, const TextureTile *tile,
 		uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
 
-	template<typename T>
+	template<typename T, bool HasAlpha>
 	void
 	BitmapToVideoMemOpaqueImpl(
-		const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+		const Bitmap *bitmap, const TextureTile *tile,
 		uint8_t *dst_ptr, const int dst_pitch);
 
 	// Texture short-term cache:


Commit: 27b34d2bf099d39ff341da893a3042c3d6c12de9
    https://github.com/scummvm/scummvm/commit/27b34d2bf099d39ff341da893a3042c3d6c12de9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: usingLinearFiltring move to compile time

>From upstream a93b553eb238d268370746b7e9532393bf8a6f5b

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index ea17296cb3e..7dbbefbdd1f 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -397,10 +397,10 @@ template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red,
 #define VMEMCOLOR_RGBA(r,g,b,a) \
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
-template<typename T, bool HasAlpha>
+template<typename T, bool HasAlpha, bool UsingLinearFiltering>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
 	const Bitmap *bitmap, const TextureTile *tile,
-	uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
+	uint8_t *dst_ptr, const int dst_pitch) {
 
 	bool lastPixelWasTransparent = false;
 	for (int y = 0; y < tile->height; y++) {
@@ -414,7 +414,7 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
 			auto *srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
 
 			if (is_color_mask<T>(*srcData)) {
-				if (!usingLinearFiltering)
+				if (!UsingLinearFiltering)
 					memPtrLong[x] = 0;
 				// set to transparent, but use the colour from the neighbouring
 				// pixel to stop the linear filter doing black outlines
@@ -461,16 +461,28 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 	const int src_depth = bitmap->GetColorDepth();
 	switch (src_depth) {
 	case 8:
-		BitmapToVideoMemImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		if (usingLinearFiltering) {
+			BitmapToVideoMemImpl<uint8_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
+		} else {
+			BitmapToVideoMemImpl<uint8_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
+		}
 		break;
 	case 16:
-		BitmapToVideoMemImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		if (usingLinearFiltering) {
+			BitmapToVideoMemImpl<uint16_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
+		} else {
+			BitmapToVideoMemImpl<uint16_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
+		}
 		break;
 	case 32:
-		if (has_alpha) {
-			BitmapToVideoMemImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+		if (has_alpha && usingLinearFiltering) {
+			BitmapToVideoMemImpl<uint32_t, true, true>(bitmap, tile, dst_ptr, dst_pitch);
+		} else if (has_alpha && !usingLinearFiltering) {
+			BitmapToVideoMemImpl<uint32_t, true, false>(bitmap, tile, dst_ptr, dst_pitch);
+		} else if (!has_alpha && usingLinearFiltering) {
+			BitmapToVideoMemImpl<uint32_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
 		} else {
-			BitmapToVideoMemImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch, usingLinearFiltering);
+			BitmapToVideoMemImpl<uint32_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
 		}
 		break;
 	default:
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index b5e5d75bf8e..70281c0d587 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -351,11 +351,11 @@ private:
 	size_t _fxIndex; // next free pool item
 
 	// specialized method to convert bitmap to video memory depending on bit depth
-	template<typename T, bool HasAlpha>
+	template<typename T, bool HasAlpha, bool UsingLinearFiltering>
 	void
 	BitmapToVideoMemImpl(
 		const Bitmap *bitmap, const TextureTile *tile,
-		uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
+		uint8_t *dst_ptr, const int dst_pitch);
 
 	template<typename T, bool HasAlpha>
 	void


Commit: 2fd65e0c5af734b0864474ac720058fee06b2505
    https://github.com/scummvm/scummvm/commit/2fd65e0c5af734b0864474ac720058fee06b2505
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: more const stuff in BitmapToVideoMem functions

>From upstream 7afc34a74e46b23549caf9b46123fc62de2e2b41

Changed paths:
    engines/ags/engine/gfx/gfx_driver_base.cpp


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 7dbbefbdd1f..1ef685cedfd 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -386,10 +386,11 @@ template <> bool is_color_mask(const uint16_t c) { return c == MASK_COLOR_16;}
 template <> bool is_color_mask(const uint32_t c) { return c == MASK_COLOR_32;}
 
 template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red, T *green, T *blue, T *divisor) {
-    if (!is_color_mask<T>(pixel[0])) {
-        *red += algetr<T>(pixel[0]);
-        *green += algetg<T>(pixel[0]);
-        *blue += algetb<T>(pixel[0]);
+	const T px_color = pixel[0];
+	if (!is_color_mask<T>(px_color)) {
+        *red += algetr<T>(px_color);
+        *green += algetg<T>(px_color);
+        *blue += algetb<T>(px_color);
         divisor[0]++;
     }
 }
@@ -401,58 +402,66 @@ template<typename T, bool HasAlpha, bool UsingLinearFiltering>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
 	const Bitmap *bitmap, const TextureTile *tile,
 	uint8_t *dst_ptr, const int dst_pitch) {
-
+	// tell the compiler these won't change mid loop execution
+	const int t_width = tile->width;
+	const int t_height = tile->height;
+	const int t_x = tile->x;
+	const int t_y = tile->y;
+
+	const int src_bpp = sizeof(T);
+	const int idst_pitch = dst_pitch * sizeof(uint8_t) / sizeof(uint32_t); // destination is always 32-bit
+	auto idst = reinterpret_cast<uint32_t *>(dst_ptr);
 	bool lastPixelWasTransparent = false;
-	for (int y = 0; y < tile->height; y++) {
+
+	for (int y = 0; y < t_height; y++) {
 		lastPixelWasTransparent = false;
-		const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + tile->y - 1) : nullptr;
-		const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
-		const uint8_t *scanline_after = (y < tile->height - 1) ? bitmap->GetScanLine(y + tile->y + 1) : nullptr;
-		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-		for (int x = 0; x < tile->width; x++) {
-			auto *srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
-
-			if (is_color_mask<T>(*srcData)) {
-				if (!UsingLinearFiltering)
-					memPtrLong[x] = 0;
-				// set to transparent, but use the colour from the neighbouring
-				// pixel to stop the linear filter doing black outlines
-				else {
+		const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + t_y - 1) : nullptr;
+		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
+		const uint8_t *scanline_after = (y < t_height - 1) ? bitmap->GetScanLine(y + t_y + 1) : nullptr;
+
+		for (int x = 0; x < t_width; x++) {
+			auto srcData = (const T *)&scanline_at[(x + t_x) * src_bpp];
+			const T src_color = srcData[0];
+
+			if (is_color_mask<T>(src_color)) {
+				if (!UsingLinearFiltering) {
+					idst[x] = 0;
+				} else {
+					// set to transparent, but use the colour from the neighbouring
+					// pixel to stop the linear filter doing black outlines
 					T red = 0, green = 0, blue = 0, divisor = 0;
 					if (x > 0)
 						get_pixel_if_not_transparent<T>(&srcData[-1], &red, &green, &blue, &divisor);
-					if (x < tile->width - 1)
+					if (x < t_width - 1)
 						get_pixel_if_not_transparent<T>(&srcData[1], &red, &green, &blue, &divisor);
 					if (y > 0)
 						get_pixel_if_not_transparent<T>(
-							(const T *)&scanline_before[(x + tile->x) * sizeof(T)], &red, &green,
+							(const T *)&scanline_before[(x + t_x) * src_bpp], &red, &green,
 							&blue, &divisor);
-					if (y < tile->height - 1)
+					if (y < t_height - 1)
 						get_pixel_if_not_transparent<T>(
-							(const T *)&scanline_after[(x + tile->x) * sizeof(T)], &red, &green,
+							(const T *)&scanline_after[(x + t_x) * src_bpp], &red, &green,
 							&blue, &divisor);
 					if (divisor > 0)
-						memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
+						idst[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
 					else
-						memPtrLong[x] = 0;
+						idst[x] = 0;
 				}
 				lastPixelWasTransparent = true;
 			} else if (HasAlpha) {
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData),
-											   algeta<T>(*srcData));
+				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color),
+										 algeta<T>(src_color));
 			} else {
-				memPtrLong[x] = VMEMCOLOR_RGBA(algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData),
-											   0xFF);
+				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
 				if (lastPixelWasTransparent) {
-					// update the colour of the previous tranparent pixel, to
+					// update the colour of the previous transparent pixel, to
 					// stop black outlines when linear filtering
-					memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
+					idst[x - 1] = idst[x] & 0x00FFFFFF;
 					lastPixelWasTransparent = false;
 				}
 			}
 		}
-		dst_ptr += dst_pitch;
+		idst += idst_pitch;
 	}
 }
 
@@ -494,27 +503,33 @@ template<typename T, bool HasAlpha>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(
 	const Bitmap *bitmap, const TextureTile *tile,
 	uint8_t *dst_ptr, const int dst_pitch) {
+	const int t_width = tile->width;
+	const int t_height = tile->height;
+	const int t_x = tile->x;
+	const int t_y = tile->y;
 	if (HasAlpha) {
-		for (int y = 0; y < tile->height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
+		for (int y = 0; y < t_height; y++) {
+			const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
 			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
 
-			for (int x = 0; x < tile->width; x++) {
-				auto srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
+			for (int x = 0; x < t_width; x++) {
+				auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
+				const T src_color = srcData[0];
 				memPtrLong[x] = VMEMCOLOR_RGBA(
-					algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData), algeta<T>(*srcData));
+					algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), algeta<T>(src_color));
 			}
 			dst_ptr += dst_pitch;
 		}
 	} else {
-		for (int y = 0; y < tile->height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
+		for (int y = 0; y < t_height; y++) {
+			const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
 			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
 
-			for (int x = 0; x < tile->width; x++) {
-				auto srcData = (const T *)&scanline_at[(x + tile->x) * sizeof(T)];
+			for (int x = 0; x < t_width; x++) {
+				auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
+				const T src_color = srcData[0];
 				memPtrLong[x] = VMEMCOLOR_RGBA(
-					algetr<T>(*srcData), algetg<T>(*srcData), algetb<T>(*srcData), 0xFF);
+					algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
 			}
 			dst_ptr += dst_pitch;
 		}


Commit: 67b9b8f6d3c190091ee0ff1fd79be8ff26f830a0
    https://github.com/scummvm/scummvm/commit/67b9b8f6d3c190091ee0ff1fd79be8ff26f830a0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed save ver constants for Characters and RoomStates

>From upstream 78713266a4316f59b13fbe0c880fbeb6c4764054

Changed paths:
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame_components.cpp


diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index 904c55d7d79..b117b8eb388 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -37,9 +37,9 @@ using namespace AGS; // FIXME later
 
 enum CharacterSvgVersion {
 	kCharSvgVersion_Initial = 0,
-	// 1 was skipped somehow
-	kCharSvgVersion_36025 = 2, // animation volume
-	kCharSvgVersion_36109 = 3, // removed movelists, save externally
+	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
+	kCharSvgVersion_36025   = 2, // animation volume
+	kCharSvgVersion_36109   = 3, // removed movelists, save externally
 };
 
 // The CharacterInfo struct size is fixed because it's exposed to script
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index f0a763ebf22..37c66a34f93 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -51,8 +51,8 @@ struct HotspotState {
 // Savegame data format for RoomStatus
 enum RoomStatSvgVersion {
 	kRoomStatSvgVersion_Initial = 0,
-	kRoomStatSvgVersion_36016   = 1, // hotspot and object names
-	// 2 was practically unused
+	kRoomStatSvgVersion_350     = 1, // new movelist format (along with pathfinder)
+	kRoomStatSvgVersion_36016   = 2, // hotspot and object names
 	kRoomStatSvgVersion_36025   = 3, // object animation volume
 	kRoomStatSvgVersion_36041   = 4, // room state's contentFormat
 	kRoomStatSvgVersion_36109   = 5, // removed movelists, save externally
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 9a14769f82e..4b1fb4aa176 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -500,6 +500,7 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
+	const int mls_cmp_ver = cmp_ver > kCharSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
 		_GP(game).chars[i].ReadFromFile(in, kGameVersion_Undefined, cmp_ver);
 		_GP(charextra)[i].ReadFromSavegame(in, cmp_ver);
@@ -507,8 +508,8 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
 		// character movement path (for old saves)
-		if (cmp_ver < 3) {
-			err = _GP(mls)[CHMLSOFFS + i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0);
+		if (cmp_ver < kCharSvgVersion_36109) {
+			err = _GP(mls)[CHMLSOFFS + i].ReadFromFile(in, mls_cmp_ver);
 			if (!err)
 				return err;
 		}
@@ -986,7 +987,7 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		int objmls_count = in->ReadInt32();
 		if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists"))
 			return err;
-		const int mls_cmp_ver = cmp_ver > 0 ? 1 : 0;
+		const int mls_cmp_ver = cmp_ver > kRoomStatSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 		for (int i = 0; i < objmls_count; ++i) {
 			err = _GP(mls)[i].ReadFromFile(in, mls_cmp_ver);
 			if (!err)


Commit: ed76e769c2202bda8a673ad4ceb650bc00b4fa68
    https://github.com/scummvm/scummvm/commit/ed76e769c2202bda8a673ad4ceb650bc00b4fa68
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.10)

Partially From upstream c20a5c533fd36d8f27010e81cd5cece46fcf1cef

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 398cbe88f7e..b5a5e92e8fc 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.9"
+#define ACI_VERSION_STR      "3.6.1.10"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.9
+#define ACI_VERSION_MSRC_DEF  3.6.1.10
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 63b36e594a4e062759eab87f24906af06d04e801
    https://github.com/scummvm/scummvm/commit/63b36e594a4e062759eab87f24906af06d04e801
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in BitmapToVideoMemOpaque() cut out "HasAlpha" branch

Handling alpha channel is meaningless in BitmapToVideoMemOpaque(),
because the whole point of this function is to produce fully opaque texture.
Alpha + opaque combination had never been a case in practice.

>From upstream ead7d5974bce25d507134ec98e8c055039d00e41

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 1ef685cedfd..e32ee9f8da0 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -399,9 +399,7 @@ template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red,
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
 template<typename T, bool HasAlpha, bool UsingLinearFiltering>
-void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(
-	const Bitmap *bitmap, const TextureTile *tile,
-	uint8_t *dst_ptr, const int dst_pitch) {
+void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
 	// tell the compiler these won't change mid loop execution
 	const int t_width = tile->width;
 	const int t_height = tile->height;
@@ -499,60 +497,38 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 	}
 }
 
-template<typename T, bool HasAlpha>
-void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(
-	const Bitmap *bitmap, const TextureTile *tile,
-	uint8_t *dst_ptr, const int dst_pitch) {
+template<typename T>
+void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
 	const int t_width = tile->width;
 	const int t_height = tile->height;
 	const int t_x = tile->x;
 	const int t_y = tile->y;
-	if (HasAlpha) {
-		for (int y = 0; y < t_height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
-			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-			for (int x = 0; x < t_width; x++) {
-				auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
-				const T src_color = srcData[0];
-				memPtrLong[x] = VMEMCOLOR_RGBA(
-					algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), algeta<T>(src_color));
-			}
-			dst_ptr += dst_pitch;
-		}
-	} else {
-		for (int y = 0; y < t_height; y++) {
-			const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
-			unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-			for (int x = 0; x < t_width; x++) {
-				auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
-				const T src_color = srcData[0];
-				memPtrLong[x] = VMEMCOLOR_RGBA(
-					algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
-			}
-			dst_ptr += dst_pitch;
+	for (int y = 0; y < t_height; y++) {
+		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
+		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
+
+		for (int x = 0; x < t_width; x++) {
+			auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
+			const T src_color = srcData[0];
+			memPtrLong[x] = VMEMCOLOR_RGBA(
+				algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
 		}
+		dst_ptr += dst_pitch;
 	}
 }
 
-void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
-													   uint8_t *dst_ptr, const int dst_pitch) {
+void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
 	const int src_depth = bitmap->GetColorDepth();
 
 	switch (src_depth) {
 	case 8:
-		BitmapToVideoMemOpaqueImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch);
+		BitmapToVideoMemOpaqueImpl<uint8_t>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
 	case 16:
-		BitmapToVideoMemOpaqueImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch);
+		BitmapToVideoMemOpaqueImpl<uint16_t>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
 	case 32:
-		if (has_alpha) {
-			BitmapToVideoMemOpaqueImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch);
-		} else {
-			BitmapToVideoMemOpaqueImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch);
-		}
+		BitmapToVideoMemOpaqueImpl<uint32_t>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
 	default:
 		break;
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 70281c0d587..7458a94afbe 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -307,7 +307,7 @@ protected:
 	void BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
 						  uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering);
 	// Same but optimized for opaque source bitmaps which ignore transparent "mask color"
-	void BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
+	void BitmapToVideoMemOpaque(const Bitmap *bitmap, const TextureTile *tile,
 								uint8_t *dst_ptr, const int dst_pitch);
 
 	// Stage virtual screen is used to let plugins draw custom graphics
@@ -357,7 +357,7 @@ private:
 		const Bitmap *bitmap, const TextureTile *tile,
 		uint8_t *dst_ptr, const int dst_pitch);
 
-	template<typename T, bool HasAlpha>
+	template<typename T>
 	void
 	BitmapToVideoMemOpaqueImpl(
 		const Bitmap *bitmap, const TextureTile *tile,


Commit: 4e41e607813b81b0c12f2d1fd8b16ac4cfc0a065
    https://github.com/scummvm/scummvm/commit/4e41e607813b81b0c12f2d1fd8b16ac4cfc0a065
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store ScriptString in memory similarly to dynamic array

This potentially allows to access any metadata while passing string pointer around the script functions.

As a consequence: never wrap external buffer, always allocate your own.
>From upstream 043758b1bb5116f038c5c597d17fe9524b92b3c2

Changed paths:
    engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
    engines/ags/engine/ac/dynobj/cc_script_object.h
    engines/ags/engine/ac/dynobj/cc_serializer.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.cpp
    engines/ags/engine/ac/dynobj/dynobj_manager.h
    engines/ags/engine/ac/dynobj/script_string.cpp
    engines/ags/engine/ac/dynobj/script_string.h
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/string.h
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
index 00adb7c6d96..4e04609954a 100644
--- a/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_dynamic_array.cpp
@@ -21,7 +21,7 @@
 
 #include "ags/engine/ac/dynobj/cc_dynamic_array.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
-#include "ags/shared/util/memory_stream.h"
+#include "ags/engine/ac/dynobj/script_string.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
@@ -62,7 +62,7 @@ size_t CCDynamicArray::CalcSerializeSize(const void *address) {
 	return hdr.TotalSize + FileHeaderSz;
 }
 
-void CCDynamicArray::Serialize(const void *address, AGS::Shared::Stream *out) {
+void CCDynamicArray::Serialize(const void *address, Stream *out) {
 	const Header &hdr = GetHeader(address);
 	out->WriteInt32(hdr.ElemCount);
 	out->WriteInt32(hdr.TotalSize);
@@ -101,7 +101,7 @@ DynObjectRef DynamicArrayHelpers::CreateStringArray(const std::vector<const char
 	// Create script strings and put handles into array
 	int32_t *slots = static_cast<int32_t *>(arr.Obj);
 	for (auto s : items) {
-		DynObjectRef str = _G(stringClassImpl)->CreateString(s);
+		DynObjectRef str = ScriptString::Create(s);
 		// We must add reference count, because the string is going to be saved
 		// within another object (array), not returned to script directly
 		ccAddObjectReference(str.Handle);
diff --git a/engines/ags/engine/ac/dynobj/cc_script_object.h b/engines/ags/engine/ac/dynobj/cc_script_object.h
index 40b6c3b43ea..dea9b4dc83f 100644
--- a/engines/ags/engine/ac/dynobj/cc_script_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_script_object.h
@@ -119,12 +119,6 @@ struct ICCObjectReader {
 	virtual void Unserialize(int32_t handle, const char *serializedData, int dataSize) = 0;
 };
 
-// The interface of a dynamic String allocator.
-struct ICCStringClass {
-	virtual ~ICCStringClass() {}
-	virtual DynObjectRef CreateString(const char *fromText) = 0;
-};
-
 } // namespace AGS3
 
 #endif
diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index ba797e21ee6..0eb23791fb6 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -82,8 +82,7 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 	} else if (strcmp(objectType, "Object") == 0) {
 		_GP(ccDynamicObject).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "String") == 0) {
-		ScriptString *scf = new ScriptString();
-		scf->Unserialize(index, &mems, data_sz);
+		_GP(myScriptStringImpl).Unserialize(index, &mems, data_sz);
 	} else if (strcmp(objectType, "File") == 0) {
 		// files cannot be restored properly -- so just recreate
 		// the object; attempting any operations on it will fail
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
index 40a19203e16..368dcbd245c 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.cpp
@@ -46,11 +46,6 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-// set the class that will be used for dynamic strings
-void ccSetStringClassImpl(ICCStringClass *theClass) {
-	_G(stringClassImpl) = theClass;
-}
-
 // register a memory handle for the object and allow script
 // pointers to point to it
 int32_t ccRegisterManagedObject(void *object, IScriptObject *callback, ScriptValueType obj_type) {
diff --git a/engines/ags/engine/ac/dynobj/dynobj_manager.h b/engines/ags/engine/ac/dynobj/dynobj_manager.h
index ccc192d3dfe..d283dfded0f 100644
--- a/engines/ags/engine/ac/dynobj/dynobj_manager.h
+++ b/engines/ags/engine/ac/dynobj/dynobj_manager.h
@@ -45,8 +45,6 @@ class Stream;
 
 using namespace AGS; // FIXME later
 
-// set the class that will be used for dynamic strings
-extern void  ccSetStringClassImpl(ICCStringClass *theClass);
 // register a memory handle for the object and allow script
 // pointers to point to it
 extern int32_t ccRegisterManagedObject(void *object, IScriptObject *, ScriptValueType obj_type = kScValScriptObject);
diff --git a/engines/ags/engine/ac/dynobj/script_string.cpp b/engines/ags/engine/ac/dynobj/script_string.cpp
index a74bf627903..800b93fea79 100644
--- a/engines/ags/engine/ac/dynobj/script_string.cpp
+++ b/engines/ags/engine/ac/dynobj/script_string.cpp
@@ -23,61 +23,57 @@
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
 #include "ags/engine/ac/string.h"
 #include "ags/shared/util/stream.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
 using namespace AGS::Shared;
 
-DynObjectRef ScriptString::CreateString(const char *fromText) {
-	return CreateNewScriptStringObj(fromText);
-}
-
-int ScriptString::Dispose(void * /*address*/, bool force) {
-	// always dispose
-	if (_text) {
-		free(_text);
-		_text = nullptr;
-	}
-	delete this;
-	return 1;
-}
-
 const char *ScriptString::GetType() {
 	return "String";
 }
 
-size_t ScriptString::CalcSerializeSize(const void * /*address*/) {
-	return _len + 1 + sizeof(int32_t);
+int ScriptString::Dispose(void *address, bool /*force*/) {
+	delete[] (static_cast<uint8_t *>(address) - MemHeaderSz);
+	return 1;
 }
 
-void ScriptString::Serialize(const void * /*address*/, Stream *out) {
-	const auto *cstr = _text ? _text : "";
-	out->WriteInt32(_len);
-	out->Write(cstr, _len + 1);
+size_t ScriptString::CalcSerializeSize(const void *address) {
+	const Header &hdr = GetHeader(address);
+	return hdr.Length + 1 + FileHeaderSz;
 }
 
-void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) {
-	_len = in->ReadInt32();
-	_text = (char *)malloc(_len + 1);
-	in->Read(_text, _len + 1);
-	_text[_len] = 0; // for safety
-	ccRegisterUnserializedObject(index, _text, this);
+void ScriptString::Serialize(const void *address, Stream *out) {
+	const Header &hdr = GetHeader(address);
+	out->WriteInt32(hdr.Length);
+	out->Write(address, hdr.Length + 1); // it was writing trailing 0 for some reason
 }
 
-ScriptString::ScriptString(const char *text) {
-	_len = strlen(text);
-	_text = (char *)malloc(_len + 1);
-	memcpy(_text, text, _len + 1);
+void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) {
+	size_t len = in->ReadInt32();
+	uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz];
+	Header &hdr = reinterpret_cast<Header &>(*buf);
+	hdr.Length = len;
+	char *text_ptr = reinterpret_cast<char *>(buf + MemHeaderSz);
+	in->Read(text_ptr, len + 1); // it was writing trailing 0 for some reason
+	text_ptr[len] = 0;           // for safety
+	ccRegisterUnserializedObject(index, text_ptr, this);
 }
 
-ScriptString::ScriptString(char *text, bool take_ownership) {
-	_len = strlen(text);
-	if (take_ownership) {
-		_text = text;
-	} else {
-		_text = (char *)malloc(_len + 1);
-		memcpy(_text, text, _len + 1);
+DynObjectRef ScriptString::CreateImpl(const char *text, size_t buf_len) {
+	size_t len = text ? strlen(text) : buf_len;
+	uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz];
+	Header &hdr = reinterpret_cast<Header &>(*buf);
+	hdr.Length = len;
+	char *text_ptr = reinterpret_cast<char *>(buf + MemHeaderSz);
+	if (text)
+		memcpy(text_ptr, text, len + 1);
+	int32_t handle = ccRegisterManagedObject(text_ptr, &_GP(myScriptStringImpl));
+	if (handle == 0) {
+		delete[] buf;
+		return DynObjectRef();
 	}
+	return DynObjectRef(handle, text_ptr, &_GP(myScriptStringImpl));
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/dynobj/script_string.h b/engines/ags/engine/ac/dynobj/script_string.h
index 548b5d00f9e..e9b92487e53 100644
--- a/engines/ags/engine/ac/dynobj/script_string.h
+++ b/engines/ags/engine/ac/dynobj/script_string.h
@@ -26,31 +26,41 @@
 
 namespace AGS3 {
 
-struct ScriptString final : AGSCCDynamicObject, ICCStringClass {
-	int Dispose(void *address, bool force) override;
+struct ScriptString final : AGSCCDynamicObject {
+public:
+	struct Header {
+		uint32_t Length = 0u;
+	};
+
+	ScriptString() = default;
+	~ScriptString() = default;
+
+	inline static const Header &GetHeader(const void *address) {
+		return reinterpret_cast<const Header &>(*(static_cast<const uint8_t *>(address) - MemHeaderSz));
+	}
+
+	// Create a new script string by copying the given text
+	static DynObjectRef Create(const char *text) { return CreateImpl(text, -1); }
+	// Create a new script string with a buffer of at least the given text length
+	static DynObjectRef Create(size_t buf_len) { return CreateImpl(nullptr, buf_len); }
+
 	const char *GetType() override;
+	int Dispose(void *address, bool force) override;
 	void Unserialize(int index, AGS::Shared::Stream *in, size_t data_sz) override;
 
-	DynObjectRef CreateString(const char *fromText) override;
+private:
+	// The size of the array's header in memory, prepended to the element data
+	static const size_t MemHeaderSz = sizeof(Header);
+	// The size of the serialized header
+	static const size_t FileHeaderSz = sizeof(uint32_t);
 
-	ScriptString() = default;
-	ScriptString(const char *text);
-	ScriptString(char *text, bool take_ownership);
-	char *GetTextPtr() const {
-		return _text;
-	}
+	static DynObjectRef CreateImpl(const char *text, size_t buf_len);
 
-protected:
+	// Savegame serialization
 	// Calculate and return required space for serialization, in bytes
 	size_t CalcSerializeSize(const void *address) override;
 	// Write object data into the provided stream
 	void Serialize(const void *address, AGS::Shared::Stream *out) override;
-
-private:
-	// TODO: the preallocated text buffer may be assigned externally;
-	// find out if it's possible to refactor while keeping same functionality
-	char *_text = nullptr;
-	size_t _len = 0;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 9b7c19dc1da..3a5e91885e9 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -189,10 +189,9 @@ const char *File_ReadStringBack(sc_File *fil) {
 		return CreateNewScriptString("");
 	}
 
-	char *retVal = (char *)malloc(lle);
-	in->Read(retVal, lle);
-
-	return CreateNewScriptString(retVal, false);
+	char *buffer = CreateNewScriptString(lle);
+	in->Read(buffer, lle);
+	return buffer;
 }
 
 int File_ReadInt(sc_File *fil) {
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 326c877d43a..432c12963f3 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -42,6 +42,14 @@
 
 namespace AGS3 {
 
+const char *CreateNewScriptString(const char *text) {
+	return (const char *)ScriptString::Create(text).Obj;
+}
+
+char *CreateNewScriptString(size_t buf_length) {
+	return (char *)ScriptString::Create(buf_length).Obj;
+}
+
 int String_IsNullOrEmpty(const char *thisString) {
 	if ((thisString == nullptr) || (thisString[0] == 0))
 		return 1;
@@ -54,20 +62,20 @@ const char *String_Copy(const char *srcString) {
 }
 
 const char *String_Append(const char *thisString, const char *extrabit) {
-	size_t ln = strlen(thisString) + strlen(extrabit) + 1;
-	char *buffer = (char *)malloc(ln);
-	Common::strcpy_s(buffer, ln, thisString);
-	Common::strcat_s(buffer, ln, extrabit);
-	return CreateNewScriptString(buffer, false);
+	size_t new_len = strlen(thisString) + strlen(extrabit);
+	char *buffer = CreateNewScriptString(new_len);
+	Common::strcpy_s(buffer, new_len, thisString);
+	Common::strcat_s(buffer, new_len, extrabit);
+	return buffer;
 }
 
 const char *String_AppendChar(const char *thisString, int extraOne) {
 	char chr[5]{};
 	size_t chw = usetc(chr, extraOne);
-	size_t ln = strlen(thisString) + chw + 1;
-	char *buffer = (char *)malloc(ln);
-	Common::sprintf_s(buffer, ln, "%s%s", thisString, chr);
-	return CreateNewScriptString(buffer, false);
+    size_t new_len = strlen(thisString) + chw;
+    char *buffer = CreateNewScriptString(new_len);
+	Common::sprintf_s(buffer, new_len, "%s%s", thisString, chr);
+	return buffer;
 }
 
 const char *String_ReplaceCharAt(const char *thisString, int index, int newChar) {
@@ -81,12 +89,12 @@ const char *String_ReplaceCharAt(const char *thisString, int index, int newChar)
 	size_t old_sz = ucwidth(uchar);
 	char new_chr[5]{};
 	size_t new_chw = usetc(new_chr, newChar);
-	size_t total_sz = off + remain_sz + new_chw - old_sz + 1;
-	char *buffer = (char *)malloc(total_sz);
+	size_t new_len = off + remain_sz + new_chw - old_sz;
+	char *buffer = CreateNewScriptString(new_len);
 	memcpy(buffer, thisString, off);
 	memcpy(buffer + off, new_chr, new_chw);
 	memcpy(buffer + off + new_chw, thisString + off + old_sz, remain_sz - old_sz + 1);
-	return CreateNewScriptString(buffer, false);
+	return buffer;
 }
 
 const char *String_Truncate(const char *thisString, int length) {
@@ -97,10 +105,10 @@ const char *String_Truncate(const char *thisString, int length) {
 		return thisString;
 
 	size_t sz = uoffset(thisString, length);
-	char *buffer = (char *)malloc(sz + 1);
+	char *buffer = CreateNewScriptString(sz);
 	memcpy(buffer, thisString, sz);
 	buffer[sz] = 0;
-	return CreateNewScriptString(buffer, false);
+	return buffer;
 }
 
 const char *String_Substring(const char *thisString, int index, int length) {
@@ -114,10 +122,10 @@ const char *String_Substring(const char *thisString, int index, int length) {
 	size_t end = uoffset(thisString + start, sublen) + start;
 	size_t copysz = end - start;
 
-	char *buffer = (char *)malloc(copysz + 1);
+	char *buffer = CreateNewScriptString(copysz);
 	memcpy(buffer, thisString + start, copysz);
 	buffer[copysz] = 0;
-	return CreateNewScriptString(buffer, false);
+	return buffer;
 }
 
 int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) {
@@ -185,19 +193,23 @@ const char *String_Replace(const char *thisString, const char *lookForText, cons
 	}
 
 	resultBuffer[outputSize] = 0; // terminate
-	return CreateNewScriptString(resultBuffer, true);
+	return CreateNewScriptString(resultBuffer);
 }
 
 const char *String_LowerCase(const char *thisString) {
-	char *buffer = ags_strdup(thisString);
+	size_t len = strlen(thisString);
+	char *buffer = CreateNewScriptString(len);
+	memcpy(buffer, thisString, len);
 	ustrlwr(buffer);
-	return CreateNewScriptString(buffer, false);
+	return buffer;
 }
 
 const char *String_UpperCase(const char *thisString) {
-	char *buffer = ags_strdup(thisString);
+	size_t len = strlen(thisString);
+	char *buffer = CreateNewScriptString(len);
+	memcpy(buffer, thisString, len);
 	ustrupr(buffer);
-	return CreateNewScriptString(buffer, false);
+	return buffer;
 }
 
 int String_GetChars(const char *texx, int index) {
@@ -245,34 +257,6 @@ int StrContains(const char *s1, const char *s2) {
 
 //=============================================================================
 
-const char *CreateNewScriptString(const String &fromText) {
-	return (const char *)CreateNewScriptStringObj(fromText.GetCStr(), true).Obj;
-}
-
-const char *CreateNewScriptString(const char *fromText, bool reAllocate) {
-	return (const char *)CreateNewScriptStringObj(fromText, reAllocate).Obj;
-}
-
-DynObjectRef CreateNewScriptStringObj(const String &fromText) {
-	return CreateNewScriptStringObj(fromText.GetCStr(), true);
-}
-
-DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) {
-	ScriptString *str;
-	if (reAllocate) {
-		str = new ScriptString(fromText);
-	} else { // TODO: refactor to avoid const casts!
-		str = new ScriptString(const_cast<char *>(fromText), true);
-	}
-	void *obj_ptr = str->GetTextPtr();
-	int32_t handle = ccRegisterManagedObject(obj_ptr, str);
-	if (handle == 0) {
-		delete str;
-		return DynObjectRef();
-	}
-	return DynObjectRef(handle, obj_ptr, str);
-}
-
 size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
 	if (fonnt == -1)
 		fonnt = _GP(play).normal_font;
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index 95025169023..79b236a7bd0 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -24,12 +24,17 @@
 
 //include <stdarg.h>
 #include "ags/engine/ac/dynobj/cc_script_object.h"
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
 // Check that a supplied buffer from a text script function was not null
 #define VALIDATE_STRING(strin) if (!strin) quit("!String argument was null: make sure you pass a string buffer")
 
+const char *CreateNewScriptString(const char *text);
+inline const char *CreateNewScriptString(const AGS::Shared::String &text) { return CreateNewScriptString(text.GetCStr()); }
+char *CreateNewScriptString(size_t buf_len); // FIXME, unsafe to expose raw buf like this
+
 int String_IsNullOrEmpty(const char *thisString);
 const char *String_Copy(const char *srcString);
 const char *String_Append(const char *thisString, const char *extrabit);
@@ -49,8 +54,6 @@ int StrContains(const char *s1, const char *s2);
 
 //=============================================================================
 
-const char *CreateNewScriptString(const char *fromText, bool reAllocate = true);
-DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate = true);
 class SplitLines;
 // Break up the text into lines restricted by the given width;
 // returns number of lines, or 0 if text cannot be split well to fit in this width.
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 309b1d13da6..8d3b2696099 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -487,7 +487,6 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 	// require access to script API at initialization time.
 	//
 	ccSetScriptAliveTimer(1000 / 60u, 1000u, 150000u);
-	ccSetStringClassImpl(&_GP(myScriptStringImpl));
 	setup_script_exports(base_api, compat_api);
 
 	//
diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 57548184739..44220c936b7 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1425,17 +1425,9 @@ int ccInstance::Run(int32_t curpc) {
 		}
 		case SCMD_CREATESTRING: {
 			auto &reg1 = registers[codeOp.Arg1i()];
-			// FIXME: provide a dummy impl to avoid this?
-			// why arrays can be created using global mgr and strings not?
-			if (_G(stringClassImpl) == nullptr) {
-				cc_error("No string class implementation set, but opcode was used");
-				return -1;
-			} else {
-				const char *ptr = reinterpret_cast<const char *>(reg1.GetDirectPtr());
-				reg1.SetScriptObject(
-					_G(stringClassImpl)->CreateString(ptr).Obj,
-					&_GP(myScriptStringImpl));
-			}
+			const char *ptr = reinterpret_cast<const char *>(reg1.GetDirectPtr());
+			DynObjectRef ref = ScriptString::Create(ptr);
+			reg1.SetScriptObject(ref.Obj, &_GP(myScriptStringImpl));
 			break;
 		}
 		case SCMD_STRINGSEQUAL: {
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 19870777f0d..630d965f0ae 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -233,7 +233,6 @@ Globals::Globals() {
 	_ccDynamicDialog = new CCDialog();
 	_ccDynamicAudioClip = new CCAudioClip();
 	_ccDynamicAudio = new CCAudioChannel();
-	_myScriptStringImpl = new ScriptString();
 	_guis = new std::vector<AGS::Shared::GUIMain>();
 	_play = new GameState();
 	_game = new GameSetupStruct();
@@ -386,6 +385,9 @@ Globals::Globals() {
 	Common::fill(_loadedInstances, _loadedInstances + MAX_LOADED_INSTANCES,
 	             (ccInstance *)nullptr);
 
+	// script_string.cpp globals
+	_myScriptStringImpl = new ScriptString();
+
 	// system_imports.cpp globals
 	_simp = new SystemImports();
 	_simp_for_plugin = new SystemImports();
@@ -510,7 +512,6 @@ Globals::~Globals() {
 	delete _ccDynamicDialog;
 	delete _ccDynamicAudioClip;
 	delete _ccDynamicAudio;
-	delete _myScriptStringImpl;
 	delete _guis;
 	delete _game;
 	delete _play;
@@ -636,6 +637,9 @@ Globals::~Globals() {
 	delete _moduleInstFork;
 	delete _moduleRepExecAddr;
 
+	// script_string.cpp globals
+	delete _myScriptStringImpl;
+
 	// system_imports.cpp globals
 	delete _simp;
 	delete _simp_for_plugin;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 9b8ca1f6e4a..b2307428133 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -747,7 +747,6 @@ public:
 	CCDialog *_ccDynamicDialog;
 	CCAudioClip *_ccDynamicAudioClip;
 	CCAudioChannel *_ccDynamicAudio;
-	ScriptString *_myScriptStringImpl;
 	ScriptObject *_scrObj;
 	std::vector<ScriptGUI> *_scrGui;
 	ScriptHotspot *_scrHotspot;
@@ -1288,7 +1287,7 @@ public:
 	/**@}*/
 
 	/**
-	 * @defgroup agsscript_runtimeglobals script_runtime globals
+	 * @defgroup agsscript_runtimeglobals agscript_string script_runtime globals
 	 * @ingroup agsglobals
 	 * @{
 	 */
@@ -1304,6 +1303,7 @@ public:
 	// after which the interpreter will abort
 	unsigned _maxWhileLoops = 0u;
 	ccInstance *_loadedInstances[MAX_LOADED_INSTANCES];
+	ScriptString *_myScriptStringImpl;
 
 	/**@}*/
 


Commit: f641445c7060f22a4ba468aac2c5d58697aace80
    https://github.com/scummvm/scummvm/commit/f641445c7060f22a4ba468aac2c5d58697aace80
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
Common: add AGS_PLATFORM_WINDOWS_MINGW

>From upstream e4440d804984313d858e10c68a76e07caaeae11a

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


diff --git a/engines/ags/shared/core/platform.h b/engines/ags/shared/core/platform.h
index 0a653096ba1..2f14e94ef03 100644
--- a/engines/ags/shared/core/platform.h
+++ b/engines/ags/shared/core/platform.h
@@ -104,6 +104,11 @@ namespace AGS3 {
 #error "Unknown platform"
 #endif
 
+#if 0
+#define AGS_PLATFORM_WINDOWS_MINGW (1)
+#else
+#define AGS_PLATFORM_WINDOWS_MINGW (0)
+#endif
 
 #if defined(__LP64__)
 // LP64 machine, macOS or Linux


Commit: db73bb6b68e2085b1c9aed5fa3eb7d0b05d818b0
    https://github.com/scummvm/scummvm/commit/db73bb6b68e2085b1c9aed5fa3eb7d0b05d818b0
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Moved "antialias" config option to "graphics" section

>From upstream 32958c6cb5e247c54de6c9d581beae7b2b31675b

Changed paths:
    engines/ags/engine/main/config.cpp


diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp
index efc4a8b883f..66e86034a77 100644
--- a/engines/ags/engine/main/config.cpp
+++ b/engines/ags/engine/main/config.cpp
@@ -261,6 +261,7 @@ static void read_legacy_graphics_config(const ConfigTree &cfg) {
 	}
 
 	_GP(usetup).Screen.Params.RefreshRate = CfgReadInt(cfg, "misc", "refresh");
+	_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "misc", "antialias");
 }
 
 static void read_legacy_config(const ConfigTree &cfg) {
@@ -308,6 +309,7 @@ void apply_config(const ConfigTree &cfg) {
 			_GP(usetup).Screen.Params.VSync = CfgReadBoolInt(cfg, "graphics", "vsync");
 
 		_GP(usetup).RenderAtScreenRes = CfgReadBoolInt(cfg, "graphics", "render_at_screenres");
+		_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "graphics", "antialias");
 		_GP(usetup).Supersampling = CfgReadInt(cfg, "graphics", "supersampling", 1);
 		_GP(usetup).software_render_driver = CfgReadString(cfg, "graphics", "software_driver");
 
@@ -318,7 +320,6 @@ void apply_config(const ConfigTree &cfg) {
 			rotation_str, CstrArr<kNumScreenRotationOptions>{ "unlocked", "portrait", "landscape" },
 			_GP(usetup).rotation);
 #endif
-		_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "misc", "antialias");
 
 		// Custom paths
 		_GP(usetup).load_latest_save = CfgReadBoolInt(cfg, "misc", "load_latest_save", _GP(usetup).load_latest_save);


Commit: 47c095545bb65f111c966151fbdcf695245d9423
    https://github.com/scummvm/scummvm/commit/47c095545bb65f111c966151fbdcf695245d9423
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: don't use AlignedStream, hardcode padding in read/write funcs

AlignedStream was used initially in attempt to automate reading legacy formats,
where structs were dumped from memory into filestream directly,
resulting in alignment padding inserted into the file format.

While using AlignedStream made working with old formats easier, this approach also has certain problems:
1. This makes extra padding in file formats implicit and non-obvious.
2. Object Read/Write functions will depend on the type of the passed stream.
3. Adding anything into Read/Write functions will also be affected by AlignedStream,
and in theory may lead to additional unnecessary padding inserted simply by keeping same serialization logic.
4. AlignedStream does not support Seek, and in general you cannot trivially skip parts of struct by skipping
or reading N bytes, you'll have to read it out step by step to keep alignment calculation working.

These concerns made me make the decision to remove AlignedStream, and restore hardcoded padding instead.
It's explicit and honest.
>From upstream ffc59e82029bed4da9dd595dac015374681fd2e4

Changed paths:
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/dynobj/script_audio_clip.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/inventory_item_info.cpp
    engines/ags/shared/ac/mouse_cursor.cpp
    engines/ags/shared/ac/view.cpp
    engines/ags/shared/ac/view.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 99a136b4577..809912091b1 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -77,7 +77,9 @@ void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver, int save_
 	StrUtil::ReadCStrCount(name, in, MAX_CHAR_NAME_LEN);
 	StrUtil::ReadCStrCount(scrname, in, MAX_SCRIPT_NAME_LEN);
 	on = in->ReadInt8();
+	in->ReadInt8(); // alignment padding to int32
 
+	// Upgrade data
 	if ((data_ver > kGameVersion_Undefined && data_ver < kGameVersion_360_16) ||
 		((data_ver == kGameVersion_Undefined) && save_ver >= 0 && save_ver < 2)) {
 		idle_anim_speed = animspeed + 5;
@@ -131,6 +133,7 @@ void CharacterInfo::WriteToFile(Stream *out) {
 	out->Write(name, 40);
 	out->Write(scrname, MAX_SCRIPT_NAME_LEN);
 	out->WriteInt8(on);
+	out->WriteInt8(0); // alignment padding to int32
 }
 
 #if defined (OBSOLETE)
diff --git a/engines/ags/shared/ac/dynobj/script_audio_clip.cpp b/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
index 1428dd0b05d..d89aab6edf3 100644
--- a/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
+++ b/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
@@ -34,8 +34,10 @@ void ScriptAudioClip::ReadFromFile(Stream *in) {
 	type = static_cast<uint8_t>(in->ReadInt8());
 	fileType = static_cast<AudioFileType>(in->ReadInt8());
 	defaultRepeat = in->ReadInt8();
+	in->ReadInt8(); // alignment padding to int16
 	defaultPriority = in->ReadInt16();
 	defaultVolume = in->ReadInt16();
+	in->ReadInt16(); // alignment padding to int32
 	in->ReadInt32(); // reserved
 }
 
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 6fcd0e952ff..3aa9f31eda6 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -25,7 +25,6 @@
 #include "ags/shared/ac/words_dictionary.h"
 #include "ags/shared/ac/dynobj/script_audio_clip.h"
 #include "ags/shared/game/interactions.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/string_utils.h"
 #include "ags/globals.h"
 
@@ -143,25 +142,21 @@ void GameSetupStruct::read_font_infos(Shared::Stream *in, GameDataVersion data_v
 	}
 }
 
-void GameSetupStruct::ReadInvInfo_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void GameSetupStruct::ReadInvInfo(Stream *in) {
 	for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) {
-		invinfo[iteratorCount].ReadFromFile(&align_s);
-		align_s.Reset();
+		invinfo[iteratorCount].ReadFromFile(in);
 	}
 }
 
-void GameSetupStruct::WriteInvInfo_Aligned(Stream *out) {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void GameSetupStruct::WriteInvInfo(Stream *out) {
 	for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) {
-		invinfo[iteratorCount].WriteToFile(&align_s);
-		align_s.Reset();
+		invinfo[iteratorCount].WriteToFile(out);
 	}
 }
 
 HGameFileError GameSetupStruct::read_cursors(Shared::Stream *in) {
 	mcurs.resize(numcursors);
-	ReadMouseCursors_Aligned(in);
+	ReadMouseCursors(in);
 	return HGameFileError::None();
 }
 
@@ -194,19 +189,15 @@ void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
 	read_dictionary(dict.get(), in);
 }
 
-void GameSetupStruct::ReadMouseCursors_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void GameSetupStruct::ReadMouseCursors(Stream *in) {
 	for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) {
-		mcurs[iteratorCount].ReadFromFile(&align_s);
-		align_s.Reset();
+		mcurs[iteratorCount].ReadFromFile(in);
 	}
 }
 
-void GameSetupStruct::WriteMouseCursors_Aligned(Stream *out) {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void GameSetupStruct::WriteMouseCursors(Stream *out) {
 	for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) {
-		mcurs[iteratorCount].WriteToFile(&align_s);
-		align_s.Reset();
+		mcurs[iteratorCount].WriteToFile(out);
 	}
 }
 
@@ -216,7 +207,7 @@ void GameSetupStruct::WriteMouseCursors_Aligned(Stream *out) {
 void GameSetupStruct::read_characters(Shared::Stream *in) {
 	chars.resize(numcharacters);
 
-	ReadCharacters_Aligned(in, false);
+	ReadCharacters(in, false);
 }
 
 void GameSetupStruct::read_lipsync(Shared::Stream *in, GameDataVersion data_ver) {
@@ -246,21 +237,17 @@ void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int> &l
 	}
 }
 
-void GameSetupStruct::ReadCharacters_Aligned(Stream *in, bool is_save) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void GameSetupStruct::ReadCharacters(Stream *in, bool is_save) {
 	const GameDataVersion data_ver = is_save ? kGameVersion_Undefined : _G(loaded_game_file_version);
 	const int save_ver = is_save ? 0 : -1;
 	for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) {
-		chars[iteratorCount].ReadFromFile(&align_s, data_ver, save_ver);
-		align_s.Reset();
+		chars[iteratorCount].ReadFromFile(in, data_ver, save_ver);
 	}
 }
 
-void GameSetupStruct::WriteCharacters_Aligned(Stream *out) {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void GameSetupStruct::WriteCharacters(Stream *out) {
 	for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) {
-		chars[iteratorCount].WriteToFile(&align_s);
-		align_s.Reset();
+		chars[iteratorCount].WriteToFile(out);
 	}
 }
 
@@ -313,7 +300,7 @@ HGameFileError GameSetupStruct::read_audio(Shared::Stream *in, GameDataVersion d
 
 		size_t audioclip_count = in->ReadInt32();
 		audioClips.resize(audioclip_count);
-		ReadAudioClips_Aligned(in, audioclip_count);
+		ReadAudioClips(in, audioclip_count);
 
 		scoreClipID = in->ReadInt32();
 	}
@@ -338,17 +325,15 @@ void GameSetupStruct::read_room_names(Stream *in, GameDataVersion data_ver) {
 	}
 }
 
-void GameSetupStruct::ReadAudioClips_Aligned(Shared::Stream *in, size_t count) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void GameSetupStruct::ReadAudioClips(Shared::Stream *in, size_t count) {
 	for (size_t i = 0; i < count; ++i) {
-		audioClips[i].ReadFromFile(&align_s);
-		align_s.Reset();
+		audioClips[i].ReadFromFile(in);
 	}
 }
 
 void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
-	ReadInvInfo_Aligned(in);
-	ReadMouseCursors_Aligned(in);
+	ReadInvInfo(in);
+	ReadMouseCursors(in);
 
 	if (_G(loaded_game_file_version) <= kGameVersion_272) {
 		for (int i = 0; i < numinvitems; ++i)
@@ -360,7 +345,7 @@ void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
 	in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1);
 	options[OPT_LIPSYNCTEXT] = in->ReadByte();
 
-	ReadCharacters_Aligned(in, true);
+	ReadCharacters(in, true);
 }
 
 //=============================================================================
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 96ac646ff0e..a5be0123644 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -147,25 +147,25 @@ struct GameSetupStruct : public GameSetupStructBase {
 	void read_interaction_scripts(Shared::Stream *in, GameDataVersion data_ver);
 	void read_words_dictionary(Shared::Stream *in);
 
-	void ReadInvInfo_Aligned(Shared::Stream *in);
-	void WriteInvInfo_Aligned(Shared::Stream *out);
-	void ReadMouseCursors_Aligned(Shared::Stream *in);
-	void WriteMouseCursors_Aligned(Shared::Stream *out);
+	void ReadInvInfo(Shared::Stream *in);
+	void WriteInvInfo(Shared::Stream *out);
+	void ReadMouseCursors(Shared::Stream *in);
+	void WriteMouseCursors(Shared::Stream *out);
 	//------------------------------
 	// Part 2
 	void read_characters(Shared::Stream *in);
 	void read_lipsync(Shared::Stream *in, GameDataVersion data_ver);
 	void read_messages(Shared::Stream *in, const std::array<int> &load_messages, GameDataVersion data_ver);
 
-	void ReadCharacters_Aligned(Shared::Stream *in, bool is_save);
-	void WriteCharacters_Aligned(Shared::Stream *out);
+	void ReadCharacters(Shared::Stream *in, bool is_save);
+	void WriteCharacters(Shared::Stream *out);
 	//------------------------------
 	// Part 3
 	HGameFileError read_customprops(Shared::Stream *in, GameDataVersion data_ver);
 	HGameFileError read_audio(Shared::Stream *in, GameDataVersion data_ver);
 	void read_room_names(Shared::Stream *in, GameDataVersion data_ver);
 
-	void ReadAudioClips_Aligned(Shared::Stream *in, size_t count);
+	void ReadAudioClips(Shared::Stream *in, size_t count);
 	//--------------------------------------------------------------------
 
 	// Functions for reading and writing appropriate data from/to save game
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 4e2b7d0041a..98a6810c64f 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -132,7 +132,11 @@ void GameSetupStructBase::OnResolutionSet() {
 }
 
 void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, SerializeInfo &info) {
+	// NOTE: historically the struct was saved by dumping whole memory
+	// into the file stream, which added padding from memory alignment;
+	// here we mark the padding bytes, as they do not belong to actual data.
 	StrUtil::ReadCStrCount(gamename, in, GAME_NAME_LENGTH);
+	in->ReadInt16(); // alignment padding to int32 (gamename: 50 -> 52 bytes)
 	in->ReadArrayOfInt32(options, MAX_OPTIONS);
 	if (game_ver < kGameVersion_340_4) { // TODO: this should probably be possible to deduce script API level
 		// using game data version and other options like OPT_STRICTSCRIPTING
@@ -147,6 +151,7 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, Ser
 	playercharacter = in->ReadInt32();
 	totalscore = in->ReadInt32();
 	numinvitems = in->ReadInt16();
+	in->ReadInt16(); // alignment padding to int32
 	numdialog = in->ReadInt32();
 	numdlgmessage = in->ReadInt32();
 	numfonts = in->ReadInt32();
@@ -178,7 +183,11 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, Ser
 }
 
 void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) const {
+	// NOTE: historically the struct was saved by dumping whole memory
+	// into the file stream, which added padding from memory alignment;
+	// here we mark the padding bytes, as they do not belong to actual data.
 	out->Write(gamename, GAME_NAME_LENGTH);
+	out->WriteInt16(0); // alignment padding to int32
 	out->WriteArrayOfInt32(options, MAX_OPTIONS);
 	out->Write(&paluses[0], sizeof(paluses));
 	// colors are an array of chars
@@ -188,6 +197,7 @@ void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) co
 	out->WriteInt32(playercharacter);
 	out->WriteInt32(totalscore);
 	out->WriteInt16(numinvitems);
+	out->WriteInt16(0); // alignment padding to int32
 	out->WriteInt32(numdialog);
 	out->WriteInt32(numdlgmessage);
 	out->WriteInt32(numfonts);
diff --git a/engines/ags/shared/ac/inventory_item_info.cpp b/engines/ags/shared/ac/inventory_item_info.cpp
index 5deb19f5c8a..c34961bdad3 100644
--- a/engines/ags/shared/ac/inventory_item_info.cpp
+++ b/engines/ags/shared/ac/inventory_item_info.cpp
@@ -29,22 +29,26 @@ using namespace AGS::Shared;
 
 void InventoryItemInfo::ReadFromFile(Stream *in) {
 	StrUtil::ReadCStrCount(name, in, MAX_INVENTORY_NAME_LENGTH);
+	in->Seek(3); // alignment padding to int32
 	pic = in->ReadInt32();
 	cursorPic = in->ReadInt32();
 	hotx = in->ReadInt32();
 	hoty = in->ReadInt32();
 	in->ReadArrayOfInt32(reserved, 5);
 	flags = in->ReadInt8();
+	in->Seek(3); // alignment padding to int32
 }
 
 void InventoryItemInfo::WriteToFile(Stream *out) {
 	out->Write(name, MAX_INVENTORY_NAME_LENGTH);
+	out->WriteByteCount(0, 3); // alignment padding to int32
 	out->WriteInt32(pic);
 	out->WriteInt32(cursorPic);
 	out->WriteInt32(hotx);
 	out->WriteInt32(hoty);
 	out->WriteArrayOfInt32(reserved, 5);
 	out->WriteInt8(flags);
+	out->WriteByteCount(0, 3); // alignment padding to int32
 }
 
 void InventoryItemInfo::ReadFromSavegame(Stream *in) {
diff --git a/engines/ags/shared/ac/mouse_cursor.cpp b/engines/ags/shared/ac/mouse_cursor.cpp
index 357a60adf35..7d265ba82a4 100644
--- a/engines/ags/shared/ac/mouse_cursor.cpp
+++ b/engines/ags/shared/ac/mouse_cursor.cpp
@@ -43,6 +43,7 @@ void MouseCursor::ReadFromFile(Stream *in) {
 	view = in->ReadInt16();
 	StrUtil::ReadCStrCount(name, in, MAX_CURSOR_NAME_LENGTH);
 	flags = in->ReadInt8();
+	in->Seek(3); // alignment padding to int32
 }
 
 void MouseCursor::WriteToFile(Stream *out) {
@@ -52,6 +53,7 @@ void MouseCursor::WriteToFile(Stream *out) {
 	out->WriteInt16(view);
 	out->Write(name, MAX_CURSOR_NAME_LENGTH);
 	out->WriteInt8(flags);
+	out->WriteByteCount(0, 3); // alignment padding to int32
 }
 
 void MouseCursor::ReadFromSavegame(Stream *in, int cmp_ver) {
diff --git a/engines/ags/shared/ac/view.cpp b/engines/ags/shared/ac/view.cpp
index d560eaeb27c..02624d1f485 100644
--- a/engines/ags/shared/ac/view.cpp
+++ b/engines/ags/shared/ac/view.cpp
@@ -20,11 +20,10 @@
  */
 
 #include "ags/shared/ac/view.h"
-#include "ags/shared/util/aligned_stream.h"
+#include "ags/shared/util/stream.h"
 
 namespace AGS3 {
 
-using AGS::Shared::AlignedStream;
 using AGS::Shared::Stream;
 
 ViewFrame::ViewFrame()
@@ -44,6 +43,7 @@ void ViewFrame::ReadFromFile(Stream *in) {
 	xoffs = in->ReadInt16();
 	yoffs = in->ReadInt16();
 	speed = in->ReadInt16();
+	in->ReadInt16(); // alignment padding to int32
 	flags = in->ReadInt32();
 	sound = in->ReadInt32();
 	in->ReadInt32(); // reserved 1
@@ -55,6 +55,7 @@ void ViewFrame::WriteToFile(Stream *out) {
 	out->WriteInt16(xoffs);
 	out->WriteInt16(yoffs);
 	out->WriteInt16(speed);
+	out->WriteInt16(0); // alignment padding to int32
 	out->WriteInt32(flags);
 	out->WriteInt32(sound);
 	out->WriteInt32(0);
@@ -85,28 +86,24 @@ void ViewLoopNew::Dispose() {
 void ViewLoopNew::WriteToFile_v321(Stream *out) {
 	out->WriteInt16(static_cast<uint16_t>(numFrames));
 	out->WriteInt32(flags);
-	WriteFrames_Aligned(out);
+	WriteFrames(out);
 }
 
-void ViewLoopNew::WriteFrames_Aligned(Stream *out) {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void ViewLoopNew::WriteFrames(Stream *out) {
 	for (int i = 0; i < numFrames; ++i) {
-		frames[i].WriteToFile(&align_s);
-		align_s.Reset();
+		frames[i].WriteToFile(out);
 	}
 }
 
 void ViewLoopNew::ReadFromFile_v321(Stream *in) {
 	Initialize(static_cast<uint16_t>(in->ReadInt16()));
 	flags = in->ReadInt32();
-	ReadFrames_Aligned(in);
+	ReadFrames(in);
 }
 
-void ViewLoopNew::ReadFrames_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void ViewLoopNew::ReadFrames(Stream *in) {
 	for (int i = 0; i < numFrames; ++i) {
-		frames[i].ReadFromFile(&align_s);
-		align_s.Reset();
+		frames[i].ReadFromFile(in);
 	}
 }
 
@@ -150,6 +147,7 @@ void ViewStruct272::ReadFromFile(Stream *in) {
 	for (int i = 0; i < 16; ++i) {
 		numframes[i] = in->ReadInt16();
 	}
+	in->ReadInt16(); // alignment padding to int32
 	in->ReadArrayOfInt32(loopflags, 16);
 	for (int j = 0; j < 16; ++j) {
 		for (int i = 0; i < 20; ++i) {
diff --git a/engines/ags/shared/ac/view.h b/engines/ags/shared/ac/view.h
index 8c32b67116a..2ec12605c2e 100644
--- a/engines/ags/shared/ac/view.h
+++ b/engines/ags/shared/ac/view.h
@@ -69,8 +69,8 @@ struct ViewLoopNew {
 	bool RunNextLoop();
 	void WriteToFile_v321(Shared::Stream *out);
 	void ReadFromFile_v321(Shared::Stream *in);
-	void WriteFrames_Aligned(Shared::Stream *out);
-	void ReadFrames_Aligned(Shared::Stream *in);
+	void WriteFrames(Shared::Stream *out);
+	void ReadFrames(Shared::Stream *in);
 };
 
 struct ViewStruct {
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index c8b174749d6..829a2f787ee 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -35,7 +35,6 @@
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/script/cc_common.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/data_ext.h"
 #include "ags/shared/util/path.h"
 #include "ags/shared/util/string_compat.h"
@@ -245,11 +244,9 @@ HGameFileError ReadScriptModules(std::vector<PScript> &sc_mods, Stream *in, Game
 }
 
 void ReadViewStruct272_Aligned(std::vector<ViewStruct272> &oldv, Stream *in, size_t count) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	oldv.resize(count);
 	for (size_t i = 0; i < count; ++i) {
-		oldv[i].ReadFromFile(&align_s);
-		align_s.Reset();
+		oldv[i].ReadFromFile(in);
 	}
 }
 
@@ -759,10 +756,7 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	// The classic data section.
 	//-------------------------------------------------------------------------
 	GameSetupStruct::SerializeInfo sinfo;
-	{
-		AlignedStream align_s(in, Shared::kAligned_Read);
-		game.GameSetupStructBase::ReadFromFile(&align_s, data_ver, sinfo);
-	}
+	game.GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
 
 	Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename);
 	Debug::Printf(kDbgMsg_Info, "Game uid (old format): `%d`", game.uniqueid);
@@ -776,7 +770,7 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	HGameFileError err = ReadSpriteFlags(ents, in, data_ver);
 	if (!err)
 		return err;
-	game.ReadInvInfo_Aligned(in);
+	game.ReadInvInfo(in);
 	err = game.read_cursors(in);
 	if (!err)
 		return err;
@@ -871,10 +865,7 @@ HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver
 
 void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver) {
 	GameSetupStruct::SerializeInfo sinfo;
-	{
-		AlignedStream align_s(in, Shared::kAligned_Read);
-		_GP(game).ReadFromFile(&align_s, data_ver, sinfo);
-	}
+	_GP(game).ReadFromFile(in, data_ver, sinfo);
 	_GP(game).read_savegame_info(in, data_ver);
 }
 


Commit: aaa8ccb38bcd702a2991b1bd7b00eb81bc11d745
    https://github.com/scummvm/scummvm/commit/aaa8ccb38bcd702a2991b1bd7b00eb81bc11d745
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: hardcode alignment padding in Interaction structs

This complements a task of removing AlignedStream in favor of explicit padding read/write.
>From upstream d0c2e4147bef4a53c63d9018906348464900bebc

Changed paths:
    engines/ags/shared/game/interactions.cpp
    engines/ags/shared/game/interactions.h


diff --git a/engines/ags/shared/game/interactions.cpp b/engines/ags/shared/game/interactions.cpp
index 237c9b44472..47ee4761604 100644
--- a/engines/ags/shared/game/interactions.cpp
+++ b/engines/ags/shared/game/interactions.cpp
@@ -22,7 +22,7 @@
 //include <string.h>
 #include "ags/shared/ac/common.h" // quit
 #include "ags/shared/game/interactions.h"
-#include "ags/shared/util/aligned_stream.h"
+#include "ags/shared/util/stream.h"
 #include "ags/shared/util/math.h"
 #include "common/util.h"
 
@@ -47,12 +47,14 @@ void InteractionValue::clear() {
 
 void InteractionValue::Read(Stream *in) {
 	Type = (InterValType)in->ReadInt8();
+	in->Seek(3); // alignment padding to int32
 	Value = in->ReadInt32();
 	Extra = in->ReadInt32();
 }
 
 void InteractionValue::Write(Stream *out) const {
 	out->WriteInt8(Type);
+	out->WriteByteCount(0, 3); // alignment padding to int32
 	out->WriteInt32(Value);
 	out->WriteInt32(Extra);
 }
@@ -82,36 +84,32 @@ void InteractionCommand::Reset() {
 	Parent = nullptr;
 }
 
-void InteractionCommand::ReadValues_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void InteractionCommand::ReadValues(Stream *in) {
 	for (int i = 0; i < MAX_ACTION_ARGS; ++i) {
-		Data[i].Read(&align_s);
-		align_s.Reset();
+		Data[i].Read(in);
 	}
 }
 
-void InteractionCommand::Read_v321(Stream *in, bool &has_children) {
+void InteractionCommand::Read(Stream *in, bool &has_children) {
 	in->ReadInt32(); // skip the 32-bit vtbl ptr (the old serialization peculiarity)
 	Type = in->ReadInt32();
-	ReadValues_Aligned(in);
+	ReadValues(in);
 	has_children = in->ReadInt32() != 0;
 	in->ReadInt32(); // skip 32-bit Parent pointer
 }
 
-void InteractionCommand::WriteValues_Aligned(Stream *out) const {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void InteractionCommand::WriteValues(Stream *out) const {
 	for (int i = 0; i < MAX_ACTION_ARGS; ++i) {
-		Data[i].Write(&align_s);
-		align_s.Reset();
+		Data[i].Write(out);
 	}
 }
 
-void InteractionCommand::Write_v321(Stream *out) const {
+void InteractionCommand::Write(Stream *out) const {
 	out->WriteInt32(0); // write dummy 32-bit vtbl ptr
 	out->WriteInt32(Type);
-	WriteValues_Aligned(out);
-	out->WriteInt32(Children.get() ? 1 : 0);
-	out->WriteInt32(Parent ? 1 : 0);
+	WriteValues(out);
+	out->WriteInt32(Children.get() ? 1 : 0);  // notify that has children
+	out->WriteInt32(0);  // skip 32-bit Parent pointer
 }
 
 InteractionCommand &InteractionCommand::operator = (const InteractionCommand &ic) {
@@ -141,52 +139,48 @@ void InteractionCommandList::Reset() {
 	TimesRun = 0;
 }
 
-void InteractionCommandList::Read_Aligned(Stream *in, std::vector<bool> &cmd_children) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void InteractionCommandList::ReadCommands(Stream *in, std::vector<bool> &cmd_children) {
 	for (size_t i = 0; i < Cmds.size(); ++i) {
 		bool has_children;
-		Cmds[i].Read_v321(&align_s, has_children);
+		Cmds[i].Read(in, has_children);
 		cmd_children[i] = has_children;
-		align_s.Reset();
 	}
 }
 
-void InteractionCommandList::Read_v321(Stream *in) {
+void InteractionCommandList::Read(Stream *in) {
 	size_t cmd_count = in->ReadInt32();
 	TimesRun = in->ReadInt32();
 
 	std::vector<bool> cmd_children;
 	Cmds.resize(cmd_count);
 	cmd_children.resize(cmd_count);
-	Read_Aligned(in, cmd_children);
+	ReadCommands(in, cmd_children);
 
 	for (size_t i = 0; i < cmd_count; ++i) {
 		if (cmd_children[i]) {
 			Cmds[i].Children.reset(new InteractionCommandList());
-			Cmds[i].Children->Read_v321(in);
+			Cmds[i].Children->Read(in);
 		}
 		Cmds[i].Parent = this;
 	}
 }
 
-void InteractionCommandList::Write_Aligned(Stream *out) const {
-	AlignedStream align_s(out, Shared::kAligned_Write);
+void InteractionCommandList::WriteCommands(Stream *out) const {
 	for (InterCmdVector::const_iterator it = Cmds.begin(); it != Cmds.end(); ++it) {
-		it->Write_v321(&align_s);
-		align_s.Reset();
+		it->Write(out);
 	}
 }
 
-void InteractionCommandList::Write_v321(Stream *out) const {
+void InteractionCommandList::Write(Stream *out) const {
 	size_t cmd_count = Cmds.size();
 	out->WriteInt32(cmd_count);
 	out->WriteInt32(TimesRun);
 
-	Write_Aligned(out);
+	WriteCommands(out);
 
 	for (size_t i = 0; i < cmd_count; ++i) {
 		if (Cmds[i].Children.get() != nullptr)
-			Cmds[i].Children->Write_v321(out);
+			Cmds[i].Children->Write(out);
 	}
 }
 
@@ -257,7 +251,7 @@ Interaction *Interaction::CreateFromStream(Stream *in) {
 		evt.Type = types[i];
 		if (load_response[i] != 0) {
 			evt.Response.reset(new InteractionCommandList());
-			evt.Response->Read_v321(in);
+			evt.Response->Read(in);
 		}
 	}
 	return inter;
@@ -278,7 +272,7 @@ void Interaction::Write(Stream *out) const {
 
 	for (size_t i = 0; i < evt_count; ++i) {
 		if (Events[i].Response.get())
-			Events[i].Response->Write_v321(out);
+			Events[i].Response->Write(out);
 	}
 }
 
diff --git a/engines/ags/shared/game/interactions.h b/engines/ags/shared/game/interactions.h
index 4d05e9d2e53..f400c598dec 100644
--- a/engines/ags/shared/game/interactions.h
+++ b/engines/ags/shared/game/interactions.h
@@ -105,14 +105,14 @@ struct InteractionCommand {
 	void Assign(const InteractionCommand &ic, InteractionCommandList *parent);
 	void Reset();
 
-	void Read_v321(Stream *in, bool &has_children);
-	void Write_v321(Stream *out) const;
+	void Read(Stream *in, bool &has_children);
+	void Write(Stream *out) const;
 
 	InteractionCommand &operator = (const InteractionCommand &ic);
 
 	private:
-	void ReadValues_Aligned(Stream *in);
-	void WriteValues_Aligned(Stream *out) const;
+	void ReadValues(Stream *in);
+	void WriteValues(Stream *out) const;
 };
 
 
@@ -128,12 +128,12 @@ struct InteractionCommandList {
 
 	void Reset();
 
-	void Read_v321(Stream *in);
-	void Write_v321(Stream *out) const;
+	void Read(Stream *in);
+	void Write(Stream *out) const;
 
 protected:
-	void Read_Aligned(Shared::Stream *in, std::vector<bool> &cmd_children);
-	void Write_Aligned(Shared::Stream *out) const;
+	void ReadCommands(Shared::Stream *in, std::vector<bool> &cmd_children);
+	void WriteCommands(Shared::Stream *out) const;
 };
 
 


Commit: bfed6e06e51562e7e4638663f59f4f8444463d73
    https://github.com/scummvm/scummvm/commit/bfed6e06e51562e7e4638663f59f4f8444463d73
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: separate CharacterInfo read/write for saves, no padding

>From upstream c9d48a1c8da3dbcc49d483973b8a478925ea195a

Changed paths:
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h


diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 4b1fb4aa176..86b1ca49726 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -487,7 +487,7 @@ HSaveError ReadInteraction272(Interaction &intr, Stream *in) {
 HSaveError WriteCharacters(Stream *out) {
 	out->WriteInt32(_GP(game).numcharacters);
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].WriteToFile(out);
+		_GP(game).chars[i].WriteToSavegame(out);
 		_GP(charextra)[i].WriteToSavegame(out);
 		Properties::WriteValues(_GP(play).charProps[i], out);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
@@ -502,7 +502,7 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 		return err;
 	const int mls_cmp_ver = cmp_ver > kCharSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].ReadFromFile(in, kGameVersion_Undefined, cmp_ver);
+		_GP(game).chars[i].ReadFromSavegame(in, cmp_ver);
 		_GP(charextra)[i].ReadFromSavegame(in, cmp_ver);
 		Properties::ReadValues(_GP(play).charProps[i], in);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index c9a29a3d9cf..cc512f3ae5e 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -207,8 +207,7 @@ static void ReadMoveList_Aligned(Stream *in) {
 }
 
 static void ReadGameSetupStructBase_Aligned(Stream *in, GameDataVersion data_ver, GameSetupStruct::SerializeInfo &info) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
-	_GP(game).GameSetupStructBase::ReadFromFile(&align_s, data_ver, info);
+	_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, info);
 }
 
 static void ReadCharacterExtras_Aligned(Stream *in) {
diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 809912091b1..5eff4d45e88 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -30,7 +30,8 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver, int save_ver) {
+void CharacterInfo::ReadFromFileImpl(Stream *in, GameDataVersion data_ver, int save_ver) {
+	const bool do_align_pad = data_ver != kGameVersion_Undefined;
 	defview = in->ReadInt32();
 	talkview = in->ReadInt32();
 	view = in->ReadInt32();
@@ -77,7 +78,8 @@ void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver, int save_
 	StrUtil::ReadCStrCount(name, in, MAX_CHAR_NAME_LEN);
 	StrUtil::ReadCStrCount(scrname, in, MAX_SCRIPT_NAME_LEN);
 	on = in->ReadInt8();
-	in->ReadInt8(); // alignment padding to int32
+	if (do_align_pad)
+		in->ReadInt8(); // alignment padding to int32
 
 	// Upgrade data
 	if ((data_ver > kGameVersion_Undefined && data_ver < kGameVersion_360_16) ||
@@ -86,7 +88,8 @@ void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver, int save_
 	}
 }
 
-void CharacterInfo::WriteToFile(Stream *out) {
+void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
+	const bool do_align_pad = !is_save;
 	out->WriteInt32(defview);
 	out->WriteInt32(talkview);
 	out->WriteInt32(view);
@@ -133,7 +136,24 @@ void CharacterInfo::WriteToFile(Stream *out) {
 	out->Write(name, 40);
 	out->Write(scrname, MAX_SCRIPT_NAME_LEN);
 	out->WriteInt8(on);
-	out->WriteInt8(0); // alignment padding to int32
+	if (do_align_pad)
+		out->WriteInt8(0); // alignment padding to int32
+}
+
+void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver) {
+	ReadFromFileImpl(in, data_ver, -1);
+}
+
+void CharacterInfo::WriteToFile(Stream *out) const {
+	WriteToFileImpl(out, false);
+}
+
+void CharacterInfo::ReadFromSavegame(Stream *in, int save_ver) {
+	ReadFromFileImpl(in, kGameVersion_Undefined, save_ver);
+}
+
+void CharacterInfo::WriteToSavegame(Stream *out) const {
+	WriteToFileImpl(out, true);
 }
 
 #if defined (OBSOLETE)
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index b41d4e503fe..0810d5bcf3c 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -181,8 +181,16 @@ struct CharacterInfo {
 	void update_character_idle(CharacterExtras *chex, int &doing_nothing);
 	void update_character_follower(int &char_index, std::vector<int> &followingAsSheep, int &doing_nothing);
 
-	void ReadFromFile(Shared::Stream *in, GameDataVersion data_ver, int save_ver = -1);
-	void WriteToFile(Shared::Stream *out);
+	void ReadFromFile(Shared::Stream *in, GameDataVersion data_ver);
+	void WriteToFile(Shared::Stream *out) const;
+	// TODO: move to runtime-only class (?)
+	void ReadFromSavegame(Shared::Stream *in, int save_ver);
+	void WriteToSavegame(Shared::Stream *out) const;
+
+private:
+	// TODO: this is likely temp here until runtime class is factored out
+	void ReadFromFileImpl(Shared::Stream *in, GameDataVersion data_ver, int save_ver);
+	void WriteToFileImpl(Shared::Stream *out, bool is_save) const;
 };
 
 
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 3aa9f31eda6..46a62cb2135 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -207,7 +207,7 @@ void GameSetupStruct::WriteMouseCursors(Stream *out) {
 void GameSetupStruct::read_characters(Shared::Stream *in) {
 	chars.resize(numcharacters);
 
-	ReadCharacters(in, false);
+	ReadCharacters(in);
 }
 
 void GameSetupStruct::read_lipsync(Shared::Stream *in, GameDataVersion data_ver) {
@@ -237,11 +237,9 @@ void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int> &l
 	}
 }
 
-void GameSetupStruct::ReadCharacters(Stream *in, bool is_save) {
-	const GameDataVersion data_ver = is_save ? kGameVersion_Undefined : _G(loaded_game_file_version);
-	const int save_ver = is_save ? 0 : -1;
+void GameSetupStruct::ReadCharacters(Stream *in) {
 	for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) {
-		chars[iteratorCount].ReadFromFile(in, data_ver, save_ver);
+		chars[iteratorCount].ReadFromFile(in, _G(loaded_game_file_version));
 	}
 }
 
@@ -332,6 +330,8 @@ void GameSetupStruct::ReadAudioClips(Shared::Stream *in, size_t count) {
 }
 
 void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
+	// NOTE: the individual object data is read from legacy saves
+	// same way as if it were from a game file
 	ReadInvInfo(in);
 	ReadMouseCursors(in);
 
@@ -345,7 +345,7 @@ void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
 	in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1);
 	options[OPT_LIPSYNCTEXT] = in->ReadByte();
 
-	ReadCharacters(in, true);
+	ReadCharacters(in);
 }
 
 //=============================================================================
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index a5be0123644..26fa8a95182 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -157,7 +157,7 @@ struct GameSetupStruct : public GameSetupStructBase {
 	void read_lipsync(Shared::Stream *in, GameDataVersion data_ver);
 	void read_messages(Shared::Stream *in, const std::array<int> &load_messages, GameDataVersion data_ver);
 
-	void ReadCharacters(Shared::Stream *in, bool is_save);
+	void ReadCharacters(Shared::Stream *in);
 	void WriteCharacters(Shared::Stream *out);
 	//------------------------------
 	// Part 3


Commit: eca9b3d23965c781ba9f23dde8d25ec0e6dab587
    https://github.com/scummvm/scummvm/commit/eca9b3d23965c781ba9f23dde8d25ec0e6dab587
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hardcode alignment padding in RoomStruct's legacy save

>From upstream fe13f614c8e8cbe63b24602b0a781b7341e643a0

Changed paths:
    engines/ags/engine/ac/room_object.cpp
    engines/ags/engine/ac/room_status.cpp
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/game/interactions.cpp


diff --git a/engines/ags/engine/ac/room_object.cpp b/engines/ags/engine/ac/room_object.cpp
index 262f773e48b..1b58b4a3917 100644
--- a/engines/ags/engine/ac/room_object.cpp
+++ b/engines/ags/engine/ac/room_object.cpp
@@ -111,6 +111,7 @@ void RoomObject::CheckViewFrame() {
 }
 
 void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
+	const bool do_align_pad = save_ver < 0;
 	x = in->ReadInt32();
 	y = in->ReadInt32();
 	transparent = in->ReadInt32();
@@ -135,6 +136,9 @@ void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
 	flags = in->ReadInt8();
 	blocking_width = in->ReadInt16();
 	blocking_height = in->ReadInt16();
+	if (do_align_pad)
+		in->ReadInt16(); // int16 padding to int32 (max)
+
 	if (save_ver >= kRoomStatSvgVersion_36016) {
 		name = StrUtil::ReadString(in);
 	}
diff --git a/engines/ags/engine/ac/room_status.cpp b/engines/ags/engine/ac/room_status.cpp
index 54b5c3d6cdb..113da0c2e73 100644
--- a/engines/ags/engine/ac/room_status.cpp
+++ b/engines/ags/engine/ac/room_status.cpp
@@ -24,7 +24,6 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/shared/game/custom_properties.h"
 #include "ags/engine/game/savegame_components.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/string_utils.h"
 #include "ags/globals.h"
 
@@ -71,20 +70,22 @@ void RoomStatus::FreeProperties() {
 	objProps.clear();
 }
 
-void RoomStatus::ReadFromFile_v321(Stream *in, GameDataVersion data_ver) {
+void RoomStatus::ReadFromSavegame_v321(Stream *in, GameDataVersion data_ver) {
 	FreeScriptData();
 	FreeProperties();
 
 	contentFormat = kRoomStatSvgVersion_Initial;
-	beenhere = in->ReadInt32();
-	numobj = in->ReadInt32();
 	obj.resize(MAX_ROOM_OBJECTS_v300);
 	objProps.resize(MAX_ROOM_OBJECTS_v300);
 	intrObject.resize(MAX_ROOM_OBJECTS_v300);
+
+	beenhere = in->ReadInt32();
+	numobj = in->ReadInt32();
 	ReadRoomObjects_Aligned(in);
 
 	int16_t dummy[MAX_LEGACY_ROOM_FLAGS]; // cannot seek with AlignedStream
 	in->ReadArrayOfInt16(dummy, MAX_LEGACY_ROOM_FLAGS); // flagstates (OBSOLETE)
+	in->ReadInt16(); // alignment padding to int32
 	tsdatasize = static_cast<uint32_t>(in->ReadInt32());
 	in->ReadInt32(); // tsdata
 	for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) {
@@ -101,6 +102,7 @@ void RoomStatus::ReadFromFile_v321(Stream *in, GameDataVersion data_ver) {
 		hotspot[i].Enabled = in->ReadInt8() != 0;
 	in->ReadArrayOfInt8((int8_t *)region_enabled, MAX_ROOM_REGIONS);
 	in->ReadArrayOfInt16(walkbehind_base, MAX_WALK_BEHINDS);
+	in->ReadInt16(); // alignment padding to int32 (66 int8 + 16 int16 = 49 int16 -> 50)
 	in->ReadArrayOfInt32(interactionVariableValues, MAX_GLOBAL_VARIABLES);
 
 	if (data_ver >= kGameVersion_340_4) {
@@ -115,10 +117,8 @@ void RoomStatus::ReadFromFile_v321(Stream *in, GameDataVersion data_ver) {
 }
 
 void RoomStatus::ReadRoomObjects_Aligned(Shared::Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	for (auto &o : obj) {
-		o.ReadFromSavegame(&align_s, 0);
-		align_s.Reset();
+		o.ReadFromSavegame(in, -1 /* legacy save with padding */);
 	}
 }
 
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index 37c66a34f93..9f050f3a5ca 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -100,7 +100,7 @@ struct RoomStatus {
 	void FreeScriptData();
 	void FreeProperties();
 
-	void ReadFromFile_v321(Shared::Stream *in, GameDataVersion data_ver);
+	void ReadFromSavegame_v321(Shared::Stream *in, GameDataVersion data_ver);
 	void ReadRoomObjects_Aligned(Shared::Stream *in);
 	void ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, RoomStatSvgVersion save_ver);
 	void WriteToSavegame(Shared::Stream *out, GameDataVersion data_ver) const;
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index cc512f3ae5e..9ffb01cf725 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -148,8 +148,7 @@ static HSaveError restore_game_scripts(Stream *in, const PreservedParams &pp, Re
 }
 
 static void ReadRoomStatus_Aligned(RoomStatus *roomstat, Stream *in, GameDataVersion data_ver) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
-	roomstat->ReadFromFile_v321(&align_s, data_ver);
+	roomstat->ReadFromSavegame_v321(in, data_ver);
 }
 
 static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
diff --git a/engines/ags/shared/game/interactions.cpp b/engines/ags/shared/game/interactions.cpp
index 47ee4761604..837b469b2a6 100644
--- a/engines/ags/shared/game/interactions.cpp
+++ b/engines/ags/shared/game/interactions.cpp
@@ -285,8 +285,8 @@ void Interaction::ReadFromSavedgame_v321(Stream *in) {
 	for (size_t i = 0; i < evt_count; ++i) {
 		Events[i].Type = in->ReadInt32();
 	}
-	const size_t padding = (MAX_NEWINTERACTION_EVENTS - evt_count);
-	for (size_t i = 0; i < padding; ++i)
+	const size_t dummy_count = (MAX_NEWINTERACTION_EVENTS - evt_count);
+	for (size_t i = 0; i < dummy_count; ++i)
 		in->ReadInt32(); // cannot skip when reading aligned structs
 	ReadTimesRunFromSave_v321(in);
 


Commit: 498a3cb270d77c8cd548e2bcc348434a3f552e3e
    https://github.com/scummvm/scummvm/commit/498a3cb270d77c8cd548e2bcc348434a3f552e3e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hardcode alignment padding in GameState's legacy save

>From upstream f8e9958f29922adde20ed16ad7135deece2a6190

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/media/audio/queued_audio_item.cpp
    engines/ags/engine/media/audio/queued_audio_item.h


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 26de444e8a6..be2fcfeb37e 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -36,7 +36,6 @@
 #include "ags/engine/game/savegame_internal.h"
 #include "ags/engine/main/engine.h"
 #include "ags/engine/media/audio/audio_system.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/string_utils.h"
 #include "ags/globals.h"
 
@@ -386,9 +385,11 @@ bool GameState::ShouldPlayVoiceSpeech() const {
 		(_GP(play).speech_mode != kSpeech_TextOnly) && (_GP(play).voice_avail);
 }
 
-void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, GameStateSvgVersion svg_ver, RestoredData &r_data) {
+void GameState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameStateSvgVersion svg_ver, RestoredData &r_data) {
 	const bool old_save = svg_ver < kGSSvgVersion_Initial;
 	const bool extended_old_save = old_save && (data_ver >= kGameVersion_340_4);
+	const bool do_align_pad = old_save;
+
 	score = in->ReadInt32();
 	usedmode = in->ReadInt32();
 	disabled_user_interface = in->ReadInt32();
@@ -482,6 +483,7 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 		in->ReadInt32(); // recording
 		in->ReadInt32(); // playback
 		in->ReadInt16(); // gamestep
+		in->ReadInt16(); // alignment padding to int32
 	}
 	randseed = in->ReadInt32();    // random seed
 	player_on_region = in->ReadInt32();    // player's current region
@@ -496,6 +498,8 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	mboundx2 = in->ReadInt16();
 	mboundy1 = in->ReadInt16();
 	mboundy2 = in->ReadInt16();
+	if (do_align_pad)
+		in->ReadInt16(); // alignment padding to int32
 	fade_effect = in->ReadInt32();
 	bg_frame_locked = in->ReadInt32();
 	in->ReadArrayOfInt32(globalscriptvars, MAXGSVALUES);
@@ -521,6 +525,8 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	normal_font = in->ReadInt32();
 	speech_font = in->ReadInt32();
 	key_skip_wait = in->ReadInt8();
+	if (do_align_pad)
+		in->Seek(3); // alignment padding to int32
 	swap_portrait_lastchar = in->ReadInt32();
 	separate_music_lib = in->ReadInt32() != 0;
 	in_conversation = in->ReadInt32();
@@ -528,6 +534,8 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	num_parsed_words = in->ReadInt32();
 	in->ReadArrayOfInt16(parsed_words, MAX_PARSED_WORDS);
 	in->Read(bad_parsed_word, 100);
+	if (do_align_pad)
+		in->ReadInt16(); // alignment padding to int32 (15 int16 + 100 int8 = 65 int16 -> 66)
 	raw_color = in->ReadInt32();
 	if (old_save)
 		in->ReadArrayOfInt32(raw_modified, MAX_ROOM_BGFRAMES);
@@ -546,10 +554,13 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	rtint_blue = in->ReadInt32();
 	rtint_level = in->ReadInt32();
 	rtint_light = in->ReadInt32();
-	if (!old_save || extended_old_save)
+	if (!old_save || extended_old_save) {
 		rtint_enabled = in->ReadBool();
-	else
+		if (do_align_pad)
+			in->Seek(3); // alignment padding to int32
+	} else {
 		rtint_enabled = rtint_level > 0;
+	}
 	end_cutscene_music = in->ReadInt32();
 	skip_until_char_stops = in->ReadInt32();
 	get_loc_name_last_time = in->ReadInt32();
@@ -561,7 +572,7 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	new_music_queue_size = in->ReadInt16();
 	if (!old_save) {
 		for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) {
-			new_music_queue[i].ReadFromFile(in);
+			new_music_queue[i].ReadFromSavegame(in);
 		}
 	}
 
@@ -573,8 +584,10 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	crossfade_in_volume_per_step = in->ReadInt16();
 	crossfade_final_volume_in = in->ReadInt16();
 
-	if (old_save)
+	if (old_save) {
+		in->ReadInt16(); // alignment padding to int32 (before array of structs)
 		ReadQueuedAudioItems_Aligned(in);
+	}
 
 	in->Read(takeover_from, 50);
 	in->Read(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN);
@@ -609,7 +622,7 @@ void GameState::ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, G
 	}
 }
 
-void GameState::WriteForSavegame(Shared::Stream *out) const {
+void GameState::WriteForSavegame(Stream *out) const {
 	// NOTE: following parameters are never saved:
 	// recording, playback, gamestep, screen_is_faded_out, room_changes
 	out->WriteInt32(score);
@@ -756,7 +769,7 @@ void GameState::WriteForSavegame(Shared::Stream *out) const {
 	out->WriteArrayOfInt16(music_queue, MAX_QUEUED_MUSIC);
 	out->WriteInt16(new_music_queue_size);
 	for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) {
-		new_music_queue[i].WriteToFile(out);
+		new_music_queue[i].WriteToSavegame(out);
 	}
 
 	out->WriteInt16(crossfading_out_channel);
@@ -790,11 +803,9 @@ void GameState::WriteForSavegame(Shared::Stream *out) const {
 	out->WriteInt32(voice_speech_flags);
 }
 
-void GameState::ReadQueuedAudioItems_Aligned(Shared::Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
+void GameState::ReadQueuedAudioItems_Aligned(Stream *in) {
 	for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) {
-		new_music_queue[i].ReadFromFile(&align_s);
-		align_s.Reset();
+		new_music_queue[i].ReadFromSavegame_v321(in);
 	}
 }
 
@@ -827,7 +838,7 @@ void GameState::FreeViewportsAndCameras() {
 	_scCameraHandles.clear();
 }
 
-void GameState::ReadCustomProperties_v340(Shared::Stream *in, GameDataVersion data_ver) {
+void GameState::ReadCustomProperties_v340(Stream *in, GameDataVersion data_ver) {
 	if (data_ver >= kGameVersion_340_4) {
 		// After runtime property values were read we also copy missing default,
 		// because we do not keep defaults in the saved game, and also in case
@@ -840,7 +851,7 @@ void GameState::ReadCustomProperties_v340(Shared::Stream *in, GameDataVersion da
 	}
 }
 
-void GameState::WriteCustomProperties_v340(Shared::Stream *out, GameDataVersion data_ver) const {
+void GameState::WriteCustomProperties_v340(Stream *out, GameDataVersion data_ver) const {
 	if (data_ver >= kGameVersion_340_4) {
 		// We temporarily remove properties that kept default values
 		// just for the saving data time to avoid getting lots of
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 9ffb01cf725..f5d7f54a6e6 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -173,8 +173,7 @@ static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
 }
 
 static void ReadGameState_Aligned(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
-	_GP(play).ReadFromSavegame(&align_s, data_ver, kGSSvgVersion_OldFormat, r_data);
+	_GP(play).ReadFromSavegame(in, data_ver, kGSSvgVersion_OldFormat, r_data);
 }
 
 static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
diff --git a/engines/ags/engine/media/audio/queued_audio_item.cpp b/engines/ags/engine/media/audio/queued_audio_item.cpp
index 58e1d1d6470..e32d466703b 100644
--- a/engines/ags/engine/media/audio/queued_audio_item.cpp
+++ b/engines/ags/engine/media/audio/queued_audio_item.cpp
@@ -27,21 +27,26 @@ namespace AGS3 {
 
 using AGS::Shared::Stream;
 
-// [IKM] 2012-07-02: these functions are used during load/save game,
-// and read/written as-is, hence cachedClip pointer should be serialized
-// simply like pointer (although that probably does not mean much sense?)
-void QueuedAudioItem::ReadFromFile(Stream *in) {
+void QueuedAudioItem::ReadFromSavegame_v321(Stream *in) {
 	audioClipIndex = in->ReadInt16();
 	priority = in->ReadInt16();
 	repeat = in->ReadBool();
-	in->ReadInt32(); // cachedClip
+	in->Seek(3);     // alignment padding to int32
+	in->ReadInt32(); // cachedClip 32-bit ptr (legacy format)
 }
 
-void QueuedAudioItem::WriteToFile(Stream *out) const {
+void QueuedAudioItem::ReadFromSavegame(Stream *in) {
+	audioClipIndex = in->ReadInt16();
+	priority = in->ReadInt16();
+	repeat = in->ReadBool();
+	in->ReadInt32(); // reserved
+}
+
+void QueuedAudioItem::WriteToSavegame(Stream *out) const {
 	out->WriteInt16(audioClipIndex);
 	out->WriteInt16(priority);
 	out->WriteBool(repeat);
-	out->WriteInt32(0); // cachedClip
+	out->WriteInt32(0); // reserved
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/media/audio/queued_audio_item.h b/engines/ags/engine/media/audio/queued_audio_item.h
index 73c28b2639a..573a56cfb95 100644
--- a/engines/ags/engine/media/audio/queued_audio_item.h
+++ b/engines/ags/engine/media/audio/queued_audio_item.h
@@ -40,8 +40,9 @@ struct QueuedAudioItem {
 	bool  repeat = false;
 	SOUNDCLIP *cachedClip = nullptr;
 
-	void ReadFromFile(Shared::Stream *in);
-	void WriteToFile(Shared::Stream *out) const;
+	void ReadFromSavegame_v321(Shared::Stream *in);
+	void ReadFromSavegame(Shared::Stream *in);
+	void WriteToSavegame(Shared::Stream *out) const;
 };
 
 } // namespace AGS3


Commit: 8b4e30fa35a80737c42dcc4e4cd382b73165d4e1
    https://github.com/scummvm/scummvm/commit/8b4e30fa35a80737c42dcc4e4cd382b73165d4e1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hardcode alignment padding in remaining legacy save parts

>From upstream b3951c9d8c8cfdd75c4a1f412735de76e1476ceb

Changed paths:
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/screen_overlay.cpp
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/main/game_file.cpp


diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index f924b2b81a0..ab10b6fd330 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -48,7 +48,7 @@ void MoveList::SetPixelUnitFraction(float frac) {
 	onpart = permove_dist > 0.f ? (1.f / permove_dist) * frac : 0.f;
 }
 
-void MoveList::ReadFromFile_Legacy(Stream *in) {
+void MoveList::ReadFromSavegame_Legacy(Stream *in) {
 	*this = MoveList(); // reset struct
 	for (int i = 0; i < MAXNEEDSTAGES_LEGACY; ++i) {
 		// X & Y was packed as high/low shorts, and hence reversed in lo-end
@@ -66,11 +66,12 @@ void MoveList::ReadFromFile_Legacy(Stream *in) {
 	in->ReadInt32(); // UNUSED
 	doneflag = in->ReadInt8();
 	direct = in->ReadInt8();
+	in->ReadInt16(); // alignment padding to int32 (finalize struct)
 }
 
-HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
+HSaveError MoveList::ReadFromSavegame(Stream *in, int32_t cmp_ver) {
 	if (cmp_ver < kMoveSvgVersion_350) {
-		ReadFromFile_Legacy(in);
+		ReadFromSavegame_Legacy(in); // FIXME: pass an arg to not use padding; OR remove support of kMoveSvgVersion_Initial?
 		return HSaveError::None();
 	}
 
@@ -111,7 +112,7 @@ HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	return HSaveError::None();
 }
 
-void MoveList::WriteToFile(Stream *out) const {
+void MoveList::WriteToSavegame(Stream *out) const {
 	out->WriteInt32(numstage);
 	if (numstage == 0)
 		return;
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 40ef514819e..4c18881eb27 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -83,9 +83,9 @@ struct MoveList {
 	// Sets a step progress to this fraction of a coordinate unit
 	void SetPixelUnitFraction(float frac);
 
-	void ReadFromFile_Legacy(Shared::Stream *in);
-	AGS::Engine::HSaveError ReadFromFile(Shared::Stream *in, int32_t cmp_ver);
-	void WriteToFile(Shared::Stream *out) const;
+	void ReadFromSavegame_Legacy(Shared::Stream *in);
+	AGS::Engine::HSaveError ReadFromSavegame(Shared::Stream *in, int32_t cmp_ver);
+	void WriteToSavegame(Shared::Stream *out) const;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index cb5217f0c78..9316a471c3f 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -65,7 +65,7 @@ void ScreenOverlay::SetSpriteNum(int sprnum, int offx, int offy) {
 	MarkChanged();
 }
 
-void ScreenOverlay::ReadFromFile(Stream *in, bool &has_bitmap, int32_t cmp_ver) {
+void ScreenOverlay::ReadFromSavegame(Stream *in, bool &has_bitmap, int32_t cmp_ver) {
 	_pic.reset();
 	ddb = nullptr;
 	in->ReadInt32(); // ddb 32-bit pointer value (nasty legacy format)
@@ -85,6 +85,9 @@ void ScreenOverlay::ReadFromFile(Stream *in, bool &has_bitmap, int32_t cmp_ver)
 			_flags |= kOver_PositionAtRoomXY;
 	}
 
+	if (cmp_ver < 0) {
+		in->ReadInt16(); // alignment padding to int32 (finalize struct)
+	}
 	if (cmp_ver >= kOverSvgVersion_35028) {
 		offsetX = in->ReadInt32();
 		offsetY = in->ReadInt32();
@@ -107,7 +110,7 @@ void ScreenOverlay::ReadFromFile(Stream *in, bool &has_bitmap, int32_t cmp_ver)
 	}
 }
 
-void ScreenOverlay::WriteToFile(Stream *out) const {
+void ScreenOverlay::WriteToSavegame(Stream *out) const {
 	out->WriteInt32(0); // ddb 32-bit pointer value (nasty legacy format)
 	if (_flags & kOver_SpriteReference)
 		out->WriteInt32(_sprnum); // sprite reference
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index ee07d8eb4c8..d4b1d0b9d23 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -122,8 +122,8 @@ struct ScreenOverlay {
 		_hasChanged = false;
 	}
 
-	void ReadFromFile(Shared::Stream *in, bool &has_bitmap, int32_t cmp_ver);
-	void WriteToFile(Shared::Stream *out) const;
+	void ReadFromSavegame(Shared::Stream *in, bool &has_bitmap, int32_t cmp_ver);
+	void WriteToSavegame(Shared::Stream *out) const;
 
 private:
 	int _flags = 0; // OverlayFlags
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 016688a9d81..cf38d733f36 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -61,7 +61,6 @@
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/script/cc_common.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/file.h"
 #include "ags/shared/util/stream.h"
 #include "ags/shared/util/string_utils.h"
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 86b1ca49726..37824a5591a 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -509,7 +509,7 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
 		// character movement path (for old saves)
 		if (cmp_ver < kCharSvgVersion_36109) {
-			err = _GP(mls)[CHMLSOFFS + i].ReadFromFile(in, mls_cmp_ver);
+			err = _GP(mls)[CHMLSOFFS + i].ReadFromSavegame(in, mls_cmp_ver);
 			if (!err)
 				return err;
 		}
@@ -773,7 +773,7 @@ HSaveError WriteOverlays(Stream *out) {
 		if (over.type < 0)
 			continue;
 		valid_count++;
-		over.WriteToFile(out);
+		over.WriteToSavegame(out);
 		if (!over.IsSpriteReference())
 			serialize_bitmap(over.GetImage(), out);
 	}
@@ -792,7 +792,7 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 	for (size_t i = 0; i < over_count; ++i) {
 		ScreenOverlay over;
 		bool has_bitmap;
-		over.ReadFromFile(in, has_bitmap, cmp_ver);
+		over.ReadFromSavegame(in, has_bitmap, cmp_ver);
 		if (over.type < 0)
 			continue; // safety abort
 		if (has_bitmap)
@@ -989,7 +989,7 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 			return err;
 		const int mls_cmp_ver = cmp_ver > kRoomStatSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 		for (int i = 0; i < objmls_count; ++i) {
-			err = _GP(mls)[i].ReadFromFile(in, mls_cmp_ver);
+			err = _GP(mls)[i].ReadFromSavegame(in, mls_cmp_ver);
 			if (!err)
 				return err;
 		}
@@ -1008,7 +1008,7 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 HSaveError WriteMoveLists(Stream *out) {
 	out->WriteInt32(static_cast<int32_t>(_GP(mls).size()));
 	for (const auto &movelist : _GP(mls)) {
-		movelist.WriteToFile(out);
+		movelist.WriteToSavegame(out);
 	}
 	return HSaveError::None();
 }
@@ -1023,7 +1023,7 @@ HSaveError ReadMoveLists(Stream *in, int32_t cmp_ver, const PreservedParams & /*
 	if (!AssertGameContent(err, movelist_count, _GP(mls).size(), "Move Lists"))
 		return err;
 	for (size_t i = 0; i < movelist_count; ++i) {
-		err = _GP(mls)[i].ReadFromFile(in, cmp_ver);
+		err = _GP(mls)[i].ReadFromSavegame(in, cmp_ver);
 		if (!err)
 			return err;
 	}
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index f5d7f54a6e6..2d4ebf55fc0 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -61,7 +61,6 @@
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/script/cc_common.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/string_utils.h"
 
 namespace AGS3 {
@@ -197,10 +196,8 @@ static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData
 }
 
 static void ReadMoveList_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	for (int i = 0; i < _GP(game).numcharacters + MAX_ROOM_OBJECTS_v300 + 1; ++i) {
-		_GP(mls)[i].ReadFromFile_Legacy(&align_s);
-		align_s.Reset();
+		_GP(mls)[i].ReadFromSavegame_Legacy(in);
 	}
 }
 
@@ -209,10 +206,8 @@ static void ReadGameSetupStructBase_Aligned(Stream *in, GameDataVersion data_ver
 }
 
 static void ReadCharacterExtras_Aligned(Stream *in) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(charextra)[i].ReadFromSavegame(&align_s, 0);
-		align_s.Reset();
+		_GP(charextra)[i].ReadFromSavegame(in, 0);
 	}
 }
 
@@ -234,12 +229,10 @@ static void restore_game_more_dynamic_values(Stream *in) {
 }
 
 void ReadAnimatedButtons_Aligned(Stream *in, int num_abuts) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	for (int i = 0; i < num_abuts; ++i) {
 		AnimatingGUIButton abtn;
-		abtn.ReadFromSavegame(&align_s, 0);
+		abtn.ReadFromSavegame(in, 0);
 		AddButtonAnimation(abtn);
-		align_s.Reset();
 	}
 }
 
@@ -314,15 +307,13 @@ static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
 }
 
 static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size_t num_overs) {
-	AlignedStream align_s(in, Shared::kAligned_Read);
 	has_bitmap.resize(num_overs);
 	// Remember that overlay indexes may be non-sequential
 	auto &overs = get_overlays();
 	for (size_t i = 0; i < num_overs; ++i) {
 		bool has_bm;
 		ScreenOverlay over;
-		over.ReadFromFile(&align_s, has_bm, 0);
-		align_s.Reset();
+		over.ReadFromSavegame(in, has_bm, -1);
 		if (over.type < 0)
 			continue; // safety abort
 		if (overs.size() <= static_cast<uint32_t>(over.type)) {
diff --git a/engines/ags/engine/main/game_file.cpp b/engines/ags/engine/main/game_file.cpp
index 704ed40596e..0c4c0a053ab 100644
--- a/engines/ags/engine/main/game_file.cpp
+++ b/engines/ags/engine/main/game_file.cpp
@@ -45,7 +45,6 @@
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/shared/script/cc_common.h"
 #include "ags/engine/script/script.h"
-#include "ags/shared/util/aligned_stream.h"
 #include "ags/shared/util/stream.h"
 #include "ags/shared/util/text_stream_reader.h"
 #include "ags/globals.h"


Commit: 8964231e7373850f32090643c780e5199d5d227d
    https://github.com/scummvm/scummvm/commit/8964231e7373850f32090643c780e5199d5d227d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: completely removed AlignedStream and ProxyStream classes

>From upstream 3de497d6623a177645c01822da0144376b6d08f7

Changed paths:
  R engines/ags/shared/util/aligned_stream.cpp
  R engines/ags/shared/util/aligned_stream.h
  R engines/ags/shared/util/proxy_stream.cpp
  R engines/ags/shared/util/proxy_stream.h
    engines/ags/module.mk


diff --git a/engines/ags/module.mk b/engines/ags/module.mk
index 56b0a24df92..93483d90d08 100644
--- a/engines/ags/module.mk
+++ b/engines/ags/module.mk
@@ -70,7 +70,6 @@ MODULE_OBJS = \
 	shared/gui/gui_textbox.o \
 	shared/script/cc_common.o \
 	shared/script/cc_script.o \
-	shared/util/aligned_stream.o \
 	shared/util/buffered_stream.o \
 	shared/util/compress.o \
 	shared/util/data_ext.o \
@@ -85,7 +84,6 @@ MODULE_OBJS = \
 	shared/util/memory_stream.o \
 	shared/util/multi_file_lib.o \
 	shared/util/path.o \
-	shared/util/proxy_stream.o \
 	shared/util/stdio_compat.o \
 	shared/util/stream.o \
 	shared/util/string.o \
diff --git a/engines/ags/shared/util/aligned_stream.cpp b/engines/ags/shared/util/aligned_stream.cpp
deleted file mode 100644
index e366b7d8d8b..00000000000
--- a/engines/ags/shared/util/aligned_stream.cpp
+++ /dev/null
@@ -1,259 +0,0 @@
-/* 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 "ags/shared/util/aligned_stream.h"
-#include "ags/shared/util/math.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Shared {
-
-AlignedStream::AlignedStream(Stream *stream, AlignedStreamMode mode, ObjectOwnershipPolicy stream_ownership_policy,
-                             size_t base_alignment)
-	: ProxyStream(stream, stream_ownership_policy)
-	, _mode(mode)
-	, _baseAlignment(base_alignment)
-	, _maxAlignment(0)
-	, _block(0) {
-}
-
-AlignedStream::~AlignedStream() {
-	AlignedStream::Close();
-}
-
-void AlignedStream::Reset() {
-	FinalizeBlock();
-}
-
-void AlignedStream::Close() {
-	FinalizeBlock();
-	ProxyStream::Close();
-}
-
-bool AlignedStream::CanRead() const {
-	return (_mode == kAligned_Read && _stream->CanRead());
-}
-
-bool AlignedStream::CanWrite() const {
-	return (_mode == kAligned_Write && _stream->CanWrite());
-}
-
-bool AlignedStream::CanSeek() const {
-	// Aligned stream does not support seeking, hence that will break padding count
-	return false;
-}
-
-size_t AlignedStream::Read(void *buffer, size_t size) {
-	ReadPadding(sizeof(int8_t));
-	size = _stream->Read(buffer, size);
-	_block += size;
-	return size;
-}
-
-int32_t AlignedStream::ReadByte() {
-	ReadPadding(sizeof(uint8_t));
-	int32_t b = _stream->ReadByte();
-	_block += sizeof(uint8_t);
-	return b;
-}
-
-int16_t AlignedStream::ReadInt16() {
-	ReadPadding(sizeof(int16_t));
-	int16_t val = _stream->ReadInt16();
-	_block += sizeof(int16_t);
-	return val;
-}
-
-int32_t AlignedStream::ReadInt32() {
-	ReadPadding(sizeof(int32_t));
-	int32_t val = _stream->ReadInt32();
-	_block += sizeof(int32_t);
-	return val;
-}
-
-int64_t AlignedStream::ReadInt64() {
-	ReadPadding(sizeof(int64_t));
-	int64_t val = _stream->ReadInt64();
-	_block += sizeof(int64_t);
-	return val;
-}
-
-size_t AlignedStream::ReadArray(void *buffer, size_t elem_size, size_t count) {
-	ReadPadding(elem_size);
-	count = _stream->ReadArray(buffer, elem_size, count);
-	_block += count * elem_size;
-	return count;
-}
-
-size_t AlignedStream::ReadArrayOfInt16(int16_t *buffer, size_t count) {
-	ReadPadding(sizeof(int16_t));
-	count = _stream->ReadArrayOfInt16(buffer, count);
-	_block += count * sizeof(int16_t);
-	return count;
-}
-
-size_t AlignedStream::ReadArrayOfInt32(int32_t *buffer, size_t count) {
-	ReadPadding(sizeof(int32_t));
-	count = _stream->ReadArrayOfInt32(buffer, count);
-	_block += count * sizeof(int32_t);
-	return count;
-}
-
-size_t AlignedStream::ReadArrayOfInt64(int64_t *buffer, size_t count) {
-	ReadPadding(sizeof(int64_t));
-	count = _stream->ReadArrayOfInt64(buffer, count);
-	_block += count * sizeof(int64_t);
-	return count;
-}
-
-size_t AlignedStream::Write(const void *buffer, size_t size) {
-	WritePadding(sizeof(int8_t));
-	size = _stream->Write(buffer, size);
-	_block += size;
-	return size;
-}
-
-int32_t AlignedStream::WriteByte(uint8_t b) {
-	WritePadding(sizeof(uint8_t));
-	int32_t res = _stream->WriteByte(b);
-	_block += sizeof(uint8_t);
-	return res;
-}
-
-size_t AlignedStream::WriteInt16(int16_t val) {
-	WritePadding(sizeof(int16_t));
-	size_t size = _stream->WriteInt16(val);
-	_block += sizeof(int16_t);
-	return size;
-}
-
-size_t AlignedStream::WriteInt32(int32_t val) {
-	WritePadding(sizeof(int32_t));
-	size_t size = _stream->WriteInt32(val);
-	_block += sizeof(int32_t);
-	return size;
-}
-
-size_t AlignedStream::WriteInt64(int64_t val) {
-	WritePadding(sizeof(int64_t));
-	size_t size = _stream->WriteInt64(val);
-	_block += sizeof(int64_t);
-	return size;
-}
-
-size_t AlignedStream::WriteArray(const void *buffer, size_t elem_size, size_t count) {
-	WritePadding(elem_size);
-	count = _stream->WriteArray(buffer, elem_size, count);
-	_block += count * elem_size;
-	return count;
-}
-
-size_t AlignedStream::WriteArrayOfInt16(const int16_t *buffer, size_t count) {
-	WritePadding(sizeof(int16_t));
-	count = _stream->WriteArrayOfInt16(buffer, count);
-	_block += count * sizeof(int16_t);
-	return count;
-}
-
-size_t AlignedStream::WriteArrayOfInt32(const int32_t *buffer, size_t count) {
-	WritePadding(sizeof(int32_t));
-	count = _stream->WriteArrayOfInt32(buffer, count);
-	_block += count * sizeof(int32_t);
-	return count;
-}
-
-size_t AlignedStream::WriteArrayOfInt64(const int64_t *buffer, size_t count) {
-	WritePadding(sizeof(int64_t));
-	count = _stream->WriteArrayOfInt64(buffer, count);
-	_block += count * sizeof(int64_t);
-	return count;
-}
-
-bool AlignedStream::Seek(soff_t /*offset*/, StreamSeek /*origin*/) {
-	// TODO: split out Seekable Stream interface
-	warning("AlignedStream::Seek not supported");
-	return false;
-}
-
-void AlignedStream::ReadPadding(size_t next_type) {
-	if (next_type == 0) {
-		return;
-	}
-
-	// The next is going to be evenly aligned data type,
-	// therefore a padding check must be made
-	if (next_type % _baseAlignment == 0) {
-		int pad = _block % next_type;
-		// Read padding only if have to
-		if (pad) {
-			// We do not know and should not care if the underlying stream
-			// supports seek, so use read to skip the padding instead.
-			for (size_t i = next_type - pad; i > 0; --i)
-				_stream->ReadByte();
-			_block += next_type - pad;
-		}
-
-		_maxAlignment = MAX(_maxAlignment, next_type);
-		// Data is evenly aligned now
-		if (_block % LargestPossibleType == 0) {
-			_block = 0;
-		}
-	}
-}
-
-void AlignedStream::WritePadding(size_t next_type) {
-	if (next_type == 0) {
-		return;
-	}
-
-	// The next is going to be evenly aligned data type,
-	// therefore a padding check must be made
-	if (next_type % _baseAlignment == 0) {
-		int pad = _block % next_type;
-		// Write padding only if have to
-		if (pad) {
-			_stream->WriteByteCount(0, next_type - pad);
-			_block += next_type - pad;
-		}
-
-		_maxAlignment = MAX(_maxAlignment, next_type);
-		// Data is evenly aligned now
-		if (_block % LargestPossibleType == 0) {
-			_block = 0;
-		}
-	}
-}
-
-void AlignedStream::FinalizeBlock() {
-	// Force the stream to read or write remaining padding to match the alignment
-	if (_mode == kAligned_Read) {
-		ReadPadding(_maxAlignment);
-	} else if (_mode == kAligned_Write) {
-		WritePadding(_maxAlignment);
-	}
-
-	_maxAlignment = 0;
-	_block = 0;
-}
-
-} // namespace Shared
-} // namespace AGS
-} // namespace AGS3
diff --git a/engines/ags/shared/util/aligned_stream.h b/engines/ags/shared/util/aligned_stream.h
deleted file mode 100644
index 65236083ab6..00000000000
--- a/engines/ags/shared/util/aligned_stream.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/* 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/>.
- *
- */
-
-//=============================================================================
-//
-// Class AlignedStream
-// A simple wrapper around stream that controls data padding.
-//
-// Originally, a number of objects in AGS were read and written directly
-// as a data struct in a whole. In order to support backwards compatibility
-// with games made by older versions of AGS, some of the game objects must
-// be read having automatic data alignment in mind.
-//-----------------------------------------------------------------------------
-//
-// AlignedStream uses the underlying stream, it overrides the reading and
-// writing, and inserts extra data padding when needed.
-//
-// Aligned stream works either in read or write mode, it cannot be opened in
-// combined mode.
-//
-// AlignedStream does not support seek, hence moving stream pointer to random
-// position will break padding count logic.
-//
-//=============================================================================
-
-#ifndef AGS_SHARED_UTIL_ALIGNEDSTREAM_H
-#define AGS_SHARED_UTIL_ALIGNEDSTREAM_H
-
-#include "ags/shared/util/proxy_stream.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Shared {
-
-enum AlignedStreamMode {
-kAligned_Read,
-kAligned_Write
-};
-
-class AlignedStream : public ProxyStream {
-public:
-	AlignedStream(Stream *stream, AlignedStreamMode mode,
-				  ObjectOwnershipPolicy stream_ownership_policy = kReleaseAfterUse,
-				  size_t base_alignment = sizeof(int16_t));
-	~AlignedStream() override;
-
-	// Read/Write cumulated padding and reset block counter
-	void            Reset();
-
-	void    Close() override;
-
-	bool    CanRead() const override;
-	bool    CanWrite() const override;
-	bool    CanSeek() const override;
-
-	size_t  Read(void *buffer, size_t size) override;
-	int32_t ReadByte() override;
-	int16_t ReadInt16() override;
-	int32_t ReadInt32() override;
-	int64_t ReadInt64() override;
-	size_t  ReadArray(void *buffer, size_t elem_size, size_t count) override;
-	size_t  ReadArrayOfInt16(int16_t *buffer, size_t count) override;
-	size_t  ReadArrayOfInt32(int32_t *buffer, size_t count) override;
-	size_t  ReadArrayOfInt64(int64_t *buffer, size_t count) override;
-
-	size_t  Write(const void *buffer, size_t size) override;
-	int32_t WriteByte(uint8_t b) override;
-	size_t  WriteInt16(int16_t val) override;
-	size_t  WriteInt32(int32_t val) override;
-	size_t  WriteInt64(int64_t val) override;
-	size_t  WriteArray(const void *buffer, size_t elem_size, size_t count) override;
-	size_t  WriteArrayOfInt16(const int16_t *buffer, size_t count) override;
-	size_t  WriteArrayOfInt32(const int32_t *buffer, size_t count) override;
-	size_t  WriteArrayOfInt64(const int64_t *buffer, size_t count) override;
-
-	bool    Seek(soff_t offset, StreamSeek origin) override;
-
-protected:
-	void            ReadPadding(size_t next_type);
-	void            WritePadding(size_t next_type);
-	void            FinalizeBlock();
-
-	private:
-	static const size_t LargestPossibleType = sizeof(int64_t);
-
-	AlignedStreamMode   _mode;
-	size_t              _baseAlignment;
-	size_t              _maxAlignment;
-	int64_t             _block;
-};
-
-} // namespace Shared
-} // namespace AGS
-} // namespace AGS3
-
-#endif
diff --git a/engines/ags/shared/util/proxy_stream.cpp b/engines/ags/shared/util/proxy_stream.cpp
deleted file mode 100644
index a6339a8b16b..00000000000
--- a/engines/ags/shared/util/proxy_stream.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/* 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 "ags/shared/util/proxy_stream.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Shared {
-
-ProxyStream::ProxyStream(Stream *stream, ObjectOwnershipPolicy stream_ownership_policy)
-	: _stream(stream)
-	, _streamOwnershipPolicy(stream_ownership_policy) {
-}
-
-ProxyStream::~ProxyStream() {
-	ProxyStream::Close();
-}
-
-void ProxyStream::Close() {
-	if (_streamOwnershipPolicy == kDisposeAfterUse) {
-		delete _stream;
-	}
-	_stream = nullptr;
-}
-
-bool ProxyStream::Flush() {
-	return _stream->Flush();
-}
-
-bool ProxyStream::IsValid() const {
-	return _stream && _stream->IsValid();
-}
-
-bool ProxyStream::EOS() const {
-	return _stream->EOS();
-}
-
-soff_t ProxyStream::GetLength() const {
-	return _stream->GetLength();
-}
-
-soff_t ProxyStream::GetPosition() const {
-	return _stream->GetPosition();
-}
-
-bool ProxyStream::CanRead() const {
-	return _stream->CanRead();
-}
-
-bool ProxyStream::CanWrite() const {
-	return _stream->CanWrite();
-}
-
-bool ProxyStream::CanSeek() const {
-	return _stream->CanSeek();
-}
-
-size_t ProxyStream::Read(void *buffer, size_t size) {
-	return _stream->Read(buffer, size);
-}
-
-int32_t ProxyStream::ReadByte() {
-	return _stream->ReadByte();
-}
-
-int16_t ProxyStream::ReadInt16() {
-	return _stream->ReadInt16();
-}
-
-int32_t ProxyStream::ReadInt32() {
-	return _stream->ReadInt32();
-}
-
-int64_t ProxyStream::ReadInt64() {
-	return _stream->ReadInt64();
-}
-
-size_t ProxyStream::ReadArray(void *buffer, size_t elem_size, size_t count) {
-	return _stream->ReadArray(buffer, elem_size, count);
-}
-
-size_t ProxyStream::ReadArrayOfInt16(int16_t *buffer, size_t count) {
-	return _stream->ReadArrayOfInt16(buffer, count);
-}
-
-size_t ProxyStream::ReadArrayOfInt32(int32_t *buffer, size_t count) {
-	return _stream->ReadArrayOfInt32(buffer, count);
-}
-
-size_t ProxyStream::ReadArrayOfInt64(int64_t *buffer, size_t count) {
-	return _stream->ReadArrayOfInt64(buffer, count);
-}
-
-size_t ProxyStream::Write(const void *buffer, size_t size) {
-	return _stream->Write(buffer, size);
-}
-
-int32_t ProxyStream::WriteByte(uint8_t b) {
-	return _stream->WriteByte(b);
-}
-
-size_t ProxyStream::WriteInt16(int16_t val) {
-	return _stream->WriteInt16(val);
-}
-
-size_t ProxyStream::WriteInt32(int32_t val) {
-	return _stream->WriteInt32(val);
-}
-
-size_t ProxyStream::WriteInt64(int64_t val) {
-	return _stream->WriteInt64(val);
-}
-
-size_t ProxyStream::WriteArray(const void *buffer, size_t elem_size, size_t count) {
-	return _stream->WriteArray(buffer, elem_size, count);
-}
-
-size_t ProxyStream::WriteArrayOfInt16(const int16_t *buffer, size_t count) {
-	return _stream->WriteArrayOfInt16(buffer, count);
-}
-
-size_t ProxyStream::WriteArrayOfInt32(const int32_t *buffer, size_t count) {
-	return _stream->WriteArrayOfInt32(buffer, count);
-}
-
-size_t ProxyStream::WriteArrayOfInt64(const int64_t *buffer, size_t count) {
-	return _stream->WriteArrayOfInt64(buffer, count);
-}
-
-bool ProxyStream::Seek(soff_t offset, StreamSeek origin) {
-	return _stream->Seek(offset, origin);
-}
-
-} // namespace Shared
-} // namespace AGS
-} // namespace AGS3
diff --git a/engines/ags/shared/util/proxy_stream.h b/engines/ags/shared/util/proxy_stream.h
deleted file mode 100644
index 6a95fc52c01..00000000000
--- a/engines/ags/shared/util/proxy_stream.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/* 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 AGS_SHARED_UTIL_PROXY_STREAM_H
-#define AGS_SHARED_UTIL_PROXY_STREAM_H
-
-#include "ags/shared/util/stream.h"
-
-namespace AGS3 {
-namespace AGS {
-namespace Shared {
-
-// TODO: replace with std::shared_ptr!!!
-enum ObjectOwnershipPolicy {
-	kReleaseAfterUse,
-	kDisposeAfterUse
-};
-
-class ProxyStream : public Stream {
-public:
-	ProxyStream(Stream *stream, ObjectOwnershipPolicy stream_ownership_policy = kReleaseAfterUse);
-	~ProxyStream() override;
-
-	void    Close() override;
-	bool    Flush() override;
-
-	// Is stream valid (underlying data initialized properly)
-	bool    IsValid() const override;
-	// Is end of stream
-	bool    EOS() const override;
-	// Total length of stream (if known)
-	soff_t  GetLength() const override;
-	// Current position (if known)
-	soff_t  GetPosition() const override;
-
-	bool    CanRead() const override;
-	bool    CanWrite() const override;
-	bool    CanSeek() const override;
-
-	size_t  Read(void *buffer, size_t size) override;
-	int32_t ReadByte() override;
-	int16_t ReadInt16() override;
-	int32_t ReadInt32() override;
-	int64_t ReadInt64() override;
-	size_t  ReadArray(void *buffer, size_t elem_size, size_t count) override;
-	size_t  ReadArrayOfInt16(int16_t *buffer, size_t count) override;
-	size_t  ReadArrayOfInt32(int32_t *buffer, size_t count) override;
-	size_t  ReadArrayOfInt64(int64_t *buffer, size_t count) override;
-
-	size_t  Write(const void *buffer, size_t size) override;
-	int32_t WriteByte(uint8_t b) override;
-	size_t  WriteInt16(int16_t val) override;
-	size_t  WriteInt32(int32_t val) override;
-	size_t  WriteInt64(int64_t val) override;
-	size_t  WriteArray(const void *buffer, size_t elem_size, size_t count) override;
-	size_t  WriteArrayOfInt16(const int16_t *buffer, size_t count) override;
-	size_t  WriteArrayOfInt32(const int32_t *buffer, size_t count) override;
-	size_t  WriteArrayOfInt64(const int64_t *buffer, size_t count) override;
-
-	bool    Seek(soff_t offset, StreamSeek origin) override;
-
-protected:
-	Stream *_stream;
-	ObjectOwnershipPolicy   _streamOwnershipPolicy;
-};
-
-} // namespace Shared
-} // namespace AGS
-} // namespace AGS3
-
-#endif


Commit: 796796e8f351defda78ba9aa039611c807fcf73e
    https://github.com/scummvm/scummvm/commit/796796e8f351defda78ba9aa039611c807fcf73e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: cleanup some mentions of AlignedStream

>From upstream 1ba008ae8260e614d2d2506ee04a02e4dabfbfc8

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/room_status.cpp
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/game/interactions.cpp
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/util/stream.h


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index be2fcfeb37e..30af4575e32 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -586,7 +586,9 @@ void GameState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameState
 
 	if (old_save) {
 		in->ReadInt16(); // alignment padding to int32 (before array of structs)
-		ReadQueuedAudioItems_Aligned(in);
+		for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) {
+			new_music_queue[i].ReadFromSavegame_v321(in);
+		}
 	}
 
 	in->Read(takeover_from, 50);
@@ -803,12 +805,6 @@ void GameState::WriteForSavegame(Stream *out) const {
 	out->WriteInt32(voice_speech_flags);
 }
 
-void GameState::ReadQueuedAudioItems_Aligned(Stream *in) {
-	for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) {
-		new_music_queue[i].ReadFromSavegame_v321(in);
-	}
-}
-
 void GameState::FreeProperties() {
 	for (auto &p : charProps)
 		p.clear();
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 9809e18739c..b6550553061 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -407,7 +407,6 @@ struct GameState {
 	//
 	// Serialization
 	//
-	void ReadQueuedAudioItems_Aligned(Shared::Stream *in);
 	void ReadCustomProperties_v340(Shared::Stream *in, GameDataVersion data_ver);
 	void WriteCustomProperties_v340(Shared::Stream *out, GameDataVersion data_ver) const;
 	void ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, GameStateSvgVersion svg_ver, AGS::Engine::RestoredData &r_data);
diff --git a/engines/ags/engine/ac/room_status.cpp b/engines/ags/engine/ac/room_status.cpp
index 113da0c2e73..fccd42af96b 100644
--- a/engines/ags/engine/ac/room_status.cpp
+++ b/engines/ags/engine/ac/room_status.cpp
@@ -81,10 +81,12 @@ void RoomStatus::ReadFromSavegame_v321(Stream *in, GameDataVersion data_ver) {
 
 	beenhere = in->ReadInt32();
 	numobj = in->ReadInt32();
-	ReadRoomObjects_Aligned(in);
+	// NOTE: legacy format always contained max object slots
+	for (auto &o : obj) {
+		o.ReadFromSavegame(in, -1 /* legacy save with padding */);
+	}
 
-	int16_t dummy[MAX_LEGACY_ROOM_FLAGS]; // cannot seek with AlignedStream
-	in->ReadArrayOfInt16(dummy, MAX_LEGACY_ROOM_FLAGS); // flagstates (OBSOLETE)
+	in->Seek(MAX_LEGACY_ROOM_FLAGS * sizeof(int16_t)); // flagstates (OBSOLETE)
 	in->ReadInt16(); // alignment padding to int32
 	tsdatasize = static_cast<uint32_t>(in->ReadInt32());
 	in->ReadInt32(); // tsdata
@@ -116,12 +118,6 @@ void RoomStatus::ReadFromSavegame_v321(Stream *in, GameDataVersion data_ver) {
 	}
 }
 
-void RoomStatus::ReadRoomObjects_Aligned(Shared::Stream *in) {
-	for (auto &o : obj) {
-		o.ReadFromSavegame(in, -1 /* legacy save with padding */);
-	}
-}
-
 void RoomStatus::ReadFromSavegame(Stream *in, GameDataVersion data_ver, RoomStatSvgVersion save_ver) {
 	FreeScriptData();
 	FreeProperties();
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index 9f050f3a5ca..7fe0c33b0cb 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -101,7 +101,6 @@ struct RoomStatus {
 	void FreeProperties();
 
 	void ReadFromSavegame_v321(Shared::Stream *in, GameDataVersion data_ver);
-	void ReadRoomObjects_Aligned(Shared::Stream *in);
 	void ReadFromSavegame(Shared::Stream *in, GameDataVersion data_ver, RoomStatSvgVersion save_ver);
 	void WriteToSavegame(Shared::Stream *out, GameDataVersion data_ver) const;
 };
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index cf38d733f36..566be868807 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/engine/ac/button.h"
 #include "ags/engine/ac/character.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/draw.h"
@@ -372,6 +373,7 @@ void DoBeforeRestore(PreservedParams &pp) {
 	_GP(play).FreeViewportsAndCameras();
 	free_do_once_tokens();
 
+	RemoveAllButtonAnimations();
 	// unregister gui controls from API exports
 	// CHECKME: find out why are we doing this here? why only to gui controls?
 	for (int i = 0; i < _GP(game).numgui; ++i) {
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 37824a5591a..942269fc93f 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -637,7 +637,6 @@ HSaveError ReadGUI(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/,
 	// Animated buttons
 	if (!AssertFormatTagStrict(err, in, "AnimatedButtons"))
 		return err;
-	RemoveAllButtonAnimations();
 	int anim_count = in->ReadInt32();
 	for (int i = 0; i < anim_count; ++i) {
 		AnimatingGUIButton abut;
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 2d4ebf55fc0..86533e8b854 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -146,10 +146,6 @@ static HSaveError restore_game_scripts(Stream *in, const PreservedParams &pp, Re
 	return HSaveError::None();
 }
 
-static void ReadRoomStatus_Aligned(RoomStatus *roomstat, Stream *in, GameDataVersion data_ver) {
-	roomstat->ReadFromSavegame_v321(in, data_ver);
-}
-
 static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
 	_G(displayed_room) = in->ReadInt32();
 
@@ -161,7 +157,7 @@ static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
 			roomstat->beenhere = beenhere;
 
 			if (roomstat->beenhere) {
-				ReadRoomStatus_Aligned(roomstat, in, data_ver);
+				roomstat->ReadFromSavegame_v321(in, data_ver);
 				if (roomstat->tsdatasize > 0) {
 					roomstat->tsdata.resize(roomstat->tsdatasize);
 					in->Read(roomstat->tsdata.data(), roomstat->tsdatasize);
@@ -171,15 +167,11 @@ static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
 	}
 }
 
-static void ReadGameState_Aligned(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
-	_GP(play).ReadFromSavegame(in, data_ver, kGSSvgVersion_OldFormat, r_data);
-}
-
 static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
 	int screenfadedout_was = _GP(play).screen_is_faded_out;
 	int roomchanges_was = _GP(play).room_changes;
 
-	ReadGameState_Aligned(in, data_ver, r_data);
+	_GP(play).ReadFromSavegame(in, data_ver, kGSSvgVersion_OldFormat, r_data);
 	r_data.Cameras[0].Flags = r_data.Camera0_Flags;
 
 	_GP(play).screen_is_faded_out = screenfadedout_was;
@@ -195,22 +187,6 @@ static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData
 	in->Seek(_GP(game).numgui * sizeof(int32_t));
 }
 
-static void ReadMoveList_Aligned(Stream *in) {
-	for (int i = 0; i < _GP(game).numcharacters + MAX_ROOM_OBJECTS_v300 + 1; ++i) {
-		_GP(mls)[i].ReadFromSavegame_Legacy(in);
-	}
-}
-
-static void ReadGameSetupStructBase_Aligned(Stream *in, GameDataVersion data_ver, GameSetupStruct::SerializeInfo &info) {
-	_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, info);
-}
-
-static void ReadCharacterExtras_Aligned(Stream *in) {
-	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(charextra)[i].ReadFromSavegame(in, 0);
-	}
-}
-
 static void restore_game_palette(Stream *in) {
 	in->ReadArray(&_G(palette)[0], sizeof(RGB), 256);
 }
@@ -228,14 +204,6 @@ static void restore_game_more_dynamic_values(Stream *in) {
 	_G(game_paused) = in->ReadInt32();
 }
 
-void ReadAnimatedButtons_Aligned(Stream *in, int num_abuts) {
-	for (int i = 0; i < num_abuts; ++i) {
-		AnimatingGUIButton abtn;
-		abtn.ReadFromSavegame(in, 0);
-		AddButtonAnimation(abtn);
-	}
-}
-
 static HSaveError restore_game_gui(Stream *in) {
 	// Legacy saves allowed to resize gui lists, and stored full gui data
 	// (could be unintentional side effect). Here we emulate this for
@@ -265,11 +233,14 @@ static HSaveError restore_game_gui(Stream *in) {
 
 		return err;
 	GUI::RebuildGUI(); // rebuild guis in case they were copied from reserved game data
-
 	_GP(game).numgui = _GP(guis).size();
-	RemoveAllButtonAnimations();
+
 	int anim_count = in->ReadInt32();
-	ReadAnimatedButtons_Aligned(in, anim_count);
+	for (int i = 0; i < anim_count; ++i) {
+		AnimatingGUIButton abtn;
+		abtn.ReadFromSavegame(in, 0);
+		AddButtonAnimation(abtn);
+	}
 	return HSaveError::None();
 }
 
@@ -371,7 +342,7 @@ static void restore_game_displayed_room_status(Stream *in, GameDataVersion data_
 			_G(raw_saved_screen) = read_serialized_bitmap(in);
 
 		// get the current troom, in case they save in room 600 or whatever
-		ReadRoomStatus_Aligned(&_GP(troom), in, data_ver);
+		_GP(troom).ReadFromSavegame_v321(in, data_ver);
 
 		if (_GP(troom).tsdatasize > 0) {
 			_GP(troom).tsdata.resize(_GP(troom).tsdatasize);
@@ -454,7 +425,10 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 		return err;
 	restore_game_room_state(in, data_ver);
 	restore_game_play(in, data_ver, r_data);
-	ReadMoveList_Aligned(in);
+	// Global character movelists
+	for (int i = 0; i < _GP(game).numcharacters + MAX_ROOM_OBJECTS_v300 + 1; ++i) {
+		_GP(mls)[i].ReadFromSavegame_Legacy(in);
+	}
 
 	// List of game objects, used to compare with the save contents
 	struct ObjectCounts {
@@ -465,7 +439,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 	} objwas;
 
 	GameSetupStruct::SerializeInfo info;
-	ReadGameSetupStructBase_Aligned(in, data_ver, info);
+	_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, info);
 
 	if (!AssertGameContent(err, objwas.CharacterCount, _GP(game).numcharacters, "Characters") ||
 		!AssertGameContent(err, objwas.DialogCount, _GP(game).numdialog, "Dialogs") ||
@@ -478,7 +452,10 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 	// Modified custom properties are read separately to keep existing save format
 	_GP(play).ReadCustomProperties_v340(in, data_ver);
 
-	ReadCharacterExtras_Aligned(in);
+	// Character extras (runtime only data)
+	for (int i = 0; i < _GP(game).numcharacters; ++i) {
+		_GP(charextra)[i].ReadFromSavegame(in, 0);
+	}
 	restore_game_palette(in);
 	restore_game_dialogs(in);
 	restore_game_more_dynamic_values(in);
diff --git a/engines/ags/shared/game/interactions.cpp b/engines/ags/shared/game/interactions.cpp
index 837b469b2a6..c57ae6eaee9 100644
--- a/engines/ags/shared/game/interactions.cpp
+++ b/engines/ags/shared/game/interactions.cpp
@@ -282,17 +282,14 @@ void Interaction::ReadFromSavedgame_v321(Stream *in) {
 		quit("Can't deserialize interaction: too many events");
 
 	Events.resize(evt_count);
+	// Read required amount and skip the remaining placeholders
 	for (size_t i = 0; i < evt_count; ++i) {
 		Events[i].Type = in->ReadInt32();
 	}
-	const size_t dummy_count = (MAX_NEWINTERACTION_EVENTS - evt_count);
-	for (size_t i = 0; i < dummy_count; ++i)
-		in->ReadInt32(); // cannot skip when reading aligned structs
+	in->Seek((MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
 	ReadTimesRunFromSave_v321(in);
-
-	// Skip an array of dummy 32-bit pointers
-	for (size_t i = 0; i < MAX_NEWINTERACTION_EVENTS; ++i)
-		in->ReadInt32();
+	// Skip an array of dummy 32-bit pointers (nasty legacy format)
+	in->Seek(MAX_NEWINTERACTION_EVENTS * sizeof(int32_t));
 }
 
 void Interaction::WriteToSavedgame_v321(Stream *out) const {
@@ -304,19 +301,17 @@ void Interaction::WriteToSavedgame_v321(Stream *out) const {
 	}
 	out->WriteByteCount(0, (MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
 	WriteTimesRunToSave_v321(out);
-
-	// Array of dummy 32-bit pointers
+	// Array of dummy 32-bit pointers (nasty legacy format)
 	out->WriteByteCount(0, MAX_NEWINTERACTION_EVENTS * sizeof(int32_t));
 }
 
 void Interaction::ReadTimesRunFromSave_v321(Stream *in) {
 	const size_t evt_count = Events.size();
+	// Read required amount and skip the remaining placeholders
 	for (size_t i = 0; i < evt_count; ++i) {
 		Events[i].TimesRun = in->ReadInt32();
 	}
-	const size_t padding = (MAX_NEWINTERACTION_EVENTS - evt_count);
-	for (size_t i = 0; i < padding; ++i)
-		in->ReadInt32(); // cannot skip when reading aligned structs
+	in->Seek((MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t));
 }
 
 void Interaction::WriteTimesRunToSave_v321(Stream *out) const {
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 829a2f787ee..5328c7b2d84 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -243,13 +243,6 @@ HGameFileError ReadScriptModules(std::vector<PScript> &sc_mods, Stream *in, Game
 	return HGameFileError::None();
 }
 
-void ReadViewStruct272_Aligned(std::vector<ViewStruct272> &oldv, Stream *in, size_t count) {
-	oldv.resize(count);
-	for (size_t i = 0; i < count; ++i) {
-		oldv[i].ReadFromFile(in);
-	}
-}
-
 void ReadViews(GameSetupStruct &game, std::vector<ViewStruct> &views, Stream *in, GameDataVersion data_ver) {
 	views.resize(game.numviews);
 	if (data_ver > kGameVersion_272) // 3.x views
@@ -259,8 +252,10 @@ void ReadViews(GameSetupStruct &game, std::vector<ViewStruct> &views, Stream *in
 		}
 	} else // 2.x views
 	{
-		std::vector<ViewStruct272> oldv;
-		ReadViewStruct272_Aligned(oldv, in, game.numviews);
+		std::vector<ViewStruct272> oldv(game.numviews);
+		for (size_t i = 0; i < game.numviews; ++i) {
+			oldv[i].ReadFromFile(in);
+		}
 		Convert272ViewsToNew(oldv, views);
 	}
 }
diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h
index 17b037d17ac..ca930331e63 100644
--- a/engines/ags/shared/util/stream.h
+++ b/engines/ags/shared/util/stream.h
@@ -27,7 +27,6 @@
 //
 // Only streams with uncommon behavior should be derived directly from Stream.
 // Most I/O devices should inherit DataStream instead.
-// Streams that wrap other streams should inherit ProxyStream.
 //
 //=============================================================================
 


Commit: 51de20d796aa795dfe3bb80d992de0d556f54c29
    https://github.com/scummvm/scummvm/commit/51de20d796aa795dfe3bb80d992de0d556f54c29
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS; Engine: drop support of pre-3.5.0-alpha movelist format version

This version was in public pre-alpha release and stayed for 3 months
before being replaced by an updated format along with the new A* pathfinder.
>From upstream ac41f53a9c40f2ceb9e3f80eb219faabfeb3da43

Changed paths:
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/ac/move_list.cpp
    engines/ags/engine/ac/move_list.h
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame_components.cpp


diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index b117b8eb388..f67f2a1f445 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -36,7 +36,7 @@ class Stream;
 using namespace AGS; // FIXME later
 
 enum CharacterSvgVersion {
-	kCharSvgVersion_Initial = 0,
+	kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
 	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
 	kCharSvgVersion_36025   = 2, // animation volume
 	kCharSvgVersion_36109   = 3, // removed movelists, save externally
diff --git a/engines/ags/engine/ac/move_list.cpp b/engines/ags/engine/ac/move_list.cpp
index ab10b6fd330..4f3dd88fb91 100644
--- a/engines/ags/engine/ac/move_list.cpp
+++ b/engines/ags/engine/ac/move_list.cpp
@@ -71,8 +71,8 @@ void MoveList::ReadFromSavegame_Legacy(Stream *in) {
 
 HSaveError MoveList::ReadFromSavegame(Stream *in, int32_t cmp_ver) {
 	if (cmp_ver < kMoveSvgVersion_350) {
-		ReadFromSavegame_Legacy(in); // FIXME: pass an arg to not use padding; OR remove support of kMoveSvgVersion_Initial?
-		return HSaveError::None();
+		return new SavegameError(kSvgErr_UnsupportedComponentVersion,
+								 String::FromFormat("Movelist format %d is no longer supported", cmp_ver));
 	}
 
 	*this = MoveList(); // reset struct
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index 4c18881eb27..d504d313666 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -46,7 +46,7 @@ enum MoveListDoneFlags {
 };
 
 enum MoveListSvgVersion {
-	kMoveSvgVersion_Initial = 0,
+	kMoveSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
 	kMoveSvgVersion_350,   // new pathfinder, arbitrary number of stages
 	kMoveSvgVersion_36109, // skip empty lists, progress as float
 };
diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index 7fe0c33b0cb..0ec86b99439 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -50,7 +50,7 @@ struct HotspotState {
 
 // Savegame data format for RoomStatus
 enum RoomStatSvgVersion {
-	kRoomStatSvgVersion_Initial = 0,
+	kRoomStatSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
 	kRoomStatSvgVersion_350     = 1, // new movelist format (along with pathfinder)
 	kRoomStatSvgVersion_36016   = 2, // hotspot and object names
 	kRoomStatSvgVersion_36025   = 3, // object animation volume
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 942269fc93f..0463e624219 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -500,7 +500,6 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
-	const int mls_cmp_ver = cmp_ver > kCharSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
 		_GP(game).chars[i].ReadFromSavegame(in, cmp_ver);
 		_GP(charextra)[i].ReadFromSavegame(in, cmp_ver);
@@ -509,7 +508,7 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
 		// character movement path (for old saves)
 		if (cmp_ver < kCharSvgVersion_36109) {
-			err = _GP(mls)[CHMLSOFFS + i].ReadFromSavegame(in, mls_cmp_ver);
+			err = _GP(mls)[CHMLSOFFS + i].ReadFromSavegame(in, kMoveSvgVersion_350);
 			if (!err)
 				return err;
 		}
@@ -986,9 +985,8 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		int objmls_count = in->ReadInt32();
 		if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists"))
 			return err;
-		const int mls_cmp_ver = cmp_ver > kRoomStatSvgVersion_Initial ? kMoveSvgVersion_350 : kMoveSvgVersion_Initial;
 		for (int i = 0; i < objmls_count; ++i) {
-			err = _GP(mls)[i].ReadFromSavegame(in, mls_cmp_ver);
+			err = _GP(mls)[i].ReadFromSavegame(in, kMoveSvgVersion_350);
 			if (!err)
 				return err;
 		}
@@ -1084,7 +1082,7 @@ struct ComponentHandlers {
 		{
 			"Characters",
 			kCharSvgVersion_36109,
-			kCharSvgVersion_Initial,
+			kCharSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteCharacters,
 			ReadCharacters
 		},
@@ -1154,21 +1152,21 @@ struct ComponentHandlers {
 		{
 			"Room States",
 			kRoomStatSvgVersion_36109,
-			kRoomStatSvgVersion_Initial,
+			kRoomStatSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteRoomStates,
 			ReadRoomStates
 		},
 		{
 			"Loaded Room State",
 			kRoomStatSvgVersion_36109, // must correspond to "Room States"
-			kRoomStatSvgVersion_Initial,
+			kRoomStatSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteThisRoom,
 			ReadThisRoom
 		},
 		{
 			"Move Lists",
 			kMoveSvgVersion_36109,
-			kMoveSvgVersion_Initial,
+			kMoveSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteMoveLists,
 			ReadMoveLists
 		},


Commit: 76ebe97d9eac553284cfee7d571ae38754baba0b
    https://github.com/scummvm/scummvm/commit/76ebe97d9eac553284cfee7d571ae38754baba0b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.11)

Partially from upstream 72ef9111da060044c5ecae351932ba5201ed589f

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 b5a5e92e8fc..27735ca20c2 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.10"
+#define ACI_VERSION_STR      "3.6.1.11"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.10
+#define ACI_VERSION_MSRC_DEF  3.6.1.11
 #endif
 
 #define SPECIAL_VERSION ""


Commit: be382a0a0a52ab522bf77f0b0aee0b179ec91646
    https://github.com/scummvm/scummvm/commit/be382a0a0a52ab522bf77f0b0aee0b179ec91646
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: separated BitmapToVideoMemLinearImpl for convenience

Split BitmapToVideoMemImpl into simpler BitmapToVideoMemImpl and
 BitmapToVideoMemLinearImpl meant for that "linear filter" transparency fix.
This does not seem to make things noticeably faster with non-linear scaling,
 but may make it easier to understand the code.

Also in case we'd like to remove/replace this hack someday.
>From upstream 5c2129b8f176e8d95b0f26132bf1749d7dabdc29

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index e32ee9f8da0..a2c5402adf6 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -398,7 +398,9 @@ template <typename T> void get_pixel_if_not_transparent(const T *pixel, T *red,
 #define VMEMCOLOR_RGBA(r,g,b,a) \
 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
 
-template<typename T, bool HasAlpha, bool UsingLinearFiltering>
+// Template helper function which converts bitmap to a video memory buffer,
+// applies transparency and optionally copies the source alpha channel (if available).
+template<typename T, bool HasAlpha>
 void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
 	// tell the compiler these won't change mid loop execution
 	const int t_width = tile->width;
@@ -406,54 +408,98 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(const Bitmap *bitmap, const
 	const int t_x = tile->x;
 	const int t_y = tile->y;
 
+	const int idst_pitch = dst_pitch * sizeof(uint8_t) / sizeof(uint32_t); // destination is always 32-bit
+	auto idst = reinterpret_cast<uint32_t *>(dst_ptr);
+
+	for (int y = 0; y < t_height; y++) {
+		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
+		for (int x = 0; x < t_width; x++) {
+			auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
+			const T src_color = srcData[0];
+			if (HasAlpha) {
+				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), algeta<T>(src_color));
+			} else if (is_color_mask<T>(src_color)) {
+				idst[x] = 0;
+			} else {
+				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
+			}
+		}
+		idst += idst_pitch;
+	}
+}
+
+// Template helper function which converts bitmap to a video memory buffer,
+// assuming that the destination is always opaque (alpha channel is filled with 0xFF)
+template<typename T>
+void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
+	// tell the compiler these won't change mid loop execution
+	const int t_width = tile->width;
+	const int t_height = tile->height;
+	const int t_x = tile->x;
+	const int t_y = tile->y;
+
+	const int idst_pitch = dst_pitch * sizeof(uint8_t) / sizeof(uint32_t); // destination is always 32-bit
+	auto idst = reinterpret_cast<uint32_t *>(dst_ptr);
+
+	for (int y = 0; y < t_height; y++) {
+		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
+		for (int x = 0; x < t_width; x++) {
+			auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
+			const T src_color = srcData[0];
+			idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
+		}
+		idst += idst_pitch;
+	}
+}
+
+// Template helper function which converts bitmap to a video memory buffer
+// with a semi-transparent pixels fix for "Linear" graphics filter which prevents
+// colored outline (usually either of black or "magic pink" color).
+template<typename T, bool HasAlpha>
+void VideoMemoryGraphicsDriver::BitmapToVideoMemLinearImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
+	// tell the compiler these won't change mid loop execution
+	const int t_width = tile->width;
+	const int t_height = tile->height;
+	const int t_x = tile->x;
+	const int t_y = tile->y;
+
 	const int src_bpp = sizeof(T);
 	const int idst_pitch = dst_pitch * sizeof(uint8_t) / sizeof(uint32_t); // destination is always 32-bit
 	auto idst = reinterpret_cast<uint32_t *>(dst_ptr);
 	bool lastPixelWasTransparent = false;
-
 	for (int y = 0; y < t_height; y++) {
 		lastPixelWasTransparent = false;
 		const uint8_t *scanline_before = (y > 0) ? bitmap->GetScanLine(y + t_y - 1) : nullptr;
 		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
 		const uint8_t *scanline_after = (y < t_height - 1) ? bitmap->GetScanLine(y + t_y + 1) : nullptr;
-
 		for (int x = 0; x < t_width; x++) {
 			auto srcData = (const T *)&scanline_at[(x + t_x) * src_bpp];
 			const T src_color = srcData[0];
 
 			if (is_color_mask<T>(src_color)) {
-				if (!UsingLinearFiltering) {
+				// set to transparent, but use the colour from the neighbouring
+				// pixel to stop the linear filter doing colored outlines
+				T red = 0, green = 0, blue = 0, divisor = 0;
+				if (x > 0)
+					get_pixel_if_not_transparent<T>(&srcData[-1], &red, &green, &blue, &divisor);
+				if (x < t_width - 1)
+					get_pixel_if_not_transparent<T>(&srcData[1], &red, &green, &blue, &divisor);
+				if (y > 0)
+					get_pixel_if_not_transparent<T>((const T *)&scanline_before[(x + t_x) * src_bpp], &red, &green, &blue, &divisor);
+				if (y < t_height - 1)
+					get_pixel_if_not_transparent<T>((const T *)&scanline_after[(x + t_x) * src_bpp], &red, &green, &blue, &divisor);
+				if (divisor > 0)
+					idst[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
+				else
 					idst[x] = 0;
-				} else {
-					// set to transparent, but use the colour from the neighbouring
-					// pixel to stop the linear filter doing black outlines
-					T red = 0, green = 0, blue = 0, divisor = 0;
-					if (x > 0)
-						get_pixel_if_not_transparent<T>(&srcData[-1], &red, &green, &blue, &divisor);
-					if (x < t_width - 1)
-						get_pixel_if_not_transparent<T>(&srcData[1], &red, &green, &blue, &divisor);
-					if (y > 0)
-						get_pixel_if_not_transparent<T>(
-							(const T *)&scanline_before[(x + t_x) * src_bpp], &red, &green,
-							&blue, &divisor);
-					if (y < t_height - 1)
-						get_pixel_if_not_transparent<T>(
-							(const T *)&scanline_after[(x + t_x) * src_bpp], &red, &green,
-							&blue, &divisor);
-					if (divisor > 0)
-						idst[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
-					else
-						idst[x] = 0;
-				}
 				lastPixelWasTransparent = true;
 			} else if (HasAlpha) {
-				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color),
-										 algeta<T>(src_color));
+				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), algeta<T>(src_color));
 			} else {
 				idst[x] = VMEMCOLOR_RGBA(algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
 				if (lastPixelWasTransparent) {
 					// update the colour of the previous transparent pixel, to
-					// stop black outlines when linear filtering
+					// stop colored outlines when linear filtering
 					idst[x - 1] = idst[x] & 0x00FFFFFF;
 					lastPixelWasTransparent = false;
 				}
@@ -462,34 +508,37 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMemImpl(const Bitmap *bitmap, const
 		idst += idst_pitch;
 	}
 }
-
 void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile,
 												 uint8_t *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
-	const int src_depth = bitmap->GetColorDepth();
-	switch (src_depth) {
+	switch (bitmap->GetColorDepth()) {
 	case 8:
 		if (usingLinearFiltering) {
-			BitmapToVideoMemImpl<uint8_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
+			BitmapToVideoMemLinearImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		} else {
-			BitmapToVideoMemImpl<uint8_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
+			BitmapToVideoMemImpl<uint8_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		}
+
 		break;
 	case 16:
 		if (usingLinearFiltering) {
-			BitmapToVideoMemImpl<uint16_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
+			BitmapToVideoMemLinearImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		} else {
-			BitmapToVideoMemImpl<uint16_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
+			BitmapToVideoMemImpl<uint16_t, false>(bitmap, tile, dst_ptr, dst_pitch);
 		}
 		break;
 	case 32:
-		if (has_alpha && usingLinearFiltering) {
-			BitmapToVideoMemImpl<uint32_t, true, true>(bitmap, tile, dst_ptr, dst_pitch);
-		} else if (has_alpha && !usingLinearFiltering) {
-			BitmapToVideoMemImpl<uint32_t, true, false>(bitmap, tile, dst_ptr, dst_pitch);
-		} else if (!has_alpha && usingLinearFiltering) {
-			BitmapToVideoMemImpl<uint32_t, false, true>(bitmap, tile, dst_ptr, dst_pitch);
+		if (usingLinearFiltering) {
+			if (has_alpha) {
+				BitmapToVideoMemLinearImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch);
+			} else {
+				BitmapToVideoMemLinearImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch);
+			}
 		} else {
-			BitmapToVideoMemImpl<uint32_t, false, false>(bitmap, tile, dst_ptr, dst_pitch);
+			if (has_alpha) {
+				BitmapToVideoMemImpl<uint32_t, true>(bitmap, tile, dst_ptr, dst_pitch);
+			} else {
+				BitmapToVideoMemImpl<uint32_t, false>(bitmap, tile, dst_ptr, dst_pitch);
+			}
 		}
 		break;
 	default:
@@ -497,30 +546,8 @@ void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const boo
 	}
 }
 
-template<typename T>
-void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaqueImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
-	const int t_width = tile->width;
-	const int t_height = tile->height;
-	const int t_x = tile->x;
-	const int t_y = tile->y;
-	for (int y = 0; y < t_height; y++) {
-		const uint8_t *scanline_at = bitmap->GetScanLine(y + t_y);
-		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
-
-		for (int x = 0; x < t_width; x++) {
-			auto srcData = (const T *)&scanline_at[(x + t_x) * sizeof(T)];
-			const T src_color = srcData[0];
-			memPtrLong[x] = VMEMCOLOR_RGBA(
-				algetr<T>(src_color), algetg<T>(src_color), algetb<T>(src_color), 0xFF);
-		}
-		dst_ptr += dst_pitch;
-	}
-}
-
 void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch) {
-	const int src_depth = bitmap->GetColorDepth();
-
-	switch (src_depth) {
+	switch (bitmap->GetColorDepth()) {
 	case 8:
 		BitmapToVideoMemOpaqueImpl<uint8_t>(bitmap, tile, dst_ptr, dst_pitch);
 		break;
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 7458a94afbe..4e4016585f8 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -351,17 +351,14 @@ private:
 	size_t _fxIndex; // next free pool item
 
 	// specialized method to convert bitmap to video memory depending on bit depth
-	template<typename T, bool HasAlpha, bool UsingLinearFiltering>
-	void
-	BitmapToVideoMemImpl(
-		const Bitmap *bitmap, const TextureTile *tile,
-		uint8_t *dst_ptr, const int dst_pitch);
+	template<typename T, bool HasAlpha>
+	void BitmapToVideoMemImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch);
 
 	template<typename T>
-	void
-	BitmapToVideoMemOpaqueImpl(
-		const Bitmap *bitmap, const TextureTile *tile,
-		uint8_t *dst_ptr, const int dst_pitch);
+	void BitmapToVideoMemOpaqueImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch);
+
+	template<typename T, bool HasAlpha>
+	void BitmapToVideoMemLinearImpl(const Bitmap *bitmap, const TextureTile *tile, uint8_t *dst_ptr, const int dst_pitch);
 
 	// Texture short-term cache:
 	// - caches textures while they are in the immediate use;


Commit: 3995f634e9abcde767cee043fbb4b52183fe02f8
    https://github.com/scummvm/scummvm/commit/3995f634e9abcde767cee043fbb4b52183fe02f8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed pushed textual button not redrawn after mouse up

Was broken by 7989edb
>From upstream 1de96521f5fece460a96814cd699c51eed49788d

Changed paths:
    engines/ags/shared/gui/gui_button.cpp


diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index 5fea3753d60..28012ba0a6e 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -257,24 +257,24 @@ void GUIButton::SetText(const String &text) {
 }
 
 bool GUIButton::OnMouseDown() {
-	IsPushed = true;
 	if (!IsImageButton())
 		MarkChanged();
+	IsPushed = true;
 	UpdateCurrentImage();
 	return false;
 }
 
 void GUIButton::OnMouseEnter() {
-	IsMouseOver = true;
 	if (IsPushed && !IsImageButton())
 		MarkChanged();
+	IsMouseOver = true;
 	UpdateCurrentImage();
 }
 
 void GUIButton::OnMouseLeave() {
-	IsMouseOver = false;
 	if (IsPushed && !IsImageButton())
 		MarkChanged();
+	IsMouseOver = false;
 	UpdateCurrentImage();
 }
 
@@ -283,9 +283,9 @@ void GUIButton::OnMouseUp() {
 		if (IsGUIEnabled(this) && IsClickable())
 			IsActivated = true;
 	}
-	IsPushed = false;
 	if (IsPushed && !IsImageButton())
 		MarkChanged();
+	IsPushed = false;
 	UpdateCurrentImage();
 }
 


Commit: 6789d244144a79c15f4b8cda9c7fd01c455f7c5b
    https://github.com/scummvm/scummvm/commit/6789d244144a79c15f4b8cda9c7fd01c455f7c5b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix improper string length calculation in string functions

Broken in 54f8b2bccec35b2c127204e6bebef674ffb6a740

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


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 432c12963f3..2b9cde69fc2 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -62,7 +62,7 @@ const char *String_Copy(const char *srcString) {
 }
 
 const char *String_Append(const char *thisString, const char *extrabit) {
-	size_t new_len = strlen(thisString) + strlen(extrabit);
+	size_t new_len = strlen(thisString) + strlen(extrabit) + 1;
 	char *buffer = CreateNewScriptString(new_len);
 	Common::strcpy_s(buffer, new_len, thisString);
 	Common::strcat_s(buffer, new_len, extrabit);
@@ -72,7 +72,7 @@ const char *String_Append(const char *thisString, const char *extrabit) {
 const char *String_AppendChar(const char *thisString, int extraOne) {
 	char chr[5]{};
 	size_t chw = usetc(chr, extraOne);
-    size_t new_len = strlen(thisString) + chw;
+    size_t new_len = strlen(thisString) + chw + 1;
     char *buffer = CreateNewScriptString(new_len);
 	Common::sprintf_s(buffer, new_len, "%s%s", thisString, chr);
 	return buffer;
@@ -89,7 +89,7 @@ const char *String_ReplaceCharAt(const char *thisString, int index, int newChar)
 	size_t old_sz = ucwidth(uchar);
 	char new_chr[5]{};
 	size_t new_chw = usetc(new_chr, newChar);
-	size_t new_len = off + remain_sz + new_chw - old_sz;
+	size_t new_len = off + remain_sz + new_chw - old_sz + 1;
 	char *buffer = CreateNewScriptString(new_len);
 	memcpy(buffer, thisString, off);
 	memcpy(buffer + off, new_chr, new_chw);
@@ -105,7 +105,7 @@ const char *String_Truncate(const char *thisString, int length) {
 		return thisString;
 
 	size_t sz = uoffset(thisString, length);
-	char *buffer = CreateNewScriptString(sz);
+	char *buffer = CreateNewScriptString(sz + 1);
 	memcpy(buffer, thisString, sz);
 	buffer[sz] = 0;
 	return buffer;
@@ -122,7 +122,7 @@ const char *String_Substring(const char *thisString, int index, int length) {
 	size_t end = uoffset(thisString + start, sublen) + start;
 	size_t copysz = end - start;
 
-	char *buffer = CreateNewScriptString(copysz);
+	char *buffer = CreateNewScriptString(copysz + 1);
 	memcpy(buffer, thisString + start, copysz);
 	buffer[copysz] = 0;
 	return buffer;
@@ -198,16 +198,16 @@ const char *String_Replace(const char *thisString, const char *lookForText, cons
 
 const char *String_LowerCase(const char *thisString) {
 	size_t len = strlen(thisString);
-	char *buffer = CreateNewScriptString(len);
-	memcpy(buffer, thisString, len);
+	char *buffer = CreateNewScriptString(len + 1);
+	memcpy(buffer, thisString, len + 1);
 	ustrlwr(buffer);
 	return buffer;
 }
 
 const char *String_UpperCase(const char *thisString) {
 	size_t len = strlen(thisString);
-	char *buffer = CreateNewScriptString(len);
-	memcpy(buffer, thisString, len);
+	char *buffer = CreateNewScriptString(len + 1);
+	memcpy(buffer, thisString, len + 1);
 	ustrupr(buffer);
 	return buffer;
 }


Commit: e710697044e91399a3ac5706e239deaa9a623eb2
    https://github.com/scummvm/scummvm/commit/e710697044e91399a3ac5706e239deaa9a623eb2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: tidy script runner remove unused variables

>From upstream 0dc1562b075cea3d2bd1c1ad1a4feafe471b2db5

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 44220c936b7..ae9bd2187d1 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -571,8 +571,6 @@ int ccInstance::Run(int32_t curpc) {
 	unsigned loopCheckIterations = 0u; // loop iterations accumulated only if check is enabled
 
 	const auto timeout = std::chrono::milliseconds(_G(timeoutCheckMs));
-	// NOTE: removed timeout_abort check for now: was working *logically* wrong;
-	//const auto timeout_abort = std::chrono::milliseconds(_G(timeoutAbortMs));
 	_lastAliveTs = AGS_Clock::now();
 
 	/* Main bytecode execution loop */
@@ -863,7 +861,6 @@ int ccInstance::Run(int32_t curpc) {
 		}
 		case SCMD_NOTREG: {
 			auto &reg1 = registers[codeOp.Arg1i()];
-			const auto &reg2 = registers[codeOp.Arg2i()];
 			reg1 = !(reg1);
 			break;
 		}


Commit: 032d4e9919b7e259cb90975d1191bdaa5d77cf5e
    https://github.com/scummvm/scummvm/commit/032d4e9919b7e259cb90975d1191bdaa5d77cf5e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in script cc_instance used const when possible

>From upstream a27fae178c350451eaaa2cb1d9c46d6aa1dda67f

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/engine/script/cc_instance.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index ae9bd2187d1..fd9860f03b5 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -60,7 +60,7 @@ enum ScriptOpArgIsReg {
 };
 
 struct ScriptCommandInfo {
-	ScriptCommandInfo(int32_t code, const char *cmdname, int arg_count, ScriptOpArgIsReg arg_is_reg)
+	ScriptCommandInfo(const int32_t code, const char *cmdname, const int arg_count, const ScriptOpArgIsReg arg_is_reg)
 		: Code(code), CmdName(cmdname), ArgCount(arg_count), ArgIsReg{
 			(arg_is_reg & kScOpArg1IsReg) != 0,
 			(arg_is_reg & kScOpArg2IsReg) != 0,
@@ -215,7 +215,7 @@ ccInstance *ccInstance::CreateFromScript(PScript scri) {
 	return CreateEx(scri, nullptr);
 }
 
-ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
+ccInstance *ccInstance::CreateEx(PScript scri, const ccInstance *joined) {
 	// allocate and copy all the memory with data, code and strings across
 	ccInstance *cinst = new ccInstance();
 	if (!cinst->_Create(scri, joined)) {
@@ -224,7 +224,7 @@ ccInstance *ccInstance::CreateEx(PScript scri, ccInstance *joined) {
 	return cinst;
 }
 
-void ccInstance::SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops) {
+void ccInstance::SetExecTimeout(const unsigned sys_poll_ms, const unsigned abort_ms, const unsigned abort_loops) {
 	_G(timeoutCheckMs) = sys_poll_ms;
 	_G(timeoutAbortMs) = abort_ms;
 	_G(maxWhileLoops) = abort_loops;
@@ -379,11 +379,11 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 	// using negative offsets, and does not care about any preceding entries.
 	int32_t startat = -1;
 	char mangledName[200];
-	size_t mangled_len = snprintf(mangledName, sizeof(mangledName), "%s$", funcname);
+	const size_t mangled_len = snprintf(mangledName, sizeof(mangledName), "%s$", funcname);
 	int32_t export_args = numargs;
 
 	for (int k = 0; k < instanceof->numexports; k++) {
-		char *thisExportName = instanceof->exports[k];
+		const char *thisExportName = instanceof->exports[k];
 		bool match = false;
 
 		// check for a mangled name match
@@ -399,7 +399,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 		}
 		// check for an exact match (if the script was compiled with an older version)
 		if (match || (strcmp(thisExportName, funcname) == 0)) {
-			int32_t etype = (instanceof->export_addr[k] >> 24L) & 0x000ff;
+			const int32_t etype = (instanceof->export_addr[k] >> 24L) & 0x000ff;
 			if (etype != EXPORT_FUNCTION) {
 				cc_error("symbol is not a function");
 				return -1;
@@ -432,7 +432,7 @@ int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const
 
 	_GP(InstThreads).push_back(this); // push instance thread
 	runningInst = this;
-	int reterr = Run(startat);
+	const int reterr = Run(startat);
 	// Cleanup before returning, even if error
 	ASSERT_STACK_SIZE(numargs);
 	PopValuesFromStack(numargs);
@@ -502,7 +502,7 @@ inline RuntimeScriptValue GetStackPtrOffsetFw(RuntimeScriptValue *stack, int32_t
 // Applies a runtime fixup to the given arg;
 // Fixup of type `fixup` is applied to the `code` value,
 // the result is assigned to the `arg`.
-inline bool FixupArgument(RuntimeScriptValue &arg, int fixup, uintptr code, RuntimeScriptValue *stack, const char *strings) {
+inline bool FixupArgument(RuntimeScriptValue &arg, const int fixup, const uintptr code, RuntimeScriptValue *stack, const char *strings) {
 	// could be relative pointer or import address
 	switch (fixup) {
 	case FIXUP_NOFIXUP:
@@ -1469,7 +1469,7 @@ int ccInstance::Run(int32_t curpc) {
 	return 0;
 }
 
-String ccInstance::GetCallStack(int maxLines) const {
+String ccInstance::GetCallStack(const int maxLines) const {
 	String buffer = String::FromFormat("in \"%s\", line %d\n", runningInst->instanceof->GetSectionName(pc), line_number);
 
 	int linesDone = 0;
@@ -1494,7 +1494,7 @@ RuntimeScriptValue ccInstance::GetSymbolAddress(const char *symname) const {
 	char altName[200];
 	snprintf(altName, sizeof(altName), "%s$", symname);
 	RuntimeScriptValue rval_null;
-	size_t len_altName = strlen(altName);
+	const size_t len_altName = strlen(altName);
 	for (k = 0; k < instanceof->numexports; k++) {
 		if (strcmp(instanceof->exports[k], symname) == 0)
 			return exports[k];
@@ -1582,7 +1582,7 @@ void ccInstance::NotifyAlive() {
 	_lastAliveTs = AGS_Clock::now();
 }
 
-bool ccInstance::_Create(PScript scri, ccInstance *joined) {
+bool ccInstance::_Create(PScript scri, const ccInstance *joined) {
 	_G(currentline) = -1;
 	if ((scri == nullptr) && (joined != nullptr))
 		scri = joined->instanceof;
@@ -1665,8 +1665,8 @@ bool ccInstance::_Create(PScript scri, ccInstance *joined) {
 
 	// find the real address of the exports
 	for (int i = 0; i < scri->numexports; i++) {
-		int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff;
-		int32_t eaddr = (scri->export_addr[i] & 0x00ffffff);
+		const int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff;
+		const int32_t eaddr = (scri->export_addr[i] & 0x00ffffff);
 		if (etype == EXPORT_FUNCTION) {
 			// NOTE: unfortunately, there seems to be no way to know if
 			// that's an extender function that expects object pointer
@@ -1805,7 +1805,7 @@ bool ccInstance::CreateGlobalVars(const ccScript *scri) {
 			// DATADATA fixup takes relative address of global data element from fixups array;
 			// this is the address of element, which stores address of actual data
 			glvar.ScAddress = scri->fixups[i];
-			int32_t data_addr = BBOp::Int32FromLE(*(int32_t *)&globaldata[glvar.ScAddress]);
+			const int32_t data_addr = BBOp::Int32FromLE(*(int32_t *)&globaldata[glvar.ScAddress]);
 			if (glvar.ScAddress - data_addr != 200 /* size of old AGS string */) {
 				// CHECKME: probably replace with mere warning in the log?
 				cc_error("unexpected old-style string's alignment");
@@ -1825,8 +1825,8 @@ bool ccInstance::CreateGlobalVars(const ccScript *scri) {
 
 	// Step Two: deduce global variables from exports
 	for (int i = 0; i < scri->numexports; ++i) {
-		int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff;
-		int32_t eaddr = (scri->export_addr[i] & 0x00ffffff);
+		const int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff;
+		const int32_t eaddr = (scri->export_addr[i] & 0x00ffffff);
 		if (etype == EXPORT_DATA) {
 			// NOTE: old-style strings could not be exported in AGS,
 			// no need to worry about these here
@@ -1855,7 +1855,7 @@ bool ccInstance::AddGlobalVar(const ScriptVariable &glvar) {
 	return true;
 }
 
-ScriptVariable *ccInstance::FindGlobalVar(int32_t var_addr) {
+ScriptVariable *ccInstance::FindGlobalVar(const int32_t var_addr) {
 	// NOTE: see comment for AddGlobalVar()
 	if (var_addr < 0 || var_addr >= globaldatasize) {
 		/*
@@ -1863,14 +1863,14 @@ ScriptVariable *ccInstance::FindGlobalVar(int32_t var_addr) {
 		*/
 		Debug::Printf(kDbgMsg_Warn, "WARNING: looking up for global variable beyond allocated buffer (%d, %d)", var_addr, globaldatasize);
 	}
-	ScVarMap::iterator it = globalvars->find(var_addr);
+	const ScVarMap::iterator it = globalvars->find(var_addr);
 	return it != globalvars->end() ? &it->_value : nullptr;
 }
 
-static int DetermineScriptLine(const int32_t *code, size_t codesz, size_t at_pc) {
+static int DetermineScriptLine(const int32_t *code, const size_t codesz, const size_t at_pc) {
 	int line = -1;
 	for (size_t pc = 0; (pc <= at_pc) && (pc < codesz); ++pc) {
-		int op = code[pc] & INSTANCE_ID_REMOVEMASK;
+		const int op = code[pc] & INSTANCE_ID_REMOVEMASK;
 		if (op < 0 || op >= CC_NUM_SCCMDS) return -1;
 		if (pc + (*g_commands)[op].ArgCount >= codesz) return -1;
 		if (op == SCMD_LINENUM)
@@ -1880,16 +1880,16 @@ static int DetermineScriptLine(const int32_t *code, size_t codesz, size_t at_pc)
 	return line;
 }
 
-static void cc_error_fixups(const ccScript *scri, size_t pc, const char *fmt, ...) {
+static void cc_error_fixups(const ccScript *scri, const size_t pc, const char *fmt, ...) {
 	va_list ap;
 	va_start(ap, fmt);
-	String displbuf = String::FromFormatV(fmt, ap);
+	const String displbuf = String::FromFormatV(fmt, ap);
 	va_end(ap);
 	const char *scname = scri->numSections > 0 ? scri->sectionNames[0] : "?";
 	if (pc == SIZE_MAX) {
 		cc_error("in script %s: %s", scname, displbuf.GetCStr());
 	} else {
-		int line = DetermineScriptLine(scri->code, scri->codesize, pc);
+		const int line = DetermineScriptLine(scri->code, scri->codesize, pc);
 		cc_error("in script %s around line %d: %s", scname, line, displbuf.GetCStr());
 	}
 }
@@ -1961,7 +1961,7 @@ void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) {
 	registers[SREG_SP].RValue++;
 }
 
-void ccInstance::PushDataToStack(int32_t num_bytes) {
+void ccInstance::PushDataToStack(const int32_t num_bytes) {
 	CC_ERROR_IF(registers[SREG_SP].RValue->IsValid(), "internal error: valid data beyond stack ptr");
 	// Assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr
 	// NOTE: memory is zeroed by SCMD_ZEROMEMORY
@@ -1973,13 +1973,13 @@ void ccInstance::PushDataToStack(int32_t num_bytes) {
 RuntimeScriptValue ccInstance::PopValueFromStack() {
 	// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 	registers[SREG_SP].RValue--;
-	RuntimeScriptValue rval = *registers[SREG_SP].RValue; // save before invalidating
+	const RuntimeScriptValue rval = *registers[SREG_SP].RValue; // save before invalidating
 	stackdata_ptr -= sizeof(int32_t); // formality, to keep data ptr consistent
 	registers[SREG_SP].RValue->Invalidate(); // FIXME: bad, this is used to separate PushValue and PushData
 	return rval;
 }
 
-void ccInstance::PopValuesFromStack(int32_t num_entries = 1) {
+void ccInstance::PopValuesFromStack(const int32_t num_entries = 1) {
 	for (int i = 0; i < num_entries; ++i) {
 		// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
 		registers[SREG_SP].RValue--;
@@ -1988,7 +1988,7 @@ void ccInstance::PopValuesFromStack(int32_t num_entries = 1) {
 	}
 }
 
-void ccInstance::PopDataFromStack(int32_t num_bytes) {
+void ccInstance::PopDataFromStack(const int32_t num_bytes) {
 	int32_t total_pop = 0;
 	while (total_pop < num_bytes && registers[SREG_SP].RValue > &stack[0]) {
 		// rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail
@@ -2002,7 +2002,7 @@ void ccInstance::PopDataFromStack(int32_t num_bytes) {
 	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop, stack corrupted?");
 }
 
-RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) {
+RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(const int32_t rw_offset) {
 	int32_t total_off = 0;
 	RuntimeScriptValue *stack_entry = registers[SREG_SP].RValue;
 	while (total_off < rw_offset && stack_entry >= &stack[0]) {
diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h
index b99ccea90e9..1c058cb0e69 100644
--- a/engines/ags/engine/script/cc_instance.h
+++ b/engines/ags/engine/script/cc_instance.h
@@ -163,7 +163,7 @@ public:
 	static void FreeInstanceStack();
 	// create a runnable instance of the supplied script
 	static ccInstance *CreateFromScript(PScript script);
-	static ccInstance *CreateEx(PScript scri, ccInstance *joined);
+	static ccInstance *CreateEx(PScript scri, const ccInstance *joined);
 	static void SetExecTimeout(unsigned sys_poll_ms, unsigned abort_ms, unsigned abort_loops);
 
 	ccInstance();
@@ -200,7 +200,7 @@ public:
 	bool    ResolveImportFixups(const ccScript *scri);
 
 private:
-	bool    _Create(PScript scri, ccInstance *joined);
+	bool    _Create(PScript scri, const ccInstance *joined);
 	// free the memory associated with the instance
 	void    Free();
 


Commit: 5549e5c0c38f73b21046e69909f2e995d0206410
    https://github.com/scummvm/scummvm/commit/5549e5c0c38f73b21046e69909f2e995d0206410
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: cc_instance adjust for loop

>From upstream e60a7411a0909b3c1b5edbdde3c8f7bf746700b6

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index fd9860f03b5..74331cf83d4 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1490,12 +1490,11 @@ void ccInstance::GetScriptPosition(ScriptPosition &script_pos) const {
 
 // get a pointer to a variable or function exported by the script
 RuntimeScriptValue ccInstance::GetSymbolAddress(const char *symname) const {
-	int k;
 	char altName[200];
 	snprintf(altName, sizeof(altName), "%s$", symname);
 	RuntimeScriptValue rval_null;
 	const size_t len_altName = strlen(altName);
-	for (k = 0; k < instanceof->numexports; k++) {
+	for (int k = 0; k < instanceof->numexports; k++) {
 		if (strcmp(instanceof->exports[k], symname) == 0)
 			return exports[k];
 		// mangled function name


Commit: 4cdfd7ebba0bf03098c729b9e3ec24dfee09b7d6
    https://github.com/scummvm/scummvm/commit/4cdfd7ebba0bf03098c729b9e3ec24dfee09b7d6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in cc_instance, use static_cast where possible

>From upstream 13835765706f16affd1e2a43015a967a73fe04f3

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 74331cf83d4..0cd10a6e0ed 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -516,7 +516,7 @@ inline bool FixupArgument(RuntimeScriptValue &arg, const int fixup, const uintpt
 		// originally commented -- CHECKME: could this be used in very old versions of AGS?
 		//      code[fixup] += (long)&code[0];
 		// This is a program counter value, presumably will be used as SCMD_CALL argument
-		arg.SetInt32((int32_t)code);
+		arg.SetInt32(static_cast<int32_t>(code));
 		return true;
 	case FIXUP_STRING:
 		arg.SetStringLiteral(strings + code);
@@ -534,7 +534,7 @@ inline bool FixupArgument(RuntimeScriptValue &arg, const int fixup, const uintpt
 	case FIXUP_DATADATA:
 		return false; // placeholder, fail at this as not supposed to be here
 	case FIXUP_STACK:
-		arg = GetStackPtrOffsetFw(stack, (int32_t)code);
+		arg = GetStackPtrOffsetFw(stack, static_cast<int32_t>(code));
 		return true;
 	default:
 		cc_error("internal fixup type error: %d", fixup);
@@ -600,13 +600,13 @@ int ccInstance::Run(int32_t curpc) {
 
 		switch (codeOp.ArgCount) {
 		case 3:
-			codeOp.Args[2].SetInt32((int32_t)codeInst->code[pc + 3]);
+			codeOp.Args[2].SetInt32(static_cast<int32_t>(codeInst->code[pc + 3]));
 			/* fall-through */
 		case 2:
-			codeOp.Args[1].SetInt32((int32_t)codeInst->code[pc + 2]);
+			codeOp.Args[1].SetInt32(static_cast<int32_t>(codeInst->code[pc + 2]));
 			/* fall-through */
 		case 1:
-			codeOp.Args[0].SetInt32((int32_t)codeInst->code[pc + 1]);
+			codeOp.Args[0].SetInt32(static_cast<int32_t>(codeInst->code[pc + 1]));
 			break;
 		default:
 			break;
@@ -1168,7 +1168,7 @@ int ccInstance::Run(int32_t curpc) {
 			}
 			callAddr /= sizeof(intptr_t); // size of ccScript::code elements
 
-			if (Run((int32_t)callAddr))
+			if (Run(static_cast<int32_t>(callAddr)))
 				return -1;
 
 			runningInst = wasRunning;
@@ -1605,14 +1605,14 @@ bool ccInstance::_Create(PScript scri, const ccInstance *joined) {
 		globaldatasize = scri->globaldatasize;
 		globaldata = nullptr;
 		if (globaldatasize > 0) {
-			globaldata = (char *)malloc(globaldatasize);
+			globaldata = static_cast<char *>(malloc(globaldatasize));
 			memcpy(globaldata, scri->globaldata, globaldatasize);
 		}
 
 		codesize = scri->codesize;
 		code = nullptr;
 		if (codesize > 0) {
-			code = (intptr_t *)malloc(codesize * sizeof(intptr_t));
+			code = static_cast<intptr_t *>(malloc(codesize * sizeof(intptr_t)));
 			// 64 bit: Read code into 8 byte array, necessary for being able to perform
 			// relocations on the references.
 			for (int i = 0; i < codesize; ++i)
@@ -1669,7 +1669,7 @@ bool ccInstance::_Create(PScript scri, const ccInstance *joined) {
 		if (etype == EXPORT_FUNCTION) {
 			// NOTE: unfortunately, there seems to be no way to know if
 			// that's an extender function that expects object pointer
-			exports[i].SetCodePtr(((intptr_t)eaddr * sizeof(intptr_t) + reinterpret_cast<uint8_t *>(&code[0])));
+			exports[i].SetCodePtr((static_cast<intptr_t>(eaddr) * sizeof(intptr_t) + reinterpret_cast<uint8_t *>(&code[0])));
 		} else if (etype == EXPORT_DATA) {
 			ScriptVariable *gl_var = FindGlobalVar(eaddr);
 			if (gl_var) {
@@ -1910,9 +1910,9 @@ bool ccInstance::CreateRuntimeCodeFixups(const ccScript *scri) {
 		code_fixups[fixup] = scri->fixuptypes[i];
 		switch (scri->fixuptypes[i]) {
 		case FIXUP_GLOBALDATA: {
-			ScriptVariable *gl_var = FindGlobalVar((int32_t)code[fixup]);
+			ScriptVariable *gl_var = FindGlobalVar(static_cast<int32_t>(code[fixup]));
 			if (!gl_var) {
-				cc_error_fixups(scri, fixup, "cannot resolve global variable (bytecode pos %d, key %d)", fixup, (int32_t)code[fixup]);
+				cc_error_fixups(scri, fixup, "cannot resolve global variable (bytecode pos %d, key %d)", fixup, static_cast<int32_t>(code[fixup]));
 				return false;
 			}
 			code[fixup] = (intptr_t)gl_var;


Commit: 960bccaf865bd9192246cc9da2e88315f0a32074
    https://github.com/scummvm/scummvm/commit/960bccaf865bd9192246cc9da2e88315f0a32074
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: removed my_strncpy() and remade check_strlen() w/o global var

>From upstream 28d73ee1d4a8dfbb76c3d53ab5b62cfbed01bcd1

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/global_string.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/string.h
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 3a5e91885e9..cd66c22c94a 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -156,8 +156,8 @@ static bool File_ReadRawLineImpl(sc_File *fil, char *buffer, size_t buf_len) {
 }
 
 void File_ReadRawLine(sc_File *fil, char *buffer) {
-	check_strlen(buffer);
-	File_ReadRawLineImpl(fil, buffer, _G(MAXSTRLEN));
+	size_t buflen = check_strcapacity(buffer);
+	File_ReadRawLineImpl(fil, buffer, buflen);
 }
 
 const char *File_ReadRawLineBack(sc_File *fil) {
diff --git a/engines/ags/engine/ac/global_string.cpp b/engines/ags/engine/ac/global_string.cpp
index d0b928e7cf9..008a6a9d142 100644
--- a/engines/ags/engine/ac/global_string.cpp
+++ b/engines/ags/engine/ac/global_string.cpp
@@ -46,29 +46,27 @@ void StrSetCharAt(char *strin, int posn, int nchar) {
 }
 
 void _sc_strcat(char *s1, const char *s2) {
-	// make sure they don't try to append a char to the string
 	VALIDATE_STRING(s2);
-	check_strlen(s1);
-	int mosttocopy = (_G(MAXSTRLEN) - strlen(s1)) - 1;
-	my_strncpy(&s1[strlen(s1)], s2, mosttocopy);
+	size_t buflen = check_strcapacity(s1);
+	size_t s1_len = strlen(s1);
+	size_t buf_avail = (buflen - s1_len);
+	snprintf(s1 + s1_len, buf_avail, "%s", s2);
 }
 
 void _sc_strlower(char *desbuf) {
 	VALIDATE_STRING(desbuf);
-	check_strlen(desbuf);
 	ags_strlwr(desbuf);
 }
 
 void _sc_strupper(char *desbuf) {
 	VALIDATE_STRING(desbuf);
-	check_strlen(desbuf);
 	ags_strupr(desbuf);
 }
 
 void _sc_strcpy(char *destt, const char *text) {
 	VALIDATE_STRING(destt);
-	check_strlen(destt);
-	my_strncpy(destt, text, _G(MAXSTRLEN) - 1);
+	size_t buflen = check_strcapacity(destt);
+	snprintf(destt, buflen, "%s", text);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 2b9cde69fc2..48f5fe0b029 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -296,26 +296,15 @@ size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLi
 		return lines.Count();
 }
 
-void check_strlen(char *ptt) {
-	_G(MAXSTRLEN) = MAX_MAXSTRLEN;
-	long charstart = (intptr_t)&_GP(game).chars[0];
-	long charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
-	if (((intptr_t)&ptt[0] >= charstart) && ((intptr_t)&ptt[0] <= charend))
-		_G(MAXSTRLEN) = 30;
-}
-
-/*void GetLanguageString(int indxx,char*buffr) {
-VALIDATE_STRING(buffr);
-char*bptr=get_language_text(indxx);
-if (bptr==NULL) Common::strcpy_s(buffr, 200, "[language string error]");
-else strncpy(buffr,bptr,199);
-buffr[199]=0;
-}*/
-
-void my_strncpy(char *dest, const char *src, int len) {
-	// the normal strncpy pads out the string with zeros up to the
-	// max length -- we don't want that
-	Common::strcpy_s(dest, len + 1, src);
+// This is a somewhat ugly safety fix that tests whether the script tries
+// to write inside the Character's struct (e.g. char.name?), and truncates
+// the write limit accordingly.
+size_t check_strcapacity(char *ptt) {
+	uintptr_t charstart = (uintptr_t)&_GP(game).chars[0];
+	uintptr_t charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
+	if (((uintptr_t)&ptt[0] >= charstart) && ((uintptr_t)&ptt[0] <= charend))
+		return sizeof(CharacterInfo::name);
+	return MAX_MAXSTRLEN;
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index 79b236a7bd0..9bf1959dc9b 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -63,8 +63,11 @@ size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLi
 inline size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines = -1) {
 	return break_up_text_into_lines(todis, true, lines, wii, fonnt, max_lines);
 }
-void check_strlen(char *ptt);
-void my_strncpy(char *dest, const char *src, int len);
+// Checks the capacity of an old-style script string buffer.
+// Commonly this should return MAX_MAXSTRLEN, but there are
+// cases when the buffer is a field inside one of the game structs,
+// in which case this returns that field's capacity.
+size_t check_strcapacity(char *ptt);
 
 } // namespace AGS3
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index b2307428133..9f0f7167fda 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1313,8 +1313,6 @@ public:
 	 * @{
 	 */
 
-	int _MAXSTRLEN = MAX_MAXSTRLEN;
-
 	/**@}*/
 
 	/**


Commit: bdb34c01cc4e5c49d1b5d158fe75030778427d7e
    https://github.com/scummvm/scummvm/commit/bdb34c01cc4e5c49d1b5d158fe75030778427d7e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unrestricted scriptname and name in CharacterInfo

NOTE 1: we *must* keep the old fixed-sized fields for the time being,
 for compaibility with the plugin API. Would the plugin API be adjusted
  and have direct access to a character data struct removed, then these
   fields may be removed as well.

NOTE 2: we still read these old fields from game data, avoid changing the
 data format in the middle (in the sake of both backward and forward
 compatibility). These fixed-sized values are assigned to the character
  properties first. For unrestricted values we'll have to add a data extension,
   appended to the end of data format.
>From upstream  b02814b8bb3bb7a2cd00110466e2bd3047e772d1

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/global_audio.cpp
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/common_defines.h
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/game/room_file.cpp
    engines/ags/shared/util/file.cpp
    engines/ags/shared/util/file.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 41bf479d0e5..34ea77bb3b6 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -162,7 +162,7 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 	int move_speed_x, move_speed_y;
 	chaa->get_effective_walkspeeds(move_speed_x, move_speed_y);
 	if ((move_speed_x == 0) && (move_speed_y == 0)) {
-		debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname);
+		debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname.GetCStr());
 	}
 
 	// There's an issue: the existing movelist is converted to room resolution,
@@ -240,7 +240,7 @@ void Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, in
 		chaa->room = room;
 
 		debug_script_log("%s moved to room %d, location %d,%d, loop %d",
-		                 chaa->scrname, room, chaa->x, chaa->y, chaa->loop);
+		                 chaa->scrname.GetCStr(), room, chaa->x, chaa->y, chaa->loop);
 
 		return;
 	}
@@ -279,7 +279,7 @@ void Character_ChangeView(CharacterInfo *chap, int vii) {
 		chap->idleleft = chap->idletime;
 	}
 
-	debug_script_log("%s: Change view to %d", chap->scrname, vii + 1);
+	debug_script_log("%s: Change view to %d", chap->scrname.GetCStr(), vii + 1);
 	chap->defview = vii;
 	chap->view = vii;
 	stop_character_anim(chap);
@@ -378,7 +378,7 @@ void FaceDirectionalLoop(CharacterInfo *char1, int direction, int blockingStyle)
 }
 
 void FaceLocationXY(CharacterInfo *char1, int xx, int yy, int blockingStyle) {
-	debug_script_log("%s: Face location %d,%d", char1->scrname, xx, yy);
+	debug_script_log("%s: Face location %d,%d", char1->scrname.GetCStr(), xx, yy);
 
 	const int diffrx = xx - char1->x;
 	const int diffry = yy - char1->y;
@@ -439,9 +439,9 @@ void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int
 		quit("!FollowCharacterEx: you cannot tell the player character to follow a character in another room");
 
 	if (tofollow != nullptr) {
-		debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname, tofollow->scrname, distaway, eagerness);
+		debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname.GetCStr(), tofollow->scrname.GetCStr(), distaway, eagerness);
 	} else {
-		debug_script_log("%s: Stop following other character", chaa->scrname);
+		debug_script_log("%s: Stop following other character", chaa->scrname.GetCStr());
 	}
 
 	if ((chaa->following >= 0) &&
@@ -559,7 +559,7 @@ 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);
+	debug_script_log("%s: View locked to %d", chap->scrname.GetCStr(), vii + 1);
 	if (chap->idleleft < 0) {
 		Character_UnlockView(chap);
 		chap->idleleft = chap->idletime;
@@ -688,7 +688,7 @@ void Character_PlaceOnWalkableArea(CharacterInfo *chap) {
 void Character_RemoveTint(CharacterInfo *chaa) {
 
 	if (chaa->flags & (CHF_HASTINT | CHF_HASLIGHT)) {
-		debug_script_log("Un-tint %s", chaa->scrname);
+		debug_script_log("Un-tint %s", chaa->scrname.GetCStr());
 		chaa->flags &= ~(CHF_HASTINT | CHF_HASLIGHT);
 	} else {
 		debug_script_warn("Character.RemoveTint called but character was not tinted");
@@ -735,7 +735,7 @@ void Character_SetAsPlayer(CharacterInfo *chaa) {
 
 	//update_invorder();
 
-	debug_script_log("%s is new player character", _G(playerchar)->scrname);
+	debug_script_log("%s is new player character", _G(playerchar)->scrname.GetCStr());
 
 	// Within game_start, return now
 	if (_G(displayed_room) < 0)
@@ -787,9 +787,9 @@ void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime) {
 		chaa->wait = 0;
 
 	if (iview >= 1) {
-		debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname, iview, itime);
+		debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname.GetCStr(), iview, itime);
 	} else {
-		debug_script_log("%s idle view disabled", chaa->scrname);
+		debug_script_log("%s idle view disabled", chaa->scrname.GetCStr());
 	}
 	if (chaa->flags & CHF_FIXVIEW) {
 		debug_script_warn("SetCharacterIdle called while character view locked with SetCharacterView; idle ignored");
@@ -893,7 +893,7 @@ void Character_StopMoving(CharacterInfo *charp) {
 		if ((_GP(mls)[charp->walking].direct == 0) && (charp->room == _G(displayed_room)))
 			Character_PlaceOnWalkableArea(charp);
 
-		debug_script_log("%s: stop moving", charp->scrname);
+		debug_script_log("%s: stop moving", charp->scrname.GetCStr());
 
 		charp->idleleft = charp->idletime;
 		// restart the idle animation straight away
@@ -914,7 +914,7 @@ void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opaci
 	        (luminance < 0) || (luminance > 100))
 		quit("!Character.Tint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100");
 
-	debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname, red, green, blue, opacity);
+	debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname.GetCStr(), red, green, blue, opacity);
 
 	_GP(charextra)[chaa->index_id].tint_r = red;
 	_GP(charextra)[chaa->index_id].tint_g = green;
@@ -935,7 +935,7 @@ void Character_UnlockView(CharacterInfo *chaa) {
 
 void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) {
 	if (chaa->flags & CHF_FIXVIEW) {
-		debug_script_log("%s: Released view back to default", chaa->scrname);
+		debug_script_log("%s: Released view back to default", chaa->scrname.GetCStr());
 	}
 	chaa->flags &= ~CHF_FIXVIEW;
 	chaa->view = chaa->defview;
@@ -1345,11 +1345,13 @@ int Character_GetDestinationY(CharacterInfo *chaa) {
 }
 
 const char *Character_GetName(CharacterInfo *chaa) {
-	return CreateNewScriptString(chaa->name);
+	return CreateNewScriptString(chaa->name.GetCStr());
 }
 
 void Character_SetName(CharacterInfo *chaa, const char *newName) {
-	snprintf(chaa->name, MAX_CHAR_NAME_LEN, "%s", newName);
+	chaa->name = newName;
+	snprintf(chaa->legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", newName);
+
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 }
 
@@ -1604,7 +1606,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 
 	if ((tox == chin->x) && (toy == chin->y)) {
 		StopMoving(chac);
-		debug_script_log("%s already at destination, not moving", chin->scrname);
+		debug_script_log("%s already at destination, not moving", chin->scrname.GetCStr());
 		return;
 	}
 
@@ -1635,12 +1637,12 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 	chin->frame = oldframe;
 	// use toxPassedIn cached variable so the hi-res co-ordinates
 	// are still displayed as such
-	debug_script_log("%s: Start move to %d,%d", chin->scrname, tox, toy);
+	debug_script_log("%s: Start move to %d,%d", chin->scrname.GetCStr(), tox, toy);
 
 	int move_speed_x, move_speed_y;
 	chin->get_effective_walkspeeds(move_speed_x, move_speed_y);
 	if ((move_speed_x == 0) && (move_speed_y == 0)) {
-		debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname);
+		debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname.GetCStr());
 	}
 
 	// Convert src and dest coords to the mask resolution, for pathfinder
@@ -1839,7 +1841,7 @@ int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *che
 			chi->x = xwas;
 			chi->y = ywas;
 		}
-		debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname, _GP(game).chars[ntf].scrname);
+		debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname.GetCStr(), _GP(game).chars[ntf].scrname.GetCStr());
 		return 1;
 	}
 	return 0;
@@ -2058,12 +2060,12 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int n
 		(loopn < 0) || (loopn >= _GP(views)[chap->view].numLoops)) {
 		quitprintf("!AnimateCharacter: invalid view and/or loop\n"
 				   "(trying to animate '%s' using view %d (range is 1..%d) and loop %d (view has %d loops)).",
-				   chap->name, chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
+				   chap->scrname.GetCStr(), chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
 	}
 	// NOTE: there's always frame 0 allocated for safety
 	sframe = std::max(0, std::min(sframe, _GP(views)[chap->view].loops[loopn].numFrames - 1));
 	debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
-					 chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
+					 chap->scrname.GetCStr(), chap->view + 1, loopn, sppd, rept, sframe);
 
 	Character_StopMoving(chap);
 
@@ -2142,11 +2144,11 @@ void update_character_scale(int charid) {
 	CharacterExtras &chex = _GP(charextra)[charid];
 	if (chin.view < 0) {
 		quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
-				   chin.name, _G(displayed_room));
+				   chin.scrname.GetCStr(), _G(displayed_room));
 	}
 	if (chin.loop >= _GP(views)[chin.view].numLoops) {
 		quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
-				   chin.name, chin.loop, chin.view + 1);
+				   chin.scrname.GetCStr(), chin.loop, chin.view + 1);
 	}
 	// If frame is too high -- fallback to the frame 0;
 	// there's always at least 1 dummy frame at index 0
@@ -2466,11 +2468,11 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			charFrameWas = speakingChar->frame;
 
 		if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
-			quitprintf("!Character %s current view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
+			quitprintf("!Character %s current view %d is invalid, or has no loops.", speakingChar->scrname.GetCStr(), speakingChar->view + 1);
 		// If current view is missing a loop - use loop 0
 		if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
 			debug_script_warn("WARNING: Character %s current view %d does not have necessary loop %d; switching to loop 0.",
-							  speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
+							  speakingChar->scrname.GetCStr(), speakingChar->view + 1, speakingChar->loop);
 			speakingChar->loop = 0;
 		}
 
@@ -2706,11 +2708,11 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			speakingChar->flags |= CHF_FIXVIEW;
 
 			if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
-				quitprintf("!Character %s speech view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
+				quitprintf("!Character %s speech view %d is invalid, or has no loops.", speakingChar->scrname.GetCStr(), speakingChar->view + 1);
 			// If speech view is missing a loop - use loop 0
 			if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
 				debug_script_warn("WARNING: Character %s speech view %d does not have necessary loop %d; switching to loop 0.",
-								  speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
+								  speakingChar->scrname.GetCStr(), speakingChar->view + 1, speakingChar->loop);
 				speakingChar->loop = 0;
 			}
 
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 2bb9a6cebd8..02f40e2b010 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -85,7 +85,7 @@ void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, st
 			// view has no frames?!
 			// amazingly enough there are old games that allow this to happen...
 			if (_G(loaded_game_file_version) >= kGameVersion_300)
-				quitprintf("!Character %s is assigned view %d that has no frames!", name, view);
+				quitprintf("!Character %s is assigned view %d that has no frames!", scrname.GetCStr(), view);
 			loop = 0;
 		}
 	}
@@ -208,7 +208,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 		}
 
 		if (loop >= _GP(views)[view].numLoops)
-			quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, name, loop, view + 1);
+			quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, scrname.GetCStr(), loop, view + 1);
 
 		// check don't overflow loop
 		int framesInLoop = _GP(views)[view].loops[loop].numFrames;
@@ -219,7 +219,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 				frame = 0;
 
 			if (framesInLoop < 1)
-				quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, name, loop);
+				quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, scrname.GetCStr(), loop);
 		}
 
 		doing_nothing = 0; // still walking?
@@ -433,7 +433,7 @@ void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_noth
 		idleleft--;
 		if (idleleft == -1) {
 			int useloop = loop;
-			debug_script_log("%s: Now idle (view %d)", scrname, idleview + 1);
+			debug_script_log("%s: Now idle (view %d)", scrname.GetCStr(), idleview + 1);
 			Character_LockView(this, idleview + 1);
 			// SetCharView resets it to 0
 			idleleft = -2;
diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp
index f2fee97ec47..344d145c8f5 100644
--- a/engines/ags/engine/ac/global_audio.cpp
+++ b/engines/ags/engine/ac/global_audio.cpp
@@ -477,9 +477,9 @@ String get_cue_filename(int charid, int sndid) {
 	if (charid >= 0) {
 		// append the first 4 characters of the script name to the filename
 		if (_GP(game).chars[charid].scrname[0] == 'c')
-			script_name.SetString(&_GP(game).chars[charid].scrname[1], 4);
+			script_name.SetString(_GP(game).chars[charid].scrname.GetCStr() + 1, 4);
 		else
-			script_name.SetString(_GP(game).chars[charid].scrname, 4);
+			script_name.SetString(_GP(game).chars[charid].scrname.GetCStr(), 4);
 	} else {
 		script_name = "NARR";
 	}
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 94c2ce0534a..39530e83235 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -117,9 +117,9 @@ int GetCharacterWidth(int ww) {
 
 	if (_GP(charextra)[ww].width < 1) {
 		if ((char1->view < 0) ||
-		        (char1->loop >= _GP(views)[char1->view].numLoops) ||
-		        (char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
-			debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
+			(char1->loop >= _GP(views)[char1->view].numLoops) ||
+			(char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
+			debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname.GetCStr(), char1->view + 1, char1->loop, char1->frame);
 			return data_to_game_coord(4);
 		}
 
@@ -135,7 +135,8 @@ int GetCharacterHeight(int charid) {
 		if ((char1->view < 0) ||
 		        (char1->loop >= _GP(views)[char1->view].numLoops) ||
 		        (char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
-			debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
+			debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d",
+							  char1->scrname.GetCStr(), char1->view + 1, char1->loop, char1->frame);
 			return data_to_game_coord(2);
 		}
 
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 39b72efdc2e..fd95b536a6c 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -605,7 +605,7 @@ void GetLocationName(int xxx, int yyy, char *tempo) {
 	// on character
 	if (loctype == LOCTYPE_CHAR) {
 		onhs = _G(getloctype_index);
-		snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).chars[onhs].name));
+		snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).chars[onhs].name.GetCStr()));
 		if (_GP(play).get_loc_name_last_time != 2000 + onhs)
 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 		_GP(play).get_loc_name_last_time = 2000 + onhs;
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 8d3b2696099..e64287a4474 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -121,7 +121,7 @@ void InitAndRegisterCharacters(GameSetupStruct &game) {
 		ccRegisterManagedObject(&game.chars[i], &_GP(ccDynamicCharacter));
 
 		// export the character's script object
-		ccAddExternalScriptObject(game.chars[i].scrname, &game.chars[i], &_GP(ccDynamicCharacter));
+		ccAddExternalScriptObject(game.chars[i].scrname.GetCStr(), &game.chars[i], &_GP(ccDynamicCharacter));
 	}
 }
 
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index e3f4c27777b..265bb2f00bf 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -375,7 +375,7 @@ bool run_service_key_controls(KeyInput &out_key) {
 			ln = strlen(bigbuffer);
 			Common::sprintf_s(&bigbuffer[ln], sizeof(bigbuffer) - ln,
 			        "%s (view/loop/frm:%d,%d,%d  x/y/z:%d,%d,%d  idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[",
-			        _GP(game).chars[chd].scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
+			        _GP(game).chars[chd].legacy_scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
 			        _GP(game).chars[chd].x, _GP(game).chars[chd].y, _GP(game).chars[chd].z,
 			        _GP(game).chars[chd].idleview, _GP(game).chars[chd].idletime, _GP(game).chars[chd].idleleft,
 			        _GP(game).chars[chd].walking, _GP(game).chars[chd].animating, _GP(game).chars[chd].following,
diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 5eff4d45e88..53d53714fd2 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -75,13 +75,15 @@ void CharacterInfo::ReadFromFileImpl(Stream *in, GameDataVersion data_ver, int s
 	in->ReadArrayOfInt16(inv, MAX_INV);
 	actx = in->ReadInt16();
 	acty = in->ReadInt16();
-	StrUtil::ReadCStrCount(name, in, MAX_CHAR_NAME_LEN);
-	StrUtil::ReadCStrCount(scrname, in, MAX_SCRIPT_NAME_LEN);
+	StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CHAR_NAME_LEN);
+	StrUtil::ReadCStrCount(legacy_scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
 	on = in->ReadInt8();
 	if (do_align_pad)
 		in->ReadInt8(); // alignment padding to int32
 
 	// Upgrade data
+	name = legacy_name;
+	scrname = legacy_scrname;
 	if ((data_ver > kGameVersion_Undefined && data_ver < kGameVersion_360_16) ||
 		((data_ver == kGameVersion_Undefined) && save_ver >= 0 && save_ver < 2)) {
 		idle_anim_speed = animspeed + 5;
@@ -133,8 +135,8 @@ void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
 	out->WriteArrayOfInt16(inv, MAX_INV);
 	out->WriteInt16(actx);
 	out->WriteInt16(acty);
-	out->Write(name, 40);
-	out->Write(scrname, MAX_SCRIPT_NAME_LEN);
+	out->Write(legacy_name, LEGACY_MAX_CHAR_NAME_LEN);
+	out->Write(legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
 	out->WriteInt8(on);
 	if (do_align_pad)
 		out->WriteInt8(0); // alignment padding to int32
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 0810d5bcf3c..98135eb664c 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -27,6 +27,8 @@
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/core/types.h"
 #include "ags/shared/util/bbop.h"
+#include "ags/shared/util/string.h"
+
 
 namespace AGS3 {
 
@@ -86,7 +88,7 @@ inline int CharFlagsToObjFlags(int chflags) {
 }
 
 // Length of deprecated character name field, in bytes
-#define MAX_CHAR_NAME_LEN 40
+#define LEGACY_MAX_CHAR_NAME_LEN 40
 
 struct CharacterExtras; // forward declaration
 // IMPORTANT: exposed to script API, and plugin API as AGSCharacter!
@@ -126,10 +128,17 @@ struct CharacterInfo {
 	short walkspeed, animspeed;
 	short inv[MAX_INV];
 	short actx, acty;
-	char  name[MAX_CHAR_NAME_LEN];
-	char  scrname[MAX_SCRIPT_NAME_LEN];
+	// These two name fields are deprecated, but must stay here
+	// for compatibility with the plugin API (unless the plugin interface is reworked)
+	char legacy_name[LEGACY_MAX_CHAR_NAME_LEN];
+	char legacy_scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
+
 	int8  on;
 
+	AGS::Shared::String scrname;
+	AGS::Shared::String name;
+
+	int get_effective_y() const;   // return Y - Z
 	int get_baseline() const;      // return baseline, or Y if not set
 	int get_blocking_top() const;    // return Y - BlockingHeight/2
 	int get_blocking_bottom() const; // return Y + BlockingHeight/2
diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h
index 2181244565e..31c29817889 100644
--- a/engines/ags/shared/ac/common_defines.h
+++ b/engines/ags/shared/ac/common_defines.h
@@ -92,7 +92,7 @@ namespace AGS3 {
 #endif
 
 // Script name length limit for some game objects
-#define MAX_SCRIPT_NAME_LEN 20
+#define LEGACY_MAX_SCRIPT_NAME_LEN 20
 // Number of state-saved rooms
 #define MAX_ROOMS 300
 // Some obsolete room data, likely pre-2.5
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 5328c7b2d84..1ea98255afd 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -564,12 +564,14 @@ void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
 
 	// Fixup charakter script names for 2.x (EGO -> cEgo)
 	if (data_ver <= kGameVersion_272) {
-		String tempbuffer;
+		char namelwr[LEGACY_MAX_CHAR_NAME_LEN];
 		for (int i = 0; i < numcharacters; i++) {
-			if (chars[i].scrname[0] == 0)
+			if (chars[i].legacy_scrname[0] == 0)
 				continue;
-			tempbuffer.Format("c%c%s", chars[i].scrname[0], ags_strlwr(&chars[i].scrname[1]));
-			snprintf(chars[i].scrname, MAX_SCRIPT_NAME_LEN, "%s", tempbuffer.GetCStr());
+			memcpy(namelwr, chars[i].legacy_scrname, LEGACY_MAX_CHAR_NAME_LEN);
+			ags_strlwr(namelwr + 1); // lowercase starting with the second char
+			snprintf(chars[i].legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "c%s", namelwr);
+			chars[i].scrname = chars[i].legacy_scrname;
 		}
 	}
 
diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp
index 7ded7723de3..6e56ba72eff 100644
--- a/engines/ags/shared/game/room_file.cpp
+++ b/engines/ags/shared/game/room_file.cpp
@@ -127,7 +127,7 @@ HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 			if (data_ver >= kRoomVersion_3415)
 				room->Hotspots[i].ScriptName = StrUtil::ReadString(in);
 			else
-				room->Hotspots[i].ScriptName = String::FromStreamCount(in, MAX_SCRIPT_NAME_LEN);
+				room->Hotspots[i].ScriptName = String::FromStreamCount(in, LEGACY_MAX_SCRIPT_NAME_LEN);
 		}
 	}
 
@@ -357,7 +357,7 @@ HError ReadObjScNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ve
 		if (data_ver >= kRoomVersion_3415)
 			obj.ScriptName = StrUtil::ReadString(in);
 		else
-			obj.ScriptName.ReadCount(in, MAX_SCRIPT_NAME_LEN);
+			obj.ScriptName.ReadCount(in, LEGACY_MAX_SCRIPT_NAME_LEN);
 	}
 	return HError::None();
 }
diff --git a/engines/ags/shared/util/file.cpp b/engines/ags/shared/util/file.cpp
index 02e0dfcd830..742c8b30a74 100644
--- a/engines/ags/shared/util/file.cpp
+++ b/engines/ags/shared/util/file.cpp
@@ -89,14 +89,14 @@ bool File::DeleteFile(const String &filename) {
 	return g_system->getSavefileManager()->removeSavefile(file);
 }
 
-bool File::RenameFile(const String &old_name, const String &new_name) {
+bool File::RenameFile(const String &old_name, const String &name) {
 	// Only allow renaming files in the savegame folder
-	if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || new_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
-		warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), new_name.GetCStr());
+	if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
+		warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), name.GetCStr());
 		return false;
 	}
 	Common::String file_old(old_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
-	Common::String file_new(new_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	Common::String file_new(name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
 	return g_system->getSavefileManager()->renameSavefile(file_old, file_new);
 }
 
diff --git a/engines/ags/shared/util/file.h b/engines/ags/shared/util/file.h
index ba0cf6993e3..228f88230ee 100644
--- a/engines/ags/shared/util/file.h
+++ b/engines/ags/shared/util/file.h
@@ -69,7 +69,7 @@ bool        TestCreateFile(const String &filename);
 // Deletes existing file; returns TRUE if was able to delete one
 bool        DeleteFile(const String &filename);
 // Renames existing file to the new name; returns TRUE on success
-bool		RenameFile(const String &old_name, const String &new_name);
+bool		RenameFile(const String &old_name, const String &name);
 // Copies a file from src_path to dst_path; returns TRUE on success
 bool		CopyFile(const String &src_path, const String &dst_path, bool overwrite);
 


Commit: 478b46a34097e64f38652a557d49f0cbe89bb80b
    https://github.com/scummvm/scummvm/commit/478b46a34097e64f38652a557d49f0cbe89bb80b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unrestricted name in InventoryItemInfo

NOTE: we still read the old name field from game data, avoid changing
the data format in the middle (in the sake of both backward and forward
compatibility). These fixed-sized value is assigned to the item properties
first. For unrestricted value we'll have to add a data extension,
appended to the end of data format.
>From upstream 3d700e3c488945945affe51924047efb9561c8d0

Changed paths:
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/global_inventory_item.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/inventory_item.cpp
    engines/ags/shared/ac/inventory_item_info.cpp
    engines/ags/shared/ac/inventory_item_info.h


diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index fd95b536a6c..9211be01210 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -575,7 +575,7 @@ void GetLocationName(int xxx, int yyy, char *tempo) {
 			if (_GP(play).get_loc_name_last_time != 1000 + mover)
 				GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 			_GP(play).get_loc_name_last_time = 1000 + mover;
-			snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).invinfo[mover].name));
+			snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).invinfo[mover].name.GetCStr()));
 		} else if ((_GP(play).get_loc_name_last_time > 1000) && (_GP(play).get_loc_name_last_time < 1000 + MAX_INV)) {
 			// no longer selecting an item
 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
diff --git a/engines/ags/engine/ac/global_inventory_item.cpp b/engines/ags/engine/ac/global_inventory_item.cpp
index 5acdc0703c2..89606ede584 100644
--- a/engines/ags/engine/ac/global_inventory_item.cpp
+++ b/engines/ags/engine/ac/global_inventory_item.cpp
@@ -61,7 +61,7 @@ void SetInvItemName(int invi, const char *newName) {
 	if ((invi < 1) || (invi > _GP(game).numinvitems))
 		quit("!SetInvName: invalid inventory item specified");
 
-	snprintf(_GP(game).invinfo[invi].name, MAX_INVENTORY_NAME_LENGTH, "%s", newName);
+	_GP(game).invinfo[invi].name = newName;
 	// might need to redraw the GUI if it has the inv item name on it
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 }
@@ -85,7 +85,7 @@ int GetInvAt(int atx, int aty) {
 void GetInvName(int indx, char *buff) {
 	VALIDATE_STRING(buff);
 	if ((indx < 0) | (indx >= _GP(game).numinvitems)) quit("!GetInvName: invalid inventory item specified");
-	snprintf(buff, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).invinfo[indx].name));
+	snprintf(buff, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).invinfo[indx].name.GetCStr()));
 }
 
 int GetInvGraphic(int indx) {
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index c85ea665737..54ac655386f 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -255,7 +255,7 @@ int InventoryScreen::Redraw() {
 	}
 
 	for (int i = 0; i < _GP(charextra)[_GP(game).playercharacter].invorder_count; ++i) {
-		if (_GP(game).invinfo[_GP(charextra)[_GP(game).playercharacter].invorder[i]].name[0] != 0) {
+		if (!_GP(game).invinfo[_GP(charextra)[_GP(game).playercharacter].invorder[i]].name.IsEmpty()) {
 			dii[numitems].num = _GP(charextra)[_GP(game).playercharacter].invorder[i];
 			dii[numitems].sprnum = _GP(game).invinfo[_GP(charextra)[_GP(game).playercharacter].invorder[i]].pic;
 			int snn = dii[numitems].sprnum;
diff --git a/engines/ags/engine/ac/inventory_item.cpp b/engines/ags/engine/ac/inventory_item.cpp
index e12be61e4e0..bb1e0989bee 100644
--- a/engines/ags/engine/ac/inventory_item.cpp
+++ b/engines/ags/engine/ac/inventory_item.cpp
@@ -75,7 +75,7 @@ void InventoryItem_GetName(ScriptInvItem *iitem, char *buff) {
 }
 
 const char *InventoryItem_GetName_New(ScriptInvItem *invitem) {
-	return CreateNewScriptString(get_translation(_GP(game).invinfo[invitem->id].name));
+	return CreateNewScriptString(get_translation(_GP(game).invinfo[invitem->id].name.GetCStr()));
 }
 
 int InventoryItem_GetGraphic(ScriptInvItem *iitem) {
diff --git a/engines/ags/shared/ac/inventory_item_info.cpp b/engines/ags/shared/ac/inventory_item_info.cpp
index c34961bdad3..b1b3a799790 100644
--- a/engines/ags/shared/ac/inventory_item_info.cpp
+++ b/engines/ags/shared/ac/inventory_item_info.cpp
@@ -28,7 +28,7 @@ namespace AGS3 {
 using namespace AGS::Shared;
 
 void InventoryItemInfo::ReadFromFile(Stream *in) {
-	StrUtil::ReadCStrCount(name, in, MAX_INVENTORY_NAME_LENGTH);
+	name.ReadCount(in, LEGACY_MAX_INVENTORY_NAME_LENGTH);
 	in->Seek(3); // alignment padding to int32
 	pic = in->ReadInt32();
 	cursorPic = in->ReadInt32();
@@ -40,7 +40,7 @@ void InventoryItemInfo::ReadFromFile(Stream *in) {
 }
 
 void InventoryItemInfo::WriteToFile(Stream *out) {
-	out->Write(name, MAX_INVENTORY_NAME_LENGTH);
+	name.WriteCount(out, LEGACY_MAX_INVENTORY_NAME_LENGTH);
 	out->WriteByteCount(0, 3); // alignment padding to int32
 	out->WriteInt32(pic);
 	out->WriteInt32(cursorPic);
@@ -52,7 +52,7 @@ void InventoryItemInfo::WriteToFile(Stream *out) {
 }
 
 void InventoryItemInfo::ReadFromSavegame(Stream *in) {
-	StrUtil::ReadString(name, in, 25);
+	name = StrUtil::ReadString(in);
 	pic = in->ReadInt32();
 	cursorPic = in->ReadInt32();
 }
diff --git a/engines/ags/shared/ac/inventory_item_info.h b/engines/ags/shared/ac/inventory_item_info.h
index 39e298d5e8c..b1f9fb23b30 100644
--- a/engines/ags/shared/ac/inventory_item_info.h
+++ b/engines/ags/shared/ac/inventory_item_info.h
@@ -23,6 +23,7 @@
 #define AGS_SHARED_AC_INVENTORY_ITEM_INFO_H
 
 #include "ags/shared/core/types.h"
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
@@ -32,22 +33,22 @@ class Stream;
 } // namespace Shared
 } // namespace AGS
 
-using namespace AGS; // FIXME later
+using namespace AGS::Shared;
 
 #define IFLG_STARTWITH 1
-#define MAX_INVENTORY_NAME_LENGTH 25
+#define LEGACY_MAX_INVENTORY_NAME_LENGTH 25
 
 struct InventoryItemInfo {
-	char name[MAX_INVENTORY_NAME_LENGTH];
+	String name;
 	int  pic;
 	int  cursorPic, hotx, hoty;
 	int32_t reserved[5];
-	int8 flags;
+	uint8_t flags;  // IFLG_STARTWITH
 
-	void ReadFromFile(Shared::Stream *in);
-	void WriteToFile(Shared::Stream *out);
-	void ReadFromSavegame(Shared::Stream *in);
-	void WriteToSavegame(Shared::Stream *out) const;
+	void ReadFromFile(Stream *in);
+	void WriteToFile(Stream *out);
+	void ReadFromSavegame(Stream *in);
+	void WriteToSavegame(Stream *out) const;
 };
 
 } // namespace AGS3


Commit: 98e59c7a93cca5aebdf950ccc2470ceb0307b081
    https://github.com/scummvm/scummvm/commit/98e59c7a93cca5aebdf950ccc2470ceb0307b081
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unrestricted script name in MouseCursor

NOTE: we still read the old script name field from game data, avoid changing
the data format in the middle (in the sake of both backward and forward compatibility).
These fixed-sized value is assigned to the item properties first.
For unrestricted value we'll have to add a data extension, appended to the end of data format.
>From upstream cda044466a5a79d61f7b536d0b4e17f073d9bfd9

Changed paths:
    engines/ags/shared/ac/mouse_cursor.cpp
    engines/ags/shared/ac/mouse_cursor.h


diff --git a/engines/ags/shared/ac/mouse_cursor.cpp b/engines/ags/shared/ac/mouse_cursor.cpp
index 7d265ba82a4..4628bbddd77 100644
--- a/engines/ags/shared/ac/mouse_cursor.cpp
+++ b/engines/ags/shared/ac/mouse_cursor.cpp
@@ -32,7 +32,7 @@ void MouseCursor::clear() {
 	pic = 0;
 	hotx = hoty = 0;
 	view = -1;
-	Common::fill(&name[0], &name[10], '\0');
+	name.Empty();
 	flags = 0;
 }
 
@@ -41,9 +41,11 @@ void MouseCursor::ReadFromFile(Stream *in) {
 	hotx = in->ReadInt16();
 	hoty = in->ReadInt16();
 	view = in->ReadInt16();
-	StrUtil::ReadCStrCount(name, in, MAX_CURSOR_NAME_LENGTH);
+	StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CURSOR_NAME_LENGTH);
 	flags = in->ReadInt8();
 	in->Seek(3); // alignment padding to int32
+
+	name = legacy_name;
 }
 
 void MouseCursor::WriteToFile(Stream *out) {
@@ -51,7 +53,7 @@ void MouseCursor::WriteToFile(Stream *out) {
 	out->WriteInt16(hotx);
 	out->WriteInt16(hoty);
 	out->WriteInt16(view);
-	out->Write(name, MAX_CURSOR_NAME_LENGTH);
+	out->Write(legacy_name, LEGACY_MAX_CURSOR_NAME_LENGTH);
 	out->WriteInt8(flags);
 	out->WriteByteCount(0, 3); // alignment padding to int32
 }
diff --git a/engines/ags/shared/ac/mouse_cursor.h b/engines/ags/shared/ac/mouse_cursor.h
index 237e9bdf57e..7dd49d8b086 100644
--- a/engines/ags/shared/ac/mouse_cursor.h
+++ b/engines/ags/shared/ac/mouse_cursor.h
@@ -23,6 +23,7 @@
 #define AGS_SHARED_AC_MOUSE_CURSOR_H
 
 #include "ags/shared/core/types.h"
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
@@ -39,24 +40,27 @@ using namespace AGS; // FIXME later
 #define MCF_STANDARD 4
 #define MCF_HOTSPOT  8  // only animate when over hotspot
 
+#define LEGACY_MAX_CURSOR_NAME_LENGTH 10
+
 enum CursorSvgVersion {
 	kCursorSvgVersion_Initial = 0,
 	kCursorSvgVersion_36016 = 1, // animation delay
 };
 
-#define MAX_CURSOR_NAME_LENGTH 10
-
 // IMPORTANT: exposed to plugin API as AGSCursor!
 // do not change topmost fields, unless planning breaking compatibility.
 struct MouseCursor {
 	int   pic = 0;
 	short hotx = 0, hoty = 0;
 	short view = -1;
-	char  name[MAX_CURSOR_NAME_LENGTH]{};
-	char  flags = 0;
+	// This is a deprecated name field, but must stay here for compatibility
+	// with the plugin API (unless the plugin interface is reworked)
+	char legacy_name[LEGACY_MAX_CURSOR_NAME_LENGTH]{};
+	char flags = 0;
 
-	// up to here is a part of plugin API
-	int   animdelay = 5;
+	// Following fields are not part of the plugin API
+	Shared::String name;
+	int animdelay = 5;
 
 	MouseCursor() {}
 


Commit: 103ee6cb8759761be72d7d477924839d5af5cb04
    https://github.com/scummvm/scummvm/commit/103ee6cb8759761be72d7d477924839d5af5cb04
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unrestricted script name an filename in ScriptAudioClip

NOTE: we still read the old fields from game data, avoid changing the
data format in the middle (in the sake of both backward and forward compatibility).
These fixed-sized values are assigned to the clip properties first.
For unrestricted values we'll have to add a data extension, appended to the end of data format.
>From upstream 0aa62d14cadcb77dfcf1351a962969069c5d1796

Changed paths:
    engines/ags/shared/ac/dynobj/script_audio_clip.cpp
    engines/ags/shared/ac/dynobj/script_audio_clip.h


diff --git a/engines/ags/shared/ac/dynobj/script_audio_clip.cpp b/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
index d89aab6edf3..5d7d154afb5 100644
--- a/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
+++ b/engines/ags/shared/ac/dynobj/script_audio_clip.cpp
@@ -28,8 +28,8 @@ using namespace AGS::Shared;
 
 void ScriptAudioClip::ReadFromFile(Stream *in) {
 	id = in->ReadInt32();
-	scriptName.ReadCount(in, SCRIPTAUDIOCLIP_SCRIPTNAMELENGTH);
-	fileName.ReadCount(in, SCRIPTAUDIOCLIP_FILENAMELENGTH);
+	scriptName.ReadCount(in, LEGACY_AUDIOCLIP_SCRIPTNAMELENGTH);
+	fileName.ReadCount(in, LEGACY_AUDIOCLIP_FILENAMELENGTH);
 	bundlingType = static_cast<uint8_t>(in->ReadInt8());
 	type = static_cast<uint8_t>(in->ReadInt8());
 	fileType = static_cast<AudioFileType>(in->ReadInt8());
diff --git a/engines/ags/shared/ac/dynobj/script_audio_clip.h b/engines/ags/shared/ac/dynobj/script_audio_clip.h
index 28777ef5b61..a1127e5ff9f 100644
--- a/engines/ags/shared/ac/dynobj/script_audio_clip.h
+++ b/engines/ags/shared/ac/dynobj/script_audio_clip.h
@@ -46,8 +46,8 @@ enum AudioFileType {
 #define AUCL_BUNDLE_EXE 1
 #define AUCL_BUNDLE_VOX 2
 
-#define SCRIPTAUDIOCLIP_SCRIPTNAMELENGTH    30
-#define SCRIPTAUDIOCLIP_FILENAMELENGTH      15
+#define LEGACY_AUDIOCLIP_SCRIPTNAMELENGTH	30
+#define LEGACY_AUDIOCLIP_FILENAMELENGTH		15
 
 struct ScriptAudioClip {
 	int id = 0;


Commit: 8d6ba11854011556125dcc47bc385fc265a0d684
    https://github.com/scummvm/scummvm/commit/8d6ba11854011556125dcc47bc385fc265a0d684
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: add unrestricted Character name to saves

NOTE: appended this new field to the end of character save chunk for consistency.
But because the field itself is in the CharacterInfo struct, I have to pass CharacterInfo
reference into CharacterExtra::ReadFromSavegame().
This is ugly, but but should be improved when the structs are refactored into having
a distinct runtime Character class.
>From upstream 5cedf554b211957d7b413a4ef7608f0748384b0e

Changed paths:
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/character_info.h


diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index 9c34d006c82..820bbb7ff05 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -22,10 +22,11 @@
 #include "ags/engine/ac/character_extras.h"
 #include "ags/engine/ac/view_frame.h"
 #include "ags/shared/util/stream.h"
+#include "ags/shared/util/string_utils.h"
 
 namespace AGS3 {
 
-using AGS::Shared::Stream;
+using namespace AGS::Shared;
 
 int CharacterExtras::GetEffectiveY(CharacterInfo *chi) const {
 	return chi->y - (chi->z * zoom_offs) / 100;
@@ -41,7 +42,7 @@ void CharacterExtras::CheckViewFrame(CharacterInfo *chi) {
 	AGS3::CheckViewFrame(chi->view, chi->loop, chi->frame, GetFrameSoundVolume(chi));
 }
 
-void CharacterExtras::ReadFromSavegame(Stream *in, int save_ver) {
+void CharacterExtras::ReadFromSavegame(Stream *in, CharacterInfo &chinfo, int save_ver) {
 	in->ReadArrayOfInt16(invorder, MAX_INVORDER);
 	invorder_count = in->ReadInt16();
 	width = in->ReadInt16();
@@ -57,16 +58,23 @@ void CharacterExtras::ReadFromSavegame(Stream *in, int save_ver) {
 	process_idle_this_time = in->ReadInt8();
 	slow_move_counter = in->ReadInt8();
 	animwait = in->ReadInt16();
-	if (save_ver >= kCharSvgVersion_36025)
-	{
+	if (save_ver >= kCharSvgVersion_36025) {
 		anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		cur_anim_volume = static_cast<uint8_t>(in->ReadInt8());
 		in->ReadInt8(); // reserved to fill int32
 		in->ReadInt8();
 	}
+	if (save_ver >= kCharSvgVersion_36114) {
+		chinfo.name = StrUtil::ReadString(in);
+	}
+
+	// Upgrade restored data
+	if (save_ver < kCharSvgVersion_36025) {
+		chinfo.idle_anim_speed = chinfo.animspeed + 5;
+	}
 }
 
-void CharacterExtras::WriteToSavegame(Stream *out) {
+void CharacterExtras::WriteToSavegame(Stream *out, const CharacterInfo &chinfo) {
 	out->WriteArrayOfInt16(invorder, MAX_INVORDER);
 	out->WriteInt16(invorder_count);
 	out->WriteInt16(width);
@@ -86,6 +94,8 @@ void CharacterExtras::WriteToSavegame(Stream *out) {
 	out->WriteInt8(static_cast<uint8_t>(cur_anim_volume));
 	out->WriteInt8(0); // reserved to fill int32
 	out->WriteInt8(0);
+	// kCharSvgVersion_36114
+	StrUtil::WriteString(chinfo.name, out);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index f67f2a1f445..c383c89fe81 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -40,6 +40,7 @@ enum CharacterSvgVersion {
 	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
 	kCharSvgVersion_36025   = 2, // animation volume
 	kCharSvgVersion_36109   = 3, // removed movelists, save externally
+	kCharSvgVersion_36114	= 4, // no limit on character name's length
 };
 
 // The CharacterInfo struct size is fixed because it's exposed to script
@@ -79,8 +80,13 @@ struct CharacterExtras {
 	// play linked sounds, and so forth.
 	void CheckViewFrame(CharacterInfo *chi);
 
-	void ReadFromSavegame(Shared::Stream *in, int save_ver);
-	void WriteToSavegame(Shared::Stream *out);
+	// Read character extra data from saves.
+	// NOTE: we read ext name fields into the CharacterInfo struct,
+	// hence require its reference as an argument. This is ugly, but should
+	// be improved when the structs are refactored into having a distinct
+	// runtime Character class, which would hold all relevant data itself.
+	void ReadFromSavegame(Shared::Stream *in, CharacterInfo &chinfo, int save_ver);
+	void WriteToSavegame(Shared::Stream *out, const CharacterInfo &chinfo);
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 0463e624219..75097b43352 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -488,7 +488,7 @@ HSaveError WriteCharacters(Stream *out) {
 	out->WriteInt32(_GP(game).numcharacters);
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
 		_GP(game).chars[i].WriteToSavegame(out);
-		_GP(charextra)[i].WriteToSavegame(out);
+		_GP(charextra)[i].WriteToSavegame(out, _GP(game).chars[i]);
 		Properties::WriteValues(_GP(play).charProps[i], out);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			WriteTimesRun272(*_GP(game).intrChar[i], out);
@@ -501,8 +501,8 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].ReadFromSavegame(in, cmp_ver);
-		_GP(charextra)[i].ReadFromSavegame(in, cmp_ver);
+		_GP(game).chars[i].ReadFromSavegame(in);
+		_GP(charextra)[i].ReadFromSavegame(in, _GP(game).chars[i], cmp_ver);
 		Properties::ReadValues(_GP(play).charProps[i], in);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
@@ -1081,7 +1081,7 @@ struct ComponentHandlers {
 		},
 		{
 			"Characters",
-			kCharSvgVersion_36109,
+			kCharSvgVersion_36114,
 			kCharSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteCharacters,
 			ReadCharacters
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 86533e8b854..22831548bdb 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -454,7 +454,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 
 	// Character extras (runtime only data)
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(charextra)[i].ReadFromSavegame(in, 0);
+		_GP(charextra)[i].ReadFromSavegame(in, _GP(game).chars[i], kCharSvgVersion_Initial);
 	}
 	restore_game_palette(in);
 	restore_game_dialogs(in);
diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 53d53714fd2..027454bd3ff 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -30,8 +30,8 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-void CharacterInfo::ReadFromFileImpl(Stream *in, GameDataVersion data_ver, int save_ver) {
-	const bool do_align_pad = data_ver != kGameVersion_Undefined;
+void CharacterInfo::ReadFromFileImpl(Stream *in, bool is_save) {
+	const bool do_align_pad = !is_save;
 	defview = in->ReadInt32();
 	talkview = in->ReadInt32();
 	view = in->ReadInt32();
@@ -80,14 +80,6 @@ void CharacterInfo::ReadFromFileImpl(Stream *in, GameDataVersion data_ver, int s
 	on = in->ReadInt8();
 	if (do_align_pad)
 		in->ReadInt8(); // alignment padding to int32
-
-	// Upgrade data
-	name = legacy_name;
-	scrname = legacy_scrname;
-	if ((data_ver > kGameVersion_Undefined && data_ver < kGameVersion_360_16) ||
-		((data_ver == kGameVersion_Undefined) && save_ver >= 0 && save_ver < 2)) {
-		idle_anim_speed = animspeed + 5;
-	}
 }
 
 void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
@@ -143,15 +135,24 @@ void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
 }
 
 void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver) {
-	ReadFromFileImpl(in, data_ver, -1);
+	ReadFromFileImpl(in, false);
+
+	// Assign names from legacy fields
+	name = legacy_name;
+	scrname = legacy_scrname;
+
+	// Upgrade data
+	if (data_ver < kGameVersion_360_16) {
+		idle_anim_speed = animspeed + 5;
+	}
 }
 
 void CharacterInfo::WriteToFile(Stream *out) const {
 	WriteToFileImpl(out, false);
 }
 
-void CharacterInfo::ReadFromSavegame(Stream *in, int save_ver) {
-	ReadFromFileImpl(in, kGameVersion_Undefined, save_ver);
+void CharacterInfo::ReadFromSavegame(Stream *in) {
+	ReadFromFileImpl(in, true);
 }
 
 void CharacterInfo::WriteToSavegame(Stream *out) const {
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 98135eb664c..b307bb8b41e 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -193,12 +193,12 @@ struct CharacterInfo {
 	void ReadFromFile(Shared::Stream *in, GameDataVersion data_ver);
 	void WriteToFile(Shared::Stream *out) const;
 	// TODO: move to runtime-only class (?)
-	void ReadFromSavegame(Shared::Stream *in, int save_ver);
+	void ReadFromSavegame(Shared::Stream *in);
 	void WriteToSavegame(Shared::Stream *out) const;
 
 private:
 	// TODO: this is likely temp here until runtime class is factored out
-	void ReadFromFileImpl(Shared::Stream *in, GameDataVersion data_ver, int save_ver);
+	void ReadFromFileImpl(Shared::Stream *in, bool is_save);
 	void WriteToFileImpl(Shared::Stream *out, bool is_save) const;
 };
 


Commit: ead8bd6d57567803c336e95f4118a0438724d7aa
    https://github.com/scummvm/scummvm/commit/ead8bd6d57567803c336e95f4118a0438724d7aa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: added game data extension for unrestricted object names

Extension "v361_objnames" contains scriptnames and descriptions for name
 fields that had fixed length in legacy format:
- Character script name and name;
- InventoryItem name;
- Mouse cursor's script name;
- Audioclip's script name and filename.

>From upstream b7024c0c284d65a6cb1b7a0a8594409a0e1cd3b9

Changed paths:
    engines/ags/shared/ac/game_version.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/ac/game_version.h b/engines/ags/shared/ac/game_version.h
index dfc965901d3..5535689876f 100644
--- a/engines/ags/shared/ac/game_version.h
+++ b/engines/ags/shared/ac/game_version.h
@@ -126,6 +126,8 @@ Some adjustments to gui text alignment.
 In RTL mode all text is reversed, not only wrappable (labels etc).
 3.6.1.10:
 Disabled automatic SetRestartPoint.
+3.6.1.14:
+Extended game object names, resolving hard length limits.
 */
 
 enum GameDataVersion {
@@ -164,7 +166,8 @@ enum GameDataVersion {
 	kGameVersion_360_21 = 3060021,
 	kGameVersion_361 = 3060100,
 	kGameVersion_361_10 = 3060110,
-	kGameVersion_Current = kGameVersion_361_10
+	kGameVersion_361_14 = 3060114,
+	kGameVersion_Current = kGameVersion_361_14
 };
 
 } // namespace AGS3
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 1ea98255afd..2384c4f04cb 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -720,30 +720,60 @@ HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
     //     // read new gui properties
     // }
 	if (ext_id.CompareNoCase("v360_fonts") == 0) {
-		for (int i = 0; i < _ents.Game.numfonts; ++i) {
+		for (FontInfo &finfo : _ents.Game.fonts) {
 			// adjustable font outlines
-			_ents.Game.fonts[i].AutoOutlineThickness = _in->ReadInt32();
-			_ents.Game.fonts[i].AutoOutlineStyle =
-				static_cast<enum FontInfo::AutoOutlineStyle>(_in->ReadInt32());
+			finfo.AutoOutlineThickness = _in->ReadInt32();
+			finfo.AutoOutlineStyle = static_cast<enum FontInfo::AutoOutlineStyle>(_in->ReadInt32());
 			// reserved
 			_in->ReadInt32();
 			_in->ReadInt32();
 			_in->ReadInt32();
 			_in->ReadInt32();
 		}
-		return HError::None();
 	} else if (ext_id.CompareNoCase("v360_cursors") == 0) {
-		for (int i = 0; i < _ents.Game.numcursors; ++i) {
-			_ents.Game.mcurs[i].animdelay = _in->ReadInt32();
+		for (MouseCursor &mcur : _ents.Game.mcurs) {
+			mcur.animdelay = _in->ReadInt32();
 			// reserved
 			_in->ReadInt32();
 			_in->ReadInt32();
 			_in->ReadInt32();
 		}
-		return HError::None();
+	} else if (ext_id.CompareNoCase("v361_objnames") == 0) {
+		// Extended object names and script names:
+		// for object types that had hard name length limits
+		size_t num_chars = _in->ReadInt32();
+		if (num_chars != _ents.Game.chars.size())
+			return new Error(String::FromFormat("Mismatching number of characters: read %zu expected %zu", num_chars, _ents.Game.chars.size()));
+		for (CharacterInfo &chinfo : _ents.Game.chars) {
+			chinfo.scrname = StrUtil::ReadString(_in);
+			chinfo.name = StrUtil::ReadString(_in);
+			// assign to the legacy fields for compatibility with old plugins
+			snprintf(chinfo.legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "%s", chinfo.scrname.GetCStr());
+			snprintf(chinfo.legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo.name.GetCStr());
+		}
+		size_t num_invitems = _in->ReadInt32();
+		if (num_invitems != _ents.Game.numinvitems)
+			return new Error(String::FromFormat("Mismatching number of inventory items: read %zu expected %zu", num_invitems, (size_t)_ents.Game.numinvitems));
+		for (int i = 0; i < _ents.Game.numinvitems; ++i) {
+			_ents.Game.invinfo[i].name = StrUtil::ReadString(_in);
+		}
+		size_t num_cursors = _in->ReadInt32();
+		if (num_cursors != _ents.Game.mcurs.size())
+			return new Error(String::FromFormat("Mismatching number of cursors: read %zu expected %zu", num_cursors, _ents.Game.mcurs.size()));
+		for (MouseCursor &mcur : _ents.Game.mcurs) {
+			mcur.name = StrUtil::ReadString(_in);
+		}
+		size_t num_clips = _in->ReadInt32();
+		if (num_clips != _ents.Game.audioClips.size())
+			return new Error(String::FromFormat("Mismatching number of audio clips: read %zu expected %zu", num_clips, _ents.Game.audioClips.size()));
+		for (ScriptAudioClip &clip : _ents.Game.audioClips) {
+			clip.scriptName = StrUtil::ReadString(_in);
+			clip.fileName = StrUtil::ReadString(_in);
+		}
+	} else {
+		return new MainGameFileError(kMGFErr_ExtUnknown, String::FromFormat("Type: %s", ext_id.GetCStr()));
 	}
-
-    return new MainGameFileError(kMGFErr_ExtUnknown, String::FromFormat("Type: %s", ext_id.GetCStr()));
+	return HError::None();
 }
 
 HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) {


Commit: e158764267a1a6ef1b76831f376effa92fbc816e
    https://github.com/scummvm/scummvm/commit/e158764267a1a6ef1b76831f376effa92fbc816e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: unrestricted game name and save folder name

>From upstream 3b06711da90621b2ee5ba33ff82da91f797cabbb

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct.h
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_setup_struct_base.h
    engines/ags/shared/ac/game_struct_defines.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index acc5f33a303..8495bde8426 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -568,8 +568,8 @@ const char *Game_GetName() {
 }
 
 void Game_SetName(const char *newName) {
-	snprintf(_GP(play).game_name, MAX_GAME_STATE_NAME_LENGTH, "%s", newName);
-	sys_window_set_title(_GP(play).game_name);
+	_GP(play).game_name = newName;
+	sys_window_set_title(_GP(play).game_name.GetCStr());
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Gamename);
 }
 
diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 30af4575e32..cddc98de7ba 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -595,7 +595,10 @@ void GameState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameState
 	in->Read(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN);
 	in->Read(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN);
 	in->Read(lastParserEntry, MAX_MAXSTRLEN);
-	StrUtil::ReadCStrCount(game_name, in, MAX_GAME_STATE_NAME_LENGTH);
+	if (svg_ver < kGSSvgVersion_361_14)
+		game_name.ReadCount(in, LEGACY_GAMESTATE_GAMENAMELENGTH);
+	else
+		game_name = StrUtil::ReadString(in);
 	ground_level_areas_disabled = in->ReadInt32();
 	next_screen_transition = in->ReadInt32();
 	in->ReadInt32(); // gamma_adjustment -- do not apply gamma level from savegame
@@ -786,7 +789,7 @@ void GameState::WriteForSavegame(Stream *out) const {
 	out->Write(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN);
 	out->Write(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN);
 	out->Write(lastParserEntry, MAX_MAXSTRLEN);
-	out->Write(game_name, MAX_GAME_STATE_NAME_LENGTH);
+	StrUtil::WriteString(game_name, out);
 	out->WriteInt32(ground_level_areas_disabled);
 	out->WriteInt32(next_screen_transition);
 	out->WriteInt32(gamma_adjustment);
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index b6550553061..a03f3dca3d7 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -60,6 +60,7 @@ struct ScriptOverlay;
 
 #define MAX_GAME_STATE_NAME_LENGTH 100
 #define GAME_STATE_RESERVED_INTS 5
+#define LEGACY_GAMESTATE_GAMENAMELENGTH 100
 
 // Savegame data format
 enum GameStateSvgVersion {
@@ -68,6 +69,7 @@ enum GameStateSvgVersion {
 	kGSSvgVersion_350 = 1,
 	kGSSvgVersion_350_9 = 2,
 	kGSSvgVersion_350_10 = 3,
+	kGSSvgVersion_361_14 = 4,
 };
 
 
@@ -246,7 +248,7 @@ struct GameState {
 	char  playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN]{};
 	char  globalstrings[MAXGLOBALSTRINGS][MAX_MAXSTRLEN]{};
 	char  lastParserEntry[MAX_MAXSTRLEN]{};
-	char  game_name[MAX_GAME_STATE_NAME_LENGTH]{};
+	AGS::Shared::String game_name;
 	int   ground_level_areas_disabled = 0;
 	int   next_screen_transition = 0;
 	int   gamma_adjustment = 0;
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 16af4dad80f..1291d64891a 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -391,7 +391,7 @@ void replace_macro_tokens(const char *text, String &fixed_text) {
 			else if (ags_stricmp(macroname, "scoretext") == 0)
 				snprintf(tempo, sizeof(tempo), "%d of %d", _GP(play).score, MAXSCORE);
 			else if (ags_stricmp(macroname, "gamename") == 0)
-				snprintf(tempo, sizeof(tempo), "%s", _GP(play).game_name);
+				snprintf(tempo, sizeof(tempo), "%s", _GP(play).game_name.GetCStr());
 			else if (ags_stricmp(macroname, "overhotspot") == 0) {
 				// While game is in Wait mode, no overhotspot text
 				if (!IsInterfaceEnabled())
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 566be868807..5a8147dc548 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -517,7 +517,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 	// ensure that the current cursor is locked
 	_GP(spriteset).Precache(_GP(game).mcurs[r_data.CursorID].pic);
 
-	sys_window_set_title(_GP(play).game_name);
+	sys_window_set_title(_GP(play).game_name.GetCStr());
 
 	if (_G(displayed_room) >= 0) {
 		// Fixup the frame index, in case the restored room does not have enough background frames
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 75097b43352..7747d1eeb6e 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -1067,7 +1067,7 @@ struct ComponentHandlers {
 	const ComponentHandler _items[18] = {
 		{
 			"Game State",
-			kGSSvgVersion_350_10,
+			kGSSvgVersion_361_14,
 			kGSSvgVersion_Initial,
 			WriteGameState,
 			ReadGameState
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 2b03801aff4..b7b5fbbdcc6 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -114,7 +114,7 @@ void engine_setup_window() {
 	Debug::Printf(kDbgMsg_Info, "Setting up window");
 
 	_G(our_eip) = -198;
-	sys_window_set_title(_GP(game).gamename);
+	sys_window_set_title(_GP(game).gamename.GetCStr());
 	sys_window_set_icon();
 	sys_evt_set_quit_callback(winclosehook);
 	_G(our_eip) = -197;
@@ -372,7 +372,7 @@ int engine_load_game_data() {
 
 // Replace special tokens inside a user path option
 static void resolve_configured_path(String &option) {
-	option.Replace("$GAMENAME$", _GP(game).gamename);
+	option.Replace(Shared::String("$GAMENAME$"), _GP(game).gamename);
 }
 
 // Setup paths and directories that may be affected by user configuration
@@ -737,7 +737,7 @@ void engine_init_game_settings() {
 	_GP(play).speech_textwindow_gui = _GP(game).options[OPT_TWCUSTOM];
 	if (_GP(play).speech_textwindow_gui == 0)
 		_GP(play).speech_textwindow_gui = -1;
-	snprintf(_GP(play).game_name, sizeof(_GP(play).game_name), "%s", _GP(game).gamename);
+	_GP(play).game_name = _GP(game).gamename;
 	_GP(play).lastParserEntry[0] = 0;
 	_GP(play).follow_change_room_timer = 150;
 	for (ee = 0; ee < MAX_ROOM_BGFRAMES; ee++)
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index a132e9997e3..a3e3d819e55 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -755,7 +755,7 @@ void IAGSEngine::GetRenderStageDesc(AGSRenderStageDesc *desc) {
 
 void IAGSEngine::GetGameInfo(AGSGameInfo* ginfo) {
 	if (ginfo->Version >= 26) {
-		snprintf(ginfo->GameName, sizeof(ginfo->GameName), "%s", _GP(game).gamename);
+		snprintf(ginfo->GameName, sizeof(ginfo->GameName), "%s", _GP(game).gamename.GetCStr());
 		snprintf(ginfo->Guid, sizeof(ginfo->Guid), "%s", _GP(game).guid);
 		ginfo->UniqueId = _GP(game).uniqueid;
 	}
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 46a62cb2135..e9544408ab4 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -39,7 +39,6 @@ GameSetupStruct::GameSetupStruct()
 	memset(lipSyncFrameLetters, 0, sizeof(lipSyncFrameLetters));
 	memset(guid, 0, sizeof(guid));
 	memset(saveGameFileExtension, 0, sizeof(saveGameFileExtension));
-	memset(saveGameFolderName, 0, sizeof(saveGameFolderName));
 }
 
 GameSetupStruct::~GameSetupStruct() {
@@ -112,7 +111,7 @@ void GameSetupStruct::read_savegame_info(Shared::Stream *in, GameDataVersion dat
 	if (data_ver > kGameVersion_272) { // only 3.x
 		StrUtil::ReadCStrCount(guid, in, MAX_GUID_LENGTH);
 		StrUtil::ReadCStrCount(saveGameFileExtension, in, MAX_SG_EXT_LENGTH);
-		StrUtil::ReadCStrCount(saveGameFolderName, in, MAX_SG_FOLDER_LEN);
+		saveGameFolderName.ReadCount(in, LEGACY_MAX_SG_FOLDER_LEN);
 	}
 }
 
diff --git a/engines/ags/shared/ac/game_setup_struct.h b/engines/ags/shared/ac/game_setup_struct.h
index 26fa8a95182..88174ab5f54 100644
--- a/engines/ags/shared/ac/game_setup_struct.h
+++ b/engines/ags/shared/ac/game_setup_struct.h
@@ -85,7 +85,7 @@ struct GameSetupStruct : public GameSetupStructBase {
 	char              guid[MAX_GUID_LENGTH];
 	char              saveGameFileExtension[MAX_SG_EXT_LENGTH];
 	// NOTE: saveGameFolderName is generally used to create game subdirs in common user directories
-	char              saveGameFolderName[MAX_SG_FOLDER_LEN];
+	Shared::String    saveGameFolderName;
 	int               roomCount;
 	std::vector<int>  roomNumbers;
 	std::vector<Shared::String> roomNames;
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 98a6810c64f..54c8f514ff5 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -55,7 +55,6 @@ GameSetupStructBase::GameSetupStructBase()
 	, _resolutionType(kGameResolution_Undefined)
 	, _dataUpscaleMult(1)
 	, _screenUpscaleMult(1) {
-	memset(gamename, 0, sizeof(gamename));
 	memset(options, 0, sizeof(options));
 	memset(paluses, 0, sizeof(paluses));
 	memset(defpal, 0, sizeof(defpal));
@@ -135,7 +134,7 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, Ser
 	// NOTE: historically the struct was saved by dumping whole memory
 	// into the file stream, which added padding from memory alignment;
 	// here we mark the padding bytes, as they do not belong to actual data.
-	StrUtil::ReadCStrCount(gamename, in, GAME_NAME_LENGTH);
+	gamename.ReadCount(in, LEGACY_GAME_NAME_LENGTH);
 	in->ReadInt16(); // alignment padding to int32 (gamename: 50 -> 52 bytes)
 	in->ReadArrayOfInt32(options, MAX_OPTIONS);
 	if (game_ver < kGameVersion_340_4) { // TODO: this should probably be possible to deduce script API level
@@ -186,7 +185,7 @@ void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) co
 	// NOTE: historically the struct was saved by dumping whole memory
 	// into the file stream, which added padding from memory alignment;
 	// here we mark the padding bytes, as they do not belong to actual data.
-	out->Write(gamename, GAME_NAME_LENGTH);
+	gamename.WriteCount(out, LEGACY_GAME_NAME_LENGTH);
 	out->WriteInt16(0); // alignment padding to int32
 	out->WriteArrayOfInt32(options, MAX_OPTIONS);
 	out->Write(&paluses[0], sizeof(paluses));
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index f9f9c25c4c6..be0f385b1a5 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -55,11 +55,11 @@ struct ccScript;
 
 
 struct GameSetupStructBase {
-	static const int  GAME_NAME_LENGTH = 50;
+	static const int  LEGACY_GAME_NAME_LENGTH = 50;
 	static const int  MAX_OPTIONS = 100;
 	static const int  NUM_INTS_RESERVED = 17;
 
-	char              gamename[GAME_NAME_LENGTH];
+	Shared::String    gamename;
 	int32_t           options[MAX_OPTIONS];
 	unsigned char     paluses[256];
 	RGB               defpal[256];
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 7d5e4b7582a..251229eb473 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -136,11 +136,12 @@ namespace AGS3 {
 
 #define DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT  14 // Yellow
 
-#define MAXVIEWNAMELENGTH 15
+// MAXVIEWNAMELENGTH comes from unknown old engine version
+#define LEGACY_MAXVIEWNAMELENGTH 15
 #define MAXLIPSYNCFRAMES  20
 #define MAX_GUID_LENGTH   40
 #define MAX_SG_EXT_LENGTH 20
-#define MAX_SG_FOLDER_LEN 50
+#define LEGACY_MAX_SG_FOLDER_LEN 50
 
 enum GameResolutionType {
 	kGameResolution_Undefined = -1,
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 2384c4f04cb..847a8467c63 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -666,17 +666,16 @@ void SetDefaultGlobalMessages(GameSetupStruct &game) {
 void FixupSaveDirectory(GameSetupStruct &game) {
 	// If the save game folder was not specified by game author, create one of
 	// the game name, game GUID, or uniqueid, as a last resort
-	if (!_GP(game).saveGameFolderName[0]) {
-		if (_GP(game).gamename[0])
-			snprintf(_GP(game).saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", _GP(game).gamename);
+	if (_GP(game).saveGameFolderName.IsEmpty()) {
+		if (!_GP(game).gamename.IsEmpty())
+			_GP(game).saveGameFolderName = _GP(game).gamename;
 		else if (_GP(game).guid[0])
-			snprintf(_GP(game).saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", _GP(game).guid);
+			_GP(game).saveGameFolderName = _GP(game).guid;
 		else
-			snprintf(_GP(game).saveGameFolderName, MAX_SG_FOLDER_LEN, "AGS-Game-%d", _GP(game).uniqueid);
+			_GP(game).saveGameFolderName.Format("AGS-Game-%d", _GP(game).uniqueid);
 	}
 	// Lastly, fixup folder name by removing any illegal characters
-	String s = Path::FixupSharedFilename(_GP(game).saveGameFolderName);
-	snprintf(_GP(game).saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", s.GetCStr());
+	_GP(game).saveGameFolderName = Path::FixupSharedFilename(_GP(game).saveGameFolderName);
 }
 
 HGameFileError ReadSpriteFlags(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) {
@@ -741,6 +740,8 @@ HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
 	} else if (ext_id.CompareNoCase("v361_objnames") == 0) {
 		// Extended object names and script names:
 		// for object types that had hard name length limits
+		_ents.Game.gamename = StrUtil::ReadString(_in);
+		_ents.Game.saveGameFolderName = StrUtil::ReadString(_in);
 		size_t num_chars = _in->ReadInt32();
 		if (num_chars != _ents.Game.chars.size())
 			return new Error(String::FromFormat("Mismatching number of characters: read %zu expected %zu", num_chars, _ents.Game.chars.size()));
@@ -785,7 +786,7 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	GameSetupStruct::SerializeInfo sinfo;
 	game.GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
 
-	Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename);
+	Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename.GetCStr());
 	Debug::Printf(kDbgMsg_Info, "Game uid (old format): `%d`", game.uniqueid);
 	Debug::Printf(kDbgMsg_Info, "Game guid: '%s'", game.guid);
 


Commit: 8dc4dc57c541ae33d99c13f821b0912f6cb98f7e
    https://github.com/scummvm/scummvm/commit/8dc4dc57c541ae33d99c13f821b0912f6cb98f7e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Support extensions offset written into game data

This writes a file offset at which extension blocks are found into one of the reserved
 ints inside main game data block.
If this information is present, the engine will also preread game name and save
folder name from "v361_objnames" block. This will ensure that game name and save
folder info is correct when setting up config, as well as reporting game properties on
"--tell-gameproperties" command.

>From upstream 8bf311956edf483030a2ec71c315282751a1de29

Changed paths:
    engines/ags/shared/ac/game_setup_struct_base.cpp
    engines/ags/shared/ac/game_setup_struct_base.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 54c8f514ff5..582535adcd7 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -173,6 +173,8 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, Ser
 	default_lipsync_frame = in->ReadInt32();
 	invhotdotsprite = in->ReadInt32();
 	in->ReadArrayOfInt32(reserved, NUM_INTS_RESERVED);
+
+	info.ExtensionOffset = static_cast<uint32_t>(in->ReadInt32());
 	in->ReadArrayOfInt32(&info.HasMessages.front(), MAXGLOBALMES);
 
 	info.HasWordsDict = in->ReadInt32() != 0;
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index be0f385b1a5..417a3b7a690 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -57,7 +57,7 @@ struct ccScript;
 struct GameSetupStructBase {
 	static const int  LEGACY_GAME_NAME_LENGTH = 50;
 	static const int  MAX_OPTIONS = 100;
-	static const int  NUM_INTS_RESERVED = 17;
+	static const int  NUM_INTS_RESERVED = 16;
 
 	Shared::String    gamename;
 	int32_t           options[MAX_OPTIONS];
@@ -101,6 +101,8 @@ struct GameSetupStructBase {
 		bool HasCCScript = false;
 		bool HasWordsDict = false;
 		std::array<int> HasMessages;
+		// File offset at which game data extensions begin
+		uint32_t ExtensionOffset = 0u;
 
 		SerializeInfo() {
 			HasMessages.resize(MAXGLOBALMES);
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 847a8467c63..7e588a21d92 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -777,6 +777,28 @@ HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
 	return HError::None();
 }
 
+// Search and read only data belonging to the general game info
+class GameDataExtPreloader : public GameDataExtReader {
+public:
+	GameDataExtPreloader(LoadedGameEntities &ents, GameDataVersion data_ver, Stream *in)
+		: GameDataExtReader(ents, data_ver, in) {}
+
+protected:
+	HError ReadBlock(int block_id, const String &ext_id, soff_t block_len, bool &read_next) override;
+};
+
+HError GameDataExtPreloader::ReadBlock(int /*block_id*/, const String &ext_id,
+									   soff_t /*block_len*/, bool &read_next) {
+	// Try reading only data which belongs to the general game info
+	read_next = true;
+	if (ext_id.CompareNoCase("v361_objnames") == 0) {
+		_ents.Game.gamename = StrUtil::ReadString(_in);
+		_ents.Game.saveGameFolderName = StrUtil::ReadString(_in);
+		read_next = false; // we're done
+	}
+	return HError::None();
+}
+
 HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) {
 	GameSetupStruct &game = ents.Game;
 
@@ -895,6 +917,16 @@ void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver
 	GameSetupStruct::SerializeInfo sinfo;
 	_GP(game).ReadFromFile(in, data_ver, sinfo);
 	_GP(game).read_savegame_info(in, data_ver);
+
+	// Check for particular expansions that might have data necessary
+	// for "preload" purposes
+	if (sinfo.ExtensionOffset == 0u)
+		return; // either no extensions, or data version is too early
+
+	in->Seek(sinfo.ExtensionOffset, kSeekBegin);
+	LoadedGameEntities ents(game);
+	GameDataExtPreloader reader(ents, data_ver, in);
+	reader.Read();
 }
 
 } // namespace Shared


Commit: 47b1ac9edcba64bab8812bdd11ae34d068386ea4
    https://github.com/scummvm/scummvm/commit/47b1ac9edcba64bab8812bdd11ae34d068386ea4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed CharacterInfo export to script, after adding new fields

I completely forgot that script has Character array where Character struct is an array
element, not Character pointer.
This means that the registered script struct size must match.

Thankfully we already have a mechanism of dealing with these mismatches
internally, it's done by a CCStaticArray. But it must be configured accordingly

To simplify things, picked out CharacterInfoBase as a parent struct to CharacterInfo,
which contains strictly the original fields. Will use sizeof(CharacterInfoBase) when
setting up StaticCharacterArray.

>From upstream 4be15565755bc54f5d8f48be0e6aebc6eee117c1

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


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index e64287a4474..2cfbb5bca80 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -121,7 +121,7 @@ void InitAndRegisterCharacters(GameSetupStruct &game) {
 		ccRegisterManagedObject(&game.chars[i], &_GP(ccDynamicCharacter));
 
 		// export the character's script object
-		ccAddExternalScriptObject(game.chars[i].scrname.GetCStr(), &game.chars[i], &_GP(ccDynamicCharacter));
+		ccAddExternalScriptObject(game.chars[i].scrname, &game.chars[i], &_GP(ccDynamicCharacter));
 	}
 }
 
@@ -208,7 +208,7 @@ void InitAndRegisterRegions() {
 
 // Registers static entity arrays in the script system
 void RegisterStaticArrays(GameSetupStruct &game) {
-	_GP(StaticCharacterArray).Create(&_GP(ccDynamicCharacter), sizeof(CharacterInfo), sizeof(CharacterInfo));
+	_GP(StaticCharacterArray).Create(&_GP(ccDynamicCharacter), sizeof(CharacterInfoBase), sizeof(CharacterInfo));
 	_GP(StaticObjectArray).Create(&_GP(ccDynamicObject), sizeof(ScriptObject), sizeof(ScriptObject));
 	_GP(StaticGUIArray).Create(&_GP(ccDynamicGUI), sizeof(ScriptGUI), sizeof(ScriptGUI));
 	_GP(StaticHotspotArray).Create(&_GP(ccDynamicHotspot), sizeof(ScriptHotspot), sizeof(ScriptHotspot));
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index b307bb8b41e..c0d8cbf07a9 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -90,12 +90,14 @@ inline int CharFlagsToObjFlags(int chflags) {
 // Length of deprecated character name field, in bytes
 #define LEGACY_MAX_CHAR_NAME_LEN 40
 
-struct CharacterExtras; // forward declaration
+// CharacterInfoBase contains original set of character fields.
+// It's picked out from CharacterInfo for convenience of adding
+// new design-time fields; easier to maintain backwards compatibility.
 // IMPORTANT: exposed to script API, and plugin API as AGSCharacter!
 // For older script compatibility the struct also has to maintain its size;
 // do not extend or change existing fields, unless planning breaking compatibility.
-// Use CharacterExtras struct for any extensions
-struct CharacterInfo {
+// Prefer to use CharacterExtras struct for any extensions.
+struct CharacterInfoBase {
 	int   defview;
 	int   talkview;
 	int   view;
@@ -132,9 +134,15 @@ struct CharacterInfo {
 	// for compatibility with the plugin API (unless the plugin interface is reworked)
 	char legacy_name[LEGACY_MAX_CHAR_NAME_LEN];
 	char legacy_scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
-
 	int8  on;
+};
+
 
+struct CharacterExtras;
+
+// Design-time Character data.
+// TODO: must refactor, some parts of it should be in a runtime Character class.
+struct CharacterInfo : public CharacterInfoBase {
 	AGS::Shared::String scrname;
 	AGS::Shared::String name;
 
@@ -202,7 +210,6 @@ private:
 	void WriteToFileImpl(Shared::Stream *out, bool is_save) const;
 };
 
-
 #if defined (OBSOLETE)
 struct OldCharacterInfo {
 	int   defview;


Commit: 419e2d7372a8df1b7e9ff9e79454c43618ef0a65
    https://github.com/scummvm/scummvm/commit/419e2d7372a8df1b7e9ff9e79454c43618ef0a65
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplify display_at, and only call it for blocking messagebox

Because _displayspeech() was calling _display_at(), there happen two consecutive
calls to try_auto_play_speech().
If text contains more "&" characters that might lead to an unexpected scripting
error, or even an overriding voice-over.
Instead made _displayspeech() call display_main() directly, and simplified
display_at() to work strictly as a message box starter.
>From upstream 1899fad4c4df0e2ddce7eb04699eb262e3638674

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/display.h
    engines/ags/engine/ac/global_display.cpp
    engines/ags/engine/ac/overlay.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 34ea77bb3b6..96997f0d2a2 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2421,18 +2421,6 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	if (useview >= _GP(game).numviews)
 		quitprintf("!Character.Say: attempted to use view %d for animation, but it does not exist", useview + 1);
 
-	int tdxp = xx, tdyp = yy;
-	int oldview = -1, oldloop = -1;
-	int ovr_type = 0;
-
-	_G(text_lips_offset) = 0;
-	_G(text_lips_text) = texx;
-
-	Bitmap *closeupface = nullptr;
-	// TODO: we always call _display_at later which may also start voice-over;
-	// find out if this may be refactored and voice started only in one place.
-	try_auto_play_speech(texx, texx, aschar);
-
 	if (_GP(game).options[OPT_SPEECHTYPE] == 3)
 		remove_screen_overlay(OVER_COMPLETE);
 	_G(our_eip) = 1500;
@@ -2445,12 +2433,21 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		ReleaseCharacterView(aschar);
 	}
 
+	int tdxp = xx, tdyp = yy;
+	int oldview = -1, oldloop = -1;
+	int ovr_type = 0;
+	_G(text_lips_offset) = 0;
+	_G(text_lips_text) = texx;
+	Bitmap *closeupface = nullptr;
 	bool overlayPositionFixed = false;
 	int charFrameWas = 0;
 	int viewWasLocked = 0;
 	if (speakingChar->flags & CHF_FIXVIEW)
 		viewWasLocked = 1;
 
+	// Start voice-over, if requested by the tokens in speech text
+	try_auto_play_speech(texx, texx, aschar);
+
 	if (speakingChar->room == _G(displayed_room)) {
 		// If the character is in this room, go for it - otherwise
 		// run the "else" clause which  does text in the middle of
@@ -2751,7 +2748,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		_G(char_thinking) = aschar;
 
 	_G(our_eip) = 155;
-	_display_at(tdxp, tdyp, bwidth, texx, DISPLAYTEXT_SPEECH, textcol, isThought, allowShrink, overlayPositionFixed);
+	display_main(tdxp, tdyp, bwidth, texx, DISPLAYTEXT_SPEECH, FONT_SPEECH, textcol, isThought, allowShrink, overlayPositionFixed);
 	if (_G(abort_engine))
 		return;
 
@@ -2783,6 +2780,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	}
 	_G(char_speaking) = -1;
 	_G(char_thinking) = -1;
+	// Stop any blocking voice-over, if was started by this function
 	if (_GP(play).IsBlockingVoiceSpeech())
 		stop_voice_speech();
 }
diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index e1ffee0372b..68b4e0fa415 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -221,7 +221,7 @@ Bitmap *create_textual_image(const char *text, int asspch, int isThought,
 // Pass yy = -1 to find Y co-ord automatically
 // allowShrink = 0 for none, 1 for leftwards, 2 for rightwards
 // pass blocking=2 to create permanent overlay
-ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont,
+ScreenOverlay *display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont,
 							 int asspch, int isThought, int allowShrink, bool overlayPositionFixed, bool roomlayer) {
 	//
 	// Prepare for the message display
@@ -383,22 +383,14 @@ ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp
 	return nullptr;
 }
 
-void _display_at(int xx, int yy, int wii, const char *text, int disp_type, int asspch, int isThought, int allowShrink, bool overlayPositionFixed) {
-	int usingfont = FONT_NORMAL;
-	if (asspch) usingfont = FONT_SPEECH;
-	// TODO: _display_at may be called from _displayspeech, which can start
-	// and finalize voice speech on its own. Find out if we really need to
-	// keep track of this and not just stop voice regardless.
-	bool need_stop_speech = false;
-
+void display_at(int xx, int yy, int wii, const char *text) {
 	EndSkippingUntilCharStops();
+	// Start voice-over, if requested by the tokens in speech text
+	try_auto_play_speech(text, text, _GP(play).narrator_speech);
+	display_main(xx, yy, wii, text, DISPLAYTEXT_MESSAGEBOX, FONT_NORMAL, 0, 0, 0, false);
 
-	if (try_auto_play_speech(text, text, _GP(play).narrator_speech)) {
-		need_stop_speech = true;
-	}
-	_display_main(xx, yy, wii, text, disp_type, usingfont, asspch, isThought, allowShrink, overlayPositionFixed);
-
-	if (need_stop_speech)
+	// Stop any blocking voice-over, if was started by this function
+	if (_GP(play).IsBlockingVoiceSpeech())
 		stop_voice_speech();
 }
 
diff --git a/engines/ags/engine/ac/display.h b/engines/ags/engine/ac/display.h
index 121e46a1bfe..769cadff313 100644
--- a/engines/ags/engine/ac/display.h
+++ b/engines/ags/engine/ac/display.h
@@ -45,9 +45,10 @@ Shared::Bitmap *create_textual_image(const char *text, int asspch, int isThought
 // Pass yy = -1 to find Y co-ord automatically
 // allowShrink = 0 for none, 1 for leftwards, 2 for rightwards
 // pass blocking=2 to create permanent overlay
-ScreenOverlay *_display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont,
-	int asspch, int isThought, int allowShrink, bool overlayPositionFixed, bool roomlayer = false);
-void _display_at(int xx, int yy, int wii, const char *text, int disp_type, int asspch, int isThought, int allowShrink, bool overlayPositionFixed);
+ScreenOverlay *display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont,
+							int asspch, int isThought, int allowShrink, bool overlayPositionFixed, bool roomlayer = false);
+// Displays a standard blocking message box at a given position
+void display_at(int xx, int yy, int wii, const char *text);
 // Cleans up display message state
 void post_display_cleanup();
 // Tests the given string for the voice-over tags and plays cue clip for the given character;
diff --git a/engines/ags/engine/ac/global_display.cpp b/engines/ags/engine/ac/global_display.cpp
index c02b0a97723..b4098e4b311 100644
--- a/engines/ags/engine/ac/global_display.cpp
+++ b/engines/ags/engine/ac/global_display.cpp
@@ -155,7 +155,7 @@ void DisplayAt(int xxp, int yyp, int widd, const char *text) {
 
 	if (widd < 1) widd = _GP(play).GetUIViewport().GetWidth() / 2;
 	if (xxp < 0) xxp = _GP(play).GetUIViewport().GetWidth() / 2 - widd / 2;
-	_display_at(xxp, yyp, widd, text, DISPLAYTEXT_MESSAGEBOX, 0, 0, 0, false);
+	display_at(xxp, yyp, widd, text);
 }
 
 void DisplayAtYImpl(int ypos, const char *texx, bool as_speech) {
@@ -184,8 +184,7 @@ void DisplayAtYImpl(int ypos, const char *texx, bool as_speech) {
 			_GP(play).disabled_user_interface--;
 		}
 
-		_display_at(-1, ypos, ui_view.GetWidth() / 2 + ui_view.GetWidth() / 4,
-		            get_translation(texx), DISPLAYTEXT_MESSAGEBOX, 0, 0, 0, false);
+		display_at(-1, ypos, ui_view.GetWidth() / 2 + ui_view.GetWidth() / 4, get_translation(texx));
 	}
 }
 
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index d8af7d25191..0b6a70d1b64 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -237,7 +237,7 @@ ScreenOverlay *Overlay_CreateTextCore(bool room_layer, int x, int y, int width,
 	if (width < 8) width = _GP(play).GetUIViewport().GetWidth() / 2;
 	if (x < 0) x = _GP(play).GetUIViewport().GetWidth() / 2 - width / 2;
 	if (text_color == 0) text_color = 16;
-	return _display_main(x, y, width, text, disp_type, font, -text_color, 0, allow_shrink, false, room_layer);
+	return display_main(x, y, width, text, disp_type, font, -text_color, 0, allow_shrink, false, room_layer);
 }
 
 ScriptOverlay *Overlay_CreateGraphicalImpl(bool room_layer, int x, int y, int slot, bool transparent, bool clone) {


Commit: bb55a804e00a37e958295fbad289c52dd988152f
    https://github.com/scummvm/scummvm/commit/bb55a804e00a37e958295fbad289c52dd988152f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: picked voice-over token parsing to a function

>From upstream ec70451d88d7bf7eae3a3f0b847e5b57e07fa54b

Changed paths:
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/string.h


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 68b4e0fa415..137c1ae4526 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -401,18 +401,16 @@ void post_display_cleanup() {
 }
 
 bool try_auto_play_speech(const char *text, const char *&replace_text, int charid) {
-	const char *src = text;
-	if (src[0] != '&')
-		return false;
-
-	int sndid = atoi(&src[1]);
-	while ((src[0] != ' ') & (src[0] != 0)) src++;
-	if (src[0] == ' ') src++;
-	if (sndid <= 0)
+	int voice_num;
+	const char *src = parse_voiceover_token(text, &voice_num);
+	if (src == text)
+		return false; // no token
+
+	if (voice_num <= 0)
 		quit("DisplaySpeech: auto-voice symbol '&' not followed by valid integer");
 
 	replace_text = src; // skip voice tag
-	if (play_voice_speech(charid, sndid)) {
+	if (play_voice_speech(charid, voice_num)) {
 		// if Voice Only, then blank out the text
 		if (_GP(play).speech_mode == kSpeech_VoiceOnly)
 			replace_text = "  ";
@@ -422,16 +420,10 @@ bool try_auto_play_speech(const char *text, const char *&replace_text, int chari
 }
 
 int GetTextDisplayLength(const char *text) {
-	int len = (int)strlen(text);
-	if ((text[0] == '&') && (_GP(play).unfactor_speech_from_textlength != 0)) {
-		// if there's an "&12 text" type line, remove "&12 " from the source length
-		size_t j = 0;
-		while ((text[j] != ' ') && (text[j] != 0))
-			j++;
-		j++;
-		len -= j;
-	}
-	return len;
+	// Skip voice-over token from the length calculation if required
+	if (_GP(play).unfactor_speech_from_textlength != 0)
+		text = parse_voiceover_token(text, nullptr);
+	return static_cast<int>(strlen(text));
 }
 
 int GetTextDisplayTime(const char *text, int canberel) {
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 48f5fe0b029..5a6da2f9ebe 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -261,11 +261,8 @@ size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLi
 	if (fonnt == -1)
 		fonnt = _GP(play).normal_font;
 
-	//  char sofar[100];
-	if (todis[0] == '&') {
-		while ((todis[0] != ' ') & (todis[0] != 0)) todis++;
-		if (todis[0] == ' ') todis++;
-	}
+	// Skip voice-over token; FIXME: should not be done in this line-splitting func!
+	todis = parse_voiceover_token(todis, nullptr);
 	lines.Reset();
 	_G(longestline) = 0;
 
@@ -307,6 +304,23 @@ size_t check_strcapacity(char *ptt) {
 	return MAX_MAXSTRLEN;
 }
 
+const char *parse_voiceover_token(const char *text, int *voice_num) {
+	if (*text != '&') {
+		if (voice_num)
+			*voice_num = 0;
+		return text; // no token
+	}
+
+	if (voice_num)
+		*voice_num = atoi(&text[1]);
+	// Skip the token and a single following space char
+	for (; *text && *text != ' '; ++text) {
+	}
+	if (*text == ' ')
+		++text;
+	return text;
+}
+
 //=============================================================================
 //
 // Script API Functions
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index 9bf1959dc9b..f3606ed1f97 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -68,6 +68,13 @@ inline size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int
 // cases when the buffer is a field inside one of the game structs,
 // in which case this returns that field's capacity.
 size_t check_strcapacity(char *ptt);
+// Tries if the input string contains a voice-over token ("&N"),
+// *optionally* fills the voice_num value (if the valid int pointer is passed),
+// and returns the pointer to the text portion after the token.
+// If returned pointer equals input pointer, that means that there was no token.
+// voice_num must be > 0 for a valid token, it's assigned 0 if no token was found,
+// or if there have been a parsing error.
+const char *parse_voiceover_token(const char *text, int *voice_num);
 
 } // namespace AGS3
 


Commit: e82e1708abe263b4be8d07fa9aaac821ca771c4c
    https://github.com/scummvm/scummvm/commit/e82e1708abe263b4be8d07fa9aaac821ca771c4c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix log of uninitialized value in gfx driver base

>From upstream f45357edccd8832c17989ef5599aa520fa93a384

Changed paths:
    engines/ags/engine/gfx/gfx_driver_base.cpp


diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index a2c5402adf6..9bee3d4ddb0 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -76,7 +76,7 @@ bool GraphicsDriverBase::SetVsync(bool enabled) {
 		_mode.Vsync = new_value;
 	}
 	else {
-		Debug::Printf("SetVsync: failed, stay at %d", new_value);
+		Debug::Printf("SetVsync: failed, stay at %d", _mode.Vsync);
 		_capsVsync = false; // mark as non-capable (at least in current mode)
 	}
 	return _mode.Vsync;


Commit: d59eeaf4a6b5d2fee204066fe01e885e7dba81e4
    https://github.com/scummvm/scummvm/commit/d59eeaf4a6b5d2fee204066fe01e885e7dba81e4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: improved Stream::HasErrors(), renamed to GetError()

Renamed HasErrors to GetError.
Require this method to clear any error flags after reading them, so to actually make
this function practical.
>From upstream e62e98a6cf6364d5dba5e90dd7b1ef2c96df2ce6

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/global_file.cpp
    engines/ags/shared/util/file_stream.cpp
    engines/ags/shared/util/file_stream.h
    engines/ags/shared/util/stream.h


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index cd66c22c94a..9027b49e434 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -530,7 +530,7 @@ static int ags_pf_feof(void *userdata) {
 }
 
 static int ags_pf_ferror(void *userdata) {
-	return ((AGS_PACKFILE_OBJ *)userdata)->stream->HasErrors() ? 1 : 0;
+	return ((AGS_PACKFILE_OBJ *)userdata)->stream->GetError() ? 1 : 0;
 }
 
 // Custom PACKFILE callback table
diff --git a/engines/ags/engine/ac/global_file.cpp b/engines/ags/engine/ac/global_file.cpp
index ca59df22a5b..3eeb4b800b5 100644
--- a/engines/ags/engine/ac/global_file.cpp
+++ b/engines/ags/engine/ac/global_file.cpp
@@ -148,7 +148,7 @@ int FileIsEOF(int32_t handle) {
 		return 1;
 
 	// TODO: stream errors
-	if (stream->HasErrors())
+	if (stream->GetError())
 		return 1;
 
 	if (stream->GetPosition() >= stream->GetLength())
@@ -159,7 +159,7 @@ int FileIsError(int32_t handle) {
 	Stream *stream = get_valid_file_stream_from_handle(handle, "FileIsError");
 
 	// TODO: stream errors
-	if (stream->HasErrors())
+	if (stream->GetError())
 		return 1;
 
 	return 0;
diff --git a/engines/ags/shared/util/file_stream.cpp b/engines/ags/shared/util/file_stream.cpp
index 927bbb5a4e7..e9c630e41fe 100644
--- a/engines/ags/shared/util/file_stream.cpp
+++ b/engines/ags/shared/util/file_stream.cpp
@@ -41,8 +41,12 @@ FileStream::~FileStream() {
 	FileStream::Close();
 }
 
-bool FileStream::HasErrors() const {
-	return IsValid() && _file->err();
+bool FileStream::GetError() const {
+	if (!_file)
+		return false;
+	bool err = _file->err();
+	_file->clearErr();
+	return err;
 }
 
 void FileStream::Close() {
diff --git a/engines/ags/shared/util/file_stream.h b/engines/ags/shared/util/file_stream.h
index e4fb4acf9e3..a90329ece38 100644
--- a/engines/ags/shared/util/file_stream.h
+++ b/engines/ags/shared/util/file_stream.h
@@ -49,7 +49,9 @@ public:
 
 	FileWorkMode GetWorkMode() const { return _workMode; }
 
-	bool HasErrors() const override;
+	// Tells if there were errors during previous io operation(s);
+	// the call to GetError() *resets* the error record.
+	bool GetError() const override;
 	void Close() override;
 	bool Flush() override;
 
diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h
index ca930331e63..3f656c4fc32 100644
--- a/engines/ags/shared/util/stream.h
+++ b/engines/ags/shared/util/stream.h
@@ -59,10 +59,9 @@ public:
 	// Returns an optional path of a stream's source, such as a filepath;
 	// primarily for diagnostic purposes
 	const String &GetPath() const { return _path; }
-	// Tells if the stream has errors
-	virtual bool HasErrors() const {
-		return false;
-	}
+	// Tells if there were errors during previous io operation(s);
+	// the call to GetError() *resets* the error record.
+	virtual bool GetError() const { return false; }
 	// Flush stream buffer to the underlying device
 	virtual bool Flush() = 0;
 
@@ -303,7 +302,7 @@ public:
 		return _stream->seek(offset, origin);
 	}
 
-	bool HasErrors() const override {
+	bool GetError() const override {
 		return _stream->err();
 	}
 	bool Flush() override {


Commit: b4f9385a3f6cb03c74f07842458b4d2031380bbc
    https://github.com/scummvm/scummvm/commit/b4f9385a3f6cb03c74f07842458b4d2031380bbc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed BufferedStream::Seek return value

It turns out our Stream's Seek return value is not defined, and not documented
either. But judging by the other Stream implementations, it was supposed to return
positive whenever there's NO ERROR, regardless of whether the position is matching
user's request.
I suppose we may also make Seek return the resulting position, similar to how some
other libs do (e.g. see SDL and .NET Framework).
>From upstream 208fd913225d0732060744fa3b2377a265ad89a3

Changed paths:
    engines/ags/shared/util/buffered_stream.cpp
    engines/ags/shared/util/file_stream.cpp


diff --git a/engines/ags/shared/util/buffered_stream.cpp b/engines/ags/shared/util/buffered_stream.cpp
index e8086b700ff..059e8ef43eb 100644
--- a/engines/ags/shared/util/buffered_stream.cpp
+++ b/engines/ags/shared/util/buffered_stream.cpp
@@ -187,7 +187,7 @@ bool BufferedStream::Seek(soff_t offset, StreamSeek origin) {
 
 	// clamp
 	_position = MIN(MAX(want_pos, (soff_t)_start), _end);
-	return _position == want_pos;
+	return true;
 }
 
 //-----------------------------------------------------------------------------
diff --git a/engines/ags/shared/util/file_stream.cpp b/engines/ags/shared/util/file_stream.cpp
index e9c630e41fe..29ba8ddf5ee 100644
--- a/engines/ags/shared/util/file_stream.cpp
+++ b/engines/ags/shared/util/file_stream.cpp
@@ -152,7 +152,6 @@ bool FileStream::Seek(soff_t offset, StreamSeek origin) {
 		stdclib_origin = SEEK_END;
 		break;
 	default:
-		// TODO: warning to the log
 		return false;
 	}
 


Commit: eefccd3067d69557197b6b57d79f341b3a0760d2
    https://github.com/scummvm/scummvm/commit/eefccd3067d69557197b6b57d79f341b3a0760d2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: explicit LockSprite/UnlockSprite methods in SpriteCache

Reimplemented From upstream 8e2d2fc9329a393dcdcddbe29dd5cc7094793350

Changed paths:
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/view_frame.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h
    engines/ags/shared/util/resource_cache.h


diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 10a54f27efb..5c955b72ed9 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -163,7 +163,7 @@ void ChangeCursorGraphic(int curs, int newslot) {
 		debug_script_warn("Mouse.ChangeModeGraphic should not be used on the Inventory cursor when the cursor is linked to the active inventory item");
 
 	_GP(game).mcurs[curs].pic = newslot;
-	_GP(spriteset).Precache(newslot);
+	_GP(spriteset).PrecacheSprite(newslot);
 	if (curs == _G(cur_mode))
 		set_mouse_cursor(curs);
 }
@@ -360,7 +360,7 @@ void update_inv_cursor(int invnum) {
 
 		_GP(game).mcurs[MODE_USE].pic = cursorSprite;
 		// all cursor images must be pre-cached
-		_GP(spriteset).Precache(cursorSprite);
+		_GP(spriteset).PrecacheSprite(cursorSprite);
 
 		if ((_GP(game).invinfo[invnum].hotx > 0) || (_GP(game).invinfo[invnum].hoty > 0)) {
 			// if the hotspot was set (unfortunately 0,0 isn't a valid co-ord)
diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index 6d27b26812f..9f75d8c4795 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -114,7 +114,7 @@ void precache_view(int view, int max_loops) {
 	max_loops = std::min(_GP(views)[view].numLoops, max_loops);
 	for (int i = 0; i < max_loops; i++) {
 		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; j++)
-			_GP(spriteset).Precache(_GP(views)[view].loops[i].frames[j].pic);
+			_GP(spriteset).PrecacheSprite(_GP(views)[view].loops[i].frames[j].pic);
 	}
 }
 
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 5a8147dc548..1811c624977 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -515,7 +515,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 	if (r_data.CursorMode == MODE_USE)
 		SetActiveInventory(_G(playerchar)->activeinv);
 	// ensure that the current cursor is locked
-	_GP(spriteset).Precache(_GP(game).mcurs[r_data.CursorID].pic);
+	_GP(spriteset).PrecacheSprite(_GP(game).mcurs[r_data.CursorID].pic);
 
 	sys_window_set_title(_GP(play).game_name.GetCStr());
 
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index b7b5fbbdcc6..886e606024d 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -549,7 +549,7 @@ void engine_init_game_settings() {
 		// The cursor graphics are assigned to mousecurs[] and so cannot
 		// be removed from memory
 		if (_GP(game).mcurs[ee].pic >= 0)
-			_GP(spriteset).Precache(_GP(game).mcurs[ee].pic);
+			_GP(spriteset).PrecacheSprite(_GP(game).mcurs[ee].pic);
 
 		// just in case they typed an invalid view number in the editor
 		if (_GP(game).mcurs[ee].view >= _GP(game).numviews)
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 1b5f280bc19..e1b9e74a1e3 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -277,7 +277,7 @@ void SpriteCache::DisposeAllCached() {
 	_mru.clear();
 }
 
-void SpriteCache::Precache(sprkey_t index) {
+void SpriteCache::PrecacheSprite(sprkey_t index) {
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return;
 	if (!_spriteData[index].IsAssetSprite())
@@ -303,11 +303,38 @@ void SpriteCache::Precache(sprkey_t index) {
 	SprCacheLog("Precached %d", index);
 }
 
+void SpriteCache::LockSprite(sprkey_t index) {
+	assert(index >= 0); // out of positive range indexes are valid to fail
+	if (index < 0 || (size_t)index >= _spriteData.size())
+		return;
+	if (!_spriteData[index].IsAssetSprite())
+		return; // cannot lock a non-asset sprite
+
+	if (_spriteData[index].DoesSpriteExist()) {
+		_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
+	} else {
+		LoadSprite(index, true);
+	}
+	SprCacheLog("Locked %d", index);
+}
+
+void SpriteCache::UnlockSprite(sprkey_t index) {
+	assert(index >= 0); // out of positive range indexes are valid to fail
+	if (index < 0 || (size_t)index >= _spriteData.size())
+		return;
+	if (!_spriteData[index].IsAssetSprite() ||
+		!_spriteData[index].IsLocked())
+		return; // cannot unlock a non-asset sprite, or non-locked sprite
+
+	_spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED;
+	SprCacheLog("Unlocked %d", index);
+}
+
 sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
 	return (_spriteData[index].Flags & SPRCACHEFLAG_REMAP0) == 0 ? index : 0;
 }
 
-size_t SpriteCache::LoadSprite(sprkey_t index) {
+size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	assert((index >= 0) && ((size_t)index < _spriteData.size()));
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return 0;
@@ -338,9 +365,10 @@ size_t SpriteCache::LoadSprite(sprkey_t index) {
 	// Clear up space before adding to cache
 	const size_t size = image->GetWidth() * image->GetHeight() * image->GetBPP();
 	FreeMem(size);
+	// Add to the cache, lock if requested or if it's sprite 0
+	const bool should_lock = lock || (index == 0);
 	_spriteData[index] = SpriteData(image, size, SPRCACHEFLAG_ISASSET);
-	if (index == 0) // keep sprite 0 locked
-		_spriteData[index].Flags |= SPRCACHEFLAG_LOCKED;
+	_spriteData[index].Flags |= (SPRCACHEFLAG_LOCKED * should_lock);
 	_cacheSize += size;
 	SprCacheLog("Loaded %d, size now %zu KB", index, _cacheSize / 1024);
 
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index d2267381876..0377eaf8c8c 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -130,8 +130,21 @@ public:
 	size_t      GetMaxCacheSize() const;
 	// Returns number of sprite slots in the bank (this includes both actual sprites and free slots)
 	size_t      GetSpriteSlotCount() const;
-	// Loads sprite and and locks in memory (so it cannot get removed implicitly)
-	void        Precache(sprkey_t index);
+	// Loads sprite using SpriteFile if such index is known,
+	// frees the space if cache size reaches the limit
+	void        PrecacheSprite(sprkey_t index);
+	// Locks sprite, preventing it from getting removed by the normal cache limit.
+	// If this is a registered sprite from the game assets, then loads it first.
+	// If this is a sprite with SPRCACHEFLAG_EXTERNAL flag, then does nothing,
+	// as these are always "locked".
+	// If such sprite does not exist, then fails silently.
+	void        LockSprite(sprkey_t index);
+	// Unlocks sprite, putting it back into the cache logic,
+	// where it counts towards normal limit may be deleted to free space.
+	// NOTE: sprites with SPRCACHEFLAG_EXTERNAL flag cannot be unlocked,
+	// only explicitly removed.
+	// If such sprite was not present in memory, then fails silently.
+	void        UnlockSprite(sprkey_t index);
 	// Unregisters sprite from the bank and returns the bitmap
 	Bitmap		*RemoveSprite(sprkey_t index);
 	// Deletes particular sprite, marks slot as unused
@@ -157,7 +170,7 @@ public:
 
 private:
 	// Load sprite from game resource
-	size_t      LoadSprite(sprkey_t index);
+	size_t      LoadSprite(sprkey_t index, bool lock = false);
 	// Remap the given index to the sprite 0
 	void        RemapSpriteToSprite0(sprkey_t index);
 	// Gets the index of a sprite which data is used for the given slot;
diff --git a/engines/ags/shared/util/resource_cache.h b/engines/ags/shared/util/resource_cache.h
index 174ce51b1f3..0b650eb7260 100644
--- a/engines/ags/shared/util/resource_cache.h
+++ b/engines/ags/shared/util/resource_cache.h
@@ -35,6 +35,12 @@
 // TODO: support data Priority, which tells which items may be disposed
 // when adding new item and surpassing the cache limit.
 //
+// TODO: as an option, consider to have Locked items separate from the normal
+// cache limit, and probably have their own limit setting as a safety measure.
+// (after reaching this limit ResourceCache would simply ignore any further
+// Lock commands until some items are unlocked.)
+// Rethink this when it's time to design a better resource handling in AGS.
+//
 // TODO: as an option, consider supporting a specialized container type that
 // has an associative container's interface, but is optimized for having most
 // keys allocated in large continious sequences by default.


Commit: d904cac0c5edb9300d1d9e46ec1e210cd55b913a
    https://github.com/scummvm/scummvm/commit/d904cac0c5edb9300d1d9e46ec1e210cd55b913a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: implemented Game.PrecacheSprite() and PrecacheView()

Partially from upstream 76aee247b7f8ae9eff613393a18192ceab4aca9f

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/view_frame.cpp
    engines/ags/engine/ac/view_frame.h
    engines/ags/engine/main/engine.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 8495bde8426..b6c97efa04e 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -699,6 +699,14 @@ int Game_BlockingWaitSkipped() {
 	return _GP(play).GetWaitSkipResult();
 }
 
+void Game_PrecacheSprite(int sprnum) {
+	_GP(spriteset).PrecacheSprite(sprnum);
+}
+
+void Game_PrecacheView(int view, int first_loop, int last_loop) {
+	precache_view(view - 1 /* to 0-based view index */, first_loop, last_loop, true);
+}
+
 //=============================================================================
 
 // save game functions
@@ -1351,6 +1359,22 @@ void game_sprite_updated(int sprnum) {
 	}
 }
 
+void precache_view(int view, int first_loop, int last_loop, bool with_sounds) {
+	if (view < 0)
+		return;
+	if (first_loop > last_loop)
+		return;
+
+	first_loop = Math::Clamp(first_loop, 0, _GP(views)[view].numLoops - 1);
+	last_loop = Math::Clamp(last_loop, 0, _GP(views)[view].numLoops - 1);
+	for (int i = first_loop; i <= last_loop; ++i) {
+		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; ++j) {
+			const ViewFrame &frame = _GP(views)[view].loops[i].frames[j];
+			_GP(spriteset).PrecacheSprite(frame.pic);
+			}
+		}
+	}
+
 void game_sprite_deleted(int sprnum) {
 	// clear from texture cache
 	_G(gfxDriver)->ClearSharedDDB(sprnum);
@@ -1707,6 +1731,14 @@ RuntimeScriptValue Sc_Game_BlockingWaitSkipped(const RuntimeScriptValue *params,
 	API_SCALL_INT(Game_BlockingWaitSkipped);
 }
 
+RuntimeScriptValue Sc_Game_PrecacheSprite(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT(Game_PrecacheSprite);
+}
+
+RuntimeScriptValue Sc_Game_PrecacheView(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_PINT3(Game_PrecacheView);
+}
+
 void RegisterGameAPI() {
 	ScFnRegister game_api[] = {
 		{"Game::IsAudioPlaying^1", API_FN_PAIR(Game_IsAudioPlaying)},
@@ -1726,7 +1758,13 @@ void RegisterGameAPI() {
 		{"Game::InputBox^1", API_FN_PAIR(Game_InputBox)},
 		{"Game::SetSaveGameDirectory^1", API_FN_PAIR(Game_SetSaveGameDirectory)},
 		{"Game::StopSound^1", API_FN_PAIR(StopAllSounds)},
+		{"Game::IsPluginLoaded", Sc_Game_IsPluginLoaded},
+		{"Game::ChangeSpeechVox", API_FN_PAIR(Game_ChangeSpeechVox)},
+		{"Game::PlayVoiceClip", Sc_Game_PlayVoiceClip},
+		{"Game::SimulateKeyPress", API_FN_PAIR(Game_SimulateKeyPress)},
 		{"Game::ResetDoOnceOnly", API_FN_PAIR(Game_ResetDoOnceOnly)},
+		{"Game::PrecacheSprite", API_FN_PAIR(Game_PrecacheSprite)},
+		{"Game::PrecacheView", API_FN_PAIR(Game_PrecacheView)},
 		{"Game::get_CharacterCount", API_FN_PAIR(Game_GetCharacterCount)},
 		{"Game::get_DialogCount", API_FN_PAIR(Game_GetDialogCount)},
 		{"Game::get_FileName", API_FN_PAIR(Game_GetFileName)},
@@ -1758,10 +1796,6 @@ void RegisterGameAPI() {
 		{"Game::get_ViewCount", API_FN_PAIR(Game_GetViewCount)},
 		{"Game::get_AudioClipCount", API_FN_PAIR(Game_GetAudioClipCount)},
 		{"Game::geti_AudioClips", API_FN_PAIR(Game_GetAudioClip)},
-		{"Game::IsPluginLoaded", Sc_Game_IsPluginLoaded},
-		{"Game::ChangeSpeechVox", API_FN_PAIR(Game_ChangeSpeechVox)},
-		{"Game::PlayVoiceClip", Sc_Game_PlayVoiceClip},
-		{"Game::SimulateKeyPress", API_FN_PAIR(Game_SimulateKeyPress)},
 		{"Game::get_BlockingWaitSkipped", API_FN_PAIR(Game_BlockingWaitSkipped)},
 		{"Game::get_SpeechVoxFilename", API_FN_PAIR(Game_GetSpeechVoxFilename)},
 		{"Game::get_Camera", API_FN_PAIR(Game_GetCamera)},
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index b0ad46316c8..32b8fcb78b4 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -211,6 +211,8 @@ void get_message_text(int msnum, char *buffer, char giveErr = 1);
 // Notifies the game objects that certain sprite was updated.
 // This make them update their render states, caches, and so on.
 void game_sprite_updated(int sprnum);
+// Precaches sprites for a view, within a selected range of loops.
+void precache_view(int view, int first_loop = 0, int last_loop = INT32_MAX, bool with_sounds = false);
 // Notifies the game objects that certain sprite was deleted.
 // Those which used that sprite will reset to dummy sprite 0, update their render states and caches.
 void game_sprite_deleted(int sprnum);
diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 5c955b72ed9..0f58655f0e3 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -25,6 +25,7 @@
 #include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/dynobj/script_mouse.h"
 #include "ags/engine/ac/dynobj/script_system.h"
+#include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
diff --git a/engines/ags/engine/ac/view_frame.cpp b/engines/ags/engine/ac/view_frame.cpp
index 9f75d8c4795..d81f296745b 100644
--- a/engines/ags/engine/ac/view_frame.cpp
+++ b/engines/ags/engine/ac/view_frame.cpp
@@ -107,17 +107,6 @@ int ViewFrame_GetFrame(ScriptViewFrame *svf) {
 
 //=============================================================================
 
-void precache_view(int view, int max_loops) {
-	if (view < 0)
-		return;
-
-	max_loops = std::min(_GP(views)[view].numLoops, max_loops);
-	for (int i = 0; i < max_loops; i++) {
-		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; j++)
-			_GP(spriteset).PrecacheSprite(_GP(views)[view].loops[i].frames[j].pic);
-	}
-}
-
 int CalcFrameSoundVolume(int obj_vol, int anim_vol, int scale) {
 	// We view the audio property relation as the relation of the entities:
 	// system -> audio type -> audio emitter (object, character) -> animation's audio
diff --git a/engines/ags/engine/ac/view_frame.h b/engines/ags/engine/ac/view_frame.h
index 6e4685856c1..13c14bb5668 100644
--- a/engines/ags/engine/ac/view_frame.h
+++ b/engines/ags/engine/ac/view_frame.h
@@ -50,7 +50,6 @@ int  ViewFrame_GetView(ScriptViewFrame *svf);
 int  ViewFrame_GetLoop(ScriptViewFrame *svf);
 int  ViewFrame_GetFrame(ScriptViewFrame *svf);
 
-void precache_view(int view, int max_loops = INT_MAX);
 // Calculate the frame sound volume from different factors;
 // pass scale as 100 if volume scaling is disabled
 // NOTE: historically scales only in 0-100 range :/
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 886e606024d..c0e15aed13a 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -560,7 +560,7 @@ void engine_init_game_settings() {
 	}
 	// may as well preload the character gfx
 	if (_G(playerchar)->view >= 0)
-		precache_view(_G(playerchar)->view, Character_GetDiagonalWalking(_G(playerchar)) ? 8 : 4);
+		precache_view(_G(playerchar)->view, 0, Character_GetDiagonalWalking(_G(playerchar)) ? 8 : 4);
 
 	_G(our_eip) = -6;
 


Commit: bc3fb837c1e9136f9dc0cad1539f26014f8a3f4f
    https://github.com/scummvm/scummvm/commit/bc3fb837c1e9136f9dc0cad1539f26014f8a3f4f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added diagnostic measurements to PrecacheSprite, PrecacheView

Partially from upstream 28d9badacd319e13684081e2752bbc5228752d31

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/timer.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index b6c97efa04e..dbeaa1f406a 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -700,7 +700,12 @@ int Game_BlockingWaitSkipped() {
 }
 
 void Game_PrecacheSprite(int sprnum) {
+	const auto tp_start = AGS_Clock::now();
 	_GP(spriteset).PrecacheSprite(sprnum);
+	const auto tp_filedone = AGS_Clock::now();
+
+	const auto dur1 = ToMilliseconds(tp_filedone - tp_start);
+	Debug::Printf("Precache sprite %d; file->mem = %lld ms", sprnum, dur1);
 }
 
 void Game_PrecacheView(int view, int first_loop, int last_loop) {
@@ -1367,14 +1372,30 @@ void precache_view(int view, int first_loop, int last_loop, bool with_sounds) {
 
 	first_loop = Math::Clamp(first_loop, 0, _GP(views)[view].numLoops - 1);
 	last_loop = Math::Clamp(last_loop, 0, _GP(views)[view].numLoops - 1);
+
+	// Record cache sizes and timestamps, for diagnostic purposes
+	const size_t spcache_before = _GP(spriteset).GetCacheSize();
+	int total_frames = 0, total_sounds = 0;
+
+	const auto tp_start = AGS_Clock::now();
+	int64_t dur_sp_load = 0, dur_tx_make = 0, dur_sound_load = 0;
 	for (int i = first_loop; i <= last_loop; ++i) {
-		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; ++j) {
-			const ViewFrame &frame = _GP(views)[view].loops[i].frames[j];
+		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; ++j, ++total_frames) {
+			const auto &frame = _GP(views)[view].loops[i].frames[j];
+			const auto tp_detail1 = AGS_Clock::now();
 			_GP(spriteset).PrecacheSprite(frame.pic);
-			}
+			const auto tp_detail2 = AGS_Clock::now();
+			dur_sp_load += ToMilliseconds(tp_detail2 - tp_detail1);
 		}
 	}
 
+	// Print gathered time and size info
+	size_t spcache_after = _GP(spriteset).GetCacheSize();
+	Debug::Printf("Precache view %d (loops %d-%d) with %d frames, total = %lld ms, average file->mem = %lld ms",
+				  view, first_loop, last_loop, total_frames, dur_sp_load, dur_sp_load / total_frames);
+	Debug::Printf("\tSprite cache: %zu -> %zu KB", spcache_before / 1024u, spcache_after / 1024u);
+}
+
 void game_sprite_deleted(int sprnum) {
 	// clear from texture cache
 	_G(gfxDriver)->ClearSharedDDB(sprnum);
diff --git a/engines/ags/engine/ac/timer.h b/engines/ags/engine/ac/timer.h
index 366344e9915..a3be26c3ded 100644
--- a/engines/ags/engine/ac/timer.h
+++ b/engines/ags/engine/ac/timer.h
@@ -37,6 +37,11 @@ using AGS_Clock = std::conditional <
                   std::chrono::high_resolution_clock, std::chrono::steady_clock
                   >::type;
 
+template<typename TDur>
+inline int64_t ToMilliseconds(TDur dur) {
+	return std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+}
+
 // Sleeps for time remaining until the next game frame, updates next frame timestamp
 extern void WaitForNextFrame();
 


Commit: d94fde8d3f9e2bdddab49c2b139ab3531e2961e4
    https://github.com/scummvm/scummvm/commit/d94fde8d3f9e2bdddab49c2b139ab3531e2961e4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: in AssetManager replace the libs sorter struct to function ptr

Switch function ptr when priority is set, instead of checking for priority during
sorting process.
>From upstream ba05c8e055b12849e99a24ffadda960b442d9f75

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 d0ea4d77f39..d4a76fa3072 100644
--- a/engines/ags/shared/core/asset_manager.cpp
+++ b/engines/ags/shared/core/asset_manager.cpp
@@ -42,18 +42,17 @@ bool AssetManager::AssetLibEx::TestFilter(const String &filter) const {
 		(std::find(Filters.begin(), Filters.end(), filter) != Filters.end());
 }
 
-bool AssetManager::LibsByPriority::operator()(const AssetLibInfo *lib1, const AssetLibInfo *lib2) const {
-	const bool lib1dir = IsAssetLibDir(lib1);
-	const bool lib2dir = IsAssetLibDir(lib2);
-	if (lib1dir == lib2dir)
-		return false; // both are equal, none is less
-	if (Priority == kAssetPriorityLib)
-		return !lib1dir; // first element is less if it's library file
-	if (Priority == kAssetPriorityDir)
-		return lib1dir; // first element is less if it's directory
-	return false; // unknown priority, just fail
+// Asset library sorting function, directories have priority
+bool SortLibsPriorityDir(const AssetLibInfo *lib1, const AssetLibInfo *lib2) {
+	// first element is less if it's a directory while second is a lib
+	return IsAssetLibDir(lib1) && !IsAssetLibDir(lib2);
 }
 
+// Asset library sorting function, packages have priority
+bool SortLibsPriorityLib(const AssetLibInfo *lib1, const AssetLibInfo *lib2) {
+	// first element is less if it's a lib while second is a directory
+	return !IsAssetLibDir(lib1) && IsAssetLibDir(lib2);
+}
 
 /* static */ bool AssetManager::IsDataFile(const String &data_file) {
 	Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
@@ -75,18 +74,14 @@ bool AssetManager::LibsByPriority::operator()(const AssetLibInfo *lib1, const As
 	return kAssetErrNoLibFile;
 }
 
-AssetManager::AssetManager() {
-	_libsByPriority.Priority = kAssetPriorityDir;
-}
-
 void AssetManager::SetSearchPriority(AssetSearchPriority priority) {
-	_libsByPriority.Priority = priority;
-
-	std::sort(_activeLibs.begin(), _activeLibs.end(), _libsByPriority);
+	_libsPriority = priority;
+	_libsSorter = _libsPriority == kAssetPriorityDir ? SortLibsPriorityDir : SortLibsPriorityLib;
+	std::sort(_activeLibs.begin(), _activeLibs.end(), _libsSorter);
 }
 
 AssetSearchPriority AssetManager::GetSearchPriority() const {
-	return _libsByPriority.Priority;
+	return _libsPriority;
 }
 
 AssetError AssetManager::AddLibrary(const String &path, const AssetLibInfo **out_lib) {
@@ -112,7 +107,7 @@ AssetError AssetManager::AddLibrary(const String &path, const String &filters, c
 	if (err != kAssetNoError)
 		return err;
 	lib->Filters = filters.Split(',');
-	auto place = std::upper_bound(_activeLibs.begin(), _activeLibs.end(), lib, _libsByPriority);
+	auto place = std::upper_bound(_activeLibs.begin(), _activeLibs.end(), lib, _libsSorter);
 	_activeLibs.insert(place, lib);
 	if (out_lib)
 		*out_lib = lib;
diff --git a/engines/ags/shared/core/asset_manager.h b/engines/ags/shared/core/asset_manager.h
index e460a30e230..ffb528d8bc5 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();
+	AssetManager() = default;
 	~AssetManager() {
 		RemoveAllLibraries();
 	}
@@ -146,14 +146,11 @@ private:
 	std::vector<AssetLibEx *> _libs;
 	std::vector<AssetLibEx *> _activeLibs;
 
-	struct LibsByPriority {
-		AssetSearchPriority Priority = kAssetPriorityDir;
-
-		bool operator()(const AssetLibInfo *x, const AssetLibInfo *y) const;
-	} _libsByPriority;
+	AssetSearchPriority _libsPriority = kAssetPriorityDir;
+	// Sorting function, depends on priority setting
+	bool (*_libsSorter)(const AssetLibInfo *, const AssetLibInfo *);
 };
 
-
 String GetAssetErrorText(AssetError err);
 
 } // namespace Shared


Commit: e487c2e657118d5301d0eda4e7fd62acda0a04a2
    https://github.com/scummvm/scummvm/commit/e487c2e657118d5301d0eda4e7fd62acda0a04a2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: reverted an unintended function argument rename

>From upstream 803abfc540a8fced1e849a60d893c03659a8daf7

Changed paths:
    engines/ags/shared/util/file.cpp
    engines/ags/shared/util/file.h


diff --git a/engines/ags/shared/util/file.cpp b/engines/ags/shared/util/file.cpp
index 742c8b30a74..02e0dfcd830 100644
--- a/engines/ags/shared/util/file.cpp
+++ b/engines/ags/shared/util/file.cpp
@@ -89,14 +89,14 @@ bool File::DeleteFile(const String &filename) {
 	return g_system->getSavefileManager()->removeSavefile(file);
 }
 
-bool File::RenameFile(const String &old_name, const String &name) {
+bool File::RenameFile(const String &old_name, const String &new_name) {
 	// Only allow renaming files in the savegame folder
-	if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
-		warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), name.GetCStr());
+	if (old_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX) || new_name.CompareLeftNoCase(SAVE_FOLDER_PREFIX)) {
+		warning("Cannot rename file %s to %s. Only files in the savegame directory can be renamed", old_name.GetCStr(), new_name.GetCStr());
 		return false;
 	}
 	Common::String file_old(old_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
-	Common::String file_new(name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
+	Common::String file_new(new_name.GetCStr() + strlen(SAVE_FOLDER_PREFIX));
 	return g_system->getSavefileManager()->renameSavefile(file_old, file_new);
 }
 
diff --git a/engines/ags/shared/util/file.h b/engines/ags/shared/util/file.h
index 228f88230ee..ba0cf6993e3 100644
--- a/engines/ags/shared/util/file.h
+++ b/engines/ags/shared/util/file.h
@@ -69,7 +69,7 @@ bool        TestCreateFile(const String &filename);
 // Deletes existing file; returns TRUE if was able to delete one
 bool        DeleteFile(const String &filename);
 // Renames existing file to the new name; returns TRUE on success
-bool		RenameFile(const String &old_name, const String &name);
+bool		RenameFile(const String &old_name, const String &new_name);
 // Copies a file from src_path to dst_path; returns TRUE on success
 bool		CopyFile(const String &src_path, const String &dst_path, bool overwrite);
 


Commit: d0fe6fd80ead0afbf21fdc60443b22be5a3b5355
    https://github.com/scummvm/scummvm/commit/d0fe6fd80ead0afbf21fdc60443b22be5a3b5355
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: remade writing of non-fixed Character name in game saves

Done for more consistency, so long as CharacterInfo and CharacterExtras are
separated structs.

WARNING: this breaks the saves made in 3.6.1.14 (Beta 15)...

Additionally, done so that legacy scriptnames are not loaded from current saves,
since these are supposed to stay fixed throughout the game (at least unless
characters are created dynamically, which they currently are not).
>From upstream c15603adface26d3ea44519f07f4992c3201ad53

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character_extras.cpp
    engines/ags/engine/ac/character_extras.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/character_info.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 96997f0d2a2..f9b47e2aa86 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -1350,8 +1350,8 @@ const char *Character_GetName(CharacterInfo *chaa) {
 
 void Character_SetName(CharacterInfo *chaa, const char *newName) {
 	chaa->name = newName;
+	// Fill legacy name fields, for compatibility with old scripts and plugins
 	snprintf(chaa->legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", newName);
-
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 }
 
diff --git a/engines/ags/engine/ac/character_extras.cpp b/engines/ags/engine/ac/character_extras.cpp
index 820bbb7ff05..4b1d1913250 100644
--- a/engines/ags/engine/ac/character_extras.cpp
+++ b/engines/ags/engine/ac/character_extras.cpp
@@ -42,7 +42,7 @@ void CharacterExtras::CheckViewFrame(CharacterInfo *chi) {
 	AGS3::CheckViewFrame(chi->view, chi->loop, chi->frame, GetFrameSoundVolume(chi));
 }
 
-void CharacterExtras::ReadFromSavegame(Stream *in, CharacterInfo &chinfo, int save_ver) {
+void CharacterExtras::ReadFromSavegame(Stream *in, CharacterSvgVersion save_ver) {
 	in->ReadArrayOfInt16(invorder, MAX_INVORDER);
 	invorder_count = in->ReadInt16();
 	width = in->ReadInt16();
@@ -64,17 +64,9 @@ void CharacterExtras::ReadFromSavegame(Stream *in, CharacterInfo &chinfo, int sa
 		in->ReadInt8(); // reserved to fill int32
 		in->ReadInt8();
 	}
-	if (save_ver >= kCharSvgVersion_36114) {
-		chinfo.name = StrUtil::ReadString(in);
-	}
-
-	// Upgrade restored data
-	if (save_ver < kCharSvgVersion_36025) {
-		chinfo.idle_anim_speed = chinfo.animspeed + 5;
-	}
 }
 
-void CharacterExtras::WriteToSavegame(Stream *out, const CharacterInfo &chinfo) {
+void CharacterExtras::WriteToSavegame(Stream *out) const {
 	out->WriteArrayOfInt16(invorder, MAX_INVORDER);
 	out->WriteInt16(invorder_count);
 	out->WriteInt16(width);
@@ -94,8 +86,6 @@ void CharacterExtras::WriteToSavegame(Stream *out, const CharacterInfo &chinfo)
 	out->WriteInt8(static_cast<uint8_t>(cur_anim_volume));
 	out->WriteInt8(0); // reserved to fill int32
 	out->WriteInt8(0);
-	// kCharSvgVersion_36114
-	StrUtil::WriteString(chinfo.name, out);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/character_extras.h b/engines/ags/engine/ac/character_extras.h
index c383c89fe81..06363a8d931 100644
--- a/engines/ags/engine/ac/character_extras.h
+++ b/engines/ags/engine/ac/character_extras.h
@@ -18,6 +18,19 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
+//=============================================================================
+//
+// CharacterExtras is a separate runtime character data. Historically it was
+// separated from the design-time CharacterInfo, because the latter is exposed
+// to script API and plugin API in such way that its memory layout could not
+// be changed at all. Although, today this is less of an issue (see comment
+// to CharacterInfo).
+//
+// TODO: in the long run it will be beneficial to remake this into a more
+// explicit runtime Character class, while perhaps keeping CharacterInfo only
+// to load design-time data.
+//
+//=============================================================================
 
 #ifndef AGS_ENGINE_AC_CHARACTER_EXTRAS_H
 #define AGS_ENGINE_AC_CHARACTER_EXTRAS_H
@@ -35,14 +48,6 @@ class Stream;
 }
 using namespace AGS; // FIXME later
 
-enum CharacterSvgVersion {
-	kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
-	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
-	kCharSvgVersion_36025   = 2, // animation volume
-	kCharSvgVersion_36109   = 3, // removed movelists, save externally
-	kCharSvgVersion_36114	= 4, // no limit on character name's length
-};
-
 // The CharacterInfo struct size is fixed because it's exposed to script
 // and plugin API, therefore new stuff has to go here
 struct CharacterExtras {
@@ -81,12 +86,8 @@ struct CharacterExtras {
 	void CheckViewFrame(CharacterInfo *chi);
 
 	// Read character extra data from saves.
-	// NOTE: we read ext name fields into the CharacterInfo struct,
-	// hence require its reference as an argument. This is ugly, but should
-	// be improved when the structs are refactored into having a distinct
-	// runtime Character class, which would hold all relevant data itself.
-	void ReadFromSavegame(Shared::Stream *in, CharacterInfo &chinfo, int save_ver);
-	void WriteToSavegame(Shared::Stream *out, const CharacterInfo &chinfo);
+	void ReadFromSavegame(Shared::Stream *in, CharacterSvgVersion save_ver);
+	void WriteToSavegame(Shared::Stream *out) const;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 7747d1eeb6e..611bee713f6 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -488,7 +488,7 @@ HSaveError WriteCharacters(Stream *out) {
 	out->WriteInt32(_GP(game).numcharacters);
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
 		_GP(game).chars[i].WriteToSavegame(out);
-		_GP(charextra)[i].WriteToSavegame(out, _GP(game).chars[i]);
+		_GP(charextra)[i].WriteToSavegame(out);
 		Properties::WriteValues(_GP(play).charProps[i], out);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			WriteTimesRun272(*_GP(game).intrChar[i], out);
@@ -501,8 +501,8 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].ReadFromSavegame(in);
-		_GP(charextra)[i].ReadFromSavegame(in, _GP(game).chars[i], cmp_ver);
+		_GP(game).chars[i].ReadFromSavegame(in, static_cast<CharacterSvgVersion>(cmp_ver));
+		_GP(charextra)[i].ReadFromSavegame(in, static_cast<CharacterSvgVersion>(cmp_ver));
 		Properties::ReadValues(_GP(play).charProps[i], in);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
 			ReadTimesRun272(*_GP(game).intrChar[i], in);
@@ -1081,7 +1081,7 @@ struct ComponentHandlers {
 		},
 		{
 			"Characters",
-			kCharSvgVersion_36114,
+			kCharSvgVersion_36115,
 			kCharSvgVersion_350, // skip pre-alpha 3.5.0 ver
 			WriteCharacters,
 			ReadCharacters
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 22831548bdb..5aca73c9692 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -454,7 +454,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 
 	// Character extras (runtime only data)
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(charextra)[i].ReadFromSavegame(in, _GP(game).chars[i], kCharSvgVersion_Initial);
+		_GP(charextra)[i].ReadFromSavegame(in, kCharSvgVersion_Initial);
 	}
 	restore_game_palette(in);
 	restore_game_dialogs(in);
diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 027454bd3ff..2a970b9e752 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -30,8 +30,7 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
-void CharacterInfo::ReadFromFileImpl(Stream *in, bool is_save) {
-	const bool do_align_pad = !is_save;
+void CharacterInfo::ReadBaseFields(Stream *in) {
 	defview = in->ReadInt32();
 	talkview = in->ReadInt32();
 	view = in->ReadInt32();
@@ -75,15 +74,9 @@ void CharacterInfo::ReadFromFileImpl(Stream *in, bool is_save) {
 	in->ReadArrayOfInt16(inv, MAX_INV);
 	actx = in->ReadInt16();
 	acty = in->ReadInt16();
-	StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CHAR_NAME_LEN);
-	StrUtil::ReadCStrCount(legacy_scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
-	on = in->ReadInt8();
-	if (do_align_pad)
-		in->ReadInt8(); // alignment padding to int32
 }
 
-void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
-	const bool do_align_pad = !is_save;
+void CharacterInfo::WriteBaseFields(Stream *out) const {
 	out->WriteInt32(defview);
 	out->WriteInt32(talkview);
 	out->WriteInt32(view);
@@ -127,36 +120,56 @@ void CharacterInfo::WriteToFileImpl(Stream *out, bool is_save) const {
 	out->WriteArrayOfInt16(inv, MAX_INV);
 	out->WriteInt16(actx);
 	out->WriteInt16(acty);
-	out->Write(legacy_name, LEGACY_MAX_CHAR_NAME_LEN);
-	out->Write(legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
-	out->WriteInt8(on);
-	if (do_align_pad)
-		out->WriteInt8(0); // alignment padding to int32
 }
 
 void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver) {
-	ReadFromFileImpl(in, false);
-
-	// Assign names from legacy fields
-	name = legacy_name;
-	scrname = legacy_scrname;
+	ReadBaseFields(in);
+	StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CHAR_NAME_LEN);
+	StrUtil::ReadCStrCount(legacy_scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
+	on = in->ReadInt8();
+	in->ReadInt8(); // alignment padding to int32
 
 	// Upgrade data
 	if (data_ver < kGameVersion_360_16) {
 		idle_anim_speed = animspeed + 5;
 	}
+	// Assign names from legacy fields
+	name = legacy_name;
+	scrname = legacy_scrname;
 }
 
 void CharacterInfo::WriteToFile(Stream *out) const {
-	WriteToFileImpl(out, false);
+	WriteBaseFields(out);
+	out->Write(legacy_name, LEGACY_MAX_CHAR_NAME_LEN);
+	out->Write(legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
+	out->WriteInt8(on);
+	out->WriteInt8(0); // alignment padding to int32
 }
 
-void CharacterInfo::ReadFromSavegame(Stream *in) {
-	ReadFromFileImpl(in, true);
+void CharacterInfo::ReadFromSavegame(Stream *in, CharacterSvgVersion save_ver) {
+	ReadBaseFields(in);
+	if (save_ver < kCharSvgVersion_36115) { // Fixed-size name and scriptname
+		name.ReadCount(in, LEGACY_MAX_CHAR_NAME_LEN);
+		in->Seek(LEGACY_MAX_SCRIPT_NAME_LEN); // skip legacy scriptname
+											  // (don't overwrite static data from save!)
+	} else {
+		name = StrUtil::ReadString(in);
+	}
+	on = in->ReadInt8();
+
+	//
+	// Upgrade restored data
+	if (save_ver < kCharSvgVersion_36025) {
+		idle_anim_speed = animspeed + 5;
+	}
+	// Fill legacy name fields, for compatibility with old scripts and plugins
+	snprintf(legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", name.GetCStr());
 }
 
 void CharacterInfo::WriteToSavegame(Stream *out) const {
-	WriteToFileImpl(out, true);
+	WriteBaseFields(out);
+	StrUtil::WriteString(name, out); // kCharSvgVersion_36115
+	out->WriteInt8(on);
 }
 
 #if defined (OBSOLETE)
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index c0d8cbf07a9..6078559466a 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -96,7 +96,7 @@ inline int CharFlagsToObjFlags(int chflags) {
 // IMPORTANT: exposed to script API, and plugin API as AGSCharacter!
 // For older script compatibility the struct also has to maintain its size;
 // do not extend or change existing fields, unless planning breaking compatibility.
-// Prefer to use CharacterExtras struct for any extensions.
+// Prefer to use CharacterInfo or CharacterExtras struct for any extensions.
 struct CharacterInfoBase {
 	int   defview;
 	int   talkview;
@@ -131,12 +131,19 @@ struct CharacterInfoBase {
 	short inv[MAX_INV];
 	short actx, acty;
 	// These two name fields are deprecated, but must stay here
-	// for compatibility with the plugin API (unless the plugin interface is reworked)
+	// for compatibility with old scripts and plugin API
 	char legacy_name[LEGACY_MAX_CHAR_NAME_LEN];
 	char legacy_scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
 	int8  on;
 };
 
+enum CharacterSvgVersion {
+	kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
+	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
+	kCharSvgVersion_36025   = 2, // animation volume
+	kCharSvgVersion_36109   = 3, // removed movelists, save externally
+	kCharSvgVersion_36115	= 4, // no limit on character name's length
+};
 
 struct CharacterExtras;
 
@@ -201,13 +208,14 @@ struct CharacterInfo : public CharacterInfoBase {
 	void ReadFromFile(Shared::Stream *in, GameDataVersion data_ver);
 	void WriteToFile(Shared::Stream *out) const;
 	// TODO: move to runtime-only class (?)
-	void ReadFromSavegame(Shared::Stream *in);
+	void ReadFromSavegame(Shared::Stream *in, CharacterSvgVersion save_ver);
 	void WriteToSavegame(Shared::Stream *out) const;
 
 private:
-	// TODO: this is likely temp here until runtime class is factored out
-	void ReadFromFileImpl(Shared::Stream *in, bool is_save);
-	void WriteToFileImpl(Shared::Stream *out, bool is_save) const;
+	// Helper functions that read and write first data fields,
+	// common for both game file and save.
+	void ReadBaseFields(Shared::Stream *in);
+	void WriteBaseFields(Shared::Stream *out) const;
 };
 
 #if defined (OBSOLETE)


Commit: 41b5b9edbde0759bf79f4e640c2b60414c48f224
    https://github.com/scummvm/scummvm/commit/41b5b9edbde0759bf79f4e640c2b60414c48f224
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: sync Character's legacy name field in old Str* script functions

Detect when a legacy string function (StrCopy, StrCat, StrFormat, etc) modifies
Character's fixed-sized field, and sync that with the new unrestricted property value.

Also fixed check_strcapacity() which incorrectly use new Character field's size instead
of legacy field size after recent changes.
>From upstream ad7d19232bf9d69b6060e0cb20585628a6687c9f and
e1a06c5abf2ae0dd55dedc25404f71ef8c4b2fe5

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/global_string.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/string.h


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 9027b49e434..711c8e052eb 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -156,8 +156,9 @@ static bool File_ReadRawLineImpl(sc_File *fil, char *buffer, size_t buf_len) {
 }
 
 void File_ReadRawLine(sc_File *fil, char *buffer) {
-	size_t buflen = check_strcapacity(buffer);
+	size_t buflen = check_scstrcapacity(buffer);
 	File_ReadRawLineImpl(fil, buffer, buflen);
+	commit_scstr_update(buffer);
 }
 
 const char *File_ReadRawLineBack(sc_File *fil) {
diff --git a/engines/ags/engine/ac/global_string.cpp b/engines/ags/engine/ac/global_string.cpp
index 008a6a9d142..2a1661f510c 100644
--- a/engines/ags/engine/ac/global_string.cpp
+++ b/engines/ags/engine/ac/global_string.cpp
@@ -43,30 +43,35 @@ void StrSetCharAt(char *strin, int posn, int nchar) {
 	strin[posn] = static_cast<char>(nchar);
 	if (static_cast<size_t>(posn) == len)
 		strin[posn + 1] = 0;
+	commit_scstr_update(strin);
 }
 
 void _sc_strcat(char *s1, const char *s2) {
 	VALIDATE_STRING(s2);
-	size_t buflen = check_strcapacity(s1);
+	size_t buflen = check_scstrcapacity(s1);
 	size_t s1_len = strlen(s1);
 	size_t buf_avail = (buflen - s1_len);
 	snprintf(s1 + s1_len, buf_avail, "%s", s2);
+	commit_scstr_update(s1);
 }
 
 void _sc_strlower(char *desbuf) {
 	VALIDATE_STRING(desbuf);
 	ags_strlwr(desbuf);
+	commit_scstr_update(desbuf);
 }
 
 void _sc_strupper(char *desbuf) {
 	VALIDATE_STRING(desbuf);
 	ags_strupr(desbuf);
+	commit_scstr_update(desbuf);
 }
 
 void _sc_strcpy(char *destt, const char *text) {
 	VALIDATE_STRING(destt);
-	size_t buflen = check_strcapacity(destt);
+	size_t buflen = check_scstrcapacity(destt);
 	snprintf(destt, buflen, "%s", text);
+	commit_scstr_update(destt);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 5a6da2f9ebe..ecb6072e233 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -296,14 +296,25 @@ size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLi
 // This is a somewhat ugly safety fix that tests whether the script tries
 // to write inside the Character's struct (e.g. char.name?), and truncates
 // the write limit accordingly.
-size_t check_strcapacity(char *ptt) {
-	uintptr_t charstart = (uintptr_t)&_GP(game).chars[0];
-	uintptr_t charend = charstart + sizeof(CharacterInfo) * _GP(game).numcharacters;
-	if (((uintptr_t)&ptt[0] >= charstart) && ((uintptr_t)&ptt[0] <= charend))
-		return sizeof(CharacterInfo::name);
+size_t check_scstrcapacity(const char *ptr) {
+	const void *charstart = &_GP(game).chars[0];
+	const void *charend = &_GP(game).chars[0] + sizeof(CharacterInfo) * _GP(game).chars.size();
+	if ((ptr >= charstart) && (ptr <= charend))
+		return sizeof(CharacterInfo::legacy_name);
 	return MAX_MAXSTRLEN;
 }
 
+// Similar in principle to check_scstrcapacity, but this will sync
+// legacy fixed-size name field with the contemporary property value.
+void commit_scstr_update(const char *ptr) {
+	const void *charstart = &_GP(game).chars[0];
+	const void *charend = &_GP(game).chars[0] + sizeof(CharacterInfo) * _GP(game).chars.size();
+	if ((ptr >= charstart) && (ptr <= charend)) {
+		size_t char_index = ((uintptr_t)ptr - (uintptr_t)charstart) / sizeof(CharacterInfo);
+		_GP(game).chars[char_index].name = _GP(game).chars[char_index].legacy_name;
+	}
+}
+
 const char *parse_voiceover_token(const char *text, int *voice_num) {
 	if (*text != '&') {
 		if (voice_num)
diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h
index f3606ed1f97..e109d9bd9d8 100644
--- a/engines/ags/engine/ac/string.h
+++ b/engines/ags/engine/ac/string.h
@@ -67,7 +67,11 @@ inline size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int
 // Commonly this should return MAX_MAXSTRLEN, but there are
 // cases when the buffer is a field inside one of the game structs,
 // in which case this returns that field's capacity.
-size_t check_strcapacity(char *ptt);
+size_t check_scstrcapacity(const char *ptr);
+// This function reports that a legacy script string was modified,
+// and checks if it is an object's field in order to sync with any contemporary
+// properties.
+void commit_scstr_update(const char *ptr);
 // Tries if the input string contains a voice-over token ("&N"),
 // *optionally* fills the voice_num value (if the valid int pointer is passed),
 // and returns the pointer to the text portion after the token.


Commit: 54d22dff15ae4d281cf3b75091d1405489ddabfd
    https://github.com/scummvm/scummvm/commit/54d22dff15ae4d281cf3b75091d1405489ddabfd
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed AGSPlatformDriver::GetDiskFreeSpaceMB() return type

>From upstream dfdb9fb449817b3d6385d919960e3de0171cea6e

Changed paths:
    engines/ags/engine/platform/base/ags_platform_driver.h
    engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp


diff --git a/engines/ags/engine/platform/base/ags_platform_driver.h b/engines/ags/engine/platform/base/ags_platform_driver.h
index 8bb98d1dca7..b8eda72afc1 100644
--- a/engines/ags/engine/platform/base/ags_platform_driver.h
+++ b/engines/ags/engine/platform/base/ags_platform_driver.h
@@ -117,7 +117,7 @@ struct AGSPlatformDriver
 	virtual const char *GetGraphicsTroubleshootingText() {
 		return "";
 	}
-	virtual unsigned long GetDiskFreeSpaceMB() = 0;
+	virtual uint64_t GetDiskFreeSpaceMB() = 0;
 	virtual const char *GetNoMouseErrorString() = 0;
 	// Tells whether build is capable of controlling mouse movement properly
 	virtual bool IsMouseControlSupported(bool windowed) {
diff --git a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
index c7dfa5afc16..5df8b756c52 100644
--- a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
+++ b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
@@ -45,7 +45,7 @@ struct ScummVMPlatformDriver : AGSPlatformDriver {
 	FSLocation GetUserConfigDirectory() override;
 	FSLocation GetUserGlobalConfigDirectory() override;
 	FSLocation GetAppOutputDirectory() override;
-	unsigned long GetDiskFreeSpaceMB() override;
+	uint64_t GetDiskFreeSpaceMB() override;
 	const char *GetNoMouseErrorString() override;
 	const char *GetAllegroFailUserHint() override;
 	eScriptSystemOSID GetSystemOSID() override;
@@ -97,7 +97,7 @@ FSLocation ScummVMPlatformDriver::GetAppOutputDirectory() {
 	return FSLocation(".");
 }
 
-unsigned long ScummVMPlatformDriver::GetDiskFreeSpaceMB() {
+uint64_t ScummVMPlatformDriver::GetDiskFreeSpaceMB() {
 	// placeholder
 	return 100;
 }


Commit: 9972dcee537cd594dca1a821d6380be70d6d7c04
    https://github.com/scummvm/scummvm/commit/9972dcee537cd594dca1a821d6380be70d6d7c04
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Removed unused variables in precache_view

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index dbeaa1f406a..e77a7f5c61d 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1375,10 +1375,8 @@ void precache_view(int view, int first_loop, int last_loop, bool with_sounds) {
 
 	// Record cache sizes and timestamps, for diagnostic purposes
 	const size_t spcache_before = _GP(spriteset).GetCacheSize();
-	int total_frames = 0, total_sounds = 0;
-
-	const auto tp_start = AGS_Clock::now();
-	int64_t dur_sp_load = 0, dur_tx_make = 0, dur_sound_load = 0;
+	int total_frames = 0;
+	int64_t dur_sp_load = 0;
 	for (int i = first_loop; i <= last_loop; ++i) {
 		for (int j = 0; j < _GP(views)[view].loops[i].numFrames; ++j, ++total_frames) {
 			const auto &frame = _GP(views)[view].loops[i].frames[j];


Commit: 96db95c255ac8a109b79054672621f1feab5f65b
    https://github.com/scummvm/scummvm/commit/96db95c255ac8a109b79054672621f1feab5f65b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix some minor warnings

>From upstream 28693377db7ae242782b313717ec9c476ecaff0f

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct_base.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 1b730161ee8..c17e8915496 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -72,7 +72,7 @@ void Button_Animate(GUIButton *butt, int view, int loop, int speed,	int repeat,
 	abtn.view = view;
 	abtn.loop = loop;
 	abtn.speed = speed;
-	abtn.repeat = static_cast<bool>(repeat) ? ANIM_REPEAT : ANIM_ONCE; // for now, clamp to supported modes
+	abtn.repeat = (repeat != 0) ? ANIM_REPEAT : ANIM_ONCE; // for now, clamp to supported modes
 	abtn.blocking = blocking;
 	abtn.direction = direction;
 	abtn.frame = SetFirstAnimFrame(view, loop, sframe, direction);
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index d7055876b2f..051b86f0998 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -727,7 +727,7 @@ void render_to_screen() {
 		_G(gfxDriver)->SetVsync(false);
 	} else {
 		bool new_vsync = _G(gfxDriver)->SetVsync(_GP(scsystem).vsync > 0);
-		if (new_vsync != _GP(scsystem).vsync)
+		if (new_vsync != (_GP(scsystem).vsync != 0))
 			System_SetVSyncInternal(new_vsync);
 	}
 
@@ -1529,7 +1529,7 @@ void prepare_characters_for_drawing() {
 	const bool hw_accel = !drawstate.SoftwareRender;
 
 	// draw characters
-	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
+	for (int charid = 0; charid < _GP(game).numcharacters; ++charid) {
 		const CharacterInfo &chin = _GP(game).chars[charid];
 		if (chin.on == 0)
 			continue;  // disabled
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 265bb2f00bf..487f7e15cff 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -616,7 +616,7 @@ static void update_objects_scale() {
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		update_object_scale(objid);
 	}
-	for (uint32_t charid = 0; charid < _GP(game).numcharacters; ++charid) {
+	for (int charid = 0; charid < _GP(game).numcharacters; ++charid) {
 		update_character_scale(charid);
 	}
 }
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index e9544408ab4..7b321a2222b 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -142,14 +142,14 @@ void GameSetupStruct::read_font_infos(Shared::Stream *in, GameDataVersion data_v
 }
 
 void GameSetupStruct::ReadInvInfo(Stream *in) {
-	for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) {
-		invinfo[iteratorCount].ReadFromFile(in);
+	for (int i = 0; i < numinvitems; ++i) {
+		invinfo[i].ReadFromFile(in);
 	}
 }
 
 void GameSetupStruct::WriteInvInfo(Stream *out) {
-	for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) {
-		invinfo[iteratorCount].WriteToFile(out);
+	for (int i = 0; i < numinvitems; ++i) {
+		invinfo[i].WriteToFile(out);
 	}
 }
 
@@ -189,14 +189,14 @@ void GameSetupStruct::read_words_dictionary(Shared::Stream *in) {
 }
 
 void GameSetupStruct::ReadMouseCursors(Stream *in) {
-	for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) {
-		mcurs[iteratorCount].ReadFromFile(in);
+	for (int i = 0; i < numcursors; ++i) {
+		mcurs[i].ReadFromFile(in);
 	}
 }
 
 void GameSetupStruct::WriteMouseCursors(Stream *out) {
-	for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) {
-		mcurs[iteratorCount].WriteToFile(out);
+	for (int i = 0; i < numcursors; ++i) {
+		mcurs[i].WriteToFile(out);
 	}
 }
 
@@ -237,14 +237,14 @@ void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int> &l
 }
 
 void GameSetupStruct::ReadCharacters(Stream *in) {
-	for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) {
-		chars[iteratorCount].ReadFromFile(in, _G(loaded_game_file_version));
+	for (int i = 0; i < numcharacters; ++i) {
+		chars[i].ReadFromFile(in, _G(loaded_game_file_version));
 	}
 }
 
 void GameSetupStruct::WriteCharacters(Stream *out) {
-	for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) {
-		chars[iteratorCount].WriteToFile(out);
+	for (int i = 0; i < numcharacters; ++i) {
+		chars[i].WriteToFile(out);
 	}
 }
 
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 417a3b7a690..391b12919ca 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -61,19 +61,21 @@ struct GameSetupStructBase {
 
 	Shared::String    gamename;
 	int32_t           options[MAX_OPTIONS];
-	unsigned char     paluses[256];
+	uint8_t           paluses[256];
 	RGB               defpal[256];
 	int               numviews;
 	int               numcharacters;
 	int               playercharacter;
 	int               totalscore;
-	short             numinvitems;
-	int               numdialog, numdlgmessage;
+	int               numinvitems;
+	int               numdialog;
+	int               numdlgmessage;  // [DEPRECATED]
 	int               numfonts;
 	int               color_depth;          // in bytes per pixel (ie. 1 or 2)
 	int               target_win;
 	int               dialog_bullet;        // 0 for none, otherwise slot num of bullet point
-	unsigned short    hotdot, hotdotouter;  // inv cursor hotspot dot color
+	int               hotdot;  // inv cursor hotspot dot color
+	int               hotdotouter;
 	int               uniqueid;    // random key identifying the game
 	int               numgui;
 	int               numcursors;


Commit: ae669f262ab3433fee5c76f435146cda779f7e5c
    https://github.com/scummvm/scummvm/commit/ae669f262ab3433fee5c76f435146cda779f7e5c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: replaced few instances of non-portable "long" type

>From upstream 22b54df5534bb87c63aff2b8b07774a4a3c2226e

Changed paths:
    engines/ags/shared/util/string_utils.cpp


diff --git a/engines/ags/shared/util/string_utils.cpp b/engines/ags/shared/util/string_utils.cpp
index c694a64183e..cd06d1d4cc8 100644
--- a/engines/ags/shared/util/string_utils.cpp
+++ b/engines/ags/shared/util/string_utils.cpp
@@ -58,7 +58,7 @@ StrUtil::ConversionError StrUtil::StringToInt(const String &s, int &val, int def
 		return StrUtil::kFailed;
 	if (lval > INT_MAX || lval < INT_MIN || _G(errnum) == AL_ERANGE)
 		return StrUtil::kOutOfRange;
-	val = (int)lval;
+	val = static_cast<int>(lval);
 	return StrUtil::kNoError;
 }
 


Commit: 25e4c703d74b8d382c4f06a2f498385047eda1cf
    https://github.com/scummvm/scummvm/commit/25e4c703d74b8d382c4f06a2f498385047eda1cf
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed BufferedStream::WriteByte return value

>From upstream 98db4c4527684d872d96c150fd25417514239c78

Changed paths:
    engines/ags/shared/util/buffered_stream.cpp


diff --git a/engines/ags/shared/util/buffered_stream.cpp b/engines/ags/shared/util/buffered_stream.cpp
index 059e8ef43eb..32ff002f97f 100644
--- a/engines/ags/shared/util/buffered_stream.cpp
+++ b/engines/ags/shared/util/buffered_stream.cpp
@@ -173,7 +173,7 @@ int32_t BufferedStream::WriteByte(uint8_t val) {
 	if (sz != 1) {
 		return -1;
 	}
-	return sz;
+	return val;
 }
 
 bool BufferedStream::Seek(soff_t offset, StreamSeek origin) {
@@ -186,7 +186,7 @@ bool BufferedStream::Seek(soff_t offset, StreamSeek origin) {
 	}
 
 	// clamp
-	_position = MIN(MAX(want_pos, (soff_t)_start), _end);
+	_position = MIN(MAX(want_pos, _start), _end);
 	return true;
 }
 


Commit: dc16de544e1fc7255600add90d1d6fbb1d0c9214
    https://github.com/scummvm/scummvm/commit/dc16de544e1fc7255600add90d1d6fbb1d0c9214
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: Stream::Seek() returns new position

This was done for the IAGSStream in plugin api in the first iteration,
 but lost when fixing the api recently.
 From upstream e248f320074d8430f14c1c4e513e5fb120e55eb2 and
 76b49c0c7a4df46b06aebea86573999f29400809

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/shared/util/buffered_stream.cpp
    engines/ags/shared/util/buffered_stream.h
    engines/ags/shared/util/file_stream.cpp
    engines/ags/shared/util/file_stream.h
    engines/ags/shared/util/iags_stream.h
    engines/ags/shared/util/memory_stream.cpp
    engines/ags/shared/util/memory_stream.h
    engines/ags/shared/util/stream.h


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 711c8e052eb..806fdfd78f4 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -209,10 +209,7 @@ int File_ReadRawInt(sc_File *fil) {
 
 int File_Seek(sc_File *fil, int offset, int origin) {
 	Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.Seek");
-	if (!in->Seek(offset, (StreamSeek)origin)) {
-		return -1;
-	}
-	return in->GetPosition();
+	return in->Seek(offset, (StreamSeek)origin);
 }
 
 int File_GetEOF(sc_File *fil) {
diff --git a/engines/ags/shared/util/buffered_stream.cpp b/engines/ags/shared/util/buffered_stream.cpp
index 32ff002f97f..0c45ae97b10 100644
--- a/engines/ags/shared/util/buffered_stream.cpp
+++ b/engines/ags/shared/util/buffered_stream.cpp
@@ -38,10 +38,11 @@ const size_t BufferedStream::BufferSize;
 BufferedStream::BufferedStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess)
 	: FileStream(file_name, open_mode, work_mode, stream_endianess) {
 	if (IsValid()) {
-		if (FileStream::Seek(0, kSeekEnd)) {
+		soff_t end_pos = FileStream::Seek(0, kSeekEnd);
+		if (end_pos >= 0) {
 			_start = 0;
-			_end = FileStream::GetPosition();
-			if (!FileStream::Seek(0, kSeekBegin))
+			_end = end_pos;
+			if (FileStream::Seek(0, kSeekBegin) < 0)
 				_end = -1;
 		}
 
@@ -176,18 +177,17 @@ int32_t BufferedStream::WriteByte(uint8_t val) {
 	return val;
 }
 
-bool BufferedStream::Seek(soff_t offset, StreamSeek origin) {
+soff_t BufferedStream::Seek(soff_t offset, StreamSeek origin) {
 	soff_t want_pos = -1;
 	switch (origin) {
 		case StreamSeek::kSeekCurrent:  want_pos = _position + offset; break;
 		case StreamSeek::kSeekBegin:    want_pos = _start + offset; break;
 		case StreamSeek::kSeekEnd:      want_pos = _end + offset; break;
-		default: return false;
+		default: return -1;
 	}
-
-	// clamp
+	// clamp to the valid range
 	_position = MIN(MAX(want_pos, _start), _end);
-	return true;
+	return _position;
 }
 
 //-----------------------------------------------------------------------------
diff --git a/engines/ags/shared/util/buffered_stream.h b/engines/ags/shared/util/buffered_stream.h
index 6dd2248f05b..b000bc8a7ed 100644
--- a/engines/ags/shared/util/buffered_stream.h
+++ b/engines/ags/shared/util/buffered_stream.h
@@ -65,7 +65,7 @@ public:
 	size_t  Write(const void *buffer, size_t size) override;
 	int32_t WriteByte(uint8_t b) override;
 
-	bool    Seek(soff_t offset, StreamSeek origin) override;
+	soff_t  Seek(soff_t offset, StreamSeek origin) override;
 
 protected:
 	soff_t _start = 0; // valid section starting offset
diff --git a/engines/ags/shared/util/file_stream.cpp b/engines/ags/shared/util/file_stream.cpp
index 29ba8ddf5ee..9166587a56a 100644
--- a/engines/ags/shared/util/file_stream.cpp
+++ b/engines/ags/shared/util/file_stream.cpp
@@ -139,7 +139,7 @@ int32_t FileStream::WriteByte(uint8_t val) {
 	return -1;
 }
 
-bool FileStream::Seek(soff_t offset, StreamSeek origin) {
+soff_t FileStream::Seek(soff_t offset, StreamSeek origin) {
 	int stdclib_origin;
 	switch (origin) {
 	case kSeekBegin:
@@ -152,10 +152,10 @@ bool FileStream::Seek(soff_t offset, StreamSeek origin) {
 		stdclib_origin = SEEK_END;
 		break;
 	default:
-		return false;
+		return -1;
 	}
 
-	return ags_fseek(_file, (file_off_t)offset, stdclib_origin) == 0;
+	return (ags_fseek(_file, (file_off_t)offset, stdclib_origin) == 0) ? ags_ftell(_file) : -1;
 }
 
 void FileStream::Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode) {
diff --git a/engines/ags/shared/util/file_stream.h b/engines/ags/shared/util/file_stream.h
index a90329ece38..95055750748 100644
--- a/engines/ags/shared/util/file_stream.h
+++ b/engines/ags/shared/util/file_stream.h
@@ -72,7 +72,7 @@ public:
 	size_t Write(const void *buffer, size_t size) override;
 	int32_t WriteByte(uint8_t b) override;
 
-	bool Seek(soff_t offset, StreamSeek origin) override;
+	soff_t Seek(soff_t offset, StreamSeek origin) override;
 
 private:
 	void Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode);
diff --git a/engines/ags/shared/util/iags_stream.h b/engines/ags/shared/util/iags_stream.h
index 9230725cd3d..f7c87b61cd3 100644
--- a/engines/ags/shared/util/iags_stream.h
+++ b/engines/ags/shared/util/iags_stream.h
@@ -101,7 +101,7 @@ public:
 	virtual size_t      WriteArrayOfInt32(const int32_t *buffer, size_t count) = 0;
 	virtual size_t      WriteArrayOfInt64(const int64_t *buffer, size_t count) = 0;
 
-	virtual bool        Seek(soff_t offset, StreamSeek origin = kSeekCurrent) = 0;
+	virtual soff_t      Seek(soff_t offset, StreamSeek origin = kSeekCurrent) = 0;
 };
 
 } // namespace Shared
diff --git a/engines/ags/shared/util/memory_stream.cpp b/engines/ags/shared/util/memory_stream.cpp
index 688d50194d2..a26588e4530 100644
--- a/engines/ags/shared/util/memory_stream.cpp
+++ b/engines/ags/shared/util/memory_stream.cpp
@@ -111,21 +111,22 @@ int32_t MemoryStream::ReadByte() {
 	return _cbuf[_pos++];
 }
 
-bool MemoryStream::Seek(soff_t offset, StreamSeek origin) {
+soff_t MemoryStream::Seek(soff_t offset, StreamSeek origin) {
 	if (!CanSeek()) {
 		return false;
 	}
-	soff_t pos = 0;
+	soff_t want_pos = -1;
 	switch (origin) {
-	case kSeekBegin:    pos = 0 + offset; break;
-	case kSeekCurrent:  pos = _pos + offset; break;
-	case kSeekEnd:      pos = _len + offset; break;
+	case kSeekBegin:    want_pos = 0 + offset; break;
+	case kSeekCurrent:  want_pos = _pos + offset; break;
+	case kSeekEnd:      want_pos = _len + offset; break;
 	default:
-		return false;
+		return -1;
 	}
-	_pos = static_cast<size_t>(MAX<soff_t>(0, pos));
+	// clamp to a valid range
+	_pos = static_cast<size_t>(MAX<soff_t>(0, want_pos));
 	_pos = std::min(_len, _pos); // clamp to EOS
-	return true;
+	return static_cast<soff_t>(_pos);
 }
 
 size_t MemoryStream::Write(const void *buffer, size_t size) {
diff --git a/engines/ags/shared/util/memory_stream.h b/engines/ags/shared/util/memory_stream.h
index 72a72151bb6..7306b7585dd 100644
--- a/engines/ags/shared/util/memory_stream.h
+++ b/engines/ags/shared/util/memory_stream.h
@@ -75,7 +75,7 @@ public:
 	size_t  Write(const void *buffer, size_t size) override;
 	int32_t WriteByte(uint8_t b) override;
 
-	bool    Seek(soff_t offset, StreamSeek origin) override;
+	soff_t  Seek(soff_t offset, StreamSeek origin) override;
 
 protected:
 	const uint8_t           *_cbuf = nullptr; // readonly buffer ptr
diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h
index 3f656c4fc32..6fde704d1b1 100644
--- a/engines/ags/shared/util/stream.h
+++ b/engines/ags/shared/util/stream.h
@@ -298,7 +298,7 @@ public:
 		return 0;
 	}
 
-	bool Seek(soff_t offset, StreamSeek origin = kSeekCurrent) override {
+	soff_t Seek(soff_t offset, StreamSeek origin = kSeekCurrent) override {
 		return _stream->seek(offset, origin);
 	}
 


Commit: fd5e6dee1e69c9187c5e39a717536faf7f45fe3f
    https://github.com/scummvm/scummvm/commit/fd5e6dee1e69c9187c5e39a717536faf7f45fe3f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: don't limit filename length in PlayMP3File()

>From upstream 36c19a4a8bfc0fd2746a719a5da022756c947e2d and
f1382f646fcd2dabfd7bc94ecc028d932fc46bff

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/global_audio.cpp
    engines/ags/engine/ac/runtime_defines.h


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index cddc98de7ba..7cc1a2a8839 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -592,7 +592,7 @@ void GameState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameState
 	}
 
 	in->Read(takeover_from, 50);
-	in->Read(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN);
+	playmp3file_name.ReadCount(in, PLAYMP3FILE_MAX_FILENAME_LEN);
 	in->Read(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN);
 	in->Read(lastParserEntry, MAX_MAXSTRLEN);
 	if (svg_ver < kGSSvgVersion_361_14)
@@ -786,7 +786,7 @@ void GameState::WriteForSavegame(Stream *out) const {
 	out->WriteInt16(crossfade_final_volume_in);
 
 	out->Write(takeover_from, 50);
-	out->Write(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN);
+	playmp3file_name.WriteCount(out, PLAYMP3FILE_MAX_FILENAME_LEN);
 	out->Write(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN);
 	out->Write(lastParserEntry, MAX_MAXSTRLEN);
 	StrUtil::WriteString(game_name, out);
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index a03f3dca3d7..0f976b70866 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -61,6 +61,9 @@ struct ScriptOverlay;
 #define MAX_GAME_STATE_NAME_LENGTH 100
 #define GAME_STATE_RESERVED_INTS 5
 #define LEGACY_GAMESTATE_GAMENAMELENGTH 100
+// This is a length limit for serialized field,
+// not actual api input argument
+#define PLAYMP3FILE_MAX_FILENAME_LEN 50
 
 // Savegame data format
 enum GameStateSvgVersion {
@@ -245,7 +248,8 @@ struct GameState {
 	short crossfade_final_volume_in = 0;
 	QueuedAudioItem new_music_queue[MAX_QUEUED_MUSIC]{};
 	char  takeover_from[50]{};
-	char  playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN]{};
+	// Currently played external file; this is only for reference
+	AGS::Shared::String playmp3file_name;
 	char  globalstrings[MAXGLOBALSTRINGS][MAX_MAXSTRLEN]{};
 	char  lastParserEntry[MAX_MAXSTRLEN]{};
 	AGS::Shared::String game_name;
diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp
index 344d145c8f5..869089f110a 100644
--- a/engines/ags/engine/ac/global_audio.cpp
+++ b/engines/ags/engine/ac/global_audio.cpp
@@ -364,15 +364,10 @@ void SetMusicRepeat(int loopflag) {
 }
 
 void PlayMP3File(const char *filename) {
-	if (strlen(filename) >= PLAYMP3FILE_MAX_FILENAME_LEN)
-		quit("!PlayMP3File: filename too long");
-
 	debug_script_log("PlayMP3File %s", filename);
 
 	AssetPath asset_name(filename, "audio");
-
-	int useChan = prepare_for_new_music();
-	bool doLoop = (_GP(play).music_repeat > 0);
+	const bool doLoop = (_GP(play).music_repeat > 0);
 
 	SOUNDCLIP *clip = my_load_ogg(asset_name, doLoop);
 	int sound_type = 0;
@@ -385,28 +380,18 @@ void PlayMP3File(const char *filename) {
 		sound_type = MUS_MP3;
 	}
 
-	if (clip) {
-		clip->set_volume255(150);
-		if (clip->play()) {
-			AudioChans::SetChannel(useChan, clip);
-			_G(current_music_type) = sound_type;
-			_GP(play).cur_music_number = 1000;
-			// save the filename (if it's not what we were supplied with)
-			if (filename != &_GP(play).playmp3file_name[0])
-				snprintf(_GP(play).playmp3file_name, sizeof(_GP(play).playmp3file_name), "%s", filename);
-		} else {
-			delete clip;
-			clip = nullptr;
-		}
-	}
-
 	if (!clip) {
-		AudioChans::SetChannel(useChan, nullptr);
-		debug_script_warn("PlayMP3File: file '%s' not found or cannot play", filename);
+		debug_script_warn("PlayMP3File: music file '%s' not found or be read", filename);
+		return;
 	}
 
+	const int use_chan = prepare_for_new_music();
+	_G(current_music_type) = sound_type;
+	_GP(play).cur_music_number = 1000;
+	_GP(play).playmp3file_name = filename;
+	clip->set_volume255(150);
+	AudioChans::SetChannel(use_chan, clip);
 	post_new_music_check();
-
 	update_music_volume();
 }
 
diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h
index 1964c005019..2c855da5b31 100644
--- a/engines/ags/engine/ac/runtime_defines.h
+++ b/engines/ags/engine/ac/runtime_defines.h
@@ -57,7 +57,6 @@ namespace AGS3 {
 #define GLED_INTERACTION 1
 #define GLED_EFFECTS     2
 #define QUEUED_MUSIC_REPEAT 10000
-#define PLAYMP3FILE_MAX_FILENAME_LEN 50
 #define MAX_AUDIO_TYPES  30
 
 // Legacy (pre 3.5.0) alignment types used in the script API


Commit: 31d95ec1e389021b68854e67c7b1375c5b0cee8b
    https://github.com/scummvm/scummvm/commit/31d95ec1e389021b68854e67c7b1375c5b0cee8b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed pointer math in check_scstrcapacity, commit_scstr_update

This fixes ad7d192
>From upstream 9cc72e49301120fc2e1a8bd672cc1328f7e5a600

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


diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index ecb6072e233..495d378608c 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -298,7 +298,7 @@ size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLi
 // the write limit accordingly.
 size_t check_scstrcapacity(const char *ptr) {
 	const void *charstart = &_GP(game).chars[0];
-	const void *charend = &_GP(game).chars[0] + sizeof(CharacterInfo) * _GP(game).chars.size();
+	const void *charend = &_GP(game).chars[0] + _GP(game).chars.size();
 	if ((ptr >= charstart) && (ptr <= charend))
 		return sizeof(CharacterInfo::legacy_name);
 	return MAX_MAXSTRLEN;
@@ -308,7 +308,7 @@ size_t check_scstrcapacity(const char *ptr) {
 // legacy fixed-size name field with the contemporary property value.
 void commit_scstr_update(const char *ptr) {
 	const void *charstart = &_GP(game).chars[0];
-	const void *charend = &_GP(game).chars[0] + sizeof(CharacterInfo) * _GP(game).chars.size();
+	const void *charend = &_GP(game).chars[0] + _GP(game).chars.size();
 	if ((ptr >= charstart) && (ptr <= charend)) {
 		size_t char_index = ((uintptr_t)ptr - (uintptr_t)charstart) / sizeof(CharacterInfo);
 		_GP(game).chars[char_index].name = _GP(game).chars[char_index].legacy_name;


Commit: b05753b217ae4e0dcbf9418534b30145e08ccdd8
    https://github.com/scummvm/scummvm/commit/b05753b217ae4e0dcbf9418534b30145e08ccdd8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: DataStreamSection, restricts another stream to a range

Partially from upstream 6e52d449aeb0d89768feb5be5a8a7b44df3bb198

Changed paths:
    engines/ags/shared/util/data_stream.cpp
    engines/ags/shared/util/data_stream.h


diff --git a/engines/ags/shared/util/data_stream.cpp b/engines/ags/shared/util/data_stream.cpp
index b22bb453201..811a886b40e 100644
--- a/engines/ags/shared/util/data_stream.cpp
+++ b/engines/ags/shared/util/data_stream.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/std/algorithm.h"
 #include "ags/shared/util/data_stream.h"
 
 namespace AGS3 {
@@ -127,6 +128,77 @@ size_t DataStream::WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t cou
 	return elem;
 }
 
+DataStreamSection::DataStreamSection(Stream *base, soff_t start, soff_t end)
+	: _base(base) {
+	_start = std::max(0ll, std::min(start, end));
+	_end = std::min(std::max(0ll, end), base->GetLength());
+	soff_t pos = base->Seek(_start, kSeekBegin);
+	if (pos >= 0)
+		_position = pos;
+	else
+		_position = base->GetPosition();
+}
+
+size_t DataStreamSection::Read(void *buffer, size_t len) {
+	if (_position >= _end)
+		return 0;
+	len = std::min(len, static_cast<size_t>(_end - _position));
+	_position += _base->Read(buffer, len);
+	return len;
+}
+
+int32_t DataStreamSection::ReadByte() {
+	if (_position >= _end)
+		return -1;
+	int32_t b = _base->ReadByte();
+	if (b >= 0)
+		_position++;
+	return b;
+}
+
+size_t DataStreamSection::Write(const void *buffer, size_t len) {
+	len = _base->Write(buffer, len);
+	_position += len;
+	_end = std::max(_end, _position); // we might be overwriting after seeking back
+	return len;
+}
+
+int32_t DataStreamSection::WriteByte(uint8_t b) {
+	int32_t rb = _base->WriteByte(b);
+	if (rb == b) {
+		_position++;
+		_end = std::max(_end, _position); // we might be overwriting after seeking back
+	}
+	return rb;
+}
+
+soff_t DataStreamSection::Seek(soff_t offset, StreamSeek origin) {
+	soff_t want_pos = -1;
+	switch (origin) {
+	case StreamSeek::kSeekCurrent:
+		want_pos = _position + offset;
+		break;
+	case StreamSeek::kSeekBegin:
+		want_pos = _start + offset;
+		break;
+	case StreamSeek::kSeekEnd:
+		want_pos = _end + offset;
+		break;
+	default:
+		return -1;
+	}
+	want_pos = std::min(std::max(want_pos, _start), _end);
+	soff_t new_pos = _base->Seek(want_pos, kSeekBegin);
+	if (new_pos >= 0) // the position remains in case of seek error
+		_position = want_pos;
+	return new_pos;
+}
+
+void DataStreamSection::Close() {
+	// We do not close nor delete the base stream, but release it from use
+	_base = nullptr;
+}
+
 } // namespace Shared
 } // namespace AGS
 } // namespace AGS3
diff --git a/engines/ags/shared/util/data_stream.h b/engines/ags/shared/util/data_stream.h
index 6ebd39e65ce..863e46add71 100644
--- a/engines/ags/shared/util/data_stream.h
+++ b/engines/ags/shared/util/data_stream.h
@@ -122,6 +122,39 @@ public:
 	}
 };
 
+//
+// DataStreamSection wraps another stream and restricts its access
+// to a particular range of offsets of the base stream.
+// StreamSection does NOT own the base stream, and closing
+// a "stream section" does NOT close the base stream.
+// Base stream must stay in memory for as long as there are
+// "stream sections" refering it.
+class DataStreamSection : public DataStream {
+public:
+	// Constructs a StreamSection over a base stream,
+	// restricting working range to [start, end), i.e. end offset is
+	// +1 past allowed position.
+	DataStreamSection(Stream *base, soff_t start, soff_t end);
+	const char *GetPath() const { return _base->GetPath().GetCStr(); }
+	bool EOS() const override { return _position >= _end; }
+	bool GetError() const override { return _base->GetError(); }
+	soff_t GetLength() const override { return _end - _start; }
+	soff_t GetPosition() const override { return _position - _start; }
+	size_t Read(void *buffer, size_t len) override;
+	int32_t ReadByte() override;
+	size_t Write(const void *buffer, size_t len) override;
+	int32_t WriteByte(uint8_t b) override;
+	soff_t Seek(soff_t offset, StreamSeek origin = kSeekCurrent) override;
+	bool Flush() override { return _base->Flush(); }
+	void Close() override;
+
+private:
+	Stream *_base = nullptr;
+	soff_t _start = 0;
+	soff_t _end = 0;
+	soff_t _position = 0;
+};
+
 } // namespace Shared
 } // namespace AGS
 } // namespace AGS3


Commit: 50e11cf086629411f34cce747f9f51aa4ac62e0a
    https://github.com/scummvm/scummvm/commit/50e11cf086629411f34cce747f9f51aa4ac62e0a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: basic stream range safeguard for plugin serialization

Reimplemented from 99ae439a1dbdca965c58f69894b623de536f5163

Changed paths:
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_internal.h
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/plugins/plugin_engine.h
    engines/ags/shared/util/data_stream.cpp


diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 1811c624977..34aea8ec4f6 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -62,6 +62,7 @@
 #include "ags/plugins/plugin_engine.h"
 #include "ags/engine/script/script.h"
 #include "ags/shared/script/cc_common.h"
+#include "ags/shared/util/data_stream.h"
 #include "ags/shared/util/file.h"
 #include "ags/shared/util/stream.h"
 #include "ags/shared/util/string_utils.h"
@@ -752,18 +753,26 @@ void SaveGameState(Stream *out) {
 	SavegameComponents::WriteAllCommon(out);
 }
 
-void ReadPluginSaveData(Stream *in) {
-	auto pluginFileHandle = AGSE_RESTOREGAME;
-	pl_set_file_handle(pluginFileHandle, in);
-	pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle);
-	pl_clear_file_handle();
+void ReadPluginSaveData(Stream *in, PluginSvgVersion svg_ver, soff_t max_size) {
+	const soff_t start_pos = in->GetPosition();
+	const soff_t end_pos = start_pos + max_size;
+	String pl_name;
+	for (int pl_index = 0; pl_query_next_plugin_for_event(AGSE_RESTOREGAME, pl_index, pl_name); ++pl_index) {
+		auto pl_handle = AGSE_RESTOREGAME;
+		pl_set_file_handle(pl_handle, in);
+		pl_run_plugin_hook_by_index(pl_index, AGSE_RESTOREGAME, pl_handle);
+		pl_clear_file_handle();
+	}
 }
 
 void WritePluginSaveData(Stream *out) {
-	auto pluginFileHandle = AGSE_SAVEGAME;
-	pl_set_file_handle(pluginFileHandle, out);
-	pl_run_plugin_hooks(AGSE_SAVEGAME, pluginFileHandle);
-	pl_clear_file_handle();
+	String pl_name;
+	for (int pl_index = 0; pl_query_next_plugin_for_event(AGSE_SAVEGAME, pl_index, pl_name); ++pl_index) {
+		auto pl_handle = AGSE_SAVEGAME;
+		pl_set_file_handle(pl_handle, out);
+		pl_run_plugin_hook_by_index(pl_index, AGSE_SAVEGAME, pl_handle);
+		pl_clear_file_handle();
+	}
 }
 
 } // namespace Engine
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 611bee713f6..0bb4e73c1d6 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -275,7 +275,7 @@ void ReadViewportState(RestoredData &r_data, Stream *in) {
 	r_data.Viewports.push_back(view);
 }
 
-HSaveError ReadGameState(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData &r_data) {
+HSaveError ReadGameState(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	GameStateSvgVersion svg_ver = (GameStateSvgVersion)cmp_ver;
 	// Game base
@@ -384,7 +384,7 @@ enum AudioSvgVersion {
 	kAudioSvgVersion_36009	 = 2, // up number of channels
 };
 
-HSaveError ReadAudio(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData &r_data) {
+HSaveError ReadAudio(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	// Game content assertion
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).audioClipTypes.size(), "Audio Clip Types"))
@@ -496,7 +496,7 @@ HSaveError WriteCharacters(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
@@ -524,7 +524,7 @@ HSaveError WriteDialogs(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadDialogs(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadDialogs(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numdialog, "Dialogs"))
 		return err;
@@ -580,7 +580,7 @@ HSaveError WriteGUI(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadGUI(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadGUI(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	const GuiSvgVersion svg_ver = (GuiSvgVersion)cmp_ver;
 	// GUI state
@@ -656,7 +656,7 @@ HSaveError WriteInventory(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadInventory(Stream *in, int32_t /*cmp_ver*/, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadInventory(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numinvitems, "Inventory Items"))
 		return err;
@@ -677,7 +677,7 @@ HSaveError WriteMouseCursors(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadMouseCursors(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadMouseCursors(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcursors, "Mouse Cursors"))
 		return err;
@@ -702,7 +702,7 @@ HSaveError WriteViews(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadViews(Stream *in, int32_t /*cmp_ver*/, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadViews(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numviews, "Views"))
 		return err;
@@ -746,7 +746,7 @@ HSaveError WriteDynamicSprites(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	const int spr_count = in->ReadInt32();
 	// ensure the sprite set is at least large enough
@@ -781,7 +781,7 @@ HSaveError WriteOverlays(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	// Remember that overlay indexes may be non-sequential
 	// the vector may be resized during read
 	size_t over_count = in->ReadInt32();
@@ -819,7 +819,7 @@ HSaveError WriteDynamicSurfaces(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadDynamicSurfaces(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData &r_data) {
+HSaveError ReadDynamicSurfaces(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	if (!AssertCompatLimit(err, in->ReadInt32(), MAX_DYNAMIC_SURFACES, "Dynamic Surfaces"))
 		return err;
@@ -851,7 +851,7 @@ HSaveError WriteScriptModules(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadScriptModules(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
+HSaveError ReadScriptModules(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams &pp, RestoredData &r_data) {
 	HSaveError err;
 	// read the global script data segment
 	int data_len = in->ReadInt32();
@@ -896,7 +896,7 @@ HSaveError WriteRoomStates(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadRoomStates(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadRoomStates(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	int roomstat_count = in->ReadInt32();
 	for (; roomstat_count > 0; --roomstat_count) {
@@ -953,7 +953,7 @@ HSaveError WriteThisRoom(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData &r_data) {
+HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	HSaveError err;
 	_G(displayed_room) = in->ReadInt32();
 	if (_G(displayed_room) < 0)
@@ -1010,7 +1010,7 @@ HSaveError WriteMoveLists(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadMoveLists(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadMoveLists(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	HSaveError err;
 	size_t movelist_count = in->ReadInt32();
 	// TODO: this assertion is needed only because mls size is fixed to the
@@ -1032,7 +1032,7 @@ HSaveError WriteManagedPool(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadManagedPool(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadManagedPool(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
 	if (ccUnserializeAllObjects(in, &_GP(ccUnserializer))) {
 		return new SavegameError(kSvgErr_GameObjectInitFailed,
 		                         String::FromFormat("Managed pool deserialization failed: %s", cc_get_error().ErrorString.GetCStr()));
@@ -1045,8 +1045,8 @@ HSaveError WritePluginData(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadPluginData(Stream *in, int32_t cmp_ver, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
-	ReadPluginSaveData(in);
+HSaveError ReadPluginData(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+	ReadPluginSaveData(in, static_cast<PluginSvgVersion>(cmp_ver), cmp_size);
 	return HSaveError::None();
 }
 
@@ -1057,7 +1057,7 @@ struct ComponentHandler {
 	int32_t            Version; // current version to write and the highest supported version
 	int32_t            LowestVersion; // lowest supported version that the engine can read
 	HSaveError(*Serialize)(Stream *);
-	HSaveError(*Unserialize)(Stream *, int32_t cmp_ver, const PreservedParams &, RestoredData &);
+	HSaveError(*Unserialize)(Stream *, int32_t cmp_ver, soff_t cmp_size, const PreservedParams &, RestoredData &);
 };
 
 // Array of supported components
@@ -1264,7 +1264,7 @@ HSaveError ReadComponent(Stream *in, SvgCmpReadHelper &hlp, ComponentInfo &info)
 		return new SavegameError(kSvgErr_UnsupportedComponent);
 	if (info.Version > handler->Version || info.Version < handler->LowestVersion)
 		return new SavegameError(kSvgErr_UnsupportedComponentVersion, String::FromFormat("Saved version: %d, supported: %d - %d", info.Version, handler->LowestVersion, handler->Version));
-	HSaveError err = handler->Unserialize(in, info.Version, hlp.PP, hlp.RData);
+	HSaveError err = handler->Unserialize(in, info.Version, info.DataSize, hlp.PP, hlp.RData);
 	if (!err)
 		return err;
 	if (in->GetPosition() - info.DataOffset != info.DataSize)
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index 69941808829..5c95808bf33 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -139,10 +139,14 @@ struct RestoredData {
 	RestoredData();
 };
 
+enum PluginSvgVersion {
+	kPluginSvgVersion_Initial = 0,
+};
+
 // Runs plugin events, requesting to read save data from the given stream.
 // NOTE: there's no error check in this function, because plugin API currently
 // does not let plugins report any errors when restoring their saved data.
-void ReadPluginSaveData(Stream *in);
+void ReadPluginSaveData(Stream *in, PluginSvgVersion svg_ver, soff_t max_size);
 // Runs plugin events, requesting to write save data into the given stream.
 void WritePluginSaveData(Stream *out);
 
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 5aca73c9692..72796113ef7 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -485,7 +485,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 	if (!err)
 		return err;
 
-	ReadPluginSaveData(in);
+	ReadPluginSaveData(in, kPluginSvgVersion_Initial, SIZE_MAX);
 	if (static_cast<uint32_t>(in->ReadInt32()) != MAGICNUMBER)
 		return new SavegameError(kSvgErr_InconsistentPlugin);
 
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index a3e3d819e55..7b53bc7f6fc 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -829,6 +829,27 @@ int pl_run_plugin_debug_hooks(const char *scriptfile, int linenum) {
 	return 0;
 }
 
+bool pl_query_next_plugin_for_event(int event, int &pl_index, String &pl_name) {
+	for (int i = pl_index; i < _GP(plugins).size(); ++i) {
+		if (_GP(plugins)[i].wantHook & event) {
+			pl_index = i;
+			pl_name = _GP(plugins)[i].filename;
+			return true;
+		}
+	}
+	return false;
+}
+
+int pl_run_plugin_hook_by_index(int pl_index, int event, int data) {
+	if (pl_index < 0 || pl_index >= _GP(plugins).size())
+		return 0;
+	auto &plugin = _GP(plugins)[pl_index];
+	if (plugin.wantHook & event) {
+		return plugin._plugin->AGS_EngineOnEvent(event, data);
+	}
+	return 0;
+}
+
 void pl_run_plugin_init_gfx_hooks(const char *driverName, void *data) {
 	for (uint i = 0; i < _GP(plugins).size(); i++) {
 		_GP(plugins)[i]._plugin->AGS_EngineInitGfx(driverName, data);
diff --git a/engines/ags/plugins/plugin_engine.h b/engines/ags/plugins/plugin_engine.h
index 8454b89ef57..5ba1aee057c 100644
--- a/engines/ags/plugins/plugin_engine.h
+++ b/engines/ags/plugins/plugin_engine.h
@@ -62,7 +62,14 @@ void pl_stop_plugins();
 void pl_startup_plugins();
 NumberPtr pl_run_plugin_hooks(int event, NumberPtr data);
 void pl_run_plugin_init_gfx_hooks(const char *driverName, void *data);
-int  pl_run_plugin_debug_hooks(const char *scriptfile, int linenum);
+int pl_run_plugin_debug_hooks(const char *scriptfile, int linenum);
+// Finds a plugin that wants this event, starting with pl_index;
+// returns TRUE on success and fills its index and name;
+// returns FALSE if no more suitable plugins found.
+bool pl_query_next_plugin_for_event(int event, int &pl_index, Shared::String &pl_name);
+// Runs event for a plugin identified by an index it was registered under.
+int pl_run_plugin_hook_by_index(int pl_index, int event, int data);
+
 // Tries to register plugins, either by loading dynamic libraries, or getting any kind of replacement
 Engine::GameInitError pl_register_plugins(const std::vector<Shared::PluginInfo> &infos);
 bool pl_is_plugin_loaded(const char *pl_name);
diff --git a/engines/ags/shared/util/data_stream.cpp b/engines/ags/shared/util/data_stream.cpp
index 811a886b40e..c931f3ded11 100644
--- a/engines/ags/shared/util/data_stream.cpp
+++ b/engines/ags/shared/util/data_stream.cpp
@@ -130,8 +130,8 @@ size_t DataStream::WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t cou
 
 DataStreamSection::DataStreamSection(Stream *base, soff_t start, soff_t end)
 	: _base(base) {
-	_start = std::max(0ll, std::min(start, end));
-	_end = std::min(std::max(0ll, end), base->GetLength());
+	_start = std::max((soff_t)0, std::min(start, end));
+	_end = std::min(std::max((soff_t)0, end), base->GetLength());
 	soff_t pos = base->Seek(_start, kSeekBegin);
 	if (pos >= 0)
 		_position = pos;


Commit: 5b793dbd38eb5355773181dd237e661537a81742
    https://github.com/scummvm/scummvm/commit/5b793dbd38eb5355773181dd237e661537a81742
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: new plugins save format, stores name & data size per plugin

>From upstream e84746a5049b2855cd0774ba0ff641753f640c27

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


diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 34aea8ec4f6..7dba4ca23fd 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -756,23 +756,69 @@ void SaveGameState(Stream *out) {
 void ReadPluginSaveData(Stream *in, PluginSvgVersion svg_ver, soff_t max_size) {
 	const soff_t start_pos = in->GetPosition();
 	const soff_t end_pos = start_pos + max_size;
-	String pl_name;
-	for (int pl_index = 0; pl_query_next_plugin_for_event(AGSE_RESTOREGAME, pl_index, pl_name); ++pl_index) {
-		auto pl_handle = AGSE_RESTOREGAME;
-		pl_set_file_handle(pl_handle, in);
-		pl_run_plugin_hook_by_index(pl_index, AGSE_RESTOREGAME, pl_handle);
-		pl_clear_file_handle();
+
+	if (svg_ver >= kPluginSvgVersion_36115) {
+		int num_plugins_read = in->ReadInt32();
+		soff_t cur_pos = start_pos;
+		while ((num_plugins_read--) > 0 && (cur_pos < end_pos)) {
+			String pl_name = StrUtil::ReadString(in);
+			size_t data_size = in->ReadInt32();
+			soff_t data_start = in->GetPosition();
+
+			auto pl_handle = AGSE_RESTOREGAME;
+			pl_set_file_handle(pl_handle, in);
+			pl_run_plugin_hook_by_name(pl_name, AGSE_RESTOREGAME, pl_handle);
+			pl_clear_file_handle();
+
+			// Seek to the end of plugin data, in case it ended up reading not in the end
+			cur_pos = data_start + data_size;
+			in->Seek(cur_pos, kSeekBegin);
+		}
+	} else {
+		String pl_name;
+		for (int pl_index = 0; pl_query_next_plugin_for_event(AGSE_RESTOREGAME, pl_index, pl_name); ++pl_index) {
+			auto pl_handle = AGSE_RESTOREGAME;
+			pl_set_file_handle(pl_handle, in);
+			pl_run_plugin_hook_by_index(pl_index, AGSE_RESTOREGAME, pl_handle);
+			pl_clear_file_handle();
+		}
 	}
 }
 
 void WritePluginSaveData(Stream *out) {
+	soff_t pluginnum_pos = out->GetPosition();
+	out->WriteInt32(0); // number of plugins which wrote data
+
+	int num_plugins_wrote = 0;
 	String pl_name;
 	for (int pl_index = 0; pl_query_next_plugin_for_event(AGSE_SAVEGAME, pl_index, pl_name); ++pl_index) {
+		// NOTE: we don't care if they really write anything,
+		// but count them so long as they subscribed to AGSE_SAVEGAME
+		num_plugins_wrote++;
+
+		// Write a header for plugin data
+		StrUtil::WriteString(pl_name, out);
+		soff_t data_size_pos = out->GetPosition();
+		out->WriteInt32(0); // data size
+
+		// Create a stream section and write plugin data
+		soff_t data_start_pos = out->GetPosition();
 		auto pl_handle = AGSE_SAVEGAME;
 		pl_set_file_handle(pl_handle, out);
 		pl_run_plugin_hook_by_index(pl_index, AGSE_SAVEGAME, pl_handle);
 		pl_clear_file_handle();
+
+		// Finalize header
+		soff_t data_end_pos = out->GetPosition();
+		out->Seek(data_size_pos, kSeekBegin);
+		out->WriteInt32(data_end_pos - data_start_pos);
+		out->Seek(0, kSeekEnd);
 	}
+
+	// Write number of plugins
+	out->Seek(pluginnum_pos, kSeekBegin);
+	out->WriteInt32(num_plugins_wrote);
+	out->Seek(0, kSeekEnd);
 }
 
 } // namespace Engine
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 0bb4e73c1d6..64c8001ef40 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -1179,8 +1179,8 @@ struct ComponentHandlers {
 		},
 		{
 			"Plugin Data",
-			0,
-			0,
+			kPluginSvgVersion_36115,
+			kPluginSvgVersion_Initial,
 			WritePluginData,
 			ReadPluginData
 		},
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index 5c95808bf33..aef28c157eb 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -141,6 +141,7 @@ struct RestoredData {
 
 enum PluginSvgVersion {
 	kPluginSvgVersion_Initial = 0,
+	kPluginSvgVersion_36115   = 1,
 };
 
 // Runs plugin events, requesting to read save data from the given stream.
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 7b53bc7f6fc..5f39b7c63f5 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -850,6 +850,15 @@ int pl_run_plugin_hook_by_index(int pl_index, int event, int data) {
 	return 0;
 }
 
+int pl_run_plugin_hook_by_name(Shared::String &pl_name, int event, int data) {
+	for (auto &plugin : _GP(plugins)) {
+		if ((plugin.wantHook & event) && plugin.filename.CompareNoCase(pl_name) == 0) {
+			return plugin._plugin->AGS_EngineOnEvent(event, data);
+		}
+	}
+	return 0;
+}
+
 void pl_run_plugin_init_gfx_hooks(const char *driverName, void *data) {
 	for (uint i = 0; i < _GP(plugins).size(); i++) {
 		_GP(plugins)[i]._plugin->AGS_EngineInitGfx(driverName, data);
diff --git a/engines/ags/plugins/plugin_engine.h b/engines/ags/plugins/plugin_engine.h
index 5ba1aee057c..3e0ca400c58 100644
--- a/engines/ags/plugins/plugin_engine.h
+++ b/engines/ags/plugins/plugin_engine.h
@@ -69,6 +69,8 @@ int pl_run_plugin_debug_hooks(const char *scriptfile, int linenum);
 bool pl_query_next_plugin_for_event(int event, int &pl_index, Shared::String &pl_name);
 // Runs event for a plugin identified by an index it was registered under.
 int pl_run_plugin_hook_by_index(int pl_index, int event, int data);
+// Runs event for a plugin identified by its name.
+int pl_run_plugin_hook_by_name(Shared::String &pl_name, int event, int data);
 
 // Tries to register plugins, either by loading dynamic libraries, or getting any kind of replacement
 Engine::GameInitError pl_register_plugins(const std::vector<Shared::PluginInfo> &infos);


Commit: e12a4ab45d6956a82756472d4d9449b77898f558
    https://github.com/scummvm/scummvm/commit/e12a4ab45d6956a82756472d4d9449b77898f558
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: raise SavegameVersion for 3.6.1

>From upstream 06fde28f9965d1b108f28d000bf828348e576bc6

Changed paths:
    engines/ags/engine/game/savegame.h


diff --git a/engines/ags/engine/game/savegame.h b/engines/ags/engine/game/savegame.h
index 60e4c05cb8c..387a9df74c3 100644
--- a/engines/ags/engine/game/savegame.h
+++ b/engines/ags/engine/game/savegame.h
@@ -64,7 +64,8 @@ enum SavegameVersion {
 	kSvgVersion_351 = 13,
 	kSvgVersion_360_beta = 3060023,
 	kSvgVersion_360_final = 3060041,
-	kSvgVersion_Current = kSvgVersion_360_final,
+	kSvgVersion_361 = 3060115,
+	kSvgVersion_Current = kSvgVersion_361,
 	kSvgVersion_LowestSupported = kSvgVersion_321 // change if support dropped
 };
 


Commit: 5de58ed646797a168f6f5fc6418d0991be9d7b8a
    https://github.com/scummvm/scummvm/commit/5de58ed646797a168f6f5fc6418d0991be9d7b8a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.15)

Partially from upstream 4632714880b02e7894d99bb17959c8174a506c1c

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 27735ca20c2..d1266f7afd8 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.11"
+#define ACI_VERSION_STR      "3.6.1.15"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.11
+#define ACI_VERSION_MSRC_DEF  3.6.1.15
 #endif
 
 #define SPECIAL_VERSION ""


Commit: fae4d1180ea71d123dc05e29d0366f3e9b1e2088
    https://github.com/scummvm/scummvm/commit/fae4d1180ea71d123dc05e29d0366f3e9b1e2088
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: AGSflashlight: added missing AGS_PluginV2() export

>From upstream 478e65dd4bf64743d5e4b682ab495b47bf9bb4d2

Changed paths:
    engines/ags/plugins/ags_flashlight/ags_flashlight.h


diff --git a/engines/ags/plugins/ags_flashlight/ags_flashlight.h b/engines/ags/plugins/ags_flashlight/ags_flashlight.h
index 506f9cb8e31..190a86b06c9 100644
--- a/engines/ags/plugins/ags_flashlight/ags_flashlight.h
+++ b/engines/ags/plugins/ags_flashlight/ags_flashlight.h
@@ -116,6 +116,7 @@ public:
 	const char *AGS_GetPluginName() override;
 	void AGS_EngineStartup(IAGSEngine *engine) override;
 	int64 AGS_EngineOnEvent(int event, NumberPtr data) override;
+	int AGS_PluginV2() const { return 1; };
 };
 
 } // namespace AGSFlashlight


Commit: 33697a821a01c011d9354b141ada89a0acf963dc
    https://github.com/scummvm/scummvm/commit/33697a821a01c011d9354b141ada89a0acf963dc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: FIx missing line setting coldepth

Completes upstream 575877a6c89da5f4299f0fc78ff0291b0bb1894a

Changed paths:
    engines/ags/engine/game/game_init.cpp


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 2cfbb5bca80..375a515a6d9 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -392,6 +392,7 @@ void InitGameResolution(GameSetupStruct &game, GameDataVersion data_ver) {
 	// Assign ScriptSystem's resolution variables
 	_GP(scsystem).width = game.GetGameRes().Width;
 	_GP(scsystem).height = game.GetGameRes().Height;
+	_GP(scsystem).coldepth = game.GetColorDepth();
 	_GP(scsystem).viewport_width = game_to_data_coord(_GP(play).GetMainViewport().GetWidth());
 	_GP(scsystem).viewport_height = game_to_data_coord(_GP(play).GetMainViewport().GetHeight());
 }


Commit: 38115f48b1d7226a011ed659afe953614ae62a62
    https://github.com/scummvm/scummvm/commit/38115f48b1d7226a011ed659afe953614ae62a62
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed Dissolve transition

>From upstream 55f1d0376ef583b5f7d767e31c83e190c005246f

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


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index de60efbd82d..db010a6bb69 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -260,7 +260,10 @@ void process_event(const EventHappened *evp) {
 			if (_GP(game).color_depth == 1)
 				quit("!Cannot use crossfade screen transition in 256-colour games");
 
-			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in();
+			// TODO: crossfade does not need a screen with transparency, it should be opaque;
+			// but Software renderer cannot alpha-blend non-masked sprite at the moment,
+			// see comment to drawing opaque sprite in SDLRendererGraphicsDriver!
+			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in(false /* transparent */);
 
 			for (int alpha = 254; alpha > 0; alpha -= 16) {
 				// do the crossfade
@@ -288,7 +291,7 @@ void process_event(const EventHappened *evp) {
 			int aa, bb, cc;
 			RGB interpal[256];
 
-			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in();
+			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in(false /* transparent */);
 			for (aa = 0; aa < 16; aa++) {
 				// merge the palette while dithering
 				if (_GP(game).color_depth == 1) {
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index d22bfbadb31..9ae775043c7 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -83,7 +83,7 @@ void current_fade_out_effect() {
 	}
 }
 
-IDriverDependantBitmap *prepare_screen_for_transition_in() {
+IDriverDependantBitmap *prepare_screen_for_transition_in(bool opaque) {
 	if (_G(saved_viewport_bitmap) == nullptr)
 		quit("Crossfade: buffer is null attempting transition");
 
@@ -99,7 +99,7 @@ IDriverDependantBitmap *prepare_screen_for_transition_in() {
 		delete _G(saved_viewport_bitmap);
 		_G(saved_viewport_bitmap) = clippedBuffer;
 	}
-	return _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false, false);
+	return _G(gfxDriver)->CreateDDBFromBitmap(_G(saved_viewport_bitmap), false, opaque);
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/ac/screen.h b/engines/ags/engine/ac/screen.h
index b8895dc4015..8df0cd13909 100644
--- a/engines/ags/engine/ac/screen.h
+++ b/engines/ags/engine/ac/screen.h
@@ -49,7 +49,7 @@ class IDriverDependantBitmap;
 void fadein_impl(PALETTE p, int speed);
 void fadeout_impl(int spdd);
 void current_fade_out_effect();
-AGS::Engine::IDriverDependantBitmap *prepare_screen_for_transition_in();
+AGS::Engine::IDriverDependantBitmap *prepare_screen_for_transition_in(bool opaque);
 
 } // namespace AGS3
 


Commit: 2f85972fcecdfcc93f01a845825dc3f2f46e4c1a
    https://github.com/scummvm/scummvm/commit/2f85972fcecdfcc93f01a845825dc3f2f46e4c1a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fix typo leads out of range memcpy

>From upstream 04cdaa59afb45323893518e2e9daf92214e0407a

Changed paths:
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 7e588a21d92..29f82b50145 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -564,11 +564,11 @@ void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
 
 	// Fixup charakter script names for 2.x (EGO -> cEgo)
 	if (data_ver <= kGameVersion_272) {
-		char namelwr[LEGACY_MAX_CHAR_NAME_LEN];
+		char namelwr[LEGACY_MAX_SCRIPT_NAME_LEN];
 		for (int i = 0; i < numcharacters; i++) {
 			if (chars[i].legacy_scrname[0] == 0)
 				continue;
-			memcpy(namelwr, chars[i].legacy_scrname, LEGACY_MAX_CHAR_NAME_LEN);
+			memcpy(namelwr, chars[i].legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
 			ags_strlwr(namelwr + 1); // lowercase starting with the second char
 			snprintf(chars[i].legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "c%s", namelwr);
 			chars[i].scrname = chars[i].legacy_scrname;


Commit: 496afb9a3501da46bb2e301e7bb9dd2b3a45a061
    https://github.com/scummvm/scummvm/commit/496afb9a3501da46bb2e301e7bb9dd2b3a45a061
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed Seek return value for stream sections

>From upstream 3d6e90b3dd0c4ed2af7a1094b8eace036721363a

Changed paths:
    engines/ags/shared/util/buffered_stream.cpp
    engines/ags/shared/util/data_stream.cpp


diff --git a/engines/ags/shared/util/buffered_stream.cpp b/engines/ags/shared/util/buffered_stream.cpp
index 0c45ae97b10..43e20bbfe20 100644
--- a/engines/ags/shared/util/buffered_stream.cpp
+++ b/engines/ags/shared/util/buffered_stream.cpp
@@ -187,7 +187,7 @@ soff_t BufferedStream::Seek(soff_t offset, StreamSeek origin) {
 	}
 	// clamp to the valid range
 	_position = MIN(MAX(want_pos, _start), _end);
-	return _position;
+	return _position - _start; // convert to a stream section pos
 }
 
 //-----------------------------------------------------------------------------
diff --git a/engines/ags/shared/util/data_stream.cpp b/engines/ags/shared/util/data_stream.cpp
index c931f3ded11..012417089b5 100644
--- a/engines/ags/shared/util/data_stream.cpp
+++ b/engines/ags/shared/util/data_stream.cpp
@@ -191,7 +191,7 @@ soff_t DataStreamSection::Seek(soff_t offset, StreamSeek origin) {
 	soff_t new_pos = _base->Seek(want_pos, kSeekBegin);
 	if (new_pos >= 0) // the position remains in case of seek error
 		_position = want_pos;
-	return new_pos;
+	return _position - _start; // convert to a stream section pos
 }
 
 void DataStreamSection::Close() {


Commit: dd6ca00d421b1370619d34807922b9fd69437fb2
    https://github.com/scummvm/scummvm/commit/dd6ca00d421b1370619d34807922b9fd69437fb2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed NumPad not handled properly

* Fixed numeric symbols not received when NumLock is on.
* Fixed some other standard NumPad keys are not converting to valid AGS Keys at all.
>From upstream 5ba9fd7867bc7c45aeedcdf4a54a5edd9d669fb7

Changed paths:
    engines/ags/events.cpp


diff --git a/engines/ags/events.cpp b/engines/ags/events.cpp
index 1b03fac3441..c62459992ee 100644
--- a/engines/ags/events.cpp
+++ b/engines/ags/events.cpp
@@ -342,6 +342,28 @@ AGS3::eAGSKeyCode EventsManager::scummvm_key_to_ags_key(const Common::Event &eve
 	if (event.kbd.ascii >= 32 && event.kbd.ascii <= 127)
 		return static_cast<AGS3::eAGSKeyCode>(event.kbd.ascii);
 
+	// NumPad with NumLock on
+	if ((sym >= Common::KEYCODE_KP1 && sym <= Common::KEYCODE_KP_PERIOD) && (mod & Common::KBD_NUM) != 0) {
+		switch (sym) {
+		case Common::KEYCODE_KP1:
+		case Common::KEYCODE_KP2:
+		case Common::KEYCODE_KP3:
+		case Common::KEYCODE_KP4:
+		case Common::KEYCODE_KP5:
+		case Common::KEYCODE_KP6:
+		case Common::KEYCODE_KP7:
+		case Common::KEYCODE_KP8:
+		case Common::KEYCODE_KP9:
+			return static_cast<AGS3::eAGSKeyCode>(sym - Common::KEYCODE_KP1 + Common::KEYCODE_1);
+		case Common::KEYCODE_KP0:
+			return AGS3::eAGSKeyCode0;
+		case Common::KEYCODE_KP_PERIOD:
+			return AGS3::eAGSKeyCodePeriod;
+		default:
+			return AGS3::eAGSKeyCodeNone;
+		}
+	}
+
 	// Remaining codes may match or not, but we use a big table anyway.
 	// TODO: this is code by [sonneveld],
 	// double check that we must use scan codes here, maybe can use sdl key (sym) too?
@@ -413,6 +435,17 @@ AGS3::eAGSKeyCode EventsManager::scummvm_key_to_ags_key(const Common::Event &eve
 	case Common::KEYCODE_KP_PERIOD:
 	case Common::KEYCODE_DELETE:
 		return AGS3::eAGSKeyCodeDelete;
+
+	// KeyPad (remaining keys, not handled above)
+	case Common::KEYCODE_KP_DIVIDE:
+		return AGS3::eAGSKeyCodeForwardSlash;
+	case Common::KEYCODE_KP_MULTIPLY:
+		return AGS3::eAGSKeyCodeAsterisk;
+	case Common::KEYCODE_KP_MINUS:
+		return AGS3::eAGSKeyCodeHyphen;
+	case Common::KEYCODE_KP_PLUS:
+		return AGS3::eAGSKeyCodePlus;
+
 	case Common::KEYCODE_LSHIFT:
 		return AGS3::eAGSKeyCodeLShift;
 	case Common::KEYCODE_RSHIFT:


Commit: c02a141063d864e8e79a767b4ab86b7539daaad2
    https://github.com/scummvm/scummvm/commit/c02a141063d864e8e79a767b4ab86b7539daaad2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support IsKeyPress for some of the NumPad keys

This only adds support for keys that do not depend on the NumLock state. This is
because this function is not supposed to depend on modifiers state (same as
IsKeyPressed in script).
This roughly corresponds to the pre-3.6.0 behavior.

OTOH, there is a distinct AGS KeyCode for NumPad5, but not others. Perhaps, it
would be desired to add other NumPad* keycodes to let user combine them with
System.NumLock mod to know the correct key state.
>From upstream c6ed7f8c37b4a0ae694c3224de6ed4efcb968c78

Changed paths:
    engines/ags/events.cpp


diff --git a/engines/ags/events.cpp b/engines/ags/events.cpp
index c62459992ee..5818a8d75ed 100644
--- a/engines/ags/events.cpp
+++ b/engines/ags/events.cpp
@@ -176,6 +176,7 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 	kc[2] = Common::KEYCODE_INVALID;
 	Common::KeyCode sym = Common::KEYCODE_INVALID;
 
+	// Process series of keys that match certain sym code
 	// SDL sym codes happen to match small ASCII letters, so lowercase ours if necessary
 	if (key >= AGS3::eAGSKeyCodeA && key <= AGS3::eAGSKeyCodeZ) {
 		sym = static_cast<Common::KeyCode>(key - AGS3::eAGSKeyCodeA + Common::KEYCODE_a);
@@ -187,11 +188,35 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 
 	if (sym != Common::KEYCODE_INVALID) {
 		kc[0] = sym;
+		// Numpad keys that do not depend on NumLock state, so should always be checked
+		// TODO: I was not certain whether to handle numeric keys here, because they
+		// are only normally generated by numpad when NumLock is on, and that classifies
+		// as "using modifier key". OTOH, there is a distinct AGS KeyCode for NumPad5,
+		// but not others. Perhaps, it would be desired to add other NumPad* keycodes
+		// to let user combine them with System.NumLock mod to know the correct key state.
+		switch (key) {
+		case AGS3::eAGSKeyCodeAsterisk:
+			kc[1] = Common::KEYCODE_KP_MULTIPLY;
+			break;
+		case AGS3::eAGSKeyCodePlus:
+			kc[1] = Common::KEYCODE_KP_PLUS;
+			break;
+		case AGS3::eAGSKeyCodeHyphen:
+			kc[1] = Common::KEYCODE_KP_MINUS;
+			break;
+		case AGS3::eAGSKeyCodeForwardSlash:
+			kc[1] = Common::KEYCODE_KP_DIVIDE;
+			break;
+		default:
+			break;
+		}
 		return true;
 	}
 
-	// Other keys are mapped directly to scancode (based on [sonneveld]'s code)
+	// Handle keys that are mapped directly to scancode (based on [sonneveld]'s code),
+	// and those that require special treatment (multiple scancode variants)
 	switch (key) {
+	// Various service keys
 	case AGS3::eAGSKeyCodeBackspace:
 		kc[0] = Common::KEYCODE_BACKSPACE;
 		return true;
@@ -206,6 +231,7 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 		kc[0] = Common::KEYCODE_ESCAPE;
 		return true;
 
+	// Functional keys
 	case AGS3::eAGSKeyCodeF1:
 		kc[0] = Common::KEYCODE_F1;
 		return true;
@@ -243,6 +269,7 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 		kc[0] = Common::KEYCODE_F12;
 		return true;
 
+	// Keys that may be found on both main keyboard and NumPad
 	case AGS3::eAGSKeyCodeHome:
 		kc[0] = Common::KEYCODE_KP7;
 		kc[1] = Common::KEYCODE_HOME;
@@ -287,6 +314,7 @@ bool EventsManager::ags_key_to_scancode(AGS3::eAGSKeyCode key, Common::KeyCode(&
 		kc[1] = Common::KEYCODE_DELETE;
 		return true;
 
+	// Modifier keys
 	case AGS3::eAGSKeyCodeLShift:
 		kc[0] = Common::KEYCODE_LSHIFT;
 		return true;


Commit: d60cccd3e075092f987c33e361406c286474fd4c
    https://github.com/scummvm/scummvm/commit/d60cccd3e075092f987c33e361406c286474fd4c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.17 RC2)

Partially from upstream d07367d2c8ad704c97c5638f0784d0a05f2314bf

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 d1266f7afd8..026beb0acfc 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.15"
+#define ACI_VERSION_STR      "3.6.1.17"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.15
+#define ACI_VERSION_MSRC_DEF  3.6.1.17
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 87c0bcdf7fd4e9d9e40f07afaccebdc27b0ba243
    https://github.com/scummvm/scummvm/commit/87c0bcdf7fd4e9d9e40f07afaccebdc27b0ba243
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed CharacterInfo breaking script compatibility again

This fixes a mistake introduced in commit 4be1556, which in turn followed b02814b

The problem is, that CCStaticArray does not work as intended, and seems won't (at
least not without some extra workarounds).

When the script compiler is told to get an Nth element of a global struct array, such
as character[n], it calculates the memory address as
    Character* ptr = (array address + sizeof(Character) * n).
If this address is used for the read/write operations, these ops can be intercepted by
interpreter and remapped into the real fields (see IScriptObject::ReadN, WriteN
interface).
But if this address is used IN POINTER COMPARISON BY VALUE, then we cannot do
anything. And if our real struct in the engine is stored on a different relative memory
offset than one expected by compiler, then this pointer comparison will fail.
This breaks script expressions like e.g.:
      if (player == character[n])
>From upstream 97ed18e95b587544ae47497f0a5f1d1ba00a7e29

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/character_info_engine.cpp
    engines/ags/engine/ac/dynobj/cc_static_array.h
    engines/ags/engine/ac/global_audio.cpp
    engines/ags/engine/ac/global_character.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/shared/ac/character_info.cpp
    engines/ags/shared/ac/character_info.h
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct_base.h
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index f9b47e2aa86..24caf3a76af 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -162,7 +162,7 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 	int move_speed_x, move_speed_y;
 	chaa->get_effective_walkspeeds(move_speed_x, move_speed_y);
 	if ((move_speed_x == 0) && (move_speed_y == 0)) {
-		debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname.GetCStr());
+		debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname);
 	}
 
 	// There's an issue: the existing movelist is converted to room resolution,
@@ -240,7 +240,7 @@ void Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, in
 		chaa->room = room;
 
 		debug_script_log("%s moved to room %d, location %d,%d, loop %d",
-		                 chaa->scrname.GetCStr(), room, chaa->x, chaa->y, chaa->loop);
+		                 chaa->scrname, room, chaa->x, chaa->y, chaa->loop);
 
 		return;
 	}
@@ -279,7 +279,7 @@ void Character_ChangeView(CharacterInfo *chap, int vii) {
 		chap->idleleft = chap->idletime;
 	}
 
-	debug_script_log("%s: Change view to %d", chap->scrname.GetCStr(), vii + 1);
+	debug_script_log("%s: Change view to %d", chap->scrname, vii + 1);
 	chap->defview = vii;
 	chap->view = vii;
 	stop_character_anim(chap);
@@ -378,7 +378,7 @@ void FaceDirectionalLoop(CharacterInfo *char1, int direction, int blockingStyle)
 }
 
 void FaceLocationXY(CharacterInfo *char1, int xx, int yy, int blockingStyle) {
-	debug_script_log("%s: Face location %d,%d", char1->scrname.GetCStr(), xx, yy);
+	debug_script_log("%s: Face location %d,%d", char1->scrname, xx, yy);
 
 	const int diffrx = xx - char1->x;
 	const int diffry = yy - char1->y;
@@ -439,9 +439,9 @@ void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int
 		quit("!FollowCharacterEx: you cannot tell the player character to follow a character in another room");
 
 	if (tofollow != nullptr) {
-		debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname.GetCStr(), tofollow->scrname.GetCStr(), distaway, eagerness);
+		debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname, tofollow->scrname, distaway, eagerness);
 	} else {
-		debug_script_log("%s: Stop following other character", chaa->scrname.GetCStr());
+		debug_script_log("%s: Stop following other character", chaa->scrname);
 	}
 
 	if ((chaa->following >= 0) &&
@@ -559,7 +559,7 @@ 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.GetCStr(), vii + 1);
+	debug_script_log("%s: View locked to %d", chap->scrname, vii + 1);
 	if (chap->idleleft < 0) {
 		Character_UnlockView(chap);
 		chap->idleleft = chap->idletime;
@@ -688,7 +688,7 @@ void Character_PlaceOnWalkableArea(CharacterInfo *chap) {
 void Character_RemoveTint(CharacterInfo *chaa) {
 
 	if (chaa->flags & (CHF_HASTINT | CHF_HASLIGHT)) {
-		debug_script_log("Un-tint %s", chaa->scrname.GetCStr());
+		debug_script_log("Un-tint %s", chaa->scrname);
 		chaa->flags &= ~(CHF_HASTINT | CHF_HASLIGHT);
 	} else {
 		debug_script_warn("Character.RemoveTint called but character was not tinted");
@@ -735,7 +735,7 @@ void Character_SetAsPlayer(CharacterInfo *chaa) {
 
 	//update_invorder();
 
-	debug_script_log("%s is new player character", _G(playerchar)->scrname.GetCStr());
+	debug_script_log("%s is new player character", _G(playerchar)->scrname);
 
 	// Within game_start, return now
 	if (_G(displayed_room) < 0)
@@ -787,9 +787,9 @@ void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime) {
 		chaa->wait = 0;
 
 	if (iview >= 1) {
-		debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname.GetCStr(), iview, itime);
+		debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname, iview, itime);
 	} else {
-		debug_script_log("%s idle view disabled", chaa->scrname.GetCStr());
+		debug_script_log("%s idle view disabled", chaa->scrname);
 	}
 	if (chaa->flags & CHF_FIXVIEW) {
 		debug_script_warn("SetCharacterIdle called while character view locked with SetCharacterView; idle ignored");
@@ -893,7 +893,7 @@ void Character_StopMoving(CharacterInfo *charp) {
 		if ((_GP(mls)[charp->walking].direct == 0) && (charp->room == _G(displayed_room)))
 			Character_PlaceOnWalkableArea(charp);
 
-		debug_script_log("%s: stop moving", charp->scrname.GetCStr());
+		debug_script_log("%s: stop moving", charp->scrname);
 
 		charp->idleleft = charp->idletime;
 		// restart the idle animation straight away
@@ -914,7 +914,7 @@ void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opaci
 	        (luminance < 0) || (luminance > 100))
 		quit("!Character.Tint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100");
 
-	debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname.GetCStr(), red, green, blue, opacity);
+	debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname, red, green, blue, opacity);
 
 	_GP(charextra)[chaa->index_id].tint_r = red;
 	_GP(charextra)[chaa->index_id].tint_g = green;
@@ -935,7 +935,7 @@ void Character_UnlockView(CharacterInfo *chaa) {
 
 void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) {
 	if (chaa->flags & CHF_FIXVIEW) {
-		debug_script_log("%s: Released view back to default", chaa->scrname.GetCStr());
+		debug_script_log("%s: Released view back to default", chaa->scrname);
 	}
 	chaa->flags &= ~CHF_FIXVIEW;
 	chaa->view = chaa->defview;
@@ -1200,7 +1200,7 @@ int Character_GetID(CharacterInfo *chaa) {
 }
 
 const char *Character_GetScriptName(CharacterInfo *chin) {
-	return CreateNewScriptString(chin->scrname);
+	return CreateNewScriptString(_GP(game).chars2[chin->index_id].scrname_new);
 }
 
 int Character_GetFrame(CharacterInfo *chaa) {
@@ -1345,13 +1345,13 @@ int Character_GetDestinationY(CharacterInfo *chaa) {
 }
 
 const char *Character_GetName(CharacterInfo *chaa) {
-	return CreateNewScriptString(chaa->name.GetCStr());
+	return CreateNewScriptString(_GP(game).chars2[chaa->index_id].name_new.GetCStr());
 }
 
 void Character_SetName(CharacterInfo *chaa, const char *newName) {
-	chaa->name = newName;
+	_GP(game).chars2[chaa->index_id].name_new = newName;
 	// Fill legacy name fields, for compatibility with old scripts and plugins
-	snprintf(chaa->legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", newName);
+	snprintf(chaa->name, LEGACY_MAX_CHAR_NAME_LEN, "%s", newName);
 	GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 }
 
@@ -1606,7 +1606,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 
 	if ((tox == chin->x) && (toy == chin->y)) {
 		StopMoving(chac);
-		debug_script_log("%s already at destination, not moving", chin->scrname.GetCStr());
+		debug_script_log("%s already at destination, not moving", chin->scrname);
 		return;
 	}
 
@@ -1637,12 +1637,12 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
 	chin->frame = oldframe;
 	// use toxPassedIn cached variable so the hi-res co-ordinates
 	// are still displayed as such
-	debug_script_log("%s: Start move to %d,%d", chin->scrname.GetCStr(), tox, toy);
+	debug_script_log("%s: Start move to %d,%d", chin->scrname, tox, toy);
 
 	int move_speed_x, move_speed_y;
 	chin->get_effective_walkspeeds(move_speed_x, move_speed_y);
 	if ((move_speed_x == 0) && (move_speed_y == 0)) {
-		debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname.GetCStr());
+		debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname);
 	}
 
 	// Convert src and dest coords to the mask resolution, for pathfinder
@@ -1841,7 +1841,7 @@ int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *che
 			chi->x = xwas;
 			chi->y = ywas;
 		}
-		debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname.GetCStr(), _GP(game).chars[ntf].scrname.GetCStr());
+		debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname, _GP(game).chars[ntf].scrname);
 		return 1;
 	}
 	return 0;
@@ -2060,12 +2060,12 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int n
 		(loopn < 0) || (loopn >= _GP(views)[chap->view].numLoops)) {
 		quitprintf("!AnimateCharacter: invalid view and/or loop\n"
 				   "(trying to animate '%s' using view %d (range is 1..%d) and loop %d (view has %d loops)).",
-				   chap->scrname.GetCStr(), chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
+				   chap->scrname, chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
 	}
 	// NOTE: there's always frame 0 allocated for safety
 	sframe = std::max(0, std::min(sframe, _GP(views)[chap->view].loops[loopn].numFrames - 1));
 	debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
-					 chap->scrname.GetCStr(), chap->view + 1, loopn, sppd, rept, sframe);
+					 chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
 
 	Character_StopMoving(chap);
 
@@ -2144,11 +2144,11 @@ void update_character_scale(int charid) {
 	CharacterExtras &chex = _GP(charextra)[charid];
 	if (chin.view < 0) {
 		quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
-				   chin.scrname.GetCStr(), _G(displayed_room));
+				   chin.scrname, _G(displayed_room));
 	}
 	if (chin.loop >= _GP(views)[chin.view].numLoops) {
 		quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
-				   chin.scrname.GetCStr(), chin.loop, chin.view + 1);
+				   chin.scrname, chin.loop, chin.view + 1);
 	}
 	// If frame is too high -- fallback to the frame 0;
 	// there's always at least 1 dummy frame at index 0
@@ -2465,11 +2465,11 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			charFrameWas = speakingChar->frame;
 
 		if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
-			quitprintf("!Character %s current view %d is invalid, or has no loops.", speakingChar->scrname.GetCStr(), speakingChar->view + 1);
+			quitprintf("!Character %s current view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
 		// If current view is missing a loop - use loop 0
 		if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
 			debug_script_warn("WARNING: Character %s current view %d does not have necessary loop %d; switching to loop 0.",
-							  speakingChar->scrname.GetCStr(), speakingChar->view + 1, speakingChar->loop);
+							  speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
 			speakingChar->loop = 0;
 		}
 
@@ -2705,11 +2705,11 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			speakingChar->flags |= CHF_FIXVIEW;
 
 			if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
-				quitprintf("!Character %s speech view %d is invalid, or has no loops.", speakingChar->scrname.GetCStr(), speakingChar->view + 1);
+				quitprintf("!Character %s speech view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
 			// If speech view is missing a loop - use loop 0
 			if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
 				debug_script_warn("WARNING: Character %s speech view %d does not have necessary loop %d; switching to loop 0.",
-								  speakingChar->scrname.GetCStr(), speakingChar->view + 1, speakingChar->loop);
+								  speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
 				speakingChar->loop = 0;
 			}
 
diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 02f40e2b010..05c358e8270 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -85,7 +85,7 @@ void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, st
 			// view has no frames?!
 			// amazingly enough there are old games that allow this to happen...
 			if (_G(loaded_game_file_version) >= kGameVersion_300)
-				quitprintf("!Character %s is assigned view %d that has no frames!", scrname.GetCStr(), view);
+				quitprintf("!Character %s is assigned view %d that has no frames!", scrname, view);
 			loop = 0;
 		}
 	}
@@ -208,7 +208,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 		}
 
 		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.GetCStr(), loop, view + 1);
+			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;
@@ -219,7 +219,7 @@ void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *ch
 				frame = 0;
 
 			if (framesInLoop < 1)
-				quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, scrname.GetCStr(), loop);
+				quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, scrname, loop);
 		}
 
 		doing_nothing = 0; // still walking?
@@ -433,7 +433,7 @@ void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_noth
 		idleleft--;
 		if (idleleft == -1) {
 			int useloop = loop;
-			debug_script_log("%s: Now idle (view %d)", scrname.GetCStr(), idleview + 1);
+			debug_script_log("%s: Now idle (view %d)", scrname, idleview + 1);
 			Character_LockView(this, idleview + 1);
 			// SetCharView resets it to 0
 			idleleft = -2;
diff --git a/engines/ags/engine/ac/dynobj/cc_static_array.h b/engines/ags/engine/ac/dynobj/cc_static_array.h
index eec5b74bb5e..f7160963630 100644
--- a/engines/ags/engine/ac/dynobj/cc_static_array.h
+++ b/engines/ags/engine/ac/dynobj/cc_static_array.h
@@ -26,6 +26,22 @@
 // real element size in the engine's memory.
 // The purpose of this is to remove size restriction from the engine's structs
 // exposed to scripts.
+//
+// FIXME: [ivan-mogilko] the above was meant to work, but in reality it doesn't
+// and won't, at least not without some extra workarounds.
+// The problem that I missed here is following:
+//   when the script compiler is told to get an Nth element of a global struct
+//   array, such as character[n], it calculates the memory address as
+//   array address + sizeof(Character) * n.
+//   If this address is used for the read/write operations, these ops can be
+//   intercepted by interpreter and remapped into the real fields
+//      (see IScriptObject::ReadN, WriteN interface)
+//   But if this address is used IN POINTER COMPARISON, then we cannot do
+//   anything. And if our real struct in the engine is stored on a different
+//   relative memory offset than one expected by compiler, then this pointer
+//   comparison will fail, e.g. script expression like
+//      if (player == character[n])
+//
 // NOTE: on the other hand, similar effect could be achieved by separating
 // object data into two or more structs, where "base" structs are stored in
 // the exposed arrays (part of API), while extending structs are stored
diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp
index 869089f110a..5731019168d 100644
--- a/engines/ags/engine/ac/global_audio.cpp
+++ b/engines/ags/engine/ac/global_audio.cpp
@@ -461,10 +461,10 @@ String get_cue_filename(int charid, int sndid) {
 	String script_name;
 	if (charid >= 0) {
 		// append the first 4 characters of the script name to the filename
-		if (_GP(game).chars[charid].scrname[0] == 'c')
-			script_name.SetString(_GP(game).chars[charid].scrname.GetCStr() + 1, 4);
+		if (_GP(game).chars2[charid].scrname_new[0] == 'c')
+			script_name.SetString(_GP(game).chars2[charid].scrname_new.GetCStr() + 1, 4);
 		else
-			script_name.SetString(_GP(game).chars[charid].scrname.GetCStr(), 4);
+			script_name.SetString(_GP(game).chars2[charid].scrname_new.GetCStr(), 4);
 	} else {
 		script_name = "NARR";
 	}
diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp
index 39530e83235..eb4bc181119 100644
--- a/engines/ags/engine/ac/global_character.cpp
+++ b/engines/ags/engine/ac/global_character.cpp
@@ -119,7 +119,7 @@ int GetCharacterWidth(int ww) {
 		if ((char1->view < 0) ||
 			(char1->loop >= _GP(views)[char1->view].numLoops) ||
 			(char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
-			debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname.GetCStr(), char1->view + 1, char1->loop, char1->frame);
+			debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
 			return data_to_game_coord(4);
 		}
 
@@ -136,7 +136,7 @@ int GetCharacterHeight(int charid) {
 		        (char1->loop >= _GP(views)[char1->view].numLoops) ||
 		        (char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
 			debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d",
-							  char1->scrname.GetCStr(), char1->view + 1, char1->loop, char1->frame);
+							  char1->scrname, char1->view + 1, char1->loop, char1->frame);
 			return data_to_game_coord(2);
 		}
 
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 9211be01210..fafaea8b17c 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -605,7 +605,7 @@ void GetLocationName(int xxx, int yyy, char *tempo) {
 	// on character
 	if (loctype == LOCTYPE_CHAR) {
 		onhs = _G(getloctype_index);
-		snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).chars[onhs].name.GetCStr()));
+		snprintf(tempo, MAX_MAXSTRLEN, "%s", get_translation(_GP(game).chars2[onhs].name_new.GetCStr()));
 		if (_GP(play).get_loc_name_last_time != 2000 + onhs)
 			GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
 		_GP(play).get_loc_name_last_time = 2000 + onhs;
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 495d378608c..99d912e8875 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -300,7 +300,7 @@ size_t check_scstrcapacity(const char *ptr) {
 	const void *charstart = &_GP(game).chars[0];
 	const void *charend = &_GP(game).chars[0] + _GP(game).chars.size();
 	if ((ptr >= charstart) && (ptr <= charend))
-		return sizeof(CharacterInfo::legacy_name);
+		return sizeof(CharacterInfo::name);
 	return MAX_MAXSTRLEN;
 }
 
@@ -311,7 +311,7 @@ void commit_scstr_update(const char *ptr) {
 	const void *charend = &_GP(game).chars[0] + _GP(game).chars.size();
 	if ((ptr >= charstart) && (ptr <= charend)) {
 		size_t char_index = ((uintptr_t)ptr - (uintptr_t)charstart) / sizeof(CharacterInfo);
-		_GP(game).chars[char_index].name = _GP(game).chars[char_index].legacy_name;
+		_GP(game).chars2[char_index].name_new = _GP(game).chars[char_index].name;
 	}
 }
 
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 375a515a6d9..a70f61aa7e5 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -121,7 +121,7 @@ void InitAndRegisterCharacters(GameSetupStruct &game) {
 		ccRegisterManagedObject(&game.chars[i], &_GP(ccDynamicCharacter));
 
 		// export the character's script object
-		ccAddExternalScriptObject(game.chars[i].scrname, &game.chars[i], &_GP(ccDynamicCharacter));
+		ccAddExternalScriptObject(game.chars2[i].scrname_new, &game.chars[i], &_GP(ccDynamicCharacter));
 	}
 }
 
@@ -208,7 +208,7 @@ void InitAndRegisterRegions() {
 
 // Registers static entity arrays in the script system
 void RegisterStaticArrays(GameSetupStruct &game) {
-	_GP(StaticCharacterArray).Create(&_GP(ccDynamicCharacter), sizeof(CharacterInfoBase), sizeof(CharacterInfo));
+	_GP(StaticCharacterArray).Create(&_GP(ccDynamicCharacter), sizeof(CharacterInfo), sizeof(CharacterInfo));
 	_GP(StaticObjectArray).Create(&_GP(ccDynamicObject), sizeof(ScriptObject), sizeof(ScriptObject));
 	_GP(StaticGUIArray).Create(&_GP(ccDynamicGUI), sizeof(ScriptGUI), sizeof(ScriptGUI));
 	_GP(StaticHotspotArray).Create(&_GP(ccDynamicHotspot), sizeof(ScriptHotspot), sizeof(ScriptHotspot));
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 64c8001ef40..65589e4e19a 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -487,7 +487,7 @@ HSaveError ReadInteraction272(Interaction &intr, Stream *in) {
 HSaveError WriteCharacters(Stream *out) {
 	out->WriteInt32(_GP(game).numcharacters);
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].WriteToSavegame(out);
+		_GP(game).chars[i].WriteToSavegame(out, _GP(game).chars2[i]);
 		_GP(charextra)[i].WriteToSavegame(out);
 		Properties::WriteValues(_GP(play).charProps[i], out);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
@@ -501,7 +501,7 @@ HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Pr
 	if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
 		return err;
 	for (int i = 0; i < _GP(game).numcharacters; ++i) {
-		_GP(game).chars[i].ReadFromSavegame(in, static_cast<CharacterSvgVersion>(cmp_ver));
+		_GP(game).chars[i].ReadFromSavegame(in, _GP(game).chars2[i], static_cast<CharacterSvgVersion>(cmp_ver));
 		_GP(charextra)[i].ReadFromSavegame(in, static_cast<CharacterSvgVersion>(cmp_ver));
 		Properties::ReadValues(_GP(play).charProps[i], in);
 		if (_G(loaded_game_file_version) <= kGameVersion_272)
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 487f7e15cff..1d3b7d707c8 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -375,7 +375,7 @@ bool run_service_key_controls(KeyInput &out_key) {
 			ln = strlen(bigbuffer);
 			Common::sprintf_s(&bigbuffer[ln], sizeof(bigbuffer) - ln,
 			        "%s (view/loop/frm:%d,%d,%d  x/y/z:%d,%d,%d  idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[",
-			        _GP(game).chars[chd].legacy_scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
+			        _GP(game).chars[chd].scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
 			        _GP(game).chars[chd].x, _GP(game).chars[chd].y, _GP(game).chars[chd].z,
 			        _GP(game).chars[chd].idleview, _GP(game).chars[chd].idletime, _GP(game).chars[chd].idleleft,
 			        _GP(game).chars[chd].walking, _GP(game).chars[chd].animating, _GP(game).chars[chd].following,
diff --git a/engines/ags/shared/ac/character_info.cpp b/engines/ags/shared/ac/character_info.cpp
index 2a970b9e752..6dd5bcbc662 100644
--- a/engines/ags/shared/ac/character_info.cpp
+++ b/engines/ags/shared/ac/character_info.cpp
@@ -122,10 +122,10 @@ void CharacterInfo::WriteBaseFields(Stream *out) const {
 	out->WriteInt16(acty);
 }
 
-void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver) {
+void CharacterInfo::ReadFromFile(Stream *in, CharacterInfo2 &chinfo2, GameDataVersion data_ver) {
 	ReadBaseFields(in);
-	StrUtil::ReadCStrCount(legacy_name, in, LEGACY_MAX_CHAR_NAME_LEN);
-	StrUtil::ReadCStrCount(legacy_scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
+	StrUtil::ReadCStrCount(name, in, LEGACY_MAX_CHAR_NAME_LEN);
+	StrUtil::ReadCStrCount(scrname, in, LEGACY_MAX_SCRIPT_NAME_LEN);
 	on = in->ReadInt8();
 	in->ReadInt8(); // alignment padding to int32
 
@@ -133,27 +133,27 @@ void CharacterInfo::ReadFromFile(Stream *in, GameDataVersion data_ver) {
 	if (data_ver < kGameVersion_360_16) {
 		idle_anim_speed = animspeed + 5;
 	}
-	// Assign names from legacy fields
-	name = legacy_name;
-	scrname = legacy_scrname;
+	// Assign unrestricted names from legacy fields
+	chinfo2.name_new = name;
+	chinfo2.scrname_new = scrname;
 }
 
 void CharacterInfo::WriteToFile(Stream *out) const {
 	WriteBaseFields(out);
-	out->Write(legacy_name, LEGACY_MAX_CHAR_NAME_LEN);
-	out->Write(legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
+	out->Write(name, LEGACY_MAX_CHAR_NAME_LEN);
+	out->Write(scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
 	out->WriteInt8(on);
 	out->WriteInt8(0); // alignment padding to int32
 }
 
-void CharacterInfo::ReadFromSavegame(Stream *in, CharacterSvgVersion save_ver) {
+void CharacterInfo::ReadFromSavegame(Stream *in, CharacterInfo2 &chinfo2, CharacterSvgVersion save_ver) {
 	ReadBaseFields(in);
 	if (save_ver < kCharSvgVersion_36115) { // Fixed-size name and scriptname
-		name.ReadCount(in, LEGACY_MAX_CHAR_NAME_LEN);
+		chinfo2.name_new.ReadCount(in, LEGACY_MAX_CHAR_NAME_LEN);
 		in->Seek(LEGACY_MAX_SCRIPT_NAME_LEN); // skip legacy scriptname
 											  // (don't overwrite static data from save!)
 	} else {
-		name = StrUtil::ReadString(in);
+		chinfo2.name_new = StrUtil::ReadString(in);
 	}
 	on = in->ReadInt8();
 
@@ -163,12 +163,12 @@ void CharacterInfo::ReadFromSavegame(Stream *in, CharacterSvgVersion save_ver) {
 		idle_anim_speed = animspeed + 5;
 	}
 	// Fill legacy name fields, for compatibility with old scripts and plugins
-	snprintf(legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", name.GetCStr());
+	snprintf(name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo2.name_new.GetCStr());
 }
 
-void CharacterInfo::WriteToSavegame(Stream *out) const {
+void CharacterInfo::WriteToSavegame(Stream *out, const CharacterInfo2 &chinfo2) const {
 	WriteBaseFields(out);
-	StrUtil::WriteString(name, out); // kCharSvgVersion_36115
+	StrUtil::WriteString(chinfo2.name_new, out); // kCharSvgVersion_36115
 	out->WriteInt8(on);
 }
 
diff --git a/engines/ags/shared/ac/character_info.h b/engines/ags/shared/ac/character_info.h
index 6078559466a..8b396fd4599 100644
--- a/engines/ags/shared/ac/character_info.h
+++ b/engines/ags/shared/ac/character_info.h
@@ -90,14 +90,31 @@ inline int CharFlagsToObjFlags(int chflags) {
 // Length of deprecated character name field, in bytes
 #define LEGACY_MAX_CHAR_NAME_LEN 40
 
-// CharacterInfoBase contains original set of character fields.
-// It's picked out from CharacterInfo for convenience of adding
-// new design-time fields; easier to maintain backwards compatibility.
+enum CharacterSvgVersion {
+	kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
+	kCharSvgVersion_350 = 1,     // new movelist format (along with pathfinder)
+	kCharSvgVersion_36025 = 2,   // animation volume
+	kCharSvgVersion_36109 = 3,   // removed movelists, save externally
+	kCharSvgVersion_36115 = 4,   // no limit on character name's length
+};
+
+
+// Predeclare a design-time Character extension
+struct CharacterInfo2;
+// Predeclare a runtime Character extension (TODO: refactor and remove this from here)
+struct CharacterExtras;
+
+// CharacterInfo is a design-time Character data.
+// Contains original set of character fields.
 // IMPORTANT: exposed to script API, and plugin API as AGSCharacter!
-// For older script compatibility the struct also has to maintain its size;
-// do not extend or change existing fields, unless planning breaking compatibility.
-// Prefer to use CharacterInfo or CharacterExtras struct for any extensions.
-struct CharacterInfoBase {
+// For older script compatibility the struct also has to maintain its size,
+// and be stored in a plain array to keep the relative memory address offsets
+// between the Character objects!
+// Do not add or change existing fields, unless planning breaking compatibility.
+// Prefer to use CharacterInfo2 and CharacterExtras structs for any extensions.
+//
+// TODO: must refactor, some parts of it should be in a runtime Character class.
+struct CharacterInfo {
 	int   defview;
 	int   talkview;
 	int   view;
@@ -132,26 +149,9 @@ struct CharacterInfoBase {
 	short actx, acty;
 	// These two name fields are deprecated, but must stay here
 	// for compatibility with old scripts and plugin API
-	char legacy_name[LEGACY_MAX_CHAR_NAME_LEN];
-	char legacy_scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
+	char name[LEGACY_MAX_CHAR_NAME_LEN];
+	char scrname[LEGACY_MAX_SCRIPT_NAME_LEN];
 	int8  on;
-};
-
-enum CharacterSvgVersion {
-	kCharSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
-	kCharSvgVersion_350	    = 1, // new movelist format (along with pathfinder)
-	kCharSvgVersion_36025   = 2, // animation volume
-	kCharSvgVersion_36109   = 3, // removed movelists, save externally
-	kCharSvgVersion_36115	= 4, // no limit on character name's length
-};
-
-struct CharacterExtras;
-
-// Design-time Character data.
-// TODO: must refactor, some parts of it should be in a runtime Character class.
-struct CharacterInfo : public CharacterInfoBase {
-	AGS::Shared::String scrname;
-	AGS::Shared::String name;
 
 	int get_effective_y() const;   // return Y - Z
 	int get_baseline() const;      // return baseline, or Y if not set
@@ -205,11 +205,11 @@ struct CharacterInfo : public CharacterInfoBase {
 	void update_character_idle(CharacterExtras *chex, int &doing_nothing);
 	void update_character_follower(int &char_index, std::vector<int> &followingAsSheep, int &doing_nothing);
 
-	void ReadFromFile(Shared::Stream *in, GameDataVersion data_ver);
+	void ReadFromFile(Shared::Stream *in, CharacterInfo2 &chinfo2, GameDataVersion data_ver);
 	void WriteToFile(Shared::Stream *out) const;
 	// TODO: move to runtime-only class (?)
-	void ReadFromSavegame(Shared::Stream *in, CharacterSvgVersion save_ver);
-	void WriteToSavegame(Shared::Stream *out) const;
+	void ReadFromSavegame(Shared::Stream *in, CharacterInfo2 &chinfo2, CharacterSvgVersion save_ver);
+	void WriteToSavegame(Shared::Stream *out, const CharacterInfo2 &chinfo2) const;
 
 private:
 	// Helper functions that read and write first data fields,
@@ -218,6 +218,15 @@ private:
 	void WriteBaseFields(Shared::Stream *out) const;
 };
 
+
+// Design-time Character extended fields
+struct CharacterInfo2 {
+	// Unrestricted scriptname and name fields
+	AGS::Shared::String scrname_new;
+	AGS::Shared::String name_new;
+};
+
+
 #if defined (OBSOLETE)
 struct OldCharacterInfo {
 	int   defview;
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 7b321a2222b..3fadc6cccba 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -205,7 +205,7 @@ void GameSetupStruct::WriteMouseCursors(Stream *out) {
 
 void GameSetupStruct::read_characters(Shared::Stream *in) {
 	chars.resize(numcharacters);
-
+	chars2.resize(numcharacters);
 	ReadCharacters(in);
 }
 
@@ -238,7 +238,7 @@ void GameSetupStruct::read_messages(Shared::Stream *in, const std::array<int> &l
 
 void GameSetupStruct::ReadCharacters(Stream *in) {
 	for (int i = 0; i < numcharacters; ++i) {
-		chars[i].ReadFromFile(in, _G(loaded_game_file_version));
+		chars[i].ReadFromFile(in, chars2[i], _G(loaded_game_file_version));
 	}
 }
 
diff --git a/engines/ags/shared/ac/game_setup_struct_base.h b/engines/ags/shared/ac/game_setup_struct_base.h
index 391b12919ca..c4c209ec83b 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.h
+++ b/engines/ags/shared/ac/game_setup_struct_base.h
@@ -85,6 +85,7 @@ struct GameSetupStructBase {
 	String			  messages[MAXGLOBALMES];
 	std::unique_ptr<WordsDictionary> dict;
 	std::vector<CharacterInfo> chars;
+	std::vector<CharacterInfo2> chars2; // extended character fields
 
 	GameSetupStructBase();
 	GameSetupStructBase(GameSetupStructBase &&gss) = default;
diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 29f82b50145..01587310f22 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -560,18 +560,19 @@ void UpgradeAudio(GameSetupStruct &game, LoadedGameEntities &ents, GameDataVersi
 // Convert character data to the current version
 void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) {
 	auto &chars = _GP(game).chars;
+	auto &chars2 = _GP(game).chars2;
 	const int numcharacters = _GP(game).numcharacters;
 
-	// Fixup charakter script names for 2.x (EGO -> cEgo)
+	// Fixup character script names for 2.x (EGO -> cEgo)
 	if (data_ver <= kGameVersion_272) {
 		char namelwr[LEGACY_MAX_SCRIPT_NAME_LEN];
 		for (int i = 0; i < numcharacters; i++) {
-			if (chars[i].legacy_scrname[0] == 0)
+			if (chars[i].scrname[0] == 0)
 				continue;
-			memcpy(namelwr, chars[i].legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
+			memcpy(namelwr, chars[i].scrname, LEGACY_MAX_SCRIPT_NAME_LEN);
 			ags_strlwr(namelwr + 1); // lowercase starting with the second char
-			snprintf(chars[i].legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "c%s", namelwr);
-			chars[i].scrname = chars[i].legacy_scrname;
+			snprintf(chars[i].scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "c%s", namelwr);
+			chars2[i].scrname_new = chars[i].scrname;
 		}
 	}
 
@@ -745,12 +746,14 @@ HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
 		size_t num_chars = _in->ReadInt32();
 		if (num_chars != _ents.Game.chars.size())
 			return new Error(String::FromFormat("Mismatching number of characters: read %zu expected %zu", num_chars, _ents.Game.chars.size()));
-		for (CharacterInfo &chinfo : _ents.Game.chars) {
-			chinfo.scrname = StrUtil::ReadString(_in);
-			chinfo.name = StrUtil::ReadString(_in);
+		for (int i = 0; i < _ents.Game.numcharacters; ++i) {
+			auto &chinfo = _ents.Game.chars[i];
+			auto &chinfo2 = _ents.Game.chars2[i];
+			chinfo2.scrname_new = StrUtil::ReadString(_in);
+			chinfo2.name_new = StrUtil::ReadString(_in);
 			// assign to the legacy fields for compatibility with old plugins
-			snprintf(chinfo.legacy_scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "%s", chinfo.scrname.GetCStr());
-			snprintf(chinfo.legacy_name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo.name.GetCStr());
+			snprintf(chinfo.scrname, LEGACY_MAX_SCRIPT_NAME_LEN, "%s", chinfo2.scrname_new.GetCStr());
+			snprintf(chinfo.name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo2.name_new.GetCStr());
 		}
 		size_t num_invitems = _in->ReadInt32();
 		if (num_invitems != _ents.Game.numinvitems)


Commit: 3e276ee58c74e0e920ab509c5e0233be1551458b
    https://github.com/scummvm/scummvm/commit/3e276ee58c74e0e920ab509c5e0233be1551458b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: assert text_window_ds before using

>From upstream 5830ae32bf896940effaf357b576950ad161a7fa

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


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 137c1ae4526..6e0ea7f7610 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -707,7 +707,7 @@ int get_textwindow_padding(int ifnum) {
 
 void draw_text_window(Bitmap **text_window_ds, bool should_free_ds,
                       int *xins, int *yins, int *xx, int *yy, int *wii, color_t *set_text_color, int ovrheight, int ifnum) {
-
+	assert(text_window_ds);
 	Bitmap *ds = *text_window_ds;
 	if (ifnum < 0)
 		ifnum = _GP(game).options[OPT_TWCUSTOM];
@@ -750,7 +750,7 @@ void draw_text_window(Bitmap **text_window_ds, bool should_free_ds,
 
 void draw_text_window_and_bar(Bitmap **text_window_ds, bool should_free_ds,
                               int *xins, int *yins, int *xx, int *yy, int *wii, color_t *set_text_color, int ovrheight, int ifnum) {
-
+	assert(text_window_ds);
 	draw_text_window(text_window_ds, should_free_ds, xins, yins, xx, yy, wii, set_text_color, ovrheight, ifnum);
 
 	if ((_GP(topBar).wantIt) && (text_window_ds && *text_window_ds)) {


Commit: ee72f2a6f541457094635efee55aee9c304f70de
    https://github.com/scummvm/scummvm/commit/ee72f2a6f541457094635efee55aee9c304f70de
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: Interactions ensure no self assignment

>From upstream 2720f8d6845b86be8745587197c78fb68e7a0919

Changed paths:
    engines/ags/shared/game/interactions.cpp


diff --git a/engines/ags/shared/game/interactions.cpp b/engines/ags/shared/game/interactions.cpp
index c57ae6eaee9..4d689acdd00 100644
--- a/engines/ags/shared/game/interactions.cpp
+++ b/engines/ags/shared/game/interactions.cpp
@@ -112,7 +112,11 @@ void InteractionCommand::Write(Stream *out) const {
 	out->WriteInt32(0);  // skip 32-bit Parent pointer
 }
 
-InteractionCommand &InteractionCommand::operator = (const InteractionCommand &ic) {
+InteractionCommand &InteractionCommand::operator=(const InteractionCommand &ic) {
+	if (this == &ic) {
+		return *this; // prevent self-assignment
+	}
+
 	Type = ic.Type;
 	memcpy(Data, ic.Data, sizeof(Data));
 	Children.reset(ic.Children.get() ? new InteractionCommandList(*ic.Children) : nullptr);


Commit: f3db5683ed991539b27e23dba71b075bc4ce0fc9
    https://github.com/scummvm/scummvm/commit/f3db5683ed991539b27e23dba71b075bc4ce0fc9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: spritefile remove unused palette variable

>From upstream 71f24c2be0eacf25ec3ba36a458a8180a47ef219

Changed paths:
    engines/ags/shared/ac/sprite_file.cpp


diff --git a/engines/ags/shared/ac/sprite_file.cpp b/engines/ags/shared/ac/sprite_file.cpp
index 0e46bebcdaa..0793930f881 100644
--- a/engines/ags/shared/ac/sprite_file.cpp
+++ b/engines/ags/shared/ac/sprite_file.cpp
@@ -512,7 +512,6 @@ int SaveSpriteFile(const String &save_to_file,
 
 	std::unique_ptr<Bitmap> temp_bmp; // for disposing temp sprites
 	std::vector<uint8_t> membuf; // for loading raw sprite data
-	std::vector<uint32_t> palette;
 
 	const bool diff_compress =
 		read_from_file &&


Commit: f2f4e9420d9ff181d2ad75cddb8d2e4f2b95e4f6
    https://github.com/scummvm/scummvm/commit/f2f4e9420d9ff181d2ad75cddb8d2e4f2b95e4f6
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: assert parserInput is not null

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


diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 3affc2a252e..98be88b7c3a 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -1030,6 +1030,7 @@ void DialogOptions::Close() {
 	invalidate_screen();
 
 	if (parserActivated) {
+		assert(parserInput);
 		snprintf(_GP(play).lastParserEntry, MAX_MAXSTRLEN, "%s", parserInput->Text.GetCStr());
 		ParseText(parserInput->Text.GetCStr());
 		chose = CHOSE_TEXTPARSER;


Commit: aa45beee50eeab5a22784ddc14c813c78bf56180
    https://github.com/scummvm/scummvm/commit/aa45beee50eeab5a22784ddc14c813c78bf56180
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: ensure Close and Seek from BufferedStream

a pointer could cause it to call parent close due to virtual
>From upstream c8760b810242d6b0c6b4cd5f92057918e13d29a0

Changed paths:
    engines/ags/shared/util/buffered_stream.cpp


diff --git a/engines/ags/shared/util/buffered_stream.cpp b/engines/ags/shared/util/buffered_stream.cpp
index 43e20bbfe20..f40a89849f0 100644
--- a/engines/ags/shared/util/buffered_stream.cpp
+++ b/engines/ags/shared/util/buffered_stream.cpp
@@ -54,7 +54,7 @@ BufferedStream::BufferedStream(const String &file_name, FileOpenMode open_mode,
 }
 
 BufferedStream::~BufferedStream() {
-	Close();
+	BufferedStream::Close();
 }
 
 void BufferedStream::FillBufferFromPosition(soff_t position) {
@@ -201,7 +201,7 @@ BufferedSectionStream::BufferedSectionStream(const String &file_name, soff_t sta
 	start_pos = MIN(start_pos, end_pos);
 	_start = MIN(start_pos, _end);
 	_end = MIN(end_pos, _end);
-	Seek(0, kSeekBegin);
+	BufferedStream::Seek(0, kSeekBegin);
 }
 
 


Commit: bcbdb4714b0041c4d3d3c2c46ff15783bcb4b2ea
    https://github.com/scummvm/scummvm/commit/bcbdb4714b0041c4d3d3c2c46ff15783bcb4b2ea
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: pass by reference in scaling and geometry

>From upstream 2a86ebd7e1d3e493fd9a1323c0e04d4ed51e6bbb

Changed paths:
    engines/ags/shared/util/geometry.h
    engines/ags/shared/util/scaling.h


diff --git a/engines/ags/shared/util/geometry.h b/engines/ags/shared/util/geometry.h
index 6be8a57075b..765814d3362 100644
--- a/engines/ags/shared/util/geometry.h
+++ b/engines/ags/shared/util/geometry.h
@@ -169,15 +169,15 @@ struct Size {
 	}
 
 	// Indicates if current size exceeds other size by any metric
-	inline bool ExceedsByAny(const Size size) const {
+	inline bool ExceedsByAny(const Size &size) const {
 		return Width > size.Width || Height > size.Height;
 	}
 
-	inline bool operator==(const Size size) const {
+	inline bool operator==(const Size &size) const {
 		return Width == size.Width && Height == size.Height;
 	}
 
-	inline bool operator!=(const Size size) const {
+	inline bool operator!=(const Size &size) const {
 		return Width != size.Width || Height != size.Height;
 	}
 
diff --git a/engines/ags/shared/util/scaling.h b/engines/ags/shared/util/scaling.h
index 0d709fcdfff..b3017cf66cd 100644
--- a/engines/ags/shared/util/scaling.h
+++ b/engines/ags/shared/util/scaling.h
@@ -137,19 +137,19 @@ struct PlaneScaling {
 		Y.SetDstOffset(y);
 	}
 
-	inline Point Scale(const Point p) const {
+	inline Point Scale(const Point &p) const {
 		return Point(X.ScalePt(p.X), Y.ScalePt(p.Y));
 	}
 
-	inline Rect ScaleRange(const Rect r) const {
+	inline Rect ScaleRange(const Rect &r) const {
 		return RectWH(X.ScalePt(r.Left), Y.ScalePt(r.Top), X.ScaleDistance(r.GetWidth()), Y.ScaleDistance(r.GetHeight()));
 	}
 
-	inline Point UnScale(const Point p) const {
+	inline Point UnScale(const Point &p) const {
 		return Point(X.UnScalePt(p.X), Y.UnScalePt(p.Y));
 	}
 
-	inline Rect UnScaleRange(const Rect r) const {
+	inline Rect UnScaleRange(const Rect &r) const {
 		return RectWH(X.UnScalePt(r.Left), Y.UnScalePt(r.Top), X.UnScaleDistance(r.GetWidth()), Y.UnScaleDistance(r.GetHeight()));
 	}
 };


Commit: 9fc395f2276e21ad96a4cb1249a0bedc36c215fb
    https://github.com/scummvm/scummvm/commit/9fc395f2276e21ad96a4cb1249a0bedc36c215fb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: add DisplayMessageBox()

Partially from upstream d4bd3dbe21e8cc0a2667055cd6e60c40e768336b

Changed paths:
    engines/ags/engine/platform/base/ags_platform_driver.h
    engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp


diff --git a/engines/ags/engine/platform/base/ags_platform_driver.h b/engines/ags/engine/platform/base/ags_platform_driver.h
index b8eda72afc1..cc41014a0cd 100644
--- a/engines/ags/engine/platform/base/ags_platform_driver.h
+++ b/engines/ags/engine/platform/base/ags_platform_driver.h
@@ -147,6 +147,9 @@ struct AGSPlatformDriver
 	// Formats message and writes to platform's error output;
 	// Always adds trailing '\n' after formatted string
 	virtual void WriteStdErr(const char *fmt, ...);
+	// Display a text in a message box with a "warning" icon.
+	// Platforms which do not support this should do nothing.
+	virtual void DisplayMessageBox(const char *text) = 0;
 	virtual void YieldCPU();
 	// Called when the game window is being switch out from
 	virtual void DisplaySwitchOut();
diff --git a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
index 5df8b756c52..9c2388195c0 100644
--- a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
+++ b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
@@ -30,6 +30,7 @@
 #include "ags/engine/platform/base/ags_platform_driver.h"
 #include "ags/plugins/ags_plugin.h"
 #include "ags/shared/util/string.h"
+#include "gui/message.h"
 
 namespace AGS3 {
 
@@ -40,6 +41,7 @@ struct ScummVMPlatformDriver : AGSPlatformDriver {
 
 	int  CDPlayerCommand(int cmdd, int datt) override;
 	void DisplayAlert(const char *, ...) override;
+	void DisplayMessageBox(const char *) override;
 	FSLocation GetAllUsersDataDirectory() override;
 	FSLocation GetUserSavedgamesDirectory() override;
 	FSLocation GetUserConfigDirectory() override;
@@ -77,6 +79,12 @@ void ScummVMPlatformDriver::DisplayAlert(const char *text, ...) {
 		::AGS::g_vm->GUIError(msg);
 }
 
+void ScummVMPlatformDriver::DisplayMessageBox(const char *text) {
+	Common::U32String msg(text);
+	GUI::MessageDialog dialog(msg);
+	dialog.runModal();
+}
+
 FSLocation ScummVMPlatformDriver::GetAllUsersDataDirectory() {
 	return FSLocation(".");
 }


Commit: 0ce4c45489d5ca372049b79602e4d8777521d188
    https://github.com/scummvm/scummvm/commit/0ce4c45489d5ca372049b79602e4d8777521d188
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: mention "--no-message-box" in help for all platforms

>From upstream e848eeb4392ba995a12d56c708c45cf1be3d1e1f

Changed paths:
    engines/ags/engine/main/main.cpp


diff --git a/engines/ags/engine/main/main.cpp b/engines/ags/engine/main/main.cpp
index 05b890d33a3..32d87e8d94f 100644
--- a/engines/ags/engine/main/main.cpp
+++ b/engines/ags/engine/main/main.cpp
@@ -128,9 +128,7 @@ void main_print_help() {
 	                          "                                 --log-file=all:warn\n"
 	                          "  --log-file-path=PATH         Define custom path for the log file\n"
 	                          //--------------------------------------------------------------------------------|
-#if AGS_PLATFORM_OS_WINDOWS
 	                          "  --no-message-box             Disable alerts as modal message boxes\n"
-#endif
 		                      "  --no-translation             Use default game language on start\n"
 	                          "  --noiface                    Don't draw game GUI\n"
 	                          "  --noscript                   Don't run room scripts; *WARNING:* unreliable\n"


Commit: 8b7b0df59c7d9c49216e6dc8c44771e41ae75e0a
    https://github.com/scummvm/scummvm/commit/8b7b0df59c7d9c49216e6dc8c44771e41ae75e0a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed MAX_WALK_AREAS value

>From upstream 39228483259a05a3e3a5d60bc1330a90bef6ae75

Changed paths:
    engines/ags/engine/ac/game_state.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/global_walkable_area.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/route_finder_impl_legacy.cpp
    engines/ags/engine/ac/walkable_area.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_internal.h
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/shared/game/room_file.cpp
    engines/ags/shared/game/room_struct.cpp
    engines/ags/shared/game/room_struct.h


diff --git a/engines/ags/engine/ac/game_state.cpp b/engines/ags/engine/ac/game_state.cpp
index 7cc1a2a8839..80f3c55b2c8 100644
--- a/engines/ags/engine/ac/game_state.cpp
+++ b/engines/ags/engine/ac/game_state.cpp
@@ -507,7 +507,7 @@ void GameState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameState
 	music_repeat = in->ReadInt32();
 	music_master_volume = in->ReadInt32();
 	digital_master_volume = in->ReadInt32();
-	in->Read(walkable_areas_on, MAX_WALK_AREAS + 1);
+	in->Read(walkable_areas_on, MAX_WALK_AREAS);
 	screen_flipped = in->ReadInt16();
 	if (svg_ver < kGSSvgVersion_350_10) {
 		short offsets_locked = in->ReadInt16();
@@ -729,7 +729,7 @@ void GameState::WriteForSavegame(Stream *out) const {
 	out->WriteInt32(music_repeat);
 	out->WriteInt32(music_master_volume);
 	out->WriteInt32(digital_master_volume);
-	out->Write(walkable_areas_on, MAX_WALK_AREAS + 1);
+	out->Write(walkable_areas_on, MAX_WALK_AREAS);
 	out->WriteInt16(screen_flipped);
 	out->WriteInt32(entered_at_x);
 	out->WriteInt32(entered_at_y);
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 0f976b70866..0325770fbcc 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -190,7 +190,7 @@ struct GameState {
 	int   music_repeat = 0;
 	int   music_master_volume = 0;
 	int   digital_master_volume = 0;
-	char  walkable_areas_on[MAX_WALK_AREAS + 1]{};
+	char  walkable_areas_on[MAX_WALK_AREAS]{};
 	short screen_flipped = 0;
 	int   entered_at_x = 0;
 	int   entered_at_y = 0;
diff --git a/engines/ags/engine/ac/global_walkable_area.cpp b/engines/ags/engine/ac/global_walkable_area.cpp
index 55d576838c5..5695ac945f8 100644
--- a/engines/ags/engine/ac/global_walkable_area.cpp
+++ b/engines/ags/engine/ac/global_walkable_area.cpp
@@ -42,7 +42,7 @@ int GetScalingAt(int x, int y) {
 }
 
 void SetAreaScaling(int area, int min, int max) {
-	if ((area < 0) || (area > MAX_WALK_AREAS))
+	if ((area < 0) || (area >= MAX_WALK_AREAS))
 		quit("!SetAreaScaling: invalid walkalbe area");
 
 	if (min > max)
@@ -91,7 +91,7 @@ int GetWalkableAreaAtRoom(int x, int y) {
 	int area = get_walkable_area_pixel(x, y);
 	// IMPORTANT: disabled walkable areas are actually erased completely from the mask;
 	// see: RemoveWalkableArea() and RestoreWalkableArea().
-	return area >= 0 && area < (MAX_WALK_AREAS + 1) ? area : 0;
+	return (area >= 0 && area < MAX_WALK_AREAS) ? area : 0;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 08e5359b515..11ae29ea421 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -267,7 +267,7 @@ void unload_old_room() {
 		save_room_data_segment();
 		FreeRoomScriptInstance();
 	} else _G(croom)->tsdatasize = 0;
-	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
+	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS);
 	_GP(play).bg_frame = 0;
 	_GP(play).bg_frame_locked = 0;
 	remove_all_overlays();
diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.cpp b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
index 6d35359d952..0308c247a4a 100644
--- a/engines/ags/engine/ac/route_finder_impl_legacy.cpp
+++ b/engines/ags/engine/ac/route_finder_impl_legacy.cpp
@@ -150,7 +150,7 @@ int find_nearest_walkable_area(Bitmap *tempw, int fromX, int fromY, int toX, int
 	return 0;
 }
 
-static int walk_area_granularity[MAX_WALK_AREAS + 1];
+static int walk_area_granularity[MAX_WALK_AREAS];
 static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss, const PathfinderConfig &pfc) {
 	_G(wallscreen) = wss;
 	suggestx = -1;
@@ -169,8 +169,8 @@ static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss
 	int dd, ff;
 	// initialize array for finding widths of walkable areas
 	int thisar, inarow = 0, lastarea = 0;
-	int walk_area_times[MAX_WALK_AREAS + 1];
-	for (dd = 0; dd <= MAX_WALK_AREAS; dd++) {
+	int walk_area_times[MAX_WALK_AREAS];
+	for (dd = 0; dd < MAX_WALK_AREAS; dd++) {
 		walk_area_times[dd] = 0;
 		walk_area_granularity[dd] = 0;
 	}
@@ -182,7 +182,7 @@ static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss
 			// count how high the area is at this point
 			if ((thisar == lastarea) && (thisar > 0))
 				inarow++;
-			else if (lastarea > MAX_WALK_AREAS)
+			else if (lastarea >= MAX_WALK_AREAS)
 				quit("!Calculate_Route: invalid colours in walkable area mask");
 			else if (lastarea != 0) {
 				walk_area_granularity[lastarea] += inarow;
@@ -212,7 +212,7 @@ static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss
 	}
 
 	// find the average "width" of a path in this walkable area
-	for (dd = 1; dd <= MAX_WALK_AREAS; dd++) {
+	for (dd = 1; dd < MAX_WALK_AREAS; dd++) {
 		if (walk_area_times[dd] == 0) {
 			walk_area_granularity[dd] = pfc.MaxGranularity;
 			continue;
diff --git a/engines/ags/engine/ac/walkable_area.cpp b/engines/ags/engine/ac/walkable_area.cpp
index 9fde29c31c0..e202b937708 100644
--- a/engines/ags/engine/ac/walkable_area.cpp
+++ b/engines/ags/engine/ac/walkable_area.cpp
@@ -59,7 +59,7 @@ int get_area_scaling(int onarea, int xx, int yy) {
 	xx = room_to_mask_coord(xx);
 	yy = room_to_mask_coord(yy);
 
-	if ((onarea >= 0) && (onarea <= MAX_WALK_AREAS) &&
+	if ((onarea >= 0) && (onarea < MAX_WALK_AREAS) &&
 	        (_GP(thisroom).WalkAreas[onarea].ScalingNear != NOT_VECTOR_SCALED)) {
 		// We have vector scaling!
 		// In case the character is off the screen, limit the Y co-ordinate
@@ -81,7 +81,7 @@ int get_area_scaling(int onarea, int xx, int yy) {
 			zoom_level = _GP(thisroom).WalkAreas[onarea].ScalingNear;
 		}
 		zoom_level += 100;
-	} else if ((onarea >= 0) & (onarea <= MAX_WALK_AREAS))
+	} else if ((onarea >= 0) & (onarea < MAX_WALK_AREAS))
 		zoom_level = _GP(thisroom).WalkAreas[onarea].ScalingFar + 100;
 
 	if (zoom_level == 0)
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 7dba4ca23fd..aecb79d78e7 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -539,7 +539,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 		}
 		generate_light_table();
 
-		for (size_t i = 0; i < MAX_WALK_AREAS + 1; ++i) {
+		for (size_t i = 0; i < MAX_WALK_AREAS; ++i) {
 			_GP(thisroom).WalkAreas[i].ScalingFar = r_data.RoomZoomLevels1[i];
 			_GP(thisroom).WalkAreas[i].ScalingNear = r_data.RoomZoomLevels2[i];
 		}
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 65589e4e19a..35ab74b97ac 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -936,7 +936,7 @@ HSaveError WriteThisRoom(Stream *out) {
 		out->WriteInt32(_GP(thisroom).Regions[i].Light);
 		out->WriteInt32(_GP(thisroom).Regions[i].Tint);
 	}
-	for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) {
+	for (int i = 0; i < MAX_WALK_AREAS; ++i) {
 		out->WriteInt32(_GP(thisroom).WalkAreas[i].ScalingFar);
 		out->WriteInt32(_GP(thisroom).WalkAreas[i].ScalingNear);
 	}
@@ -975,7 +975,7 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Pres
 		r_data.RoomLightLevels[i] = in->ReadInt32();
 		r_data.RoomTintLevels[i] = in->ReadInt32();
 	}
-	for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) {
+	for (int i = 0; i < MAX_WALK_AREAS; ++i) {
 		r_data.RoomZoomLevels1[i] = in->ReadInt32();
 		r_data.RoomZoomLevels2[i] = in->ReadInt32();
 	}
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index aef28c157eb..f4c3805aaa0 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -88,8 +88,8 @@ struct RestoredData {
 	PBitmap                 RoomBkgScene[MAX_ROOM_BGFRAMES];
 	int16_t                 RoomLightLevels[MAX_ROOM_REGIONS];
 	int32_t                 RoomTintLevels[MAX_ROOM_REGIONS];
-	int16_t                 RoomZoomLevels1[MAX_WALK_AREAS + 1];
-	int16_t                 RoomZoomLevels2[MAX_WALK_AREAS + 1];
+	int16_t                 RoomZoomLevels1[MAX_WALK_AREAS];
+	int16_t                 RoomZoomLevels2[MAX_WALK_AREAS];
 	RoomVolumeMod           RoomVolume;
 	// Mouse cursor parameters
 	int                     CursorID;
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 72796113ef7..fc583b3dafe 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -258,8 +258,8 @@ static HSaveError restore_game_audiocliptypes(Stream *in) {
 static void restore_game_thisroom(Stream *in, RestoredData &r_data) {
 	in->ReadArrayOfInt16(r_data.RoomLightLevels, MAX_ROOM_REGIONS);
 	in->ReadArrayOfInt32(r_data.RoomTintLevels, MAX_ROOM_REGIONS);
-	in->ReadArrayOfInt16(r_data.RoomZoomLevels1, MAX_WALK_AREAS + 1);
-	in->ReadArrayOfInt16(r_data.RoomZoomLevels2, MAX_WALK_AREAS + 1);
+	in->ReadArrayOfInt16(r_data.RoomZoomLevels1, MAX_WALK_AREAS);
+	in->ReadArrayOfInt16(r_data.RoomZoomLevels2, MAX_WALK_AREAS);
 }
 
 static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index c0e15aed13a..95fb2707400 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -752,7 +752,7 @@ void engine_init_game_settings() {
 	// Force GUI metrics recalculation, accommodating for loaded fonts
 	GUI::MarkForFontUpdate(-1);
 
-	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
+	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS);
 	memset(&_GP(play).script_timers[0], 0, MAX_TIMERS * sizeof(int));
 	memset(&_GP(play).default_audio_type_volumes[0], -1, MAX_AUDIO_TYPES * sizeof(int));
 
diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp
index 6e56ba72eff..867a6f18ee1 100644
--- a/engines/ags/shared/game/room_file.cpp
+++ b/engines/ags/shared/game/room_file.cpp
@@ -211,8 +211,8 @@ HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 	room->WalkAreaCount = MAX_WALK_AREAS;
 	if (data_ver >= kRoomVersion_240)
 		room->WalkAreaCount = in->ReadInt32();
-	if (room->WalkAreaCount > MAX_WALK_AREAS + 1)
-		return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walkable areas (in room: %d, max: %d).", room->WalkAreaCount, MAX_WALK_AREAS + 1));
+	if (room->WalkAreaCount > MAX_WALK_AREAS)
+		return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walkable areas (in room: %d, max: %d).", room->WalkAreaCount, MAX_WALK_AREAS));
 
 	if (data_ver >= kRoomVersion_200_alpha7)
 		for (size_t i = 0; i < room->WalkAreaCount; ++i)
@@ -283,7 +283,7 @@ HError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
 
 	if (data_ver >= kRoomVersion_114) {
 		// NOTE: this WA value was written for the second time here, for some weird reason
-		for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+		for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 			room->WalkAreas[i].PlayerView = in->ReadInt16();
 	}
 	if (data_ver >= kRoomVersion_255b) {
@@ -679,16 +679,16 @@ void WriteMainBlock(const RoomStruct *room, Stream *out) {
 		out->WriteInt16(obj.Flags);
 	out->WriteInt16(room->MaskResolution);
 
-	out->WriteInt32(MAX_WALK_AREAS + 1);
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	out->WriteInt32(MAX_WALK_AREAS);
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].ScalingFar);
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].PlayerView);
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].ScalingNear);
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].Top);
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].Bottom);
 
 	out->WriteByteCount(0, LEGACY_ROOM_PASSWORD_LENGTH);
@@ -711,7 +711,7 @@ void WriteMainBlock(const RoomStruct *room, Stream *out) {
 	out->WriteInt16(0); // legacy room animations
 
 	// NOTE: this WA value was written for the second time here, for some weird reason
-	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		out->WriteInt16(room->WalkAreas[i].PlayerView);
 	for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
 		out->WriteInt16(room->Regions[i].Light);
diff --git a/engines/ags/shared/game/room_struct.cpp b/engines/ags/shared/game/room_struct.cpp
index 5d6628deeaa..0e0c94193ef 100644
--- a/engines/ags/shared/game/room_struct.cpp
+++ b/engines/ags/shared/game/room_struct.cpp
@@ -165,7 +165,7 @@ void RoomStruct::InitDefaults() {
 		Hotspots[i] = RoomHotspot();
 	for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i)
 		Regions[i] = RoomRegion();
-	for (size_t i = 0; i <= (size_t)MAX_WALK_AREAS; ++i)
+	for (size_t i = 0; i < (size_t)MAX_WALK_AREAS; ++i)
 		WalkAreas[i] = WalkArea();
 	for (size_t i = 0; i < (size_t)MAX_WALK_BEHINDS; ++i)
 		WalkBehinds[i] = WalkBehind();
diff --git a/engines/ags/shared/game/room_struct.h b/engines/ags/shared/game/room_struct.h
index 93edf4cf1a9..57a7ac1227a 100644
--- a/engines/ags/shared/game/room_struct.h
+++ b/engines/ags/shared/game/room_struct.h
@@ -105,10 +105,7 @@ enum RoomFlags {
 #define MAX_ROOM_OBJECTS_v300 40 // for some legacy logic support
 #define MAX_ROOM_OBJECTS   256 // v3.6.0: 40 -> 256 (now limited by room format)
 #define MAX_ROOM_REGIONS   16
-// TODO: this is remains of the older code, MAX_WALK_AREAS = real number - 1, where
-// -1 is for the solid wall. When fixing this you need to be careful, because some
-// walk-area indexes are 0-based and some 1-based (and some arrays have MAX_WALK_AREAS + 1 size)
-#define MAX_WALK_AREAS     15
+#define MAX_WALK_AREAS     16
 #define MAX_WALK_BEHINDS   16
 
 #define MAX_MESSAGES       100
@@ -356,7 +353,7 @@ public:
 	size_t                  RegionCount;
 	RoomRegion              Regions[MAX_ROOM_REGIONS];
 	size_t                  WalkAreaCount;
-	WalkArea                WalkAreas[MAX_WALK_AREAS + 1];
+	WalkArea                WalkAreas[MAX_WALK_AREAS];
 	size_t                  WalkBehindCount;
 	WalkBehind              WalkBehinds[MAX_WALK_BEHINDS];
 


Commit: 97e847ff5079111b2c414af1d823ed6e5639ba89
    https://github.com/scummvm/scummvm/commit/97e847ff5079111b2c414af1d823ed6e5639ba89
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: replaced ReadByte with ReadInt8 where makes sense

ReadByte has special behavior for return value in case of EOF, and in case of
reading data fields it's more clear to indicate an integer of particular size.
>From upstream 47a21109c4ef35226fbc89f06c8527db6eaad480

Changed paths:
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/game/room_file.cpp
    engines/ags/shared/game/room_file_deprecated.cpp
    engines/ags/shared/util/multi_file_lib.cpp


diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index fc583b3dafe..4e6f8097c17 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -111,7 +111,7 @@ static void restore_game_spriteset(Stream *in) {
 	// get serialized dynamic sprites
 	int sprnum = in->ReadInt32();
 	while (sprnum) {
-		unsigned char spriteflag = in->ReadByte();
+		unsigned char spriteflag = in->ReadInt8();
 		add_dynamic_sprite(sprnum, read_serialized_bitmap(in));
 		_GP(game).SpriteInfos[sprnum].Flags = spriteflag;
 		sprnum = in->ReadInt32();
@@ -151,7 +151,7 @@ static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
 
 	// read the room state for all the rooms the player has been in
 	for (int vv = 0; vv < MAX_ROOMS; vv++) {
-		int beenhere = in->ReadByte();
+		int beenhere = in->ReadInt8();
 		if (beenhere) {
 			RoomStatus *roomstat = getRoomStatus(vv);
 			roomstat->beenhere = beenhere;
diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 3fadc6cccba..12de2d7f0fb 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -342,7 +342,7 @@ void GameSetupStruct::ReadFromSaveGame_v321(Stream *in) {
 	}
 
 	in->ReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1);
-	options[OPT_LIPSYNCTEXT] = in->ReadByte();
+	options[OPT_LIPSYNCTEXT] = in->ReadInt8();
 
 	ReadCharacters(in);
 }
diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp
index 867a6f18ee1..4dfd392dde9 100644
--- a/engines/ags/shared/game/room_file.cpp
+++ b/engines/ags/shared/game/room_file.cpp
@@ -364,11 +364,11 @@ HError ReadObjScNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ve
 
 // Secondary backgrounds
 HError ReadAnimBgBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) {
-	room->BgFrameCount = in->ReadByte();
+	room->BgFrameCount = in->ReadInt8();
 	if (room->BgFrameCount > MAX_ROOM_BGFRAMES)
 		return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many room backgrounds (in room: %d, max: %d).", room->BgFrameCount, MAX_ROOM_BGFRAMES));
 
-	room->BgAnimSpeed = in->ReadByte();
+	room->BgAnimSpeed = in->ReadInt8();
 	if (data_ver >= kRoomVersion_255a) {
 		for (size_t i = 0; i < room->BgFrameCount; ++i)
 			room->BgFrames[i].IsPaletteShared = in->ReadInt8() != 0;
diff --git a/engines/ags/shared/game/room_file_deprecated.cpp b/engines/ags/shared/game/room_file_deprecated.cpp
index e04bdd0dbab..4933320c985 100644
--- a/engines/ags/shared/game/room_file_deprecated.cpp
+++ b/engines/ags/shared/game/room_file_deprecated.cpp
@@ -115,7 +115,7 @@ HRoomFileError ReadAncientScriptConfig(Stream *in) {
 
 	size_t var_count = in->ReadInt32();
 	for (size_t i = 0; i < var_count; ++i) {
-		size_t len = in->ReadByte();
+		size_t len = in->ReadInt8();
 		in->Seek(len);
 	}
 	return HRoomFileError::None();
diff --git a/engines/ags/shared/util/multi_file_lib.cpp b/engines/ags/shared/util/multi_file_lib.cpp
index 745e921a278..7fc66bb14e8 100644
--- a/engines/ags/shared/util/multi_file_lib.cpp
+++ b/engines/ags/shared/util/multi_file_lib.cpp
@@ -68,7 +68,7 @@ MFLUtil::MFLError MFLUtil::TestIsMFL(Stream *in, bool test_is_main) {
 	if (err == kMFLNoError) {
 		if (lib_version >= kMFLVersion_MultiV10 && test_is_main) {
 			// this version supports multiple data files, check if it is the first one
-			if (in->ReadByte() != 0)
+			if (in->ReadInt8() != 0)
 				return kMFLErrNoLibBase; // not first datafile in chain
 		}
 	}
@@ -192,7 +192,7 @@ MFLUtil::MFLError MFLUtil::ReadSingleFileLib(AssetLibInfo &lib, Stream *in) {
 }
 
 MFLUtil::MFLError MFLUtil::ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) {
-	if (in->ReadByte() != 0)
+	if (in->ReadInt8() != 0)
 		return kMFLErrNoLibBase; // not first datafile in chain
 
 	if (lib_version >= kMFLVersion_MultiV30) {


Commit: 5d6d2bd7a7e13b89b51b906b977f3fc16d0d1b73
    https://github.com/scummvm/scummvm/commit/5d6d2bd7a7e13b89b51b906b977f3fc16d0d1b73
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed GameDataExtPreloader causing assertions to trigger

>From upstream e7165e7e3c5863b87d6dcf83786f93b97860127b

Changed paths:
    engines/ags/shared/game/main_game_file.cpp
    engines/ags/shared/util/data_ext.cpp


diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index 01587310f22..bb9ad90afb1 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -799,6 +799,7 @@ HError GameDataExtPreloader::ReadBlock(int /*block_id*/, const String &ext_id,
 		_ents.Game.saveGameFolderName = StrUtil::ReadString(_in);
 		read_next = false; // we're done
 	}
+	SkipBlock(); // prevent assertion trigger
 	return HError::None();
 }
 
diff --git a/engines/ags/shared/util/data_ext.cpp b/engines/ags/shared/util/data_ext.cpp
index 953ed491c78..ab2357b7f8b 100644
--- a/engines/ags/shared/util/data_ext.cpp
+++ b/engines/ags/shared/util/data_ext.cpp
@@ -69,7 +69,7 @@ HError DataExtParser::OpenBlock() {
 
 void DataExtParser::SkipBlock() {
 	if (_blockID >= 0)
-		_in->Seek(_blockLen);
+		_in->Seek(_blockStart + _blockLen, kSeekBegin);
 }
 
 HError DataExtParser::PostAssert() {


Commit: 822842d038102f46ec9285b2e07becbde174a9cb
    https://github.com/scummvm/scummvm/commit/822842d038102f46ec9285b2e07becbde174a9cb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine, Common: refactor guard using set_our_eip

Remove naked our_eip usages
>From upstream 9f4371ab8c2fbc9ccf41b49f7b975ebe8bc5d1b8

Changed paths:
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/sprite.cpp
    engines/ags/engine/font/fonts_engine.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/main/game_start.cpp
    engines/ags/engine/main/main.cpp
    engines/ags/engine/main/quit.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/engine/script/script.cpp
    engines/ags/shared/ac/common.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index 24caf3a76af..a5e7b14fda3 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2366,7 +2366,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 
 	// the strings are pre-translated
 	//texx = get_translation(texx);
-	_G(our_eip) = 150;
+	set_our_eip(150);
 
 	int isPause = 1;
 	// if the message is all .'s, don't display anything
@@ -2401,7 +2401,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	if (bwidth < 0)
 		bwidth = ui_view.GetWidth() / 2 + ui_view.GetWidth() / 4;
 
-	_G(our_eip) = 151;
+	set_our_eip(151);
 
 	int useview = speakingChar->talkview;
 	if (isThought) {
@@ -2423,7 +2423,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 
 	if (_GP(game).options[OPT_SPEECHTYPE] == 3)
 		remove_screen_overlay(OVER_COMPLETE);
-	_G(our_eip) = 1500;
+	set_our_eip(1500);
 
 	if (_GP(game).options[OPT_SPEECHTYPE] == 0)
 		allowShrink = 1;
@@ -2452,7 +2452,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		// If the character is in this room, go for it - otherwise
 		// run the "else" clause which  does text in the middle of
 		// the screen.
-		_G(our_eip) = 1501;
+		set_our_eip(1501);
 
 		if (speakingChar->walking)
 			StopMoving(aschar);
@@ -2473,7 +2473,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			speakingChar->loop = 0;
 		}
 
-		_G(our_eip) = 1504;
+		set_our_eip(1504);
 
 		// Calculate speech position based on character's position on screen
 		auto view = FindNearestViewport(aschar);
@@ -2494,7 +2494,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 		if (tdyp < 5)
 			tdyp = 5;
 
-		_G(our_eip) = 152;
+		set_our_eip(152);
 
 		if ((useview >= 0) && (_GP(game).options[OPT_SPEECHTYPE] > 0)) {
 			// Sierra-style close-up portrait
@@ -2582,7 +2582,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			if (widd > 0)
 				bwidth = widd - bigx;
 
-			_G(our_eip) = 153;
+			set_our_eip(153);
 			int ovr_yp = get_fixed_pixel_size(20);
 			int view_frame_x = 0;
 			int view_frame_y = 0;
@@ -2691,7 +2691,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 			CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe), frame_vol);
 		} else if (useview >= 0) {
 			// Lucasarts-style speech
-			_G(our_eip) = 154;
+			set_our_eip(154);
 
 			oldview = speakingChar->view;
 			oldloop = speakingChar->loop;
@@ -2747,12 +2747,12 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	if (isThought)
 		_G(char_thinking) = aschar;
 
-	_G(our_eip) = 155;
+	set_our_eip(155);
 	display_main(tdxp, tdyp, bwidth, texx, DISPLAYTEXT_SPEECH, FONT_SPEECH, textcol, isThought, allowShrink, overlayPositionFixed);
 	if (_G(abort_engine))
 		return;
 
-	_G(our_eip) = 156;
+	set_our_eip(156);
 	if ((_GP(play).in_conversation > 0) && (_GP(game).options[OPT_SPEECHTYPE] == 3))
 		closeupface = nullptr;
 	if (closeupface != nullptr)
@@ -2760,7 +2760,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
 	mark_screen_dirty();
 	_G(face_talking) = -1;
 	_G(facetalkchar) = nullptr;
-	_G(our_eip) = 157;
+	set_our_eip(157);
 	if (oldview >= 0) {
 		speakingChar->flags &= ~CHF_FIXVIEW;
 		if (viewWasLocked)
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 051b86f0998..c409dff239b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1151,7 +1151,7 @@ static Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, std::unique_ptr
 		return src; // No transform: return source image
 
 	recycle_bitmap(dst, src->GetColorDepth(), dst_sz.Width, dst_sz.Height, true);
-	_G(our_eip) = 339;
+	set_our_eip(339);
 
 	// If scaled: first scale then optionally mirror
 	if (src->GetSize() != dst_sz) {
@@ -1417,7 +1417,7 @@ bool construct_object_gfx(int objid, bool force_software) {
 }
 
 void prepare_objects_for_drawing() {
-	_G(our_eip) = 32;
+	set_our_eip(32);
 
 	const bool hw_accel = !drawstate.SoftwareRender;
 
@@ -1525,7 +1525,7 @@ bool construct_char_gfx(int charid, bool force_software) {
 }
 
 void prepare_characters_for_drawing() {
-	_G(our_eip) = 33;
+	set_our_eip(33);
 	const bool hw_accel = !drawstate.SoftwareRender;
 
 	// draw characters
@@ -1613,7 +1613,7 @@ void prepare_room_sprites() {
 		add_roomovers_for_drawing();
 
 		if ((_G(debug_flags) & DBG_NODRAWSPRITES) == 0) {
-			_G(our_eip) = 34;
+			set_our_eip(34);
 
 			if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
 				for (size_t wb = 1 /* 0 is "no area" */;
@@ -1632,7 +1632,7 @@ void prepare_room_sprites() {
 			draw_sprite_list(true);
 		}
 	}
-	_G(our_eip) = 36;
+	set_our_eip(36);
 
 	// Debug room overlay
 	update_room_debug();
@@ -1660,7 +1660,7 @@ void draw_preroom_background() {
 // no_transform flag tells to copy dirty regions on roomcam_surface without any coordinate conversion
 // whatsoever.
 PBitmap draw_room_background(Viewport *view) {
-	_G(our_eip) = 31;
+	set_our_eip(31);
 
 	// For the sake of software renderer, if there is any kind of camera transform required
 	// except screen offset, we tell it to draw on separate bitmap first with zero transformation.
@@ -1785,7 +1785,7 @@ void draw_gui_and_overlays() {
 	}
 
 	// Add GUIs
-	_G(our_eip) = 35;
+	set_our_eip(35);
 	if (((_G(debug_flags) & DBG_NOIFACE) == 0) && (_G(displayed_room) >= 0)) {
 		if (_G(playerchar)->activeinv >= MAX_INV) {
 			quit("!The player.activeinv variable has been corrupted, probably as a result\n"
@@ -1793,7 +1793,7 @@ void draw_gui_and_overlays() {
 		}
 		if (_G(playerchar)->activeinv < 1) _G(gui_inv_pic) = -1;
 		else _G(gui_inv_pic) = _GP(game).invinfo[_G(playerchar)->activeinv].pic;
-		_G(our_eip) = 37;
+		set_our_eip(37);
 		// Prepare and update GUI textures
 		{
 			for (int index = 0; index < _GP(game).numgui; ++index) {
@@ -1803,7 +1803,7 @@ void draw_gui_and_overlays() {
 				if (gui.Transparency == 255) continue; // 100% transparent
 
 				_G(eip_guinum) = index;
-				_G(our_eip) = 372;
+				set_our_eip(372);
 				const bool draw_with_controls = !draw_controls_as_textures;
 				if (gui.HasChanged() || (draw_with_controls && gui.HasControlsChanged())) {
 					auto &gbg = _GP(guibg)[index];
@@ -1825,16 +1825,16 @@ void draw_gui_and_overlays() {
                     sync_object_texture(gbg, is_alpha);
 				}
 
-				_G(our_eip) = 373;
+				set_our_eip(373);
 				if (!draw_with_controls && gui.HasControlsChanged()) {
 					draw_gui_controls(gui);
 				}
-				_G(our_eip) = 374;
+				set_our_eip(374);
 
 				gui.ClearChanged();
 			}
 		}
-		_G(our_eip) = 38;
+		set_our_eip(38);
 		// Draw the GUIs
 		for (int index = 0; index < _GP(game).numgui; ++index) {
 			const auto &gui = _GP(guis)[index];
@@ -1889,7 +1889,7 @@ void draw_gui_and_overlays() {
 		_G(gfxDriver)->EndSpriteBatch();
 	}
 
-	_G(our_eip) = 1099;
+	set_our_eip(1099);
 }
 
 // Push the gathered list of sprites into the active graphic renderer
@@ -1909,7 +1909,7 @@ void put_sprite_list_on_screen(bool in_room) {
 		}
 	}
 
-	_G(our_eip) = 1100;
+	set_our_eip(1100);
 }
 
 bool GfxDriverSpriteEvtCallback(int evt, int data) {
@@ -2055,7 +2055,7 @@ void construct_game_scene(bool full_redraw) {
 	if (_GP(play).fast_forward)
 		return;
 
-	_G(our_eip) = 3;
+	set_our_eip(3);
 
 	// React to changes to viewports and cameras (possibly from script) just before the render
 	_GP(play).UpdateViewports();
@@ -2093,7 +2093,7 @@ void construct_game_scene(bool full_redraw) {
 		}
 	}
 
-	_G(our_eip) = 4;
+	set_our_eip(4);
 
 	// Stage: UI overlay
 	if (_GP(play).screen_is_faded_out == 0) {
@@ -2248,7 +2248,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 	update_shakescreen();
 
 	construct_game_scene(false);
-	_G(our_eip) = 5;
+	set_our_eip(5);
 	// TODO: extraBitmap is a hack, used to place an additional gui element
 	// on top of the screen. Normally this should be a part of the game UI stage.
 	if (extraBitmap != nullptr) {
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index e77a7f5c61d..d0282cfba75 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -961,8 +961,8 @@ HSaveError load_game(const String &path, int slotNumber, bool &data_overwritten)
 	data_overwritten = false;
 	_G(gameHasBeenRestored)++;
 
-	_G(oldeip) = _G(our_eip);
-	_G(our_eip) = 2050;
+	_G(oldeip) = get_our_eip();
+	set_our_eip(2050);
 
 	HSaveError err;
 	SavegameSource src;
@@ -1012,7 +1012,7 @@ HSaveError load_game(const String &path, int slotNumber, bool &data_overwritten)
 	if (!err)
 		return err;
 	src.InputStream.reset();
-	_G(our_eip) = _G(oldeip);
+	set_our_eip(_G(oldeip));
 
 	// ensure input state is reset
 	ags_clear_input_state();
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 11ae29ea421..84bb028cba4 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -443,7 +443,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	}
 
 	// load the room from disk
-	_G(our_eip) = 200;
+	set_our_eip(200);
 	_GP(thisroom).GameID = NO_GAME_ID_IN_ROOM_FILE;
 	load_room(room_filename, &_GP(thisroom), _GP(game).IsLegacyHiRes(), _GP(game).SpriteInfos);
 
@@ -459,7 +459,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 
 	convert_room_coordinates_to_data_res(&_GP(thisroom));
 
-	_G(our_eip) = 201;
+	set_our_eip(201);
 
 	_GP(play).room_width = _GP(thisroom).Width;
 	_GP(play).room_height = _GP(thisroom).Height;
@@ -485,13 +485,13 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 		_GP(thisroom).BgFrames[i].Graphic = PrepareSpriteForUse(_GP(thisroom).BgFrames[i].Graphic, false);
 	}
 
-	_G(our_eip) = 202;
+	set_our_eip(202);
 	// Update game viewports
 	if (_GP(game).IsLegacyLetterbox())
 		update_letterbox_mode();
 	SetMouseBounds(0, 0, 0, 0);
 
-	_G(our_eip) = 203;
+	set_our_eip(203);
 	_G(in_new_room) = 1;
 
 	set_color_depth(_GP(game).GetColorDepth());
@@ -509,11 +509,11 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	// copy the walls screen
 	_G(walkareabackup) = BitmapHelper::CreateBitmapCopy(_GP(thisroom).WalkAreaMask.get());
 
-	_G(our_eip) = 204;
+	set_our_eip(204);
 	redo_walkable_areas();
 	walkbehinds_recalc();
 
-	_G(our_eip) = 205;
+	set_our_eip(205);
 	// setup objects
 	if (forchar != nullptr) {
 		// if not restoring a game, always reset this room
@@ -638,7 +638,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 		ccAddExternalScriptObject(_GP(thisroom).Hotspots[cc].ScriptName, &_G(scrHotspot)[cc], &_GP(ccDynamicHotspot));
 	}
 
-	_G(our_eip) = 210;
+	set_our_eip(210);
 	if (IS_ANTIALIAS_SPRITES) {
 		// sometimes the palette has corrupt entries, which crash
 		// the create_rgb_table call
@@ -654,7 +654,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 		create_rgb_table(&_GP(rgb_table), _G(palette), nullptr);
 		_G(rgb_map) = &_GP(rgb_table);
 	}
-	_G(our_eip) = 211;
+	set_our_eip(211);
 	if (forchar != nullptr) {
 		// if it's not a Restore Game
 
@@ -690,7 +690,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 			memcpy(&_G(roominst)->globaldata[0], _G(croom)->tsdata.data(), _G(croom)->tsdatasize);
 		}
 	}
-	_G(our_eip) = 207;
+	set_our_eip(207);
 	_GP(play).entered_edge = -1;
 
 	if ((_G(new_room_x) != SCR_NO_VALUE) && (forchar != nullptr)) {
@@ -815,7 +815,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	if (_GP(thisroom).Options.StartupMusic > 0)
 		PlayMusicResetQueue(_GP(thisroom).Options.StartupMusic);
 
-	_G(our_eip) = 208;
+	set_our_eip(208);
 	if (forchar != nullptr) {
 		if (_GP(thisroom).Options.PlayerCharOff == 0) {
 			forchar->on = 1;
@@ -835,7 +835,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	}
 	_G(color_map) = nullptr;
 
-	_G(our_eip) = 209;
+	set_our_eip(209);
 	generate_light_table();
 	update_music_volume();
 
@@ -849,7 +849,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	}
 	init_room_drawdata();
 
-	_G(our_eip) = 212;
+	set_our_eip(212);
 	invalidate_screen();
 	for (size_t cc = 0; cc < _G(croom)->numobj; cc++) {
 		if (_G(objs)[cc].on == 2)
@@ -864,7 +864,7 @@ void load_new_room(int newnum, CharacterInfo *forchar) {
 	if (_GP(game).color_depth > 1)
 		setpal();
 
-	_G(our_eip) = 220;
+	set_our_eip(220);
 	update_polled_stuff();
 	debug_script_log("Now in room %d", _G(displayed_room));
 	GUI::MarkAllGUIForUpdate(true, true);
diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp
index 01f18af6bd5..60fd11a160d 100644
--- a/engines/ags/engine/ac/sprite.cpp
+++ b/engines/ags/engine/ac/sprite.cpp
@@ -93,8 +93,8 @@ Bitmap *remove_alpha_channel(Bitmap *from) {
 }
 
 Bitmap *initialize_sprite(sprkey_t index, Bitmap *image, uint32_t &sprite_flags) {
-	int oldeip = _G(our_eip);
-	_G(our_eip) = 4300;
+	int oldeip = get_our_eip();
+	set_our_eip(4300);
 
 	if (sprite_flags & SPF_HADALPHACHANNEL) {
 		// we stripped the alpha channel out last time, put
@@ -124,7 +124,7 @@ Bitmap *initialize_sprite(sprkey_t index, Bitmap *image, uint32_t &sprite_flags)
 		sprite_flags |= SPF_HADALPHACHANNEL;
 	}
 
-	_G(our_eip) = oldeip;
+	set_our_eip(oldeip);
 	return use_bmp;
 }
 
diff --git a/engines/ags/engine/font/fonts_engine.cpp b/engines/ags/engine/font/fonts_engine.cpp
index 9a4b118cf63..b6d5c0ecdbe 100644
--- a/engines/ags/engine/font/fonts_engine.cpp
+++ b/engines/ags/engine/font/fonts_engine.cpp
@@ -30,19 +30,4 @@
 
 namespace AGS3 {
 
-
-
-
-//=============================================================================
-// Engine-specific implementation split out of acfonts.cpp
-//=============================================================================
-
-void set_our_eip(int eip) {
-	_G(our_eip) = eip;
-}
-
-int get_our_eip() {
-	return _G(our_eip);
-}
-
 } // namespace AGS3
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 95fb2707400..48bae6d5f84 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -82,7 +82,7 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 bool engine_init_backend() {
-	_G(our_eip) = -199;
+	set_our_eip(-199);
 	_G(platform)->PreBackendInit();
 	// Initialize SDL
 	Debug::Printf(kDbgMsg_Info, "Initializing backend libs");
@@ -113,11 +113,11 @@ void winclosehook() {
 void engine_setup_window() {
 	Debug::Printf(kDbgMsg_Info, "Setting up window");
 
-	_G(our_eip) = -198;
+	set_our_eip(-198);
 	sys_window_set_title(_GP(game).gamename.GetCStr());
 	sys_window_set_icon();
 	sys_evt_set_quit_callback(winclosehook);
-	_G(our_eip) = -197;
+	set_our_eip(-197);
 }
 
 // Fills map with game settings, to e.g. let setup application(s)
@@ -360,7 +360,7 @@ void engine_pre_init_gfx() {
 
 int engine_load_game_data() {
 	Debug::Printf("Load game data");
-	_G(our_eip) = -17;
+	set_our_eip(-17);
 	HError err = load_game_file();
 	if (!err) {
 		_G(proper_exit) = 1;
@@ -403,7 +403,7 @@ int check_write_access() {
 	if (_G(platform)->GetDiskFreeSpaceMB() < 2)
 		return 0;
 
-	_G(our_eip) = -1895;
+	set_our_eip(-1895);
 
 	// The Save Game Dir is the only place that we should write to
 	String svg_dir = get_save_game_directory();
@@ -426,12 +426,12 @@ int check_write_access() {
 		return 0;
 #endif // AGS_PLATFORM_OS_ANDROID
 
-	_G(our_eip) = -1896;
+	set_our_eip(-1896);
 
 	temp_s->Write("just to test the drive free space", 30);
 	delete temp_s;
 
-	_G(our_eip) = -1897;
+	set_our_eip(-1897);
 
 	if (File::DeleteFile(tempPath))
 		return 0;
@@ -517,7 +517,7 @@ int engine_init_sprites() {
 // TODO: this should not be a part of "engine_" function group,
 // move this elsewhere (InitGameState?).
 void engine_init_game_settings() {
-	_G(our_eip) = -7;
+	set_our_eip(-7);
 	Debug::Printf("Initialize game settings");
 
 	// Initialize randomizer
@@ -562,7 +562,7 @@ void engine_init_game_settings() {
 	if (_G(playerchar)->view >= 0)
 		precache_view(_G(playerchar)->view, 0, Character_GetDiagonalWalking(_G(playerchar)) ? 8 : 4);
 
-	_G(our_eip) = -6;
+	set_our_eip(-6);
 
 	for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++) {
 		_G(scrObj)[ee].id = ee;
@@ -594,7 +594,7 @@ void engine_init_game_settings() {
 		_GP(charextra)[ee].animwait = 0;
 	}
 
-	_G(our_eip) = -5;
+	set_our_eip(-5);
 	for (ee = 0; ee < _GP(game).numinvitems; ee++) {
 		if (_GP(game).invinfo[ee].flags & IFLG_STARTWITH) _G(playerchar)->inv[ee] = 1;
 		else _G(playerchar)->inv[ee] = 0;
@@ -763,7 +763,7 @@ void engine_init_game_settings() {
 	_G(displayed_room) = -10;
 
 	_G(currentcursor) = 0;
-	_G(our_eip) = -4;
+	set_our_eip(-4);
 	_G(mousey) = 100; // stop icon bar popping up
 
 	// We use same variable to read config and be used at runtime for now,
@@ -1073,61 +1073,61 @@ int initialize_engine(const ConfigTree &startup_opts) {
 		return EXIT_NORMAL;
 	}
 
-	_G(our_eip) = -190;
+	set_our_eip(-190);
 
 	//-----------------------------------------------------
 	// Init auxiliary data files and other directories, initialize asset manager
 	engine_init_user_directories();
 
-	_G(our_eip) = -191;
+	set_our_eip(-191);
 
 	engine_locate_speech_pak();
 
-	_G(our_eip) = -192;
+	set_our_eip(-192);
 
 	engine_locate_audio_pak();
 
-	_G(our_eip) = -193;
+	set_our_eip(-193);
 
 	engine_assign_assetpaths();
 
 	//-----------------------------------------------------
 	// Begin setting up systems
 
-	_G(our_eip) = -194;
+	set_our_eip(-194);
 
 	engine_init_fonts();
 
-	_G(our_eip) = -195;
+	set_our_eip(-195);
 
 	engine_init_keyboard();
 
-	_G(our_eip) = -196;
+	set_our_eip(-196);
 
 	engine_init_mouse();
 
-	_G(our_eip) = -198;
+	set_our_eip(-198);
 
 	engine_init_audio();
 
-	_G(our_eip) = -199;
+	set_our_eip(-199);
 
 	engine_init_debug();
 
-	_G(our_eip) = -10;
+	set_our_eip(-10);
 
 	engine_init_pathfinder();
 
 	set_game_speed(40);
 
-	_G(our_eip) = -20;
-	_G(our_eip) = -19;
+	set_our_eip(-20);
+	set_our_eip(-19);
 
 	int res = engine_load_game_data();
 	if (res != 0)
 		return res;
 
-	_G(our_eip) = -189;
+	set_our_eip(-189);
 
 	res = engine_check_disk_space();
 	if (res != 0)
@@ -1140,7 +1140,7 @@ int initialize_engine(const ConfigTree &startup_opts) {
 	if (res != 0)
 		return res;
 
-	_G(our_eip) = -179;
+	set_our_eip(-179);
 
 	engine_adjust_for_rotation_settings();
 
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 1d3b7d707c8..8927cbffaa2 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -530,7 +530,7 @@ static void check_keyboard_controls() {
 
 // check_controls: checks mouse & keyboard interface
 static void check_controls() {
-	_G(our_eip) = 1007;
+	set_our_eip(1007);
 
 	sys_evt_process_pending();
 
@@ -574,7 +574,7 @@ static void check_room_edges(size_t numevents_was) {
 			}
 		}
 	}
-	_G(our_eip) = 1008;
+	set_our_eip(1008);
 
 }
 
@@ -797,7 +797,7 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	}
 
 	ccNotifyScriptStillAlive();
-	_G(our_eip) = 1;
+	set_our_eip(1);
 
 	game_loop_check_problems_at_start();
 
@@ -805,11 +805,11 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	if ((_GP(play).no_hicolor_fadein) && (_GP(game).options[OPT_FADETYPE] == FADE_NORMAL))
 		_GP(play).screen_is_faded_out = 0;
 
-	_G(our_eip) = 1014;
+	set_our_eip(1014);
 
 	update_gui_disabled_status();
 
-	_G(our_eip) = 1004;
+	set_our_eip(1004);
 
 	game_loop_do_early_script_update();
 	// run this immediately to make sure it gets done before fade-in
@@ -819,7 +819,7 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	if (_G(abort_engine))
 		return;
 
-	_G(our_eip) = 1005;
+	set_our_eip(1005);
 
 	res = game_loop_check_ground_level_interactions();
 	if (res != RETURN_CONTINUE) {
@@ -845,7 +845,7 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	if (_G(abort_engine))
 		return;
 
-	_G(our_eip) = 2;
+	set_our_eip(2);
 
 	// do the overall game state update
 	game_loop_do_update();
@@ -866,14 +866,14 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	if (!_GP(play).fast_forward)
 		render_graphics(extraBitmap, extraX, extraY);
 
-	_G(our_eip) = 6;
+	set_our_eip(6);
 
 	game_loop_update_events();
 
 	if (_G(abort_engine))
 		return;
 
-	_G(our_eip) = 7;
+	set_our_eip(7);
 
 	update_polled_stuff();
 	if (_G(abort_engine))
@@ -887,7 +887,7 @@ void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int
 	if (_GP(play).fast_forward)
 		return;
 
-	_G(our_eip) = 72;
+	set_our_eip(72);
 
 	game_loop_update_fps();
 
@@ -980,7 +980,7 @@ static int UpdateWaitMode() {
 
 	if (!ShouldStayInWaitMode())
 		_G(restrict_until).type = 0;
-	_G(our_eip) = 77;
+	set_our_eip(77);
 
 	if (_G(restrict_until).type > 0) {
 		return RETURN_CONTINUE;
@@ -1023,7 +1023,7 @@ static int GameTick() {
 
 	UpdateMouseOverLocation();
 
-	_G(our_eip) = 76;
+	set_our_eip(76);
 
 	int res = UpdateWaitMode();
 	if (res == RETURN_CONTINUE) {
@@ -1064,7 +1064,7 @@ static void GameLoopUntilEvent(int untilwhat, const void *data_ptr = nullptr, in
 	SetupLoopParameters(untilwhat, data_ptr, data1, data2);
 	while (GameTick() == 0);
 
-	_G(our_eip) = 78;
+	set_our_eip(78);
 
 	_G(restrict_until) = cached_restrict_until;
 }
diff --git a/engines/ags/engine/main/game_start.cpp b/engines/ags/engine/main/game_start.cpp
index b3ac2b4b92f..b66f72e2014 100644
--- a/engines/ags/engine/main/game_start.cpp
+++ b/engines/ags/engine/main/game_start.cpp
@@ -84,21 +84,21 @@ void start_game() {
 	_GP(mouse).SetPosition(Point(160, 100));
 	newmusic(0);
 
-	_G(our_eip) = -42;
+	set_our_eip(-42);
 
 	// skip ticks to account for initialisation or a restored game.
 	skipMissedTicks();
 
 	RunScriptFunctionInModules("game_start");
 
-	_G(our_eip) = -43;
+	set_our_eip(-43);
 
 	// Only auto-set first restart point in < 3.6.1 games,
 	// since 3.6.1+ users are suggested to set one manually in script.
 	if (_G(loaded_game_file_version) < kGameVersion_361_10)
 		SetRestartPoint();
 
-	_G(our_eip) = -3;
+	set_our_eip(-3);
 
 	if (_G(displayed_room) < 0) {
 		current_fade_out_effect();
diff --git a/engines/ags/engine/main/main.cpp b/engines/ags/engine/main/main.cpp
index 32d87e8d94f..290a4482c49 100644
--- a/engines/ags/engine/main/main.cpp
+++ b/engines/ags/engine/main/main.cpp
@@ -48,7 +48,7 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 void main_init(int argc, const char *argv[]) {
-	_G(our_eip) = -999;
+	set_our_eip(-999);
 
 	// Init libraries: set text encoding
 	set_uformat(U_UTF8);
diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp
index 663a217a629..e30ed509701 100644
--- a/engines/ags/engine/main/quit.cpp
+++ b/engines/ags/engine/main/quit.cpp
@@ -86,7 +86,7 @@ void quit_check_dynamic_sprites(QuitReason qreason) {
 }
 
 void quit_shutdown_audio() {
-	_G(our_eip) = 9917;
+	set_our_eip(9917);
 	_GP(game).options[OPT_CROSSFADEMUSIC] = 0;
 	shutdown_sound();
 }
@@ -186,31 +186,31 @@ void quit_free() {
 
 	quit_tell_editor_debugger(errmsg, qreason);
 
-	_G(our_eip) = 9900;
+	set_our_eip(9900);
 
 	quit_stop_cd();
 
-	_G(our_eip) = 9020;
+	set_our_eip(9020);
 
 	// Be sure to unlock mouse on exit, or users will hate us
 	sys_window_lock_mouse(false);
 
-	_G(our_eip) = 9016;
+	set_our_eip(9016);
 
 	quit_check_dynamic_sprites(qreason);
 
 	if (_G(use_cdplayer))
 		_G(platform)->ShutdownCDPlayer();
 
-	_G(our_eip) = 9019;
+	set_our_eip(9019);
 
 	quit_shutdown_audio();
 
-	_G(our_eip) = 9901;
+	set_our_eip(9901);
 
 	_GP(spriteset).Reset();
 
-	_G(our_eip) = 9908;
+	set_our_eip(9908);
 
 	shutdown_pathfinder();
 
@@ -234,7 +234,7 @@ void quit_free() {
 
 	_G(platform)->PostAllegroExit();
 
-	_G(our_eip) = 9903;
+	set_our_eip(9903);
 
 	quit_delete_temp_files();
 
@@ -244,7 +244,7 @@ void quit_free() {
 
 	shutdown_debug();
 
-	_G(our_eip) = 9904;
+	set_our_eip(9904);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index b64075def20..e9bb2cc3444 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -472,17 +472,17 @@ void update_sierra_speech() {
 // the like.
 void update_stuff() {
 
-	_G(our_eip) = 20;
+	set_our_eip(20);
 
 	update_script_timers();
 
 	update_cycling_views();
 
-	_G(our_eip) = 21;
+	set_our_eip(21);
 
 	update_player_view();
 
-	_G(our_eip) = 22;
+	set_our_eip(22);
 
 	std::vector<int> followingAsSheep;
 
@@ -490,17 +490,17 @@ void update_stuff() {
 
 	update_following_exactly_characters(followingAsSheep);
 
-	_G(our_eip) = 23;
+	set_our_eip(23);
 
 	update_overlay_timers();
 
 	update_speech_and_messages();
 
-	_G(our_eip) = 24;
+	set_our_eip(24);
 
 	update_sierra_speech();
 
-	_G(our_eip) = 25;
+	set_our_eip(25);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index de2f73fd1d1..c2dd2a4aad3 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -687,11 +687,11 @@ InteractionVariable *FindGraphicalVariable(const char *varName) {
 struct TempEip {
 	int oldval;
 	TempEip(int newval) {
-		oldval = _G(our_eip);
-		_G(our_eip) = newval;
+		oldval = get_our_eip();
+		set_our_eip(newval);
 	}
 	~TempEip() {
-		_G(our_eip) = oldval;
+		set_our_eip(oldval);
 	}
 };
 
diff --git a/engines/ags/shared/ac/common.cpp b/engines/ags/shared/ac/common.cpp
index 4159e0ba965..53dc02dbdbb 100644
--- a/engines/ags/shared/ac/common.cpp
+++ b/engines/ags/shared/ac/common.cpp
@@ -21,6 +21,7 @@
 
 #include "ags/shared/ac/common.h"
 #include "ags/shared/util/string.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -42,4 +43,12 @@ void quitprintf(const char *fmt, ...) {
 	error("%s", text.GetCStr());
 }
 
+void set_our_eip(int eip) {
+	_G(our_eip) = eip;
+}
+
+int get_our_eip() {
+	return _G(our_eip);
+}
+
 } // namespace AGS3
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 9b6b48e6eb1..91274099302 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -21,6 +21,7 @@
 
 #include "common/std/algorithm.h"
 #include "ags/shared/gui/gui_main.h"
+#include "ags/shared/ac/common.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/shared/debugging/out.h"
@@ -225,12 +226,12 @@ bool GUIMain::BringControlToFront(int32_t index) {
 }
 
 void GUIMain::DrawSelf(Bitmap *ds) {
-	SET_EIP(375);
+	set_our_eip(375);
 
 	if ((Width < 1) || (Height < 1))
 		return;
 
-	SET_EIP(376);
+	set_our_eip(376);
 	// stop border being transparent, if the whole GUI isn't
 	if ((FgColor == 0) && (BgColor != 0))
 		FgColor = 16;
@@ -238,7 +239,7 @@ void GUIMain::DrawSelf(Bitmap *ds) {
 	if (BgColor != 0)
 		ds->Fill(ds->GetCompatibleColor(BgColor));
 
-	SET_EIP(377);
+	set_our_eip(377);
 
 	color_t draw_color;
 	if (FgColor != BgColor) {
@@ -248,12 +249,12 @@ void GUIMain::DrawSelf(Bitmap *ds) {
 			ds->DrawRect(Rect(1, 1, ds->GetWidth() - 2, ds->GetHeight() - 2), draw_color);
 	}
 
-	SET_EIP(378);
+	set_our_eip(378);
 
 	if (BgImage > 0 && _GP(spriteset).DoesSpriteExist(BgImage))
 		draw_gui_sprite(ds, BgImage, 0, 0, false);
 
-	SET_EIP(379);
+	set_our_eip(379);
 }
 
 void GUIMain::DrawWithControls(Bitmap *ds) {
@@ -317,7 +318,7 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 		}
 	}
 
-	SET_EIP(380);
+	set_our_eip(380);
 }
 
 void GUIMain::DrawBlob(Bitmap *ds, int x, int y, color_t draw_color) {
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index adde3ad9dac..dd8b7ca213e 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -293,7 +293,6 @@ extern int get_adjusted_spritewidth(int spr);
 extern int get_adjusted_spriteheight(int spr);
 extern bool is_sprite_alpha(int spr);
 
-#define SET_EIP(x) set_our_eip(x);
 extern void set_eip_guiobj(int eip);
 extern int get_eip_guiobj();
 


Commit: 00de3853ed95fe0d01d7f31fa30b79c4f7a67e7a
    https://github.com/scummvm/scummvm/commit/00de3853ed95fe0d01d7f31fa30b79c4f7a67e7a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix backwards compatible Overlay z-sorting in creation order

Prior to introduction of explicit Overlay.ZOrder property, overlays were not sorted at all, and were
 drawn in the order of their storage in container, which coincidentally matched their order of creation.
 After Overlay.ZOrder was added, and overlays are sorted among GUIs using qsort, there's a potential
 issue of their drawing order being undefined (if two overlays have matching or unassigned ZOrder).
 This became a real issue after modification of the overlays internal storage, which was done for
 performance reasons.

The change introduces a global incrementing counter that assigns a "creation index" to Overlays, and
is used as a second factor during sorting, similar to how GUIs use their ID property.

>From upstream 278dbc40e91a56a3728a567abb8620cc917729cb

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/screen_overlay.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index c409dff239b..bc91c87daa8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1581,7 +1581,7 @@ static void add_roomovers_for_drawing() {
 		if (!over.IsRoomLayer()) continue; // not a room layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false);
+		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
 	}
 }
 
@@ -1781,7 +1781,7 @@ void draw_gui_and_overlays() {
 		if (over.IsRoomLayer()) continue; // not a ui layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false);
+		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
 	}
 
 	// Add GUIs
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 0325770fbcc..2791c3979e5 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -282,6 +282,13 @@ struct GameState {
 
 	// Special overlays
 	//
+	// Total number of existing overlays, only for the reference
+	int overlay_count = 0;
+	// Incrementing index of creation, assigned to overlays.
+	// Currently is used when resolving sorting conflicts with equal z-order.
+	// TODO: this method theoretically can overflow, but was added still as a quick fix;
+	// investigate better solutions later.
+	int overlay_creation_id = 0;
 	// Is there a QFG4-style dialog overlay on screen (contains overlay ID)
 	int  complete_overlay_on = 0;
 	// Is there a blocking text overlay on screen (contains overlay ID)
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 0b6a70d1b64..3085a7e0db6 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -368,10 +368,16 @@ void remove_screen_overlay(int type) {
 		invalidate_and_subref(over);
 	}
 	dispose_overlay(over);
-    // Don't erase vector elements, instead set invalid and record free index
-    _GP(screenover)[type] = ScreenOverlay();
-    if (type >= OVER_FIRSTFREE)
-        _GP(over_free_ids).push(type);
+
+	// Don't erase vector elements, instead set invalid and record free index
+	_GP(screenover)[type] = ScreenOverlay();
+	if (type >= OVER_FIRSTFREE)
+		_GP(over_free_ids).push(type);
+
+	// If all overlays have been removed, reset creation index (helps vs overflows)
+	_GP(play).overlay_count--;
+	if (_GP(play).overlay_count == 0)
+		_GP(play).overlay_creation_id = 0;
 }
 
 void remove_all_overlays() {
@@ -398,6 +404,8 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	if (_GP(screenover).size() <= static_cast<uint32_t>(type))
 		_GP(screenover).resize(type + 1);
 	ScreenOverlay over;
+	over.type = type;
+	over.creation_id = _GP(play).overlay_creation_id++;
 	if (piccy) {
 		over.SetImage(piccy, pic_offx, pic_offy);
 		over.SetAlphaChannel(has_alpha);
@@ -411,7 +419,6 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	// by default draw speech and portraits over GUI, and the rest under GUI
 	over.zorder = (roomlayer || type == OVER_TEXTMSG || type == OVER_PICTURE || type == OVER_TEXTSPEECH) ?
 		INT_MAX : INT_MIN;
-	over.type = type;
 	over.timeout = 0;
 	over.bgSpeechForChar = -1;
 	over.associatedOverlayHandle = 0;
@@ -432,6 +439,7 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	}
 	over.MarkChanged();
 	_GP(screenover)[type] = std::move(over);
+	_GP(play).overlay_count++;
 	return type;
 }
 
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index d4b1d0b9d23..d4081f272c6 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -66,7 +66,11 @@ enum OverlaySvgVersion {
 struct ScreenOverlay {
 	// Texture
 	Engine::IDriverDependantBitmap *ddb = nullptr;
-	int type = -1, timeout = 0;
+	// Overlay's "type" is a merged special overlay ID and internal container index
+	int type = -1;
+	// Arbitrary creation order index, meant for resolving equal z-sorting
+	int creation_id = 0;
+	int timeout = 0;
 	// Note that x,y are overlay's properties, that define its position in script;
 	// but real drawn position is x + offsetX, y + offsetY;
 	int x = 0, y = 0;


Commit: 38ea062044bb56d8d9717a9fc7aec97d49662345
    https://github.com/scummvm/scummvm/commit/38ea062044bb56d8d9717a9fc7aec97d49662345
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: just amended comments to IScriptObject

>From upstream 0f8ef52a60b54fc8db0ea89cf15569ba8b876611

Changed paths:
    engines/ags/engine/ac/dynobj/cc_script_object.h
    engines/ags/engine/script/runtime_script_value.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_script_object.h b/engines/ags/engine/ac/dynobj/cc_script_object.h
index dea9b4dc83f..0eecc06516f 100644
--- a/engines/ags/engine/ac/dynobj/cc_script_object.h
+++ b/engines/ags/engine/ac/dynobj/cc_script_object.h
@@ -75,11 +75,15 @@ struct IScriptObject {
 	// TODO: pass savegame format version
 	virtual int Serialize(void *address, uint8_t *buffer, int bufsize) = 0;
 
-	// Legacy support for reading and writing object values by their relative offset.
-	// WARNING: following were never a part of plugin API, therefore these methods
-	// should **never** be called for kScValPluginObject script objects!
-	//
-	// RE: GetFieldPtr()
+    // WARNING: following section is not a part of plugin API, therefore these methods
+    // should **never** be called for kScValPluginObject script objects!
+
+    // Legacy support for reading and writing object values by their relative offset.
+    // These methods allow to "remap" script struct field access, by taking the
+    // legacy offset, and using it rather as a field ID than an address, for example.
+    // Consequently these also let trigger side-effects, such as updating an object
+    // after a field value is written to.
+    // RE: GetFieldPtr() -
 	// According to AGS script specification, when the old-string pointer or char array is passed
 	// as an argument, the byte-code does not include any specific command for the member variable
 	// retrieval and instructs to pass an address of the object itself with certain offset.
diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index 804869550b7..e79f4d34e93 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -33,10 +33,6 @@ using namespace AGS::Shared;
 // distinguish Runtime Values.
 //
 
-// TODO: test again if stack entry really can hold an offset itself
-
-// TODO: use endian-agnostic method to access global vars
-
 uint8_t RuntimeScriptValue::ReadByte() const {
 	switch (this->Type) {
 	case kScValStackPtr:


Commit: 2f8634abdc364eeacee82fbb91e282eba8c8aefa
    https://github.com/scummvm/scummvm/commit/2f8634abdc364eeacee82fbb91e282eba8c8aefa
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: add allocWidth/allocHeight parameters

Partially from upstream b0b63f1e4b6c60ec8b3aaf4c1d4bc0e5cad9658a

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 4e4016585f8..f4ef451a668 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -228,6 +228,8 @@ protected:
 struct TextureTile {
 	int x = 0, y = 0;
 	int width = 0, height = 0;
+	// allocWidth and allocHeight tell the actual allocated texture size
+	int allocWidth = 0, allocHeight = 0;
 };
 
 


Commit: de40de36fbca61fe8f827e02e586c92564e20c30
    https://github.com/scummvm/scummvm/commit/de40de36fbca61fe8f827e02e586c92564e20c30
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: change return type of GetDirectPtr() to void*

It makes little sense as intptr_t.
>From upstream 23335a6b7c00267bb0419790f3f92d8ae4aedc3e

Changed paths:
    engines/ags/engine/script/runtime_script_value.cpp
    engines/ags/engine/script/runtime_script_value.h


diff --git a/engines/ags/engine/script/runtime_script_value.cpp b/engines/ags/engine/script/runtime_script_value.cpp
index e79f4d34e93..94c6ffa900e 100644
--- a/engines/ags/engine/script/runtime_script_value.cpp
+++ b/engines/ags/engine/script/runtime_script_value.cpp
@@ -190,7 +190,7 @@ RuntimeScriptValue &RuntimeScriptValue::DirectPtrObj() {
 	return *this;
 }
 
-intptr_t RuntimeScriptValue::GetDirectPtr() const {
+void *RuntimeScriptValue::GetDirectPtr() const {
 	const RuntimeScriptValue *temp_val = this;
 	int ival = temp_val->IValue;
 	if (temp_val->Type == kScValGlobalVar || temp_val->Type == kScValStackPtr) {
@@ -198,9 +198,9 @@ intptr_t RuntimeScriptValue::GetDirectPtr() const {
 		ival += temp_val->IValue;
 	}
 	if (temp_val->Type == kScValScriptObject)
-		return (intptr_t)temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
+		return temp_val->ObjMgr->GetFieldPtr(temp_val->Ptr, ival);
 	else
-		return (intptr_t)(temp_val->PtrU8 + ival);
+		return temp_val->PtrU8 + ival;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/script/runtime_script_value.h b/engines/ags/engine/script/runtime_script_value.h
index 04338cb47fe..873ce66478a 100644
--- a/engines/ags/engine/script/runtime_script_value.h
+++ b/engines/ags/engine/script/runtime_script_value.h
@@ -437,7 +437,7 @@ public:
 	// tell for certain that we are expecting a pointer to the object and not its (first) field.
 	RuntimeScriptValue &DirectPtrObj();
 	// Resolve and return direct pointer to the referenced data; non pointer types return IValue
-	intptr_t           GetDirectPtr() const;
+	void *GetDirectPtr() const;
 };
 
 } // namespace AGS3


Commit: 6c209a2a98b98cfc9ab8b61c4bd8d6f3db512dfc
    https://github.com/scummvm/scummvm/commit/6c209a2a98b98cfc9ab8b61c4bd8d6f3db512dfc
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: use uintptr_t in couple of places in ccInstance::Run()

...although this does not change much, because code offsets are int32-based everywhere else.

>From upstream dd10f63d993278eb6c063bac24e25815a7146f99

Changed paths:
    engines/ags/engine/script/cc_instance.cpp
    engines/ags/shared/core/types.h


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 0cd10a6e0ed..cb3b03eed99 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -1161,12 +1161,12 @@ int ccInstance::Run(int32_t curpc) {
 			int32_t instId = codeOp.Instruction.InstanceId;
 			// determine the offset into the code of the instance we want
 			runningInst = _G(loadedInstances)[instId];
-			intptr_t callAddr = reg1.PtrU8 - reinterpret_cast<uint8_t *>(&runningInst->code[0]);
-			if (callAddr % sizeof(intptr_t) != 0) {
+			uintptr_t callAddr = reg1.PtrU8 - reinterpret_cast<uint8_t *>(&runningInst->code[0]);
+			if (callAddr % sizeof(uintptr_t) != 0) {
 				cc_error("call address not aligned");
 				return -1;
 			}
-			callAddr /= sizeof(intptr_t); // size of ccScript::code elements
+			callAddr /= sizeof(uintptr_t); // size of ccScript::code elements
 
 			if (Run(static_cast<int32_t>(callAddr)))
 				return -1;
@@ -1669,7 +1669,7 @@ bool ccInstance::_Create(PScript scri, const ccInstance *joined) {
 		if (etype == EXPORT_FUNCTION) {
 			// NOTE: unfortunately, there seems to be no way to know if
 			// that's an extender function that expects object pointer
-			exports[i].SetCodePtr((static_cast<intptr_t>(eaddr) * sizeof(intptr_t) + reinterpret_cast<uint8_t *>(&code[0])));
+			exports[i].SetCodePtr(reinterpret_cast<uint8_t *>(&code[0]) + (static_cast<uintptr_t>(eaddr) * sizeof(uintptr_t)));
 		} else if (etype == EXPORT_DATA) {
 			ScriptVariable *gl_var = FindGlobalVar(eaddr);
 			if (gl_var) {
diff --git a/engines/ags/shared/core/types.h b/engines/ags/shared/core/types.h
index dd1757caf64..3fe53330e7f 100644
--- a/engines/ags/shared/core/types.h
+++ b/engines/ags/shared/core/types.h
@@ -66,6 +66,7 @@ typedef int64 int64_t;
 
 typedef int64 soff_t;       // Stream offset type
 typedef int64 intptr_t;
+typedef uint64 uintptr_t;
 
 // fixed point type
 #define fixed_t int32_t


Commit: 43c37eabf61ca44eba076cc3c7c7024359d9c2c9
    https://github.com/scummvm/scummvm/commit/43c37eabf61ca44eba076cc3c7c7024359d9c2c9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: define special DRAWENTRY values in gfxdriver as uintptr_t

This is mostly formality, but do this to avoid misinterpretation.
>From upstream 941db378110115556dde971d77c021e530829dbd

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


diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index f4ef451a668..52f8632942f 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -125,9 +125,9 @@ public:
 
 protected:
 	// Special internal values, applied to DrawListEntry
-	static const intptr_t DRAWENTRY_STAGECALLBACK = 0x0;
-	static const intptr_t DRAWENTRY_FADE = 0x1;
-	static const intptr_t DRAWENTRY_TINT = 0x2;
+	static const uintptr_t DRAWENTRY_STAGECALLBACK = 0x0;
+	static const uintptr_t DRAWENTRY_FADE = 0x1;
+	static const uintptr_t DRAWENTRY_TINT = 0x2;
 
 	// Called after graphics driver was initialized for use for the first time
 	virtual void OnInit();


Commit: 7b81ba52d639cbd3cf891d4f3897b35837597715
    https://github.com/scummvm/scummvm/commit/7b81ba52d639cbd3cf891d4f3897b35837597715
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix "Engine: fixed speech stuck in "post state" under some conditions" commit

Fix line that was lost for some reason.
>From upstream dd209c2ce73d5c07e3672604cf4f442dc7683bde

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


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 6e0ea7f7610..a69154f9830 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -262,7 +262,7 @@ ScreenOverlay *display_main(int xx, int yy, int wii, const char *text, int disp_
 	// If fast-forwarding, then skip any blocking message immediately
 	if (_GP(play).fast_forward && (disp_type < DISPLAYTEXT_NORMALOVERLAY)) {
 		_GP(play).SetWaitSkipResult(SKIP_AUTOTIMER);
-		_GP(play).messagetime = -1;
+		post_display_cleanup();
 		return nullptr;
 	}
 


Commit: b2e96c46aad8551aab1cc3c4d931a15049b01fda
    https://github.com/scummvm/scummvm/commit/b2e96c46aad8551aab1cc3c4d931a15049b01fda
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.20 RC5)

Partially from upstream 5b64bb1b86761731513aa325c8b9bc6f80b862e1

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 026beb0acfc..2fe851b7852 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.17"
+#define ACI_VERSION_STR      "3.6.1.20"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.17
+#define ACI_VERSION_MSRC_DEF  3.6.1.20
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 8d468642cbde59173bfeb7b54148130fdf778400
    https://github.com/scummvm/scummvm/commit/8d468642cbde59173bfeb7b54148130fdf778400
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implement add_waypoint_direct(), fix AddWaypoint's resolution

Completes 0b50067abb702248edea51339e73f677c4864f1c

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index a5e7b14fda3..db31b86c7e9 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -154,9 +154,9 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 		return;
 	}
 
-	cmls->pos[cmls->numstage] = {x, y};
 	// They're already walking there anyway
-	if (cmls->pos[cmls->numstage] == cmls->pos[cmls->numstage - 1])
+	const Point &last_pos = cmls->GetLastPos();
+	if (last_pos == Point(x, y))
 		return;
 
 	int move_speed_x, move_speed_y;
diff --git a/engines/ags/engine/ac/move_list.h b/engines/ags/engine/ac/move_list.h
index d504d313666..8399e03189b 100644
--- a/engines/ags/engine/ac/move_list.h
+++ b/engines/ags/engine/ac/move_list.h
@@ -74,6 +74,8 @@ struct MoveList {
 	fixed	fin_move = 0;
 	float	fin_from_part = 0.f;
 
+	const Point &GetLastPos() const { return numstage > 0 ? pos[numstage - 1] : pos[0]; }
+
 	// Gets a movelist's step length, in coordinate units
 	// (normally the coord unit is a game pixel)
 	float GetStepLength() const;
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 84bb028cba4..06148f10de4 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1039,7 +1039,9 @@ void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_st
 	if (_GP(thisroom).MaskResolution == _GP(game).GetDataUpscaleMult())
 		return;
 
-	ml->from = {mask_to_room_coord(ml->from.X), mask_to_room_coord(ml->from.Y)};
+	if (from_step == 0) {
+		ml->from = {mask_to_room_coord(ml->from.X), mask_to_room_coord(ml->from.Y)};
+	}
 
 	for (int i = from_step; i <= to_step; i++) {
 		ml->pos[i] = {mask_to_room_coord(ml->pos[i].X), mask_to_room_coord(ml->pos[i].Y)};


Commit: 3cf3531d4fdfe1312b9d9fe2fd8afd3680477f03
    https://github.com/scummvm/scummvm/commit/3cf3531d4fdfe1312b9d9fe2fd8afd3680477f03
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed Software render crashes if rendering in game_start script

The easiest way to replicate the problem is to call Display() in "game_start" script function.
>From upstream 1a712f7a69105ec7730eb45d8bc483e95ec0af27

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index bc91c87daa8..c04fb0577b5 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -555,7 +555,7 @@ void init_room_drawdata() {
 }
 
 void on_roomviewport_created(int index) {
-	if (!_G(gfxDriver) || drawstate.FullFrameRedraw)
+	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if ((size_t)index < _GP(CameraDrawData).size())
 		return;
@@ -563,14 +563,14 @@ void on_roomviewport_created(int index) {
 }
 
 void on_roomviewport_deleted(int index) {
-	if (drawstate.FullFrameRedraw)
+	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	_GP(CameraDrawData).erase(_GP(CameraDrawData).begin() + index);
 	delete_invalid_regions(index);
 }
 
 void on_roomviewport_changed(Viewport *view) {
-	if (drawstate.FullFrameRedraw)
+	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if (!view->IsVisible() || view->GetCamera() == nullptr)
 		return;
@@ -588,7 +588,7 @@ void on_roomviewport_changed(Viewport *view) {
 }
 
 void detect_roomviewport_overlaps(size_t z_index) {
-	if (drawstate.FullFrameRedraw)
+	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	// Find out if we overlap or are overlapped by anything;
 	const auto &viewports = _GP(play).GetRoomViewportsZOrdered();
@@ -612,7 +612,7 @@ void detect_roomviewport_overlaps(size_t z_index) {
 }
 
 void on_roomcamera_changed(Camera *cam) {
-	if (drawstate.FullFrameRedraw)
+	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if (cam->HasChangedSize()) {
 		auto viewrefs = cam->GetLinkedViewports();


Commit: c3b5eac297043a19e55e8085b584b7ceba77a959
    https://github.com/scummvm/scummvm/commit/c3b5eac297043a19e55e8085b584b7ceba77a959
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed IAGSEngine::SimulateMouseClick()

>From upstream 2acb74a7d7769139efc678fd03e2ae631c6ce0de

Changed paths:
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 5f39b7c63f5..747fa35bb67 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -703,7 +703,7 @@ void IAGSEngine::SetMousePosition(int32 x, int32 y) {
 }
 
 void IAGSEngine::SimulateMouseClick(int32 button) {
-	SimulateMouseClick(button);
+	AGS3::SimulateMouseClick(button);
 }
 
 int IAGSEngine::GetMovementPathWaypointCount(int32 pathId) {


Commit: d585831bdef5a3a2b0f0a5493b8bd576dcb22edb
    https://github.com/scummvm/scummvm/commit/d585831bdef5a3a2b0f0a5493b8bd576dcb22edb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Use getAt instead of braces in string access

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


diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp
index 5731019168d..aea181a97f0 100644
--- a/engines/ags/engine/ac/global_audio.cpp
+++ b/engines/ags/engine/ac/global_audio.cpp
@@ -461,7 +461,7 @@ String get_cue_filename(int charid, int sndid) {
 	String script_name;
 	if (charid >= 0) {
 		// append the first 4 characters of the script name to the filename
-		if (_GP(game).chars2[charid].scrname_new[0] == 'c')
+		if (_GP(game).chars2[charid].scrname_new.GetAt(0) == 'c')
 			script_name.SetString(_GP(game).chars2[charid].scrname_new.GetCStr() + 1, 4);
 		else
 			script_name.SetString(_GP(game).chars2[charid].scrname_new.GetCStr(), 4);


Commit: 49af10966920a76a7433dc8fb2ccf76490efec40
    https://github.com/scummvm/scummvm/commit/49af10966920a76a7433dc8fb2ccf76490efec40
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: implemented a method of skipping sprite batches on screenshot

This is meant to skip certain groups of sprites when taking a "screenshot",
 for instance when preparing a frozen game screen for the room transition
  (fade-out etc).
Think of hiding mouse cursor and engine overlay (fps counter etc).
Partially from upstream b0cd961483dcee8ffff7543c429fcd3272b413d5

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/gfx_driver_base.cpp
    engines/ags/engine/gfx/gfx_driver_base.h
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index c04fb0577b5..c1ff43038df 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -236,11 +236,11 @@ PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha) {
 	return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr!
 }
 
-Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res) {
+Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res, uint32_t batch_skip_filter) {
 	Bitmap *dst = new Bitmap(width, height, _GP(game).GetColorDepth());
 	GraphicResolution want_fmt;
 	// If the size and color depth are supported we may copy right into our bitmap
-	if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt))
+	if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt, batch_skip_filter))
 		return dst;
 	// Otherwise we might need to copy between few bitmaps...
 	Bitmap *buf_screenfmt = new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth);
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 3587f1529f8..f94f131a4d2 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -255,7 +255,7 @@ Shared::Bitmap *PrepareSpriteForUse(Shared::Bitmap *bitmap, bool has_alpha);
 Shared::PBitmap PrepareSpriteForUse(Shared::PBitmap bitmap, bool has_alpha);
 // Makes a screenshot corresponding to the last screen render and returns it as a bitmap
 // of the requested width and height and game's native color depth.
-Shared::Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res = false);
+Shared::Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res = false, uint32_t batch_skip_filter = 0u);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index 3efcb9d6171..b988d637959 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -679,7 +679,7 @@ void ScummVMRendererGraphicsDriver::SetStageBackBuffer(Bitmap *backBuffer) {
 		_stageVirtualScreen = cur_stage;
 }
 
-bool ScummVMRendererGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) {
+bool ScummVMRendererGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt, uint32_t /*batch_skip_filter*/) {
 	(void)at_native_res; // software driver always renders at native resolution at the moment
 	// software filter is taught to copy to any size
 	if (destination->GetColorDepth() != _srcColorDepth) {
@@ -817,7 +817,7 @@ void ScummVMRendererGraphicsDriver::__fade_out_range(int speed, int from, int to
 	__fade_from_range(temp, faded_out_palette, speed, from, to);
 }
 
-void ScummVMRendererGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) {
+void ScummVMRendererGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue, uint32_t /*batch_skip_filter*/) {
 	if (_srcColorDepth > 8) {
 		highcolor_fade_out(virtualScreen, _drawPostScreenCallback, speed * 4, targetColourRed, targetColourGreen, targetColourBlue);
 	} else {
@@ -825,7 +825,7 @@ void ScummVMRendererGraphicsDriver::FadeOut(int speed, int targetColourRed, int
 	}
 }
 
-void ScummVMRendererGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) {
+void ScummVMRendererGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue, uint32_t /*batch_skip_filter*/) {
 	if (_drawScreenCallback) {
 		_drawScreenCallback();
 		RenderToBackBuffer();
@@ -838,7 +838,7 @@ void ScummVMRendererGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColou
 	}
 }
 
-void ScummVMRendererGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay) {
+void ScummVMRendererGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay, uint32_t /*batch_skip_filter*/) {
 	if (blackingOut) {
 		int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed);
 		int boxwid = speed, boxhit = yspeed;
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 56ad587656b..b92118f5f73 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -217,10 +217,13 @@ public:
 	void RenderToBackBuffer() override;
 	void Render() override;
 	void Render(int xoff, int yoff, Shared::GraphicFlip flip) override;
-	bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) override;
-	void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) override;
-	void FadeIn(int speed, PALETTE pal, int targetColourRed, int targetColourGreen, int targetColourBlue) override;
-	void BoxOutEffect(bool blackingOut, int speed, int delay) override;
+	bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt,
+								   uint32_t batch_skip_filter = 0u) override;
+	void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue,
+				 uint32_t batch_skip_filter = 0u) override;
+	void FadeIn(int speed, PALETTE pal, int targetColourRed, int targetColourGreen, int targetColourBlue,
+				uint32_t batch_skip_filter = 0u) override;
+	void BoxOutEffect(bool blackingOut, int speed, int delay, uint32_t batch_skip_filter = 0u) override;
 	bool SupportsGammaControl() override;
 	void SetGamma(int newGamma) override;
 	void UseSmoothScaling(bool /*enabled*/) override {}
diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 9bee3d4ddb0..fc8c190b894 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -87,8 +87,8 @@ bool GraphicsDriverBase::GetVsync() const {
 }
 
 void GraphicsDriverBase::BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
-	GraphicFlip flip, PBitmap surface) {
-	_spriteBatchDesc.push_back(SpriteBatchDesc(_actSpriteBatch, viewport, transform, flip, surface));
+	GraphicFlip flip, PBitmap surface, uint32_t filter_flags) {
+	_spriteBatchDesc.push_back(SpriteBatchDesc(_actSpriteBatch, viewport, transform, flip, surface, filter_flags));
 	_spriteBatchRange.push_back(std::make_pair(GetLastDrawEntryIndex(), (size_t) SIZE_MAX));
 	_actSpriteBatch = _spriteBatchDesc.size() - 1;
 	InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]);
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 52f8632942f..fe4d92ac3a6 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -55,15 +55,19 @@ struct SpriteBatchDesc {
 	Shared::GraphicFlip      Flip = Shared::kFlip_None;
 	// Optional bitmap to draw sprites upon. Used exclusively by the software rendering mode.
 	PBitmap                  Surface;
+	// Optional filter flags; this lets to filter certain batches out during some operations,
+	// such as fading effects or making screenshots.
+	uint32_t				 FilterFlags = 0u;
 
 	SpriteBatchDesc() = default;
 	SpriteBatchDesc(uint32_t parent, const Rect viewport, const SpriteTransform & transform,
-		Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr)
+		Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr, uint32_t filter_flags = 0)
 		: Parent(parent)
 		, Viewport(viewport)
 		, Transform(transform)
 		, Flip(flip)
-		, Surface(surface) {
+		, Surface(surface)
+		, FilterFlags(filter_flags) {
 	}
 };
 
@@ -104,8 +108,9 @@ public:
 	bool		SetVsync(bool enabled) override;
 	bool		GetVsync() const override;
 
-	void        BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
-	                             Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr) override;
+	void 		BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
+						 		 Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr,
+								 uint32_t filter_flags = 0) override;
 	void        EndSpriteBatch() override;
 	void        ClearDrawLists() override;
 
@@ -178,7 +183,7 @@ protected:
 	// Sprite batch parameters
 	SpriteBatchDescs _spriteBatchDesc;
 	// The range of sprites in this sprite batch (counting nested sprites):
-	// the last of the previous batch, and the last of the current.
+	// the index of a first of the current batch, and the next index past the last one.
 	std::vector<std::pair<size_t, size_t>> _spriteBatchRange;
 	// The index of a currently filled sprite batch
 	size_t _actSpriteBatch;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 4056943f701..76b4fa9cd38 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -177,8 +177,10 @@ public:
 	// sprites to this batch's list.
 	// Beginning a batch while the previous was not ended will create a sub-batch
 	// (think of it as of a child scene node).
+	// Optionally you can assign "filter flags" to this batch; this lets to filter certain
+	// batches out during some operations, such as fading effects or making screenshots.
 	virtual void BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform = SpriteTransform(),
-		Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr) = 0;
+		Shared::GraphicFlip flip = Shared::kFlip_None, PBitmap surface = nullptr, uint32_t filter_flags = 0) = 0;
 	// Ends current sprite batch
 	virtual void EndSpriteBatch() = 0;
 	// Adds sprite to the active batch
@@ -205,7 +207,8 @@ public:
 	// Copies contents of the game screen into bitmap using simple blit or pixel copy.
 	// Bitmap must be of supported size and pixel format. If it's not the method will
 	// fail and optionally write wanted destination format into 'want_fmt' pointer.
-	virtual bool GetCopyOfScreenIntoBitmap(Shared::Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt = nullptr) = 0;
+	virtual bool GetCopyOfScreenIntoBitmap(Shared::Bitmap *destination, bool at_native_res,
+										   GraphicResolution *want_fmt = nullptr, uint32_t batch_skip_filter = 0u) = 0;
 	// Tells if the renderer supports toggling vsync after initializing the mode.
 	virtual bool DoesSupportVsyncToggle() = 0;
 	// Toggles vertical sync mode, if renderer supports one; returns the *new state*.
@@ -223,11 +226,13 @@ public:
 	// main drawing procedure. Since currently it does not - we need to init our own sprite batch
 	// internally to let it set up correct viewport settings instead of relying on a chance.
 	// Runs fade-out animation in a blocking manner.
-	virtual void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) = 0;
+	virtual void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue,
+						 uint32_t batch_skip_filter = 0u) = 0;
 	// Runs fade-in animation in a blocking manner.
-	virtual void FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) = 0;
+	virtual void FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue,
+						uint32_t batch_skip_filter = 0u) = 0;
 	// Runs box-out animation in a blocking manner.
-	virtual void BoxOutEffect(bool blackingOut, int speed, int delay) = 0;
+	virtual void BoxOutEffect(bool blackingOut, int speed, int delay, uint32_t batch_skip_filter = 0u) = 0;
 	virtual void UseSmoothScaling(bool enabled) = 0;
 	virtual bool SupportsGammaControl() = 0;
 	virtual void SetGamma(int newGamma) = 0;


Commit: 58dfedf32390ed28c6e0ebd6bcfc0c06f1ddb77a
    https://github.com/scummvm/scummvm/commit/58dfedf32390ed28c6e0ebd6bcfc0c06f1ddb77a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hide mouse cursor and engine overlay when fading-out

>From upstream b26c8920894a0622f8640ce6cbfe369f75c08422

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/global_screen.cpp
    engines/ags/engine/ac/screen.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index c1ff43038df..2353fd2d28f 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -712,10 +712,11 @@ static void render_black_borders() {
 }
 
 void render_to_screen() {
-	// Stage: final plugin callback (still drawn on game screen
+	// Stage: final plugin callback (still drawn on game screen)
 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped);
+										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped,
+										nullptr, RENDER_BATCH_POST_GAME_SCENE);
 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
 		_G(gfxDriver)->EndSpriteBatch();
 	}
@@ -2107,7 +2108,8 @@ void construct_game_scene(bool full_redraw) {
 void construct_game_screen_overlay(bool draw_mouse) {
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
 									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
-									(GraphicFlip)_GP(play).screen_flipped);
+									(GraphicFlip)_GP(play).screen_flipped,
+									nullptr, RENDER_BATCH_POST_GAME_SCENE);
 	if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) {
 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
 	}
@@ -2127,7 +2129,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 
 	// For hardware-accelerated renderers: legacy letterbox and global screen fade effect
 	if (drawstate.FullFrameRedraw) {
-		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_POST_GAME_SCENE);
 		// Stage: legacy letterbox mode borders
 		if (_GP(play).screen_is_faded_out == 0)
 			render_black_borders();
@@ -2140,7 +2142,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 
 void construct_engine_overlay() {
 	const Rect &viewport = RectWH(_GP(game).GetGameRes());
-	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform());
+	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_POST_GAME_SCENE);
 
 	if (_G(display_fps) != kFPS_Hide)
 		draw_fps(viewport);
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index f94f131a4d2..c26dbddd6e7 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -45,6 +45,11 @@ using namespace AGS; // FIXME later
 
 #define IS_ANTIALIAS_SPRITES _GP(usetup).enable_antialiasing && (_GP(play).disable_antialiasing == 0)
 
+// Render stage flags, for filtering out certain elements
+// during room transitions and screenshots, etc.
+// NOTE: these values are internal and purely arbitrary atm.
+#define RENDER_BATCH_POST_GAME_SCENE 0x0001
+
 /**
  * Buffer and info flags for viewport/camera pairs rendering in software mode
  */
diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp
index 2d36a5149a9..b74a2d4661c 100644
--- a/engines/ags/engine/ac/global_screen.cpp
+++ b/engines/ags/engine/ac/global_screen.cpp
@@ -145,7 +145,7 @@ void FadeOut(int sppd) {
 
 void fadeout_impl(int spdd) {
 	if (_GP(play).screen_is_faded_out == 0) {
-		_G(gfxDriver)->FadeOut(spdd, _GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue);
+		_G(gfxDriver)->FadeOut(spdd, _GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue, RENDER_BATCH_POST_GAME_SCENE);
 	}
 
 	if (_GP(game).color_depth > 1)
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 9ae775043c7..8a5e8383b03 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -74,12 +74,12 @@ void current_fade_out_effect() {
 	} else if (theTransition == FADE_NORMAL) {
 		fadeout_impl(5);
 	} else if (theTransition == FADE_BOXOUT) {
-		_G(gfxDriver)->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed());
+		_G(gfxDriver)->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed(), RENDER_BATCH_POST_GAME_SCENE);
 		_GP(play).screen_is_faded_out = 1;
 	} else {
 		get_palette(_G(old_palette));
 		const Rect &viewport = _GP(play).GetMainViewport();
-		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight());
+		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), false /* use current resolution */, RENDER_BATCH_POST_GAME_SCENE);
 	}
 }
 


Commit: 00fc3c38c3b2cc692172fd97caafd4d1e9001ec9
    https://github.com/scummvm/scummvm/commit/00fc3c38c3b2cc692172fd97caafd4d1e9001ec9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: narrow down sprite batch filtering for fade-out

This corrects recent b26c892
>From upstream debccfe4a445df8bb16c7a16387c810adffcef10

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/global_screen.cpp
    engines/ags/engine/ac/screen.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 2353fd2d28f..0db289e04fe 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -715,8 +715,7 @@ void render_to_screen() {
 	// Stage: final plugin callback (still drawn on game screen)
 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped,
-										nullptr, RENDER_BATCH_POST_GAME_SCENE);
+										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped);
 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
 		_G(gfxDriver)->EndSpriteBatch();
 	}
@@ -2108,28 +2107,29 @@ void construct_game_scene(bool full_redraw) {
 void construct_game_screen_overlay(bool draw_mouse) {
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
 									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
-									(GraphicFlip)_GP(play).screen_flipped,
-									nullptr, RENDER_BATCH_POST_GAME_SCENE);
+									(GraphicFlip)_GP(play).screen_flipped);
 	if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) {
 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
 	}
 
-	// Add mouse cursor pic, and global screen tint effect
+	// Mouse cursor
 	if (_GP(play).screen_is_faded_out == 0) {
-		// Stage: mouse cursor
 		if (draw_mouse && !_GP(play).mouse_cursor_hidden) {
+			// Exclusive sub-batch for mouse cursor, to let filter it out (CHECKME later?)
+			_G(gfxDriver)->BeginSpriteBatch(Rect(), SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_MOUSE_CURSOR);
 			_G(gfxDriver)->DrawSprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor));
 			invalidate_sprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor), false);
+			_G(gfxDriver)->EndSpriteBatch();
 		}
-		// Stage: screen fx
-		if (_GP(play).screen_tint >= 1)
-			_G(gfxDriver)->SetScreenTint(_GP(play).screen_tint & 0xff, (_GP(play).screen_tint >> 8) & 0xff, (_GP(play).screen_tint >> 16) & 0xff);
 	}
+	// Full screen tint fx, covers everything except for fade fx(?) and engine overlay
+	if ((_GP(play).screen_tint >= 1) && (_GP(play).screen_is_faded_out == 0))
+		_G(gfxDriver)->SetScreenTint(_GP(play).screen_tint & 0xff, (_GP(play).screen_tint >> 8) & 0xff, (_GP(play).screen_tint >> 16) & 0xff);
 	_G(gfxDriver)->EndSpriteBatch();
 
 	// For hardware-accelerated renderers: legacy letterbox and global screen fade effect
 	if (drawstate.FullFrameRedraw) {
-		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_POST_GAME_SCENE);
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 		// Stage: legacy letterbox mode borders
 		if (_GP(play).screen_is_faded_out == 0)
 			render_black_borders();
@@ -2142,7 +2142,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 
 void construct_engine_overlay() {
 	const Rect &viewport = RectWH(_GP(game).GetGameRes());
-	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_POST_GAME_SCENE);
+	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform(), kFlip_None, nullptr, RENDER_BATCH_ENGINE_OVERLAY);
 
 	if (_G(display_fps) != kFPS_Hide)
 		draw_fps(viewport);
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index c26dbddd6e7..b16a7f93b1c 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -46,9 +46,11 @@ using namespace AGS; // FIXME later
 #define IS_ANTIALIAS_SPRITES _GP(usetup).enable_antialiasing && (_GP(play).disable_antialiasing == 0)
 
 // Render stage flags, for filtering out certain elements
-// during room transitions and screenshots, etc.
+// during room transitions, capturing screenshots, etc.
 // NOTE: these values are internal and purely arbitrary atm.
-#define RENDER_BATCH_POST_GAME_SCENE 0x0001
+#define RENDER_BATCH_ENGINE_OVERLAY 0x0001
+#define RENDER_BATCH_MOUSE_CURSOR   0x0002
+#define RENDER_SHOT_SKIP_ON_FADE    (RENDER_BATCH_ENGINE_OVERLAY | RENDER_BATCH_MOUSE_CURSOR)
 
 /**
  * Buffer and info flags for viewport/camera pairs rendering in software mode
diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp
index b74a2d4661c..189ced6aa41 100644
--- a/engines/ags/engine/ac/global_screen.cpp
+++ b/engines/ags/engine/ac/global_screen.cpp
@@ -145,7 +145,7 @@ void FadeOut(int sppd) {
 
 void fadeout_impl(int spdd) {
 	if (_GP(play).screen_is_faded_out == 0) {
-		_G(gfxDriver)->FadeOut(spdd, _GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue, RENDER_BATCH_POST_GAME_SCENE);
+		_G(gfxDriver)->FadeOut(spdd, _GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue, RENDER_SHOT_SKIP_ON_FADE);
 	}
 
 	if (_GP(game).color_depth > 1)
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 8a5e8383b03..415ceec0caf 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -74,12 +74,12 @@ void current_fade_out_effect() {
 	} else if (theTransition == FADE_NORMAL) {
 		fadeout_impl(5);
 	} else if (theTransition == FADE_BOXOUT) {
-		_G(gfxDriver)->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed(), RENDER_BATCH_POST_GAME_SCENE);
+		_G(gfxDriver)->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed(), RENDER_SHOT_SKIP_ON_FADE);
 		_GP(play).screen_is_faded_out = 1;
 	} else {
 		get_palette(_G(old_palette));
 		const Rect &viewport = _GP(play).GetMainViewport();
-		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), false /* use current resolution */, RENDER_BATCH_POST_GAME_SCENE);
+		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), false /* use current resolution */, RENDER_SHOT_SKIP_ON_FADE);
 	}
 }
 


Commit: 2199dd1555858ea7228eceff33f19a59353db8eb
    https://github.com/scummvm/scummvm/commit/2199dd1555858ea7228eceff33f19a59353db8eb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: check failure of adding a sprite

Partially from upstream 9c54262d973114110952b8794d737e6e45a532fb

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index e1b9e74a1e3..289fdf0159f 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -105,8 +105,9 @@ bool SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite, int flags) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: unable to use index %d", index);
 		return false;
 	}
-	if (!sprite) {
-		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign nullptr to index %d", index);
+	if (!sprite || sprite->GetSize().IsNull() || sprite->GetColorDepth() <= 0) {
+		DisposeSprite(index); // free previous item in this slot anyway
+		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign an invalid bitmap to index %d", index);
 		return false;
 	}
 


Commit: 567b5bb54bc8b8428c31274b97764af21e781938
    https://github.com/scummvm/scummvm/commit/567b5bb54bc8b8428c31274b97764af21e781938
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in DynamicSprite.CreateFrom* functions fixup bad sprite size

>From upstream 83e78fb1341b6852fb13366bae5aaed340571651

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


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 1293f9e711a..e87fd08c544 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -341,20 +341,27 @@ ScriptDynamicSprite *DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface
 	if (gotSlot <= 0)
 		return nullptr;
 
+	if (width <= 0 || height <= 0) {
+		debug_script_warn("WARNING: DynamicSprite.CreateFromDrawingSurface: invalid size %d x %d, will adjust", width, height);
+		width = std::max(1, width);
+		height = std::max(1, height);
+	}
+
 	// use DrawingSurface resolution
 	sds->PointToGameResolution(&x, &y);
 	sds->SizeToGameResolution(&width, &height);
 
 	Bitmap *ds = sds->StartDrawing();
-
 	if ((x < 0) || (y < 0) || (x + width > ds->GetWidth()) || (y + height > ds->GetHeight()))
 		quit("!DynamicSprite.CreateFromDrawingSurface: requested area is outside the surface");
 
 	int colDepth = ds->GetColorDepth();
 
 	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, colDepth);
-	if (newPic == nullptr)
+	if (newPic == nullptr) {
+		sds->FinishedDrawingReadOnly();
 		return nullptr;
+	}
 
 	newPic->Blit(ds, x, y, 0, 0, width, height);
 
@@ -366,6 +373,10 @@ ScriptDynamicSprite *DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface
 }
 
 ScriptDynamicSprite *DynamicSprite_Create(int width, int height, int alphaChannel) {
+	int gotSlot = _GP(spriteset).GetFreeIndex();
+	if (gotSlot <= 0)
+		return nullptr;
+
 	if (width <= 0 || height <= 0) {
 		debug_script_warn("WARNING: DynamicSprite.Create: invalid size %d x %d, will adjust", width, height);
 		width = MAX(1, width);
@@ -374,10 +385,6 @@ ScriptDynamicSprite *DynamicSprite_Create(int width, int height, int alphaChanne
 
 	data_to_game_coords(&width, &height);
 
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
-		return nullptr;
-
 	Bitmap *newPic = CreateCompatBitmap(width, height);
 	if (newPic == nullptr)
 		return nullptr;
@@ -396,28 +403,36 @@ ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot) {
 }
 
 ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height) {
+	int gotSlot = _GP(spriteset).GetFreeIndex();
+	if (gotSlot <= 0)
+		return nullptr;
 
 	if (frame == SCR_NO_VALUE) {
 		frame = _GP(play).bg_frame;
 	} else if ((frame < 0) || ((size_t)frame >= _GP(thisroom).BgFrameCount))
 		quit("!DynamicSprite.CreateFromBackground: invalid frame specified");
 
-	if (x1 == SCR_NO_VALUE) {
+	if (x1 == SCR_NO_VALUE)
 		x1 = 0;
+	if (y1 == SCR_NO_VALUE)
 		y1 = 0;
+	if (width == SCR_NO_VALUE)
 		width = _GP(play).room_width;
+	if (height == SCR_NO_VALUE)
 		height = _GP(play).room_height;
-	} else if ((x1 < 0) || (y1 < 0) || (width < 1) || (height < 1) ||
-	           (x1 + width > _GP(play).room_width) || (y1 + height > _GP(play).room_height))
+
+	if (width <= 0 || height <= 0) {
+		debug_script_warn("WARNING: DynamicSprite.CreateFromBackground: invalid size %d x %d, will adjust", width, height);
+		width = std::max(1, width);
+		height = std::max(1, height);
+	}
+
+	if ((x1 < 0) || (y1 < 0) || (x1 + width > _GP(play).room_width) || (y1 + height > _GP(play).room_height))
 		quit("!DynamicSprite.CreateFromBackground: invalid co-ordinates specified");
 
 	data_to_game_coords(&x1, &y1);
 	data_to_game_coords(&width, &height);
 
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
-		return nullptr;
-
 	// create a new sprite as a copy of the existing one
 	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(thisroom).BgFrames[frame].Graphic->GetColorDepth());
 	if (newPic == nullptr)


Commit: 184a45ad0200d6b178e0575a6731cffdd1fd1f52
    https://github.com/scummvm/scummvm/commit/184a45ad0200d6b178e0575a6731cffdd1fd1f52
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix warning text in DrawImage to mention any "destination"

>From upstream 9d12250e5756f406eb1cfe2d7062d5d8bd931b0c

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


diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index e466939c392..9d2d9a7e5ef 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -192,9 +192,9 @@ void DrawingSurface_DrawImageImpl(ScriptDrawingSurface *sds, Bitmap *src,
 
 	if (src->GetColorDepth() != ds->GetColorDepth()) {
 		if (sprite_id >= 0)
-			debug_script_warn("DrawImage: Sprite %d colour depth %d-bit not same as background depth %d-bit", sprite_id, src->GetColorDepth(), ds->GetColorDepth());
+			debug_script_warn("DrawImage: Sprite %d colour depth %d-bit not same as destination depth %d-bit", sprite_id, src->GetColorDepth(), ds->GetColorDepth());
 		else
-			debug_script_warn("DrawImage: Source image colour depth %d-bit not same as background depth %d-bit", src->GetColorDepth(), ds->GetColorDepth());
+			debug_script_warn("DrawImage: Source image colour depth %d-bit not same as destination depth %d-bit", src->GetColorDepth(), ds->GetColorDepth());
 	}
 
 	draw_sprite_support_alpha(ds, sds->hasAlphaChannel != 0, dst_x, dst_y, src, src_has_alpha,


Commit: 73526c8f1c8c590921d174ae659cb14e3fce66cd
    https://github.com/scummvm/scummvm/commit/73526c8f1c8c590921d174ae659cb14e3fce66cd
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: add SPF_OBJECTOWNED flag for dyn sprites owned by Overlays

Currently this is only to skip checking them when exiting the game.

Partially from upstream f535a4b7fec65bb00b13c883a0b3e1154509f651

Changed paths:
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/dynamic_sprite.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/main/quit.cpp
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index e87fd08c544..803c8c8be96 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -448,7 +448,7 @@ ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y
 
 //=============================================================================
 
-void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha) {
+void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha, uint32_t extra_flags) {
 
 	_GP(spriteset).SetSprite(gotSlot, redin);
 
@@ -461,6 +461,8 @@ void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha) {
 	if (hasAlpha)
 		_GP(game).SpriteInfos[gotSlot].Flags |= SPF_ALPHACHANNEL;
 
+	_GP(game).SpriteInfos[gotSlot].Flags |= extra_flags;
+
 	_GP(game).SpriteInfos[gotSlot].Width = redin->GetWidth();
 	_GP(game).SpriteInfos[gotSlot].Height = redin->GetHeight();
 }
diff --git a/engines/ags/engine/ac/dynamic_sprite.h b/engines/ags/engine/ac/dynamic_sprite.h
index 7133715fa2e..ad6c51d1773 100644
--- a/engines/ags/engine/ac/dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynamic_sprite.h
@@ -51,7 +51,7 @@ ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot);
 ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height);
 
 
-void    add_dynamic_sprite(int gotSlot, AGS::Shared::Bitmap *redin, bool hasAlpha = false);
+void    add_dynamic_sprite(int gotSlot, AGS::Shared::Bitmap *redin, bool hasAlpha = false, uint32_t extra_flags = 0u);
 void    free_dynamic_sprite(int gotSlot);
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 35ab74b97ac..37e3171bd0a 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -756,7 +756,7 @@ HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size,
 	for (int i = 0; i < spr_count; ++i) {
 		int id = in->ReadInt32();
 		int flags = in->ReadInt32();
-		add_dynamic_sprite(id, read_serialized_bitmap(in), (flags & SPF_ALPHACHANNEL) != 0);
+		add_dynamic_sprite(id, read_serialized_bitmap(in), (flags & SPF_ALPHACHANNEL) != 0, flags);
 	}
 	return err;
 }
diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp
index e30ed509701..51904144b69 100644
--- a/engines/ags/engine/main/quit.cpp
+++ b/engines/ags/engine/main/quit.cpp
@@ -74,13 +74,14 @@ void quit_stop_cd() {
 }
 
 void quit_check_dynamic_sprites(QuitReason qreason) {
-	if ((qreason & kQuitKind_NormalExit) && (_G(check_dynamic_sprites_at_exit)) &&
-	        (_GP(game).options[OPT_DEBUGMODE] != 0)) {
-		// game exiting normally -- make sure the dynamic sprites
-		// have been deleted
+	if ((qreason & kQuitKind_NormalExit) && _G(check_dynamic_sprites_at_exit) && (_GP(game).options[OPT_DEBUGMODE] != 0)) {
+		// Check that the dynamic sprites have been deleted;
+		// ignore those that are owned by the game objects.
 		for (size_t i = 1; i < _GP(spriteset).GetSpriteSlotCount(); i++) {
-			if (_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC)
+			if ((_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) &&
+				((_GP(game).SpriteInfos[i].Flags & SPF_OBJECTOWNED) == 0)) {
 				debug_script_warn("Dynamic sprite %d was never deleted", i);
+			}
 		}
 	}
 }
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 251229eb473..7ac1383af0e 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -220,8 +220,9 @@ enum GameGuiAlphaRenderingStyle {
 	kGuiAlphaRender_Proper
 };
 
-
-// Sprite flags (serialized as 8-bit)
+// Sprite flags
+// SERIALIZATION NOTE: serialized as 8-bit in game data and legacy saves
+//                     serialized as 32-bit in new saves (for dynamic sprites only).
 #define SPF_HIRES           0x01  // sized for high native resolution (legacy option)
 #define SPF_HICOLOR         0x02  // is 16-bit
 #define SPF_DYNAMICALLOC    0x04  // created by runtime script
@@ -229,6 +230,7 @@ enum GameGuiAlphaRenderingStyle {
 #define SPF_ALPHACHANNEL    0x10  // has alpha-channel
 #define SPF_VAR_RESOLUTION  0x20  // variable resolution (refer to SPF_HIRES)
 #define SPF_HADALPHACHANNEL 0x80  // the saved sprite on disk has one
+#define SPF_OBJECTOWNED		0x0100 // owned by a game object (not created in user script)
 
 // General information about sprite (properties, size)
 struct SpriteInfo {


Commit: 7a0876b6b0b694e5dbe8743f1b1f9f2930eb4b12
    https://github.com/scummvm/scummvm/commit/7a0876b6b0b694e5dbe8743f1b1f9f2930eb4b12
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in CopyScreenIntoBitmap support src rect for legacy letterbox

Partially From upstream 6c028465648be5afe20eef34f0e5d0955329d3dd

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/screen.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 0db289e04fe..e9af8982d7d 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -236,27 +236,32 @@ PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha) {
 	return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr!
 }
 
-Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res, uint32_t batch_skip_filter) {
+Bitmap *CopyScreenIntoBitmap(int width, int height, const Rect *src_rect, bool at_native_res, uint32_t batch_skip_filter) {
 	Bitmap *dst = new Bitmap(width, height, _GP(game).GetColorDepth());
 	GraphicResolution want_fmt;
-	// If the size and color depth are supported we may copy right into our bitmap
-	if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt, batch_skip_filter))
+	// If the size and color depth are supported, then we may copy right into our final bitmap
+	if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, src_rect, at_native_res, &want_fmt, batch_skip_filter))
 		return dst;
+
 	// Otherwise we might need to copy between few bitmaps...
-	Bitmap *buf_screenfmt = new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth);
-	_G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt, at_native_res);
-	// If at least size matches then we may blit
-	if (dst->GetSize() == buf_screenfmt->GetSize()) {
-		dst->Blit(buf_screenfmt);
-	}
-	// Otherwise we need to go through another bitmap of the matching format
-	else {
-		Bitmap *buf_dstfmt = new Bitmap(buf_screenfmt->GetWidth(), buf_screenfmt->GetHeight(), dst->GetColorDepth());
-		buf_dstfmt->Blit(buf_screenfmt);
-		dst->StretchBlt(buf_dstfmt, RectWH(dst->GetSize()));
-		delete buf_dstfmt;
-	}
-	delete buf_screenfmt;
+	// Get screenshot in the suitable format
+	std::unique_ptr<Bitmap> buf_screenfmt(new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth));
+	_G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt.get(), src_rect, at_native_res);
+	// If color depth does not match, and we must stretch-blit, then we need another helper bmp,
+	// because Allegro does not support stretching with mismatching color depths
+	std::unique_ptr<Bitmap> buf_fixdepth;
+	Bitmap *blit_from = buf_screenfmt.get();
+	if ((dst->GetSize() != blit_from->GetSize()) && (want_fmt.ColorDepth != _GP(game).GetColorDepth())) {
+		buf_fixdepth.reset(new Bitmap(want_fmt.Width, want_fmt.Height, _GP(game).GetColorDepth()));
+		buf_fixdepth->Blit(buf_screenfmt.get());
+		blit_from = buf_fixdepth.get();
+	}
+	// Now either blit or stretch-blit
+	if (dst->GetSize() == blit_from->GetSize()) {
+		dst->Blit(blit_from);
+	} else {
+		dst->StretchBlt(blit_from, RectWH(dst->GetSize()));
+	}
 	return dst;
 }
 
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index b16a7f93b1c..676d5698697 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -262,7 +262,8 @@ Shared::Bitmap *PrepareSpriteForUse(Shared::Bitmap *bitmap, bool has_alpha);
 Shared::PBitmap PrepareSpriteForUse(Shared::PBitmap bitmap, bool has_alpha);
 // Makes a screenshot corresponding to the last screen render and returns it as a bitmap
 // of the requested width and height and game's native color depth.
-Shared::Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res = false, uint32_t batch_skip_filter = 0u);
+Shared::Bitmap *CopyScreenIntoBitmap(int width, int height, const Rect *src_rect = nullptr,
+									 bool at_native_res = false, uint32_t batch_skip_filter = 0u);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 803c8c8be96..3ac1c7aa412 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -295,6 +295,8 @@ ScriptDynamicSprite *DynamicSprite_CreateFromScreenShot(int width, int height) {
 	if (gotSlot <= 0)
 		return nullptr;
 
+	// NOTE: be aware that by the historical logic AGS makes a screenshot
+	// of a "main viewport", that may be smaller in legacy "letterbox" mode.
 	const Rect &viewport = _GP(play).GetMainViewport();
 	if (width <= 0)
 		width = viewport.GetWidth();
@@ -306,7 +308,7 @@ ScriptDynamicSprite *DynamicSprite_CreateFromScreenShot(int width, int height) {
 	else
 		height = data_to_game_coord(height);
 
-	Bitmap *newPic = CopyScreenIntoBitmap(width, height);
+	Bitmap *newPic = CopyScreenIntoBitmap(width, height, &viewport);
 
 	// replace the bitmap in the sprite set
 	add_dynamic_sprite(gotSlot, newPic);
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index d0282cfba75..8dab6614319 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -826,6 +826,9 @@ long write_screen_shot_for_vista(Stream *out, Bitmap *screenshot) {
 }
 
 Bitmap *create_savegame_screenshot() {
+	if ((_GP(play).screenshot_width < 16) || (_GP(play).screenshot_height < 16))
+		quit("!Invalid game.screenshot_width/height, must be from 16x16 to screen res");
+
 	// Render the view without any UI elements
 	int old_flags = _G(debug_flags);
 	_G(debug_flags) |= DBG_NOIFACE;
@@ -835,16 +838,13 @@ Bitmap *create_savegame_screenshot() {
 
 	int usewid = data_to_game_coord(_GP(play).screenshot_width);
 	int usehit = data_to_game_coord(_GP(play).screenshot_height);
+	// NOTE: be aware that by the historical logic AGS makes a screenshot
+	// of a "main viewport", that may be smaller in legacy "letterbox" mode.
 	const Rect &viewport = _GP(play).GetMainViewport();
-	if (usewid > viewport.GetWidth())
-		usewid = viewport.GetWidth();
-	if (usehit > viewport.GetHeight())
-		usehit = viewport.GetHeight();
-
-	if ((_GP(play).screenshot_width < 16) || (_GP(play).screenshot_height < 16))
-		quit("!Invalid game.screenshot_width/height, must be from 16x16 to screen res");
+	usewid = std::min(usewid, viewport.GetWidth());
+	usehit = std::min(usehit, viewport.GetHeight());
 
-	Bitmap *screenshot = CopyScreenIntoBitmap(usewid, usehit);
+	Bitmap *screenshot = CopyScreenIntoBitmap(usewid, usehit, &viewport);
 	screenshot->GetAllegroBitmap()->makeOpaque();
 
 	// Restore original screen
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index fafaea8b17c..5ff44728628 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -647,7 +647,10 @@ int SaveScreenShot(const char *namm) {
 	else
 		fileName = Path::ConcatPaths(svg_dir, namm);
 
-	Bitmap *buffer = CopyScreenIntoBitmap(_GP(play).GetMainViewport().GetWidth(), _GP(play).GetMainViewport().GetHeight());
+	// NOTE: be aware that by the historical logic AGS makes a screenshot
+	// of a "main viewport", that may be smaller in legacy "letterbox" mode.
+	const Rect &viewport = _GP(play).GetMainViewport();
+	Bitmap *buffer = CopyScreenIntoBitmap(_GP(play).GetMainViewport().GetWidth(), _GP(play).GetMainViewport().GetHeight(), &viewport);
 	if (!buffer->SaveToFile(fileName, _G(palette)) != 0) {
 		delete buffer;
 		return 0;
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index 415ceec0caf..cc2c830c994 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -79,7 +79,7 @@ void current_fade_out_effect() {
 	} else {
 		get_palette(_G(old_palette));
 		const Rect &viewport = _GP(play).GetMainViewport();
-		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), false /* use current resolution */, RENDER_SHOT_SKIP_ON_FADE);
+		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), &viewport, false /* use current resolution */, RENDER_SHOT_SKIP_ON_FADE);
 	}
 }
 
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index b988d637959..ee12a64c109 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -679,21 +679,21 @@ void ScummVMRendererGraphicsDriver::SetStageBackBuffer(Bitmap *backBuffer) {
 		_stageVirtualScreen = cur_stage;
 }
 
-bool ScummVMRendererGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt, uint32_t /*batch_skip_filter*/) {
+bool ScummVMRendererGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, const Rect *src_rect, bool at_native_res,
+															  GraphicResolution *want_fmt, uint32_t /*batch_skip_filter*/) {
 	(void)at_native_res; // software driver always renders at native resolution at the moment
-	// software filter is taught to copy to any size
+	// software filter is taught to copy to any size, so only check color depth
 	if (destination->GetColorDepth() != _srcColorDepth) {
 		if (want_fmt)
 			*want_fmt = GraphicResolution(destination->GetWidth(), destination->GetHeight(), _srcColorDepth);
 		return false;
 	}
 
-	if (destination->GetSize() == virtualScreen->GetSize()) {
-		destination->Blit(virtualScreen, 0, 0, 0, 0, virtualScreen->GetWidth(), virtualScreen->GetHeight());
+	Rect copy_from = src_rect ? *src_rect : _srcRect;
+	if (destination->GetSize() == copy_from.GetSize()) {
+		destination->Blit(virtualScreen, copy_from.Left, copy_from.Top, 0, 0, copy_from.GetWidth(), copy_from.GetHeight());
 	} else {
-		destination->StretchBlt(virtualScreen,
-		                        RectWH(0, 0, virtualScreen->GetWidth(), virtualScreen->GetHeight()),
-		                        RectWH(0, 0, destination->GetWidth(), destination->GetHeight()));
+		destination->StretchBlt(virtualScreen, copy_from, RectWH(destination->GetSize()));
 	}
 	return true;
 }
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index b92118f5f73..095f45666d1 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -217,7 +217,7 @@ public:
 	void RenderToBackBuffer() override;
 	void Render() override;
 	void Render(int xoff, int yoff, Shared::GraphicFlip flip) override;
-	bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt,
+	bool GetCopyOfScreenIntoBitmap(Bitmap *destination, const Rect *src_rect, bool at_native_res, GraphicResolution *want_fmt,
 								   uint32_t batch_skip_filter = 0u) override;
 	void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue,
 				 uint32_t batch_skip_filter = 0u) override;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 76b4fa9cd38..6fe0f82f4ec 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -207,7 +207,7 @@ public:
 	// Copies contents of the game screen into bitmap using simple blit or pixel copy.
 	// Bitmap must be of supported size and pixel format. If it's not the method will
 	// fail and optionally write wanted destination format into 'want_fmt' pointer.
-	virtual bool GetCopyOfScreenIntoBitmap(Shared::Bitmap *destination, bool at_native_res,
+	virtual bool GetCopyOfScreenIntoBitmap(Shared::Bitmap *destination, const Rect *src_rect, bool at_native_res,
 										   GraphicResolution *want_fmt = nullptr, uint32_t batch_skip_filter = 0u) = 0;
 	// Tells if the renderer supports toggling vsync after initializing the mode.
 	virtual bool DoesSupportVsyncToggle() = 0;


Commit: 1f2ef9ce4a380fe5b3c5b826725b2e8da10c8010
    https://github.com/scummvm/scummvm/commit/1f2ef9ce4a380fe5b3c5b826725b2e8da10c8010
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed Crossfade and Dissolve transitions in letterbox mode

>From upstream 4ad64d3f2a7dfaaff07b8ccd17ececab76720477

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


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index db010a6bb69..35dcbf01b2b 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -260,6 +260,8 @@ void process_event(const EventHappened *evp) {
 			if (_GP(game).color_depth == 1)
 				quit("!Cannot use crossfade screen transition in 256-colour games");
 
+			const bool fullredraw = _G(gfxDriver)->RequiresFullRedrawEachFrame();
+			const SpriteTransform spr_trans = _GP(play).GetGlobalTransform(fullredraw);
 			// TODO: crossfade does not need a screen with transparency, it should be opaque;
 			// but Software renderer cannot alpha-blend non-masked sprite at the moment,
 			// see comment to drawing opaque sprite in SDLRendererGraphicsDriver!
@@ -273,7 +275,7 @@ void process_event(const EventHappened *evp) {
 				construct_game_screen_overlay(false);
 				// draw old screen on top while alpha > 16
 				if (alpha > 16) {
-					_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
+					_G(gfxDriver)->BeginSpriteBatch(viewport, spr_trans);
 					_G(gfxDriver)->DrawSprite(0, 0, ddb);
 					_G(gfxDriver)->EndSpriteBatch();
 				}
@@ -291,6 +293,8 @@ void process_event(const EventHappened *evp) {
 			int aa, bb, cc;
 			RGB interpal[256];
 
+			const bool fullredraw = _G(gfxDriver)->RequiresFullRedrawEachFrame();
+			const SpriteTransform spr_trans = _GP(play).GetGlobalTransform(fullredraw);
 			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in(false /* transparent */);
 			for (aa = 0; aa < 16; aa++) {
 				// merge the palette while dithering
@@ -308,7 +312,7 @@ void process_event(const EventHappened *evp) {
 				_G(gfxDriver)->UpdateDDBFromBitmap(ddb, _G(saved_viewport_bitmap), false);
 				construct_game_scene(true);
 				construct_game_screen_overlay(false);
-				_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
+				_G(gfxDriver)->BeginSpriteBatch(viewport, spr_trans);
 				_G(gfxDriver)->DrawSprite(0, 0, ddb);
 				_G(gfxDriver)->EndSpriteBatch();
 				render_to_screen();


Commit: 9a766e1a35b758be12608a14b0074f96fd3ec48a
    https://github.com/scummvm/scummvm/commit/9a766e1a35b758be12608a14b0074f96fd3ec48a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed extra frame drawn before Crossfade or Dissolve fadein

>From upstream ed5e81cfb6e933ef5b6fc139b362723f1de34f10

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


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 35dcbf01b2b..dc8543d9c69 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -255,7 +255,6 @@ void process_event(const EventHappened *evp) {
 				}
 				_G(gfxDriver)->SetMemoryBackBuffer(saved_backbuf);
 			}
-			_GP(play).screen_is_faded_out = 0;
 		} else if (theTransition == FADE_CROSSFADE) {
 			if (_GP(game).color_depth == 1)
 				quit("!Cannot use crossfade screen transition in 256-colour games");
diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp
index 189ced6aa41..f7e150005c8 100644
--- a/engines/ags/engine/ac/global_screen.cpp
+++ b/engines/ags/engine/ac/global_screen.cpp
@@ -146,10 +146,8 @@ void FadeOut(int sppd) {
 void fadeout_impl(int spdd) {
 	if (_GP(play).screen_is_faded_out == 0) {
 		_G(gfxDriver)->FadeOut(spdd, _GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue, RENDER_SHOT_SKIP_ON_FADE);
-	}
-
-	if (_GP(game).color_depth > 1)
 		_GP(play).screen_is_faded_out = 1;
+	}
 }
 
 void SetScreenTransition(int newtrans) {
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index cc2c830c994..c9540b79f02 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -43,11 +43,11 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 void fadein_impl(PALETTE p, int speed) {
+	// reset this early to force whole game draw during fading in
+	_GP(play).screen_is_faded_out = 0;
+
 	if (_GP(game).color_depth > 1) {
 		set_palette(p);
-
-		_GP(play).screen_is_faded_out = 0;
-
 		if (_GP(play).no_hicolor_fadein) {
 			return;
 		}
@@ -75,12 +75,13 @@ void current_fade_out_effect() {
 		fadeout_impl(5);
 	} else if (theTransition == FADE_BOXOUT) {
 		_G(gfxDriver)->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed(), RENDER_SHOT_SKIP_ON_FADE);
-		_GP(play).screen_is_faded_out = 1;
 	} else {
 		get_palette(_G(old_palette));
 		const Rect &viewport = _GP(play).GetMainViewport();
 		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), &viewport, false /* use current resolution */, RENDER_SHOT_SKIP_ON_FADE);
 	}
+
+	_GP(play).screen_is_faded_out = 1;
 }
 
 IDriverDependantBitmap *prepare_screen_for_transition_in(bool opaque) {


Commit: 258f9fa795761bea4ced2f274d6c27d1fb44bf78
    https://github.com/scummvm/scummvm/commit/258f9fa795761bea4ced2f274d6c27d1fb44bf78
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed mouse cursor texture broken after RunAGSGame()

Partially from upstream 5ac3e6a97932a193250557eaa360d7e0b8e7b51f

Changed paths:
    engines/ags/engine/ac/mouse.cpp
    engines/ags/engine/ac/mouse.h
    engines/ags/engine/game/savegame.cpp


diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp
index 0f58655f0e3..65721178002 100644
--- a/engines/ags/engine/ac/mouse.cpp
+++ b/engines/ags/engine/ac/mouse.cpp
@@ -98,13 +98,12 @@ void SetMouseBounds(int x1, int y1, int x2, int y2) {
 
 // mouse cursor functions:
 // set_mouse_cursor: changes visual appearance to specified cursor
-void set_mouse_cursor(int newcurs) {
+void set_mouse_cursor(int newcurs, bool force_update) {
 	const int hotspotx = _GP(game).mcurs[newcurs].hotx, hotspoty = _GP(game).mcurs[newcurs].hoty;
 	_GP(mouse).SetHotspot(hotspotx, hotspoty);
 
 	// if it's same cursor and there's animation in progress, then don't assign a new pic just yet
-	if (newcurs == _G(cur_cursor) && _GP(game).mcurs[newcurs].view >= 0 &&
-	        (_G(mouse_frame) > 0 || _G(mouse_delay) > 0)) {
+	if (!force_update && newcurs == _G(cur_cursor) && _GP(game).mcurs[newcurs].view >= 0 && (_G(mouse_frame) > 0 || _G(mouse_delay) > 0)) {
 		return;
 	}
 
diff --git a/engines/ags/engine/ac/mouse.h b/engines/ags/engine/ac/mouse.h
index 858cc13122c..29a53e81276 100644
--- a/engines/ags/engine/ac/mouse.h
+++ b/engines/ags/engine/ac/mouse.h
@@ -46,7 +46,7 @@ void SetMouseBounds(int x1, int y1, int x2, int y2);
 void RefreshMouse();
 // mouse cursor functions:
 // set_mouse_cursor: changes visual appearance to specified cursor
-void set_mouse_cursor(int newcurs);
+void set_mouse_cursor(int newcurs, bool force_update = false);
 // set_default_cursor: resets visual appearance to current mode (walk, look, etc);
 void set_default_cursor();
 // set_cursor_mode: changes mode and appearance
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index aecb79d78e7..6787c4b41a7 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -512,7 +512,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 	_GP(mouse).SetMoveLimit(Rect(oldx1, oldy1, oldx2, oldy2));
 
 	set_cursor_mode(r_data.CursorMode);
-	set_mouse_cursor(r_data.CursorID);
+	set_mouse_cursor(r_data.CursorID, true);
 	if (r_data.CursorMode == MODE_USE)
 		SetActiveInventory(_G(playerchar)->activeinv);
 	// ensure that the current cursor is locked


Commit: e6ea49d9d3b30100ad975563eaa82092dcb37530
    https://github.com/scummvm/scummvm/commit/e6ea49d9d3b30100ad975563eaa82092dcb37530
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed restoring letterboxed viewport after loading an old save

>From upstream 027d724427c7460ace03b6cc7e23a4cc7ccf6e5b

Changed paths:
    engines/ags/engine/game/savegame.cpp
    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 6787c4b41a7..d695b85e7f9 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -392,6 +392,10 @@ void DoBeforeRestore(PreservedParams &pp) {
 }
 
 void RestoreViewportsAndCameras(const RestoredData &r_data) {
+	// If restored from older saves, we have to adjust
+	// cam and view sizes to a main viewport, which is init later
+	const auto &main_view = _GP(play).GetMainViewport();
+
 	for (size_t i = 0; i < r_data.Cameras.size(); ++i) {
 		const auto &cam_dat = r_data.Cameras[i];
 		auto cam = _GP(play).GetRoomCamera(i);
@@ -400,16 +404,21 @@ void RestoreViewportsAndCameras(const RestoredData &r_data) {
 			cam->Lock();
 		else
 			cam->Release();
-		// Set size first, or offset position may clamp to the room
-		cam->SetSize(Size(cam_dat.Width, cam_dat.Height));
 		cam->SetAt(cam_dat.Left, cam_dat.Top);
+		if (r_data.LegacyViewCamera)
+			cam->SetSize(main_view.GetSize());
+		else
+			cam->SetSize(Size(cam_dat.Width, cam_dat.Height));
 	}
 	for (size_t i = 0; i < r_data.Viewports.size(); ++i) {
 		const auto &view_dat = r_data.Viewports[i];
 		auto view = _GP(play).GetRoomViewport(i);
 		view->SetID(view_dat.ID);
 		view->SetVisible((view_dat.Flags & kSvgViewportVisible) != 0);
-		view->SetRect(RectWH(view_dat.Left, view_dat.Top, view_dat.Width, view_dat.Height));
+		if (r_data.LegacyViewCamera)
+			view->SetRect(RectWH(view_dat.Left, view_dat.Top, main_view.GetWidth(), main_view.GetHeight()));
+		else
+			view->SetRect(RectWH(view_dat.Left, view_dat.Top, view_dat.Width, view_dat.Height));
 		view->SetZOrder(view_dat.ZOrder);
 		// Restore camera link
 		int cam_index = view_dat.CamID;
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 37e3171bd0a..39240fe25b1 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -234,21 +234,17 @@ void ReadLegacyCameraState(Stream *in, RestoredData & r_data) {
 	int camy = in->ReadInt32();
 	_GP(play).CreateRoomCamera();
 	_GP(play).CreateRoomViewport();
-	const auto &main_view = _GP(play).GetMainViewport();
 	RestoredData::CameraData cam_dat;
 	cam_dat.ID = 0;
 	cam_dat.Left = camx;
 	cam_dat.Top = camy;
-	cam_dat.Width = main_view.GetWidth();
-	cam_dat.Height = main_view.GetHeight();
 	r_data.Cameras.push_back(cam_dat);
 	RestoredData::ViewportData view_dat;
 	view_dat.ID = 0;
-	view_dat.Width = main_view.GetWidth();
-	view_dat.Height = main_view.GetHeight();
 	view_dat.Flags = kSvgViewportVisible;
 	view_dat.CamID = 0;
 	r_data.Viewports.push_back(view_dat);
+	r_data.LegacyViewCamera = true;
 }
 
 void ReadCameraState(RestoredData &r_data, Stream *in) {
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index f4c3805aaa0..736f8fd3f75 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -134,6 +134,7 @@ struct RestoredData {
 	};
 	std::vector<ViewportData> Viewports;
 	std::vector<CameraData> Cameras;
+	bool LegacyViewCamera = false;
 	int32_t Camera0_Flags = 0; // flags for primary camera, when data is read in legacy order
 
 	RestoredData();


Commit: 209ec5a953a94a5f32020ea7026004135765d082
    https://github.com/scummvm/scummvm/commit/209ec5a953a94a5f32020ea7026004135765d082
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hotfix room objects not visible if FadeIn called in "room load"

>From upstream b25b9eebe3525433251d23298ba0339a7505ef8b

Changed paths:
    engines/ags/engine/ac/global_screen.cpp
    engines/ags/engine/main/game_run.cpp
    engines/ags/engine/main/game_run.h


diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp
index f7e150005c8..09ac8d4e33c 100644
--- a/engines/ags/engine/ac/global_screen.cpp
+++ b/engines/ags/engine/ac/global_screen.cpp
@@ -184,6 +184,9 @@ void FadeIn(int sppd) {
 	if (_GP(play).fast_forward)
 		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
+	SyncDrawablesState();
 	// FIXME: we have to sync audio here explicitly, because FadeIn
 	// does not call any game update function while it works
 	sync_audio_playback();
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 8927cbffaa2..a2d79ba29ca 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -1131,6 +1131,11 @@ void UpdateCursorAndDrawables() {
 	update_objects_scale();
 }
 
+void SyncDrawablesState() {
+	// TODO: there's likely more things that could've be done here
+	update_objects_scale();
+}
+
 void update_polled_stuff() {
 	::AGS::g_events->pollEvents();
 
diff --git a/engines/ags/engine/main/game_run.h b/engines/ags/engine/main/game_run.h
index 02f1fc15f91..f944faaef62 100644
--- a/engines/ags/engine/main/game_run.h
+++ b/engines/ags/engine/main/game_run.h
@@ -55,6 +55,10 @@ void UpdateGameAudioOnly();
 // this function is useful when you don't want to update whole game, but only things
 // that are necessary for rendering the game screen.
 void UpdateCursorAndDrawables();
+// Syncs object drawable states with their logical states.
+// Useful after a major game state change, such as loading new room, in case we expect
+// that a render may occur before a normal game update is performed.
+void SyncDrawablesState();
 // Gets current logical game FPS, this is normally a fixed number set in script;
 // in case of "maxed fps" mode this function returns real measured FPS.
 float get_game_fps();


Commit: 5d58384af22eb15f3e14550ffa36c344053d0d6b
    https://github.com/scummvm/scummvm/commit/5d58384af22eb15f3e14550ffa36c344053d0d6b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: remove "supersampling" setting, as not entirely supported

This setting comes from the original mobile port of AGS, but it's not clear whether it was
useful. More importantly, there are issues with this:
1. It was never implemented in another hardware accelerated driver (Direct3D), being
exclusive to OpenGL.
2. It was never added to standard setup, so not very visible.
3. It's not clear whether it was working properly all this time. There were multiple changes to graphic renderers, and some of them may assume that rendering at "native resolution" is done at exactly, well, native resolution.

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game_setup.cpp
    engines/ags/engine/ac/game_setup.h
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/graphics_driver.h
    engines/ags/engine/main/config.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index e9af8982d7d..fdddbd5bb98 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2066,7 +2066,7 @@ void construct_game_scene(bool full_redraw) {
 	_GP(play).UpdateViewports();
 
 	_G(gfxDriver)->UseSmoothScaling(IS_ANTIALIAS_SPRITES);
-	_G(gfxDriver)->RenderSpritesAtScreenResolution(_GP(usetup).RenderAtScreenRes, _GP(usetup).Supersampling);
+	_G(gfxDriver)->RenderSpritesAtScreenResolution(_GP(usetup).RenderAtScreenRes);
 
 	pl_run_plugin_hooks(AGSE_PRERENDER, 0);
 
diff --git a/engines/ags/engine/ac/game_setup.cpp b/engines/ags/engine/ac/game_setup.cpp
index bc9273f6048..3030519e46c 100644
--- a/engines/ags/engine/ac/game_setup.cpp
+++ b/engines/ags/engine/ac/game_setup.cpp
@@ -39,7 +39,6 @@ GameSetup::GameSetup() {
 	mouse_ctrl_enabled = true;
 	mouse_speed_def = kMouseSpeed_CurrentDisplay;
 	RenderAtScreenRes = false;
-	Supersampling = 1;
 	clear_cache_on_room_change = false;
 	load_latest_save = false;
 	rotation = kScreenRotation_Unlocked;
diff --git a/engines/ags/engine/ac/game_setup.h b/engines/ags/engine/ac/game_setup.h
index ab99993516c..f1499b871bf 100644
--- a/engines/ags/engine/ac/game_setup.h
+++ b/engines/ags/engine/ac/game_setup.h
@@ -90,7 +90,6 @@ struct GameSetup {
 	bool  mouse_ctrl_enabled;
 	MouseSpeedDef mouse_speed_def;
 	bool  RenderAtScreenRes; // render sprites at screen resolution, as opposed to native one
-	int   Supersampling;
 	size_t SpriteCacheSize = DefSpriteCacheSize;  // in KB
 	size_t TextureCacheSize = DefTexCacheSize;  // in KB
 	bool  clear_cache_on_room_change; // for low-end devices: clear resource caches on room change
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 095f45666d1..843cbaafe64 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -228,7 +228,7 @@ public:
 	void SetGamma(int newGamma) override;
 	void UseSmoothScaling(bool /*enabled*/) override {}
 	bool DoesSupportVsyncToggle() override;
-	void RenderSpritesAtScreenResolution(bool /*enabled*/, int /*supersampling*/) override {}
+	void RenderSpritesAtScreenResolution(bool /*enabled*/) override {}
 	Bitmap *GetMemoryBackBuffer() override;
 	void SetMemoryBackBuffer(Bitmap *backBuffer) override;
 	Bitmap *GetStageBackBuffer(bool mark_dirty) override;
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 6fe0f82f4ec..82cf1474d78 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -221,7 +221,7 @@ public:
 	// drawn with additional fractional scaling will appear more detailed than
 	// the rest of the game. The effect is stronger for the low-res games being
 	// rendered in the high-res mode.
-	virtual void RenderSpritesAtScreenResolution(bool enabled, int supersampling = 1) = 0;
+	virtual void RenderSpritesAtScreenResolution(bool enabled) = 0;
 	// TODO: move fade-in/out/boxout functions out of the graphics driver!! make everything render through
 	// main drawing procedure. Since currently it does not - we need to init our own sprite batch
 	// internally to let it set up correct viewport settings instead of relying on a chance.
diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp
index 66e86034a77..dd898e90b1e 100644
--- a/engines/ags/engine/main/config.cpp
+++ b/engines/ags/engine/main/config.cpp
@@ -310,7 +310,6 @@ void apply_config(const ConfigTree &cfg) {
 
 		_GP(usetup).RenderAtScreenRes = CfgReadBoolInt(cfg, "graphics", "render_at_screenres");
 		_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "graphics", "antialias");
-		_GP(usetup).Supersampling = CfgReadInt(cfg, "graphics", "supersampling", 1);
 		_GP(usetup).software_render_driver = CfgReadString(cfg, "graphics", "software_driver");
 
 #ifdef TODO


Commit: 26cf741f8a6e3bcbeef535ac9ae2a9fd0ac17c83
    https://github.com/scummvm/scummvm/commit/26cf741f8a6e3bcbeef535ac9ae2a9fd0ac17c83
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed camera may fail to restore its position in the room

>From upstream c1335bcabfcfa6a8a4dec0b5b8c175cfe5cf02e9

Changed paths:
    engines/ags/engine/game/savegame.cpp


diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index d695b85e7f9..6f5fe97c745 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -404,11 +404,12 @@ void RestoreViewportsAndCameras(const RestoredData &r_data) {
 			cam->Lock();
 		else
 			cam->Release();
-		cam->SetAt(cam_dat.Left, cam_dat.Top);
+		// Set size first, or offset position may clamp to the room
 		if (r_data.LegacyViewCamera)
 			cam->SetSize(main_view.GetSize());
 		else
 			cam->SetSize(Size(cam_dat.Width, cam_dat.Height));
+		cam->SetAt(cam_dat.Left, cam_dat.Top);
 	}
 	for (size_t i = 0; i < r_data.Viewports.size(); ++i) {
 		const auto &view_dat = r_data.Viewports[i];


Commit: 77c508662f4300a741daf77592a0d324648b7802
    https://github.com/scummvm/scummvm/commit/77c508662f4300a741daf77592a0d324648b7802
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fixed "Engine: hotfix potential exception if cc_error is called during quit()"

>From upstream a7416bfb473a68ad34958bcba028454565bb5756

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/script/script.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 8dab6614319..0feed597cc4 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -373,8 +373,6 @@ void unload_game() {
 	pl_stop_plugins();
 
 	// Free all script instances and script modules
-	ccInstance::FreeInstanceStack();
-
 	FreeAllScriptInstances();
 	FreeGlobalScripts();
 
diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp
index c2dd2a4aad3..e3ea592c6f6 100644
--- a/engines/ags/engine/script/script.cpp
+++ b/engines/ags/engine/script/script.cpp
@@ -473,6 +473,7 @@ void AllocScriptModules() {
 }
 
 void FreeAllScriptInstances() {
+	ccInstance::FreeInstanceStack();
 	FreeRoomScriptInstance();
 
 	// NOTE: don't know why, but Forks must be deleted prior to primary inst,


Commit: afde8d4a9dd6d8476e35544314f34763a46a2744
    https://github.com/scummvm/scummvm/commit/afde8d4a9dd6d8476e35544314f34763a46a2744
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.22), mark "stable" 3.6.1 release

Partially from upstream f2b1a7db6cc745830f6b52c3af91387044bb03ca

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 2fe851b7852..1b23c05e1e1 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.20"
+#define ACI_VERSION_STR      "3.6.1.22"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.20
+#define ACI_VERSION_MSRC_DEF  3.6.1.22
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 91ea7cb09941296ace89a594fa07225ee2b457f3
    https://github.com/scummvm/scummvm/commit/91ea7cb09941296ace89a594fa07225ee2b457f3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Don't attempt to access endtimeoffs/frame vectors for lines with no phonemes

Changed paths:
    engines/ags/engine/game/game_init.cpp


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index a70f61aa7e5..16ee8a1e16d 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -298,6 +298,8 @@ void LoadLipsyncData() {
 		for (int ee = 0; ee < _G(numLipLines); ee++) {
 			_GP(splipsync)[ee].numPhonemes = speechsync->ReadInt16();
 			speechsync->Read(_GP(splipsync)[ee].filename, 14);
+			if (_GP(splipsync)[ee].numPhonemes == 0)
+				continue;
 			_GP(splipsync)[ee].endtimeoffs.resize(_GP(splipsync)[ee].numPhonemes);
 			speechsync->ReadArrayOfInt32(&_GP(splipsync)[ee].endtimeoffs.front(), _GP(splipsync)[ee].numPhonemes);
 			_GP(splipsync)[ee].frame.resize(_GP(splipsync)[ee].numPhonemes);


Commit: 794318b9dbc478e8bb573b1e71906d36265a6f63
    https://github.com/scummvm/scummvm/commit/794318b9dbc478e8bb573b1e71906d36265a6f63
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed item crosshair colors read with mistake

>From upstream 4de9f8b1064823130945d780e7ec16104c678e00

Changed paths:
    engines/ags/shared/ac/game_setup_struct.cpp
    engines/ags/shared/ac/game_setup_struct_base.cpp


diff --git a/engines/ags/shared/ac/game_setup_struct.cpp b/engines/ags/shared/ac/game_setup_struct.cpp
index 12de2d7f0fb..5e2d64772aa 100644
--- a/engines/ags/shared/ac/game_setup_struct.cpp
+++ b/engines/ags/shared/ac/game_setup_struct.cpp
@@ -400,8 +400,8 @@ void GameSetupStruct::ReadFromSavegame(Stream *in) {
 	// of GameSetupStructBase
 	playercharacter = in->ReadInt32();
 	dialog_bullet = in->ReadInt32();
-	hotdot = in->ReadInt16();
-	hotdotouter = in->ReadInt16();
+	hotdot = static_cast<uint16_t>(in->ReadInt16());
+	hotdotouter = static_cast<uint16_t>(in->ReadInt16());
 	invhotdotsprite = in->ReadInt32();
 	default_lipsync_frame = in->ReadInt32();
 }
@@ -413,8 +413,8 @@ void GameSetupStruct::WriteForSavegame(Stream *out) {
 	// of GameSetupStructBase
 	out->WriteInt32(playercharacter);
 	out->WriteInt32(dialog_bullet);
-	out->WriteInt16(hotdot);
-	out->WriteInt16(hotdotouter);
+	out->WriteInt16(static_cast<uint16_t>(hotdot));
+	out->WriteInt16(static_cast<uint16_t>(hotdotouter));
 	out->WriteInt32(invhotdotsprite);
 	out->WriteInt32(default_lipsync_frame);
 }
diff --git a/engines/ags/shared/ac/game_setup_struct_base.cpp b/engines/ags/shared/ac/game_setup_struct_base.cpp
index 582535adcd7..38ce01259b9 100644
--- a/engines/ags/shared/ac/game_setup_struct_base.cpp
+++ b/engines/ags/shared/ac/game_setup_struct_base.cpp
@@ -157,8 +157,8 @@ void GameSetupStructBase::ReadFromFile(Stream *in, GameDataVersion game_ver, Ser
 	color_depth = in->ReadInt32();
 	target_win = in->ReadInt32();
 	dialog_bullet = in->ReadInt32();
-	hotdot = in->ReadInt16();
-	hotdotouter = in->ReadInt16();
+	hotdot = static_cast<uint16_t>(in->ReadInt16());
+	hotdotouter = static_cast<uint16_t>(in->ReadInt16());
 	uniqueid = in->ReadInt32();
 	numgui = in->ReadInt32();
 	numcursors = in->ReadInt32();
@@ -205,8 +205,8 @@ void GameSetupStructBase::WriteToFile(Stream *out, const SerializeInfo &info) co
 	out->WriteInt32(color_depth);
 	out->WriteInt32(target_win);
 	out->WriteInt32(dialog_bullet);
-	out->WriteInt16(hotdot);
-	out->WriteInt16(hotdotouter);
+	out->WriteInt16(static_cast<uint16_t>(hotdot));
+	out->WriteInt16(static_cast<uint16_t>(hotdotouter));
 	out->WriteInt32(uniqueid);
 	out->WriteInt32(numgui);
 	out->WriteInt32(numcursors);


Commit: 8e53e97a2609ba1184c637b7e8a1d4537eb8c1fb
    https://github.com/scummvm/scummvm/commit/8e53e97a2609ba1184c637b7e8a1d4537eb8c1fb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix type/add cast in main_game_file

Changed paths:
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index bb9ad90afb1..e4cefd0a3a3 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -253,7 +253,7 @@ void ReadViews(GameSetupStruct &game, std::vector<ViewStruct> &views, Stream *in
 	} else // 2.x views
 	{
 		std::vector<ViewStruct272> oldv(game.numviews);
-		for (size_t i = 0; i < game.numviews; ++i) {
+		for (int i = 0; i < game.numviews; ++i) {
 			oldv[i].ReadFromFile(in);
 		}
 		Convert272ViewsToNew(oldv, views);
@@ -756,7 +756,7 @@ HError GameDataExtReader::ReadBlock(int /*block_id*/, const String &ext_id,
 			snprintf(chinfo.name, LEGACY_MAX_CHAR_NAME_LEN, "%s", chinfo2.name_new.GetCStr());
 		}
 		size_t num_invitems = _in->ReadInt32();
-		if (num_invitems != _ents.Game.numinvitems)
+		if (num_invitems != (size_t)_ents.Game.numinvitems)
 			return new Error(String::FromFormat("Mismatching number of inventory items: read %zu expected %zu", num_invitems, (size_t)_ents.Game.numinvitems));
 		for (int i = 0; i < _ents.Game.numinvitems; ++i) {
 			_ents.Game.invinfo[i].name = StrUtil::ReadString(_in);


Commit: 5ce015027ff87289828326aa4869803e28d7e556
    https://github.com/scummvm/scummvm/commit/5ce015027ff87289828326aa4869803e28d7e556
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add const_casts, comment out unneeded bindings

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/character.cpp
    engines/ags/engine/ac/dialog.cpp
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/hotspot.cpp
    engines/ags/engine/ac/inventory_item.cpp
    engines/ags/engine/ac/label.cpp
    engines/ags/engine/ac/listbox.cpp
    engines/ags/engine/ac/object.cpp
    engines/ags/engine/ac/parser.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/script_containers.cpp
    engines/ags/engine/ac/string.cpp
    engines/ags/engine/ac/system.cpp
    engines/ags/engine/ac/textbox.cpp
    engines/ags/engine/script/script_api.h


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index c17e8915496..bf97c0bf094 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -314,7 +314,7 @@ RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *param
 
 // const char* | GUIButton *butt
 RuntimeScriptValue Sc_Button_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(GUIButton, const char, _GP(myScriptStringImpl), Button_GetText_New);
+	API_OBJCALL_OBJ(GUIButton, const char, _GP(myScriptStringImpl), Button_GetText_New);
 }
 
 // void | GUIButton *butt, char *buffer
diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index db31b86c7e9..dbae6dde16c 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -2988,7 +2988,7 @@ RuntimeScriptValue Sc_Character_GetPropertyText(void *self, const RuntimeScriptV
 
 // const char* (CharacterInfo *chaa, const char *property)
 RuntimeScriptValue Sc_Character_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetTextProperty, const char);
+	API_OBJCALL_OBJ_POBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetTextProperty, const char);
 }
 
 RuntimeScriptValue Sc_Character_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
@@ -3430,7 +3430,7 @@ RuntimeScriptValue Sc_Character_GetDestinationY(void *self, const RuntimeScriptV
 
 // const char* (CharacterInfo *chaa)
 RuntimeScriptValue Sc_Character_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetName);
+	API_OBJCALL_OBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetName);
 }
 
 // void (CharacterInfo *chaa, const char *newName)
diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp
index 98be88b7c3a..0e890a2c7c6 100644
--- a/engines/ags/engine/ac/dialog.cpp
+++ b/engines/ags/engine/ac/dialog.cpp
@@ -1226,7 +1226,7 @@ RuntimeScriptValue Sc_Dialog_GetOptionState(void *self, const RuntimeScriptValue
 
 // const char* (ScriptDialog *sd, int option)
 RuntimeScriptValue Sc_Dialog_GetOptionText(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT(ScriptDialog, const char, _GP(myScriptStringImpl), Dialog_GetOptionText);
+	API_OBJCALL_OBJ_PINT(ScriptDialog, const char, _GP(myScriptStringImpl), Dialog_GetOptionText);
 }
 
 // int (ScriptDialog *sd, int option)
diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 806fdfd78f4..4f30a2f7927 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -669,7 +669,7 @@ RuntimeScriptValue Sc_File_ReadRawLine(void *self, const RuntimeScriptValue *par
 
 // const char* (sc_File *fil)
 RuntimeScriptValue Sc_File_ReadRawLineBack(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadRawLineBack);
+	API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadRawLineBack);
 }
 
 // void (sc_File *fil, char *toread)
@@ -679,7 +679,7 @@ RuntimeScriptValue Sc_File_ReadString(void *self, const RuntimeScriptValue *para
 
 // const char* (sc_File *fil)
 RuntimeScriptValue Sc_File_ReadStringBack(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadStringBack);
+	API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadStringBack);
 }
 
 // void (sc_File *fil, int towrite)
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 0feed597cc4..e081994bc63 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1520,7 +1520,7 @@ RuntimeScriptValue Sc_Game_GetFrameCountForLoop(const RuntimeScriptValue *params
 
 // const char* (int x, int y)
 RuntimeScriptValue Sc_Game_GetLocationName(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_PINT2(const char, _GP(myScriptStringImpl), Game_GetLocationName);
+	API_SCALL_OBJ_PINT2(const char, _GP(myScriptStringImpl), Game_GetLocationName);
 }
 
 // int (int viewNumber)
@@ -1540,7 +1540,7 @@ RuntimeScriptValue Sc_Game_GetRunNextSettingForLoop(const RuntimeScriptValue *pa
 
 // const char* (int slnum)
 RuntimeScriptValue Sc_Game_GetSaveSlotDescription(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetSaveSlotDescription);
+	API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetSaveSlotDescription);
 }
 
 // ScriptViewFrame* (int viewNumber, int loopNumber, int frame)
@@ -1550,7 +1550,7 @@ RuntimeScriptValue Sc_Game_GetViewFrame(const RuntimeScriptValue *params, int32_
 
 // const char* (const char *msg)
 RuntimeScriptValue Sc_Game_InputBox(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Game_InputBox, const char);
+	API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Game_InputBox, const char);
 }
 
 // int (const char *newFolder)
@@ -1575,7 +1575,7 @@ RuntimeScriptValue Sc_Game_GetDialogCount(const RuntimeScriptValue *params, int3
 
 // const char *()
 RuntimeScriptValue Sc_Game_GetFileName(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetFileName);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetFileName);
 }
 
 // int ()
@@ -1585,12 +1585,12 @@ RuntimeScriptValue Sc_Game_GetFontCount(const RuntimeScriptValue *params, int32_
 
 // const char* (int index)
 RuntimeScriptValue Sc_Game_GetGlobalMessages(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalMessages);
+	API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalMessages);
 }
 
 // const char* (int index)
 RuntimeScriptValue Sc_Game_GetGlobalStrings(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalStrings);
+	API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalStrings);
 }
 
 // void  (int index, char *newval);
@@ -1640,7 +1640,7 @@ RuntimeScriptValue Sc_Game_GetMouseCursorCount(const RuntimeScriptValue *params,
 
 // const char *()
 RuntimeScriptValue Sc_Game_GetName(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetName);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetName);
 }
 
 // void (const char *newName)
@@ -1695,11 +1695,11 @@ RuntimeScriptValue Sc_Game_SetTextReadingSpeed(const RuntimeScriptValue *params,
 
 // const char* ()
 RuntimeScriptValue Sc_Game_GetTranslationFilename(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetTranslationFilename);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetTranslationFilename);
 }
 
 RuntimeScriptValue Sc_Game_GetSpeechVoxFilename(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetSpeechVoxFilename);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetSpeechVoxFilename);
 }
 
 // int ()
diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index ff475b1fec3..5c92f650f81 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -691,7 +691,7 @@ RuntimeScriptValue Sc_sc_GetTime(const RuntimeScriptValue *params, int32_t param
 
 // char * (const char *text)
 RuntimeScriptValue Sc_get_translation(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), get_translation, const char);
+	API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), get_translation, const char);
 }
 
 // int  (char* buffer)
diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp
index 7b2a22f44be..e6e58b8f1a1 100644
--- a/engines/ags/engine/ac/hotspot.cpp
+++ b/engines/ags/engine/ac/hotspot.cpp
@@ -187,7 +187,7 @@ RuntimeScriptValue Sc_Hotspot_GetPropertyText(void *self, const RuntimeScriptVal
 
 // const char* (ScriptHotspot *hss, const char *property)
 RuntimeScriptValue Sc_Hotspot_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetTextProperty, const char);
+	API_OBJCALL_OBJ_POBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetTextProperty, const char);
 }
 
 RuntimeScriptValue Sc_Hotspot_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
@@ -228,7 +228,7 @@ RuntimeScriptValue Sc_Hotspot_GetScriptName(void *self, const RuntimeScriptValue
 
 // const char* (ScriptHotspot *hss)
 RuntimeScriptValue Sc_Hotspot_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetName_New);
+	API_OBJCALL_OBJ(ScriptHotspot, const char, _GP(myScriptStringImpl), Hotspot_GetName_New);
 }
 
 RuntimeScriptValue Sc_Hotspot_SetName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/inventory_item.cpp b/engines/ags/engine/ac/inventory_item.cpp
index bb1e0989bee..d900990e88f 100644
--- a/engines/ags/engine/ac/inventory_item.cpp
+++ b/engines/ags/engine/ac/inventory_item.cpp
@@ -162,7 +162,7 @@ RuntimeScriptValue Sc_InventoryItem_GetPropertyText(void *self, const RuntimeScr
 
 // const char* (ScriptInvItem *scii, const char *property)
 RuntimeScriptValue Sc_InventoryItem_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetTextProperty, const char);
+	API_OBJCALL_OBJ_POBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetTextProperty, const char);
 }
 
 RuntimeScriptValue Sc_InventoryItem_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
@@ -214,7 +214,7 @@ RuntimeScriptValue Sc_InventoryItem_GetScriptName(void *self, const RuntimeScrip
 
 // const char* (ScriptInvItem *invitem)
 RuntimeScriptValue Sc_InventoryItem_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetName_New);
+	API_OBJCALL_OBJ(ScriptInvItem, const char, _GP(myScriptStringImpl), InventoryItem_GetName_New);
 }
 
 void RegisterInventoryItemAPI() {
diff --git a/engines/ags/engine/ac/label.cpp b/engines/ags/engine/ac/label.cpp
index 4835f55e03a..7e5d97c0188 100644
--- a/engines/ags/engine/ac/label.cpp
+++ b/engines/ags/engine/ac/label.cpp
@@ -133,7 +133,7 @@ RuntimeScriptValue Sc_Label_SetFont(void *self, const RuntimeScriptValue *params
 
 // const char* (GUILabel *labl)
 RuntimeScriptValue Sc_Label_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(GUILabel, const char, _GP(myScriptStringImpl), Label_GetText_New);
+	API_OBJCALL_OBJ(GUILabel, const char, _GP(myScriptStringImpl), Label_GetText_New);
 }
 
 // int (GUILabel *labl)
diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp
index c17c5aeaec1..c8a5315864d 100644
--- a/engines/ags/engine/ac/listbox.cpp
+++ b/engines/ags/engine/ac/listbox.cpp
@@ -511,7 +511,7 @@ RuntimeScriptValue Sc_ListBox_GetItemCount(void *self, const RuntimeScriptValue
 
 // const char* (GUIListBox *listbox, int index)
 RuntimeScriptValue Sc_ListBox_GetItems(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT(GUIListBox, const char, _GP(myScriptStringImpl), ListBox_GetItems);
+	API_OBJCALL_OBJ_PINT(GUIListBox, const char, _GP(myScriptStringImpl), ListBox_GetItems);
 }
 
 // int (GUIListBox *listbox)
diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp
index 6c0f4904e7c..f25890f19ea 100644
--- a/engines/ags/engine/ac/object.cpp
+++ b/engines/ags/engine/ac/object.cpp
@@ -765,7 +765,7 @@ RuntimeScriptValue Sc_Object_GetPropertyText(void *self, const RuntimeScriptValu
 
 //const char* (ScriptObject *objj, const char *property)
 RuntimeScriptValue Sc_Object_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(ScriptObject, const char, _GP(myScriptStringImpl), Object_GetTextProperty, const char);
+	API_OBJCALL_OBJ_POBJ(ScriptObject, const char, _GP(myScriptStringImpl), Object_GetTextProperty, const char);
 }
 
 RuntimeScriptValue Sc_Object_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
@@ -983,7 +983,7 @@ RuntimeScriptValue Sc_Object_GetMoving(void *self, const RuntimeScriptValue *par
 
 // const char* (ScriptObject *objj)
 RuntimeScriptValue Sc_Object_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(ScriptObject, const char, _GP(myScriptStringImpl), Object_GetName_New);
+	API_OBJCALL_OBJ(ScriptObject, const char, _GP(myScriptStringImpl), Object_GetName_New);
 }
 
 RuntimeScriptValue Sc_Object_SetName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/parser.cpp b/engines/ags/engine/ac/parser.cpp
index 6699fc8a05f..27fea9a1173 100644
--- a/engines/ags/engine/ac/parser.cpp
+++ b/engines/ags/engine/ac/parser.cpp
@@ -324,7 +324,7 @@ RuntimeScriptValue Sc_ParseText(const RuntimeScriptValue *params, int32_t param_
 
 // const char* ()
 RuntimeScriptValue Sc_Parser_SaidUnknownWord(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), Parser_SaidUnknownWord);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Parser_SaidUnknownWord);
 }
 
 // int  (char*checkwords)
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 06148f10de4..413ead9eb39 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1074,7 +1074,7 @@ RuntimeScriptValue Sc_Room_GetProperty(const RuntimeScriptValue *params, int32_t
 
 // const char* (const char *property)
 RuntimeScriptValue Sc_Room_GetTextProperty(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Room_GetTextProperty, const char);
+	API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Room_GetTextProperty, const char);
 }
 
 RuntimeScriptValue Sc_Room_SetProperty(const RuntimeScriptValue *params, int32_t param_count) {
@@ -1108,7 +1108,7 @@ RuntimeScriptValue Sc_Room_GetLeftEdge(const RuntimeScriptValue *params, int32_t
 
 // const char* (int index)
 RuntimeScriptValue Sc_Room_GetMessages(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Room_GetMessages);
+	API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Room_GetMessages);
 }
 
 // int ()
diff --git a/engines/ags/engine/ac/script_containers.cpp b/engines/ags/engine/ac/script_containers.cpp
index f6f5da74283..f4aea94fdc5 100644
--- a/engines/ags/engine/ac/script_containers.cpp
+++ b/engines/ags/engine/ac/script_containers.cpp
@@ -143,7 +143,7 @@ RuntimeScriptValue Sc_Dict_Contains(void *self, const RuntimeScriptValue *params
 }
 
 RuntimeScriptValue Sc_Dict_Get(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(ScriptDictBase, const char, _GP(myScriptStringImpl), Dict_Get, const char);
+	API_OBJCALL_OBJ_POBJ(ScriptDictBase, const char, _GP(myScriptStringImpl), Dict_Get, const char);
 }
 
 RuntimeScriptValue Sc_Dict_Remove(void *self, const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp
index 99d912e8875..fdc630a56f8 100644
--- a/engines/ags/engine/ac/string.cpp
+++ b/engines/ags/engine/ac/string.cpp
@@ -345,12 +345,12 @@ RuntimeScriptValue Sc_String_IsNullOrEmpty(const RuntimeScriptValue *params, int
 
 // const char* (const char *thisString, const char *extrabit)
 RuntimeScriptValue Sc_String_Append(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ(const char, const char, _GP(myScriptStringImpl), String_Append, const char);
+	API_OBJCALL_OBJ_POBJ(const char, const char, _GP(myScriptStringImpl), String_Append, const char);
 }
 
 // const char* (const char *thisString, char extraOne)
 RuntimeScriptValue Sc_String_AppendChar(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_AppendChar);
+	API_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_AppendChar);
 }
 
 // int (const char *thisString, const char *otherString, bool caseSensitive)
@@ -365,7 +365,7 @@ RuntimeScriptValue Sc_StrContains(void *self, const RuntimeScriptValue *params,
 
 // const char* (const char *srcString)
 RuntimeScriptValue Sc_String_Copy(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_Copy);
+	API_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_Copy);
 }
 
 // int (const char *thisString, const char *checkForString, bool caseSensitive)
@@ -381,17 +381,17 @@ RuntimeScriptValue Sc_String_Format(const RuntimeScriptValue *params, int32_t pa
 
 // const char* (const char *thisString)
 RuntimeScriptValue Sc_String_LowerCase(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_LowerCase);
+	API_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_LowerCase);
 }
 
 // const char* (const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive)
 RuntimeScriptValue Sc_String_Replace(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_POBJ2_PBOOL(const char, const char, _GP(myScriptStringImpl), String_Replace, const char, const char);
+	API_OBJCALL_OBJ_POBJ2_PBOOL(const char, const char, _GP(myScriptStringImpl), String_Replace, const char, const char);
 }
 
 // const char* (const char *thisString, int index, char newChar)
 RuntimeScriptValue Sc_String_ReplaceCharAt(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_ReplaceCharAt);
+	API_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_ReplaceCharAt);
 }
 
 // int (const char *thisString, const char *checkForString, bool caseSensitive)
@@ -401,17 +401,17 @@ RuntimeScriptValue Sc_String_StartsWith(void *self, const RuntimeScriptValue *pa
 
 // const char* (const char *thisString, int index, int length)
 RuntimeScriptValue Sc_String_Substring(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_Substring);
+	API_OBJCALL_OBJ_PINT2(const char, const char, _GP(myScriptStringImpl), String_Substring);
 }
 
 // const char* (const char *thisString, int length)
 RuntimeScriptValue Sc_String_Truncate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_Truncate);
+	API_OBJCALL_OBJ_PINT(const char, const char, _GP(myScriptStringImpl), String_Truncate);
 }
 
 // const char* (const char *thisString)
 RuntimeScriptValue Sc_String_UpperCase(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_UpperCase);
+	API_OBJCALL_OBJ(const char, const char, _GP(myScriptStringImpl), String_UpperCase);
 }
 
 // FLOAT_RETURN_TYPE (const char *theString);
diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index f8acbe5eedb..536393a5bd2 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -291,7 +291,7 @@ RuntimeScriptValue Sc_System_GetSupportsGammaControl(const RuntimeScriptValue *p
 
 // const char *()
 RuntimeScriptValue Sc_System_GetVersion(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), System_GetVersion);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), System_GetVersion);
 }
 
 // int ()
@@ -334,7 +334,7 @@ RuntimeScriptValue Sc_System_SetWindowed(const RuntimeScriptValue *params, int32
 
 // const char *()
 RuntimeScriptValue Sc_System_GetRuntimeInfo(const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_SCALL_OBJ(const char, _GP(myScriptStringImpl), System_GetRuntimeInfo);
+	API_SCALL_OBJ(const char, _GP(myScriptStringImpl), System_GetRuntimeInfo);
 }
 
 RuntimeScriptValue Sc_System_GetRenderAtScreenResolution(const RuntimeScriptValue *params, int32_t param_count) {
diff --git a/engines/ags/engine/ac/textbox.cpp b/engines/ags/engine/ac/textbox.cpp
index 943d869d407..69025000e6f 100644
--- a/engines/ags/engine/ac/textbox.cpp
+++ b/engines/ags/engine/ac/textbox.cpp
@@ -121,7 +121,7 @@ RuntimeScriptValue Sc_TextBox_SetShowBorder(void *self, const RuntimeScriptValue
 
 // const char* (GUITextBox *texbox)
 RuntimeScriptValue Sc_TextBox_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
-	API_CONST_OBJCALL_OBJ(GUITextBox, const char, _GP(myScriptStringImpl), TextBox_GetText_New);
+	API_OBJCALL_OBJ(GUITextBox, const char, _GP(myScriptStringImpl), TextBox_GetText_New);
 }
 
 // int (GUITextBox *guit)
diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h
index c1ec7202322..2fe5319df51 100644
--- a/engines/ags/engine/script/script_api.h
+++ b/engines/ags/engine/script/script_api.h
@@ -306,43 +306,43 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 
 #define API_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \
 	(void)params; (void)param_count; \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(), &RET_MGR)
-
-#define API_CONST_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION()), &RET_MGR)
-
+/*
+	#define API_CONST_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION()), &RET_MGR)
+*/
 #define API_SCALL_OBJ_PINT(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue)), &RET_MGR)
+/*
 #define API_CONST_SCALL_OBJ_PINT(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue)), &RET_MGR)
-
+*/
 #define API_SCALL_OBJ_POBJ_PINT_PBOOL(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 3); \
 	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].GetAsBool()), &RET_MGR)
 
 #define API_SCALL_OBJ_PINT2(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue)), &RET_MGR)
+/*
 #define API_CONST_SCALL_OBJ_PINT2(RET_CLASS, RET_MGR, FUNCTION) \
 	ASSERT_PARAM_COUNT(FUNCTION, 2); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue)), &RET_MGR)
-
+*/
 #define API_SCALL_OBJ_PINT3_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 4); \
 	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr), &RET_MGR)
 
 #define API_SCALL_OBJ_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr)), &RET_MGR)
+/*
 #define API_CONST_SCALL_OBJ_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \
 	ASSERT_PARAM_COUNT(FUNCTION, 1); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr)), &RET_MGR)
-
+*/
 #define API_SCALL_OBJAUTO(RET_CLASS, FUNCTION) \
 	(void)params; (void)param_count; \
 	RET_CLASS* ret_obj = FUNCTION(); \
@@ -583,48 +583,48 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
 
 #define API_OBJCALL_OBJ_POBJ2_PBOOL(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
-	return RuntimeScriptValue().SetScriptObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool()), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool())), &RET_MGR)
+/*
 #define API_CONST_OBJCALL_OBJ_POBJ2_PBOOL(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool())), &RET_MGR)
-
+*/
 #define API_OBJCALL_OBJ(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_SELF(METHOD); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self)), &RET_MGR)
+/*
 #define API_CONST_OBJCALL_OBJ(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_SELF(METHOD); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self)), &RET_MGR)
-
+*/
 #define API_OBJCALL_OBJ_PINT(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue)), &RET_MGR)
+/*
 #define API_CONST_OBJCALL_OBJ_PINT(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue)), &RET_MGR)
-
+*/
 #define API_OBJCALL_OBJ_PINT2(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue)), &RET_MGR)
+/*
 #define API_CONST_OBJCALL_OBJ_PINT2(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue)), &RET_MGR)
-
+*/
 #define API_OBJCALL_OBJ_PINT3(CLASS, RET_CLASS, RET_MGR, METHOD) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \
 	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue), &RET_MGR)
 
 #define API_OBJCALL_OBJ_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
-	return RuntimeScriptValue().SetScriptObject((void*)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr), &RET_MGR)
-
+	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)), &RET_MGR)
+/*
 #define API_CONST_OBJCALL_OBJ_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \
 	ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
 	return RuntimeScriptValue().SetScriptObject(const_cast<void *>((const void *)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)), &RET_MGR)
-
+*/
 #define API_OBJCALL_OBJAUTO(CLASS, RET_CLASS, METHOD) \
 	ASSERT_SELF(METHOD); \
 	RET_CLASS* ret_obj = METHOD((CLASS*)self); \


Commit: 08a2d9cc9e870ea89a7c09bf7db024d487b85bf8
    https://github.com/scummvm/scummvm/commit/08a2d9cc9e870ea89a7c09bf7db024d487b85bf8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add casts in cc_instance

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index cb3b03eed99..847e2b83155 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -323,7 +323,7 @@ void ccInstance::AbortAndDestroy() {
 // ASSERT_STACK_SPACE_AVAILABLE tests that we do not exceed stack limit
 #define ASSERT_STACK_SPACE_AVAILABLE(N_VALS, N_BYTES) \
 	if ((registers[SREG_SP].RValue + N_VALS - &stack[0]) >= CC_STACK_SIZE || \
-		(stackdata_ptr + N_BYTES - stackdata) >= CC_STACK_DATA_SIZE) \
+		(stackdata_ptr + N_BYTES - stackdata) >= (uint32_t)CC_STACK_DATA_SIZE) \
 	{ \
 		cc_error("stack overflow, attempted to grow from %d by %d bytes", (stackdata_ptr - stackdata), N_BYTES); \
 		return -1; \
@@ -1538,7 +1538,7 @@ void ccInstance::DumpInstruction(const ScriptOperation &op) const {
 				debugN(" %f", arg.FValue);
 				break;
 			case kScValStringLiteral:
-				debugN(" \"%s\"", arg.Ptr);
+				debugN(" \"%s\"", (char *)arg.Ptr);
 				break;
 			case kScValStackPtr:
 			case kScValGlobalVar:


Commit: 1bce308ff00956f48c777b8dcce72534f597654d
    https://github.com/scummvm/scummvm/commit/1bce308ff00956f48c777b8dcce72534f597654d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add casts in ags_plugin

Changed paths:
    engines/ags/plugins/ags_plugin.cpp


diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index 747fa35bb67..f83fe03ef74 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -830,7 +830,7 @@ int pl_run_plugin_debug_hooks(const char *scriptfile, int linenum) {
 }
 
 bool pl_query_next_plugin_for_event(int event, int &pl_index, String &pl_name) {
-	for (int i = pl_index; i < _GP(plugins).size(); ++i) {
+	for (int i = pl_index; i < (int)_GP(plugins).size(); ++i) {
 		if (_GP(plugins)[i].wantHook & event) {
 			pl_index = i;
 			pl_name = _GP(plugins)[i].filename;
@@ -841,7 +841,7 @@ bool pl_query_next_plugin_for_event(int event, int &pl_index, String &pl_name) {
 }
 
 int pl_run_plugin_hook_by_index(int pl_index, int event, int data) {
-	if (pl_index < 0 || pl_index >= _GP(plugins).size())
+	if (pl_index < 0 || pl_index >= (int)_GP(plugins).size())
 		return 0;
 	auto &plugin = _GP(plugins)[pl_index];
 	if (plugin.wantHook & event) {


Commit: 0da3289047a674eb4554e9303a93ecd5e2f5d22f
    https://github.com/scummvm/scummvm/commit/0da3289047a674eb4554e9303a93ecd5e2f5d22f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add casts in cc_character

Changed paths:
    engines/ags/engine/ac/dynobj/cc_character.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp
index c641ec39a09..cb9ca4fb2f4 100644
--- a/engines/ags/engine/ac/dynobj/cc_character.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_character.cpp
@@ -80,7 +80,7 @@ int16_t CCCharacter::ReadInt16(void *address, intptr_t offset) {
 
 	// Handle inventory fields
 	const int invoffset = 112;
-	if (offset >= invoffset && offset < (invoffset + MAX_INV * sizeof(short))) {
+	if (offset >= invoffset && offset < (uint)(invoffset + MAX_INV * sizeof(short))) {
 		return ci->inv[(offset - invoffset) / sizeof(short)];
 	}
 
@@ -156,7 +156,7 @@ void CCCharacter::WriteInt16(void *address, intptr_t offset, int16_t val) {
 	// and actual inventory to diverge since 2.70. Force an update of the displayed
 	// inventory for older games that rely on this behaviour.
 	const int invoffset = 112;
-	if (offset >= invoffset && offset < (invoffset + MAX_INV * sizeof(short))) {
+	if (offset >= invoffset && offset < (uint)(invoffset + MAX_INV * sizeof(short))) {
 		ci->inv[(offset - invoffset) / sizeof(short)] = val;
 		update_invorder();
 		return;


Commit: eac87601c4278bc37f6d6ece3551134bc303267e
    https://github.com/scummvm/scummvm/commit/eac87601c4278bc37f6d6ece3551134bc303267e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fix couple of GUI functions that may fail when restoring a save

+ add workaround for Kathy Rain

>From upstream b2859f93136cecc800e3b5cf30b09ad8f91fd0e2

Changed paths:
    engines/ags/shared/gui/gui_main.cpp


diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 91274099302..875c0c227aa 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -37,6 +37,8 @@
 #include "ags/shared/util/string_compat.h"
 #include "ags/globals.h"
 
+#include "common/config-manager.h"
+
 namespace AGS3 {
 
 extern void wouttext_outline(Shared::Bitmap *ds, int xxp, int yyp, int usingfont, color_t text_color, const char *texx);
@@ -188,7 +190,7 @@ void GUIMain::NotifyControlState(int objid, bool mark_changed) {
 	// Update cursor-over-control state, if necessary
 	const int overctrl = MouseOverCtrl;
 	if (!_polling &&
-		(objid >= 0) && (objid == overctrl) &&
+		(objid >= 0) && (objid == overctrl) && ((size_t)objid < _controls.size()) &&
 		(!_controls[overctrl]->IsClickable() ||
 		 !_controls[overctrl]->IsVisible() ||
 		 !_controls[overctrl]->IsEnabled())) {
@@ -203,7 +205,7 @@ void GUIMain::ClearChanged() {
 }
 
 void GUIMain::ResetOverControl() {
-	if (MouseOverCtrl >= 0)
+	if ((MouseOverCtrl >= 0) && ((size_t)MouseOverCtrl < _controls.size()))
 		_controls[MouseOverCtrl]->OnMouseLeave();
 	// Force it to re-check for which control is under the mouse
 	MouseWasAt.X = -1;
@@ -411,7 +413,10 @@ void GUIMain::ResortZOrder() {
 void GUIMain::SetClickable(bool on) {
 	if (on != ((_flags & kGUIMain_Clickable) != 0)) {
 		_flags = (_flags & ~kGUIMain_Clickable) | kGUIMain_Clickable * on;
-		ResetOverControl(); // clear the cursor-over-control
+
+		//  WORKAROUND: Don't reset the GUI in Kathy Rain
+		if (ConfMan.get("gameid") != "kathyrain")
+			ResetOverControl(); // clear the cursor-over-control
 	}
 }
 


Commit: 3078b207d33d5e551811b3f26879f45016d6fe33
    https://github.com/scummvm/scummvm/commit/3078b207d33d5e551811b3f26879f45016d6fe33
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: refactor around add_dynamic_sprite()

* separate variant of add_dynamic_sprite() that searches for the free slot itself,
not requiring user to do this;
* the previously existing variant is used for: replacing modified dynamic sprite
and restoring sprites from save.
* add_dynamic_sprite() accepts unique_ptr<Bitmap>, explicitly saying that the
ownership will be taken by the spriteset.
* sprite info and SPF_* flags are set inside spriteset, instead of hacking them in
add_dynamic_sprite().
* simplified LoadSaveSlotScreenshot()

>From upstream 0913f49e4212fea3a48539251ffc1ab92518a231
and aca47b9f466fe41d73d99a15b9f0ae41e65c6926
+ minor changes

Changed paths:
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/dynamic_sprite.h
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/global_dynamic_sprite.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/plugins/ags_plugin.cpp
    engines/ags/shared/ac/game_struct_defines.h
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 3ac1c7aa412..8fb3ee7e0ed 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -106,13 +106,12 @@ void DynamicSprite_Resize(ScriptDynamicSprite *sds, int width, int height) {
 
 	// resize the sprite to the requested size
 	Bitmap *sprite = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
-	newPic->StretchBlt(sprite,
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth()));
+	new_pic->StretchBlt(sprite,
 					   RectWH(0, 0, _GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height),
 					   RectWH(0, 0, width, height));
 
-	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
@@ -124,13 +123,12 @@ void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction) {
 
 	// resize the sprite to the requested size
 	Bitmap *sprite = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(sprite->GetWidth(), sprite->GetHeight(), sprite->GetColorDepth());
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(sprite->GetWidth(), sprite->GetHeight(), sprite->GetColorDepth()));
 
 	// AGS script FlipDirection corresponds to internal GraphicFlip
-	newPic->FlipBlt(sprite, 0, 0, static_cast<GraphicFlip>(direction));
+	new_pic->FlipBlt(sprite, 0, 0, static_cast<GraphicFlip>(direction));
 
-	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
@@ -172,12 +170,12 @@ void DynamicSprite_ChangeCanvasSize(ScriptDynamicSprite *sds, int width, int hei
 	data_to_game_coords(&width, &height);
 
 	Bitmap *sprite = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth());
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth()));
 	// blit it into the enlarged image
-	newPic->Blit(sprite, 0, 0, x, y, sprite->GetWidth(), sprite->GetHeight());
+	new_pic->Blit(sprite, 0, 0, x, y, sprite->GetWidth(), sprite->GetHeight());
 
 	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
@@ -194,12 +192,12 @@ void DynamicSprite_Crop(ScriptDynamicSprite *sds, int x1, int y1, int width, int
 		quit("!DynamicSprite.Crop: requested to crop an area larger than the source");
 
 	Bitmap *sprite = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth()));
 	// blit it cropped
-	newPic->Blit(sprite, x1, y1, 0, 0, newPic->GetWidth(), newPic->GetHeight());
+	new_pic->Blit(sprite, x1, y1, 0, 0, new_pic->GetWidth(), new_pic->GetHeight());
 
 	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
@@ -232,26 +230,25 @@ void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int he
 
 	// resize the sprite to the requested size
 	Bitmap *sprite = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth());
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateTransparentBitmap(width, height, sprite->GetColorDepth()));
 
 	// rotate the sprite about its centre
 	// (+ width%2 fixes one pixel offset problem)
-	newPic->RotateBlt(sprite, width / 2 + width % 2, height / 2,
+	new_pic->RotateBlt(sprite, width / 2 + width % 2, height / 2,
 					  sprite->GetWidth() / 2, sprite->GetHeight() / 2, itofix(angle));
 
 	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
 void DynamicSprite_Tint(ScriptDynamicSprite *sds, int red, int green, int blue, int saturation, int luminance) {
 	Bitmap *source = _GP(spriteset)[sds->slot];
-	Bitmap *newPic = BitmapHelper::CreateBitmap(source->GetWidth(), source->GetHeight(), source->GetColorDepth());
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(source->GetWidth(), source->GetHeight(), source->GetColorDepth()));
 
-	tint_image(newPic, source, red, green, blue, saturation, (luminance * 25) / 10);
+	tint_image(new_pic.get(), source, red, green, blue, saturation, (luminance * 25) / 10);
 
-	// replace the bitmap in the sprite set
-	add_dynamic_sprite(sds->slot, newPic, (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
+	add_dynamic_sprite(sds->slot, std::move(new_pic), (_GP(game).SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0);
 	game_sprite_updated(sds->slot);
 }
 
@@ -291,8 +288,7 @@ ScriptDynamicSprite *DynamicSprite_CreateFromScreenShot(int width, int height) {
 
 	// TODO: refactor and merge with create_savegame_screenshot()
 
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
+	if (!_GP(spriteset).HasFreeSlots())
 		return nullptr;
 
 	// NOTE: be aware that by the historical logic AGS makes a screenshot
@@ -308,39 +304,33 @@ ScriptDynamicSprite *DynamicSprite_CreateFromScreenShot(int width, int height) {
 	else
 		height = data_to_game_coord(height);
 
-	Bitmap *newPic = CopyScreenIntoBitmap(width, height, &viewport);
+	std::unique_ptr<Bitmap> new_pic(CopyScreenIntoBitmap(width, height, &viewport));
 
 	// replace the bitmap in the sprite set
-	add_dynamic_sprite(gotSlot, newPic);
-	ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot);
-	return new_spr;
+	int new_slot = add_dynamic_sprite(std::move(new_pic));
+	return new ScriptDynamicSprite(new_slot);
 }
 
 ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite(int slot, int preserveAlphaChannel) {
 
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
+	if (!_GP(spriteset).HasFreeSlots())
 		return nullptr;
 
 	if (!_GP(spriteset).DoesSpriteExist(slot))
 		quitprintf("DynamicSprite.CreateFromExistingSprite: sprite %d does not exist", slot);
 
 	// create a new sprite as a copy of the existing one
-	Bitmap *newPic = BitmapHelper::CreateBitmapCopy(_GP(spriteset)[slot]);
-	if (newPic == nullptr)
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmapCopy(_GP(spriteset)[slot]));
+	if (!new_pic)
 		return nullptr;
 
 	bool hasAlpha = (preserveAlphaChannel) && ((_GP(game).SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) != 0);
-
-	// replace the bitmap in the sprite set
-	add_dynamic_sprite(gotSlot, newPic, hasAlpha);
-	ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot);
-	return new_spr;
+	int new_slot = add_dynamic_sprite(std::move(new_pic), hasAlpha);
+	return new ScriptDynamicSprite(new_slot);
 }
 
 ScriptDynamicSprite *DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface *sds, int x, int y, int width, int height) {
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
+	if (!_GP(spriteset).HasFreeSlots())
 		return nullptr;
 
 	if (width <= 0 || height <= 0) {
@@ -357,28 +347,19 @@ ScriptDynamicSprite *DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface
 	if ((x < 0) || (y < 0) || (x + width > ds->GetWidth()) || (y + height > ds->GetHeight()))
 		quit("!DynamicSprite.CreateFromDrawingSurface: requested area is outside the surface");
 
-	int colDepth = ds->GetColorDepth();
-
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, colDepth);
-	if (newPic == nullptr) {
-		sds->FinishedDrawingReadOnly();
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(width, height, ds->GetColorDepth()));
+	if (!new_pic)
 		return nullptr;
-	}
 
-	newPic->Blit(ds, x, y, 0, 0, width, height);
+	new_pic->Blit(ds, x, y, 0, 0, width, height);
 
 	sds->FinishedDrawingReadOnly();
 
-	add_dynamic_sprite(gotSlot, newPic, (sds->hasAlphaChannel != 0));
-	ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot);
-	return new_spr;
+	int new_slot = add_dynamic_sprite(std::move(new_pic), (sds->hasAlphaChannel != 0));
+	return new ScriptDynamicSprite(new_slot);
 }
 
 ScriptDynamicSprite *DynamicSprite_Create(int width, int height, int alphaChannel) {
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
-		return nullptr;
-
 	if (width <= 0 || height <= 0) {
 		debug_script_warn("WARNING: DynamicSprite.Create: invalid size %d x %d, will adjust", width, height);
 		width = MAX(1, width);
@@ -387,17 +368,19 @@ ScriptDynamicSprite *DynamicSprite_Create(int width, int height, int alphaChanne
 
 	data_to_game_coords(&width, &height);
 
-	Bitmap *newPic = CreateCompatBitmap(width, height);
-	if (newPic == nullptr)
+	if (!_GP(spriteset).HasFreeSlots())
 		return nullptr;
 
-	newPic->ClearTransparent();
+	std::unique_ptr<Bitmap> new_pic(CreateCompatBitmap(width, height));
+	if (!new_pic)
+		return nullptr;
+
+	new_pic->ClearTransparent();
 	if ((alphaChannel) && (_GP(game).GetColorDepth() < 32))
 		alphaChannel = false;
 
-	add_dynamic_sprite(gotSlot, newPic, alphaChannel != 0);
-	ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot);
-	return new_spr;
+	int new_slot = add_dynamic_sprite(std::move(new_pic), alphaChannel != 0);
+	return new ScriptDynamicSprite(new_slot);
 }
 
 ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot) {
@@ -405,10 +388,6 @@ ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot) {
 }
 
 ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height) {
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
-		return nullptr;
-
 	if (frame == SCR_NO_VALUE) {
 		frame = _GP(play).bg_frame;
 	} else if ((frame < 0) || ((size_t)frame >= _GP(thisroom).BgFrameCount))
@@ -435,38 +414,40 @@ ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y
 	data_to_game_coords(&x1, &y1);
 	data_to_game_coords(&width, &height);
 
+	if (!_GP(spriteset).HasFreeSlots())
+		return nullptr;
+
 	// create a new sprite as a copy of the existing one
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, _GP(thisroom).BgFrames[frame].Graphic->GetColorDepth());
-	if (newPic == nullptr)
+	std::unique_ptr<Bitmap> new_pic(BitmapHelper::CreateBitmap(width, height, _GP(thisroom).BgFrames[frame].Graphic->GetColorDepth()));
+	if (!new_pic)
 		return nullptr;
 
-	newPic->Blit(_GP(thisroom).BgFrames[frame].Graphic.get(), x1, y1, 0, 0, width, height);
+	new_pic->Blit(_GP(thisroom).BgFrames[frame].Graphic.get(), x1, y1, 0, 0, width, height);
 
-	// replace the bitmap in the sprite set
-	add_dynamic_sprite(gotSlot, newPic);
-	ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot);
-	return new_spr;
+	int new_slot = add_dynamic_sprite(std::move(new_pic));
+	return new ScriptDynamicSprite(new_slot);
 }
 
 //=============================================================================
 
-void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha, uint32_t extra_flags) {
-
-	_GP(spriteset).SetSprite(gotSlot, redin);
+int add_dynamic_sprite(std::unique_ptr<Bitmap> image, bool has_alpha, uint32_t extra_flags) {
+	int slot = _GP(spriteset).GetFreeIndex();
+	if (slot <= 0)
+		return 0;
 
-	_GP(game).SpriteInfos[gotSlot].Flags = SPF_DYNAMICALLOC;
+	add_dynamic_sprite(slot, std::move(image), has_alpha, extra_flags);
+	return slot;
+}
 
-	if (redin->GetColorDepth() > 8)
-		_GP(game).SpriteInfos[gotSlot].Flags |= SPF_HICOLOR;
-	if (redin->GetColorDepth() > 16)
-		_GP(game).SpriteInfos[gotSlot].Flags |= SPF_TRUECOLOR;
-	if (hasAlpha)
-		_GP(game).SpriteInfos[gotSlot].Flags |= SPF_ALPHACHANNEL;
+int add_dynamic_sprite(int slot, std::unique_ptr<Bitmap> image, bool has_alpha, uint32_t extra_flags) {
+	assert(slot > 0 && !_GP(spriteset).IsAssetSprite(slot));
+	if (slot <= 0 || _GP(spriteset).IsAssetSprite(slot))
+		return 0; // invalid slot, or reserved for the static sprite
 
-	_GP(game).SpriteInfos[gotSlot].Flags |= extra_flags;
+	uint32_t flags = SPF_DYNAMICALLOC | (SPF_ALPHACHANNEL * has_alpha) | extra_flags;
 
-	_GP(game).SpriteInfos[gotSlot].Width = redin->GetWidth();
-	_GP(game).SpriteInfos[gotSlot].Height = redin->GetHeight();
+	_GP(spriteset).SetSprite(slot, std::move(image), flags);
+	return slot;
 }
 
 void free_dynamic_sprite(int slot) {
diff --git a/engines/ags/engine/ac/dynamic_sprite.h b/engines/ags/engine/ac/dynamic_sprite.h
index ad6c51d1773..ff7084c2db2 100644
--- a/engines/ags/engine/ac/dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynamic_sprite.h
@@ -50,9 +50,17 @@ ScriptDynamicSprite *DynamicSprite_Create(int width, int height, int alphaChanne
 ScriptDynamicSprite *DynamicSprite_CreateFromExistingSprite_Old(int slot);
 ScriptDynamicSprite *DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height);
 
-
-void    add_dynamic_sprite(int gotSlot, AGS::Shared::Bitmap *redin, bool hasAlpha = false, uint32_t extra_flags = 0u);
-void    free_dynamic_sprite(int gotSlot);
+// Registers a new dynamic sprite, and returns a slot number;
+// returns 0 if no free slot could be found or allocated.
+// Updates game.SpriteInfos[].
+int     add_dynamic_sprite(std::unique_ptr<AGS::Shared::Bitmap> image, bool has_alpha = false, uint32_t extra_flags = 0u);
+// Registers a new dynamic sprite in the given slot number,
+// previous bitmap on this slot (if any) will be deleted.
+// Returns same slot number on success, or 0 if there was an error.
+// Updates game.SpriteInfos[].
+int     add_dynamic_sprite(int slot, std::unique_ptr<AGS::Shared::Bitmap> image, bool hasAlpha = false, uint32_t extra_flags = 0u);
+// Disposes a dynamic sprite, and frees the slot
+void    free_dynamic_sprite(int slot);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index e081994bc63..b955707dca7 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -913,25 +913,15 @@ bool read_savedgame_description(const String &savedgame, String &description) {
 	return true;
 }
 
-bool read_savedgame_screenshot(const String &savedgame, int &want_shot) {
-	want_shot = 0;
-
+std::unique_ptr<Shared::Bitmap> read_savedgame_screenshot(const String &savedgame) {
 	SavegameDescription desc;
 	HSaveError err = OpenSavegame(savedgame, desc, kSvgDesc_UserImage);
 	if (!err) {
 		Debug::Printf(kDbgMsg_Error, "Unable to read save's screenshot.\n%s", err->FullMessage().GetCStr());
-		return false;
-	}
-
-	if (desc.UserImage.get()) {
-		int slot = _GP(spriteset).GetFreeIndex();
-		if (slot > 0) {
-			// add it into the sprite set
-			add_dynamic_sprite(slot, PrepareSpriteForUse(desc.UserImage.release(), false));
-			want_shot = slot;
-		}
+		return {};
 	}
-	return true;
+	desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false));
+	return std::move(desc.UserImage);
 }
 
 
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index 32b8fcb78b4..d43dfae6217 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -173,7 +173,7 @@ void free_do_once_tokens();
 void unload_game();
 void save_game(int slotn, const char *descript);
 bool read_savedgame_description(const Shared::String &savedgame, Shared::String &description);
-bool read_savedgame_screenshot(const Shared::String &savedgame, int &want_shot);
+std::unique_ptr<Shared::Bitmap> read_savedgame_screenshot(const Shared::String &savedgame);
 // Tries to restore saved game and displays an error on failure; if the error occurred
 // too late, when the game data was already overwritten, shuts engine down.
 bool try_restore_save(int slot);
diff --git a/engines/ags/engine/ac/global_dynamic_sprite.cpp b/engines/ags/engine/ac/global_dynamic_sprite.cpp
index 524daf07893..58448299ae4 100644
--- a/engines/ags/engine/ac/global_dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/global_dynamic_sprite.cpp
@@ -35,6 +35,8 @@ using namespace AGS::Shared;
 using namespace AGS::Engine;
 
 int LoadImageFile(const char *filename) {
+	if (!_GP(spriteset).HasFreeSlots())
+		return 0;
 	ResolvedPath rp;
 	if (!ResolveScriptPath(filename, true, rp))
 		return 0;
@@ -54,13 +56,10 @@ int LoadImageFile(const char *filename) {
 	if (!loadedFile)
 		return 0;
 
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
+	std::unique_ptr<Bitmap> image(loadedFile);
+	if (!image)
 		return 0;
-
-	add_dynamic_sprite(gotSlot, PrepareSpriteForUse(loadedFile, false));
-
-	return gotSlot;
+	return add_dynamic_sprite(std::unique_ptr<Bitmap>(PrepareSpriteForUse(image.release(), false)));
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index 5ff44728628..be726781a2f 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -151,30 +151,23 @@ int GetSaveSlotDescription(int slnum, char *desbuf) {
 }
 
 int LoadSaveSlotScreenshot(int slnum, int width, int height) {
-	int gotSlot;
-	data_to_game_coords(&width, &height);
-
-	if (!read_savedgame_screenshot(get_save_game_path(slnum), gotSlot))
+	if (!_GP(spriteset).HasFreeSlots())
 		return 0;
 
-	if (gotSlot == 0)
+	auto screenshot = read_savedgame_screenshot(get_save_game_path(slnum));
+	if (!screenshot)
 		return 0;
 
-	if ((_GP(game).SpriteInfos[gotSlot].Width == width) && (_GP(game).SpriteInfos[gotSlot].Height == height))
-		return gotSlot;
-
 	// resize the sprite to the requested size
-	Bitmap *sprite = _GP(spriteset)[gotSlot];
-	Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, sprite->GetColorDepth());
-	newPic->StretchBlt(sprite,
-					   RectWH(0, 0, _GP(game).SpriteInfos[gotSlot].Width, _GP(game).SpriteInfos[gotSlot].Height),
-					   RectWH(0, 0, width, height));
-
-	// replace the bitmap in the sprite set
-	free_dynamic_sprite(gotSlot);
-	add_dynamic_sprite(gotSlot, newPic);
-
-	return gotSlot;
+	data_to_game_coords(&width, &height);
+	if ((screenshot->GetWidth() != width) || (screenshot->GetHeight() != height)) {
+		std::unique_ptr<Bitmap> temp(BitmapHelper::CreateBitmap(width, height, screenshot->GetColorDepth()));
+		temp->StretchBlt(screenshot.get(),
+						 RectWH(0, 0, screenshot->GetWidth(), screenshot->GetHeight()),
+						 RectWH(0, 0, width, height));
+		screenshot = std::move(temp);
+	}
+	return add_dynamic_sprite(std::move(screenshot));
 }
 
 void FillSaveList(std::vector<SaveListItem> &saves, size_t max_count) {
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 39240fe25b1..d7df3e7f9e8 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -752,7 +752,8 @@ HSaveError ReadDynamicSprites(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size,
 	for (int i = 0; i < spr_count; ++i) {
 		int id = in->ReadInt32();
 		int flags = in->ReadInt32();
-		add_dynamic_sprite(id, read_serialized_bitmap(in), (flags & SPF_ALPHACHANNEL) != 0, flags);
+		std::unique_ptr<Bitmap> image(read_serialized_bitmap(in));
+		add_dynamic_sprite(id, std::move(image), (flags & SPF_ALPHACHANNEL) != 0, flags);
 	}
 	return err;
 }
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 4e6f8097c17..ec792291562 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -112,7 +112,8 @@ static void restore_game_spriteset(Stream *in) {
 	int sprnum = in->ReadInt32();
 	while (sprnum) {
 		unsigned char spriteflag = in->ReadInt8();
-		add_dynamic_sprite(sprnum, read_serialized_bitmap(in));
+		std::unique_ptr<Bitmap> image(read_serialized_bitmap(in));
+		add_dynamic_sprite(sprnum, std::move(image));
 		_GP(game).SpriteInfos[sprnum].Flags = spriteflag;
 		sprnum = in->ReadInt32();
 	}
diff --git a/engines/ags/plugins/ags_plugin.cpp b/engines/ags/plugins/ags_plugin.cpp
index f83fe03ef74..2384db4b472 100644
--- a/engines/ags/plugins/ags_plugin.cpp
+++ b/engines/ags/plugins/ags_plugin.cpp
@@ -568,28 +568,24 @@ int IAGSEngine::GetFontType(int32 fontNum) {
 }
 int IAGSEngine::CreateDynamicSprite(int32 coldepth, int32 width, int32 height) {
 
-	// TODO: why is this implemented right here, should not an existing
-	// script handling implementation be called instead?
-
-	int gotSlot = _GP(spriteset).GetFreeIndex();
-	if (gotSlot <= 0)
-		return 0;
-
 	if ((width < 1) || (height < 1))
 		quit("!IAGSEngine::CreateDynamicSprite: invalid width/height requested by plugin");
 
-	// resize the sprite to the requested size
-	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, coldepth);
-	if (newPic == nullptr)
+	if (!_GP(spriteset).HasFreeSlots())
+		return 0;
+
+	std::unique_ptr<Bitmap> image(BitmapHelper::CreateTransparentBitmap(width, height, coldepth));
+	if (!image)
 		return 0;
 
 	// add it into the sprite set
-	add_dynamic_sprite(gotSlot, newPic);
-	return gotSlot;
+	return add_dynamic_sprite(std::move(image));
 }
+
 void IAGSEngine::DeleteDynamicSprite(int32 slot) {
 	free_dynamic_sprite(slot);
 }
+
 int IAGSEngine::IsSpriteAlphaBlended(int32 slot) {
 	if (_GP(game).SpriteInfos[slot].Flags & SPF_ALPHACHANNEL)
 		return 1;
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 7ac1383af0e..4c93133f792 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -224,9 +224,9 @@ enum GameGuiAlphaRenderingStyle {
 // SERIALIZATION NOTE: serialized as 8-bit in game data and legacy saves
 //                     serialized as 32-bit in new saves (for dynamic sprites only).
 #define SPF_HIRES           0x01  // sized for high native resolution (legacy option)
-#define SPF_HICOLOR         0x02  // is 16-bit
+#define SPF_HICOLOR         0x02  // is 16-bit (UNUSED)
 #define SPF_DYNAMICALLOC    0x04  // created by runtime script
-#define SPF_TRUECOLOR       0x08  // is 32-bit
+#define SPF_TRUECOLOR       0x08  // is 32-bit (UNUSED)
 #define SPF_ALPHACHANNEL    0x10  // has alpha-channel
 #define SPF_VAR_RESOLUTION  0x20  // variable resolution (refer to SPF_HIRES)
 #define SPF_HADALPHACHANNEL 0x80  // the saved sprite on disk has one
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index 289fdf0159f..e23e696b5de 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -92,6 +92,15 @@ void SpriteCache::SetMaxCacheSize(size_t size) {
 	_maxCacheSize = size;
 }
 
+bool SpriteCache::HasFreeSlots() const {
+	return !((_spriteData.size() == SIZE_MAX) || (_spriteData.size() > MAX_SPRITE_INDEX));
+}
+
+bool SpriteCache::IsAssetSprite(sprkey_t index) const {
+	return index >= 0 && (size_t)index < _spriteData.size() && // in the valid range
+		   _spriteData[index].IsAssetSprite();                 // found in the game resources
+}
+
 void SpriteCache::Reset() {
 	_file.Close();
 	_spriteData.clear();
@@ -100,20 +109,23 @@ void SpriteCache::Reset() {
 	_lockedSize = 0;
 }
 
-bool SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite, int flags) {
+bool SpriteCache::SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags) {
 	if (index < 0 || EnlargeTo(index) != index) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: unable to use index %d", index);
 		return false;
 	}
-	if (!sprite || sprite->GetSize().IsNull() || sprite->GetColorDepth() <= 0) {
+	if (!image || image->GetSize().IsNull() || image->GetColorDepth() <= 0) {
 		DisposeSprite(index); // free previous item in this slot anyway
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign an invalid bitmap to index %d", index);
 		return false;
 	}
 
+	const int spf_flags = flags
+		| (SPF_HICOLOR * image->GetColorDepth() > 8)
+		| (SPF_TRUECOLOR * image->GetColorDepth() > 16);
+	_sprInfos[index] = SpriteInfo(image->GetWidth(), image->GetHeight(), spf_flags);
 	// Assign sprite with 0 size, as it will not be included into the cache size
-	_spriteData[index] = SpriteData(sprite, 0, SPRCACHEFLAG_EXTERNAL | SPRCACHEFLAG_LOCKED);
-	_sprInfos[index] = SpriteInfo(sprite->GetWidth(), sprite->GetHeight(), flags);
+	_spriteData[index] = SpriteData(image.release(), 0, SPRCACHEFLAG_EXTERNAL | SPRCACHEFLAG_LOCKED);
 	SprCacheLog("SetSprite: (external) %d", index);
 	return true;
 }
@@ -158,6 +170,12 @@ sprkey_t SpriteCache::EnlargeTo(sprkey_t topmost) {
 }
 
 sprkey_t SpriteCache::GetFreeIndex() {
+	// FIXME: inefficient if large number of sprites were created in game;
+	// use "available ids" stack, see managed pool for an example;
+	// NOTE: this is shared with the Editor, which means we cannot rely on the
+	// number of "static" sprites and search for slots after... this may be
+	// resolved by splitting SpriteCache class further on "cache builder" and
+	// "runtime cache".
 	for (size_t i = MIN_SPRITE_INDEX; i < _spriteData.size(); ++i) {
 		// slot empty
 		if (!DoesSpriteExist(i)) {
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 0377eaf8c8c..88f2165552a 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -130,6 +130,11 @@ public:
 	size_t      GetMaxCacheSize() const;
 	// Returns number of sprite slots in the bank (this includes both actual sprites and free slots)
 	size_t      GetSpriteSlotCount() const;
+	// Tells if the sprite storage still has unoccupied slots to put new sprites in
+	bool		HasFreeSlots() const;
+	// Tells if the given slot is reserved for the asset sprite, that is a "static"
+	// sprite cached from the game assets
+	bool		IsAssetSprite(sprkey_t index) const;
 	// Loads sprite using SpriteFile if such index is known,
 	// frees the space if cache size reaches the limit
 	void        PrecacheSprite(sprkey_t index);
@@ -157,7 +162,7 @@ public:
 	// Assigns new sprite for the given index; this sprite won't be auto disposed.
 	// *Deletes* the previous sprite if one was found at the same index.
 	// "flags" are SPF_* constants that define sprite's behavior in game.
-	bool        SetSprite(sprkey_t index, Bitmap *, int flags = 0);
+	bool        SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags = 0);
 	// Assigns new sprite for the given index, remapping it to sprite 0;
 	// optionally marks it as an asset placeholder.
 	// *Deletes* the previous sprite if one was found at the same index.


Commit: 1d70b396d24c8611aeb800b1f633f5e8b083a375
    https://github.com/scummvm/scummvm/commit/1d70b396d24c8611aeb800b1f633f5e8b083a375
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: store dynamic surfaces in unique_ptrs

>From upstream 885df22361432d1d273e5f1dde4309b2d9ae38e1

Changed paths:
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/drawing_surface.cpp
    engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
    engines/ags/engine/ac/global_drawing_surface.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_internal.h
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 676d5698697..12559690d3f 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -29,6 +29,7 @@
 #include "ags/shared/gfx/allegro_bitmap.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/shared/game/room_struct.h"
+#include "ags/engine/ac/runtime_defines.h"
 
 namespace AGS3 {
 namespace AGS {
diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index 9d2d9a7e5ef..a4cc694c21e 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -77,8 +77,7 @@ void DrawingSurface_Release(ScriptDrawingSurface *sds) {
 		sds->dynamicSpriteNumber = -1;
 	}
 	if (sds->dynamicSurfaceNumber >= 0) {
-		delete _G(dynamicallyCreatedSurfaces)[sds->dynamicSurfaceNumber];
-		_G(dynamicallyCreatedSurfaces)[sds->dynamicSurfaceNumber] = nullptr;
+		_G(dynamicallyCreatedSurfaces)[sds->dynamicSurfaceNumber].reset();
 		sds->dynamicSurfaceNumber = -1;
 	}
 	sds->modified = 0;
@@ -106,7 +105,7 @@ ScriptDrawingSurface *DrawingSurface_CreateCopy(ScriptDrawingSurface *sds) {
 
 	for (int i = 0; i < MAX_DYNAMIC_SURFACES; i++) {
 		if (_G(dynamicallyCreatedSurfaces)[i] == nullptr) {
-			_G(dynamicallyCreatedSurfaces)[i] = BitmapHelper::CreateBitmapCopy(sourceBitmap);
+			_G(dynamicallyCreatedSurfaces)[i].reset(BitmapHelper::CreateBitmapCopy(sourceBitmap));
 			ScriptDrawingSurface *newSurface = new ScriptDrawingSurface();
 			newSurface->dynamicSurfaceNumber = i;
 			newSurface->hasAlphaChannel = sds->hasAlphaChannel;
diff --git a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
index d25f3944a93..016bb26ce8b 100644
--- a/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
+++ b/engines/ags/engine/ac/dynobj/script_drawing_surface.cpp
@@ -24,6 +24,7 @@
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/shared/ac/common.h"
+#include "ags/engine/ac/draw.h"
 #include "ags/engine/ac/drawing_surface.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/ac/game_setup_struct.h"
@@ -42,7 +43,7 @@ Bitmap *ScriptDrawingSurface::GetBitmapSurface() {
 	else if (dynamicSpriteNumber >= 0)
 		return _GP(spriteset)[dynamicSpriteNumber];
 	else if (dynamicSurfaceNumber >= 0)
-		return _G(dynamicallyCreatedSurfaces)[dynamicSurfaceNumber];
+		return _G(dynamicallyCreatedSurfaces)[dynamicSurfaceNumber].get();
 	else if (linkedBitmapOnly != nullptr)
 		return linkedBitmapOnly;
 	else if (roomMaskType > kRoomAreaNone)
diff --git a/engines/ags/engine/ac/global_drawing_surface.cpp b/engines/ags/engine/ac/global_drawing_surface.cpp
index 9ae498b98b2..06b25102990 100644
--- a/engines/ags/engine/ac/global_drawing_surface.cpp
+++ b/engines/ags/engine/ac/global_drawing_surface.cpp
@@ -48,10 +48,8 @@ using namespace AGS::Engine;
 
 // RawSaveScreen: copy the current screen to a backup bitmap
 void RawSaveScreen() {
-	if (_G(raw_saved_screen) != nullptr)
-		delete _G(raw_saved_screen);
-	PBitmap source = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
-	_G(raw_saved_screen) = BitmapHelper::CreateBitmapCopy(source.get());
+	auto source = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
+	_G(raw_saved_screen).reset(BitmapHelper::CreateBitmapCopy(source.get()));
 }
 // RawRestoreScreen: copy backup bitmap back to screen; we
 // deliberately don't free the Bitmap *cos they can multiple restore
@@ -61,8 +59,8 @@ void RawRestoreScreen() {
 		debug_script_warn("RawRestoreScreen: unable to restore, since the screen hasn't been saved previously.");
 		return;
 	}
-	PBitmap deston = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
-	deston->Blit(_G(raw_saved_screen), 0, 0, 0, 0, deston->GetWidth(), deston->GetHeight());
+	auto deston = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
+	deston->Blit(_G(raw_saved_screen).get(), 0, 0, 0, 0, deston->GetWidth(), deston->GetHeight());
 	invalidate_screen();
 	mark_current_background_dirty();
 }
@@ -80,7 +78,7 @@ void RawRestoreScreenTinted(int red, int green, int blue, int opacity) {
 	debug_script_log("RawRestoreTinted RGB(%d,%d,%d) %d%%", red, green, blue, opacity);
 
 	PBitmap deston = _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic;
-	tint_image(deston.get(), _G(raw_saved_screen), red, green, blue, opacity);
+	tint_image(deston.get(), _G(raw_saved_screen).get(), red, green, blue, opacity);
 	invalidate_screen();
 	mark_current_background_dirty();
 }
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 413ead9eb39..83ea7266403 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -271,8 +271,7 @@ void unload_old_room() {
 	_GP(play).bg_frame = 0;
 	_GP(play).bg_frame_locked = 0;
 	remove_all_overlays();
-	delete _G(raw_saved_screen);
-	_G(raw_saved_screen) = nullptr;
+	_G(raw_saved_screen).reset();
 	for (int ff = 0; ff < MAX_ROOM_BGFRAMES; ff++)
 		_GP(play).raw_modified[ff] = 0;
 	for (size_t i = 0; i < _GP(thisroom).LocalVariables.size() && i < MAX_GLOBAL_VARIABLES; ++i)
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 6f5fe97c745..6aaf121282f 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -336,8 +336,7 @@ void DoBeforeRestore(PreservedParams &pp) {
 	memcpy(pp.GameOptions, _GP(game).options, GameSetupStruct::MAX_OPTIONS * sizeof(int));
 
 	unload_old_room();
-	delete _G(raw_saved_screen);
-	_G(raw_saved_screen) = nullptr;
+	_G(raw_saved_screen).reset();
 	remove_all_overlays();
 	_GP(play).complete_overlay_on = 0;
 	_GP(play).text_overlay_on = 0;
@@ -439,7 +438,7 @@ static void CopyPreservedGameOptions(GameSetupStructBase &gs, const PreservedPar
 }
 
 // Final processing after successfully restoring from save
-HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data) {
+HSaveError DoAfterRestore(const PreservedParams &pp, RestoredData &r_data) {
 	// Use a yellow dialog highlight for older game versions
 	// CHECKME: it is dubious that this should be right here
 	if (_G(loaded_game_file_version) < kGameVersion_331)
@@ -467,7 +466,7 @@ HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data)
 	// restore these to the ones retrieved from the save game
 	const size_t dynsurf_num = MIN((uint)MAX_DYNAMIC_SURFACES, r_data.DynamicSurfaces.size());
 	for (size_t i = 0; i < dynsurf_num; ++i) {
-		_G(dynamicallyCreatedSurfaces)[i] = r_data.DynamicSurfaces[i];
+		_G(dynamicallyCreatedSurfaces)[i] = std::move(r_data.DynamicSurfaces[i]);
 	}
 
 	// Re-export any missing audio channel script objects, e.g. if restoring old save
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index d7df3e7f9e8..d945724fe75 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -810,7 +810,7 @@ HSaveError WriteDynamicSurfaces(Stream *out) {
 			out->WriteInt8(0);
 		} else {
 			out->WriteInt8(1);
-			serialize_bitmap(_G(dynamicallyCreatedSurfaces)[i], out);
+			serialize_bitmap(_G(dynamicallyCreatedSurfaces)[i].get(), out);
 		}
 	}
 	return HSaveError::None();
@@ -824,9 +824,9 @@ HSaveError ReadDynamicSurfaces(Stream *in, int32_t /*cmp_ver*/, soff_t cmp_size,
 	r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES);
 	for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) {
 		if (in->ReadInt8() == 0)
-			r_data.DynamicSurfaces[i] = nullptr;
+			r_data.DynamicSurfaces[i].reset();
 		else
-			r_data.DynamicSurfaces[i] = read_serialized_bitmap(in);
+			r_data.DynamicSurfaces[i].reset(read_serialized_bitmap(in));
 	}
 	return err;
 }
@@ -926,7 +926,7 @@ HSaveError WriteThisRoom(Stream *out) {
 	}
 	out->WriteBool(_G(raw_saved_screen) != nullptr);
 	if (_G(raw_saved_screen))
-		serialize_bitmap(_G(raw_saved_screen), out);
+		serialize_bitmap(_G(raw_saved_screen).get(), out);
 
 	// room region state
 	for (int i = 0; i < MAX_ROOM_REGIONS; ++i) {
@@ -965,7 +965,7 @@ HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Pres
 			r_data.RoomBkgScene[i] = nullptr;
 	}
 	if (in->ReadBool())
-		_G(raw_saved_screen) = read_serialized_bitmap(in);
+		_G(raw_saved_screen).reset(read_serialized_bitmap(in));
 
 	// room region state
 	for (int i = 0; i < MAX_ROOM_REGIONS; ++i) {
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index 736f8fd3f75..1d4e90b8e5e 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -72,7 +72,7 @@ enum ViewportSaveFlags {
 struct RestoredData {
 	int                     FPS;
 	// Unserialized bitmaps for dynamic surfaces
-	std::vector<Bitmap *>    DynamicSurfaces;
+	std::vector<std::unique_ptr<Bitmap>> DynamicSurfaces;
 	// Scripts global data
 	struct ScriptData {
 		std::vector<char>	Data;
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index ec792291562..a79fb740173 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -317,9 +317,9 @@ static void restore_game_dynamic_surfaces(Stream *in, RestoredData &r_data) {
 	r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES);
 	for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) {
 		if (in->ReadInt8() == 0) {
-			r_data.DynamicSurfaces[i] = nullptr;
+			r_data.DynamicSurfaces[i].reset();
 		} else {
-			r_data.DynamicSurfaces[i] = read_serialized_bitmap(in);
+			r_data.DynamicSurfaces[i].reset(read_serialized_bitmap(in));
 		}
 	}
 }
@@ -340,7 +340,7 @@ static void restore_game_displayed_room_status(Stream *in, GameDataVersion data_
 		bb = in->ReadInt32();
 
 		if (bb)
-			_G(raw_saved_screen) = read_serialized_bitmap(in);
+			_G(raw_saved_screen).reset(read_serialized_bitmap(in));
 
 		// get the current troom, in case they save in room 600 or whatever
 		_GP(troom).ReadFromSavegame_v321(in, data_ver);
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 630d965f0ae..c5e992fdceb 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -182,9 +182,9 @@ Globals::Globals() {
 	_CameraDrawData = new std::vector<RoomCameraDrawData>();
 	_sprlist = new std::vector<SpriteListEntry>();
 	_thingsToDrawList = new std::vector<SpriteListEntry>();
-	_dynamicallyCreatedSurfaces = new AGS::Shared::Bitmap *[MAX_DYNAMIC_SURFACES];
-	Common::fill(_dynamicallyCreatedSurfaces, _dynamicallyCreatedSurfaces +
-	             MAX_DYNAMIC_SURFACES, (AGS::Shared::Bitmap *)nullptr);
+	_dynamicallyCreatedSurfaces = new std::unique_ptr<AGS::Shared::Bitmap>[MAX_DYNAMIC_SURFACES];
+	for (int i = 0; i < MAX_DYNAMIC_SURFACES; i++)
+		_dynamicallyCreatedSurfaces[i].reset(nullptr);
 
 	_actsps = new std::vector<ObjTexture>();
 	_walkbehindobj = new std::vector<ObjTexture>();
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 9f0f7167fda..a2ef77c0cce 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -613,8 +613,8 @@ public:
 	AGS::Engine::IDriverDependantBitmap *_roomBackgroundBmp = nullptr;
 	// whether there are currently remnants of a DisplaySpeech
 	bool _screen_is_dirty = false;
-	AGS::Shared::Bitmap *_raw_saved_screen = nullptr;
-	AGS::Shared::Bitmap **_dynamicallyCreatedSurfaces = nullptr;
+	std::unique_ptr<Shared::Bitmap> _raw_saved_screen;
+	std::unique_ptr<Shared::Bitmap> *_dynamicallyCreatedSurfaces;
 	color *_palette;
 	COLOR_MAP *_maincoltable;
 


Commit: 3d6f4c62f7d8edc1c3d3349a6fbd15535ea5037f
    https://github.com/scummvm/scummvm/commit/3d6f4c62f7d8edc1c3d3349a6fbd15535ea5037f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: fixed stupid mistake in SpriteCache::InitNullSprite()

This fixes deleted dynamic sprites leaving sprite slots marked as "used" indefinitely.
>From upstream dcef4084dcde6a2d9fbdc35b5dc91a6009884e5c

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index e23e696b5de..dc4420ff252 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -144,7 +144,7 @@ Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return nullptr;
 	Bitmap *image = _spriteData[index].Image.release();
-	InitNullSpriteParams(index);
+	InitNullSprite(index);
 	SprCacheLog("RemoveSprite: %d", index);
 	return image;
 }
@@ -153,7 +153,7 @@ void SpriteCache::DisposeSprite(sprkey_t index) {
 	assert(index >= 0); // out of positive range indexes are valid to fail
 	if (index < 0 || (size_t)index >= _spriteData.size())
 		return;
-	InitNullSpriteParams(index);
+	InitNullSprite(index);
 	SprCacheLog("RemoveAndDispose: %d", index);
 }
 
@@ -365,7 +365,7 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
 			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
 			err ? "Sprite does not exist." : err->FullMessage().GetCStr());
-		InitNullSpriteParams(index);
+		RemapSpriteToSprite0(index);
 		return 0;
 	}
 
@@ -374,7 +374,7 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	if (!image) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
 					  "LoadSprite: failed to initialize sprite %d, remapping to sprite 0.", index);
-		InitNullSpriteParams(index);
+		RemapSpriteToSprite0(index);
 		return 0;
 	}
 
@@ -401,22 +401,21 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 
 void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
 	assert((index > 0) && ((size_t)index < _spriteData.size()));
+	if (index <= 0)
+		return; // don't remap sprite 0 to itself
+
 	_sprInfos[index] = _sprInfos[0];
 
-	_spriteData[index].Image.reset();
+//	_spriteData[index].Image.reset();
 	_spriteData[index].Size = _spriteData[0].Size;
 	_spriteData[index].Flags |= SPRCACHEFLAG_REMAP0;
 	SprCacheLog("RemapSpriteToSprite0: %d", index);
 }
 
-void SpriteCache::InitNullSpriteParams(sprkey_t index) {
+void SpriteCache::InitNullSprite(sprkey_t index) {
 	assert(index >= 0);
-	if (index > 0) {
-		RemapSpriteToSprite0(index);
-	} else {
-		_sprInfos[index] = SpriteInfo();
-		_spriteData[index] = SpriteData();
-	}
+	_sprInfos[index] = SpriteInfo();
+	_spriteData[index] = SpriteData();
 }
 
 int SpriteCache::SaveToFile(const String &filename, int store_flags, SpriteCompression compress, SpriteFileIndex &index) {
@@ -449,8 +448,8 @@ HError SpriteCache::InitFile(const String &filename, const String &sprindex_file
 			_sprInfos[i].Width = newsz.Width;
 			_sprInfos[i].Height = newsz.Height;
 		} else {
-			// Handle empty slot: remap to sprite 0
-			InitNullSpriteParams(i);
+			// Mark as empty slot
+			InitNullSprite(i);
 		}
 	}
 	return HError::None();
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 88f2165552a..84c18cc10ee 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -163,7 +163,7 @@ public:
 	// *Deletes* the previous sprite if one was found at the same index.
 	// "flags" are SPF_* constants that define sprite's behavior in game.
 	bool        SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags = 0);
-	// Assigns new sprite for the given index, remapping it to sprite 0;
+	// Assigns new dummy for the given index, silently remapping it to sprite 0;
 	// optionally marks it as an asset placeholder.
 	// *Deletes* the previous sprite if one was found at the same index.
 	void        SetEmptySprite(sprkey_t index, bool as_asset);
@@ -186,7 +186,7 @@ private:
 	// Keep disposing oldest elements until cache has at least the given free space
 	void        FreeMem(size_t space);
 	// Initialize the empty sprite slot
-	void 		InitNullSpriteParams(sprkey_t index);
+	void 		InitNullSprite(sprkey_t index);
 	//
     // Dummy no-op variants for callbacks
     //


Commit: 1464a7e30679d6b282ca3fb9b38d34525869334d
    https://github.com/scummvm/scummvm/commit/1464a7e30679d6b282ca3fb9b38d34525869334d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: for software mode fixed removed overlay cache not invalidated

This is less of an issue in 3.6.1 (as opposed to 3.6.0), where overlays keep their
 container indexes if anything is removed in the middle; but still is meant to
  dispose remaining data.
>From upstream 61afb63adbd3de77e7d23c40f1f5600bc58e9599

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index fdddbd5bb98..ab1fc564f35 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -659,6 +659,13 @@ void reset_objcache_for_sprite(int sprnum, bool deleted) {
 	}
 }
 
+void reset_drawobj_for_overlay(int objnum) {
+	if (objnum > 0 && static_cast<size_t>(objnum) < _GP(overlaybmp).size()) {
+		_GP(overlaybmp)[objnum].reset();
+		_GP(screenovercache)[objnum] = Point(INT32_MIN, INT32_MIN);
+	}
+}
+
 void mark_screen_dirty() {
 	drawstate.ScreenIsDirty = true;
 }
@@ -2007,9 +2014,9 @@ static void construct_overlays() {
 	const bool crop_walkbehinds = (drawstate.WalkBehindMethod == DrawOverCharSprite);
 
 	auto &overs = get_overlays();
-	if (_GP(overlaybmp).size() < overs.size()) {
+	if (is_software_mode && _GP(overlaybmp).size() < overs.size()) {
 		_GP(overlaybmp).resize(overs.size());
-		_GP(screenovercache).resize(overs.size());
+		_GP(screenovercache).resize(overs.size(), Point(INT32_MIN, INT32_MIN));
 	}
 	for (size_t i = 0; i < overs.size(); ++i) {
 		auto &over = overs[i];
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 12559690d3f..4019bbbeced 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -148,6 +148,9 @@ void on_roomcamera_changed(Camera *cam);
 void mark_object_changed(int objid);
 // Resets all object caches which reference this sprite
 void reset_objcache_for_sprite(int sprnum, bool deleted);
+// TODO: write a generic drawable/objcache system where each object
+// allocates a drawable for itself, and disposes one if being removed.
+void reset_drawobj_for_overlay(int objnum);
 
 // whether there are currently remnants of a DisplaySpeech
 void mark_screen_dirty();
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 3085a7e0db6..a1343394106 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -374,6 +374,8 @@ void remove_screen_overlay(int type) {
 	if (type >= OVER_FIRSTFREE)
 		_GP(over_free_ids).push(type);
 
+    reset_drawobj_for_overlay(type);
+
 	// If all overlays have been removed, reset creation index (helps vs overflows)
 	_GP(play).overlay_count--;
 	if (_GP(play).overlay_count == 0)


Commit: 8d7cca3acfb10bbb5ccd8207f7e1b938d39e9c2d
    https://github.com/scummvm/scummvm/commit/8d7cca3acfb10bbb5ccd8207f7e1b938d39e9c2d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed read_savedgame_screenshot() in case there's no screenshot

>From upstream a1032d1018a86d611f8cfe1b82320d54d3553f11

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index b955707dca7..579a7ebe859 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -920,8 +920,11 @@ std::unique_ptr<Shared::Bitmap> read_savedgame_screenshot(const String &savedgam
 		Debug::Printf(kDbgMsg_Error, "Unable to read save's screenshot.\n%s", err->FullMessage().GetCStr());
 		return {};
 	}
-	desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false));
-	return std::move(desc.UserImage);
+	if (desc.UserImage) {
+		desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false));
+		return std::move(desc.UserImage);
+	}
+	return {};
 }
 
 


Commit: 2d2755f49156208c93d3159679165f63752604b1
    https://github.com/scummvm/scummvm/commit/2d2755f49156208c93d3159679165f63752604b1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS; Engine: changed ScreenOverlay to store owned images as dynamic sprites

>From upstream 4728d57086b7eedf112796da011f3c7b0dd3ad30

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/dynamic_sprite.h
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/screen_overlay.cpp
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/engine/game/savegame.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/game/savegame_internal.h
    engines/ags/engine/game/savegame_v321.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index ab1fc564f35..30359f6a2c9 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2046,10 +2046,7 @@ static void construct_overlays() {
 					walkbehinds_cropout(_GP(overlaybmp)[i].get(), pos.X, pos.Y, over.zorder);
 					use_bmp = _GP(overlaybmp)[i].get();
 				}
-			} else if (over.GetSpriteNum() < 0) {
-				use_bmp = over.GetImage();
 			}
-
 			over.ddb = recycle_ddb_sprite(over.ddb, over.GetSpriteNum(), use_bmp, over.HasAlphaChannel());
 			over.ClearChanged();
 		}
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 8fb3ee7e0ed..9316cb884de 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -450,9 +450,11 @@ int add_dynamic_sprite(int slot, std::unique_ptr<Bitmap> image, bool has_alpha,
 	return slot;
 }
 
-void free_dynamic_sprite(int slot) {
-	assert((slot > 0) && (_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC));
-	if (slot <= 0 || (_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC) == 0)
+void free_dynamic_sprite(int slot, bool notify_all) {
+	assert((slot > 0) && (static_cast<size_t>(slot) < _GP(game).SpriteInfos.size()) &&
+		   (_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC));
+	if ((slot <= 0) || (static_cast<size_t>(slot) >= _GP(game).SpriteInfos.size()) ||
+		(_GP(game).SpriteInfos[slot].Flags & SPF_DYNAMICALLOC) == 0)
 		return;
 
 	_GP(spriteset).DisposeSprite(slot);
diff --git a/engines/ags/engine/ac/dynamic_sprite.h b/engines/ags/engine/ac/dynamic_sprite.h
index ff7084c2db2..add72c22fd9 100644
--- a/engines/ags/engine/ac/dynamic_sprite.h
+++ b/engines/ags/engine/ac/dynamic_sprite.h
@@ -60,7 +60,7 @@ int     add_dynamic_sprite(std::unique_ptr<AGS::Shared::Bitmap> image, bool has_
 // Updates game.SpriteInfos[].
 int     add_dynamic_sprite(int slot, std::unique_ptr<AGS::Shared::Bitmap> image, bool hasAlpha = false, uint32_t extra_flags = 0u);
 // Disposes a dynamic sprite, and frees the slot
-void    free_dynamic_sprite(int slot);
+void    free_dynamic_sprite(int slot, bool notify_all = true);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index a1343394106..d756afe1827 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -81,8 +81,7 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 										 width, fontid, allow_shrink, has_alpha);
 
 	// Update overlay properties
-	over->SetImage(image, adj_x - dummy_x, adj_y - dummy_y);
-	over->SetAlphaChannel(has_alpha);
+	over->SetImage(std::unique_ptr<Bitmap>(image), has_alpha, adj_x - dummy_x, adj_y - dummy_y);
 	over->ddb = nullptr; // is generated during first draw pass
 }
 
@@ -163,14 +162,14 @@ int Overlay_GetGraphicWidth(ScriptOverlay *scover) {
 	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(over->GetImage()->GetWidth());
+	return game_to_data_coord(over->GetGraphicSize().Width);
 }
 
 int Overlay_GetGraphicHeight(ScriptOverlay *scover) {
 	auto *over = get_overlay(scover->overlayId);
 	if (!over)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(over->GetImage()->GetHeight());
+	return game_to_data_coord(over->GetGraphicSize().Height);
 }
 
 void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
@@ -409,11 +408,9 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	over.type = type;
 	over.creation_id = _GP(play).overlay_creation_id++;
 	if (piccy) {
-		over.SetImage(piccy, pic_offx, pic_offy);
-		over.SetAlphaChannel(has_alpha);
+		over.SetImage(std::unique_ptr<Bitmap>(piccy), has_alpha, pic_offx, pic_offy);
 	} else {
 		over.SetSpriteNum(sprnum, pic_offx, pic_offy);
-		over.SetAlphaChannel((_GP(game).SpriteInfos[sprnum].Flags & SPF_ALPHACHANNEL) != 0);
 	}
 	over.ddb = nullptr; // is generated during first draw pass
 	over.x = x;
@@ -470,20 +467,20 @@ Point get_overlay_position(const ScreenOverlay &over) {
 		auto view = FindNearestViewport(charid);
 		const int charpic = _GP(views)[_GP(game).chars[charid].view].loops[_GP(game).chars[charid].loop].frames[0].pic;
 		const int height = (_GP(charextra)[charid].height < 1) ? _GP(game).SpriteInfos[charpic].Height : _GP(charextra)[charid].height;
-		Point screenpt = view->RoomToScreen(
+		const Point screenpt = view->RoomToScreen(
 			data_to_game_coord(_GP(game).chars[charid].x),
 			data_to_game_coord(_GP(charextra)[charid].GetEffectiveY(&_GP(game).chars[charid])) - height).first;
-		Bitmap *pic = over.GetImage();
-		int tdxp = std::max(0, screenpt.X - pic->GetWidth() / 2);
+		const Size pic_size = over.GetGraphicSize();
+		int tdxp = std::max(0, screenpt.X - pic_size.Width / 2);
 		int tdyp = screenpt.Y - get_fixed_pixel_size(5);
-		tdyp -= pic->GetHeight();
+		tdyp -= pic_size.Height;
 		tdyp = std::max(5, tdyp);
 
-		if ((tdxp + pic->GetWidth()) >= ui_view.GetWidth())
-			tdxp = (ui_view.GetWidth() - pic->GetWidth()) - 1;
+		if ((tdxp + pic_size.Width) >= ui_view.GetWidth())
+			tdxp = (ui_view.GetWidth() - pic_size.Width) - 1;
 		if (_GP(game).chars[charid].room != _G(displayed_room)) {
-			tdxp = ui_view.GetWidth() / 2 - pic->GetWidth() / 2;
-			tdyp = ui_view.GetHeight() / 2 - pic->GetHeight() / 2;
+			tdxp = ui_view.GetWidth() / 2 - pic_size.Width / 2;
+			tdyp = ui_view.GetHeight() / 2 - pic_size.Height / 2;
 		}
 		return Point(tdxp, tdyp);
 	} else {
diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index 9316a471c3f..f5d19af1180 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -20,8 +20,10 @@
  */
 
 #include "common/std/utility.h"
+#include "ags/engine/ac/dynamic_sprite.h"
 #include "ags/engine/ac/screen_overlay.h"
 #include "ags/shared/ac/sprite_cache.h"
+#include "ags/shared/ac/game_setup_struct.h"
 #include "ags/shared/util/stream.h"
 #include "ags/globals.h"
 
@@ -29,44 +31,64 @@ namespace AGS3 {
 
 using namespace AGS::Shared;
 
+ScreenOverlay::ScreenOverlay(ScreenOverlay &&over) {
+	*this = std::move(over);
+}
+
+ScreenOverlay::~ScreenOverlay() {
+	if (_sprnum > 0 && !IsSpriteShared())
+		free_dynamic_sprite(_sprnum, false);
+}
+
+// TODO: this may be avoided if we somehow make (dynamic) sprites reference counted when assigning ID too
+ScreenOverlay &ScreenOverlay::operator=(ScreenOverlay &&over) {
+	*this = over;
+	over._sprnum = 0;
+	return *this;
+}
+
+void ScreenOverlay::ResetImage() {
+	if (_sprnum > 0 && !IsSpriteShared())
+		free_dynamic_sprite(_sprnum, false);
+	_sprnum = 0;
+	_flags &= ~(kOver_SpriteShared | kOver_AlphaChannel);
+	scaleWidth = scaleHeight = offsetX = offsetY = 0;
+}
+
 Bitmap *ScreenOverlay::GetImage() const {
-	return IsSpriteReference() ?
-		_GP(spriteset)[_sprnum] :
-		_pic.get();
+	return _GP(spriteset)[_sprnum];
 }
 
-void ScreenOverlay::SetImage(Shared::Bitmap *pic, int offx, int offy) {
-	_flags &= ~kOver_SpriteReference;
-	_pic.reset(pic);
-	_sprnum = -1;
-	offsetX = offx;
-	offsetY = offy;
-	scaleWidth = scaleHeight = 0;
-	const auto *img = GetImage();
-	if (img) {
-		scaleWidth = img->GetWidth();
-		scaleHeight = img->GetHeight();
+Size ScreenOverlay::GetGraphicSize() const {
+	return Size(_GP(game).SpriteInfos[_sprnum].Width, _GP(game).SpriteInfos[_sprnum].Height);
+}
+
+void ScreenOverlay::SetImage(std::unique_ptr<Shared::Bitmap> pic, bool has_alpha, int offx, int offy) {
+	ResetImage();
+	if (pic) {
+		_flags |= kOver_AlphaChannel * has_alpha;
+		offsetX = offx;
+		offsetY = offy;
+		scaleWidth = pic->GetWidth();
+		scaleHeight = pic->GetHeight();
+		_sprnum = add_dynamic_sprite(std::move(pic), has_alpha);
 	}
 	MarkChanged();
 }
 
 void ScreenOverlay::SetSpriteNum(int sprnum, int offx, int offy) {
-	_flags |= kOver_SpriteReference;
-	_pic.reset();
+	ResetImage();
+	_flags |= kOver_SpriteShared | kOver_AlphaChannel * ((_GP(game).SpriteInfos[sprnum].Flags & SPF_ALPHACHANNEL) != 0);
 	_sprnum = sprnum;
 	offsetX = offx;
 	offsetY = offy;
-	scaleWidth = scaleHeight = 0;
-	const auto *img = GetImage();
-	if (img) {
-		scaleWidth = img->GetWidth();
-		scaleHeight = img->GetHeight();
-	}
+	scaleWidth = _GP(game).SpriteInfos[sprnum].Width;
+	scaleHeight = _GP(game).SpriteInfos[sprnum].Height;
 	MarkChanged();
 }
 
 void ScreenOverlay::ReadFromSavegame(Stream *in, bool &has_bitmap, int32_t cmp_ver) {
-	_pic.reset();
+	ResetImage();
 	ddb = nullptr;
 	in->ReadInt32(); // ddb 32-bit pointer value (nasty legacy format)
 	int pic = in->ReadInt32();
@@ -101,21 +123,18 @@ void ScreenOverlay::ReadFromSavegame(Stream *in, bool &has_bitmap, int32_t cmp_v
 
 	// New saves always save overlay images as a part of the dynamicsprite set;
 	// old saves could contain images saved along with overlays
-	if ((cmp_ver >= kOverSvgVersion_36108) || (_flags & kOver_SpriteReference)) {
+	if ((cmp_ver >= kOverSvgVersion_36108) || (_flags & kOver_SpriteShared)) {
 		_sprnum = pic;
 		has_bitmap = false;
 	} else {
-		_sprnum = -1;
+		_sprnum = 0;
 		has_bitmap = pic != 0;
 	}
 }
 
 void ScreenOverlay::WriteToSavegame(Stream *out) const {
 	out->WriteInt32(0); // ddb 32-bit pointer value (nasty legacy format)
-	if (_flags & kOver_SpriteReference)
-		out->WriteInt32(_sprnum); // sprite reference
-	else
-		out->WriteInt32(_pic ? 1 : 0); // has bitmap
+	out->WriteInt32(_sprnum); // sprite id
 	out->WriteInt32(type);
 	out->WriteInt32(x);
 	out->WriteInt32(y);
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index d4081f272c6..bb8992eb54e 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -19,16 +19,24 @@
  *
  */
 
- // ScreenOverlay is a simple sprite container with no advanced functions.
-// May contain owned bitmap or reference persistent sprite's id, similar to how
-// other game objects do that.
+// ScreenOverlay is a simple sprite container with no advanced functions.
+// Contains an id of a sprite, which may be either owned by overlay, or shared
+// to whole game similar to how other objects use sprites.
 // May logically exist either on UI or room layer.
+// TODO: historically overlay objects contained an actual bitmap in them.
+// This was remade into having a dynamic sprite allocated exclusively for
+// overlay. But sprites do not have any kind of a ref count of their own
+// (unless exported into script as DynamicSprite), so we have to keep an
+// overlay's flag, which tells that the sprite it references must be deleted
+// on overlay's disposal. This should be improved at some point, by devising
+// a better kind of a sprite's ownership mechanic.
 
 #ifndef AGS_ENGINE_AC_SCREEN_OVERLAY_H
 #define AGS_ENGINE_AC_SCREEN_OVERLAY_H
 
 #include "common/std/memory.h"
 #include "ags/shared/core/types.h"
+#include "ags/shared/util/geometry.h"
 
 namespace AGS3 {
 
@@ -52,7 +60,7 @@ enum OverlayFlags {
 	kOver_AlphaChannel = 0x0001,
 	kOver_PositionAtRoomXY = 0x0002, // room-relative position, may be in ui
 	kOver_RoomLayer = 0x0004,        // work in room layer (as opposed to UI)
-	kOver_SpriteReference = 0x0008   // reference persistent sprite
+	kOver_SpriteShared = 0x0008   // reference shared sprite (as opposed to exclusive)
 };
 
 enum OverlaySvgVersion {
@@ -83,11 +91,16 @@ struct ScreenOverlay {
 	int zorder = INT_MIN;
 	int transparency = 0;
 
+	ScreenOverlay() = default;
+	ScreenOverlay(ScreenOverlay &&);
+	~ScreenOverlay();
+	ScreenOverlay &operator=(ScreenOverlay &&);
+
 	bool HasAlphaChannel() const {
 		return (_flags & kOver_AlphaChannel) != 0;
 	}
-	bool IsSpriteReference() const {
-		return (_flags & kOver_SpriteReference) != 0;
+	bool IsSpriteShared() const {
+		return (_flags & kOver_SpriteShared) != 0;
 	}
 	bool IsRoomRelative() const {
 		return (_flags & kOver_PositionAtRoomXY) != 0;
@@ -107,11 +120,15 @@ struct ScreenOverlay {
 	}
 	// Gets actual overlay's image, whether owned by overlay or by a sprite reference
 	Shared::Bitmap *GetImage() const;
-	// Get sprite reference id, or -1 if none set
+	// Get sprite reference id, or 0 if none set
 	int GetSpriteNum() const {
 		return _sprnum;
 	}
-	void SetImage(Shared::Bitmap *pic, int offx = 0, int offy = 0);
+	Size GetGraphicSize() const;
+	// Assigns an exclusive image to this overlay; the image will be stored as a dynamic sprite
+    // in a sprite cache, but owned by this overlay and therefore disposed at its disposal
+	void SetImage(std::unique_ptr<Shared::Bitmap> pic, bool has_alpha = false, int offx = 0, int offy = 0);
+	// Assigns a shared sprite to this overlay
 	void SetSpriteNum(int sprnum, int offx = 0, int offy = 0);
 	// Tells if Overlay has graphically changed recently
 	bool HasChanged() const {
@@ -130,10 +147,13 @@ struct ScreenOverlay {
 	void WriteToSavegame(Shared::Stream *out) const;
 
 private:
-	int _flags = 0; // OverlayFlags
+	void ResetImage();
+	ScreenOverlay(const ScreenOverlay &) = default;
+	ScreenOverlay &operator=(const ScreenOverlay &) = default;
+
+	int _flags = 0;  // OverlayFlags
+	int _sprnum = 0; // sprite id
 	bool _hasChanged = false;
-	std::shared_ptr<Shared::Bitmap> _pic; // owned bitmap
-	int _sprnum = -1; // sprite reference
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp
index 6aaf121282f..d58a926b744 100644
--- a/engines/ags/engine/game/savegame.cpp
+++ b/engines/ags/engine/game/savegame.cpp
@@ -345,8 +345,6 @@ void DoBeforeRestore(PreservedParams &pp) {
 	// NOTE: sprite 0 is a special constant sprite that cannot be dynamic
 	for (int i = 1; i < (int)_GP(spriteset).GetSpriteSlotCount(); ++i) {
 		if (_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) {
-			// do this early, so that it changing _GP(guibuts) doesn't
-			// affect the restored data
 			free_dynamic_sprite(i);
 		}
 	}
@@ -463,7 +461,14 @@ HSaveError DoAfterRestore(const PreservedParams &pp, RestoredData &r_data) {
 	// Remap old sound nums in case we restored a save having a different list of audio clips
 	RemapLegacySoundNums(_GP(game), _GP(views), _G(loaded_game_file_version));
 
-	// restore these to the ones retrieved from the save game
+	// Restore Overlay bitmaps (older save format, which stored them along with overlays)
+	auto &overs = get_overlays();
+	for (auto &over_im : r_data.OverlayImages) {
+		auto &over = overs[over_im._key];
+		over.SetImage(std::move(over_im._value), over.HasAlphaChannel(), over.offsetX, over.offsetY);
+	}
+
+	// Restore dynamic surfaces
 	const size_t dynsurf_num = MIN((uint)MAX_DYNAMIC_SURFACES, r_data.DynamicSurfaces.size());
 	for (size_t i = 0; i < dynsurf_num; ++i) {
 		_G(dynamicallyCreatedSurfaces)[i] = std::move(r_data.DynamicSurfaces[i]);
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index d945724fe75..7546ba9165c 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -769,8 +769,6 @@ HSaveError WriteOverlays(Stream *out) {
 			continue;
 		valid_count++;
 		over.WriteToSavegame(out);
-		if (!over.IsSpriteReference())
-			serialize_bitmap(over.GetImage(), out);
 	}
 	out->Seek(count_off, kSeekBegin);
 	out->WriteInt32(valid_count);
@@ -778,7 +776,7 @@ HSaveError WriteOverlays(Stream *out) {
 	return HSaveError::None();
 }
 
-HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData & /*r_data*/) {
+HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, soff_t cmp_size, const PreservedParams & /*pp*/, RestoredData &r_data) {
 	// Remember that overlay indexes may be non-sequential
 	// the vector may be resized during read
 	size_t over_count = in->ReadInt32();
@@ -791,11 +789,7 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, soff_t cmp_size, const Pres
 		if (over.type < 0)
 			continue; // safety abort
 		if (has_bitmap)
-			over.SetImage(read_serialized_bitmap(in), over.offsetX, over.offsetY);
-		if (has_bitmap && (over.scaleWidth <= 0 || over.scaleHeight <= 0)) {
-			over.scaleWidth = over.GetImage()->GetWidth();
-			over.scaleHeight = over.GetImage()->GetHeight();
-		}
+			r_data.OverlayImages[over.type].reset(read_serialized_bitmap(in));
 		if (overs.size() <= static_cast<uint32_t>(over.type))
 			overs.resize(over.type + 1);
 		overs[over.type] = std::move(over);
diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h
index 1d4e90b8e5e..cf180a598e4 100644
--- a/engines/ags/engine/game/savegame_internal.h
+++ b/engines/ags/engine/game/savegame_internal.h
@@ -24,6 +24,7 @@
 
 #include "common/std/memory.h"
 #include "common/std/vector.h"
+#include "common/std/map.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/shared/gfx/bitmap.h"
@@ -73,6 +74,8 @@ struct RestoredData {
 	int                     FPS;
 	// Unserialized bitmaps for dynamic surfaces
 	std::vector<std::unique_ptr<Bitmap>> DynamicSurfaces;
+	// Unserialized bitmaps for overlays (old-style saves)
+	std::unordered_map<int, std::unique_ptr<Bitmap> > OverlayImages;
 	// Scripts global data
 	struct ScriptData {
 		std::vector<char>	Data;
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index a79fb740173..d9a4868d606 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -297,7 +297,7 @@ static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size
 	}
 }
 
-static void restore_game_overlays(Stream *in) {
+static void restore_game_overlays(Stream *in, RestoredData &r_data) {
 	size_t num_overs = in->ReadInt32();
 	// Remember that overlay indexes may be not sequential,
 	// the vector may be resized during read
@@ -307,7 +307,7 @@ static void restore_game_overlays(Stream *in) {
 	ReadOverlays_Aligned(in, has_bitmap, num_overs);
 	for (size_t i = 0; i < overs.size(); ++i) {
 		if (has_bitmap[i])
-			overs[i].SetImage(read_serialized_bitmap(in), overs[i].offsetX, overs[i].offsetY);
+			r_data.OverlayImages[i].reset(read_serialized_bitmap(in));
 	}
 }
 
@@ -468,7 +468,7 @@ HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const Pr
 		return err;
 	restore_game_thisroom(in, r_data);
 	restore_game_ambientsounds(in, r_data);
-	restore_game_overlays(in);
+	restore_game_overlays(in, r_data);
 	restore_game_dynamic_surfaces(in, r_data);
 	restore_game_displayed_room_status(in, data_ver, r_data);
 	err = restore_game_globalvars(in);


Commit: dc30f4e2a81136ac4249cffc0e99a3cb602bf459
    https://github.com/scummvm/scummvm/commit/dc30f4e2a81136ac4249cffc0e99a3cb602bf459
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: Overlay uses ObjTexture similar to object and chars

+ fix free_dynamic_sprite in previous commit

>From upstream 84432448725c77fbd3448a8d9c92e7e44e07a217

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/screen_overlay.cpp
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 30359f6a2c9..03198bd90d8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -469,7 +469,7 @@ void clear_drawobj_cache() {
 	}
 
 	// room overlays cache
-	_GP(screenovercache).clear();
+	_GP(overcache).clear();
 
 	// cleanup Character + Room object textures
 	for (auto &o : _GP(actsps)) o = ObjTexture();
@@ -477,8 +477,7 @@ void clear_drawobj_cache() {
 	// cleanup GUI and controls textures
 	for (auto &o : _GP(guibg)) o = ObjTexture();
 	for (auto &o : _GP(guiobjbg)) o = ObjTexture();
-	// cleanup Overlay intermediate bitmaps
-	_GP(overlaybmp).clear();
+	_GP(overtxs).clear();
 
 	dispose_debug_room_drawdata();
 }
@@ -660,9 +659,10 @@ void reset_objcache_for_sprite(int sprnum, bool deleted) {
 }
 
 void reset_drawobj_for_overlay(int objnum) {
-	if (objnum > 0 && static_cast<size_t>(objnum) < _GP(overlaybmp).size()) {
-		_GP(overlaybmp)[objnum].reset();
-		_GP(screenovercache)[objnum] = Point(INT32_MIN, INT32_MIN);
+	if (objnum > 0 && static_cast<size_t>(objnum) < _GP(overtxs).size()) {
+		_GP(overtxs)[objnum] = ObjTexture();
+		if (drawstate.SoftwareRender)
+			_GP(overcache)[objnum] = Point(INT32_MIN, INT32_MIN);
 	}
 }
 
@@ -1593,7 +1593,7 @@ static void add_roomovers_for_drawing() {
 		if (!over.IsRoomLayer()) continue; // not a room layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
+		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
 	}
 }
 
@@ -1793,7 +1793,7 @@ void draw_gui_and_overlays() {
 		if (over.IsRoomLayer()) continue; // not a ui layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(over.ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
+		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
 	}
 
 	// Add GUIs
@@ -2014,9 +2014,10 @@ static void construct_overlays() {
 	const bool crop_walkbehinds = (drawstate.WalkBehindMethod == DrawOverCharSprite);
 
 	auto &overs = get_overlays();
-	if (is_software_mode && _GP(overlaybmp).size() < overs.size()) {
-		_GP(overlaybmp).resize(overs.size());
-		_GP(screenovercache).resize(overs.size(), Point(INT32_MIN, INT32_MIN));
+	if ( _GP(overtxs).size() < overs.size()) {
+		_GP(overtxs).resize(overs.size());
+		if (is_software_mode)
+			_GP(overcache).resize(overs.size(), Point(INT32_MIN, INT32_MIN));
 	}
 	for (size_t i = 0; i < overs.size(); ++i) {
 		auto &over = overs[i];
@@ -2027,34 +2028,38 @@ static void construct_overlays() {
 		// If walk behinds are drawn over the cached object sprite, then check if positions were updated
 		if (crop_walkbehinds && over.IsRoomLayer()) {
 			Point pos = get_overlay_position(over);
-			has_changed |= (pos.X != _GP(screenovercache)[i].X || pos.Y != _GP(screenovercache)[i].Y);
-			_GP(screenovercache)[i].X = pos.X; _GP(screenovercache)[i].Y = pos.Y;
+			has_changed |= (pos.X != _GP(overcache)[i].X || pos.Y != _GP(overcache)[i].Y);
+			_GP(overcache)[i].X = pos.X; _GP(overcache)[i].Y = pos.Y;
 		}
 
+		auto &overtx = _GP(overtxs)[i];
 		if (has_changed) {
+			overtx.SpriteID = over.GetSpriteNum();
 			// For software mode - prepare transformed bitmap if necessary;
 			// for hardware-accelerated - use the sprite ID if possible, to avoid redundant sprite load
+			// TODO: find a way to unify this code with the character & object ObjTexture preparation;
+			// they use practically same approach, except of different fields cache.
 			Bitmap *use_bmp = nullptr;
 			if (is_software_mode) {
-				use_bmp = transform_sprite(over.GetImage(), over.HasAlphaChannel(), _GP(overlaybmp)[i], Size(over.scaleWidth, over.scaleHeight));
+				use_bmp = transform_sprite(over.GetImage(), over.HasAlphaChannel(), overtx.Bmp, Size(over.scaleWidth, over.scaleHeight));
 				if (crop_walkbehinds && over.IsRoomLayer()) {
-					if (use_bmp != _GP(overlaybmp)[i].get()) {
-						recycle_bitmap(_GP(overlaybmp)[i], use_bmp->GetColorDepth(), use_bmp->GetWidth(), use_bmp->GetHeight(), true);
-						_GP(overlaybmp)[i]->Blit(use_bmp);
+					if (use_bmp != overtx.Bmp.get()) {
+						recycle_bitmap(overtx.Bmp, use_bmp->GetColorDepth(), use_bmp->GetWidth(), use_bmp->GetHeight(), true);
+						overtx.Bmp->Blit(use_bmp);
 					}
 					Point pos = get_overlay_position(over);
-					walkbehinds_cropout(_GP(overlaybmp)[i].get(), pos.X, pos.Y, over.zorder);
-					use_bmp = _GP(overlaybmp)[i].get();
+					walkbehinds_cropout(overtx.Bmp.get(), pos.X, pos.Y, over.zorder);
+					use_bmp = overtx.Bmp.get();
 				}
 			}
-			over.ddb = recycle_ddb_sprite(over.ddb, over.GetSpriteNum(), use_bmp, over.HasAlphaChannel());
+			sync_object_texture(overtx, over.HasAlphaChannel());
 			over.ClearChanged();
 		}
 
-		assert(over.ddb); // Test for missing texture, might happen if not marked for update
-		if (!over.ddb) continue;
-		over.ddb->SetStretch(over.scaleWidth, over.scaleHeight);
-		over.ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(over.transparency));
+		assert(overtx.Ddb); // Test for missing texture, might happen if not marked for update
+		if (!overtx.Ddb) continue;
+		overtx.Ddb->SetStretch(over.scaleWidth, over.scaleHeight);
+		overtx.Ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(over.transparency));
 	}
 }
 
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 9316cb884de..2e11f920d38 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -435,8 +435,7 @@ int add_dynamic_sprite(std::unique_ptr<Bitmap> image, bool has_alpha, uint32_t e
 	if (slot <= 0)
 		return 0;
 
-	add_dynamic_sprite(slot, std::move(image), has_alpha, extra_flags);
-	return slot;
+	return add_dynamic_sprite(slot, std::move(image), has_alpha, extra_flags);
 }
 
 int add_dynamic_sprite(int slot, std::unique_ptr<Bitmap> image, bool has_alpha, uint32_t extra_flags) {
@@ -458,7 +457,8 @@ void free_dynamic_sprite(int slot, bool notify_all) {
 		return;
 
 	_GP(spriteset).DisposeSprite(slot);
-	game_sprite_deleted(slot);
+	if (notify_all)
+		game_sprite_deleted(slot);
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index d756afe1827..2115316501b 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -82,7 +82,6 @@ void Overlay_SetText(ScriptOverlay *scover, int width, int fontid, int text_colo
 
 	// Update overlay properties
 	over->SetImage(std::unique_ptr<Bitmap>(image), has_alpha, adj_x - dummy_x, adj_y - dummy_y);
-	over->ddb = nullptr; // is generated during first draw pass
 }
 
 int Overlay_GetX(ScriptOverlay *scover) {
@@ -336,9 +335,6 @@ static void invalidate_and_subref(ScreenOverlay &over) {
 // Frees overlay resources and tell to dispose script object if there are no refs left
 static void dispose_overlay(ScreenOverlay &over) {
 	over.SetImage(nullptr);
-	if (over.ddb != nullptr)
-		_G(gfxDriver)->DestroyDDB(over.ddb);
-	over.ddb = nullptr;
 	// invalidate script object and dispose it if there are no more refs
 	if (over.associatedOverlayHandle > 0) {
 		ScriptOverlay *scover = (ScriptOverlay *)ccGetObjectAddressFromHandle(over.associatedOverlayHandle);
@@ -412,7 +408,6 @@ size_t add_screen_overlay_impl(bool roomlayer, int x, int y, int type, int sprnu
 	} else {
 		over.SetSpriteNum(sprnum, pic_offx, pic_offy);
 	}
-	over.ddb = nullptr; // is generated during first draw pass
 	over.x = x;
 	over.y = y;
 	// by default draw speech and portraits over GUI, and the rest under GUI
diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index f5d19af1180..9d75559796d 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -89,7 +89,6 @@ void ScreenOverlay::SetSpriteNum(int sprnum, int offx, int offy) {
 
 void ScreenOverlay::ReadFromSavegame(Stream *in, bool &has_bitmap, int32_t cmp_ver) {
 	ResetImage();
-	ddb = nullptr;
 	in->ReadInt32(); // ddb 32-bit pointer value (nasty legacy format)
 	int pic = in->ReadInt32();
 	type = in->ReadInt32();
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index bb8992eb54e..868beb8f4c1 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -72,8 +72,6 @@ enum OverlaySvgVersion {
 };
 
 struct ScreenOverlay {
-	// Texture
-	Engine::IDriverDependantBitmap *ddb = nullptr;
 	// Overlay's "type" is a merged special overlay ID and internal container index
 	int type = -1;
 	// Arbitrary creation order index, meant for resolving equal z-sorting
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index c5e992fdceb..ad3724096d6 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -194,7 +194,7 @@ Globals::Globals() {
 	_guiobjddb = new std::vector<Engine::IDriverDependantBitmap *>();
 	_guiobjoff = new std::vector<Point>();
 	_guiobjddbref = new std::vector<int>();
-	_overlaybmp = new std::vector<std::unique_ptr<Shared::Bitmap> >();
+	_overtxs = new std::vector<ObjTexture>();
 	_debugRoomMaskObj =  new ObjTexture();
 	_debugMoveListObj = new ObjTexture();
 	_debugConsoleBuffer = new AGS::Shared::Bitmap();
@@ -255,7 +255,7 @@ Globals::Globals() {
 	_scrDialog = new std::vector<ScriptDialog>();
 	_charcache = new std::vector<ObjectCache>();
 	_objcache = new ObjectCache[MAX_ROOM_OBJECTS];
-	_screenovercache = new std::vector<Point>();
+	_overcache = new std::vector<Point>();
 	_charextra = new std::vector<CharacterExtras>();
 	_mls = new std::vector<MoveList>();
 	_views = new std::vector<ViewStruct>();
@@ -475,7 +475,7 @@ Globals::~Globals() {
 	delete _guiobjddbref;
 	delete _guiobjddb;
 	delete _guiobjoff;
-	delete _overlaybmp;
+	delete _overtxs;
 	delete _debugRoomMaskObj;
 	delete _debugMoveListObj;
 
@@ -527,7 +527,7 @@ Globals::~Globals() {
 	delete _scrDialog;
 	delete _charcache;
 	delete[] _objcache;
-	delete _screenovercache;
+	delete _overcache;
 	delete _charextra;
 	delete _mls;
 	delete _views;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index a2ef77c0cce..689ce7d768b 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -598,8 +598,8 @@ public:
 	std::vector<ObjTexture> *_guiobjbg;
 	// first control texture index of each GUI
 	std::vector<int> *_guiobjddbref;
-	// Overlay's cached transformed bitmap, for software mode
-	std::vector<std::unique_ptr<Shared::Bitmap> > *_overlaybmp;
+	// Overlays textures
+	std::vector<ObjTexture> *_overtxs;
 	// For debugging room masks
 	ObjTexture *_debugRoomMaskObj;
 	ObjTexture *_debugMoveListObj;
@@ -759,7 +759,7 @@ public:
 	// whether these require texture update
 	std::vector<ObjectCache> *_charcache;
 	ObjectCache *_objcache;
-	std::vector<Point> *_screenovercache;
+	std::vector<Point> *_overcache;
 	std::vector<CharacterExtras> *_charextra;
 	// MoveLists for characters and room objects; NOTE: 1-based array!
 	// object sprites begin with index 1, characters are after MAX_ROOM_OBJECTS + 1


Commit: 68333582044136a3e6acde37e8f682c7ab9e30c1
    https://github.com/scummvm/scummvm/commit/68333582044136a3e6acde37e8f682c7ab9e30c1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: made SpriteCache::GetFreeIndex() much faster

>From upstream 5cc9cfb6c537898ec6ef8181ec18fb8b04f9976f
and 7f745a0906adaf8288306cc49c296d26827f15b8

Changed paths:
    engines/ags/engine/ac/dynobj/managed_object_pool.cpp
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
index d37632eb4b1..db47a382a0e 100644
--- a/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
+++ b/engines/ags/engine/ac/dynobj/managed_object_pool.cpp
@@ -310,9 +310,7 @@ int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectCollectionReader *reade
 	}
 
 	// re-adjust next handles. (in case saved in random order)
-	while (!available_ids.empty()) {
-		available_ids.pop();
-	}
+	available_ids = std::queue<int32_t>();
 	nextHandle = 1;
 
 	for (const auto &o : objects) {
diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index dc4420ff252..ddc5cd1bccd 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -210,7 +210,8 @@ bool SpriteCache::SpriteData::IsLocked() const {
 }
 
 bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
-	return index >= 0 && (size_t)index < _spriteData.size() && _spriteData[index].DoesSpriteExist();
+	return index >= 0 && (size_t)index < _spriteData.size() &&  // in the valid range
+		   _spriteData[index].IsValid();  // has assigned sprite
 }
 
 Size SpriteCache::GetSpriteResolution(sprkey_t index) const {
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 84c18cc10ee..48652378e3b 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -210,6 +210,8 @@ private:
 
 		SpriteData &operator=(SpriteData &&other) = default;
 
+		// Tells if this slot has a valid sprite assigned (not empty slot)
+		bool IsValid() const { return Flags != 0u; }
 		// Tells if there actually is a registered sprite in this slot
 		bool DoesSpriteExist() const;
 		// Tells if there's a game resource corresponding to this slot


Commit: f6e4d415a53a74fa89fe64b0a6a81482f38a4e03
    https://github.com/scummvm/scummvm/commit/f6e4d415a53a74fa89fe64b0a6a81482f38a4e03
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: implemented a sprite placeholder in SpriteCache

Currently placeholder is a 1x1 transparent sprite.
Placeholder is returned by the operator[] for safety purposes.
DoesSpriteExist() is still a valid way to test if a sprite is actually registered.
Removed the obscure mechanic of remapping failed sprites to sprite 0.
>From upstream 74ff2bdf0ccba4d62821099bce11817f161f47ae

Changed paths:
    engines/ags/shared/ac/sprite_cache.cpp
    engines/ags/shared/ac/sprite_cache.h


diff --git a/engines/ags/shared/ac/sprite_cache.cpp b/engines/ags/shared/ac/sprite_cache.cpp
index ddc5cd1bccd..001c2da65b5 100644
--- a/engines/ags/shared/ac/sprite_cache.cpp
+++ b/engines/ags/shared/ac/sprite_cache.cpp
@@ -43,8 +43,8 @@ using namespace AGS::Shared;
 #define SPRCACHEFLAG_ISASSET  0x01
 // Tells that the sprite is assigned externally and cannot be autodisposed.
 #define SPRCACHEFLAG_EXTERNAL 0x02
-// Tells that the sprite index was remapped to the placeholder (sprite 0).
-#define SPRCACHEFLAG_REMAP0	  0x04
+// Tells that the asset sprite failed to load
+#define SPRCACHEFLAG_ERROR	  0x04
 // Locked sprites are ones that should not be freed when out of cache space.
 #define SPRCACHEFLAG_LOCKED	  0x08
 
@@ -65,10 +65,9 @@ SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &cal
 	_callbacks.InitSprite = (callbacks.InitSprite) ? callbacks.InitSprite : DummyInitSprite;
 	_callbacks.PostInitSprite = (callbacks.PostInitSprite) ? callbacks.PostInitSprite : DummyPostInitSprite;
 	_callbacks.PrewriteSprite = (callbacks.PrewriteSprite) ? callbacks.PrewriteSprite : DummyPrewriteSprite;
-}
 
-SpriteCache::~SpriteCache() {
-	Reset();
+	// Generate a placeholder sprite: 1x1 transparent bitmap
+	_placeholder.reset(BitmapHelper::CreateTransparentBitmap(1, 1, 8));
 }
 
 size_t SpriteCache::GetCacheSize() const {
@@ -137,7 +136,7 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) {
 	}
 	if (as_asset)
 		_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
-	RemapSpriteToSprite0(index);
+	RemapSpriteToPlaceholder(index);
 }
 
 Bitmap *SpriteCache::RemoveSprite(sprkey_t index) {
@@ -194,15 +193,15 @@ bool SpriteCache::SpriteData::DoesSpriteExist() const {
 }
 
 bool SpriteCache::SpriteData::IsAssetSprite() const {
-	return (Flags & SPRCACHEFLAG_ISASSET) != 0; // found in game resources
+	return (Flags & SPRCACHEFLAG_ISASSET) != 0;
 }
 
-bool SpriteCache::SpriteData::IsRemapped() const {
-	return (Flags & SPRCACHEFLAG_REMAP0) != 0; // was remapped to placeholder (sprite 0)
+bool SpriteCache::SpriteData::IsError() const {
+	return (Flags & SPRCACHEFLAG_ERROR) != 0;
 }
 
 bool SpriteCache::SpriteData::IsExternalSprite() const {
-	return (Flags & SPRCACHEFLAG_EXTERNAL) != 0; // assigned externally
+	return (Flags & SPRCACHEFLAG_EXTERNAL) != 0;
 }
 
 bool SpriteCache::SpriteData::IsLocked() const {
@@ -210,7 +209,7 @@ bool SpriteCache::SpriteData::IsLocked() const {
 }
 
 bool SpriteCache::DoesSpriteExist(sprkey_t index) const {
-	return index >= 0 && (size_t)index < _spriteData.size() &&  // in the valid range
+	return (index >= 0 && (size_t)index < _spriteData.size()) &&  // in the valid range
 		   _spriteData[index].IsValid();  // has assigned sprite
 }
 
@@ -218,26 +217,27 @@ Size SpriteCache::GetSpriteResolution(sprkey_t index) const {
 	return DoesSpriteExist(index) ? _sprInfos[index].GetResolution() : Size();
 }
 
-Bitmap *SpriteCache::operator [] (sprkey_t index) {
+Bitmap *SpriteCache::operator[](sprkey_t index) {
 	// invalid sprite slot
-	if (index < 0 || (size_t)index >= _spriteData.size())
-		return nullptr;
+	if (!DoesSpriteExist(index) || _spriteData[index].IsError())
+		return _placeholder.get();
 
 	// Externally added sprite or locked sprite, don't put it into MRU list
 	if (_spriteData[index].IsExternalSprite() || _spriteData[index].IsLocked())
 		return _spriteData[index].Image.get();
-	// Resolve potentially remapped sprites
-	index = GetDataIndex(index);
 	// Either use ready image, or load one from assets
 	if (_spriteData[index].Image) {
 		// Move to the beginning of the MRU list
 		_mru.splice(_mru.begin(), _mru, _spriteData[index].MruIt);
+		return _spriteData[index].Image.get();
 	} else {
 		// Sprite exists in file but is not in mem, load it and add to MRU list
-		LoadSprite(index);
-		_spriteData[index].MruIt = _mru.insert(_mru.begin(), index);
+		if (LoadSprite(index)) {
+			_spriteData[index].MruIt = _mru.insert(_mru.begin(), index);
+			return _spriteData[index].Image.get();
+		}
 	}
-	return _spriteData[index].Image.get();
+	return _placeholder.get();
 }
 
 void SpriteCache::FreeMem(size_t space) {
@@ -350,10 +350,6 @@ void SpriteCache::UnlockSprite(sprkey_t index) {
 	SprCacheLog("Unlocked %d", index);
 }
 
-sprkey_t SpriteCache::GetDataIndex(sprkey_t index) {
-	return (_spriteData[index].Flags & SPRCACHEFLAG_REMAP0) == 0 ? index : 0;
-}
-
 size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	assert((index >= 0) && ((size_t)index < _spriteData.size()));
 	if (index < 0 || (size_t)index >= _spriteData.size())
@@ -364,9 +360,9 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	HError err = _file.LoadSprite(index, image);
 	if (!image) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
-			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
+			"LoadSprite: failed to load sprite %d:\n%s\n - remapping to placeholder", index,
 			err ? "Sprite does not exist." : err->FullMessage().GetCStr());
-		RemapSpriteToSprite0(index);
+		RemapSpriteToPlaceholder(index);
 		return 0;
 	}
 
@@ -374,8 +370,8 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	image = _callbacks.InitSprite(index, image, _sprInfos[index].Flags);
 	if (!image) {
 		Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
-					  "LoadSprite: failed to initialize sprite %d, remapping to sprite 0.", index);
-		RemapSpriteToSprite0(index);
+					  "LoadSprite: failed to initialize sprite %d, remapping to placeholder", index);
+		RemapSpriteToPlaceholder(index);
 		return 0;
 	}
 
@@ -400,17 +396,11 @@ size_t SpriteCache::LoadSprite(sprkey_t index, bool lock) {
 	return size;
 }
 
-void SpriteCache::RemapSpriteToSprite0(sprkey_t index) {
+void SpriteCache::RemapSpriteToPlaceholder(sprkey_t index) {
 	assert((index > 0) && ((size_t)index < _spriteData.size()));
-	if (index <= 0)
-		return; // don't remap sprite 0 to itself
-
-	_sprInfos[index] = _sprInfos[0];
-
-//	_spriteData[index].Image.reset();
-	_spriteData[index].Size = _spriteData[0].Size;
-	_spriteData[index].Flags |= SPRCACHEFLAG_REMAP0;
-	SprCacheLog("RemapSpriteToSprite0: %d", index);
+	_sprInfos[index] = SpriteInfo(_placeholder->GetWidth(), _placeholder->GetHeight(), _placeholder->GetColorDepth());
+	_spriteData[index].Flags |= SPRCACHEFLAG_ERROR;
+	SprCacheLog("RemapSpriteToPlaceholder: %d", index);
 }
 
 void SpriteCache::InitNullSprite(sprkey_t index) {
diff --git a/engines/ags/shared/ac/sprite_cache.h b/engines/ags/shared/ac/sprite_cache.h
index 48652378e3b..997278497a9 100644
--- a/engines/ags/shared/ac/sprite_cache.h
+++ b/engines/ags/shared/ac/sprite_cache.h
@@ -96,7 +96,7 @@ public:
 	};
 
 	SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks);
-	~SpriteCache();
+	~SpriteCache() = default;
 
 	// Loads sprite reference information and inits sprite stream
 	HError      InitFile(const String &filename, const String &sprindex_filename);
@@ -163,7 +163,7 @@ public:
 	// *Deletes* the previous sprite if one was found at the same index.
 	// "flags" are SPF_* constants that define sprite's behavior in game.
 	bool        SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags = 0);
-	// Assigns new dummy for the given index, silently remapping it to sprite 0;
+	// Assigns new dummy for the given index, silently remapping it to placeholder;
 	// optionally marks it as an asset placeholder.
 	// *Deletes* the previous sprite if one was found at the same index.
 	void        SetEmptySprite(sprkey_t index, bool as_asset);
@@ -176,11 +176,8 @@ public:
 private:
 	// Load sprite from game resource
 	size_t      LoadSprite(sprkey_t index, bool lock = false);
-	// Remap the given index to the sprite 0
-	void        RemapSpriteToSprite0(sprkey_t index);
-	// Gets the index of a sprite which data is used for the given slot;
-	// in case of remapped sprite this will return the one given sprite is remapped to
-	sprkey_t    GetDataIndex(sprkey_t index);
+	// Remap the given index to the placeholder
+	void        RemapSpriteToPlaceholder(sprkey_t index);
 	// Delete the oldest (least recently used) image in cache
 	void        DisposeOldest();
 	// Keep disposing oldest elements until cache has at least the given free space
@@ -216,8 +213,8 @@ private:
 		bool DoesSpriteExist() const;
 		// Tells if there's a game resource corresponding to this slot
 		bool IsAssetSprite() const;
-		// Tells if a sprite is remapped to placeholder (e.g. failed to load)
-		bool IsRemapped() const;
+		// Tells if a sprite failed to load from assets, and should not be used
+		bool IsError() const;
 		// Tells if sprite was added externally, not loaded from game resources
 		bool IsExternalSprite() const;
 		// Tells if sprite is locked and should not be disposed by cache logic
@@ -228,6 +225,8 @@ private:
 	std::vector<SpriteInfo> &_sprInfos;
 	// Array of sprite references
 	std::vector<SpriteData> _spriteData;
+	// Placeholder sprite, returned from operator[] for a non-existing sprite
+	std::unique_ptr<Bitmap> _placeholder;
 
 	Callbacks _callbacks;
 	SpriteFile _file;


Commit: 7af9888de2a1aabc9351843e0873056bcd12a27d
    https://github.com/scummvm/scummvm/commit/7af9888de2a1aabc9351843e0873056bcd12a27d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: rewrote dynamic sprite change notification method

Previous existing method ran through virtually all the game objects that may reference a sprite, and
checked each one of them. Those who did reference the modified dynamic sprite were marked for
update and/or had their draw caches cleared.
Furthermore, for safety reasons, the Graphic properties of these objects were reset to 0.

This process was slow under two conditions:
- if there are alot of objects (especially overlays which may be created at runtime in large quantities);
- if there are alot of temporary dynamic sprites created and deleted per game frame (for example,
when drawing something complicated).

The new method:
1. We no longer reset object properties, this turned to be a bad design. There's another safety fix done
in the SpriteCache, which returns a placeholder sprite for any invalid sprite ID. This should make things
simpler and faster.
2. For the texture-based renderers, it turned out that updating the shared texture entries was enough
to make objects using them redraw. So for these renderers we don't need to reset any more caches.
3. For software renderer, introduced a GameState::spritemodified vector of bools. Whenever a sprite is
modified or deleted, engine sets a corresponding flag in this vector, and that triggers an update at the
next draw pass for all the objects that still reference that sprite.

GUI PROBLEM: because GUIs rendering is still different from other objects, even in texture render
mode, they are still marked for update the old way.
This should be addressed separately in the future.
>From upstream 0b84a21cfb486e3a9fa03e6c2b354750742ed3fd
and 3f70782761a698a69d31af77833c05bf38eb7394

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 03198bd90d8..3ddb888f7ae 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -405,6 +405,8 @@ void init_draw_method() {
 	init_room_drawdata();
 	if (_G(gfxDriver)->UsesMemoryBackBuffer())
 		_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
+
+	_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
 }
 
 void dispose_draw_method() {
@@ -479,6 +481,10 @@ void clear_drawobj_cache() {
 	for (auto &o : _GP(guiobjbg)) o = ObjTexture();
 	_GP(overtxs).clear();
 
+	// Clear "modified sprite" flags
+	_GP(play).spritemodifiedlist.clear();
+	Common::fill(_GP(play).spritemodified.begin(), _GP(play).spritemodified.end(), false);
+
 	dispose_debug_room_drawdata();
 }
 
@@ -634,30 +640,6 @@ void mark_object_changed(int objid) {
 	_G(objcache)[objid].y = -9999;
 }
 
-void reset_objcache_for_sprite(int sprnum, bool deleted) {
-	// Check if this sprite is assigned to any game object, and mark these for update;
-	// if the sprite was deleted, also mark texture objects as invalid.
-	// IMPORTANT!!: do NOT dispose textures themselves here.
-	// * if the next valid image is of the same size, then the texture will be reused;
-	// * BACKWARD COMPAT: keep last images during room transition out!
-	// room objects cache
-	if (_G(croom) != nullptr) {
-		for (size_t i = 0; i < (size_t)_G(croom)->numobj; ++i) {
-			if (_G(objcache)[i].sppic == sprnum)
-				_G(objcache)[i].sppic = -1;
-			if (deleted && ((int)(_GP(actsps)[i].SpriteID) == sprnum))
-				_GP(actsps)[i].SpriteID = UINT32_MAX; // invalid sprite ref
-		}
-	}
-	// character cache
-	for (size_t i = 0; i < (size_t)_GP(game).numcharacters; ++i) {
-		if (_GP(charcache)[i].sppic == sprnum)
-			_GP(charcache)[i].sppic = -1;
-		if (deleted && ((int)(_GP(actsps)[ACTSP_OBJSOFF + i].SpriteID) == sprnum))
-			_GP(actsps)[ACTSP_OBJSOFF + i].SpriteID = UINT32_MAX; // invalid sprite ref
-	}
-}
-
 void reset_drawobj_for_overlay(int objnum) {
 	if (objnum > 0 && static_cast<size_t>(objnum) < _GP(overtxs).size()) {
 		_GP(overtxs)[objnum] = ObjTexture();
@@ -666,6 +648,18 @@ void reset_drawobj_for_overlay(int objnum) {
 	}
 }
 
+void notify_sprite_changed(int sprnum, bool deleted) {
+	assert(sprnum >= 0 && sprnum < _GP(game).SpriteInfos.size());
+
+	// For software renderer we should notify drawables that currently
+	// reference this sprite.
+	if (drawstate.SoftwareRender) {
+		assert(sprnum < _GP(play).spritemodified.size());
+		_GP(play).spritemodified[sprnum] = true;
+		_GP(play).spritemodifiedlist.push_back(sprnum);
+	}
+}
+
 void mark_screen_dirty() {
 	drawstate.ScreenIsDirty = true;
 }
@@ -1284,6 +1278,8 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// If we have the image cached, use it
 	if ((objsav.image != nullptr) &&
 	        (objsav.sppic == specialpic) &&
+			// not a dynamic sprite, or not sprite modified lately
+			(!_GP(play).spritemodified[pic]) &&
 			(objsav.tintamnt == tint_level) &&
 			(objsav.tintlight == tint_light) &&
 			(objsav.tintr == tint_red) &&
@@ -1292,7 +1288,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 			(objsav.lightlev == light_level) &&
 			(objsav.zoom == objsrc.zoom) &&
 			(objsav.mirrored == is_mirrored)) {
-		// the image is the same, we can use it cached!
+		// if the image is the same, we can use it cached
 		if ((drawstate.WalkBehindMethod != DrawOverCharSprite) &&
 			(actsp.Bmp != nullptr))
 			return true;
@@ -2024,7 +2020,8 @@ static void construct_overlays() {
 		if (over.type < 0) continue; // empty slot
 		if (over.transparency == 255) continue; // skip fully transparent
 
-		bool has_changed = over.HasChanged();
+		auto &overtx = _GP(overtxs)[i];
+		bool has_changed = over.HasChanged() || _GP(play).spritemodified[over.GetSpriteNum()];
 		// If walk behinds are drawn over the cached object sprite, then check if positions were updated
 		if (crop_walkbehinds && over.IsRoomLayer()) {
 			Point pos = get_overlay_position(over);
@@ -2032,7 +2029,6 @@ static void construct_overlays() {
 			_GP(overcache)[i].X = pos.X; _GP(overcache)[i].Y = pos.Y;
 		}
 
-		auto &overtx = _GP(overtxs)[i];
 		if (has_changed) {
 			overtx.SpriteID = over.GetSpriteNum();
 			// For software mode - prepare transformed bitmap if necessary;
@@ -2063,6 +2059,19 @@ static void construct_overlays() {
 	}
 }
 
+static void reset_spritemodified() {
+	if (_GP(play).spritemodifiedlist.size() > 0) {
+		// Sort and remove duplicates;
+		// CHECKME: or is it more optimal to just run over raw list?
+		std::sort(_GP(play).spritemodifiedlist.begin(), _GP(play).spritemodifiedlist.end());
+		_GP(play).spritemodifiedlist.erase(std::unique(_GP(play).spritemodifiedlist.begin(), _GP(play).spritemodifiedlist.end()),
+										   _GP(play).spritemodifiedlist.end());
+		for (auto sprnum : _GP(play).spritemodifiedlist)
+			_GP(play).spritemodified[sprnum] = false;
+		_GP(play).spritemodifiedlist.clear();
+	}
+}
+
 void construct_game_scene(bool full_redraw) {
 	_G(gfxDriver)->ClearDrawLists();
 
@@ -2116,6 +2125,9 @@ void construct_game_scene(bool full_redraw) {
 
 	// End the parent scene node
 	_G(gfxDriver)->EndSpriteBatch();
+
+	// Clear "modified sprite" flags
+	reset_spritemodified();
 }
 
 void construct_game_screen_overlay(bool draw_mouse) {
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 4019bbbeced..da87ea0f51f 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -146,11 +146,11 @@ void detect_roomviewport_overlaps(size_t z_index);
 void on_roomcamera_changed(Camera *cam);
 // Marks particular object as need to update the texture
 void mark_object_changed(int objid);
-// Resets all object caches which reference this sprite
-void reset_objcache_for_sprite(int sprnum, bool deleted);
 // TODO: write a generic drawable/objcache system where each object
 // allocates a drawable for itself, and disposes one if being removed.
 void reset_drawobj_for_overlay(int objnum);
+// Marks all game objects which reference this sprite for redraw
+void notify_sprite_changed(int sprnum, bool deleted);
 
 // whether there are currently remnants of a DisplaySpeech
 void mark_screen_dirty();
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 2e11f920d38..f90db68293a 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -446,6 +446,8 @@ int add_dynamic_sprite(int slot, std::unique_ptr<Bitmap> image, bool has_alpha,
 	uint32_t flags = SPF_DYNAMICALLOC | (SPF_ALPHACHANNEL * has_alpha) | extra_flags;
 
 	_GP(spriteset).SetSprite(slot, std::move(image), flags);
+	if (_GP(play).spritemodified.size() < _GP(game).SpriteInfos.size())
+		_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
 	return slot;
 }
 
@@ -458,7 +460,7 @@ void free_dynamic_sprite(int slot, bool notify_all) {
 
 	_GP(spriteset).DisposeSprite(slot);
 	if (notify_all)
-		game_sprite_deleted(slot);
+		game_sprite_updated(slot, true);
 }
 
 //=============================================================================
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 579a7ebe859..fed38ff31c6 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1322,13 +1322,13 @@ void get_message_text(int msnum, char *buffer, char giveErr) {
 	replace_tokens(get_translation(_GP(thisroom).Messages[msnum].GetCStr()), buffer, maxlen);
 }
 
-void game_sprite_updated(int sprnum) {
-	// update the shared texture (if exists)
-	_G(gfxDriver)->UpdateSharedDDB(sprnum, _GP(spriteset)[sprnum], (_GP(game).SpriteInfos[sprnum].Flags & SPF_ALPHACHANNEL) != 0, false);
-
-	// character and object draw caches
-	reset_objcache_for_sprite(sprnum, false);
+void game_sprite_updated(int sprnum, bool deleted) {
+	// Notify draw system about dynamic sprite change
+	notify_sprite_changed(sprnum, deleted);
 
+	// GUI still have a special draw route, so cannot rely on object caches;
+	// will have to do a per-GUI and per-control check.
+	//
 	// gui backgrounds
 	for (auto &gui : _GP(guis)) {
 		if (gui.BgImage == sprnum) {
@@ -1347,12 +1347,6 @@ void game_sprite_updated(int sprnum) {
 			slider.MarkChanged();
 		}
 	}
-	// overlays
-	auto &overs = get_overlays();
-	for (auto &over : overs) {
-		if (over.GetSpriteNum() == sprnum)
-			over.MarkChanged();
-	}
 }
 
 void precache_view(int view, int first_loop, int last_loop, bool with_sounds) {
@@ -1385,77 +1379,6 @@ void precache_view(int view, int first_loop, int last_loop, bool with_sounds) {
 	Debug::Printf("\tSprite cache: %zu -> %zu KB", spcache_before / 1024u, spcache_after / 1024u);
 }
 
-void game_sprite_deleted(int sprnum) {
-	// clear from texture cache
-	_G(gfxDriver)->ClearSharedDDB(sprnum);
-	// character and object draw caches
-	reset_objcache_for_sprite(sprnum, true);
-
-	// This is ugly, but apparently there are few games that may rely
-	// (either with or without author's intent) on newly created sprite
-	// being assigned same index as a recently deleted one, which results
-	// in new sprite "secretly" taking place of an old one on the GUI, etc.
-	// So for old games we keep only partial reset (full cleanup is 3.5.0+).
-	const bool reset_sprindex_oldstyle =
-		_G(loaded_game_file_version) < kGameVersion_350;
-
-	// room object graphics
-	if (_G(croom) != nullptr) {
-		for (size_t i = 0; i < (size_t)_G(croom)->numobj; ++i) {
-			if (_G(objs)[i].num == sprnum)
-				_G(objs)[i].num = 0;
-		}
-	}
-	// gui buttons
-	for (auto &but : _GP(guibuts)) {
-		if (but.GetCurrentImage() == sprnum)
-			but.SetCurrentImage(0);
-		if (but.GetMouseOverImage() == sprnum)
-			but.SetMouseOverImage(0);
-		if (but.GetPushedImage() == sprnum)
-			but.SetPushedImage(0);
-
-		if (but.GetCurrentImage() == sprnum) {
-			but.SetCurrentImage(0);
-			but.MarkChanged();
-		}
-	}
-
-	if (reset_sprindex_oldstyle)
-		return; // stop here for < 3.5.0 games
-
-	// gui backgrounds
-	for (size_t i = 0; i < (size_t)_GP(game).numgui; ++i) {
-		if (_GP(guis)[i].BgImage == sprnum) {
-			_GP(guis)[i].BgImage = 0;
-			_GP(guis)[i].MarkChanged();
-		}
-	}
-	// gui sliders
-	for (auto &slider : _GP(guislider)) {
-		if ((slider.BgImage == sprnum) || (slider.HandleImage == sprnum))
-			slider.MarkChanged();
-		if (slider.BgImage == sprnum)
-			slider.BgImage = 0;
-		if (slider.HandleImage == sprnum)
-			slider.HandleImage = 0;
-	}
-	// views
-	for (size_t v = 0; v < (size_t)_GP(game).numviews; ++v) {
-		for (size_t l = 0; l < (size_t)_GP(views)[v].numLoops; ++l) {
-			for (size_t f = 0; f < (size_t)_GP(views)[v].loops[l].numFrames; ++f) {
-				if (_GP(views)[v].loops[l].frames[f].pic == sprnum)
-					_GP(views)[v].loops[l].frames[f].pic = 0;
-			}
-		}
-	}
-	// overlays
-	auto &overs = get_overlays();
-	for (auto &over : overs) {
-		if (over.GetSpriteNum() == sprnum)
-			over.SetSpriteNum(0);
-	}
-}
 
 //=============================================================================
 //
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index d43dfae6217..293b2575a75 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -210,13 +210,9 @@ void get_message_text(int msnum, char *buffer, char giveErr = 1);
 
 // Notifies the game objects that certain sprite was updated.
 // This make them update their render states, caches, and so on.
-void game_sprite_updated(int sprnum);
+void game_sprite_updated(int sprnum, bool deleted = false);
 // Precaches sprites for a view, within a selected range of loops.
 void precache_view(int view, int first_loop = 0, int last_loop = INT32_MAX, bool with_sounds = false);
-// Notifies the game objects that certain sprite was deleted.
-// Those which used that sprite will reset to dummy sprite 0, update their render states and caches.
-void game_sprite_deleted(int sprnum);
-
 
 extern void set_loop_counter(unsigned int new_counter);
 
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 2791c3979e5..5959ee9dbb4 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -300,7 +300,16 @@ struct GameState {
 	// Speech portrait overlay managed handle
 	int  speech_face_schandle = 0;
 
-	int shake_screen_yoff = 0; // y offset of the shaking screen
+	 // y offset of the shaking screen
+	int shake_screen_yoff = 0;
+
+	// CHECKME: we might consider hiding these in draw unit, but then,
+	// because the drawing system still has standalone parts (GUIs...)
+	// there still may be cases when we may require these accessed elsewhere.
+	// Sprite modified flag, used to detect dynamic sprite modifications
+	std::vector<bool> spritemodified;
+	// Which sprites were modified since the recent drawing pass
+	std::vector<int> spritemodifiedlist;
 
 
 	GameState();
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 83ea7266403..63aae83374a 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -302,9 +302,8 @@ void unload_old_room() {
 
 	croom_ptr_clear();
 
-	// clear the _GP(actsps) buffers to save memory, since the
-	// objects/characters involved probably aren't on the
-	// new screen. this also ensures all cached data is flushed
+	// clear the draw caches to save memory, since many of the the involved
+	// objects probably aren't on the new screen
 	clear_drawobj_cache();
 
 	// if Hide Player Character was ticked, restore it to visible
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 0c75920fda3..9f9a67df231 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -51,8 +51,6 @@ using namespace AGS::Shared;
 // For engine these are defined in ac.cpp
 extern void replace_macro_tokens(const char *, String &);
 
-// in ac_runningame
-
 
 bool GUIMain::HasAlphaChannel() const {
 	if (this->BgImage > 0) {
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 689ce7d768b..37dacd538d1 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -755,6 +755,8 @@ public:
 	std::vector<ScriptDialog> *_scrDialog;
 
 	std::vector<ViewStruct> *_views;
+
+	// Draw cache: keep record of all kinds of things related to the previous drawing state
 	// Cached character and object states, used to determine
 	// whether these require texture update
 	std::vector<ObjectCache> *_charcache;


Commit: efc70d1e1d2adf8d08ee59136eb78ce616cfaa41
    https://github.com/scummvm/scummvm/commit/efc70d1e1d2adf8d08ee59136eb78ce616cfaa41
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: mark portrait sprite updated, as it's now a dynamic sprite

This is necessary since overlays keep custom images as dynamic sprites too.
>From upstream b88167fd0ba608352482f9fd09e87e2c0246cdbc

Changed paths:
    engines/ags/engine/main/update.cpp


diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index e9bb2cc3444..58a33cf2934 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -27,6 +27,7 @@
 #include "ags/engine/ac/character.h"
 #include "ags/engine/ac/character_extras.h"
 #include "ags/engine/ac/draw.h"
+#include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_state.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/global_character.h"
@@ -37,6 +38,7 @@
 #include "ags/engine/ac/room_status.h"
 #include "ags/engine/main/update.h"
 #include "ags/engine/ac/screen_overlay.h"
+#include "ags/shared/ac/sprite_cache.h"
 #include "ags/engine/ac/view_frame.h"
 #include "ags/engine/ac/walkable_area.h"
 #include "ags/shared/gfx/bitmap.h"
@@ -435,7 +437,7 @@ void update_sierra_speech() {
 
 			auto *face_over = get_overlay(_G(face_talking));
 			assert(face_over != nullptr);
-			Bitmap *frame_pic = face_over->GetImage();
+			Bitmap *frame_pic = _GP(spriteset)[face_over->GetSpriteNum()];
 			if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
 				// QFG4-style fullscreen dialog
 				if (_G(facetalk_qfg4_override_placement_x)) {


Commit: 6dfd66be6588909b8ffb38c820f9f9d5741c230b
    https://github.com/scummvm/scummvm/commit/6dfd66be6588909b8ffb38c820f9f9d5741c230b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed Overlay bitmap order read from legacy saves

>From upstream 4bbc4228b64c76620e36ab53087eee13ff8c4a76

Changed paths:
    engines/ags/engine/game/savegame_v321.cpp


diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index d9a4868d606..a6f52a09932 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -278,8 +278,7 @@ static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
 	}
 }
 
-static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size_t num_overs) {
-	has_bitmap.resize(num_overs);
+static void ReadOverlays_Aligned(Stream *in, std::vector<int> &has_bitmap, size_t num_overs) {
 	// Remember that overlay indexes may be non-sequential
 	auto &overs = get_overlays();
 	for (size_t i = 0; i < num_overs; ++i) {
@@ -288,12 +287,11 @@ static void ReadOverlays_Aligned(Stream *in, std::vector<bool> &has_bitmap, size
 		over.ReadFromSavegame(in, has_bm, -1);
 		if (over.type < 0)
 			continue; // safety abort
-		if (overs.size() <= static_cast<uint32_t>(over.type)) {
+		if (overs.size() <= static_cast<uint32_t>(over.type))
 			overs.resize(over.type + 1);
-			has_bitmap.resize(over.type + 1);
-		}
 		overs[over.type] = std::move(over);
-		has_bitmap[over.type] = has_bm;
+		if (has_bm)
+			has_bitmap.push_back(over.type);
 	}
 }
 
@@ -303,11 +301,10 @@ static void restore_game_overlays(Stream *in, RestoredData &r_data) {
 	// the vector may be resized during read
 	auto &overs = get_overlays();
 	overs.resize(num_overs);
-	std::vector<bool> has_bitmap(num_overs);
+	std::vector<int> has_bitmap;
 	ReadOverlays_Aligned(in, has_bitmap, num_overs);
-	for (size_t i = 0; i < overs.size(); ++i) {
-		if (has_bitmap[i])
-			r_data.OverlayImages[i].reset(read_serialized_bitmap(in));
+	for (auto over_id : has_bitmap) {
+		r_data.OverlayImages[over_id].reset(read_serialized_bitmap(in));
 	}
 }
 


Commit: 102525446573fc7928bdcd89fe6990f6c4a4ee0c
    https://github.com/scummvm/scummvm/commit/102525446573fc7928bdcd89fe6990f6c4a4ee0c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix implicit warning

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


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index fed38ff31c6..67987259c04 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -918,13 +918,13 @@ std::unique_ptr<Shared::Bitmap> read_savedgame_screenshot(const String &savedgam
 	HSaveError err = OpenSavegame(savedgame, desc, kSvgDesc_UserImage);
 	if (!err) {
 		Debug::Printf(kDbgMsg_Error, "Unable to read save's screenshot.\n%s", err->FullMessage().GetCStr());
-		return {};
+		return nullptr;
 	}
 	if (desc.UserImage) {
 		desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false));
 		return std::move(desc.UserImage);
 	}
-	return {};
+	return nullptr;
 }
 
 


Commit: 7314dfc22ef0c70d5ed6ad7fd238e0f806834d5f
    https://github.com/scummvm/scummvm/commit/7314dfc22ef0c70d5ed6ad7fd238e0f806834d5f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix Screenoverlay private initializer error

Changed paths:
    engines/ags/engine/ac/screen_overlay.h


diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index 868beb8f4c1..5cfc9552391 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -94,6 +94,11 @@ struct ScreenOverlay {
 	~ScreenOverlay();
 	ScreenOverlay &operator=(ScreenOverlay &&);
 
+	// FIXME: These are private in the upstream repo,
+	// but Windows CI complains
+	ScreenOverlay(const ScreenOverlay &) = default;
+	ScreenOverlay &operator=(const ScreenOverlay &) = default;
+
 	bool HasAlphaChannel() const {
 		return (_flags & kOver_AlphaChannel) != 0;
 	}
@@ -146,8 +151,6 @@ struct ScreenOverlay {
 
 private:
 	void ResetImage();
-	ScreenOverlay(const ScreenOverlay &) = default;
-	ScreenOverlay &operator=(const ScreenOverlay &) = default;
 
 	int _flags = 0;  // OverlayFlags
 	int _sprnum = 0; // sprite id


Commit: 55d81cb237b13477b35a45f826b7422622fa14ed
    https://github.com/scummvm/scummvm/commit/55d81cb237b13477b35a45f826b7422622fa14ed
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: safeguard Overlay.Create in case of spritenum out of range

>From upstream ecc9737f29b7c0ceb809d2ee19ad2cc499d3cb76

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


diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index 9d75559796d..457cca93d92 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -78,6 +78,11 @@ void ScreenOverlay::SetImage(std::unique_ptr<Shared::Bitmap> pic, bool has_alpha
 
 void ScreenOverlay::SetSpriteNum(int sprnum, int offx, int offy) {
 	ResetImage();
+
+	assert(sprnum >= 0 && sprnum < _GP(game).SpriteInfos.size());
+	if (sprnum < 0 || sprnum >= _GP(game).SpriteInfos.size())
+		return;
+
 	_flags |= kOver_SpriteShared | kOver_AlphaChannel * ((_GP(game).SpriteInfos[sprnum].Flags & SPF_ALPHACHANNEL) != 0);
 	_sprnum = sprnum;
 	offsetX = offx;


Commit: 38d23b5159b59a3af6d6adbc98a31bbd09f7296a
    https://github.com/scummvm/scummvm/commit/38d23b5159b59a3af6d6adbc98a31bbd09f7296a
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed spritemodified array not init after RunAGSGame()

>From upstream ea108e9a01306641ec36128cb1ef5492766f2c32

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 3ddb888f7ae..33386d3dc6b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -405,8 +405,6 @@ void init_draw_method() {
 	init_room_drawdata();
 	if (_G(gfxDriver)->UsesMemoryBackBuffer())
 		_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
-
-	_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
 }
 
 void dispose_draw_method() {
@@ -435,6 +433,8 @@ void init_game_drawdata() {
 		guio_num += gui.GetControlCount();
 	}
 	_GP(guiobjbg).resize(guio_num);
+
+	_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
 }
 
 void dispose_game_drawdata() {
@@ -447,6 +447,9 @@ void dispose_game_drawdata() {
 	_GP(guibg).clear();
 	_GP(guiobjbg).clear();
 	_GP(guiobjddbref).clear();
+
+	_GP(play).spritemodified.clear();
+	_GP(play).spritemodifiedlist.clear();
 }
 
 static void dispose_debug_room_drawdata() {


Commit: 81130f543a8ecc4084e6ba8491dc0d25093e6a7e
    https://github.com/scummvm/scummvm/commit/81130f543a8ecc4084e6ba8491dc0d25093e6a7e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed software renderer closing with textual overlay on screen

>From upstream 466acb5ec274f4cef5f809a304c81e7922f95051

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 33386d3dc6b..d04b4535784 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -656,7 +656,7 @@ void notify_sprite_changed(int sprnum, bool deleted) {
 
 	// For software renderer we should notify drawables that currently
 	// reference this sprite.
-	if (drawstate.SoftwareRender) {
+	if (drawstate.SoftwareRender && !_GP(play).spritemodified.empty()) {
 		assert(sprnum < _GP(play).spritemodified.size());
 		_GP(play).spritemodified[sprnum] = true;
 		_GP(play).spritemodifiedlist.push_back(sprnum);


Commit: 92809e3114a1127e185ec360cf8ca85bc6d94c60
    https://github.com/scummvm/scummvm/commit/92809e3114a1127e185ec360cf8ca85bc6d94c60
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: rewrote dynamic sprite to texture notification mechanism

The previous attempt, using a boolean spritemodified array, proved to be having logical
loopholes. In simple words, because spritemodified flag was resetting each draw pass, it won't
affect objects that are temporarily not visible, and they failed to reupdate when becoming
visible again. This affected Software renderer.

Texture-based renderers have a different problem, where they fail to recognize when an old
sprite was deleted and a completely new dynamic sprite created accidentally with the same ID.
The cause was simply that the check for this was done inside sync_object_texture(), but
sync_object_texture itself was not called unless draw() function thought that object had
changed. So there has to be a check outside too...

Here I decided to reimplement notification mechanism, and use it for both types of renderers,
for the sake of more straightforward code logic. (Although, strictly speaking, texture-based
renderers could check their texture's "ref id" instead.)

The idea of a new implementation is this:
1. There's a map of "control blocks" per each active sprite (that is - sprite that was drawn as a
part of object). The "control block" is simply a integer with a sprite ID in it, allocated and
wrapped by shared_ptr.
2. When the ObjectTexture syncs its texture with a new sprite, the control block is retrieved (or
new created), and from now on it may be shared among multiple objects which display same
sprite.
3. When the sprite gets either updated or deleted two things happen: the previous control
block is assigned an "invalid value", serving as a marker, and then it is removed from the map,
left only if referenced by any ObjectTexture.
4. When checking whether texture needs to be re-synced we test the control block too, to see
if it was invalidated. In such case this tells us to call sync_object_texture() again.

NOTE that dynamic sprites that do not appear on screen, and are only used for drawing on
another surface, do not allocate control blocks, nor notify anything.

>From upstream ab2dd5846075a73e3a3415c45c37fe6b37805964

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/engine/ac/game_state.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index d04b4535784..dad702e545b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -90,6 +90,16 @@ struct DrawState {
 	WalkBehindMethodEnum WalkBehindMethod = DrawAsSeparateSprite;
 	// Whether there are currently remnants of a on-screen effect
 	bool ScreenIsDirty = false;
+
+	// A map of shared "control blocks" per each sprite used
+	// when preparing object textures. "Control block" is currently just
+	// an integer which lets to check whether the object texture is in sync
+	// with the sprite. When the dynamic sprite is updated or deleted,
+	// the control block is marked as invalid and removed from the map;
+	// but certain objects may keep the shared ptr to the old block with
+	// "invalid" mark, thus they know that they must reset their texture.
+	std::unordered_map<sprkey_t, std::shared_ptr<uint32_t> >
+		SpriteNotifyMap;
 };
 
 DrawState drawstate;
@@ -433,8 +443,6 @@ void init_game_drawdata() {
 		guio_num += gui.GetControlCount();
 	}
 	_GP(guiobjbg).resize(guio_num);
-
-	_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
 }
 
 void dispose_game_drawdata() {
@@ -447,9 +455,6 @@ void dispose_game_drawdata() {
 	_GP(guibg).clear();
 	_GP(guiobjbg).clear();
 	_GP(guiobjddbref).clear();
-
-	_GP(play).spritemodified.clear();
-	_GP(play).spritemodifiedlist.clear();
 }
 
 static void dispose_debug_room_drawdata() {
@@ -484,9 +489,8 @@ void clear_drawobj_cache() {
 	for (auto &o : _GP(guiobjbg)) o = ObjTexture();
 	_GP(overtxs).clear();
 
-	// Clear "modified sprite" flags
-	_GP(play).spritemodifiedlist.clear();
-	Common::fill(_GP(play).spritemodified.begin(), _GP(play).spritemodified.end(), false);
+	// Clear sprite update notification blocks
+	drawstate.SpriteNotifyMap.clear();
 
 	dispose_debug_room_drawdata();
 }
@@ -654,12 +658,17 @@ void reset_drawobj_for_overlay(int objnum) {
 void notify_sprite_changed(int sprnum, bool deleted) {
 	assert(sprnum >= 0 && sprnum < _GP(game).SpriteInfos.size());
 
-	// For software renderer we should notify drawables that currently
-	// reference this sprite.
-	if (drawstate.SoftwareRender && !_GP(play).spritemodified.empty()) {
-		assert(sprnum < _GP(play).spritemodified.size());
-		_GP(play).spritemodified[sprnum] = true;
-		_GP(play).spritemodifiedlist.push_back(sprnum);
+	// software renderer
+	// will need to know to redraw active cached sprite for objects.
+	// We have this notification for both kinds of renderers though,
+	// because it makes the code simpler, and also it makes it simpler to
+	// notify texture-based ones in a specific case when a deleted sprite
+	// was replaced by another of same ID.
+
+	auto it_notify = drawstate.SpriteNotifyMap.find(sprnum);
+	if (it_notify != drawstate.SpriteNotifyMap.end()) {
+		*it_notify->_value = UINT32_MAX;
+		drawstate.SpriteNotifyMap.erase(sprnum);
 	}
 }
 
@@ -847,6 +856,17 @@ Engine::IDriverDependantBitmap* recycle_ddb_sprite(Engine::IDriverDependantBitma
 static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool opaque = false) {
 	Bitmap *use_bmp = obj.Bmp.get() ? obj.Bmp.get() : _GP(spriteset)[obj.SpriteID];
 	obj.Ddb = recycle_ddb_sprite(obj.Ddb, obj.SpriteID, use_bmp, has_alpha, opaque);
+	// If this is a sprite-referencing texture, then assign a control block,
+	// let the object receive notification of sprite updates.
+	if ((obj.SpriteID != UINT32_MAX) && (!obj.IsSynced())) {
+		auto it_notify = drawstate.SpriteNotifyMap.find(obj.SpriteID);
+		if (it_notify != drawstate.SpriteNotifyMap.end()) { // assign existing
+			obj.SpriteNotify = it_notify->_value;
+		} else { // if does not exist, then create and share one
+			obj.SpriteNotify.reset(new (uint32_t)(obj.SpriteID));
+			drawstate.SpriteNotifyMap.insert(std::make_pair((int)obj.SpriteID, obj.SpriteNotify));
+		}
+	}
 }
 
 //------------------------------------------------------------------------
@@ -1257,7 +1277,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// Hardware accelerated mode: always use original sprite and apply texture transform
 	if (use_hw_transform) {
 		// HW acceleration
-		const bool is_texture_intact = objsav.sppic == specialpic;
+		const bool is_texture_intact = (objsav.sppic == specialpic) && actsp.IsSynced();
 		objsav.sppic = specialpic;
 		objsav.tintamnt = tint_level;
 		objsav.tintr = tint_red;
@@ -1282,7 +1302,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	if ((objsav.image != nullptr) &&
 	        (objsav.sppic == specialpic) &&
 			// not a dynamic sprite, or not sprite modified lately
-			(!_GP(play).spritemodified[pic]) &&
+			(actsp.IsSynced()) &&
 			(objsav.tintamnt == tint_level) &&
 			(objsav.tintlight == tint_light) &&
 			(objsav.tintr == tint_red) &&
@@ -2024,7 +2044,7 @@ static void construct_overlays() {
 		if (over.transparency == 255) continue; // skip fully transparent
 
 		auto &overtx = _GP(overtxs)[i];
-		bool has_changed = over.HasChanged() || _GP(play).spritemodified[over.GetSpriteNum()];
+		bool has_changed = over.HasChanged();
 		// If walk behinds are drawn over the cached object sprite, then check if positions were updated
 		if (crop_walkbehinds && over.IsRoomLayer()) {
 			Point pos = get_overlay_position(over);
@@ -2032,7 +2052,7 @@ static void construct_overlays() {
 			_GP(overcache)[i].X = pos.X; _GP(overcache)[i].Y = pos.Y;
 		}
 
-		if (has_changed) {
+		if (has_changed || !overtx.IsSynced()) {
 			overtx.SpriteID = over.GetSpriteNum();
 			// For software mode - prepare transformed bitmap if necessary;
 			// for hardware-accelerated - use the sprite ID if possible, to avoid redundant sprite load
@@ -2062,19 +2082,6 @@ static void construct_overlays() {
 	}
 }
 
-static void reset_spritemodified() {
-	if (_GP(play).spritemodifiedlist.size() > 0) {
-		// Sort and remove duplicates;
-		// CHECKME: or is it more optimal to just run over raw list?
-		std::sort(_GP(play).spritemodifiedlist.begin(), _GP(play).spritemodifiedlist.end());
-		_GP(play).spritemodifiedlist.erase(std::unique(_GP(play).spritemodifiedlist.begin(), _GP(play).spritemodifiedlist.end()),
-										   _GP(play).spritemodifiedlist.end());
-		for (auto sprnum : _GP(play).spritemodifiedlist)
-			_GP(play).spritemodified[sprnum] = false;
-		_GP(play).spritemodifiedlist.clear();
-	}
-}
-
 void construct_game_scene(bool full_redraw) {
 	_G(gfxDriver)->ClearDrawLists();
 
@@ -2128,9 +2135,6 @@ void construct_game_scene(bool full_redraw) {
 
 	// End the parent scene node
 	_G(gfxDriver)->EndSpriteBatch();
-
-	// Clear "modified sprite" flags
-	reset_spritemodified();
 }
 
 void construct_game_screen_overlay(bool draw_mouse) {
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index da87ea0f51f..f27ef296954 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -78,6 +78,9 @@ struct ObjTexture {
 	std::unique_ptr<Shared::Bitmap> Bmp;
 	// Corresponding texture, created by renderer
 	Engine::IDriverDependantBitmap *Ddb = nullptr;
+	// Sprite notification block: becomes invalid to notify an updated
+	// or deleted sprtie
+	std::shared_ptr<uint32_t> SpriteNotify;
 	// Sprite's position
 	Point Pos;
 	// Texture's offset, *relative* to the logical sprite's position;
@@ -92,6 +95,12 @@ struct ObjTexture {
 	~ObjTexture();
 
 	ObjTexture &operator =(ObjTexture &&o);
+
+	// Tests the sprite notification block to ensure that the texture
+	// is synchronized with the latest sprite version
+	inline bool IsSynced() const {
+		return SpriteNotify && (*SpriteNotify == SpriteID);
+	}
 };
 
 // ObjectCache stores cached object data, used to determine
diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index f90db68293a..43cf7aa013f 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -445,9 +445,8 @@ int add_dynamic_sprite(int slot, std::unique_ptr<Bitmap> image, bool has_alpha,
 
 	uint32_t flags = SPF_DYNAMICALLOC | (SPF_ALPHACHANNEL * has_alpha) | extra_flags;
 
-	_GP(spriteset).SetSprite(slot, std::move(image), flags);
-	if (_GP(play).spritemodified.size() < _GP(game).SpriteInfos.size())
-		_GP(play).spritemodified.resize(_GP(game).SpriteInfos.size());
+	if(!_GP(spriteset).SetSprite(slot, std::move(image), flags))
+		return 0; // failed to add the sprite, bad image or realloc failed
 	return slot;
 }
 
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 5959ee9dbb4..88de3a25741 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -303,14 +303,6 @@ struct GameState {
 	 // y offset of the shaking screen
 	int shake_screen_yoff = 0;
 
-	// CHECKME: we might consider hiding these in draw unit, but then,
-	// because the drawing system still has standalone parts (GUIs...)
-	// there still may be cases when we may require these accessed elsewhere.
-	// Sprite modified flag, used to detect dynamic sprite modifications
-	std::vector<bool> spritemodified;
-	// Which sprites were modified since the recent drawing pass
-	std::vector<int> spritemodifiedlist;
-
 
 	GameState();
 


Commit: a5831a36a3d0fe6e7556d4156e3cbe4911653f53
    https://github.com/scummvm/scummvm/commit/a5831a36a3d0fe6e7556d4156e3cbe4911653f53
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: only alloc sprite notifiers for dynamic sprites + add comment

>From upstream d1e10c554662108b2b2f0fa89dce4eb3a5526e69
and ee554032fe2183170df7c577f829dac1706e144e

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index dad702e545b..575caa8131f 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -98,6 +98,11 @@ struct DrawState {
 	// the control block is marked as invalid and removed from the map;
 	// but certain objects may keep the shared ptr to the old block with
 	// "invalid" mark, thus they know that they must reset their texture.
+	//
+	// TODO: investigate an alternative of having a equivalent of
+	// "shared texture" with sprite ID ref in Software renderer too,
+	// which would allow to use same method of testing DDB ID for both
+	// kinds of renderers, thus saving on 1 extra notification mechanism.
 	std::unordered_map<sprkey_t, std::shared_ptr<uint32_t> >
 		SpriteNotifyMap;
 };
@@ -856,16 +861,20 @@ Engine::IDriverDependantBitmap* recycle_ddb_sprite(Engine::IDriverDependantBitma
 static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool opaque = false) {
 	Bitmap *use_bmp = obj.Bmp.get() ? obj.Bmp.get() : _GP(spriteset)[obj.SpriteID];
 	obj.Ddb = recycle_ddb_sprite(obj.Ddb, obj.SpriteID, use_bmp, has_alpha, opaque);
-	// If this is a sprite-referencing texture, then assign a control block,
-	// let the object receive notification of sprite updates.
-	if ((obj.SpriteID != UINT32_MAX) && (!obj.IsSynced())) {
-		auto it_notify = drawstate.SpriteNotifyMap.find(obj.SpriteID);
-		if (it_notify != drawstate.SpriteNotifyMap.end()) { // assign existing
-			obj.SpriteNotify = it_notify->_value;
+	// Handle notification control block for the dynamic sprites
+	if ((obj.SpriteID != UINT32_MAX) && _GP(game).SpriteInfos[obj.SpriteID].IsDynamicSprite()) {
+		// For dynamic sprite: check and update a notification block for this drawable
+		if (!obj.SpriteNotify || (*obj.SpriteNotify != obj.SpriteID)) {
+			auto it_notify = drawstate.SpriteNotifyMap.find(obj.SpriteID);
+			if (it_notify != drawstate.SpriteNotifyMap.end()) { // assign existing
+				obj.SpriteNotify = it_notify->_value;
+			}
 		} else { // if does not exist, then create and share one
 			obj.SpriteNotify.reset(new (uint32_t)(obj.SpriteID));
 			drawstate.SpriteNotifyMap.insert(std::make_pair((int)obj.SpriteID, obj.SpriteNotify));
 		}
+	} else {
+		obj.SpriteNotify = nullptr; // reset, for static sprite or without ID
 	}
 }
 
@@ -1277,7 +1286,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// Hardware accelerated mode: always use original sprite and apply texture transform
 	if (use_hw_transform) {
 		// HW acceleration
-		const bool is_texture_intact = (objsav.sppic == specialpic) && actsp.IsSynced();
+		const bool is_texture_intact = (objsav.sppic == specialpic) && !actsp.IsChangeNotified();
 		objsav.sppic = specialpic;
 		objsav.tintamnt = tint_level;
 		objsav.tintr = tint_red;
@@ -1302,7 +1311,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	if ((objsav.image != nullptr) &&
 	        (objsav.sppic == specialpic) &&
 			// not a dynamic sprite, or not sprite modified lately
-			(actsp.IsSynced()) &&
+			(!actsp.IsChangeNotified()) &&
 			(objsav.tintamnt == tint_level) &&
 			(objsav.tintlight == tint_light) &&
 			(objsav.tintr == tint_red) &&
@@ -2052,7 +2061,7 @@ static void construct_overlays() {
 			_GP(overcache)[i].X = pos.X; _GP(overcache)[i].Y = pos.Y;
 		}
 
-		if (has_changed || !overtx.IsSynced()) {
+		if (has_changed || overtx.IsChangeNotified()) {
 			overtx.SpriteID = over.GetSpriteNum();
 			// For software mode - prepare transformed bitmap if necessary;
 			// for hardware-accelerated - use the sprite ID if possible, to avoid redundant sprite load
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index f27ef296954..fa208581109 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -96,10 +96,9 @@ struct ObjTexture {
 
 	ObjTexture &operator =(ObjTexture &&o);
 
-	// Tests the sprite notification block to ensure that the texture
-	// is synchronized with the latest sprite version
-	inline bool IsSynced() const {
-		return SpriteNotify && (*SpriteNotify == SpriteID);
+	// Tests if the sprite change was notified
+	inline bool IsChangeNotified() const {
+		return SpriteNotify && (*SpriteNotify != SpriteID);
 	}
 };
 
diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 4c93133f792..770e1d08ac6 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -242,6 +242,8 @@ struct SpriteInfo {
 	SpriteInfo(int w, int h, uint32_t flags) : Width(w), Height(h), Flags(flags) {}
 
 	inline Size GetResolution() const { return Size(Width, Height); }
+	// Gets if sprite is created at runtime (by engine, or a script command)
+	inline bool IsDynamicSprite() const { return (Flags & SPF_DYNAMICALLOC) != 0; }
 
 	//
 	// Legacy game support


Commit: a5223d249b2a78542045df0b6e0f6014e74e28fe
    https://github.com/scummvm/scummvm/commit/a5223d249b2a78542045df0b6e0f6014e74e28fe
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.23 P1)

Partially from upstream edef7314f3f9ff92140360a85c2c2faf015c3d3f

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 1b23c05e1e1..e9d5418b433 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.22"
+#define ACI_VERSION_STR      "3.6.1.23"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.22
+#define ACI_VERSION_MSRC_DEF  3.6.1.23
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 6efd1cc41ee9b4bd5fa531feddd68ee8736a0c99
    https://github.com/scummvm/scummvm/commit/6efd1cc41ee9b4bd5fa531feddd68ee8736a0c99
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: calculate real TTF glyphs extent, fix fonts cut from the top

This is an attempt to fix certain fonts getting "cut off" from the top when
being drawn on a limited size texture or bitmap.

The reason for such behavior is that fonts may include glyphs which
topmost extent is higher than the reported "ascender".
In this case the engine simply does not know that glyphs may be drawn
higher, and the prepared texture is not accommodated for that.

How we fix this:
1. Following a caller's instruction (a new flag ALFONT_FLG_PRECALC_MAX_CBOX),
alfont preloads all glyphs one by one and records the maximal extents of their "control boxes".
These values are saved and returned on demand.
2. Our FontMetrics struct has a new VExtent field, that keeps these values (converted to top-down
 Y axis) for the reference.
3. get_font_surface_height() now returns a surface height calculated from these extent values.
4. CalcFontGraphicalVExtent() now uses the extent values when telling how the text will be
graphically positioned, relative to the requested "text position".

>From upstream ec362d744f7e7c0630d68a92219f2129fec7ab3d

Changed paths:
    engines/ags/lib/alfont/alfont.cpp
    engines/ags/lib/alfont/alfont.h
    engines/ags/shared/font/ags_font_renderer.h
    engines/ags/shared/font/fonts.cpp
    engines/ags/shared/font/fonts.h
    engines/ags/shared/font/ttf_font_renderer.cpp
    engines/ags/shared/gui/gui_main.cpp


diff --git a/engines/ags/lib/alfont/alfont.cpp b/engines/ags/lib/alfont/alfont.cpp
index 0eadd2e6b8b..5b1ad15c8df 100644
--- a/engines/ags/lib/alfont/alfont.cpp
+++ b/engines/ags/lib/alfont/alfont.cpp
@@ -94,6 +94,8 @@ struct ALFONT_FONT {
 	int face_h;             /* face height */
 	int real_face_h;        /* real face height */
 	int face_ascender;      /* face ascender */
+	int real_face_extent_asc;  /* calculated max extent of glyphs (ascender) */
+	int real_face_extent_desc; /* calculated max extent of glyphs (descender) */
 	char *data;             /* if loaded from memory, the data chunk */
 	int data_size;          /* and its size */
 	int ch_spacing;         /* extra spacing */
@@ -498,6 +500,27 @@ static void _alfont_new_cache_glyph(ALFONT_FONT *f) {
 	}
 }
 
+static void _alfont_calculate_max_cbox(ALFONT_FONT *f) {
+	int i;
+	int max_box_top = 0, min_box_bottom = 0;
+	FT_Glyph glyph;
+	FT_BBox box;
+	for (i = 0; i < f->face->num_glyphs; i++) {
+		// CHECKME: is FT_LOAD_DEFAULT optimal here? there are various load modes
+		FT_Load_Glyph(f->face, i, FT_LOAD_DEFAULT);
+		FT_Get_Glyph(f->face->glyph, &glyph);
+		FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &box);
+		if (max_box_top < box.yMax) {
+			max_box_top = box.yMax;
+		}
+		if (min_box_bottom > box.yMin) {
+			min_box_bottom = box.yMin;
+		}
+		FT_Done_Glyph(glyph);
+	}
+	f->real_face_extent_asc = max_box_top;
+	f->real_face_extent_desc = -min_box_bottom;
+}
 
 /* API */
 
@@ -572,7 +595,11 @@ int alfont_set_font_size_ex(ALFONT_FONT *f, int h, int flags) {
 		f->real_face_h = real_height;
 		f->face_ascender = f->face->size->metrics.ascender >> 6;
 
-		// AGS COMPAT HACK: set ascender to the formal font height
+		/* Precalculate actual glyphs vertical extent */
+		if ((flags & ALFONT_FLG_PRECALC_MAX_CBOX) != 0) {
+			_alfont_calculate_max_cbox(f);
+		}
+		/* AGS COMPAT HACK: set ascender to the formal font height */
 		if ((flags & ALFONT_FLG_ASCENDER_EQ_HEIGHT) != 0) {
 			f->face_ascender = test_h;
 			f->real_face_h = test_h + abs(f->face->size->metrics.descender >> 6);
@@ -595,6 +622,11 @@ int alfont_get_font_real_height(ALFONT_FONT *f) {
 	return f->real_face_h;
 }
 
+ALFONT_DLL_DECLSPEC void alfont_get_font_real_vextent(ALFONT_FONT *f, int *top, int *bottom) {
+	*top = f->face_ascender - f->real_face_extent_asc; // may be negative
+	*bottom = f->face_ascender + f->real_face_extent_desc;
+}
+
 void alfont_exit(void) {
 	if (alfont_inited) {
 		alfont_inited = 0;
diff --git a/engines/ags/lib/alfont/alfont.h b/engines/ags/lib/alfont/alfont.h
index 3de23919ac2..850a4c78baa 100644
--- a/engines/ags/lib/alfont/alfont.h
+++ b/engines/ags/lib/alfont/alfont.h
@@ -51,6 +51,10 @@ namespace AGS3 {
 // otherwise will search for the point size which results in pixel
 // height closest to the requested size.
 #define ALFONT_FLG_SELECT_NOMINAL_SZ  0x04
+// Precalculate maximal glyph control box, that is maximal graphical
+// extent of any glyph in the font (which may exceed font's height).
+// Note that this requires FreeType to load each glyph one by one.
+#define ALFONT_FLG_PRECALC_MAX_CBOX   0x08
 
 /* structs */
 typedef struct ALFONT_FONT ALFONT_FONT;
@@ -72,6 +76,8 @@ ALFONT_DLL_DECLSPEC int alfont_set_font_size_ex(ALFONT_FONT *f, int h, int flags
 ALFONT_DLL_DECLSPEC int alfont_get_font_height(ALFONT_FONT *f);
 /* Returns the real font graphical height */
 ALFONT_DLL_DECLSPEC int alfont_get_font_real_height(ALFONT_FONT *f);
+/* Returns the real font graphical extent (top, bottom) */
+ALFONT_DLL_DECLSPEC void alfont_get_font_real_vextent(ALFONT_FONT *f, int *top, int *bottom);
 
 ALFONT_DLL_DECLSPEC int alfont_text_mode(int mode);
 
diff --git a/engines/ags/shared/font/ags_font_renderer.h b/engines/ags/shared/font/ags_font_renderer.h
index 3ee7a1f4c58..a17472eade0 100644
--- a/engines/ags/shared/font/ags_font_renderer.h
+++ b/engines/ags/shared/font/ags_font_renderer.h
@@ -22,6 +22,10 @@
 #ifndef AGS_SHARED_FONT_AGS_FONT_RENDERER_H
 #define AGS_SHARED_FONT_AGS_FONT_RENDERER_H
 
+#include "common/std/utility.h"
+#include "ags/shared/core/types.h"
+
+
 namespace AGS3 {
 
 class BITMAP;
@@ -76,9 +80,24 @@ struct FontRenderParams {
 
 // Describes loaded font's properties
 struct FontMetrics {
-	int Height = 0; // formal font height value
-	int RealHeight = 0; // real graphical height of a font
-	int CompatHeight = 0; // either formal or real height, depending on compat settings
+	// Nominal font's height, equals to the game-requested size of the font.
+	// This may or not be equal to font's face height; sometimes a font cannot
+	// be scaled exactly to particular size, and then nominal height appears different
+	// (usually - smaller) than the real one.
+	int NominalHeight = 0;
+	// Real font's height, equals to reported ascender + descender.
+	// This is what you normally think as a font's height.
+	int RealHeight = 0;
+	// Compatible height, equals to either NominalHeight or RealHeight,
+	// selected depending on the game settings.
+	// This property is used in calculating linespace, etc.
+	int CompatHeight = 0;
+	// Maximal vertical extent of a font (top; bottom), this tells the actual
+	// graphical bounds that may be occupied by font's glyphs.
+	// In a "proper" font this extent is (0; RealHeight-1), but "bad" fonts may
+	// have individual glyphs exceeding these bounds, in both directions.
+	// Note that "top" may be negative!
+	std::pair<int, int> VExtent;
 };
 
 // The strictly internal font renderer interface, not to use in plugin API.
diff --git a/engines/ags/shared/font/fonts.cpp b/engines/ags/shared/font/fonts.cpp
index 092e2573bd8..0affbd88838 100644
--- a/engines/ags/shared/font/fonts.cpp
+++ b/engines/ags/shared/font/fonts.cpp
@@ -77,7 +77,7 @@ static void font_post_init(size_t fontNumber) {
 	Font &font = _GP(fonts)[fontNumber];
 	// If no font height property was provided, then try several methods,
 	// depending on which interface is available
-	if (font.Metrics.Height == 0 && font.Renderer) {
+	if (font.Metrics.NominalHeight == 0 && font.Renderer) {
 		int height = 0;
 		if (font.Renderer2)
 			height = font.Renderer2->GetFontHeight(fontNumber);
@@ -90,13 +90,14 @@ static void font_post_init(size_t fontNumber) {
 			height = font.Renderer->GetTextHeight(height_test_string, fontNumber);
 		}
 
-		font.Metrics.Height = std::max(0, height);
-		font.Metrics.RealHeight = font.Metrics.Height;
+		font.Metrics.NominalHeight = std::max(0, height);
+		font.Metrics.RealHeight = font.Metrics.NominalHeight;
+		font.Metrics.VExtent = std::make_pair(0, font.Metrics.RealHeight);
 	}
 	// Use either nominal or real pixel height to define font's logical height
 	// and default linespacing; logical height = nominal height is compatible with the old games
 	font.Metrics.CompatHeight = (font.Info.Flags & FFLG_REPORTNOMINALHEIGHT) != 0 ?
-		font.Metrics.Height : font.Metrics.RealHeight;
+		font.Metrics.NominalHeight : font.Metrics.RealHeight;
 
 	if (font.Info.Outline != FONT_OUTLINE_AUTO) {
 		font.Info.AutoOutlineThickness = 0;
@@ -264,7 +265,13 @@ int get_font_height_outlined(size_t fontNumber) {
 int get_font_surface_height(size_t fontNumber) {
 	if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
 		return 0;
-	return _GP(fonts)[fontNumber].Metrics.RealHeight;
+	return _GP(fonts)[fontNumber].Metrics.VExtent.second - _GP(fonts)[fontNumber].Metrics.VExtent.first;
+}
+
+std::pair<int, int> get_font_surface_extent(size_t fontNumber) {
+	if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
+		return std::make_pair(0, 0);
+	return _GP(fonts)[fontNumber].Metrics.VExtent;
 }
 
 int get_font_linespacing(size_t fontNumber) {
diff --git a/engines/ags/shared/font/fonts.h b/engines/ags/shared/font/fonts.h
index 57de3595bfb..6cb82fc2071 100644
--- a/engines/ags/shared/font/fonts.h
+++ b/engines/ags/shared/font/fonts.h
@@ -101,6 +101,10 @@ int get_font_height_outlined(size_t fontNumber);
 // Get font's surface height: this always returns the height enough to accommodate
 // font letters on a bitmap or a texture; the distinction is needed for compatibility reasons
 int get_font_surface_height(size_t fontNumber);
+// Get font's maximal graphical extent: this means the farthest vertical positions of glyphs,
+// relative to the "pen" position. Besides letting to calculate the surface height,
+// this information also lets to detect if some of the glyphs may appear above y0.
+std::pair<int, int> get_font_surface_extent(size_t fontNumber);
 // Get font's line spacing
 int get_font_linespacing(size_t fontNumber);
 // Set font's line spacing
diff --git a/engines/ags/shared/font/ttf_font_renderer.cpp b/engines/ags/shared/font/ttf_font_renderer.cpp
index a137d4f90ac..713db81c768 100644
--- a/engines/ags/shared/font/ttf_font_renderer.cpp
+++ b/engines/ags/shared/font/ttf_font_renderer.cpp
@@ -82,6 +82,8 @@ static int GetAlfontFlags(int load_mode) {
 	if (((load_mode & FFLG_ASCENDERFIXUP) != 0) &&
 		!(ShouldAntiAliasText() && (_G(loaded_game_file_version) < kGameVersion_341)))
 		flags |= ALFONT_FLG_ASCENDER_EQ_HEIGHT;
+	// Precalculate real glyphs extent (will make loading fonts relatively slower)
+	flags |= ALFONT_FLG_PRECALC_MAX_CBOX;
 	return flags;
 }
 
@@ -105,9 +107,13 @@ static ALFONT_FONT *LoadTTF(const String &filename, int fontSize, int alfont_fla
 
 // Fill the FontMetrics struct from the given ALFONT
 static void FillMetrics(ALFONT_FONT *alfptr, FontMetrics *metrics) {
-	metrics->Height = alfont_get_font_height(alfptr);
+	metrics->NominalHeight  = alfont_get_font_height(alfptr);
 	metrics->RealHeight = alfont_get_font_real_height(alfptr);
-	metrics->CompatHeight = metrics->Height; // just set to default here
+	metrics->CompatHeight = metrics->NominalHeight; // just set to default here
+	alfont_get_font_real_vextent(alfptr, &metrics->VExtent.first, &metrics->VExtent.second);
+	// fixup vextent to be *not less* than realheight
+	metrics->VExtent.first = std::min(0, metrics->VExtent.first);
+	metrics->VExtent.second = std::max(metrics->RealHeight, metrics->VExtent.second);
 }
 
 bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 875c0c227aa..56b82f0e181 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -657,12 +657,13 @@ GuiVersion GameGuiVersion = kGuiVersion_Initial;
 
 Line CalcFontGraphicalVExtent(int font) {
 	// Following factors are affecting the graphical vertical metrics:
+	// * font's real graphical extent (top and bottom offsets relative to the "pen")
 	// * custom vertical offset set by user (if non-zero),
-	// * font's real graphical height
-	int font_yoffset = get_fontinfo(font).YOffset;
-	int yoff = std::min(0, font_yoffset);       // only if yoff is negative
-	int height_off = std::max(0, font_yoffset); // only if yoff is positive
-	return Line(0, yoff, 0, get_font_surface_height(font) + height_off);
+	const auto finfo = get_fontinfo(font);
+	const auto fextent = get_font_surface_extent(font);
+	int top = fextent.first + std::min(0, finfo.YOffset);     // apply YOffset only if negative
+	int bottom = fextent.second + std::max(0, finfo.YOffset); // apply YOffset only if positive
+	return Line(0, top, 0, bottom);
 }
 
 Point CalcTextPosition(const char *text, int font, const Rect &frame, FrameAlignment align, Rect *gr_rect) {


Commit: a8b50fed68279201bffa6c4aa9053d0776bf9c9b
    https://github.com/scummvm/scummvm/commit/a8b50fed68279201bffa6c4aa9053d0776bf9c9b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: print loaded font metrics to log

>From upstream 7ab815c0591d57c401ec54d6de68c78a9648c479

Changed paths:
    engines/ags/shared/font/ags_font_renderer.h
    engines/ags/shared/font/fonts.cpp
    engines/ags/shared/font/ttf_font_renderer.cpp
    engines/ags/shared/font/ttf_font_renderer.h
    engines/ags/shared/font/wfn_font_renderer.cpp
    engines/ags/shared/font/wfn_font_renderer.h


diff --git a/engines/ags/shared/font/ags_font_renderer.h b/engines/ags/shared/font/ags_font_renderer.h
index a17472eade0..2e6448fd455 100644
--- a/engines/ags/shared/font/ags_font_renderer.h
+++ b/engines/ags/shared/font/ags_font_renderer.h
@@ -24,7 +24,7 @@
 
 #include "common/std/utility.h"
 #include "ags/shared/core/types.h"
-
+#include "ags/shared/util/string.h"
 
 namespace AGS3 {
 
@@ -107,8 +107,8 @@ public:
 	// Tells if this is a bitmap font (otherwise it's a vector font)
 	virtual bool IsBitmapFont() = 0;
 	// Load font, applying extended font rendering parameters
-	virtual bool LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params,
-		FontMetrics *metrics) = 0;
+	virtual bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
+								const FontRenderParams *params, FontMetrics *metrics) = 0;
 	// Fill FontMetrics struct; note that it may be left cleared if this is not supported
 	virtual void GetFontMetrics(int fontNumber, FontMetrics *metrics) = 0;
 	// Perform any necessary adjustments when the AA mode is toggled
diff --git a/engines/ags/shared/font/fonts.cpp b/engines/ags/shared/font/fonts.cpp
index 0affbd88838..56f6e113bbc 100644
--- a/engines/ags/shared/font/fonts.cpp
+++ b/engines/ags/shared/font/fonts.cpp
@@ -24,6 +24,7 @@
 #include "common/std/vector.h"
 #include "ags/shared/ac/common.h" // set_our_eip
 #include "ags/shared/ac/game_struct_defines.h"
+#include "ags/shared/debugging/out.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/font/ttf_font_renderer.h"
 #include "ags/shared/font/wfn_font_renderer.h"
@@ -465,22 +466,28 @@ bool load_font_size(size_t fontNumber, const FontInfo &font_info) {
 	params.LoadMode = (font_info.Flags & FFLG_LOADMODEMASK);
 	FontMetrics metrics;
 
-	if (_GP(ttfRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &params, &metrics)) {
-		_GP(fonts)[fontNumber].Renderer = &_GP(ttfRenderer);
-		_GP(fonts)[fontNumber].Renderer2 = &_GP(ttfRenderer);
-		_GP(fonts)[fontNumber].RendererInt = &_GP(ttfRenderer);
-	} else if (_GP(wfnRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &params, &metrics)) {
-		_GP(fonts)[fontNumber].Renderer = &_GP(wfnRenderer);
-		_GP(fonts)[fontNumber].Renderer2 = &_GP(wfnRenderer);
-		_GP(fonts)[fontNumber].RendererInt = &_GP(wfnRenderer);
+	Font &font = _GP(fonts)[fontNumber];
+	String src_filename;
+	if (_GP(ttfRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
+		font.Renderer = &_GP(ttfRenderer);
+		font.Renderer2 = &_GP(ttfRenderer);
+		font.RendererInt = &_GP(ttfRenderer);
+	} else if (_GP(wfnRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
+		font.Renderer = &_GP(wfnRenderer);
+		font.Renderer2 = &_GP(wfnRenderer);
+		font.RendererInt = &_GP(wfnRenderer);
 	}
 
-	if (!_GP(fonts)[fontNumber].Renderer)
+	if (!font.Renderer)
 		return false;
 
-	_GP(fonts)[fontNumber].Info = font_info;
-	_GP(fonts)[fontNumber].Metrics = metrics;
+	font.Info = font_info;
+	font.Metrics = metrics;
 	font_post_init(fontNumber);
+
+	Debug::Printf("Loaded font %d: %s, req size: %d; nominal h: %d, real h: %d, extent: %d,%d",
+				  fontNumber, src_filename.GetCStr(), font_info.Size, font.Metrics.NominalHeight, font.Metrics.RealHeight,
+				  font.Metrics.VExtent.first, font.Metrics.VExtent.second);
 	return true;
 }
 
diff --git a/engines/ags/shared/font/ttf_font_renderer.cpp b/engines/ags/shared/font/ttf_font_renderer.cpp
index 713db81c768..fca453078f5 100644
--- a/engines/ags/shared/font/ttf_font_renderer.cpp
+++ b/engines/ags/shared/font/ttf_font_renderer.cpp
@@ -67,7 +67,7 @@ void TTFFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *desti
 }
 
 bool TTFFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
-	return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr);
+	return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
 }
 
 bool TTFFontRenderer::IsBitmapFont() {
@@ -116,8 +116,8 @@ static void FillMetrics(ALFONT_FONT *alfptr, FontMetrics *metrics) {
 	metrics->VExtent.second = std::max(metrics->RealHeight, metrics->VExtent.second);
 }
 
-bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
-	const FontRenderParams *params, FontMetrics *metrics) {
+bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize, String *src_filename,
+									 const FontRenderParams *params, FontMetrics *metrics) {
 	String filename = String::FromFormat("agsfnt%d.ttf", fontNumber);
 	if (fontSize <= 0)
 		fontSize = 8; // compatibility fix
@@ -133,6 +133,8 @@ bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
 
 	_fontData[fontNumber].AlFont = alfptr;
 	_fontData[fontNumber].Params = f_params;
+	if (src_filename)
+		*src_filename = filename;
 	if (metrics)
 		FillMetrics(alfptr, metrics);
 	return true;
diff --git a/engines/ags/shared/font/ttf_font_renderer.h b/engines/ags/shared/font/ttf_font_renderer.h
index 7611d64df5d..90cfb6b71c5 100644
--- a/engines/ags/shared/font/ttf_font_renderer.h
+++ b/engines/ags/shared/font/ttf_font_renderer.h
@@ -55,8 +55,8 @@ public:
 
 	// IAGSFontRendererInternal implementation
 	bool IsBitmapFont() override;
-	bool LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params,
-		FontMetrics *metrics) override;
+	bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
+						const FontRenderParams *params, FontMetrics *metrics) override;
 	void GetFontMetrics(int fontNumber, FontMetrics *metrics) override;
 	void AdjustFontForAntiAlias(int fontNumber, bool aa_mode) override;
 
diff --git a/engines/ags/shared/font/wfn_font_renderer.cpp b/engines/ags/shared/font/wfn_font_renderer.cpp
index 00e94918592..9dfff34c5c8 100644
--- a/engines/ags/shared/font/wfn_font_renderer.cpp
+++ b/engines/ags/shared/font/wfn_font_renderer.cpp
@@ -109,15 +109,15 @@ static int RenderChar(Bitmap *ds, const int at_x, const int at_y, Rect clip,
 }
 
 bool WFNFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
-	return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr);
+	return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
 }
 
 bool WFNFontRenderer::IsBitmapFont() {
 	return true;
 }
 
-bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
-		const FontRenderParams *params, FontMetrics *metrics) {
+bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int /*fontSize*/, String *src_filename,
+									 const FontRenderParams *params, FontMetrics *metrics) {
 	String file_name;
 	Stream *ffi = nullptr;
 
@@ -125,6 +125,8 @@ bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
 	ffi = _GP(AssetMgr)->OpenAsset(file_name);
 	if (ffi == nullptr) {
 		// actual font not found, try font 0 instead
+		// FIXME: this should not be done here in this font renderer implementation,
+		// but somewhere outside, when whoever calls this method
 		file_name = "agsfnt0.wfn";
 		ffi = _GP(AssetMgr)->OpenAsset(file_name);
 		if (ffi == nullptr)
@@ -142,6 +144,8 @@ bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize,
 	}
 	_fontData[fontNumber].Font = font;
 	_fontData[fontNumber].Params = params ? *params : FontRenderParams();
+	if (src_filename)
+		*src_filename = file_name;
 	if (metrics)
 		*metrics = FontMetrics();
 	return true;
diff --git a/engines/ags/shared/font/wfn_font_renderer.h b/engines/ags/shared/font/wfn_font_renderer.h
index 4518bc49a31..7f48eb377b6 100644
--- a/engines/ags/shared/font/wfn_font_renderer.h
+++ b/engines/ags/shared/font/wfn_font_renderer.h
@@ -53,8 +53,8 @@ public:
 
 	// IAGSFontRendererInternal implementation
 	bool IsBitmapFont() override;
-	bool LoadFromDiskEx(int fontNumber, int fontSize,
-		const FontRenderParams *params, FontMetrics *metrics) override;
+	bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
+						const FontRenderParams *params, FontMetrics *metrics) override;
 	void GetFontMetrics(int fontNumber, FontMetrics *metrics) override { *metrics = FontMetrics(); }
 	void AdjustFontForAntiAlias(int /*fontNumber*/, bool /*aa_mode*/) override { /* do nothing */ }
 


Commit: fcde33269f39fbdc7b089b587b45a6618385270c
    https://github.com/scummvm/scummvm/commit/fcde33269f39fbdc7b089b587b45a6618385270c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed auto outline for the fonts with abnormal extents

>From upstream 307c15797c3c094507a281f39041ca5524313388

Changed paths:
    engines/ags/engine/ac/display.cpp
    engines/ags/shared/font/ags_font_renderer.h
    engines/ags/shared/font/fonts.cpp


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index a69154f9830..419a6e1ac37 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -485,19 +485,22 @@ void wouttextxy_AutoOutline(Bitmap *ds, size_t font, int32_t color, const char *
 	// WORKAROUND: Clifftop's Spritefont plugin returns a wrong font height for font 2 in Kathy Rain, which causes a partial outline
 	// for some letters. Unfortunately fixing the value on the plugin side breaks the line spacing, so let's just correct it here.
 	size_t const t_width = get_text_width(texx, font);
-	size_t const t_height = get_font_surface_height(font) + ((strcmp(_GP(game).guid, "{d6795d1c-3cfe-49ec-90a1-85c313bfccaf}") == 0) && (font == 2) ? 1 : 0);
+	const auto t_extent = get_font_surface_extent(font);
+	size_t const t_height = t_extent.second - t_extent.first + ((strcmp(_GP(game).guid, "{d6795d1c-3cfe-49ec-90a1-85c313bfccaf}") == 0) && (font == 2) ? 1 : 0);
 
 	if (t_width == 0 || t_height == 0)
 		return;
 
 	// Prepare stencils
+	size_t const t_yoff = t_extent.first;
 	Bitmap *texx_stencil, *outline_stencil;
 	alloc_font_outline_buffers(font, &texx_stencil, &outline_stencil,
 		t_width, t_height, stencil_cd);
 	texx_stencil->ClearTransparent();
 	outline_stencil->ClearTransparent();
 	// Ready text stencil
-	wouttextxy(texx_stencil, 0, 0, font, color, texx);
+	// Note we are drawing with y off, in case some font's glyphs exceed font's ascender
+	wouttextxy(texx_stencil, 0, -t_yoff, font, color, texx);
 
 	// Anti-aliased TTFs require to be alpha-blended, not blit,
 	// or the alpha values will be plain copied and final image will be broken.
@@ -511,7 +514,7 @@ void wouttextxy_AutoOutline(Bitmap *ds, size_t font, int32_t color, const char *
 
 	// move start of text so that the outline doesn't drop off the bitmap
 	xxp += thickness;
-	int const outline_y = yyp;
+	int const outline_y = yyp + t_yoff;
 	yyp += thickness;
 
 	// What we do here: first we paint text onto outline_stencil offsetting vertically;
diff --git a/engines/ags/shared/font/ags_font_renderer.h b/engines/ags/shared/font/ags_font_renderer.h
index 2e6448fd455..60a8a57a320 100644
--- a/engines/ags/shared/font/ags_font_renderer.h
+++ b/engines/ags/shared/font/ags_font_renderer.h
@@ -98,6 +98,8 @@ struct FontMetrics {
 	// have individual glyphs exceeding these bounds, in both directions.
 	// Note that "top" may be negative!
 	std::pair<int, int> VExtent;
+
+	inline int ExtentHeight() const { return VExtent.second - VExtent.first; }
 };
 
 // The strictly internal font renderer interface, not to use in plugin API.
diff --git a/engines/ags/shared/font/fonts.cpp b/engines/ags/shared/font/fonts.cpp
index 56f6e113bbc..b1a785d9971 100644
--- a/engines/ags/shared/font/fonts.cpp
+++ b/engines/ags/shared/font/fonts.cpp
@@ -266,7 +266,7 @@ int get_font_height_outlined(size_t fontNumber) {
 int get_font_surface_height(size_t fontNumber) {
 	if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
 		return 0;
-	return _GP(fonts)[fontNumber].Metrics.VExtent.second - _GP(fonts)[fontNumber].Metrics.VExtent.first;
+	return _GP(fonts)[fontNumber].Metrics.ExtentHeight();
 }
 
 std::pair<int, int> get_font_surface_extent(size_t fontNumber) {


Commit: b9879cb9c4d4f9f1288cb63aef3c3a253d74d92c
    https://github.com/scummvm/scummvm/commit/b9879cb9c4d4f9f1288cb63aef3c3a253d74d92c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: made FPS overlay react on font changes and abnormal fonts

>From upstream 39649ad98e749a9f14909444df7d827305aa0039

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 575caa8131f..10333874de8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -450,6 +450,8 @@ void init_game_drawdata() {
 	_GP(guiobjbg).resize(guio_num);
 }
 
+extern void dispose_engine_overlay();
+
 void dispose_game_drawdata() {
 	clear_drawobj_cache();
 
@@ -460,6 +462,8 @@ void dispose_game_drawdata() {
 	_GP(guibg).clear();
 	_GP(guiobjbg).clear();
 	_GP(guiobjddbref).clear();
+
+	dispose_engine_overlay();
 }
 
 static void dispose_debug_room_drawdata() {
@@ -1732,18 +1736,30 @@ PBitmap draw_room_background(Viewport *view) {
 	return _GP(CameraDrawData)[view_index].Frame;
 }
 
+struct DrawFPS {
+	IDriverDependantBitmap *ddb = nullptr;
+	std::unique_ptr<Bitmap> bmp;
+	int font = -1; // in case normal font changes at runtime
+} gl_DrawFPS;
+
+void dispose_engine_overlay() {
+	gl_DrawFPS.bmp.reset();
+	if (gl_DrawFPS.ddb)
+		_G(gfxDriver)->DestroyDDB(gl_DrawFPS.ddb);
+	gl_DrawFPS.ddb = nullptr;
+	gl_DrawFPS.font = -1;
+}
+
 void draw_fps(const Rect &viewport) {
-	// TODO: make allocated "fps struct" instead of using static vars!!
-	static IDriverDependantBitmap *ddb = nullptr;
-	static Bitmap *fpsDisplay = nullptr;
 	const int font = FONT_NORMAL;
-	if (fpsDisplay == nullptr) {
-		fpsDisplay = CreateCompatBitmap(viewport.GetWidth(), (get_font_surface_height(font) + get_fixed_pixel_size(5)));
+	auto &fpsDisplay = gl_DrawFPS.bmp;
+	if (fpsDisplay == nullptr || gl_DrawFPS.font != font) {
+		recycle_bitmap(fpsDisplay, _GP(game).GetColorDepth(), viewport.GetWidth(), (get_font_surface_height(font) + get_fixed_pixel_size(5)));
+		gl_DrawFPS.font = font;
 	}
-	fpsDisplay->ClearTransparent();
-
-	color_t text_color = fpsDisplay->GetCompatibleColor(14);
 
+	fpsDisplay->ClearTransparent();
+	const color_t text_color = fpsDisplay->GetCompatibleColor(14);
 	char base_buffer[20];
 	if (!isTimerFpsMaxed()) {
 		snprintf(base_buffer, sizeof(base_buffer), "%d", _G(frames_per_second));
@@ -1759,19 +1775,16 @@ void draw_fps(const Rect &viewport) {
 	} else {
 		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: --.- / %s", base_buffer);
 	}
-	wouttext_outline(fpsDisplay, 1, 1, font, text_color, fps_buffer);
-
 	char loop_buffer[60];
 	snprintf(loop_buffer, sizeof(loop_buffer), "Loop %u", _G(loopcounter));
-	wouttext_outline(fpsDisplay, viewport.GetWidth() / 2, 1, font, text_color, loop_buffer);
 
-	if (ddb)
-		_G(gfxDriver)->UpdateDDBFromBitmap(ddb, fpsDisplay, false);
-	else
-		ddb = _G(gfxDriver)->CreateDDBFromBitmap(fpsDisplay, false);
+	int text_off = get_font_surface_extent(font).first; // TODO: a generic function that accounts for this?
+	wouttext_outline(fpsDisplay.get(), 1, 1 - text_off, font, text_color, fps_buffer);
+	wouttext_outline(fpsDisplay.get(), viewport.GetWidth() / 2, 1 - text_off, font, text_color, loop_buffer);
+	gl_DrawFPS.ddb = recycle_ddb_bitmap(gl_DrawFPS.ddb, gl_DrawFPS.bmp.get());
 	int yp = viewport.GetHeight() - fpsDisplay->GetHeight();
-	_G(gfxDriver)->DrawSprite(1, yp, ddb);
-	invalidate_sprite_glob(1, yp, ddb);
+	_G(gfxDriver)->DrawSprite(1, yp, gl_DrawFPS.ddb);
+	invalidate_sprite_glob(1, yp, gl_DrawFPS.ddb);
 }
 
 // Draw GUI controls as separate sprites


Commit: c8ddd881ae6b9c807865d5019a8870f1eb5be926
    https://github.com/scummvm/scummvm/commit/c8ddd881ae6b9c807865d5019a8870f1eb5be926
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hotfix buffer usage when printing room debug info

>From upstream 2e2e1578d60d482341574dc741a5c51aa5ce8dc6

Changed paths:
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index a2d79ba29ca..250ad0df317 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -339,49 +339,46 @@ bool run_service_key_controls(KeyInput &out_key) {
 		return false;
 	}
 
+	// FIXME: review this command! - practically inconvenient
 	if ((agskey == eAGSKeyCodeCtrlD) && (_GP(play).debug_mode > 0)) {
 		// ctrl+D - show info
-		char infobuf[900];
-		size_t ln = 0;
-		ln += Common::sprintf_s(infobuf, "In room %d %s[Player at %d, %d (view %d, loop %d, frame %d)%s%s%s",
-		        _G(displayed_room), (_G(noWalkBehindsAtAll) ? "(has no walk-behinds)" : ""), _G(playerchar)->x, _G(playerchar)->y,
-		        _G(playerchar)->view + 1, _G(playerchar)->loop, _G(playerchar)->frame,
-		        (IsGamePaused() == 0) ? "" : "[Game paused.",
-		        (_GP(play).ground_level_areas_disabled == 0) ? "" : "[Ground areas disabled.",
-		        (IsInterfaceEnabled() == 0) ? "[Game in Wait state" : "");
+		String buffer = String::FromFormat("In room %d %s[Player at %d, %d (view %d, loop %d, frame %d)%s%s%s",
+										   _G(displayed_room), (_G(noWalkBehindsAtAll) ? "(has no walk-behinds)" : ""),
+										   _G(playerchar)->x, _G(playerchar)->y,
+										   _G(playerchar)->view + 1, _G(playerchar)->loop, _G(playerchar)->frame,
+										   (IsGamePaused() == 0) ? "" : "[Game paused.",
+										   (_GP(play).ground_level_areas_disabled == 0) ? "" : "[Ground areas disabled.",
+										   (IsInterfaceEnabled() == 0) ? "[Game in Wait state" : "");
 		for (uint32_t ff = 0; ff < _G(croom)->numobj; ff++) {
-			if (ff >= 8) break; // buffer not big enough for more than 7
-			ln += Common::sprintf_s(&infobuf[ln], sizeof(infobuf) - ln,
-			        "[Object %d: (%d,%d) size (%d x %d) on:%d moving:%s animating:%d slot:%d trnsp:%d clkble:%d",
-			        ff, _G(objs)[ff].x, _G(objs)[ff].y,
-			        (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Width : 0),
-			        (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Height : 0),
-			        _G(objs)[ff].on,
-			        (_G(objs)[ff].moving > 0) ? "yes" : "no", _G(objs)[ff].cycling,
-			        _G(objs)[ff].num, _G(objs)[ff].transparent,
-			        ((_G(objs)[ff].flags & OBJF_NOINTERACT) != 0) ? 0 : 1);
+			if (ff >= 8) break; // FIXME: measure graphical size instead?
+			buffer.AppendFmt("[Object %d: (%d,%d) size (%d x %d) on:%d moving:%s animating:%d slot:%d trnsp:%d clkble:%d",
+							 ff, _G(objs)[ff].x, _G(objs)[ff].y,
+							 (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Width : 0),
+							 (_GP(spriteset).DoesSpriteExist(_G(objs)[ff].num) ? _GP(game).SpriteInfos[_G(objs)[ff].num].Height : 0),
+							 _G(objs)[ff].on,
+							 (_G(objs)[ff].moving > 0) ? "yes" : "no", _G(objs)[ff].cycling,
+							 _G(objs)[ff].num, _G(objs)[ff].transparent,
+							 ((_G(objs)[ff].flags & OBJF_NOINTERACT) != 0) ? 0 : 1);
 		}
-		DisplayMB(infobuf);
+		DisplayMB(buffer.GetCStr());
 		int chd = _GP(game).playercharacter;
-		char bigbuffer[STD_BUFFER_SIZE] = "CHARACTERS IN THIS ROOM:[";
+		buffer = "CHARACTERS IN THIS ROOM:[";
 		for (int ff = 0; ff < _GP(game).numcharacters; ff++) {
-			if (_GP(game).chars[ff].room != _G(displayed_room)) continue;
-			if (strlen(bigbuffer) > 430) {
-				Common::strcat_s(bigbuffer, "and more...");
-				DisplayMB(bigbuffer);
-				Common::strcpy_s(bigbuffer, "CHARACTERS IN THIS ROOM (cont'd):[");
+			if (_GP(game).chars[ff].room != _G(displayed_room))	continue;
+			if (buffer.GetLength() > 430) { // FIXME: why 430? measure graphical size instead?
+				buffer.Append("and more...");
+				DisplayMB(buffer.GetCStr());
+				buffer = "CHARACTERS IN THIS ROOM (cont'd):[";
 			}
 			chd = ff;
-			ln = strlen(bigbuffer);
-			Common::sprintf_s(&bigbuffer[ln], sizeof(bigbuffer) - ln,
-			        "%s (view/loop/frm:%d,%d,%d  x/y/z:%d,%d,%d  idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[",
-			        _GP(game).chars[chd].scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
-			        _GP(game).chars[chd].x, _GP(game).chars[chd].y, _GP(game).chars[chd].z,
-			        _GP(game).chars[chd].idleview, _GP(game).chars[chd].idletime, _GP(game).chars[chd].idleleft,
-			        _GP(game).chars[chd].walking, _GP(game).chars[chd].animating, _GP(game).chars[chd].following,
-			        _GP(game).chars[chd].flags, _GP(game).chars[chd].wait, _GP(charextra)[chd].zoom);
+			buffer.AppendFmt("%s (view/loop/frm:%d,%d,%d  x/y/z:%d,%d,%d  idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[",
+							 _GP(game).chars[chd].scrname, _GP(game).chars[chd].view + 1, _GP(game).chars[chd].loop, _GP(game).chars[chd].frame,
+							 _GP(game).chars[chd].x, _GP(game).chars[chd].y, _GP(game).chars[chd].z,
+							 _GP(game).chars[chd].idleview, _GP(game).chars[chd].idletime, _GP(game).chars[chd].idleleft,
+							 _GP(game).chars[chd].walking, _GP(game).chars[chd].animating, _GP(game).chars[chd].following,
+							 _GP(game).chars[chd].flags, _GP(game).chars[chd].wait, _GP(charextra)[chd].zoom);
 		}
-		DisplayMB(bigbuffer);
+		DisplayMB(buffer.GetCStr());
 		return false;
 	}
 


Commit: 6f20bee7529c6d5819bbe536e2d4d29f63076df8
    https://github.com/scummvm/scummvm/commit/6f20bee7529c6d5819bbe536e2d4d29f63076df8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hotfix buffer size check in InputBox()

>From upstream 2e282b99e92e60cd1eabe1777b053bdc593a69e9

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/global_api.cpp
    engines/ags/engine/ac/global_game.cpp
    engines/ags/engine/ac/global_game.h
    engines/ags/engine/gui/gui_dialog.cpp
    engines/ags/engine/gui/gui_dialog.h
    engines/ags/plugins/core/global_api.cpp
    engines/ags/plugins/core/global_api.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 67987259c04..1b5d6150954 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -602,7 +602,7 @@ int Game_GetColorFromRGB(int red, int grn, int blu) {
 
 const char *Game_InputBox(const char *msg) {
 	char buffer[STD_BUFFER_SIZE];
-	sc_inputbox(msg, buffer);
+	ShowInputBoxImpl(msg, buffer, STD_BUFFER_SIZE);
 	return CreateNewScriptString(buffer);
 }
 
diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp
index 5c92f650f81..cfefe49438a 100644
--- a/engines/ags/engine/ac/global_api.cpp
+++ b/engines/ags/engine/ac/global_api.cpp
@@ -742,8 +742,8 @@ RuntimeScriptValue Sc_HideMouseCursor(const RuntimeScriptValue *params, int32_t
 }
 
 // void (const char*msg,char*bufr)
-RuntimeScriptValue Sc_sc_inputbox(const RuntimeScriptValue *params, int32_t param_count) {
-	API_SCALL_VOID_POBJ2(sc_inputbox, const char, char);
+RuntimeScriptValue Sc_ShowInputBox(const RuntimeScriptValue *params, int32_t param_count) {
+	API_SCALL_VOID_POBJ2(ShowInputBox, const char, char);
 }
 
 // void (int ifn)
@@ -2028,7 +2028,7 @@ void RegisterGlobalAPI() {
 		{"GiveScore", API_FN_PAIR(GiveScore)},
 		{"HasPlayerBeenInRoom", API_FN_PAIR(HasPlayerBeenInRoom)},
 		{"HideMouseCursor", API_FN_PAIR(HideMouseCursor)},
-		{"InputBox", API_FN_PAIR(sc_inputbox)},
+		{"InputBox", API_FN_PAIR(ShowInputBox)},
 		{"InterfaceOff", API_FN_PAIR(InterfaceOff)},
 		{"InterfaceOn", API_FN_PAIR(InterfaceOn)},
 		{"IntToFloat", API_FN_PAIR(IntToFloat)},
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index be726781a2f..dd1a5a048c6 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -528,10 +528,14 @@ int EndCutscene() {
 	return retval;
 }
 
-void sc_inputbox(const char *msg, char *bufr) {
+void ShowInputBox(const char *msg, char *bufr) {
 	VALIDATE_STRING(bufr);
+	ShowInputBoxImpl(msg, bufr, MAX_MAXSTRLEN);
+}
+
+void ShowInputBoxImpl(const char *msg, char *bufr, size_t buf_len) {
 	setup_for_dialog();
-	enterstringwindow(get_translation(msg), bufr);
+	enterstringwindow(get_translation(msg), bufr, buf_len);
 	restore_after_dialog();
 }
 
diff --git a/engines/ags/engine/ac/global_game.h b/engines/ags/engine/ac/global_game.h
index 3fb06debd3b..e7455bced41 100644
--- a/engines/ags/engine/ac/global_game.h
+++ b/engines/ags/engine/ac/global_game.h
@@ -81,7 +81,9 @@ int EndCutscene();
 // Tell the game to skip current cutscene
 void SkipCutscene();
 
-void sc_inputbox(const char *msg, char *bufr);
+// ShowInputBox assumes a string buffer of MAX_MAXSTRLEN
+void ShowInputBox(const char *msg, char *bufr);
+void ShowInputBoxImpl(const char *msg, char *bufr, size_t buf_len);
 
 int GetLocationType(int xxx, int yyy);
 void SaveCursorForLocationChange();
diff --git a/engines/ags/engine/gui/gui_dialog.cpp b/engines/ags/engine/gui/gui_dialog.cpp
index 91d731d2cff..57d429463e7 100644
--- a/engines/ags/engine/gui/gui_dialog.cpp
+++ b/engines/ags/engine/gui/gui_dialog.cpp
@@ -297,7 +297,7 @@ void preparesavegamelist(int ctrllist) {
 	CSCISendControlMessage(ctrllist, CLB_SETCURSEL, 0, 0);
 }
 
-void enterstringwindow(const char *prompttext, char *stouse) {
+void enterstringwindow(const char *prompttext, char *dst_buf, size_t dst_sz) {
 	const int wnd_width = 200;
 	const int wnd_height = 40;
 	const int boxleft = 60, boxtop = 80;
@@ -333,17 +333,12 @@ void enterstringwindow(const char *prompttext, char *stouse) {
 	if (wantCancel)
 		CSCIDeleteControl(ctrlcancel);
 	CSCIEraseWindow(handl);
-	/* FIXME: Function should take a length parameter
-	 * It is called with a 200 bytes buffer below
-	 * but also called with a STD_BUFFER_SIZE (3000) buffer
-	 * and undetermined size buffer in the API
-	 * Using STD_BUFFER_SIZE as we don't want to break too much stuff */
-	Common::strcpy_s(stouse, STD_BUFFER_SIZE, _G(buffer2));
+	snprintf(dst_buf, dst_sz, "%s", _G(buffer2));
 }
 
 int enternumberwindow(char *prompttext) {
 	char ourbuf[200];
-	enterstringwindow(prompttext, ourbuf);
+	enterstringwindow(prompttext, ourbuf, sizeof(ourbuf));
 	if (ourbuf[0] == 0)
 		return -9999;
 	return atoi(ourbuf);
diff --git a/engines/ags/engine/gui/gui_dialog.h b/engines/ags/engine/gui/gui_dialog.h
index dc8e7eca00e..597f3544dea 100644
--- a/engines/ags/engine/gui/gui_dialog.h
+++ b/engines/ags/engine/gui/gui_dialog.h
@@ -45,7 +45,7 @@ void refresh_gui_screen();
 int  loadgamedialog();
 int  savegamedialog();
 void preparesavegamelist(int ctrllist);
-void enterstringwindow(const char *prompttext, char *stouse);
+void enterstringwindow(const char *prompttext, char *dst_buf, size_t dst_sz);
 int  enternumberwindow(char *prompttext);
 int  roomSelectorWindow(int currentRoom, int numRooms,
 	const std::vector<int> &roomNumbers, const std::vector<AGS::Shared::String> &roomNames);
diff --git a/engines/ags/plugins/core/global_api.cpp b/engines/ags/plugins/core/global_api.cpp
index df30f696ec4..04db255f983 100644
--- a/engines/ags/plugins/core/global_api.cpp
+++ b/engines/ags/plugins/core/global_api.cpp
@@ -207,7 +207,7 @@ void GlobalAPI::AGS_EngineStartup(IAGSEngine *engine) {
 	SCRIPT_METHOD(GiveScore, GlobalAPI::GiveScore);
 	SCRIPT_METHOD(HasPlayerBeenInRoom, GlobalAPI::HasPlayerBeenInRoom);
 	SCRIPT_METHOD(HideMouseCursor, GlobalAPI::HideMouseCursor);
-	SCRIPT_METHOD(InputBox, GlobalAPI::sc_inputbox);
+	SCRIPT_METHOD(InputBox, GlobalAPI::ShowInputBox);
 	SCRIPT_METHOD(InterfaceOff, GlobalAPI::InterfaceOff);
 	SCRIPT_METHOD(InterfaceOn, GlobalAPI::InterfaceOn);
 	SCRIPT_METHOD(IntToFloat, GlobalAPI::IntToFloat);
@@ -1078,9 +1078,9 @@ void GlobalAPI::HideMouseCursor(ScriptMethodParams &params) {
 	AGS3::HideMouseCursor();
 }
 
-void GlobalAPI::sc_inputbox(ScriptMethodParams &params) {
+void GlobalAPI::ShowInputBox(ScriptMethodParams &params) {
 	PARAMS2(const char *, msg, char *, bufr);
-	AGS3::sc_inputbox(msg, bufr);
+	AGS3::ShowInputBox(msg, bufr);
 }
 
 void GlobalAPI::InterfaceOff(ScriptMethodParams &params) {
diff --git a/engines/ags/plugins/core/global_api.h b/engines/ags/plugins/core/global_api.h
index b8a327422f8..12bcc87d8d4 100644
--- a/engines/ags/plugins/core/global_api.h
+++ b/engines/ags/plugins/core/global_api.h
@@ -163,7 +163,7 @@ public:
 	void GiveScore(ScriptMethodParams &params);
 	void HasPlayerBeenInRoom(ScriptMethodParams &params);
 	void HideMouseCursor(ScriptMethodParams &params);
-	void sc_inputbox(ScriptMethodParams &params);
+	void ShowInputBox(ScriptMethodParams &params);
 	void InterfaceOff(ScriptMethodParams &params);
 	void InterfaceOn(ScriptMethodParams &params);
 	void IntToFloat(ScriptMethodParams &params);


Commit: 7f1ad613d5f5e3067d6088e10a67c32a121bed52
    https://github.com/scummvm/scummvm/commit/7f1ad613d5f5e3067d6088e10a67c32a121bed52
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: added comments around remaining uses of STD_BUFFER_SIZE

>From upstream 67a1cd06e115472d80e8ef546a7a134385571b8c

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h
    engines/ags/engine/ac/global_game.h
    engines/ags/engine/ac/global_translation.h
    engines/ags/engine/ac/room.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 1b5d6150954..343cedb34da 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -608,7 +608,7 @@ const char *Game_InputBox(const char *msg) {
 
 const char *Game_GetLocationName(int x, int y) {
 	char buffer[STD_BUFFER_SIZE];
-	GetLocationName(x, y, buffer);
+	GetLocationName(x, y, buffer); // fills up to MAX_MAXSTRLEN
 	return CreateNewScriptString(buffer);
 }
 
@@ -617,7 +617,6 @@ const char *Game_GetGlobalMessages(int index) {
 		return nullptr;
 	}
 	char buffer[STD_BUFFER_SIZE];
-	buffer[0] = 0;
 	replace_tokens(get_translation(get_global_message(index)), buffer, STD_BUFFER_SIZE);
 	return CreateNewScriptString(buffer);
 }
@@ -631,7 +630,7 @@ int Game_GetNormalFont() {
 
 const char *Game_GetTranslationFilename() {
 	char buffer[STD_BUFFER_SIZE];
-	GetTranslationName(buffer);
+	GetTranslationName(buffer); // fills up to MAX_MAXSTRLEN
 	return CreateNewScriptString(buffer);
 }
 
@@ -1247,7 +1246,7 @@ void display_switch_in_resume() {
 	_G(game_update_suspend)--;
 }
 
-void replace_tokens(const char *srcmes, char *destm, int maxlen) {
+void replace_tokens(const char *srcmes, char *destm, size_t maxlen) {
 	int indxdest = 0, indxsrc = 0;
 	const char *srcp;
 	char *destp;
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index 293b2575a75..acd41732a6b 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -204,7 +204,7 @@ void display_switch_out_suspend();
 // Called when the game gets input focus and should resume
 void display_switch_in_resume();
 
-void replace_tokens(const char *srcmes, char *destm, int maxlen = 99999);
+void replace_tokens(const char *srcmes, char *destm, size_t maxlen);
 const char *get_global_message(int msnum);
 void get_message_text(int msnum, char *buffer, char giveErr = 1);
 
diff --git a/engines/ags/engine/ac/global_game.h b/engines/ags/engine/ac/global_game.h
index e7455bced41..a8c3edf619f 100644
--- a/engines/ags/engine/ac/global_game.h
+++ b/engines/ags/engine/ac/global_game.h
@@ -87,7 +87,8 @@ void ShowInputBoxImpl(const char *msg, char *bufr, size_t buf_len);
 
 int GetLocationType(int xxx, int yyy);
 void SaveCursorForLocationChange();
-void GetLocationName(int xxx, int yyy, char *tempo);
+// GetLocationName assumes a string buffer of MAX_MAXSTRLEN
+void GetLocationName(int xxx, int yyy, char *buf);
 
 int IsKeyPressed(int keycode);
 
diff --git a/engines/ags/engine/ac/global_translation.h b/engines/ags/engine/ac/global_translation.h
index 3272a1290a5..dbfa0014989 100644
--- a/engines/ags/engine/ac/global_translation.h
+++ b/engines/ags/engine/ac/global_translation.h
@@ -26,6 +26,7 @@ namespace AGS3 {
 
 const char *get_translation(const char *text);
 int IsTranslationAvailable();
+// GetTranslationName assumes a string buffer of MAX_MAXSTRLEN
 int GetTranslationName(char *buffer);
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 63aae83374a..eadafc66ff9 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -169,7 +169,6 @@ const char *Room_GetMessages(int index) {
 		return nullptr;
 	}
 	char buffer[STD_BUFFER_SIZE];
-	buffer[0] = 0;
 	replace_tokens(get_translation(_GP(thisroom).Messages[index].GetCStr()), buffer, STD_BUFFER_SIZE);
 	return CreateNewScriptString(buffer);
 }


Commit: 340a7f209857748cc8c05725c177917b13ea317f
    https://github.com/scummvm/scummvm/commit/340a7f209857748cc8c05725c177917b13ea317f
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed InputBox draws input string beyond textbox borders

This is a very old bug, apparently.
>From upstream 79f96a7dfb57f31f0e6c85095a1f1d3be59dd5c7

Changed paths:
    engines/ags/engine/gui/my_textbox.cpp


diff --git a/engines/ags/engine/gui/my_textbox.cpp b/engines/ags/engine/gui/my_textbox.cpp
index 9057887af97..51af88bd23b 100644
--- a/engines/ags/engine/gui/my_textbox.cpp
+++ b/engines/ags/engine/gui/my_textbox.cpp
@@ -44,6 +44,7 @@ MyTextBox::MyTextBox(int xx, int yy, int wii, const char *tee) {
 }
 
 void MyTextBox::draw(Bitmap *ds) {
+	ds->SetClip(RectWH(x, y, wid + 1, hit + 1));
 	color_t draw_color = ds->GetCompatibleColor(_G(windowbackgroundcolor));
 	ds->FillRect(Rect(x, y, x + wid, y + hit), draw_color);
 	draw_color = ds->GetCompatibleColor(0);
@@ -53,6 +54,7 @@ void MyTextBox::draw(Bitmap *ds) {
 
 	char tbu[2] = "_";
 	wouttextxy(ds, x + 2 + get_text_width(text, _G(cbuttfont)), y + 1, _G(cbuttfont), text_color, tbu);
+	ds->ResetClip();
 }
 
 int MyTextBox::pressedon(int /*mx*/, int /*my*/) {


Commit: bb0ff49ee4ef20b05296d1b8fe76e3d9b84099e2
    https://github.com/scummvm/scummvm/commit/bb0ff49ee4ef20b05296d1b8fe76e3d9b84099e2
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed wouttextxy_AutoOutline inconsistent int param types

>From upstream d6d10c38e60292d24d343d8220b58fc83e7eabd7

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


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 419a6e1ac37..04e83f3e3b2 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -484,15 +484,15 @@ void wouttextxy_AutoOutline(Bitmap *ds, size_t font, int32_t color, const char *
 
 	// WORKAROUND: Clifftop's Spritefont plugin returns a wrong font height for font 2 in Kathy Rain, which causes a partial outline
 	// for some letters. Unfortunately fixing the value on the plugin side breaks the line spacing, so let's just correct it here.
-	size_t const t_width = get_text_width(texx, font);
+	const int t_width = get_text_width(texx, font);
 	const auto t_extent = get_font_surface_extent(font);
-	size_t const t_height = t_extent.second - t_extent.first + ((strcmp(_GP(game).guid, "{d6795d1c-3cfe-49ec-90a1-85c313bfccaf}") == 0) && (font == 2) ? 1 : 0);
+	const int t_height = t_extent.second - t_extent.first + ((strcmp(_GP(game).guid, "{d6795d1c-3cfe-49ec-90a1-85c313bfccaf}") == 0) && (font == 2) ? 1 : 0);
 
 	if (t_width == 0 || t_height == 0)
 		return;
 
 	// Prepare stencils
-	size_t const t_yoff = t_extent.first;
+	const int t_yoff = t_extent.first;
 	Bitmap *texx_stencil, *outline_stencil;
 	alloc_font_outline_buffers(font, &texx_stencil, &outline_stencil,
 		t_width, t_height, stencil_cd);


Commit: f2b5bb80a1e9e827e79eeac97ef5827e07100ace
    https://github.com/scummvm/scummvm/commit/f2b5bb80a1e9e827e79eeac97ef5827e07100ace
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: when calculating TTF's extents, use first 128 glyphs

This fixes loading very big fonts, which may take literally seconds to calculate if we process each glyph.

This load mode is primarily purposed for backwards compatibility, as with new games authors may
fix / replace the font if they have issues.
Because of that, it's not a good thing to pessimize loading times for games that don't require this autofix.

>From upstream b058b35ec82f19a4f2b70d952eede9589e626083

Changed paths:
    engines/ags/lib/alfont/alfont.cpp


diff --git a/engines/ags/lib/alfont/alfont.cpp b/engines/ags/lib/alfont/alfont.cpp
index 5b1ad15c8df..d1de87c14b7 100644
--- a/engines/ags/lib/alfont/alfont.cpp
+++ b/engines/ags/lib/alfont/alfont.cpp
@@ -500,12 +500,16 @@ static void _alfont_new_cache_glyph(ALFONT_FONT *f) {
 	}
 }
 
-static void _alfont_calculate_max_cbox(ALFONT_FONT *f) {
+static void _alfont_calculate_max_cbox(ALFONT_FONT *f, int max_glyphs) {
 	int i;
 	int max_box_top = 0, min_box_bottom = 0;
 	FT_Glyph glyph;
 	FT_BBox box;
-	for (i = 0; i < f->face->num_glyphs; i++) {
+
+	if (max_glyphs <= 0)
+		max_glyphs = f->face->num_glyphs;
+
+	for (i = 0; (i < f->face->num_glyphs) && (max_glyphs > 0); i++, max_glyphs--) {
 		// CHECKME: is FT_LOAD_DEFAULT optimal here? there are various load modes
 		FT_Load_Glyph(f->face, i, FT_LOAD_DEFAULT);
 		FT_Get_Glyph(f->face->glyph, &glyph);
@@ -597,7 +601,7 @@ int alfont_set_font_size_ex(ALFONT_FONT *f, int h, int flags) {
 
 		/* Precalculate actual glyphs vertical extent */
 		if ((flags & ALFONT_FLG_PRECALC_MAX_CBOX) != 0) {
-			_alfont_calculate_max_cbox(f);
+			_alfont_calculate_max_cbox(f, 128);
 		}
 		/* AGS COMPAT HACK: set ascender to the formal font height */
 		if ((flags & ALFONT_FLG_ASCENDER_EQ_HEIGHT) != 0) {


Commit: 3b52b26652e3597fd963bc5e4bdf6956ef8d0be9
    https://github.com/scummvm/scummvm/commit/3b52b26652e3597fd963bc5e4bdf6956ef8d0be9
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Common: when calculating TTF font's extent just use FT_Face->bbox

This apparently already stores precalculated maximal glyphs extents.
Testing this side-by-side with the previous solution shows that these bbox values are mostly matching the
results of loading and measuring each glyph.
They are 100% matching in the "bad" fonts which I've been adding this calculation for. For some "good"
fonts they sometimes have 1 pixel less offset, but are not less than the extent given by (0, height).

And obviously this is faster than loading and calculating glyphs.
>From upstream d96da7e7a7cbdf1d5b101921c5fb83fe0040169d

Changed paths:
    engines/ags/lib/alfont/alfont.cpp


diff --git a/engines/ags/lib/alfont/alfont.cpp b/engines/ags/lib/alfont/alfont.cpp
index d1de87c14b7..b201695639b 100644
--- a/engines/ags/lib/alfont/alfont.cpp
+++ b/engines/ags/lib/alfont/alfont.cpp
@@ -501,29 +501,13 @@ static void _alfont_new_cache_glyph(ALFONT_FONT *f) {
 }
 
 static void _alfont_calculate_max_cbox(ALFONT_FONT *f, int max_glyphs) {
-	int i;
-	int max_box_top = 0, min_box_bottom = 0;
-	FT_Glyph glyph;
-	FT_BBox box;
-
-	if (max_glyphs <= 0)
-		max_glyphs = f->face->num_glyphs;
-
-	for (i = 0; (i < f->face->num_glyphs) && (max_glyphs > 0); i++, max_glyphs--) {
-		// CHECKME: is FT_LOAD_DEFAULT optimal here? there are various load modes
-		FT_Load_Glyph(f->face, i, FT_LOAD_DEFAULT);
-		FT_Get_Glyph(f->face->glyph, &glyph);
-		FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &box);
-		if (max_box_top < box.yMax) {
-			max_box_top = box.yMax;
-		}
-		if (min_box_bottom > box.yMin) {
-			min_box_bottom = box.yMin;
-		}
-		FT_Done_Glyph(glyph);
-	}
-	f->real_face_extent_asc = max_box_top;
-	f->real_face_extent_desc = -min_box_bottom;
+	(void) max_glyphs; // kept just in case, but this was used to load N glyphs
+
+	FT_Long bbox_ymin = FT_MulFix(FT_DivFix(f->face->bbox.yMin, f->face->units_per_EM), f->face->size->metrics.y_ppem);
+	FT_Long bbox_ymax = FT_MulFix(FT_DivFix(f->face->bbox.yMax, f->face->units_per_EM), f->face->size->metrics.y_ppem);
+
+	f->real_face_extent_asc = (int)bbox_ymax;
+	f->real_face_extent_desc = -(int)bbox_ymin;
 }
 
 /* API */
@@ -601,7 +585,7 @@ int alfont_set_font_size_ex(ALFONT_FONT *f, int h, int flags) {
 
 		/* Precalculate actual glyphs vertical extent */
 		if ((flags & ALFONT_FLG_PRECALC_MAX_CBOX) != 0) {
-			_alfont_calculate_max_cbox(f, 128);
+			_alfont_calculate_max_cbox(f, 256);
 		}
 		/* AGS COMPAT HACK: set ascender to the formal font height */
 		if ((flags & ALFONT_FLG_ASCENDER_EQ_HEIGHT) != 0) {


Commit: 56ec78d7b86cf46f2efc49b34a0d405c1c23fe1e
    https://github.com/scummvm/scummvm/commit/56ec78d7b86cf46f2efc49b34a0d405c1c23fe1e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed ScPl_System_Log() had wrong argument list (copy-paste?)

>From upstream ff2f0d53559e9811691963e915f6c6f5f2337ea8

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


diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp
index 536393a5bd2..0baf8481be7 100644
--- a/engines/ags/engine/ac/system.cpp
+++ b/engines/ags/engine/ac/system.cpp
@@ -361,7 +361,7 @@ RuntimeScriptValue Sc_System_Log(const RuntimeScriptValue *params, int32_t param
 //
 //=============================================================================
 
-void ScPl_System_Log(CharacterInfo *chaa, int message_type, const char *texx, ...) {
+void ScPl_System_Log(int message_type, const char *texx, ...) {
 	API_PLUGIN_SCRIPT_SPRINTF_PURE(texx);
 	Debug::Printf(kDbgGroup_Script, (MessageType)message_type, scsf_buffer);
 }


Commit: 4308801cae173cb801a4ea108141e1a018809e76
    https://github.com/scummvm/scummvm/commit/4308801cae173cb801a4ea108141e1a018809e76
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in GetDiskFreeSpaceMB() ensure we check the save dir's disk

Partially from upstream 220ada5413886e80f42c9f2950e20ea46c141283

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/main/engine.cpp
    engines/ags/engine/platform/base/ags_platform_driver.h
    engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 343cedb34da..442717c62a6 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -852,6 +852,9 @@ Bitmap *create_savegame_screenshot() {
 }
 
 void save_game(int slotn, const char *descript) {
+
+	VALIDATE_STRING(descript);
+
 	// dont allow save in rep_exec_always, because we dont save
 	// the state of blocked scripts
 	can_run_delayed_command();
@@ -862,12 +865,11 @@ void save_game(int slotn, const char *descript) {
 		return;
 	}
 
-	if (_G(platform)->GetDiskFreeSpaceMB() < 2) {
+	if (_G(platform)->GetDiskFreeSpaceMB(get_save_game_directory()) < 2) {
 		Display("ERROR: There is not enough disk space free to save the game. Clear some disk space and try again.");
 		return;
 	}
 
-	VALIDATE_STRING(descript);
 	String nametouse = get_save_game_path(slotn);
 	std::unique_ptr<Bitmap> screenShot;
 
diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp
index 48bae6d5f84..ba0c300c819 100644
--- a/engines/ags/engine/main/engine.cpp
+++ b/engines/ags/engine/main/engine.cpp
@@ -400,13 +400,13 @@ int check_write_access() {
 #if AGS_PLATFORM_SCUMMVM
 	return true;
 #else
-	if (_G(platform)->GetDiskFreeSpaceMB() < 2)
-		return 0;
 
 	set_our_eip(-1895);
 
 	// The Save Game Dir is the only place that we should write to
 	String svg_dir = get_save_game_directory();
+	if (platform->GetDiskFreeSpaceMB(svg_dir) < 2)
+		return 0;
 	String tempPath = String::FromFormat("%s""tmptest.tmp", svg_dir.GetCStr());
 	Stream *temp_s = Shared::File::CreateFile(tempPath);
 	if (!temp_s)
diff --git a/engines/ags/engine/platform/base/ags_platform_driver.h b/engines/ags/engine/platform/base/ags_platform_driver.h
index cc41014a0cd..f5d057f3451 100644
--- a/engines/ags/engine/platform/base/ags_platform_driver.h
+++ b/engines/ags/engine/platform/base/ags_platform_driver.h
@@ -117,7 +117,7 @@ struct AGSPlatformDriver
 	virtual const char *GetGraphicsTroubleshootingText() {
 		return "";
 	}
-	virtual uint64_t GetDiskFreeSpaceMB() = 0;
+	virtual uint64_t GetDiskFreeSpaceMB(const Shared::String &path) = 0;
 	virtual const char *GetNoMouseErrorString() = 0;
 	// Tells whether build is capable of controlling mouse movement properly
 	virtual bool IsMouseControlSupported(bool windowed) {
diff --git a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
index 9c2388195c0..f026d50119d 100644
--- a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
+++ b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
@@ -47,7 +47,7 @@ struct ScummVMPlatformDriver : AGSPlatformDriver {
 	FSLocation GetUserConfigDirectory() override;
 	FSLocation GetUserGlobalConfigDirectory() override;
 	FSLocation GetAppOutputDirectory() override;
-	uint64_t GetDiskFreeSpaceMB() override;
+	uint64_t GetDiskFreeSpaceMB(const AGS::Shared::String &path) override;
 	const char *GetNoMouseErrorString() override;
 	const char *GetAllegroFailUserHint() override;
 	eScriptSystemOSID GetSystemOSID() override;
@@ -105,7 +105,7 @@ FSLocation ScummVMPlatformDriver::GetAppOutputDirectory() {
 	return FSLocation(".");
 }
 
-uint64_t ScummVMPlatformDriver::GetDiskFreeSpaceMB() {
+uint64_t ScummVMPlatformDriver::GetDiskFreeSpaceMB(const String &path) {
 	// placeholder
 	return 100;
 }


Commit: 1c5ca014ca646788ba07a3db8fa3d9782485f15e
    https://github.com/scummvm/scummvm/commit/1c5ca014ca646788ba07a3db8fa3d9782485f15e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
Engine: allow scene render in "load room" event if transition instant

Fixes behavior after ed5e81c.
Although somewhat inconsistent, but AGS historically allowed to run animations in "room load" event,
 and they would be rendered if last room transition was "Instant", because the screen was not faded out.
 From upstream fb2fd763a471af7532324a4da8e0f655e7cf5516

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


diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index dc8543d9c69..3002df10e81 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -204,9 +204,9 @@ void process_event(const EventHappened *evp) {
 		if (_GP(play).fast_forward)
 			return;
 
-		const bool ignore_transition = (_GP(play).screen_tint > 0);
+		const bool instant_transition = (theTransition == FADE_INSTANT) || (_GP(play).screen_tint > 0);
 		if (((theTransition == FADE_CROSSFADE) || (theTransition == FADE_DISSOLVE)) &&
-		        (_G(saved_viewport_bitmap) == nullptr) && !ignore_transition) {
+		        (_G(saved_viewport_bitmap) == nullptr) && !instant_transition) {
 			// transition type was not crossfade/dissolve when the screen faded out,
 			// but it is now when the screen fades in (Eg. a save game was restored
 			// with a different setting). Therefore just fade normally.
@@ -217,7 +217,7 @@ void process_event(const EventHappened *evp) {
 		// TODO: use normal coordinates instead of "native_size" and multiply_up_*?
 		const Rect &viewport = _GP(play).GetMainViewport();
 
-		if ((theTransition == FADE_INSTANT) || ignore_transition)
+		if (instant_transition)
 			set_palette_range(_G(palette), 0, 255, 0);
 		else if (theTransition == FADE_NORMAL) {
 			fadein_impl(_G(palette), 5);
diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp
index c9540b79f02..666aa57c101 100644
--- a/engines/ags/engine/ac/screen.cpp
+++ b/engines/ags/engine/ac/screen.cpp
@@ -66,9 +66,9 @@ void current_fade_out_effect() {
 	// was a temporary transition selected? if so, use it
 	if (_GP(play).next_screen_transition >= 0)
 		theTransition = _GP(play).next_screen_transition;
-	const bool ignore_transition = _GP(play).screen_tint > 0;
-
-	if ((theTransition == FADE_INSTANT) || ignore_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 (instant_transition) {
 		if (!_GP(play).keep_screen_during_instant_transition)
 			set_palette_range(_G(black_palette), 0, 255, 0);
 	} else if (theTransition == FADE_NORMAL) {
@@ -81,7 +81,8 @@ void current_fade_out_effect() {
 		_G(saved_viewport_bitmap) = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight(), &viewport, false /* use current resolution */, RENDER_SHOT_SKIP_ON_FADE);
 	}
 
-	_GP(play).screen_is_faded_out = 1;
+	// NOTE: the screen could have been faded out prior to transition out
+	_GP(play).screen_is_faded_out |= (!instant_transition);
 }
 
 IDriverDependantBitmap *prepare_screen_for_transition_in(bool opaque) {


Commit: 6814c8dbe2e17c4230c55ffca4aa05a7f598f5e1
    https://github.com/scummvm/scummvm/commit/6814c8dbe2e17c4230c55ffca4aa05a7f598f5e1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: support loading 3.5.0 saves with mismatching "Room States" ver

>From upstream 02281846f8d25041b6179617eba4bd65ffef0cc2

Changed paths:
    engines/ags/engine/ac/room_status.h
    engines/ags/engine/game/savegame_components.cpp


diff --git a/engines/ags/engine/ac/room_status.h b/engines/ags/engine/ac/room_status.h
index 0ec86b99439..a2d2297be6b 100644
--- a/engines/ags/engine/ac/room_status.h
+++ b/engines/ags/engine/ac/room_status.h
@@ -51,6 +51,8 @@ struct HotspotState {
 // Savegame data format for RoomStatus
 enum RoomStatSvgVersion {
 	kRoomStatSvgVersion_Initial = 0, // [UNSUPPORTED] from 3.5.0 pre-alpha
+	// NOTE: in 3.5.0 "Room States" had lower index than "Loaded Room State" by mistake
+	kRoomStatSvgVersion_350_Mismatch = 0, // an incorrect "Room States" version from 3.5.0
 	kRoomStatSvgVersion_350     = 1, // new movelist format (along with pathfinder)
 	kRoomStatSvgVersion_36016   = 2, // hotspot and object names
 	kRoomStatSvgVersion_36025   = 3, // object animation volume
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 7546ba9165c..7a809d83a45 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -1143,7 +1143,7 @@ struct ComponentHandlers {
 		{
 			"Room States",
 			kRoomStatSvgVersion_36109,
-			kRoomStatSvgVersion_350, // skip pre-alpha 3.5.0 ver
+			kRoomStatSvgVersion_350_Mismatch, // support mismatching 3.5.0 ver here
 			WriteRoomStates,
 			ReadRoomStates
 		},


Commit: 0c4d929d6d8374a62868369385ab571b9f813438
    https://github.com/scummvm/scummvm/commit/0c4d929d6d8374a62868369385ab571b9f813438
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: hotfix certain platforms to not save "warnings.log" in cwd

ScummVM restricts file operations to the savedir, so this is added for the sake of completeness.
Partially from upstream 965d4ecd11b64b7bedb5f4cfc570cc7211be5332

Changed paths:
    engines/ags/engine/debugging/debug.cpp
    engines/ags/engine/platform/base/ags_platform_driver.h
    engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp


diff --git a/engines/ags/engine/debugging/debug.cpp b/engines/ags/engine/debugging/debug.cpp
index ac465142855..574b0ee87d1 100644
--- a/engines/ags/engine/debugging/debug.cpp
+++ b/engines/ags/engine/debugging/debug.cpp
@@ -91,18 +91,29 @@ static const char *OutputMsgBufID = "buffer";
 static const char *OutputFileID = "file";
 static const char *OutputSystemID = "stdout";
 
-PDebugOutput create_log_output(const String &name, const String &path = "", LogFile::OpenMode open_mode = LogFile::kLogFile_Overwrite) {
+// Create a new log output by ID
+
+PDebugOutput create_log_output(const String &name, const String &dir = "", const String &filename = "", LogFile::OpenMode open_mode = LogFile::kLogFile_Overwrite) {
 	// Else create new one, if we know this ID
 	if (name.CompareNoCase(OutputSystemID) == 0) {
 		return _GP(DbgMgr).RegisterOutput(OutputSystemID, AGSPlatformDriver::GetDriver(), kDbgMsg_None);
 	} else if (name.CompareNoCase(OutputFileID) == 0) {
 		_GP(DebugLogFile).reset(new LogFile());
-		String logfile_path = path;
-		if (logfile_path.IsEmpty()) {
+		String logfile_dir = dir;
+		if (dir.IsEmpty()) {
 			FSLocation fs = _G(platform)->GetAppOutputDirectory();
 			CreateFSDirs(fs);
-			logfile_path = Path::ConcatPaths(fs.FullDir, "ags.log");
+			logfile_dir = fs.FullDir;
+		} else if (Path::IsRelativePath(dir) && _G(platform)->IsLocalDirRestricted()) {
+			FSLocation fs = GetGameUserDataDir();
+			CreateFSDirs(fs);
+			logfile_dir = fs.FullDir;
 		}
+		String logfilename = filename.IsEmpty() ? "ags.log" : filename;
+#if AGS_PLATFORM_SCUMMVM
+		logfile_dir = "";  // ignore path
+#endif
+		String logfile_path = Path::ConcatPaths(logfile_dir, logfilename);
 		if (!_GP(DebugLogFile)->OpenFile(logfile_path, open_mode))
 			return nullptr;
 		Debug::Printf(kDbgMsg_Info, "Logging to %s", logfile_path.GetCStr());
@@ -241,7 +252,7 @@ void apply_debug_config(const ConfigTree &cfg) {
 	// If the game was compiled in Debug mode *and* there's no regular file log,
 	// then open "warnings.log" for printing script warnings.
 	if (_GP(game).options[OPT_DEBUGMODE] != 0 && !_GP(DebugLogFile)) {
-		auto dbgout = create_log_output(OutputFileID, "warnings.log", LogFile::kLogFile_OverwriteAtFirstMessage);
+		auto dbgout = create_log_output(OutputFileID, "./", "warnings.log", LogFile::kLogFile_OverwriteAtFirstMessage);
 		if (dbgout)
 			dbgout->SetGroupFilter(kDbgGroup_Game, kDbgMsg_Warn);
 	}
diff --git a/engines/ags/engine/platform/base/ags_platform_driver.h b/engines/ags/engine/platform/base/ags_platform_driver.h
index f5d057f3451..395d6c62346 100644
--- a/engines/ags/engine/platform/base/ags_platform_driver.h
+++ b/engines/ags/engine/platform/base/ags_platform_driver.h
@@ -109,6 +109,10 @@ struct AGSPlatformDriver
 	virtual FSLocation GetAppOutputDirectory() {
 		return FSLocation(".");
 	}
+	// Tells whether it's not permitted to write to the local directory (cwd, or game dir),
+	// and only specified user/app directories should be used.
+	// FIXME: this is a part of a hotfix, review uses of this function later.
+	virtual bool IsLocalDirRestricted() { return true; }
 	// Returns array of characters illegal to use in file names
 	virtual const char *GetIllegalFileChars() {
 		return "\\/";
diff --git a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
index f026d50119d..768818720ea 100644
--- a/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
+++ b/engines/ags/engine/platform/scummvm/scummvm_platform_driver.cpp
@@ -47,6 +47,7 @@ struct ScummVMPlatformDriver : AGSPlatformDriver {
 	FSLocation GetUserConfigDirectory() override;
 	FSLocation GetUserGlobalConfigDirectory() override;
 	FSLocation GetAppOutputDirectory() override;
+	bool IsLocalDirRestricted() override;
 	uint64_t GetDiskFreeSpaceMB(const AGS::Shared::String &path) override;
 	const char *GetNoMouseErrorString() override;
 	const char *GetAllegroFailUserHint() override;
@@ -105,6 +106,11 @@ FSLocation ScummVMPlatformDriver::GetAppOutputDirectory() {
 	return FSLocation(".");
 }
 
+bool ScummVMPlatformDriver::IsLocalDirRestricted() {
+	// Let them to create temp files in the current working dir
+	return false;
+}
+
 uint64_t ScummVMPlatformDriver::GetDiskFreeSpaceMB(const String &path) {
 	// placeholder
 	return 100;


Commit: 4ffe9e680336791eac5fcdf845186651084faca3
    https://github.com/scummvm/scummvm/commit/4ffe9e680336791eac5fcdf845186651084faca3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.25 P3)

Partially from upstream 55fc5a4304ce67e65f858966a10a9a07d5d12457

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 e9d5418b433..2e59c35b10f 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.23"
+#define ACI_VERSION_STR      "3.6.1.25"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.23
+#define ACI_VERSION_MSRC_DEF  3.6.1.25
 #endif
 
 #define SPECIAL_VERSION ""


Commit: 6a2d740403aa169e834d0a2d12ab019be581f10d
    https://github.com/scummvm/scummvm/commit/6a2d740403aa169e834d0a2d12ab019be581f10d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fix setting Viewport and Camera sizes in game_start

1. Fixed primary viewport and camera created with zero sizes, because they are initialized before the game resolution.
2. Fixed camera's size set in "game_start" function is clamped against a room placeholder size of 320x200.
>From upstream ff615af2f5c8d0ee21e6ad633930352c2621883f

Changed paths:
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/viewport.cpp


diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index 16ee8a1e16d..93446429120 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -239,7 +239,6 @@ HError InitAndRegisterGameEntities(GameSetupStruct &game) {
 	InitAndRegisterHotspots();
 	InitAndRegisterRegions();
 	InitAndRegisterRoomObjects();
-	_GP(play).CreatePrimaryViewportAndCamera();
 
 	RegisterStaticArrays(game);
 
@@ -483,6 +482,9 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 
 	update_gui_zorder();
 	calculate_reserved_channel_count();
+	// Default viewport and camera, draw data, etc, should be created when resolution is set
+	_GP(play).CreatePrimaryViewportAndCamera();
+	init_game_drawdata();
 
 	//
 	// 6. Register engine API exports
diff --git a/engines/ags/engine/game/viewport.cpp b/engines/ags/engine/game/viewport.cpp
index 37f3f3830e1..d1c51353648 100644
--- a/engines/ags/engine/game/viewport.cpp
+++ b/engines/ags/engine/game/viewport.cpp
@@ -43,8 +43,11 @@ const Rect &Camera::GetRect() const {
 void Camera::SetSize(const Size cam_size) {
 	// TODO: currently we don't support having camera larger than room background
 	// (or rather - looking outside of the room background); look into this later
-	const Size real_room_sz = Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height));
-	Size real_size = Size::Clamp(cam_size, Size(1, 1), real_room_sz);
+	const Size real_room_sz = (_G(displayed_room) >= 0 && (_GP(thisroom).Width > 0 && _GP(thisroom).Height > 0)) ?
+							  Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height)) :
+							  Size(INT32_MAX, INT32_MAX);
+
+	const Size real_size = Size::Clamp(cam_size, Size(1, 1), real_room_sz);
 	if (_position.GetWidth() == real_size.Width && _position.GetHeight() == real_size.Height)
 		return;
 


Commit: 187cf953d295bf6d91ced769ba57bc4d3ed9e9f1
    https://github.com/scummvm/scummvm/commit/187cf953d295bf6d91ced769ba57bc4d3ed9e9f1
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Script API: added missing OPT_* constants for Get/SetGameOption

Partially from upstream 8a3c283829a537c719ec2e82f2437fb4bb8a4344
and e4e6677523935b771d209f57906c0717be54141f

Changed paths:
    engines/ags/shared/ac/game_struct_defines.h


diff --git a/engines/ags/shared/ac/game_struct_defines.h b/engines/ags/shared/ac/game_struct_defines.h
index 770e1d08ac6..43aa0d57b30 100644
--- a/engines/ags/shared/ac/game_struct_defines.h
+++ b/engines/ags/shared/ac/game_struct_defines.h
@@ -70,15 +70,15 @@ namespace AGS3 {
 #define OPT_PORTRAITSIDE    31
 #define OPT_STRICTSCRIPTING 32  // don't allow MoveCharacter-style commands
 #define OPT_LEFTTORIGHTEVAL 33  // left-to-right operator evaluation
-#define OPT_COMPRESSSPRITES 34  // [DEPRECATED]
+#define OPT_COMPRESSSPRITES 34  // sprite compression type (None, RLE, LZW, Deflate)
 #define OPT_STRICTSTRINGS   35  // don't allow old-style strings, for reference only
-#define OPT_NEWGUIALPHA     36
+#define OPT_NEWGUIALPHA     36  // alpha blending method when drawing GUI and controls
 #define OPT_RUNGAMEDLGOPTS  37
 #define OPT_NATIVECOORDINATES 38 // defines coordinate relation between game logic and game screen
 #define OPT_GLOBALTALKANIMSPD 39
 #define OPT_HIGHESTOPTION_321 39
-#define OPT_SPRITEALPHA     40
-#define OPT_SAFEFILEPATHS   41
+#define OPT_SPRITEALPHA     40  // alpha blending method when drawing images on DrawingSurface
+#define OPT_SAFEFILEPATHS   41  // restricted file path in script (not writing to the game dir, etc)
 #define OPT_DIALOGOPTIONSAPI 42 // version of dialog options API (-1 for pre-3.4.0 API)
 #define OPT_BASESCRIPTAPI   43 // version of the Script API (ScriptAPIVersion) used to compile game script
 #define OPT_SCRIPTCOMPATLEV 44 // level of API compatibility (ScriptAPIVersion) used to compile game script


Commit: 455aaef9f04cf514316162a7d83cadf1f56eb50c
    https://github.com/scummvm/scummvm/commit/455aaef9f04cf514316162a7d83cadf1f56eb50c
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.26 P4)

Partially from upstream 7673e34a8c99c8221d8057e9f8f08688bca60919

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 2e59c35b10f..2030a1eadb4 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.25"
+#define ACI_VERSION_STR      "3.6.1.26"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.25
+#define ACI_VERSION_MSRC_DEF  3.6.1.26
 #endif
 
 #define SPECIAL_VERSION ""


Commit: f31492632359003cdd75801964d87941b535b262
    https://github.com/scummvm/scummvm/commit/f31492632359003cdd75801964d87941b535b262
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed GetRegionAtScreen to return region[0] for off coordinates

This is for consistency with other masks (Hotspots etc), and backwards
 compatibility.
>From upstream d318f83acc6cf02c92fb4bdad9318e66fa537c19

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


diff --git a/engines/ags/engine/ac/region.cpp b/engines/ags/engine/ac/region.cpp
index 9ef70593a59..b1cbef65a5c 100644
--- a/engines/ags/engine/ac/region.cpp
+++ b/engines/ags/engine/ac/region.cpp
@@ -46,7 +46,7 @@ ScriptRegion *GetRegionAtRoom(int xx, int yy) {
 ScriptRegion *GetRegionAtScreen(int x, int y) {
 	VpPoint vpt = _GP(play).ScreenToRoomDivDown(x, y);
 	if (vpt.second < 0)
-		return nullptr;
+		return &_G(scrRegion)[0]; // return region[0] for consistency and backwards compatibility
 	return GetRegionAtRoom(vpt.first.X, vpt.first.Y);
 }
 


Commit: d05eeb5dc253cb6165b59f53c154b54854b0e0ad
    https://github.com/scummvm/scummvm/commit/d05eeb5dc253cb6165b59f53c154b54854b0e0ad
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: get rid of the room object sorting hack, use same rules for all

The sorting of the room objects + walkbehinds used some old hack, which behavior
dependent on a "walkbehind method" setting. But this setting is now deprecated
(and software renderer does not make walk-behinds a separate object).

Instead use a custom "sprite entry id" tag to help with sorting objects with equal zorder
(baseline). This lets to use same sorting algorithm for everything.
Walk-behinds should always be sorted back in case of equal zorder, so they receive
id = INT32_MIN.
>From upstream 3b195eba0aaba9765e4dbfe427a0ba980fc3a1f6

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/sprite_list_entry.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 10333874de8..53b4a181f1b 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -908,7 +908,7 @@ static void clear_sprite_list() {
 	_GP(sprlist).clear();
 }
 
-static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zorder, bool isWalkBehind, int id = -1) {
+static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zorder, int id = -1) {
 	assert(ddb);
 	// completely invisible, so don't draw it at all
 	if (ddb->GetAlpha() == 0)
@@ -921,11 +921,6 @@ static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zo
 	sprite.x = x;
 	sprite.y = y;
 
-	if (drawstate.WalkBehindMethod == DrawAsSeparateSprite)
-		sprite.takesPriorityIfEqual = !isWalkBehind;
-	else
-		sprite.takesPriorityIfEqual = isWalkBehind;
-
 	_GP(sprlist).push_back(sprite);
 }
 
@@ -936,21 +931,9 @@ static bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntr
 		   ((e1.zorder == e2.zorder) && (e1.id < e2.id));
 }
 
-// Room-specialized function to sort the sprites into baseline order;
-// does not account for IDs, but has special handling for walk-behinds.
-static bool spritelistentry_room_less(const SpriteListEntry &e1, const SpriteListEntry &e2) {
-	if (e1.zorder == e2.zorder) {
-		if (e1.takesPriorityIfEqual)
-			return false;
-		if (e2.takesPriorityIfEqual)
-			return true;
-	}
-	return e1.zorder < e2.zorder;
-}
-
 // copy the sorted sprites into the Things To Draw list
-static void draw_sprite_list(bool is_room) {
-	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), is_room ? spritelistentry_room_less : spritelistentry_less);
+static void draw_sprite_list() {
+	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), spritelistentry_less);
 	_GP(thingsToDrawList).insert(_GP(thingsToDrawList).end(),
 		_GP(sprlist).begin(), _GP(sprlist).end());
 }
@@ -1489,7 +1472,7 @@ void prepare_objects_for_drawing() {
 								   Size(obj.last_width, obj.last_height), atx, aty, usebasel,
 								   (obj.flags & OBJF_NOWALKBEHINDS) == 0, obj.transparent, hw_accel);
 		// Finally, add the texture to the draw list
-		add_to_sprite_list(actsp.Ddb, atx, aty, usebasel, false);
+		add_to_sprite_list(actsp.Ddb, atx, aty, usebasel);
 	}
 }
 
@@ -1597,7 +1580,7 @@ void prepare_characters_for_drawing() {
             Size(chex.width, chex.height), atx, aty, usebasel,
             (chin.flags & CHF_NOWALKBEHINDS) == 0, chin.transparency, hw_accel);
         // Finally, add the texture to the draw list
-        add_to_sprite_list(actsp.Ddb, atx, aty, usebasel, false);
+        add_to_sprite_list(actsp.Ddb, atx, aty, usebasel);
 	}
 }
 
@@ -1625,7 +1608,7 @@ static void add_roomovers_for_drawing() {
 		if (!over.IsRoomLayer()) continue; // not a room layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
+		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, over.creation_id);
 	}
 }
 
@@ -1664,8 +1647,8 @@ void prepare_room_sprites() {
 					(wb < MAX_WALK_BEHINDS) && (wb < (size_t)_GP(walkbehindobj).size()); ++wb) {
 					const auto &wbobj = _GP(walkbehindobj)[wb];
 					if (wbobj.Ddb) {
-						add_to_sprite_list(wbobj.Ddb, wbobj.Pos.X, wbobj.Pos.Y,
-							_G(croom)->walkbehind_base[wb], true);
+						// when baselines are equal, walk-behinds must be sorted back, so tag as INT32_MIN
+						add_to_sprite_list(wbobj.Ddb, wbobj.Pos.X, wbobj.Pos.Y, _G(croom)->walkbehind_base[wb], INT32_MIN);
 					}
 				}
 			}
@@ -1673,7 +1656,7 @@ void prepare_room_sprites() {
 			if (pl_any_want_hook(AGSE_PRESCREENDRAW))
 				add_render_stage(AGSE_PRESCREENDRAW);
 
-			draw_sprite_list(true);
+			draw_sprite_list();
 		}
 	}
 	set_our_eip(36);
@@ -1834,7 +1817,7 @@ void draw_gui_and_overlays() {
 		if (over.IsRoomLayer()) continue; // not a ui layer
 		if (over.transparency == 255) continue; // skip fully transparent
 		Point pos = get_overlay_position(over);
-		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, false, over.creation_id);
+		add_to_sprite_list(_GP(overtxs)[over.type].Ddb, pos.X, pos.Y, over.zorder, over.creation_id);
 	}
 
 	// Add GUIs
@@ -1904,14 +1887,14 @@ void draw_gui_and_overlays() {
 			assert(gui_ddb); // Test for missing texture, might happen if not marked for update
 			if (!gui_ddb) continue;
 			gui_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(gui.Transparency));
-			add_to_sprite_list(gui_ddb, gui.X, gui.Y, gui.ZOrder, false, index);
+			add_to_sprite_list(gui_ddb, gui.X, gui.Y, gui.ZOrder, index);
 		}
 	}
 
 	// If not adding gui controls as textures, simply move the resulting sprlist to render
 	if (!draw_controls_as_textures ||
 		(_G(all_buttons_disabled >= 0) && (GUI::Options.DisabledStyle == kGuiDis_Blackout))) {
-		draw_sprite_list(false);
+		draw_sprite_list();
 		put_sprite_list_on_screen(false);
 		return;
 	}
diff --git a/engines/ags/engine/ac/sprite_list_entry.h b/engines/ags/engine/ac/sprite_list_entry.h
index b4d3e3e0568..96c65cf42bc 100644
--- a/engines/ags/engine/ac/sprite_list_entry.h
+++ b/engines/ags/engine/ac/sprite_list_entry.h
@@ -28,14 +28,12 @@ namespace AGS3 {
 
 // Describes a texture or node description, for sorting and passing into renderer
 struct SpriteListEntry {
-	int id = -1; // user identifier, for any custom purpose
+	// Optional sprite identifier; used as a second factor when sorting
+	int id = -1;
 	Engine::IDriverDependantBitmap *ddb = nullptr;
 	AGS::Shared::Bitmap *pic = nullptr;
 	int x = 0, y = 0;
 	int zorder = 0;
-	// Tells if this item should take priority during sort if z1 == z2
-	// TODO: this is some compatibility feature - find out if may be omitted and done without extra struct?
-	bool takesPriorityIfEqual = false;
 	// Mark for the render stage callback (if >= 0 other fields are ignored)
 	int renderStage = -1;
 };


Commit: fdab95b34b7c59659c9a8c9b569641cc065a704b
    https://github.com/scummvm/scummvm/commit/fdab95b34b7c59659c9a8c9b569641cc065a704b
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: gui mouse handles accept mouse pos as arguments

* This also fixes GUI.ProcessClick, apparently it always had an issue of using mouse
coordinates instead of provided ones.
>From upstream ab3561359eb23fe17ba0758741f139165eb1f13c

Changed paths:
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui.h
    engines/ags/engine/main/game_run.cpp


diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 1291d64891a..24f13d79c69 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -285,10 +285,10 @@ void GUI_Click(ScriptGUI *scgui, int mbut) {
 
 void GUI_ProcessClick(int x, int y, int mbut) {
 	int guiid = gui_get_interactable(x, y);
-	if (guiid >= 0) {
+	if (guiid >= 0) { // simulate mouse click at the given coordinates
 		_GP(guis)[guiid].Poll(x, y);
-		gui_on_mouse_down(guiid, mbut);
-		gui_on_mouse_up(guiid, mbut);
+		gui_on_mouse_down(guiid, mbut, x, y);
+		gui_on_mouse_up(guiid, mbut, x, y);
 	}
 }
 
@@ -513,7 +513,7 @@ int gui_get_interactable(int x, int y) {
 	return GetGUIAt(x, y);
 }
 
-int gui_on_mouse_move() {
+int gui_on_mouse_move(const int mx, const int my) {
 	int mouse_over_gui = -1;
 	// If all GUIs are off, skip the loop
 	if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) && (_G(all_buttons_disabled) >= 0));
@@ -523,7 +523,7 @@ int gui_on_mouse_move() {
 		// CHECKME: not sure why, but we're testing forward draw order here -
 		// from farthest to nearest (this was in original code?)
 		for (int guin : _GP(play).gui_draw_order) {
-			if (_GP(guis)[guin].IsInteractableAt(_G(mousex), _G(mousey))) mouse_over_gui = guin;
+			if (_GP(guis)[guin].IsInteractableAt(mx, my)) mouse_over_gui = guin;
 
 			if (_GP(guis)[guin].PopupStyle != kGUIPopupMouseY) continue;
 			if (_GP(play).complete_overlay_on > 0) break;  // interfaces disabled			//    if (_GP(play).disabled_user_interface>0) break;
@@ -556,7 +556,7 @@ void gui_on_mouse_hold(const int wasongui, const int wasbutdown) {
 	}
 }
 
-void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
+void gui_on_mouse_up(const int wasongui, const int wasbutdown, const int mx, const int my) {
 	_GP(guis)[wasongui].OnMouseButtonUp();
 
 	for (int i = 0; i < _GP(guis)[wasongui].GetControlCount(); i++) {
@@ -569,8 +569,8 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 		if ((cttype == kGUIButton) || (cttype == kGUISlider) || (cttype == kGUIListBox)) {
 			force_event(EV_IFACECLICK, wasongui, i, wasbutdown);
 		} else if (cttype == kGUIInvWindow) {
-			_G(mouse_ifacebut_xoffs) = _G(mousex) - (guio->X) - _GP(guis)[wasongui].X;
-			_G(mouse_ifacebut_yoffs) = _G(mousey) - (guio->Y) - _GP(guis)[wasongui].Y;
+			_G(mouse_ifacebut_xoffs) = mx - (guio->X) - _GP(guis)[wasongui].X;
+			_G(mouse_ifacebut_yoffs) = my - (guio->Y) - _GP(guis)[wasongui].Y;
 			int iit = offset_over_inv((GUIInvWindow *)guio);
 			if (iit >= 0) {
 				_GP(play).used_inv_on = iit;
@@ -594,9 +594,9 @@ void gui_on_mouse_up(const int wasongui, const int wasbutdown) {
 	run_on_event(GE_GUI_MOUSEUP, RuntimeScriptValue().SetInt32(wasongui));
 }
 
-void gui_on_mouse_down(const int guin, const int mbut) {
+void gui_on_mouse_down(const int guin, const int mbut, const int mx, const int my) {
 	debug_script_log("Mouse click over GUI %d", guin);
-	_GP(guis)[guin].OnMouseButtonDown(_G(mousex), _G(mousey));
+	_GP(guis)[guin].OnMouseButtonDown(mx, my);
 	// run GUI click handler if not on any control
 	if ((_GP(guis)[guin].MouseDownCtrl < 0) && (!_GP(guis)[guin].OnClickHandler.IsEmpty()))
 		force_event(EV_IFACECLICK, guin, -1, mbut);
diff --git a/engines/ags/engine/ac/gui.h b/engines/ags/engine/ac/gui.h
index 188904fd437..c408e44579a 100644
--- a/engines/ags/engine/ac/gui.h
+++ b/engines/ags/engine/ac/gui.h
@@ -79,10 +79,10 @@ int     adjust_x_for_guis(int x, int y, bool assume_blocking = false);
 int     adjust_y_for_guis(int y, bool assume_blocking = false);
 
 int     gui_get_interactable(int x, int y);
-int     gui_on_mouse_move();
+int     gui_on_mouse_move(const int mx, const int my);
 void    gui_on_mouse_hold(const int wasongui, const int wasbutdown);
-void    gui_on_mouse_up(const int wasongui, const int wasbutdown);
-void    gui_on_mouse_down(const int guin, const int mbut);
+void    gui_on_mouse_up(const int wasongui, const int wasbutdown, const int mx, const int my);
+void    gui_on_mouse_down(const int guin, const int mbut, const int mx, const int my);
 
 } // namespace AGS3
 
diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp
index 250ad0df317..6e44907fcfa 100644
--- a/engines/ags/engine/main/game_run.cpp
+++ b/engines/ags/engine/main/game_run.cpp
@@ -188,7 +188,7 @@ static void toggle_mouse_lock() {
 static void check_mouse_controls() {
 	int mongu = -1;
 
-	mongu = gui_on_mouse_move();
+	mongu = gui_on_mouse_move(_G(mousex), _G(mousey));
 
 	_G(mouse_on_iface) = mongu;
 	if ((_G(ifacepopped) >= 0) && (_G(mousey) >= _GP(guis)[_G(ifacepopped)].Y + _GP(guis)[_G(ifacepopped)].Height))
@@ -200,7 +200,7 @@ static void check_mouse_controls() {
 	} else if ((_G(wasbutdown) > kMouseNone) && (!ags_misbuttondown(_G(wasbutdown)))) {
 		eAGSMouseButton mouse_btn_up = _G(wasbutdown);
 		_G(wasbutdown) = kMouseNone; // reset before event, avoid recursive call of "mouse up"
-		gui_on_mouse_up(_G(wasongui), mouse_btn_up);
+		gui_on_mouse_up(_G(wasongui), mouse_btn_up, _G(mousex), _G(mousey));
 	}
 
 	eAGSMouseButton mbut;
@@ -223,7 +223,7 @@ static void check_mouse_controls() {
 			debug_script_log("Plugin handled mouse button %d", mbut);
 		} else if (mongu >= 0) {
 			if (_G(wasbutdown) == kMouseNone) {
-				gui_on_mouse_down(mongu, mbut);
+				gui_on_mouse_down(mongu, mbut, _G(mousex), _G(mousey));
 			}
 			_G(wasongui) = mongu;
 			_G(wasbutdown) = mbut;


Commit: a9dc8d7cbfe4a97441179acb84f471533dfb5448
    https://github.com/scummvm/scummvm/commit/a9dc8d7cbfe4a97441179acb84f471533dfb5448
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: in ASCII mode in TextBox don't try printing unsupported chars

Text input never worked for >128 chars in ASCII mode, this will prevent a random garbage
in in case of unsupported characters.

>From upstream 7c59c33e8c56bb7859ec9c82966c7a81776e0843

Changed paths:
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index b4ce3b86657..93091ef8674 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -109,12 +109,12 @@ void GUITextBox::OnKeyPress(const KeyInput &ki) {
 
 	if (ki.UChar == 0)
 		return; // not a textual event
-	if ((ki.UChar >= 128) && (!font_supports_extended_characters(Font)))
-		return; // unsupported letter
-
-	(get_uformat() == U_UTF8) ?
-		Text.Append(ki.Text) :
-		Text.AppendChar(ki.UChar);
+	if (get_uformat() == U_UTF8)
+		Text.Append(ki.Text); // proper unicode char
+	else if (ki.UChar < 256)
+		Text.AppendChar(static_cast<uint8_t>(ki.UChar)); // ascii/ansi-range char in ascii mode
+	else
+		return; // char from an unsupported range, don't print but still report as handled
 	// if the new string is too long, remove the new character
 	if (get_text_width(Text.GetCStr(), Font) > (_width - (6 + get_fixed_pixel_size(5))))
 		Backspace(Text);


Commit: 90863afb9b24b51cf2ac578a172a56558c90e160
    https://github.com/scummvm/scummvm/commit/90863afb9b24b51cf2ac578a172a56558c90e160
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed ConvertUtf8ToAscii to restore old locale properly

>From upstream 8ca6452c417329c7c7ae3db77ca82842f4884f09

Changed paths:
    engines/ags/shared/util/string_utils.cpp


diff --git a/engines/ags/shared/util/string_utils.cpp b/engines/ags/shared/util/string_utils.cpp
index cd06d1d4cc8..8b93a52910f 100644
--- a/engines/ags/shared/util/string_utils.cpp
+++ b/engines/ags/shared/util/string_utils.cpp
@@ -253,6 +253,8 @@ void StrUtil::WriteStringMap(const StringMap &map, Stream *out) {
 size_t StrUtil::ConvertUtf8ToAscii(const char *mbstr, const char *loc_name, char *out_cstr, size_t out_sz) {
 	// TODO: later consider using alternative conversion methods
 	// (e.g. see C++11 features), as setlocale is unreliable.
+	char old_locale[64];
+	snprintf(old_locale, sizeof(old_locale), "%s", setlocale(LC_CTYPE, nullptr));
 	if (setlocale(LC_CTYPE, loc_name) == nullptr) { // If failed setlocale, then resort to plain copy the mb string
 		return static_cast<size_t>(snprintf(out_cstr, out_sz, "%s", mbstr));
 	}
@@ -268,7 +270,7 @@ size_t StrUtil::ConvertUtf8ToAscii(const char *mbstr, const char *loc_name, char
 	}
 	// Then convert widestring to single-byte string using specified locale
 	size_t res_sz = wcstombs(out_cstr, &wcsbuf[0], out_sz);
-	setlocale(LC_CTYPE, "");
+	setlocale(LC_CTYPE, old_locale);
 	return res_sz;
 }
 


Commit: 6f25625b918976fc2d6fa38f922de9f678bf187e
    https://github.com/scummvm/scummvm/commit/6f25625b918976fc2d6fa38f922de9f678bf187e
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: simplify DrawSpriteWithTransparency()

>From upstream ade4f4d066b683c39b5f771f552ae0dc1dc3a378

Changed paths:
    engines/ags/engine/gfx/gfx_util.cpp
    engines/ags/engine/gfx/gfx_util.h


diff --git a/engines/ags/engine/gfx/gfx_util.cpp b/engines/ags/engine/gfx/gfx_util.cpp
index b29c5265bc5..32084e375ce 100644
--- a/engines/ags/engine/gfx/gfx_util.cpp
+++ b/engines/ags/engine/gfx/gfx_util.cpp
@@ -100,40 +100,15 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 		return;
 	}
 
-	Bitmap hctemp;
 	const int surface_depth = ds->GetColorDepth();
 	const int sprite_depth = sprite->GetColorDepth();
 
-	if (sprite_depth < surface_depth) {
-		// If sprite is lower color depth than destination surface, e.g.
-		// 8-bit sprites drawn on 16/32-bit surfaces.
-		if (sprite_depth == 8 && surface_depth >= 24) {
-			// 256-col sprite -> truecolor background
-			// this is automatically supported by allegro, no twiddling needed
-			ds->Blit(sprite, x, y, kBitmap_Transparency);
-			return;
-		}
-
-		// 256-col sprite -> hi-color background, or
-		// 16-bit sprite -> 32-bit background
-		hctemp.CreateCopy(sprite, surface_depth);
-		if (sprite_depth == 8) {
-			// only do this for 256-col -> hi-color, cos the Blit call converts
-			// transparency for 16->32 bit
-			color_t mask_color = hctemp.GetMaskColor();
-			for (int scan_y = 0; scan_y < hctemp.GetHeight(); ++scan_y) {
-				// we know this must be 1 bpp source and 2 bpp pixel destination
-				const uint8_t *src_scanline = sprite->GetScanLine(scan_y);
-				uint16_t *dst_scanline = (uint16_t *)hctemp.GetScanLineForWriting(scan_y);
-				for (int scan_x = 0; scan_x < hctemp.GetWidth(); ++scan_x) {
-					if (src_scanline[scan_x] == 0) {
-						dst_scanline[scan_x] = mask_color;
-					}
-				}
-			}
-		}
-
-		sprite = &hctemp;
+	// Allegro does not support masked blit or blend between different formats
+	// *except* when drawing 8-bit sprites onto a higher dest.
+	Bitmap conv_bm;
+	if ((surface_depth != sprite_depth) && (sprite_depth > 8)) {
+		conv_bm.CreateCopy(sprite, surface_depth);
+		sprite = &conv_bm;
 	}
 
 	if ((alpha < 0xFF) && (surface_depth > 8) && (sprite_depth > 8)) {
diff --git a/engines/ags/engine/gfx/gfx_util.h b/engines/ags/engine/gfx/gfx_util.h
index 0883ba53981..c9c167d84d4 100644
--- a/engines/ags/engine/gfx/gfx_util.h
+++ b/engines/ags/engine/gfx/gfx_util.h
@@ -57,7 +57,7 @@ void DrawSpriteBlend(Bitmap *ds, const Point &ds_at, Bitmap *sprite,
 // Draws a bitmap over another one with given alpha level (0 - 255),
 // takes account of the bitmap's mask color,
 // ignores image's alpha channel, even if there's one;
-// does proper conversion depending on respected color depths.
+// does a conversion if sprite and destination color depths do not match.
 void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int alpha = 0xFF);
 } // namespace GfxUtil
 


Commit: a7a995571b235feeb3538213626cc07d9593b878
    https://github.com/scummvm/scummvm/commit/a7a995571b235feeb3538213626cc07d9593b878
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: reorganized code in DrawingSurface_DrawImageImpl() a little

>From upstream a5f327901d5235ca2dd6a674cd47109a110bc9fd

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


diff --git a/engines/ags/engine/ac/drawing_surface.cpp b/engines/ags/engine/ac/drawing_surface.cpp
index a4cc694c21e..2d6723f8065 100644
--- a/engines/ags/engine/ac/drawing_surface.cpp
+++ b/engines/ags/engine/ac/drawing_surface.cpp
@@ -124,7 +124,13 @@ void DrawingSurface_DrawImageImpl(ScriptDrawingSurface *sds, Bitmap *src,
 	Bitmap *ds = sds->GetBitmapSurface();
 	if (src == ds) {
 	} // ignore for now; bitmap lib supports, and may be used for effects
-/* debug_script_warn("DrawingSurface.DrawImage: drawing onto itself"); */
+	/* debug_script_warn("DrawingSurface.DrawImage: drawing onto itself"); */
+	if (src->GetColorDepth() != ds->GetColorDepth()) {
+		if (sprite_id >= 0)
+			debug_script_warn("DrawImage: Sprite %d colour depth %d-bit not same as destination depth %d-bit", sprite_id, src->GetColorDepth(), ds->GetColorDepth());
+		else
+			debug_script_warn("DrawImage: Source image colour depth %d-bit not same as destination depth %d-bit", src->GetColorDepth(), ds->GetColorDepth());
+	}
 	if ((trans < 0) || (trans > 100))
 		debug_script_warn("DrawingSurface.DrawImage: invalid transparency %d, range is %d - %d", trans, 0, 100);
 	trans = Math::Clamp(trans, 0, 100);
@@ -164,6 +170,8 @@ void DrawingSurface_DrawImageImpl(ScriptDrawingSurface *sds, Bitmap *src,
 		sds->SizeToGameResolution(&src_height);
 	}
 
+	sds->PointToGameResolution(&dst_x, &dst_y);
+
 	if (dst_x >= ds->GetWidth() || dst_x + dst_width <= 0 || dst_y >= ds->GetHeight() || dst_y + dst_height <= 0 ||
 		src_x >= src->GetWidth() || src_x + src_width <= 0 || src_y >= src->GetHeight() || src_y + src_height <= 0)
 		return; // source or destination rects lie completely off surface
@@ -173,36 +181,24 @@ void DrawingSurface_DrawImageImpl(ScriptDrawingSurface *sds, Bitmap *src,
 
 	// TODO: possibly optimize by not making a stretched intermediate bitmap
 	// if simpler blit/draw_sprite could be called (no translucency with alpha channel).
-	bool needToFreeBitmap = false;
+	std::unique_ptr<Bitmap> conv_src;
 	if (dst_width != src->GetWidth() || dst_height != src->GetHeight() ||
 		src_width != src->GetWidth() || src_height != src->GetHeight()) {
 		// Resize and/or partial copy specified
-		Bitmap *newPic = BitmapHelper::CreateBitmap(dst_width, dst_height, src->GetColorDepth());
-		newPic->StretchBlt(src,
+		conv_src.reset(BitmapHelper::CreateBitmap(dst_width, dst_height, src->GetColorDepth()));
+		conv_src->StretchBlt(src,
 			RectWH(src_x, src_y, src_width, src_height),
 			RectWH(0, 0, dst_width, dst_height));
 
-		src = newPic;
-		needToFreeBitmap = true;
+		src = conv_src.get();
 	}
 
 	ds = sds->StartDrawing();
-	sds->PointToGameResolution(&dst_x, &dst_y);
-
-	if (src->GetColorDepth() != ds->GetColorDepth()) {
-		if (sprite_id >= 0)
-			debug_script_warn("DrawImage: Sprite %d colour depth %d-bit not same as destination depth %d-bit", sprite_id, src->GetColorDepth(), ds->GetColorDepth());
-		else
-			debug_script_warn("DrawImage: Source image colour depth %d-bit not same as destination depth %d-bit", src->GetColorDepth(), ds->GetColorDepth());
-	}
 
 	draw_sprite_support_alpha(ds, sds->hasAlphaChannel != 0, dst_x, dst_y, src, src_has_alpha,
 		kBlendMode_Alpha, GfxDef::Trans100ToAlpha255(trans));
 
 	sds->FinishedDrawing();
-
-	if (needToFreeBitmap)
-		delete src;
 }
 
 void DrawingSurface_DrawImage(ScriptDrawingSurface *sds,


Commit: c5b8f70d65b80c90b95eafd2846a206ca71be5f8
    https://github.com/scummvm/scummvm/commit/c5b8f70d65b80c90b95eafd2846a206ca71be5f8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: clamp return value of get_walkable_area_at_location()

>From upstream b6c425e1384665a514286b16cd14e8c9e71165bf

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


diff --git a/engines/ags/engine/ac/walkable_area.cpp b/engines/ags/engine/ac/walkable_area.cpp
index e202b937708..837d35e0f07 100644
--- a/engines/ags/engine/ac/walkable_area.cpp
+++ b/engines/ags/engine/ac/walkable_area.cpp
@@ -215,7 +215,7 @@ int get_walkable_area_at_location(int xx, int yy) {
 			onarea = 0;
 	}
 
-	return onarea;
+	return (onarea >= 0 && onarea < MAX_WALK_AREAS) ? onarea : 0;
 }
 
 int get_walkable_area_at_character(int charnum) {


Commit: 99a6c041127352d34959f411af8eec2384265bf7
    https://github.com/scummvm/scummvm/commit/99a6c041127352d34959f411af8eec2384265bf7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed DrawSpriteWithTransparency to keep mask pixels on conv

>From upstream d6df7f5cd7f01d9852a1842babb6af5332887cef

Changed paths:
    engines/ags/engine/gfx/gfx_util.cpp
    engines/ags/engine/gfx/gfx_util.h


diff --git a/engines/ags/engine/gfx/gfx_util.cpp b/engines/ags/engine/gfx/gfx_util.cpp
index 32084e375ce..23e964945d0 100644
--- a/engines/ags/engine/gfx/gfx_util.cpp
+++ b/engines/ags/engine/gfx/gfx_util.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/std/memory.h"
 #include "ags/shared/core/platform.h"
 #include "ags/engine/gfx/gfx_util.h"
 #include "ags/engine/gfx/blender.h"
@@ -105,10 +106,11 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 
 	// Allegro does not support masked blit or blend between different formats
 	// *except* when drawing 8-bit sprites onto a higher dest.
-	Bitmap conv_bm;
+	std::unique_ptr<Bitmap> conv_bm;
 	if ((surface_depth != sprite_depth) && (sprite_depth > 8)) {
-		conv_bm.CreateCopy(sprite, surface_depth);
-		sprite = &conv_bm;
+		// use ConvertBitmap in order to keep mask pixels
+		conv_bm.reset(ConvertBitmap(sprite, surface_depth));
+		sprite = conv_bm.get();
 	}
 
 	if ((alpha < 0xFF) && (surface_depth > 8) && (sprite_depth > 8)) {
diff --git a/engines/ags/engine/gfx/gfx_util.h b/engines/ags/engine/gfx/gfx_util.h
index c9c167d84d4..681204e711f 100644
--- a/engines/ags/engine/gfx/gfx_util.h
+++ b/engines/ags/engine/gfx/gfx_util.h
@@ -45,6 +45,7 @@ using Shared::Bitmap;
 
 namespace GfxUtil {
 // Creates a COPY of the source bitmap, converted to the given format.
+// Keeps mask pixels intact, only converting mask color value if necessary.
 Bitmap *ConvertBitmap(Bitmap *src, int dst_color_depth);
 
 // Considers the given information about source and destination surfaces,


Commit: 81757a11219aba0dcf7dcda6051a4efe78b6aea7
    https://github.com/scummvm/scummvm/commit/81757a11219aba0dcf7dcda6051a4efe78b6aea7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed text-based lipsync inconsistent frame delay

The problem was that the delay formula used integer division (twice), and precision loss
resulted in calculated value to "jump" up and down even from a slight change in input text length.
Fixed this by reimplementing calculation using a floating point division instead.
Used a different literal coefficient, found experimentally, to approximately match the result of the
old formula in certain key text lengths, for backwards compatibility.
>From upstream d783f5678d974b8d4cfdde3c7011b0a56dc4c3ac

Changed paths:
    engines/ags/engine/ac/display.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game.h


diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp
index 04e83f3e3b2..13b33cb9088 100644
--- a/engines/ags/engine/ac/display.cpp
+++ b/engines/ags/engine/ac/display.cpp
@@ -426,13 +426,23 @@ int GetTextDisplayLength(const char *text) {
 	return static_cast<int>(strlen(text));
 }
 
+// Calculates lipsync frame duration (or duration per character) in game loops.
+// NOTE: historical formula was this:
+//   loops_per_character = (((text_len / play.lipsync_speed) + 1) * fps) / text_len;
+// But because of a precision loss due integer division this resulted in "jumping" values.
+// The new formula uses float division, and coefficent found experimentally to make
+// results match the old formula in certain key text lengths, for backwards compatibility.
+int CalcLipsyncFrameDuration(int text_len, int fps) {
+	return static_cast<int>((((static_cast<float>(text_len) / _GP(play).lipsync_speed) + 0.75f) * fps) / text_len);
+}
+
 int GetTextDisplayTime(const char *text, int canberel) {
 	int uselen = 0;
 	auto fpstimer = ::lround(get_game_fps());
 
 	// if it's background speech, make it stay relative to game speed
 	if ((canberel == 1) && (_GP(play).bgspeech_game_speed == 1))
-		fpstimer = 40;
+		fpstimer = 40; // NOTE: should be a fixed constant here, not game speed value
 
 	if (_G(source_text_length) >= 0) {
 		// sync to length of original text, to make sure any animations
@@ -450,10 +460,7 @@ int GetTextDisplayTime(const char *text, int canberel) {
 		quit("!Text speed is zero; unable to display text. Check your _GP(game).text_speed settings.");
 
 	// Store how many game loops per character of text
-	// This is calculated using a hard-coded 15 for the text speed,
-	// so that it's always the same no matter how fast the user
-	// can read.
-	_G(loops_per_character) = (((uselen / _GP(play).lipsync_speed) + 1) * fpstimer) / uselen;
+	_G(loops_per_character) = CalcLipsyncFrameDuration(uselen, fpstimer);
 
 	int textDisplayTimeInMS = ((uselen / (_GP(play).text_speed + _GP(play).text_speed_modifier)) + 1) * 1000;
 	if (textDisplayTimeInMS < _GP(play).text_min_display_time_ms)
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 442717c62a6..626437dc2dc 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -197,6 +197,10 @@ void set_game_speed(int new_fps) {
 		setTimerFps(new_fps);
 }
 
+float get_game_speed() {
+	return _G(frames_per_second);
+}
+
 void setup_for_dialog() {
 	_G(cbuttfont) = _GP(play).normal_font;
 	_G(acdialog_font) = _GP(play).normal_font;
diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h
index acd41732a6b..9fb13a0db55 100644
--- a/engines/ags/engine/ac/game.h
+++ b/engines/ags/engine/ac/game.h
@@ -150,7 +150,10 @@ bool Game_ChangeSpeechVox(const char *newFilename);
 //=============================================================================
 
 void set_debug_mode(bool on);
+// Sets logical game FPS, telling how often the game should update
 void set_game_speed(int new_fps);
+// Gets strictly logical game FPS, regardless of whether this is real FPS right now or not.
+float get_game_speed();
 void setup_for_dialog();
 void restore_after_dialog();
 Shared::String get_save_game_directory();


Commit: 74b261a1c7d782f310927d1a96eb0a118b9f3bff
    https://github.com/scummvm/scummvm/commit/74b261a1c7d782f310927d1a96eb0a118b9f3bff
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed walkbehind sprites not recreated after walkbehinds change

>From upstream 821ee3a13a52dc6df41942f62d3f86d38f90c96d

Changed paths:
    engines/ags/engine/ac/walk_behind.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/walk_behind.cpp b/engines/ags/engine/ac/walk_behind.cpp
index 3a193e91399..a1053f51980 100644
--- a/engines/ags/engine/ac/walk_behind.cpp
+++ b/engines/ags/engine/ac/walk_behind.cpp
@@ -159,6 +159,7 @@ void walkbehinds_recalc() {
 			}
 		}
 	}
+	_G(walkBehindsCachedForBgNum) = -1;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 37dacd538d1..241c7291348 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1398,7 +1398,7 @@ public:
 	int _walkBehindLeft[MAX_WALK_BEHINDS], _walkBehindTop[MAX_WALK_BEHINDS];
 	int _walkBehindRight[MAX_WALK_BEHINDS], _walkBehindBottom[MAX_WALK_BEHINDS];
 	AGS::Engine::IDriverDependantBitmap *_walkBehindBitmap[MAX_WALK_BEHINDS];
-	int _walkBehindsCachedForBgNum = 0;
+	int _walkBehindsCachedForBgNum = -1;
 	int _walk_behind_baselines_changed = 0;
 	Rect _walkBehindAABB[MAX_WALK_BEHINDS]; // WB bounding box
 	std::vector<WalkBehindColumn> _walkBehindCols; // precalculated WB positions


Commit: 3e57cad5deb48a5c574d12933fc0275211966b86
    https://github.com/scummvm/scummvm/commit/3e57cad5deb48a5c574d12933fc0275211966b86
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed CopyScreenIntoBitmap not applying filter when resizing

>From upstream f1bca5eaa7ba2f2eb1363e7198a6f9fcf3518d8d

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


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 53b4a181f1b..ce1b209b8ab 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -261,7 +261,7 @@ Bitmap *CopyScreenIntoBitmap(int width, int height, const Rect *src_rect, bool a
 	// Otherwise we might need to copy between few bitmaps...
 	// Get screenshot in the suitable format
 	std::unique_ptr<Bitmap> buf_screenfmt(new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth));
-	_G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt.get(), src_rect, at_native_res);
+	_G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt.get(), src_rect, at_native_res, nullptr, batch_skip_filter);
 	// If color depth does not match, and we must stretch-blit, then we need another helper bmp,
 	// because Allegro does not support stretching with mismatching color depths
 	std::unique_ptr<Bitmap> buf_fixdepth;


Commit: ec9f0dcb51d4310e89ff0c41d7c5d446d8dfacdb
    https://github.com/scummvm/scummvm/commit/ec9f0dcb51d4310e89ff0c41d7c5d446d8dfacdb
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed a comment with obsolete info and link to the manual

>From upstream 470e9085441e502fb29b142ed2216e5d9467e231

Changed paths:
    engines/ags/shared/ac/keycode.h


diff --git a/engines/ags/shared/ac/keycode.h b/engines/ags/shared/ac/keycode.h
index 1ea2d62b2b0..5c9671ae315 100644
--- a/engines/ags/shared/ac/keycode.h
+++ b/engines/ags/shared/ac/keycode.h
@@ -221,9 +221,8 @@ enum eAGSKeyCode {
 	// not certain if necessary anymore (and not certain what was the origin of this value)
 	eAGSKeyCodeAltTab = AGS_EXT_KEY_SHIFT + 99,
 
-	// These keys are not defined in the script eAGSKey enum but are in the manual
-	// https://adventuregamestudio.github.io/ags-manual/ASCIIcodes.html
-	// *Probably* made-up numbers not derived from allegro scan codes.
+	// Mod-key codes
+	// *probably* made-up numbers, not derived from allegro scan codes.
 	eAGSKeyCodeLShift = 403,
 	eAGSKeyCodeRShift = 404,
 	eAGSKeyCodeLCtrl = 405,


Commit: 1fad505ba5d8f190fa27d7bec082d6c4aa642421
    https://github.com/scummvm/scummvm/commit/1fad505ba5d8f190fa27d7bec082d6c4aa642421
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add old_keyhandle flag to ags_simulate_keypress

The flag is not really used, and scummvm has a different implementation.
>From upstream babb4a9cce674bfa1338de1213956791af6181ca

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/sys_events.cpp
    engines/ags/engine/ac/sys_events.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 626437dc2dc..b8d07e343ba 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -693,7 +693,7 @@ ScriptCamera *Game_GetAnyCamera(int index) {
 }
 
 void Game_SimulateKeyPress(int key) {
-	ags_simulate_keypress(static_cast<eAGSKeyCode>(key));
+	ags_simulate_keypress(static_cast<eAGSKeyCode>(key), (_GP(game).options[OPT_KEYHANDLEAPI] == 0));
 }
 
 int Game_BlockingWaitSkipped() {
diff --git a/engines/ags/engine/ac/sys_events.cpp b/engines/ags/engine/ac/sys_events.cpp
index 0b120ca8f59..f8a04163924 100644
--- a/engines/ags/engine/ac/sys_events.cpp
+++ b/engines/ags/engine/ac/sys_events.cpp
@@ -88,7 +88,7 @@ int ags_iskeydown(eAGSKeyCode ags_key) {
 	return ::AGS::g_events->isKeyPressed(ags_key, _GP(game).options[OPT_KEYHANDLEAPI] == 0);
 }
 
-void ags_simulate_keypress(eAGSKeyCode ags_key) {
+void ags_simulate_keypress(eAGSKeyCode ags_key, bool old_keyhandle) {
 	Common::KeyCode keycode[3];
 	if (!::AGS::EventsManager::ags_key_to_scancode(ags_key, keycode))
 		return;
diff --git a/engines/ags/engine/ac/sys_events.h b/engines/ags/engine/ac/sys_events.h
index dc4f4ebe249..10bc234a69e 100644
--- a/engines/ags/engine/ac/sys_events.h
+++ b/engines/ags/engine/ac/sys_events.h
@@ -63,7 +63,7 @@ extern Common::Event ags_get_next_keyevent();
 // NOTE: for particular script codes this function returns positive if either of two keys are down.
 extern int ags_iskeydown(eAGSKeyCode ags_key);
 // Simulates key press with the given AGS key
-extern void ags_simulate_keypress(eAGSKeyCode ags_key);
+extern void ags_simulate_keypress(eAGSKeyCode ags_key, bool old_keyhandle);
 
 
 // Mouse input handling


Commit: 8e915febdea3481243f09fbc3314fdbd4ac26f59
    https://github.com/scummvm/scummvm/commit/8e915febdea3481243f09fbc3314fdbd4ac26f59
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed callstack not reported in case of script api errors

This was broken by 9e76800
>From upstream 716a36aa0252a7481d299d4e409eed2d56239bb3

Changed paths:
    engines/ags/engine/main/quit.cpp
    engines/ags/shared/script/cc_common.cpp
    engines/ags/shared/script/cc_common.h


diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp
index 51904144b69..e9a2b1aaa98 100644
--- a/engines/ags/engine/main/quit.cpp
+++ b/engines/ags/engine/main/quit.cpp
@@ -117,7 +117,7 @@ QuitReason quit_check_for_error_state(const char *qmsg, String &errmsg, String &
 			               "(Engine version %s)\n\n", _G(EngineVersion).LongString.GetCStr());
 		}
 
-		alertis.Append(cc_get_error().CallStack);
+		alertis.Append(cc_get_err_callstack());
 
 		if (qreason != kQuit_UserAbort) {
 			alertis.AppendFmt("\nError: %s", qmsg);
@@ -129,7 +129,7 @@ QuitReason quit_check_for_error_state(const char *qmsg, String &errmsg, String &
 		qmsg++;
 		alertis.Format("A warning has been generated. This is not normally fatal, but you have selected "
 		               "to treat warnings as errors.\n"
-		               "(Engine version %s)\n\n%s\n%s", _G(EngineVersion).LongString.GetCStr(), cc_get_error().CallStack.GetCStr(), qmsg);
+		               "(Engine version %s)\n\n%s\n%s", _G(EngineVersion).LongString.GetCStr(), cc_get_err_callstack().GetCStr(), qmsg);
 		errmsg = qmsg;
 		return kQuit_GameWarning;
 	} else {
diff --git a/engines/ags/shared/script/cc_common.cpp b/engines/ags/shared/script/cc_common.cpp
index c6c95e0a49d..d0a7260c7ef 100644
--- a/engines/ags/shared/script/cc_common.cpp
+++ b/engines/ags/shared/script/cc_common.cpp
@@ -54,6 +54,10 @@ const ScriptError &cc_get_error() {
 	return _GP(ccError);
 }
 
+String cc_get_err_callstack(int max_lines) {
+	return cc_has_error() ? _GP(ccError).CallStack : cc_get_callstack(max_lines);
+}
+
 void cc_error(const char *descr, ...) {
 	_GP(ccError).IsUserError = false;
 	if (descr[0] == '!') {
diff --git a/engines/ags/shared/script/cc_common.h b/engines/ags/shared/script/cc_common.h
index 19c758a29fa..3b4dad68bd6 100644
--- a/engines/ags/shared/script/cc_common.h
+++ b/engines/ags/shared/script/cc_common.h
@@ -54,6 +54,9 @@ struct ScriptError {
 void cc_clear_error();
 bool cc_has_error();
 const ScriptError &cc_get_error();
+// Returns callstack of the last recorded script error, or a callstack
+// of a current execution point, if no script error is currently saved in memory.
+AGS::Shared::String cc_get_err_callstack(int max_lines = INT_MAX);
 void cc_error(const char *, ...);
 void cc_error(const ScriptError &err);
 // Project-dependent script error formatting


Commit: 6ee1a4978f3615a0eea55862671ec1161fcee9a4
    https://github.com/scummvm/scummvm/commit/6ee1a4978f3615a0eea55862671ec1161fcee9a4
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: expanded error messages for character being in wrong room

>From upstream f1cdee7b689e6b12c14c593d1c540e2c13808554

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


diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp
index dbae6dde16c..e388604e734 100644
--- a/engines/ags/engine/ac/character.cpp
+++ b/engines/ags/engine/ac/character.cpp
@@ -140,7 +140,8 @@ void Character_AddInventory(CharacterInfo *chaa, ScriptInvItem *invi, int addInd
 void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
 
 	if (chaa->room != _G(displayed_room))
-		quit("!MoveCharacterPath: specified character not in current room");
+		quitprintf("!MoveCharacterPath: character %s is not in current room %d (it is in room %d)",
+				   chaa->scrname, _G(displayed_room), chaa->room);
 
 	// not already walking, so just do a normal move
 	if (chaa->walking <= 0) {
@@ -424,7 +425,8 @@ void Character_FaceCharacter(CharacterInfo *char1, CharacterInfo *char2, int blo
 		quit("!FaceCharacter: invalid character specified");
 
 	if (char1->room != char2->room)
-		quit("!FaceCharacter: characters are in different rooms");
+		quitprintf("!FaceCharacter: characters %s and %s are in different rooms (room %d and room %d respectively)",
+				   char1->scrname, char2->scrname, char1->room, char2->room);
 
 	FaceLocationXY(char1, char2->x, char2->y, blockingStyle);
 }
@@ -436,7 +438,8 @@ void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int
 
 	if ((chaa->index_id == _GP(game).playercharacter) && (tofollow != nullptr) &&
 	        (tofollow->room != chaa->room))
-		quit("!FollowCharacterEx: you cannot tell the player character to follow a character in another room");
+		quitprintf("!FollowCharacterEx: you cannot tell the player character %s, who is in room %d, to follow a character %s who is in another room %d",
+				   chaa->scrname, chaa->room, tofollow->scrname, tofollow->room);
 
 	if (tofollow != nullptr) {
 		debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname, tofollow->scrname, distaway, eagerness);
@@ -970,7 +973,8 @@ void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct)
 void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking) {
 
 	if (chaa->room != _G(displayed_room))
-		quit("!MoveCharacterStraight: specified character not in current room");
+		quitprintf("!MoveCharacterStraight: character %s is not in current room %d (it is in room %d)",
+				   chaa->scrname, _G(displayed_room), chaa->room);
 
 	int movetox = xx, movetoy = yy;
 
@@ -1600,7 +1604,8 @@ int turnlooporder[8] = {0, 6, 1, 7, 3, 5, 2, 4};
 void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims) {
 	CharacterInfo *chin = &_GP(game).chars[chac];
 	if (chin->room != _G(displayed_room))
-		quit("!MoveCharacter: character not in current room");
+		quitprintf("!MoveCharacter: character %s is not in current room %d (it is in room %d)",
+				   chin->scrname, _G(displayed_room), chin->room);
 
 	chin->flags &= ~CHF_MOVENOTWALK;
 
diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp
index dd1a5a048c6..6b254467b75 100644
--- a/engines/ags/engine/ac/global_game.cpp
+++ b/engines/ags/engine/ac/global_game.cpp
@@ -464,7 +464,8 @@ void SkipUntilCharacterStops(int cc) {
 	if (!is_valid_character(cc))
 		quit("!SkipUntilCharacterStops: invalid character specified");
 	if (_GP(game).chars[cc].room != _G(displayed_room))
-		quit("!SkipUntilCharacterStops: specified character not in current room");
+		quitprintf("!SkipUntilCharacterStops: character %s is not in current room %d (it is in room %d)",
+				   _GP(game).chars[cc].scrname, _G(displayed_room), _GP(game).chars[cc].room);
 
 	// if they are not currently moving, do nothing
 	if (!_GP(game).chars[cc].walking)


Commit: 8a0cc8e3f912b2979fc2a76b7c526ecb21dcba51
    https://github.com/scummvm/scummvm/commit/8a0cc8e3f912b2979fc2a76b7c526ecb21dcba51
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Engine: fixed ReadGameData() to read GUID before logging

It worked in most cases because there's PreReadGameData() called earlier, but the mistake
can be noticed in case RunAGSGame is run.
>From upstream 4c2cb8e3ae9144de7080cc29d7a003901ab276c0

Changed paths:
    engines/ags/shared/game/main_game_file.cpp


diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp
index e4cefd0a3a3..69d086f7918 100644
--- a/engines/ags/shared/game/main_game_file.cpp
+++ b/engines/ags/shared/game/main_game_file.cpp
@@ -807,10 +807,11 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	GameSetupStruct &game = ents.Game;
 
 	//-------------------------------------------------------------------------
-	// The classic data section.
+	// The standard data section.
 	//-------------------------------------------------------------------------
 	GameSetupStruct::SerializeInfo sinfo;
 	game.GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
+	game.read_savegame_info(in, data_ver); // here we also read GUID in v3.* games
 
 	Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename.GetCStr());
 	Debug::Printf(kDbgMsg_Info, "Game uid (old format): `%d`", game.uniqueid);
@@ -819,7 +820,6 @@ HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersio
 	if (game.GetGameRes().IsNull())
 		return new MainGameFileError(kMGFErr_InvalidNativeResolution);
 
-	game.read_savegame_info(in, data_ver);
 	game.read_font_infos(in, data_ver);
 	HGameFileError err = ReadSpriteFlags(ents, in, data_ver);
 	if (!err)
@@ -919,8 +919,8 @@ HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver
 
 void PreReadGameData(GameSetupStruct &game, Stream *in, GameDataVersion data_ver) {
 	GameSetupStruct::SerializeInfo sinfo;
-	_GP(game).ReadFromFile(in, data_ver, sinfo);
-	_GP(game).read_savegame_info(in, data_ver);
+	_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, sinfo);
+	_GP(game).read_savegame_info(in, data_ver);  // here we also read GUID in v3.* games
 
 	// Check for particular expansions that might have data necessary
 	// for "preload" purposes


Commit: f0c92b61562802eda508cd4318c1a07b34f75097
    https://github.com/scummvm/scummvm/commit/f0c92b61562802eda508cd4318c1a07b34f75097
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Updated build version (3.6.1.29 P7)

Partially from upstream 4bfe25b0f7f780a0f8f8c71dc455de6d2c69a681

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 2030a1eadb4..cc4795a7d5a 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.26"
+#define ACI_VERSION_STR      "3.6.1.29"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.1.26
+#define ACI_VERSION_MSRC_DEF  3.6.1.29
 #endif
 
 #define SPECIAL_VERSION ""


Commit: a19929bd30f6597d9ffbf73283994777eda05dda
    https://github.com/scummvm/scummvm/commit/a19929bd30f6597d9ffbf73283994777eda05dda
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix operand typo

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


diff --git a/engines/ags/engine/ac/character_info_engine.cpp b/engines/ags/engine/ac/character_info_engine.cpp
index 05c358e8270..45ea2c433b8 100644
--- a/engines/ags/engine/ac/character_info_engine.cpp
+++ b/engines/ags/engine/ac/character_info_engine.cpp
@@ -400,10 +400,10 @@ void CharacterInfo::update_character_follower(int &aa, std::vector<int> &followi
 				}
 			}
 		} else if (room != _G(displayed_room)) {
-			// if the characetr is following another character and
+			// if the character is following another character and
 			// neither is in the current room, don't try to move
-		} else if ((abs(_GP(game).chars[following].x - x) > distaway + 30) |
-		           (abs(_GP(game).chars[following].y - y) > distaway + 30) |
+		} else if ((abs(_GP(game).chars[following].x - x) > distaway + 30) ||
+		           (abs(_GP(game).chars[following].y - y) > distaway + 30) ||
 		           ((followinfo & 0x00ff) == 0)) {
 			// in same room
 			int goxoffs = (Random(50) - 25);


Commit: 9778105b8f5b3f55843352090a3bd788b6c79f2d
    https://github.com/scummvm/scummvm/commit/9778105b8f5b3f55843352090a3bd788b6c79f2d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Add missing override keyword

Changed paths:
    engines/ags/plugins/ags_flashlight/ags_flashlight.h


diff --git a/engines/ags/plugins/ags_flashlight/ags_flashlight.h b/engines/ags/plugins/ags_flashlight/ags_flashlight.h
index 190a86b06c9..42e84018a46 100644
--- a/engines/ags/plugins/ags_flashlight/ags_flashlight.h
+++ b/engines/ags/plugins/ags_flashlight/ags_flashlight.h
@@ -116,7 +116,7 @@ public:
 	const char *AGS_GetPluginName() override;
 	void AGS_EngineStartup(IAGSEngine *engine) override;
 	int64 AGS_EngineOnEvent(int event, NumberPtr data) override;
-	int AGS_PluginV2() const { return 1; };
+	int AGS_PluginV2() const override { return 1; };
 };
 
 } // namespace AGSFlashlight


Commit: cf3fcb8073da5999ac8f3ad61b349d2e342789a8
    https://github.com/scummvm/scummvm/commit/cf3fcb8073da5999ac8f3ad61b349d2e342789a8
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Move DrawFPS to globals

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index ce1b209b8ab..b4b80d29041 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1719,26 +1719,20 @@ PBitmap draw_room_background(Viewport *view) {
 	return _GP(CameraDrawData)[view_index].Frame;
 }
 
-struct DrawFPS {
-	IDriverDependantBitmap *ddb = nullptr;
-	std::unique_ptr<Bitmap> bmp;
-	int font = -1; // in case normal font changes at runtime
-} gl_DrawFPS;
-
 void dispose_engine_overlay() {
-	gl_DrawFPS.bmp.reset();
-	if (gl_DrawFPS.ddb)
-		_G(gfxDriver)->DestroyDDB(gl_DrawFPS.ddb);
-	gl_DrawFPS.ddb = nullptr;
-	gl_DrawFPS.font = -1;
+	_G(gl_DrawFPS).bmp.reset();
+	if (_G(gl_DrawFPS).ddb)
+		_G(gfxDriver)->DestroyDDB(_G(gl_DrawFPS).ddb);
+	_G(gl_DrawFPS).ddb = nullptr;
+	_G(gl_DrawFPS).font = -1;
 }
 
 void draw_fps(const Rect &viewport) {
 	const int font = FONT_NORMAL;
-	auto &fpsDisplay = gl_DrawFPS.bmp;
-	if (fpsDisplay == nullptr || gl_DrawFPS.font != font) {
+	auto &fpsDisplay = _G(gl_DrawFPS).bmp;
+	if (fpsDisplay == nullptr || _G(gl_DrawFPS).font != font) {
 		recycle_bitmap(fpsDisplay, _GP(game).GetColorDepth(), viewport.GetWidth(), (get_font_surface_height(font) + get_fixed_pixel_size(5)));
-		gl_DrawFPS.font = font;
+		_G(gl_DrawFPS).font = font;
 	}
 
 	fpsDisplay->ClearTransparent();
@@ -1764,10 +1758,10 @@ void draw_fps(const Rect &viewport) {
 	int text_off = get_font_surface_extent(font).first; // TODO: a generic function that accounts for this?
 	wouttext_outline(fpsDisplay.get(), 1, 1 - text_off, font, text_color, fps_buffer);
 	wouttext_outline(fpsDisplay.get(), viewport.GetWidth() / 2, 1 - text_off, font, text_color, loop_buffer);
-	gl_DrawFPS.ddb = recycle_ddb_bitmap(gl_DrawFPS.ddb, gl_DrawFPS.bmp.get());
+	_G(gl_DrawFPS).ddb = recycle_ddb_bitmap(_G(gl_DrawFPS).ddb, _G(gl_DrawFPS).bmp.get());
 	int yp = viewport.GetHeight() - fpsDisplay->GetHeight();
-	_G(gfxDriver)->DrawSprite(1, yp, gl_DrawFPS.ddb);
-	invalidate_sprite_glob(1, yp, gl_DrawFPS.ddb);
+	_G(gfxDriver)->DrawSprite(1, yp, _G(gl_DrawFPS).ddb);
+	invalidate_sprite_glob(1, yp, _G(gl_DrawFPS).ddb);
 }
 
 // Draw GUI controls as separate sprites
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index fa208581109..3f4b5056786 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -120,6 +120,12 @@ struct ObjectCache {
 		, lightlev(light_), zoom(zoom_), mirrored(mirror_), x(posx_), y(posy_) {}
 };
 
+struct DrawFPS {
+	Engine::IDriverDependantBitmap *ddb = nullptr;
+	std::unique_ptr<Shared::Bitmap> bmp;
+	int font = -1; // in case normal font changes at runtime
+};
+
 // Converts AGS color index to the actual bitmap color using game's color depth
 int MakeColor(int color_index);
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 241c7291348..617a261ee9a 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -621,6 +621,8 @@ public:
 	std::vector<Engine::IDriverDependantBitmap *> *_guiobjddb;
 	std::vector<Point> *_guiobjoff; // because surface may be larger than logical position
 
+	DrawFPS _gl_DrawFPS;
+
 	/**@}*/
 
 	/**


Commit: 8d90ee9c2c2d9fd41d4e180b1182d5ec401b3651
    https://github.com/scummvm/scummvm/commit/8d90ee9c2c2d9fd41d4e180b1182d5ec401b3651
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Move valid_handles to globals

Changed paths:
    engines/ags/engine/ac/file.cpp
    engines/ags/engine/ac/file.h
    engines/ags/engine/ac/global_file.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/file.cpp b/engines/ags/engine/ac/file.cpp
index 4f30a2f7927..8ac88cb2ba6 100644
--- a/engines/ags/engine/ac/file.cpp
+++ b/engines/ags/engine/ac/file.cpp
@@ -580,15 +580,11 @@ AssetPath get_voice_over_assetpath(const String &filename) {
 	return AssetPath(filename, "voice");
 }
 
-ScriptFileHandle valid_handles[MAX_OPEN_SCRIPT_FILES + 1];
-// [IKM] NOTE: this is not precisely the number of files opened at this moment,
-// but rather maximal number of handles that were used simultaneously during game run
-int num_open_script_files = 0;
 ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name) {
 	if (stream_ptr) {
-		for (int i = 0; i < num_open_script_files; ++i) {
-			if (stream_ptr == valid_handles[i].stream.get()) {
-				return &valid_handles[i];
+		for (int i = 0; i < _G(num_open_script_files); ++i) {
+			if (stream_ptr == _G(valid_handles)[i].stream.get()) {
+				return &_G(valid_handles)[i];
 			}
 		}
 	}
@@ -600,9 +596,9 @@ ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *op
 
 ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *operation_name) {
 	if (handle > 0) {
-		for (int i = 0; i < num_open_script_files; ++i) {
-			if (handle == valid_handles[i].handle) {
-				return &valid_handles[i];
+		for (int i = 0; i < _G(num_open_script_files); ++i) {
+			if (handle == _G(valid_handles)[i].handle) {
+				return &_G(valid_handles)[i];
 			}
 		}
 	}
diff --git a/engines/ags/engine/ac/file.h b/engines/ags/engine/ac/file.h
index afa059c74dc..9a5ab38485e 100644
--- a/engines/ags/engine/ac/file.h
+++ b/engines/ags/engine/ac/file.h
@@ -62,8 +62,6 @@ struct ScriptFileHandle {
 	std::unique_ptr<Stream> stream;
 	int32_t  handle = 0;
 };
-extern ScriptFileHandle valid_handles[MAX_OPEN_SCRIPT_FILES + 1];
-extern int num_open_script_files;
 
 ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name);
 ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *operation_name);
diff --git a/engines/ags/engine/ac/global_file.cpp b/engines/ags/engine/ac/global_file.cpp
index 3eeb4b800b5..c6a927e32d8 100644
--- a/engines/ags/engine/ac/global_file.cpp
+++ b/engines/ags/engine/ac/global_file.cpp
@@ -53,13 +53,12 @@ int32_t FileOpenCMode(const char *fnmm, const char *cmode) {
 // Find a free file slot to use
 int32_t FindFreeFileSlot() {
 	int useindx = 0;
-	for (; useindx < num_open_script_files; useindx++) {
-		if (valid_handles[useindx].stream == nullptr)
+	for (; useindx < _G(num_open_script_files); useindx++) {
+		if (_G(valid_handles)[useindx].stream == nullptr)
 			break;
 	}
 
-	if (useindx >= num_open_script_files &&
-	        num_open_script_files >= MAX_OPEN_SCRIPT_FILES) {
+	if (useindx >= _G(num_open_script_files) && _G(num_open_script_files) >= MAX_OPEN_SCRIPT_FILES) {
 		quit("!FileOpen: tried to open more than 10 files simultaneously - close some first");
 		return -1;
 	}
@@ -96,17 +95,17 @@ int32_t FileOpen(const char *fnmm, Shared::FileOpenMode open_mode, Shared::FileW
 		}
 	}
 
-	valid_handles[useindx].stream.reset(s);
-	if (valid_handles[useindx].stream == nullptr) {
+	_G(valid_handles)[useindx].stream.reset(s);
+	if (_G(valid_handles)[useindx].stream == nullptr) {
 		debug_script_warn("FileOpen: FAILED: %s", resolved_path.GetCStr());
 		return 0;
 	}
-	valid_handles[useindx].handle = useindx + 1; // make handle indexes 1-based
+	_G(valid_handles)[useindx].handle = useindx + 1; // make handle indexes 1-based
 	debug_script_print(kDbgMsg_Info, "FileOpen: success: %s", resolved_path.GetCStr());
 
-	if (useindx >= num_open_script_files)
-		num_open_script_files++;
-	return valid_handles[useindx].handle;
+	if (useindx >= _G(num_open_script_files))
+		_G(num_open_script_files)++;
+	return _G(valid_handles)[useindx].handle;
 }
 
 void FileClose(int32_t handle) {
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 617a261ee9a..7740e90f768 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -37,6 +37,7 @@
 #include "ags/shared/script/cc_script.h"
 #include "ags/engine/ac/dynobj/script_game.h"
 #include "ags/engine/ac/event.h"
+#include "ags/engine/ac/file.h"
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/walk_behind.h"
 #include "ags/engine/main/engine.h"
@@ -709,6 +710,11 @@ public:
 	// Installation directory, containing voice-over files
 	String _installVoiceDirectory;
 
+	ScriptFileHandle _valid_handles[MAX_OPEN_SCRIPT_FILES + 1];
+	// [IKM] NOTE: this is not precisely the number of files opened at this moment,
+	// but rather maximal number of handles that were used simultaneously during game run
+	int _num_open_script_files = 0;
+
 	/**@}*/
 
 	/**


Commit: 5275aaaeec51543e888ccc7dcb785bc59dcf6b4d
    https://github.com/scummvm/scummvm/commit/5275aaaeec51543e888ccc7dcb785bc59dcf6b4d
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Move room_statuses to globals

Changed paths:
    engines/ags/engine/ac/room_status.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/room_status.cpp b/engines/ags/engine/ac/room_status.cpp
index fccd42af96b..1c2b0309b76 100644
--- a/engines/ags/engine/ac/room_status.cpp
+++ b/engines/ags/engine/ac/room_status.cpp
@@ -210,15 +210,13 @@ void RoomStatus::WriteToSavegame(Stream *out, GameDataVersion data_ver) const {
 	out->WriteInt32(0);
 }
 
-std::unique_ptr<RoomStatus> room_statuses[MAX_ROOMS];
-
 // Replaces all accesses to the roomstats array
 RoomStatus *getRoomStatus(int room) {
-	if (!room_statuses[room]) {
+	if (!_G(room_statuses)[room]) {
 		// First access, allocate and initialise the status
-		room_statuses[room].reset(new RoomStatus());
+		_G(room_statuses)[room].reset(new RoomStatus());
 	}
-	return room_statuses[room].get();
+	return _G(room_statuses)[room].get();
 }
 
 // Used in places where it is only important to know whether the player
@@ -226,12 +224,12 @@ RoomStatus *getRoomStatus(int room) {
 // to initialise the status because a player can only have been in
 // a room if the status is already initialised.
 bool isRoomStatusValid(int room) {
-	return (room_statuses[room] != nullptr);
+	return (_G(room_statuses)[room] != nullptr);
 }
 
 void resetRoomStatuses() {
 	for (int i = 0; i < MAX_ROOMS; i++) {
-		room_statuses[i].reset();
+		_G(room_statuses)[i].reset();
 	}
 }
 
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 7740e90f768..d2e28fbf36c 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -1221,6 +1221,7 @@ public:
 	int _new_room_flags = 0;
 	int _gs_to_newroom = -1;
 	int _bg_just_changed = 0;
+	std::unique_ptr<RoomStatus> _room_statuses[MAX_ROOMS];
 
 	/**@}*/
 


Commit: 281d798b84e04a8be17d585a6d011b2a31002a48
    https://github.com/scummvm/scummvm/commit/281d798b84e04a8be17d585a6d011b2a31002a48
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Move globalDynamicStruct to globals

Changed paths:
    engines/ags/engine/ac/dynobj/script_user_object.cpp
    engines/ags/engine/ac/dynobj/script_user_object.h
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/dynobj/script_user_object.cpp b/engines/ags/engine/ac/dynobj/script_user_object.cpp
index 35a62f76d0e..38dee0bba59 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.cpp
+++ b/engines/ags/engine/ac/dynobj/script_user_object.cpp
@@ -23,6 +23,7 @@
 #include "ags/shared/util/stream.h"
 #include "ags/engine/ac/dynobj/script_user_object.h"
 #include "ags/engine/ac/dynobj/dynobj_manager.h"
+#include "ags/globals.h"
 
 namespace AGS3 {
 
@@ -41,12 +42,12 @@ const char *ScriptUserObject::GetType() {
 	Header &hdr = reinterpret_cast<Header &>(*new_data);
 	hdr.Size = size;
 	void *obj_ptr = &new_data[MemHeaderSz];
-	int32_t handle = ccRegisterManagedObject(obj_ptr, &globalDynamicStruct);
+	int32_t handle = ccRegisterManagedObject(obj_ptr, &_G(globalDynamicStruct));
 	if (handle == 0) {
 		delete[] new_data;
 		return DynObjectRef();
 	}
-	return DynObjectRef(handle, obj_ptr, &globalDynamicStruct);
+	return DynObjectRef(handle, obj_ptr, &_G(globalDynamicStruct));
 }
 
 int ScriptUserObject::Dispose(void *address, bool /*force*/) {
@@ -73,8 +74,6 @@ void ScriptUserObject::Unserialize(int index, Stream *in, size_t data_sz) {
 	ccRegisterUnserializedObject(index, &new_data[MemHeaderSz], this);
 }
 
-ScriptUserObject globalDynamicStruct;
-
 // Allocates managed struct containing two ints: X and Y
 ScriptUserObject *ScriptStructHelpers::CreatePoint(int x, int y) {
 	DynObjectRef ref = ScriptUserObject::Create(sizeof(int32_t) * 2);
diff --git a/engines/ags/engine/ac/dynobj/script_user_object.h b/engines/ags/engine/ac/dynobj/script_user_object.h
index 19aef8a4ff8..374f9b6846f 100644
--- a/engines/ags/engine/ac/dynobj/script_user_object.h
+++ b/engines/ags/engine/ac/dynobj/script_user_object.h
@@ -75,8 +75,6 @@ private:
 	void Serialize(const void *address, AGS::Shared::Stream *out) override;
 };
 
-extern ScriptUserObject globalDynamicStruct;
-
 // Helper functions for setting up custom managed structs based on ScriptUserObject.
 namespace ScriptStructHelpers {
 // Creates a managed Point object, represented as a pair of X and Y coordinates.
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index d2e28fbf36c..8333b31d47c 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -36,6 +36,7 @@
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/script/cc_script.h"
 #include "ags/engine/ac/dynobj/script_game.h"
+#include "ags/engine/ac/dynobj/script_user_object.h"
 #include "ags/engine/ac/event.h"
 #include "ags/engine/ac/file.h"
 #include "ags/engine/ac/runtime_defines.h"
@@ -1298,7 +1299,7 @@ public:
 	/**@}*/
 
 	/**
-	 * @defgroup agsscript_runtimeglobals agscript_string script_runtime globals
+	 * @defgroup agsscript_runtimeglobals agsscript_user_object agscript_string script_runtime globals
 	 * @ingroup agsglobals
 	 * @{
 	 */
@@ -1315,6 +1316,7 @@ public:
 	unsigned _maxWhileLoops = 0u;
 	ccInstance *_loadedInstances[MAX_LOADED_INSTANCES];
 	ScriptString *_myScriptStringImpl;
+	ScriptUserObject _globalDynamicStruct;
 
 	/**@}*/
 


Commit: dcf9cfd880ac587ac5ecb2fd73bde45ce7ef0838
    https://github.com/scummvm/scummvm/commit/dcf9cfd880ac587ac5ecb2fd73bde45ce7ef0838
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Move drawstate to globals

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index b4b80d29041..6a58a778a14 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -53,7 +53,6 @@
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/view_frame.h"
 #include "ags/engine/ac/walkable_area.h"
-#include "ags/engine/ac/walk_behind.h"
 #include "ags/engine/ac/dynobj/script_system.h"
 #include "ags/engine/debugging/debugger.h"
 #include "ags/engine/debugging/debug_log.h"
@@ -77,37 +76,6 @@ namespace AGS3 {
 using namespace AGS::Shared;
 using namespace AGS::Engine;
 
-// TODO: refactor the draw unit into a virtual interface with
-// two implementations: for software and video-texture render,
-// instead of checking whether the current method is "software".
-struct DrawState {
-	// Whether we should use software rendering methods
-	// (aka raw draw), as opposed to video texture transform & fx
-	bool SoftwareRender = false;
-	// Whether we should redraw whole game screen each frame
-	bool FullFrameRedraw = false;
-	// Walk-behinds representation
-	WalkBehindMethodEnum WalkBehindMethod = DrawAsSeparateSprite;
-	// Whether there are currently remnants of a on-screen effect
-	bool ScreenIsDirty = false;
-
-	// A map of shared "control blocks" per each sprite used
-	// when preparing object textures. "Control block" is currently just
-	// an integer which lets to check whether the object texture is in sync
-	// with the sprite. When the dynamic sprite is updated or deleted,
-	// the control block is marked as invalid and removed from the map;
-	// but certain objects may keep the shared ptr to the old block with
-	// "invalid" mark, thus they know that they must reset their texture.
-	//
-	// TODO: investigate an alternative of having a equivalent of
-	// "shared texture" with sprite ID ref in Software renderer too,
-	// which would allow to use same method of testing DDB ID for both
-	// kinds of renderers, thus saving on 1 extra notification mechanism.
-	std::unordered_map<sprkey_t, std::shared_ptr<uint32_t> >
-		SpriteNotifyMap;
-};
-
-DrawState drawstate;
 
 ObjTexture::ObjTexture(ObjTexture &&o) {
 	*this = std::move(o);
@@ -398,14 +366,14 @@ int MakeColor(int color_index) {
 }
 
 void init_draw_method() {
-	drawstate.SoftwareRender = !_G(gfxDriver)->HasAcceleratedTransform();
-	drawstate.FullFrameRedraw = _G(gfxDriver)->RequiresFullRedrawEachFrame();
+	_G(drawstate).SoftwareRender = !_G(gfxDriver)->HasAcceleratedTransform();
+	_G(drawstate).FullFrameRedraw = _G(gfxDriver)->RequiresFullRedrawEachFrame();
 
-	if (drawstate.SoftwareRender) {
-		drawstate.SoftwareRender = true;
-		drawstate.WalkBehindMethod = DrawOverCharSprite;
+	if (_G(drawstate).SoftwareRender) {
+		_G(drawstate).SoftwareRender = true;
+		_G(drawstate).WalkBehindMethod = DrawOverCharSprite;
 	} else {
-		drawstate.WalkBehindMethod = DrawAsSeparateSprite;
+		_G(drawstate).WalkBehindMethod = DrawAsSeparateSprite;
 		create_blank_image(_GP(game).GetColorDepth());
 		size_t tx_cache_size = _GP(usetup).TextureCacheSize * 1024;
 		// If graphics driver can report available texture memory,
@@ -499,13 +467,13 @@ void clear_drawobj_cache() {
 	_GP(overtxs).clear();
 
 	// Clear sprite update notification blocks
-	drawstate.SpriteNotifyMap.clear();
+	_G(drawstate).SpriteNotifyMap.clear();
 
 	dispose_debug_room_drawdata();
 }
 
 void on_mainviewport_changed() {
-	if (!drawstate.FullFrameRedraw) {
+	if (!_G(drawstate).FullFrameRedraw) {
 		const auto &view = _GP(play).GetMainViewport();
 		set_invalidrects_globaloffs(view.Left, view.Top);
 		// the black background region covers whole game screen
@@ -563,7 +531,7 @@ void init_room_drawdata() {
 	if (_G(displayed_room) < 0)
 		return; // not loaded yet
 
-	if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
+	if (_G(drawstate).WalkBehindMethod == DrawAsSeparateSprite) {
 		walkbehinds_generate_sprites();
 	}
 	// Update debug overlays, if any were on
@@ -571,7 +539,7 @@ void init_room_drawdata() {
 	debug_draw_movelist(_G(debugMoveListChar));
 
 	// Following data is only updated for software renderer
-	if (drawstate.FullFrameRedraw)
+	if (_G(drawstate).FullFrameRedraw)
 		return;
 	// Make sure all frame buffers are created for software drawing
 	int view_count = _GP(play).GetRoomViewportCount();
@@ -581,7 +549,7 @@ void init_room_drawdata() {
 }
 
 void on_roomviewport_created(int index) {
-	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
+	if (_G(drawstate).FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if ((size_t)index < _GP(CameraDrawData).size())
 		return;
@@ -589,14 +557,14 @@ void on_roomviewport_created(int index) {
 }
 
 void on_roomviewport_deleted(int index) {
-	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
+	if (_G(drawstate).FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	_GP(CameraDrawData).erase(_GP(CameraDrawData).begin() + index);
 	delete_invalid_regions(index);
 }
 
 void on_roomviewport_changed(Viewport *view) {
-	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
+	if (_G(drawstate).FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if (!view->IsVisible() || view->GetCamera() == nullptr)
 		return;
@@ -614,7 +582,7 @@ void on_roomviewport_changed(Viewport *view) {
 }
 
 void detect_roomviewport_overlaps(size_t z_index) {
-	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
+	if (_G(drawstate).FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	// Find out if we overlap or are overlapped by anything;
 	const auto &viewports = _GP(play).GetRoomViewportsZOrdered();
@@ -638,7 +606,7 @@ void detect_roomviewport_overlaps(size_t z_index) {
 }
 
 void on_roomcamera_changed(Camera *cam) {
-	if (drawstate.FullFrameRedraw || (_G(displayed_room) < 0))
+	if (_G(drawstate).FullFrameRedraw || (_G(displayed_room) < 0))
 		return;
 	if (cam->HasChangedSize()) {
 		auto viewrefs = cam->GetLinkedViewports();
@@ -659,7 +627,7 @@ void mark_object_changed(int objid) {
 void reset_drawobj_for_overlay(int objnum) {
 	if (objnum > 0 && static_cast<size_t>(objnum) < _GP(overtxs).size()) {
 		_GP(overtxs)[objnum] = ObjTexture();
-		if (drawstate.SoftwareRender)
+		if (_G(drawstate).SoftwareRender)
 			_GP(overcache)[objnum] = Point(INT32_MIN, INT32_MIN);
 	}
 }
@@ -674,19 +642,19 @@ void notify_sprite_changed(int sprnum, bool deleted) {
 	// notify texture-based ones in a specific case when a deleted sprite
 	// was replaced by another of same ID.
 
-	auto it_notify = drawstate.SpriteNotifyMap.find(sprnum);
-	if (it_notify != drawstate.SpriteNotifyMap.end()) {
+	auto it_notify = _G(drawstate).SpriteNotifyMap.find(sprnum);
+	if (it_notify != _G(drawstate).SpriteNotifyMap.end()) {
 		*it_notify->_value = UINT32_MAX;
-		drawstate.SpriteNotifyMap.erase(sprnum);
+		_G(drawstate).SpriteNotifyMap.erase(sprnum);
 	}
 }
 
 void mark_screen_dirty() {
-	drawstate.ScreenIsDirty = true;
+	_G(drawstate).ScreenIsDirty = true;
 }
 
 bool is_screen_dirty() {
-	return drawstate.ScreenIsDirty;
+	return _G(drawstate).ScreenIsDirty;
 }
 
 void invalidate_screen() {
@@ -742,7 +710,7 @@ void render_to_screen() {
 	// Stage: final plugin callback (still drawn on game screen)
 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-										_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped);
+										_GP(play).GetGlobalTransform(_G(drawstate).FullFrameRedraw), (GraphicFlip)_GP(play).screen_flipped);
 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
 		_G(gfxDriver)->EndSpriteBatch();
 	}
@@ -762,7 +730,7 @@ void render_to_screen() {
 	while (!succeeded && !_G(want_exit) && !_G(abort_engine)) {
 		//     try
 		//     {
-		if (drawstate.FullFrameRedraw) {
+		if (_G(drawstate).FullFrameRedraw) {
 			_G(gfxDriver)->Render();
 		}
 		else {
@@ -869,13 +837,13 @@ static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool op
 	if ((obj.SpriteID != UINT32_MAX) && _GP(game).SpriteInfos[obj.SpriteID].IsDynamicSprite()) {
 		// For dynamic sprite: check and update a notification block for this drawable
 		if (!obj.SpriteNotify || (*obj.SpriteNotify != obj.SpriteID)) {
-			auto it_notify = drawstate.SpriteNotifyMap.find(obj.SpriteID);
-			if (it_notify != drawstate.SpriteNotifyMap.end()) { // assign existing
+			auto it_notify = _G(drawstate).SpriteNotifyMap.find(obj.SpriteID);
+			if (it_notify != _G(drawstate).SpriteNotifyMap.end()) { // assign existing
 				obj.SpriteNotify = it_notify->_value;
 			}
 		} else { // if does not exist, then create and share one
 			obj.SpriteNotify.reset(new (uint32_t)(obj.SpriteID));
-			drawstate.SpriteNotifyMap.insert(std::make_pair((int)obj.SpriteID, obj.SpriteNotify));
+			_G(drawstate).SpriteNotifyMap.insert(std::make_pair((int)obj.SpriteID, obj.SpriteNotify));
 		}
 	} else {
 		obj.SpriteNotify = nullptr; // reset, for static sprite or without ID
@@ -1237,7 +1205,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 								 ObjTexture &actsp,			// object texture to draw upon
 								 bool optimize_by_position, // allow to optimize walk-behind merging using object's pos
 								 bool force_software) {
-	const bool use_hw_transform = !force_software && !drawstate.SoftwareRender;
+	const bool use_hw_transform = !force_software && !_G(drawstate).SoftwareRender;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
@@ -1290,7 +1258,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 	// Software mode below
 	//
 	// They want to draw it in software mode with the hw driver, so force a redraw (???)
-	if (!drawstate.SoftwareRender) {
+	if (!_G(drawstate).SoftwareRender) {
 		objsav.sppic = INT32_MIN;
 	}
 
@@ -1308,7 +1276,7 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
 			(objsav.zoom == objsrc.zoom) &&
 			(objsav.mirrored == is_mirrored)) {
 		// if the image is the same, we can use it cached
-		if ((drawstate.WalkBehindMethod != DrawOverCharSprite) &&
+		if ((_G(drawstate).WalkBehindMethod != DrawOverCharSprite) &&
 			(actsp.Bmp != nullptr))
 			return true;
 		// Check if the X & Y co-ords are the same, too -- if so, there
@@ -1382,13 +1350,13 @@ void prepare_and_add_object_gfx(const ObjectCache &objsav, ObjTexture &actsp, bo
 	// This potentially may edit actsp's raw bitmap if actsp_modified is set.
 	if (use_walkbehinds) {
 		// Only merge sprite with the walk-behinds in software mode
-		if ((drawstate.WalkBehindMethod == DrawOverCharSprite) && (actsp_modified)) {
+		if ((_G(drawstate).WalkBehindMethod == DrawOverCharSprite) && (actsp_modified)) {
 			walkbehinds_cropout(actsp.Bmp.get(), atx, aty, usebasel);
 		}
 	} else {
 		// Ignore walk-behinds by shifting baseline to a larger value
 		// CHECKME: may this fail if WB somehow got larger than room baseline?
-		if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
+		if (_G(drawstate).WalkBehindMethod == DrawAsSeparateSprite) {
 			usebasel += _GP(thisroom).Height;
 		}
 	}
@@ -1446,7 +1414,7 @@ bool construct_object_gfx(int objid, bool force_software) {
 void prepare_objects_for_drawing() {
 	set_our_eip(32);
 
-	const bool hw_accel = !drawstate.SoftwareRender;
+	const bool hw_accel = !_G(drawstate).SoftwareRender;
 
 	for (uint32_t objid = 0; objid < _G(croom)->numobj; ++objid) {
 		const RoomObject &obj = _G(objs)[objid];
@@ -1553,7 +1521,7 @@ bool construct_char_gfx(int charid, bool force_software) {
 
 void prepare_characters_for_drawing() {
 	set_our_eip(33);
-	const bool hw_accel = !drawstate.SoftwareRender;
+	const bool hw_accel = !_G(drawstate).SoftwareRender;
 
 	// draw characters
 	for (int charid = 0; charid < _GP(game).numcharacters; ++charid) {
@@ -1622,9 +1590,9 @@ void prepare_room_sprites() {
 			recycle_ddb_bitmap(_G(roomBackgroundBmp), _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false, true);
 
 	}
-	if (drawstate.FullFrameRedraw) {
+	if (_G(drawstate).FullFrameRedraw) {
 		if (_G(current_background_is_dirty) || _G(walkBehindsCachedForBgNum) != _GP(play).bg_frame) {
-			if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
+			if (_G(drawstate).WalkBehindMethod == DrawAsSeparateSprite) {
 				walkbehinds_generate_sprites();
 			}
 		}
@@ -1642,7 +1610,7 @@ void prepare_room_sprites() {
 		if ((_G(debug_flags) & DBG_NODRAWSPRITES) == 0) {
 			set_our_eip(34);
 
-			if (drawstate.WalkBehindMethod == DrawAsSeparateSprite) {
+			if (_G(drawstate).WalkBehindMethod == DrawAsSeparateSprite) {
 				for (size_t wb = 1 /* 0 is "no area" */;
 					(wb < MAX_WALK_BEHINDS) && (wb < (size_t)_GP(walkbehindobj).size()); ++wb) {
 					const auto &wbobj = _GP(walkbehindobj)[wb];
@@ -1674,7 +1642,7 @@ void prepare_room_sprites() {
 
 // Draws the black surface behind (or rather between) the room viewports
 void draw_preroom_background() {
-	if (drawstate.FullFrameRedraw)
+	if (_G(drawstate).FullFrameRedraw)
 		return;
 	update_black_invreg_and_reset(_G(gfxDriver)->GetMemoryBackBuffer());
 }
@@ -1974,7 +1942,7 @@ static void construct_room_view() {
 		const SpriteTransform view_trans(view_rc.Left, view_rc.Top, view_sx, view_sy);
 		const SpriteTransform cam_trans(-cam_rc.Left, -cam_rc.Top);
 
-		if (drawstate.FullFrameRedraw) {
+		if (_G(drawstate).FullFrameRedraw) {
 			// For hw renderer we draw everything as a sprite stack;
 			// viewport-camera pair is done as 2 nested scene nodes,
 			// where first defines how camera's image translates into the viewport on screen,
@@ -2028,8 +1996,8 @@ static void construct_ui_view() {
 // Prepares overlay textures;
 // but does not put them on screen yet - that's done in respective construct_*_view functions
 static void construct_overlays() {
-	const bool is_software_mode = drawstate.SoftwareRender;
-	const bool crop_walkbehinds = (drawstate.WalkBehindMethod == DrawOverCharSprite);
+	const bool is_software_mode = _G(drawstate).SoftwareRender;
+	const bool crop_walkbehinds = (_G(drawstate).WalkBehindMethod == DrawOverCharSprite);
 
 	auto &overs = get_overlays();
 	if ( _GP(overtxs).size() < overs.size()) {
@@ -2111,14 +2079,14 @@ void construct_game_scene(bool full_redraw) {
 
 	// Begin with the parent scene node, defining global offset and flip
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
+									_GP(play).GetGlobalTransform(_G(drawstate).FullFrameRedraw),
 									(GraphicFlip)_GP(play).screen_flipped);
 
 	// Stage: room viewports
 	if (_GP(play).screen_is_faded_out == 0 && _GP(play).complete_overlay_on == 0) {
 		if (_G(displayed_room) >= 0) {
 			construct_room_view();
-		} else if (!drawstate.FullFrameRedraw) {
+		} else if (!_G(drawstate).FullFrameRedraw) {
 			// black it out so we don't get cursor trails
 			// TODO: this is possible to do with dirty rects system now too (it can paint black rects outside of room viewport)
 			_G(gfxDriver)->GetMemoryBackBuffer()->Fill(0);
@@ -2138,7 +2106,7 @@ void construct_game_scene(bool full_redraw) {
 
 void construct_game_screen_overlay(bool draw_mouse) {
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
-									_GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
+									_GP(play).GetGlobalTransform(_G(drawstate).FullFrameRedraw),
 									(GraphicFlip)_GP(play).screen_flipped);
 	if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) {
 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
@@ -2160,7 +2128,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 	_G(gfxDriver)->EndSpriteBatch();
 
 	// For hardware-accelerated renderers: legacy letterbox and global screen fade effect
-	if (drawstate.FullFrameRedraw) {
+	if (_G(drawstate).FullFrameRedraw) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 		// Stage: legacy letterbox mode borders
 		if (_GP(play).screen_is_faded_out == 0)
@@ -2207,7 +2175,7 @@ void debug_draw_room_mask(RoomAreaMask mask) {
 
 	// Software mode scaling
 	// note we don't use transparency in software mode - may be slow in hi-res games
-	if (drawstate.SoftwareRender &&
+	if (_G(drawstate).SoftwareRender &&
 		(mask != kRoomAreaWalkBehind) &&
 		(bmp->GetSize() != Size(_GP(thisroom).Width, _GP(thisroom).Height))) {
 		recycle_bitmap(_GP(debugRoomMaskObj).Bmp,
@@ -2229,7 +2197,7 @@ void update_room_debug() {
 	if (_G(debugRoomMask) == kRoomAreaWalkable) {
 		Bitmap *bmp = prepare_walkable_areas(-1);
 		// Software mode scaling
-		if (drawstate.SoftwareRender && (_GP(thisroom).MaskResolution > 1)) {
+		if (_G(drawstate).SoftwareRender && (_GP(thisroom).MaskResolution > 1)) {
 			recycle_bitmap(_GP(debugRoomMaskObj).Bmp,
 				bmp->GetColorDepth(), _GP(thisroom).Width, _GP(thisroom).Height);
 			_GP(debugRoomMaskObj).Bmp->StretchBlt(bmp, RectWH(0, 0, _GP(thisroom).Width, _GP(thisroom).Height));
@@ -2240,8 +2208,8 @@ void update_room_debug() {
 		_GP(debugRoomMaskObj).Ddb->SetStretch(_GP(thisroom).Width, _GP(thisroom).Height);
 	}
 	if (_G(debugMoveListChar) >= 0) {
-		const int mult = drawstate.SoftwareRender ? 1 : _GP(thisroom).MaskResolution;
-		if (drawstate.SoftwareRender)
+		const int mult = _G(drawstate).SoftwareRender ? 1 : _GP(thisroom).MaskResolution;
+		if (_G(drawstate).SoftwareRender)
 			recycle_bitmap(_GP(debugMoveListObj).Bmp, _GP(game).GetColorDepth(),
 				_GP(thisroom).Width, _GP(thisroom).Height, true);
 		else
@@ -2286,7 +2254,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 	// TODO: extraBitmap is a hack, used to place an additional gui element
 	// on top of the screen. Normally this should be a part of the game UI stage.
 	if (extraBitmap != nullptr) {
-		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), _GP(play).GetGlobalTransform(drawstate.FullFrameRedraw),
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), _GP(play).GetGlobalTransform(_G(drawstate).FullFrameRedraw),
 										(GraphicFlip)_GP(play).screen_flipped);
 		invalidate_sprite(extraX, extraY, extraBitmap, false);
 		_G(gfxDriver)->DrawSprite(extraX, extraY, extraBitmap);
@@ -2304,7 +2272,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 		}
 	}
 
-	drawstate.ScreenIsDirty = false;
+	_G(drawstate).ScreenIsDirty = false;
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 3f4b5056786..6766662f61e 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -30,6 +30,7 @@
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/shared/game/room_struct.h"
 #include "ags/engine/ac/runtime_defines.h"
+#include "ags/engine/ac/walk_behind.h"
 
 namespace AGS3 {
 namespace AGS {
@@ -68,6 +69,37 @@ struct RoomCameraDrawData {
 	bool    IsOverlap;   // whether room viewport overlaps any others (marking dirty rects is complicated)
 };
 
+typedef int32_t sprkey_t;
+// TODO: refactor the draw unit into a virtual interface with
+// two implementations: for software and video-texture render,
+// instead of checking whether the current method is "software".
+struct DrawState {
+	// Whether we should use software rendering methods
+	// (aka raw draw), as opposed to video texture transform & fx
+	bool SoftwareRender = false;
+	// Whether we should redraw whole game screen each frame
+	bool FullFrameRedraw = false;
+	// Walk-behinds representation
+	WalkBehindMethodEnum WalkBehindMethod = DrawAsSeparateSprite;
+	// Whether there are currently remnants of a on-screen effect
+	bool ScreenIsDirty = false;
+
+	// A map of shared "control blocks" per each sprite used
+	// when preparing object textures. "Control block" is currently just
+	// an integer which lets to check whether the object texture is in sync
+	// with the sprite. When the dynamic sprite is updated or deleted,
+	// the control block is marked as invalid and removed from the map;
+	// but certain objects may keep the shared ptr to the old block with
+	// "invalid" mark, thus they know that they must reset their texture.
+	//
+	// TODO: investigate an alternative of having a equivalent of
+	// "shared texture" with sprite ID ref in Software renderer too,
+	// which would allow to use same method of testing DDB ID for both
+	// kinds of renderers, thus saving on 1 extra notification mechanism.
+	std::unordered_map<sprkey_t, std::shared_ptr<uint32_t> >
+		SpriteNotifyMap;
+};
+
 // ObjTexture is a helper struct that pairs a raw bitmap with
 // a renderer's texture and an optional position
 struct ObjTexture {
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 8333b31d47c..6afa7dfb9eb 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -623,6 +623,7 @@ public:
 	std::vector<Engine::IDriverDependantBitmap *> *_guiobjddb;
 	std::vector<Point> *_guiobjoff; // because surface may be larger than logical position
 
+	DrawState _drawstate;
 	DrawFPS _gl_DrawFPS;
 
 	/**@}*/


Commit: 543dff99a60e26fc6fed6fec3ecd7ac2384e6f07
    https://github.com/scummvm/scummvm/commit/543dff99a60e26fc6fed6fec3ecd7ac2384e6f07
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Expand info in cc_instance error logging macros

Changed paths:
    engines/ags/engine/script/cc_instance.cpp


diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp
index 847e2b83155..fd85a092f73 100644
--- a/engines/ags/engine/script/cc_instance.cpp
+++ b/engines/ags/engine/script/cc_instance.cpp
@@ -492,8 +492,8 @@ inline RuntimeScriptValue GetStackPtrOffsetFw(RuntimeScriptValue *stack, int32_t
 		stack_entry++;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RETVAL(total_off < fw_offset, RuntimeScriptValue, "accessing address beyond stack's tail");
-	CC_ERROR_IF_RETVAL(total_off > fw_offset, RuntimeScriptValue, "stack offset forward: trying to access stack data inside stack entry, stack corrupted?");
+	CC_ERROR_IF_RETVAL(total_off < fw_offset, RuntimeScriptValue, "accessing address %d beyond stack's tail (%d)", fw_offset, total_off);
+	CC_ERROR_IF_RETVAL(total_off > fw_offset, RuntimeScriptValue, "stack offset forward (%d): trying to access stack data inside stack entry (%d), stack corrupted?", fw_offset, total_off);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	return stack_ptr;
@@ -1040,7 +1040,7 @@ int ccInstance::Run(int32_t curpc) {
 				break;
 			default:
 				// There's one possible case when the reg1 is 0, which means writing nullptr
-				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: MEMWRITEPTR argument is not a dynamic object");
+				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: MEMWRITEPTR argument is not a dynamic object (Type = %d)", reg1.Type);
 				address = nullptr;
 				break;
 			}
@@ -1078,7 +1078,7 @@ int ccInstance::Run(int32_t curpc) {
 				break;
 			default:
 				// There's one possible case when the reg1 is 0, which means writing nullptr
-				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object");
+				CC_ERROR_IF_RETCODE(!reg1.IsNull(), "internal error: SCMD_MEMINITPTR argument is not a dynamic object (Type = %d)", reg1.Type);
 				address = nullptr;
 				break;
 			}
@@ -1961,7 +1961,7 @@ void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) {
 }
 
 void ccInstance::PushDataToStack(const int32_t num_bytes) {
-	CC_ERROR_IF(registers[SREG_SP].RValue->IsValid(), "internal error: valid data beyond stack ptr");
+	CC_ERROR_IF(registers[SREG_SP].RValue->IsValid(), "internal error: valid data (%d bytes) beyond stack ptr", num_bytes);
 	// Assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr
 	// NOTE: memory is zeroed by SCMD_ZEROMEMORY
 	registers[SREG_SP].RValue->SetData(stackdata_ptr, num_bytes);
@@ -1997,8 +1997,8 @@ void ccInstance::PopDataFromStack(const int32_t num_bytes) {
 		total_pop += registers[SREG_SP].RValue->Size;
 		registers[SREG_SP].RValue->Invalidate(); // FIXME: bad, this is used to separate PushValue and PushData
 	}
-	CC_ERROR_IF(total_pop < num_bytes, "stack underflow");
-	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop, stack corrupted?");
+	CC_ERROR_IF(total_pop < num_bytes, "stack underflow: %d bytes popped of %d total", total_pop, num_bytes);
+	CC_ERROR_IF(total_pop > num_bytes, "stack pointer points inside local variable after pop (%d bytes), stack corrupted?", total_pop);
 }
 
 RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(const int32_t rw_offset) {
@@ -2008,12 +2008,13 @@ RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(const int32_t rw_offset) {
 		stack_entry--;
 		total_off += stack_entry->Size;
 	}
-	CC_ERROR_IF_RETVAL(total_off < rw_offset, RuntimeScriptValue, "accessing address before stack's head");
+	CC_ERROR_IF_RETVAL(total_off < rw_offset, RuntimeScriptValue, "accessing address (off: %d) before stack's head (%d)", rw_offset, total_off);
 	RuntimeScriptValue stack_ptr;
 	stack_ptr.SetStackPtr(stack_entry);
 	stack_ptr.IValue += total_off - rw_offset; // possibly offset to the mid-array
 	// Could be accessing array element, so state error only if stack entry does not refer to data array
-	CC_ERROR_IF_RETVAL((total_off > rw_offset) && (stack_entry->Type != kScValData), RuntimeScriptValue, "stack offset backward: trying to access stack data inside stack entry, stack corrupted?")
+	CC_ERROR_IF_RETVAL((total_off > rw_offset) && (stack_entry->Type != kScValData), RuntimeScriptValue,
+					   "stack offset backward: trying to access stack data (off: %d) inside stack entry (tot: %d), stack corrupted?", rw_offset, total_off);
 	return stack_ptr;
 }
 


Commit: 37f8378cd66542adf6277d69e4c397467018a2c3
    https://github.com/scummvm/scummvm/commit/37f8378cd66542adf6277d69e4c397467018a2c3
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Avoid unserializing plugins with zero datasize

This is to avoid a crash when using plugins with a stubbed unserialize method
(e.g. Controller plugin)

Changed paths:
    engines/ags/engine/ac/dynobj/cc_serializer.cpp


diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
index 0eb23791fb6..6a61249a5c3 100644
--- a/engines/ags/engine/ac/dynobj/cc_serializer.cpp
+++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp
@@ -125,6 +125,10 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char
 		// check if the type is read by a plugin
 		for (const auto &pr : _GP(pluginReaders)) {
 			if (pr.Type == objectType) {
+				if (dataSize == 0) { // avoid unserializing stubbed plugins
+					debug(0, "Skipping %s plugin unserialization (dataSize = 0)", objectType);
+					return;
+				}
 				pr.Reader->Unserialize(index, serializedData, dataSize);
 				return;
 			}


Commit: 1ac7de437ed3d361ca315a4502583d5d1c74aba7
    https://github.com/scummvm/scummvm/commit/1ac7de437ed3d361ca315a4502583d5d1c74aba7
Author: Walter Agazzi (walter.agazzi at protonmail.com)
Date: 2024-10-30T18:07:42+02:00

Commit Message:
AGS: Fix flipped parameters in ArcTan2 plugin function

Changed paths:
    engines/ags/plugins/core/maths.cpp


diff --git a/engines/ags/plugins/core/maths.cpp b/engines/ags/plugins/core/maths.cpp
index 67d493d7e87..0283fccd41d 100644
--- a/engines/ags/plugins/core/maths.cpp
+++ b/engines/ags/plugins/core/maths.cpp
@@ -66,7 +66,7 @@ void Maths::ArcTan(ScriptMethodParams &params) {
 
 void Maths::ArcTan2(ScriptMethodParams &params) {
 	PARAMS2(float, yval, float, xval);
-	params._result = AGS3::Math_ArcTan2(xval, yval);
+	params._result = AGS3::Math_ArcTan2(yval, xval);
 }
 
 void Maths::Cos(ScriptMethodParams &params) {




More information about the Scummvm-git-logs mailing list