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

sev- noreply at scummvm.org
Thu Mar 7 19:10:34 UTC 2024


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

Summary:
fa3b586e27 TWP: Add initial Thimbleweed Park engine
00274a3565 TWP: Add squirrel and ggpack
4e27881da2 TWP: Test threads and breaktime
1a05102d6b TWP: Remove unuseful scene
1d6ce20f95 TWP: Add OpenGL graphics
c85dd17062 TWP: Add a resource manager
b7adc07de9 TWP: Add spritesheet
3a7ea6029a TWP: Add room
42e348186a TWP: Add lighting shader
b057e67a0f TWP: Add GGFont and text
da03d0b291 TWP: Add general function stubs
06de3f2eef TWP: Add object function stubs
36319a142b TWP: Add sys function stubs
2fec67e5ec TWP: Add scenegraph
1d63a8390e TWP: Split room and object
3658cbe47d TWP: Add bnut, defineRoom, etc.
d8883990f9 TWP: Add include
6991c88a1b TWP: Add camera and interpolations
ae5776941f TWP: Add actorlib stubs
764ffa65bd TWP: Add roomlib stubs
618c001016 TWP: Add soundlib stubs
b6613e04ec TWP: Add several function implementations
ca61129f17 TWP: Implement several actor functions
3523b02fcf TWP: Add BmFont and Motor
3a6ff63314 TWP: Add motor implementations
f7f2f7c2be TWP: Add cutscene
ed36a07d0e TWP: Add yack parser
f3ac588ba2 TWP: Fix breakwhilecutscene
76797f8c63 TWP: Add dialog
8fd8b0aac0 TWP: Add room fade and fix crash in threads
0e6ef39297 TWP: Fix cutscene resumed too early
8f933b9e82 TWP: Add actor functions
736129c667 TWP: Fix objects duplicated
a055e4deaf TWP: Add walkto
e9ac13baf4 TWP: Fix HUD draw
c48bfe68b7 TWP: Add talking
8acd373b0c TWP: Add callbacks
dcb5cd5834 TWP: Fix head anim
57075bd922 TWP: Add GGPackSet
e88c12424c TWP: Fix inventory
4e67d24e04 TWP: Add path finding
c045312fd2 TWP: Add triggers
309a3b456c TWP: Add cursor shape
5bde2ec4c6 TWP: Add missing string functions
ac90460348 TWP: Fix crash when calling leave trigger
987146d451 TWP: Fix several bugs
65ac57b991 TWP: Support UTF-8 text
09f6f404c5 TWP: Add actorswitcher and enginedialogtarget
1b7f4ef586 TWP: Fix actorswitcher select
0f784c64ad TWP: Fix break light animation
8f43063247 TWP: Hide actorswitcher when necessary
cc8e1478de TWP: Fix objectMoveTo
7af87bb96a TWP: Add dialog choose
e75c4d891b TWP: Add blink, fix room size, actor scaling and \"
27365286ee TWP: Fix camera limit and ) in dialog
e1cc3bb7f0 TWP: Add audio
bcb1c4d1cd TWP: Add trigger sound
52a0000fa0 TWP: Fix animation frame drawing
6c55dce29f TWP: misc fixes
fe0c2723fc TWP: Load savegame
45c6656ffc TWP: Fix crash after cutscene where the parent thread is dead
e9f648497c TWP: Fix van not visible in the Highway
2dbcc17db4 TWP: Add exec command in console
9404fe941d TWP: Fix scenegraph issues
10b1e8c812 TWP: Fix thread crash
29e84e18c6 TWP: Fix loadObject in savegame
a650a66013 TWP: Fix inventory offset
75e2632053 TWP: Fix camera y position
a2a2397d8a TWP: Add scaling triggers
17d924dee9 TWP: Add skip cutscene
1b9732110a TWP: Add options dialog
8adea5d125 TWP: Fix default verb
098c53cd5d TWP: Fix cursor shape
f2c7730757 TWP: Fix HUD shader
dca1cfea21 TWP: Add black & white and ghost shaders
6972a16981 TWP: Add sepia shader
022168cd32 TWP: Add language options
16ef14aaeb TWP: Add input actions
4f88b8e026 TWP: Add hotspot marker
a5d90e24e0 TWP: Add jiggleObject
d32400501e TWP: Fix not compiling on Linux
dc52874c07 TWP: Try to autodetect TWP games
e729524698 TWP: Add sound panning
75281a8655 TWP: Add Boost Software License
d52b83b83f TWP: Add inventory scroll
66a163f702 TWP: Actor should walk while mouse is down
ef8a563251 TWP: Gear icon should show options
c6569b3e9e TWP: Fix costume not set in map
a691564fab TWP: Fix costume bug
cf2e05e86a TWP: Fix polygon merging + walkbox debug
4b6ac21caf TWP: Display use pos in debug
803cc32afa TWP: Fix TesterTron 3000
68541132c3 TWP: Fix Franklin can't execute verbs
0a51deccef TWP: Add description in savegame list
4cde5db729 TWP: Add savegame
fee1d7b121 TWP: Fix tons of warnings
b3db646969 TWP: Add some checks and fix some array access
20cd0507e1 TWP: Fix some compilation errors on Windows
49b359b567 TWP: Remove lambdas
add0288def TWP: Fix invalid light id
6ccb14d838 TWP: Fix crash on Linux when capturing screen
71e44fe5e0 TWP: Fix missing warnings on Linux
a35d873ba8 TWP: Fix crash with sound not initialized
8e49787281 TWP: Remove static variable in gfx.cpp
ebc160da66 TWP: Reset room effect when entering room
478b1a505b TWP: Fix some uninitialized variables
03dd90452d TWP: Fix resume thread and use of uninitialized variables
97c4353738 TWP: Fix pathfinding
05a055b3e7 TWP: Fix twp not building after merge
99170bf2f1 TWP: Add roomRotateTo
38e58778b6 TWP: Fix talking timings
087679d6f9 TWP: Fix cursor text in closeup room
1783a2f731 TWP: Fix trigger not working
9c50ad6348 TWP: Click only if button state changed
9d5de99e36 TWP: Update exCommand
bc9c277cb6 TWP: Add jiggleInventory
8f946e4a0d TWP: Call sayingLine if any
c8acfe2083 TWP: Load and save jiggle
243776e27c TWP: Fix jiggle sometimes not present
a912e659e3 TWP: Add HUD fade animation
afcea85246 TWP: Add debug channels
3c56af0e29 TWP: Add DLC option
154131e505 TWP: Add dear imgui
2cb52baa86 TWP: Fix delay between frames
27968433de TWP: Use OpenGL::Shader class
ef2d87e1d7 TWP: Simplify shader class
0d87082d11 TWP: Debug lighting shader
f2521739c1 TWP: Fix lighting shader \o/
31a29481e5 TWP: Fix null frame in animation
e2d956f398 TWP: Fix room leaks
47bf594d73 TWP: Fix layer leaks
34387419c2 TWP: Fix leaks with tasks/threess/callbacks
ad0dd458b6 TWP: Fix leaks with objects/actors
6cb55e0fec TWP: Fix leaks with object nodes
d1b5955fb4 TWP: Fix leaks with sounds
5711e7f1f1 TWP: Fix misc leaks
6842d68185 TWP: Fix yack leaks
61f0357120 TWP: Fix leaks with motors
4f2d991170 TWP: Fix various leaks
ee6736d7b6 TWP: Fix leaks with ggpack and savegames
a093828a1a TWP: Add missing int function
c8d7559169 TWP: Fix several issues
ddcf22df7a TWP: Fix room FADE_WOBBLE
9eb8fe627e TWP: Fix scaleTo
2c38aa8ab7 TWP: Fix screen upside down after fading
8f7b303426 TWP: Debug savegame only if channel activated
4ed379403a TWP: Input should be disabled if requested
866a7db57f TWP: Fix impossible to pickup some objects
48171056b3 TWP: Fix crash in tasks
29f6c91491 TWP: Fix cursor text visible when input off
45227fa0d7 TWP: Cursort text missing verb the first time
9b4390be99 TWP: Fix we can use object on same itself
98dfa1c025 TWP: Fix we can switch actor even if disabled
4b3f6d25e5 TWP: Disable savegame when necessary
ca9867b6c6 TWP: Fix node draw order
ae5cd629e1 TWP: Fix bug with pseudo objects
18c96a499d TWP: Fix crash with tasks
1547e4654b TWP: Fix actorDistanceTo with 1 parameter
79b3ace7ca TWP: Fix crash with text in parenthesis
cc3ce37994 TWP: Fix impossible to open Mansion gate
49273670d5 TWP: Fix Franklin icon should be a ghost
3545c138bd TWP: Use inventory_slot
2852c14f0b TWP: Clean code
12b83a75a3 TWP: Fix image not translated
ab9de54d42 TWP: Clean motor code
6e9f002106 TWP: Missing names in phone book
59f09a8736 TWP: When calling kscumm the text contains $
3eb969ce85 TWP: Fix issue with walkbox
3ec7b6d28f TWP: Fix crash when changing actor
4eabd859f2 TWP: Fix crash when loading game
69cef8c942 TWP: Fix crash with invalid key anim
50895370f4 TWP: Add autosave
bc1be1cfb6 TWP: Fix can't use strange device on pillowTronSlot
2efa0d60b5 TWP: Fix audio loop
bce1313ad6 TWP: Fix several bugs
c89c419bd5 TWP: Remove hardcoded shader version
4cb6a24c06 TWP: Fix regression, actors should not have their alpha reset
c7ac09c116 TWP: Actor should stop walking when exiting room
df1f1a1d1c TWP: Fix regression actorEnter and actorExit should be called
d0e7ac4628 TWP: Clear inventory offset when no more inventory
f12099e667 TWP: Add find_if
c3b7b5b397 TWP: Don't use verb on actor if not talk or give
dc065124bf TWP: Do some fixes
f2b8a5ec29 TWP: Fix shuffle method
e4dfbdbaa0 TWP: Fix module.mk for create_project
f2b016d538 TWP: Add USE_IMGUI flag
77f379014f TWP: Fix phone keeps ringing
c27cbbaf63 TWP: Fix some complation issues on Windows
f6073f1d48 TWP: Indicate dependencies in configure.engine
16e9235d42 TWP: Add games explicitly in detection tables
b8e0a37037 TWP: Fix several code review comments
2423ba3ed6 TWP: Move btea to common
6d3f6555fb TWP: Remove std from ClipperLib
e98219abde TWP: Rename g_engine by g_twp
c13fdb899f TWP: Fix initKeymaps
3ce8f220d1 TWP: Fix some warnings in clipper/squirrel and imgui
9fb8431d87 TWP: Remove imgui.ini
959365ae48 TWP: Remove unused replace method
de8939ff58 TWP: Fix nArgs uninitilized in squirrel
d25814db2a TWP: Fix crash with nodes
fd1cdb9f12 TWP: Fix crashes with font and thumbnail
a943ad0a27 TWP: Fix formatting, includes order and this
48a37f03aa TWP: Use GLuint and GLint in Gfx class
305fe167f4 TWP: Fix Windows compilations
85aa952517 TWP: Make it clearer ImGui flag
2c84ed86b8 TWP: Reuse normal settings on the "Audio" tab
8a4d11eb64 TWP: Fix wrong error message in ggpack
4f34f5e172 TWP: Fix define in enginedialogtarget.h
276e16cbde TWP: Use and restrict common language option
1be4d5b5a5 TWP: Make OpenGL ES 2.0 compatible
b840146b6c TWP: Move imgui into graphics/
6cb0874af2 TWP: Remove the preferences class and share getKey
31fa339069 TWP: Fix some warnings in genlib.cpp
7183590557 TWP: Show ImGui window only if debug flag enabled
f1c3b01f7e TWP: Fix Linux compilation with imgui enabled
72278f8902 TWP: Fix can hear actors in dialogs
0f05553590 TWP: Fix invalid types use
f1a741221a TWP: Stop audio before destructing engine
b8eea416a2 TWP: Fix various compilation issues
7f44571779 TWP: Fix node removing
dafe00424f TWP: Remove two full stops in dialogs.cpp
c7a24e15bc COMMON: Change BTEACrypto class to namespace
e0857c7200 TWP: Fix clipper inclusion
ab907a83a8 TWP: Fix strange init in DialogConditionState
86f2963abd TWP: Fix ids inclusion
f2fccce4f6 TWP: Fix files order in module.mk
e1571a9183 TWP: Fix bad formatting in hud.cpp
7503b204c3 TWP: Use CLIP instead of clamp
f0f5b3e10d TWP: Fix TWP_VM_H define
3248bf19a6 TWP: Fix TWP_WALKBOXNODE_H define
2ef2f29ea0 TWP: Minimize the include dependency
09a6bc98ac TWP: Allow changing options during game
ebb5d9c367 TWP: Remove unused unicode code in squirrel
db26ead64b TWP: Save imgui configuration in savepath
cac2f3aae0 TWP: Split engine and imgui
f0866ab187 TWP: Remove static ids
158257c0f9 TWP: Remove unsued find_if method
0cba66b204 TWP: Move ImGui to backends
11ca8850ac TWP: Fix retroFonts is not updated when config changed
f64cb4fad7 TWP: Fix regression in dialog with \"
28295c3de8 TWP: Forbid ScummVM autosave
f9f051be8e TWP: Fix fadeOutTimeMs not reset
07dd0846db TWP: Simplify savegame management
e6e09b2f53 TWP: Use sys types not GL types
5318b11c39 TWP: Show warning if DLC selected but not detected
4df291584f TWP: Fix regression in notes and phone book
100c996ce5 TWP: Move ImGui to backends
f69d2c35e0 TWP: Fix assertion when screen size is greater than room height
efc6ee8752 TWP: Fix audio crash when loading savegames
b917604200 BACKENDS: Fix create random IniFilename
cf9cbad969 TWP: Fix it's hard to scare the kid
9c7098fb84 BACKENDS: Fix regression imgui windows not visible
2fdde4ccc2 TWP: Hide system cursor
8807ab74db BACKENDS: New imgui proposition
d922f17f4a TWP: Fix mansion door trigger
e1208eaec3 TWP: Fix actor meeting delores cutscene
c99572efe7 TWP: Detect more versions
9ff903dd75 BACKENDS: Fix imgui width sdl1
b8a444a815 TWP: Fix skip cutscene crash
94c97c7c63 TWP: Fix not possible to pickup the glass in kitchen
1c9e1a9333 TWP: Fix objectHotspot
e8161a32ac TWP: Fix stop sound when stop talk
0f94a8257c TWP: Fix strange walk path
0041949337 TWP: Move static functions to util
e6a9096a99 TWP: Fix randomFrom crash
eb04a3a159 TWP: Fix never ending breakwhiletalking
7dd6c4ba62 TWP: Fix give minimum distance
c8e7ce8f61 TWP: Fix invalid target in cameraPanTo
fcf9a7fee6 TWP: Fix autosave should be enabled by default
ac597e184b TWP: Fix system mouse cursor still visible


Commit: fa3b586e276e800fc8e402a32bdc503974734cea
    https://github.com/scummvm/scummvm/commit/fa3b586e276e800fc8e402a32bdc503974734cea
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add initial Thimbleweed Park engine

Changed paths:
  A engines/twp/POTFILES
  A engines/twp/configure.engine
  A engines/twp/console.cpp
  A engines/twp/console.h
  A engines/twp/credits.pl
  A engines/twp/detection.cpp
  A engines/twp/detection.h
  A engines/twp/detection_tables.h
  A engines/twp/metaengine.cpp
  A engines/twp/metaengine.h
  A engines/twp/module.mk
  A engines/twp/twp.cpp
  A engines/twp/twp.h


diff --git a/engines/twp/POTFILES b/engines/twp/POTFILES
new file mode 100644
index 00000000000..388ceab2140
--- /dev/null
+++ b/engines/twp/POTFILES
@@ -0,0 +1 @@
+engines/twp/metaengine.cpp
diff --git a/engines/twp/configure.engine b/engines/twp/configure.engine
new file mode 100644
index 00000000000..84ec54804ef
--- /dev/null
+++ b/engines/twp/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine twp "Thimbleweed Park" yes "" "" ""
diff --git a/engines/twp/console.cpp b/engines/twp/console.cpp
new file mode 100644
index 00000000000..88d8044583c
--- /dev/null
+++ b/engines/twp/console.cpp
@@ -0,0 +1,38 @@
+/* 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 "twp/console.h"
+
+namespace Twp {
+
+Console::Console() : GUI::Debugger() {
+	registerCmd("test",   WRAP_METHOD(Console, Cmd_test));
+}
+
+Console::~Console() {
+}
+
+bool Console::Cmd_test(int argc, const char **argv) {
+	debugPrintf("Test\n");
+	return true;
+}
+
+} // End of namespace Twp
diff --git a/engines/twp/console.h b/engines/twp/console.h
new file mode 100644
index 00000000000..00bcea2c181
--- /dev/null
+++ b/engines/twp/console.h
@@ -0,0 +1,40 @@
+
+/* 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 TWP_CONSOLE_H
+#define TWP_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace Twp {
+
+class Console : public GUI::Debugger {
+private:
+	bool Cmd_test(int argc, const char **argv);
+public:
+	Console();
+	~Console() override;
+};
+
+} // End of namespace Twp
+
+#endif // TWP_CONSOLE_H
diff --git a/engines/twp/credits.pl b/engines/twp/credits.pl
new file mode 100644
index 00000000000..0ed5006e0c7
--- /dev/null
+++ b/engines/twp/credits.pl
@@ -0,0 +1,3 @@
+begin_section("Twp");
+	add_person("Name 1", "Handle 1", "");
+end_section();
diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
new file mode 100644
index 00000000000..453fb84b4f1
--- /dev/null
+++ b/engines/twp/detection.cpp
@@ -0,0 +1,45 @@
+/* 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 "base/plugins.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "common/str-array.h"
+#include "common/translation.h"
+#include "common/util.h"
+#include "twp/detection.h"
+#include "twp/detection_tables.h"
+
+const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
+	{ Twp::kDebugGraphics, "Graphics", "Graphics debug level" },
+	{ Twp::kDebugPath, "Path", "Pathfinding debug level" },
+	{ Twp::kDebugFilePath, "FilePath", "File path debug level" },
+	{ Twp::kDebugScan, "Scan", "Scan for unrecognised games" },
+	{ Twp::kDebugScript, "Script", "Enable debug script dump" },
+	DEBUG_CHANNEL_END
+};
+
+TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(Twp::gameDescriptions,
+	sizeof(ADGameDescription), Twp::twpGames) {
+}
+
+REGISTER_PLUGIN_STATIC(TWP_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TwpMetaEngineDetection);
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
new file mode 100644
index 00000000000..9c07520127e
--- /dev/null
+++ b/engines/twp/detection.h
@@ -0,0 +1,69 @@
+/* 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 TWP_DETECTION_H
+#define TWP_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace Twp {
+
+enum TwpDebugChannels {
+	kDebugGraphics = 1 << 0,
+	kDebugPath     = 1 << 1,
+	kDebugScan     = 1 << 2,
+	kDebugFilePath = 1 << 3,
+	kDebugScript   = 1 << 4,
+};
+
+extern const PlainGameDescriptor twpGames[];
+
+extern const ADGameDescription gameDescriptions[];
+
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+
+} // End of namespace Twp
+
+class TwpMetaEngineDetection : public AdvancedMetaEngineDetection {
+	static const DebugChannelDef debugFlagList[];
+
+public:
+	TwpMetaEngineDetection();
+	~TwpMetaEngineDetection() override {}
+
+	const char *getName() const override {
+		return "twp";
+	}
+
+	const char *getEngineName() const override {
+		return "Thimbleweed Park";
+	}
+
+	const char *getOriginalCopyright() const override {
+		return "Thimbleweed Park (C) Terrible Toybox";
+	}
+
+	const DebugChannelDef *getDebugChannels() const override {
+		return debugFlagList;
+	}
+};
+
+#endif // TWP_DETECTION_H
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
new file mode 100644
index 00000000000..900b8c6361e
--- /dev/null
+++ b/engines/twp/detection_tables.h
@@ -0,0 +1,43 @@
+/* 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/>.
+ *
+ */
+
+namespace Twp {
+
+const PlainGameDescriptor twpGames[] = {
+	{ "twp", "Twp" },
+	{ 0, 0 }
+};
+
+const ADGameDescription gameDescriptions[] = {
+	{
+		"twp",
+		nullptr,
+		AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
+		Common::EN_ANY,
+		Common::kPlatformWindows,
+		ADGF_UNSTABLE,
+		GUIO1(GUIO_NONE)
+	},
+
+	AD_TABLE_END_MARKER
+};
+
+} // End of namespace Twp
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
new file mode 100644
index 00000000000..775ebbc3f4e
--- /dev/null
+++ b/engines/twp/metaengine.cpp
@@ -0,0 +1,69 @@
+/* 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 "common/translation.h"
+
+#include "twp/metaengine.h"
+#include "twp/detection.h"
+#include "twp/twp.h"
+
+namespace Twp {
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+	{
+		GAMEOPTION_ORIGINAL_SAVELOAD,
+		{
+			_s("Use original save/load screens"),
+			_s("Use the original save/load screens instead of the ScummVM ones"),
+			"original_menus",
+			false,
+			0,
+			0
+		}
+	},
+	AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+} // End of namespace Twp
+
+const char *TwpMetaEngine::getName() const {
+	return "twp";
+}
+
+const ADExtraGuiOptionsMap *TwpMetaEngine::getAdvancedExtraGuiOptions() const {
+	return Twp::optionsList;
+}
+
+Common::Error TwpMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+	*engine = new Twp::TwpEngine(syst, desc);
+	return Common::kNoError;
+}
+
+bool TwpMetaEngine::hasFeature(MetaEngineFeature f) const {
+	return checkExtendedSaves(f) ||
+		(f == kSupportsLoadingDuringStartup);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(TWP)
+REGISTER_PLUGIN_DYNAMIC(TWP, PLUGIN_TYPE_ENGINE, TwpMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(TWP, PLUGIN_TYPE_ENGINE, TwpMetaEngine);
+#endif
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
new file mode 100644
index 00000000000..4938effecff
--- /dev/null
+++ b/engines/twp/metaengine.h
@@ -0,0 +1,43 @@
+/* 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 TWP_METAENGINE_H
+#define TWP_METAENGINE_H
+
+#include "engines/advancedDetector.h"
+
+class TwpMetaEngine : public AdvancedMetaEngine {
+public:
+	const char *getName() const override;
+
+	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+
+	/**
+	 * Determine whether the engine supports the specified MetaEngine feature.
+	 *
+	 * Used by e.g. the launcher to determine whether to enable the Load button.
+	 */
+	bool hasFeature(MetaEngineFeature f) const override;
+
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+};
+
+#endif // TWP_METAENGINE_H
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
new file mode 100644
index 00000000000..22d51ece94b
--- /dev/null
+++ b/engines/twp/module.mk
@@ -0,0 +1,17 @@
+MODULE := engines/twp
+
+MODULE_OBJS = \
+	twp.o \
+	console.o \
+	metaengine.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
new file mode 100644
index 00000000000..6eadfb982f8
--- /dev/null
+++ b/engines/twp/twp.cpp
@@ -0,0 +1,107 @@
+/* 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 "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/console.h"
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+
+namespace Twp {
+
+TwpEngine *g_engine;
+
+TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
+	_gameDescription(gameDesc), _randomSource("Twp") {
+	g_engine = this;
+}
+
+TwpEngine::~TwpEngine() {
+	delete _screen;
+}
+
+uint32 TwpEngine::getFeatures() const {
+	return _gameDescription->flags;
+}
+
+Common::String TwpEngine::getGameId() const {
+	return _gameDescription->gameId;
+}
+
+Common::Error TwpEngine::run() {
+	// Initialize 320x200 paletted graphics mode
+	initGraphics(320, 200);
+	_screen = new Graphics::Screen();
+
+	// Set the engine's debugger console
+	setDebugger(new Console());
+
+	// If a savegame was selected from the launcher, load it
+	int saveSlot = ConfMan.getInt("save_slot");
+	if (saveSlot != -1)
+		(void)loadGameState(saveSlot);
+
+	// Draw a series of boxes on screen as a sample
+	for (int i = 0; i < 100; ++i)
+		_screen->frameRect(Common::Rect(i, i, 320 - i, 200 - i), i);
+	_screen->update();
+
+	// Simple event handling loop
+	byte pal[256 * 3] = { 0 };
+	Common::Event e;
+	int offset = 0;
+
+	while (!shouldQuit()) {
+		while (g_system->getEventManager()->pollEvent(e)) {
+		}
+
+		// Cycle through a simple palette
+		++offset;
+		for (int i = 0; i < 256; ++i)
+			pal[i * 3 + 1] = (i + offset) % 256;
+		g_system->getPaletteManager()->setPalette(pal, 0, 256);
+		_screen->update();
+
+		// Delay for a bit. All events loops should have a delay
+		// to prevent the system being unduly loaded
+		g_system->delayMillis(10);
+	}
+
+	return Common::kNoError;
+}
+
+Common::Error TwpEngine::syncGame(Common::Serializer &s) {
+	// The Serializer has methods isLoading() and isSaving()
+	// if you need to specific steps; for example setting
+	// an array size after reading it's length, whereas
+	// for saving it would write the existing array's length
+	int dummy = 0;
+	s.syncAsUint32LE(dummy);
+
+	return Common::kNoError;
+}
+
+} // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
new file mode 100644
index 00000000000..df5e7730328
--- /dev/null
+++ b/engines/twp/twp.h
@@ -0,0 +1,105 @@
+/* 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 TWP_H
+#define TWP_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "common/random.h"
+#include "common/serializer.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "engines/savestate.h"
+#include "graphics/screen.h"
+
+#include "twp/detection.h"
+
+namespace Twp {
+
+struct TwpGameDescription;
+
+class TwpEngine : public Engine {
+private:
+	const ADGameDescription *_gameDescription;
+	Common::RandomSource _randomSource;
+protected:
+	// Engine APIs
+	Common::Error run() override;
+public:
+	Graphics::Screen *_screen = nullptr;
+public:
+	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
+	~TwpEngine() override;
+
+	uint32 getFeatures() const;
+
+	/**
+	 * Returns the game Id
+	 */
+	Common::String getGameId() const;
+
+	/**
+	 * Gets a random number
+	 */
+	uint32 getRandomNumber(uint maxNum) {
+		return _randomSource.getRandomNumber(maxNum);
+	}
+
+	bool hasFeature(EngineFeature f) const override {
+		return
+		    (f == kSupportsLoadingDuringRuntime) ||
+		    (f == kSupportsSavingDuringRuntime) ||
+		    (f == kSupportsReturnToLauncher);
+	};
+
+	bool canLoadGameStateCurrently() override {
+		return true;
+	}
+	bool canSaveGameStateCurrently() override {
+		return true;
+	}
+
+	/**
+	 * Uses a serializer to allow implementing savegame
+	 * loading and saving using a single method
+	 */
+	Common::Error syncGame(Common::Serializer &s);
+
+	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
+		Common::Serializer s(nullptr, stream);
+		return syncGame(s);
+	}
+	Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
+		Common::Serializer s(stream, nullptr);
+		return syncGame(s);
+	}
+};
+
+extern TwpEngine *g_engine;
+#define SHOULD_QUIT ::Twp::g_engine->shouldQuit()
+
+} // End of namespace Twp
+
+#endif // TWP_H


Commit: 00274a35652a2748112c8ec4fde3dbab1cfa66ee
    https://github.com/scummvm/scummvm/commit/00274a35652a2748112c8ec4fde3dbab1cfa66ee
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add squirrel and ggpack

Changed paths:
  A engines/twp/ggpack.cpp
  A engines/twp/ggpack.h
  A engines/twp/squirrel/sqapi.cpp
  A engines/twp/squirrel/sqarray.h
  A engines/twp/squirrel/sqbaselib.cpp
  A engines/twp/squirrel/sqclass.cpp
  A engines/twp/squirrel/sqclass.h
  A engines/twp/squirrel/sqclosure.h
  A engines/twp/squirrel/sqcompiler.cpp
  A engines/twp/squirrel/sqcompiler.h
  A engines/twp/squirrel/sqconfig.h
  A engines/twp/squirrel/sqdebug.cpp
  A engines/twp/squirrel/sqfuncproto.h
  A engines/twp/squirrel/sqfuncstate.cpp
  A engines/twp/squirrel/sqfuncstate.h
  A engines/twp/squirrel/sqlexer.cpp
  A engines/twp/squirrel/sqlexer.h
  A engines/twp/squirrel/sqmem.cpp
  A engines/twp/squirrel/sqobject.cpp
  A engines/twp/squirrel/sqobject.h
  A engines/twp/squirrel/sqopcodes.h
  A engines/twp/squirrel/sqpcheader.h
  A engines/twp/squirrel/sqstate.cpp
  A engines/twp/squirrel/sqstate.h
  A engines/twp/squirrel/sqstdaux.cpp
  A engines/twp/squirrel/sqstdaux.h
  A engines/twp/squirrel/sqstdblob.cpp
  A engines/twp/squirrel/sqstdblob.h
  A engines/twp/squirrel/sqstdblobimpl.h
  A engines/twp/squirrel/sqstdio.cpp
  A engines/twp/squirrel/sqstdio.h
  A engines/twp/squirrel/sqstdmath.cpp
  A engines/twp/squirrel/sqstdmath.h
  A engines/twp/squirrel/sqstdrex.cpp
  A engines/twp/squirrel/sqstdstream.cpp
  A engines/twp/squirrel/sqstdstream.h
  A engines/twp/squirrel/sqstdstring.cpp
  A engines/twp/squirrel/sqstdstring.h
  A engines/twp/squirrel/sqstdsystem.h
  A engines/twp/squirrel/sqstring.h
  A engines/twp/squirrel/sqtable.cpp
  A engines/twp/squirrel/sqtable.h
  A engines/twp/squirrel/squirrel.dsp
  A engines/twp/squirrel/squirrel.h
  A engines/twp/squirrel/squserdata.h
  A engines/twp/squirrel/squtils.h
  A engines/twp/squirrel/sqvm.cpp
  A engines/twp/squirrel/sqvm.h
  A engines/twp/vm.cpp
  A engines/twp/vm.h
    engines/twp/module.mk
    engines/twp/twp.cpp


diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
new file mode 100644
index 00000000000..fd7c27387ae
--- /dev/null
+++ b/engines/twp/ggpack.cpp
@@ -0,0 +1,340 @@
+/* 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 "twp/ggpack.h"
+#include "common/debug.h"
+
+namespace Twp {
+
+#define GGP_NULL 1
+#define GGP_DICTIONARY 2
+#define GGP_ARRAY 3
+#define GGP_STRING 4
+#define GGP_INTEGER 5
+#define GGP_DOUBLE 6
+#define GGP_OFFSETS 7
+#define GGP_KEYS 8
+#define GGP_ENDOFFSETS 0xFFFFFFFF
+
+static bool _readPlo(Common::SeekableReadStream *s, Common::Array<int> &offsets) {
+	uint32 ploIndex = s->readUint32LE();
+	int64 pos = s->pos();
+	s->seek(ploIndex);
+	byte c = s->readByte();
+	if (c != GGP_OFFSETS)
+		return false;
+	uint32 offset = 0;
+	while (offset != GGP_ENDOFFSETS) {
+		offset = s->readUint32LE();
+		if (offset != GGP_ENDOFFSETS)
+			offsets.push_back(offset);
+	}
+	s->seek(pos);
+	return true;
+}
+
+Common::String GGHashMapDecoder::readString(uint32 i) {
+	Common::String result;
+	int64 pos = _stream->pos();
+	_stream->seek(_offsets[i]);
+	while (true) {
+		byte c = _stream->readByte();
+		if (c == 0) {
+			_stream->seek(pos);
+			return result;
+		}
+		result += (char)c;
+	}
+	return result;
+}
+
+Common::JSONValue *GGHashMapDecoder::readValue() {
+	byte ggType = _stream->readByte();
+	switch (ggType) {
+	case GGP_NULL: {
+		return new Common::JSONValue();
+	}
+	case GGP_DICTIONARY:
+		_stream->seek(_stream->pos() - 1);
+		return readHash();
+	case GGP_ARRAY:
+		_stream->seek(_stream->pos() - 1);
+		return readArray();
+	case GGP_STRING: {
+		uint32 ploIdx = _stream->readUint32LE();
+		return new Common::JSONValue(readString(ploIdx));
+	}
+	case GGP_INTEGER:
+	case GGP_DOUBLE: {
+		uint32 ploIdx = _stream->readUint32LE();
+		Common::String num_str = readString(ploIdx);
+		return ggType == GGP_INTEGER ? new Common::JSONValue((long long int)atol(num_str.c_str())) : new Common::JSONValue(atof(num_str.c_str()));
+	}
+	default:
+		error("Not Implemented: value type {ggType}");
+		return new Common::JSONValue();
+	}
+}
+
+Common::JSONValue *GGHashMapDecoder::readArray() {
+	byte c = _stream->readByte();
+	if (c != GGP_ARRAY)
+		error("trying to parse a non-array");
+	Common::JSONArray arr;
+	uint32 length = _stream->readUint32LE();
+	for (int i = 0; i < length; i++) {
+		Common::JSONValue *item = readValue();
+		arr.push_back(item);
+	}
+	c = _stream->readByte();
+	if (c != GGP_ARRAY)
+		error("unterminated array");
+	return new Common::JSONValue(arr);
+}
+
+Common::JSONValue *GGHashMapDecoder::readHash() {
+	Common::JSONObject obj;
+	byte c = _stream->readByte();
+	if (c != GGP_DICTIONARY) {
+		error("trying to parse a non-hash: %d", c);
+	}
+	uint32 nPairs = _stream->readUint32LE();
+	for (int i = 0; i < nPairs; i++) {
+		Common::String key = readString(_stream->readUint32LE());
+		obj[key] = readValue();
+	}
+	c = _stream->readByte();
+	if (c != GGP_DICTIONARY) {
+		error("unterminated hash");
+	}
+	return new Common::JSONValue(obj);
+}
+
+GGHashMapDecoder::GGHashMapDecoder()
+	: _stream(nullptr) {
+}
+
+Common::JSONValue *GGHashMapDecoder::open(Common::SeekableReadStream *s) {
+	uint32 signature = s->readUint32LE();
+	if (signature != 0x04030201) {
+		error("This version of package is not supported (yet?)");
+		return new Common::JSONValue();
+	}
+	_stream = s;
+	uint32 numEntries = s->readUint32LE();
+	if (!_readPlo(s, _offsets))
+		error("This version of package is not supported (yet?)");
+	return readHash();
+}
+
+MemStream::MemStream() : _buf(nullptr), _bufSize(0), _pos(0) {
+}
+
+bool MemStream::open(const byte *buf, int64 bufSize) {
+	_buf = buf;
+	_bufSize = bufSize;
+	_pos = 0;
+	return true;
+}
+
+uint32 MemStream::read(void *dataPtr, uint32 dataSize) {
+	int64 size = MIN((int64)dataSize, (int64)_bufSize - _pos);
+	memcpy(dataPtr, _buf + _pos, size);
+	_pos += size;
+	return size;
+}
+
+bool MemStream::eos() const {
+	return _pos >= _bufSize;
+}
+
+int64 MemStream::pos() const {
+	return _pos;
+}
+
+int64 MemStream::size() const {
+	return _bufSize;
+}
+
+bool MemStream::seek(int64 offset, int whence) {
+	if (whence == SEEK_SET) {
+		_pos = offset;
+		return true;
+	}
+	_pos = _bufSize + offset;
+	return true;
+}
+
+RangeStream::RangeStream() : _s(nullptr), _size(0) {
+}
+
+bool RangeStream::open(Common::SeekableReadStream *stream, int64 size) {
+	_s = stream;
+	_start = _s->pos();
+	_size = size;
+	return true;
+}
+
+uint32 RangeStream::read(void *dataPtr, uint32 dataSize) {
+	return _s->read(dataPtr, dataSize);
+}
+
+bool RangeStream::eos() const {
+	return pos() >= _size;
+}
+
+int64 RangeStream::pos() const {
+	return _s->pos() - _start;
+}
+
+int64 RangeStream::size() const {
+	return _size;
+}
+
+bool RangeStream::seek(int64 offset, int whence) {
+	if (whence == SEEK_SET) {
+		return _s->seek(_start + offset, SEEK_SET);
+	}
+	return _s->seek(offset, whence);
+}
+
+XorStream::XorStream() : _s(nullptr), _size(0) {
+}
+
+bool XorStream::open(Common::SeekableReadStream *stream, int len, const XorKey &key) {
+	_s = stream;
+	_start = _s->pos();
+	_previous = (len & 0xFF);
+	_key = key;
+	_size = len;
+	return true;
+}
+
+uint32 XorStream::read(void *dataPtr, uint32 dataSize) {
+	int p = (int)pos();
+	uint32 result = _s->read(dataPtr, dataSize);
+	char *buf = (char *)dataPtr;
+	for (int i = 0; i < dataSize; i++) {
+		int x = buf[i] ^ _key.magicBytes[p & 0x0F] ^ (i * _key.multiplier);
+		buf[i] = (char)(x ^ _previous);
+		_previous = x;
+		p++;
+	}
+	return result;
+}
+
+bool XorStream::eos() const {
+	return pos() >= _size;
+}
+
+int64 XorStream::pos() const {
+	return _s->pos() - _start;
+}
+
+int64 XorStream::size() const {
+	return _size;
+}
+
+bool XorStream::seek(int64 offset, int whence) {
+	if (whence == SEEK_SET) {
+		return _s->seek(_start + offset, SEEK_SET);
+	}
+	return _s->seek(offset, whence);
+}
+
+GGPackDecoder::GGPackDecoder() {
+}
+
+bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
+	_entries.clear();
+	_key = key;
+	_s = s;
+
+	uint32 entriesOffset = s->readUint32LE();
+	uint32 entriesSize = s->readUint32LE();
+	s->seek(entriesOffset);
+
+	// decode entries
+	XorStream xor ;
+	xor.open(s, entriesSize, key);
+	Common::Array<byte> buffer(entriesSize);
+	xor.read(buffer.data(), entriesSize);
+
+	// read entries as hash
+	MemStream ms;
+	ms.open(buffer.data(), entriesSize);
+	GGHashMapDecoder tblDecoder;
+	Common::JSONValue *value = tblDecoder.open(&ms);
+	const Common::JSONObject &obj = value->asObject();
+	const Common::JSONArray &files = obj["files"]->asArray();
+	for (size_t i = 0; i < files.size(); i++) {
+		const Common::JSONObject &file = files[i]->asObject();
+		const Common::String &filename = file["filename"]->asString();
+		int offset = (int)file["offset"]->asIntegerNumber();
+		int size = (int)file["size"]->asIntegerNumber();
+		_entries[filename] = GGPackEntry{offset, size};
+		debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
+	}
+	delete value;
+
+	return true;
+}
+
+GGPackEntryReader::GGPackEntryReader() {}
+
+bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
+	if(!pack._entries.contains(entry))
+		return false;
+	GGPackEntry e = pack._entries[entry];
+	pack._s->seek(e.offset);
+
+	RangeStream rs;
+	rs.open(pack._s, e.size);
+	XorStream xs;
+	xs.open(&rs, e.size, pack._key);
+
+	_buf.resize(e.size);
+	xs.read(&_buf[0], e.size);
+
+	_ms.open(&_buf[0], e.size);
+	return true;
+}
+
+uint32 GGPackEntryReader::read(void *dataPtr, uint32 dataSize) {
+	return _ms.read(dataPtr, dataSize);
+}
+bool GGPackEntryReader::eos() const {
+	return _ms.eos();
+}
+
+int64 GGPackEntryReader::pos() const {
+	return _ms.pos();
+}
+
+int64 GGPackEntryReader::size() const {
+	return _ms.size();
+}
+
+bool GGPackEntryReader::seek(int64 offset, int whence) {
+	return _ms.seek(offset, whence);
+}
+
+} // namespace Twp
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
new file mode 100644
index 00000000000..9725576619e
--- /dev/null
+++ b/engines/twp/ggpack.h
@@ -0,0 +1,151 @@
+/* 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 TWP_GGPACK_H
+#define TWP_GGPACK_H
+
+#include "common/hash-str.h"
+#include "common/stream.h"
+#include "common/list.h"
+#include "common/path.h"
+#include "common/formats/json.h"
+
+namespace Twp {
+
+struct XorKey {
+	Common::Array<int> magicBytes;
+    int multiplier;
+};
+
+class MemStream: public Common::SeekableReadStream {
+public:
+	MemStream();
+
+	bool open(const byte* buf, int64 bufSize);
+	uint32 read(void *dataPtr, uint32 dataSize);
+	bool eos() const;
+
+	int64 pos() const;
+	int64 size() const;
+	bool seek(int64 offset, int whence = SEEK_SET);
+
+private:
+    const byte* _buf;
+	int64 _bufSize;
+	int64 _pos;
+};
+
+class XorStream: public Common::SeekableReadStream {
+public:
+	XorStream();
+
+	bool open(Common::SeekableReadStream *stream, int len, const XorKey& key);
+	uint32 read(void *dataPtr, uint32 dataSize);
+	bool eos() const;
+
+	int64 pos() const;
+	int64 size() const;
+	bool seek(int64 offset, int whence = SEEK_SET);
+
+private:
+    Common::SeekableReadStream *_s;
+    int _previous;
+    int _start;
+    int _size;
+    XorKey _key;
+};
+
+class RangeStream: public Common::SeekableReadStream {
+public:
+	RangeStream();
+
+	bool open(Common::SeekableReadStream *stream, int64 size);
+	uint32 read(void *dataPtr, uint32 dataSize);
+	bool eos() const;
+
+	int64 pos() const;
+	int64 size() const;
+	bool seek(int64 offset, int whence = SEEK_SET);
+
+private:
+    Common::SeekableReadStream *_s;
+    int64 _start;
+    int64 _size;
+};
+
+class GGHashMapDecoder {
+public:
+	GGHashMapDecoder();
+
+	Common::JSONValue* open(Common::SeekableReadStream *stream);
+
+private:
+	Common::JSONValue* readHash();
+	Common::String readString(uint32 i);
+	Common::JSONValue* readValue();
+	Common::JSONValue* readArray();
+
+private:
+	Common::SeekableReadStream *_stream;
+	Common::Array<int> _offsets;
+};
+
+struct GGPackEntry {
+    int offset, size;
+};
+
+typedef Common::HashMap<Common::String, GGPackEntry, Common::IgnoreCase_Hash> GGPackEntries;
+
+class GGPackDecoder {
+public:
+	friend class GGPackEntryReader;
+public:
+	GGPackDecoder();
+
+	bool open(Common::SeekableReadStream *s, const XorKey& key);
+
+private:
+	XorKey _key;
+	GGPackEntries _entries;
+	Common::SeekableReadStream *_s;
+};
+
+class GGPackEntryReader: public Common::SeekableReadStream {
+public:
+	GGPackEntryReader();
+
+	bool open(GGPackDecoder& pack, const Common::String& entry);
+
+	uint32 read(void *dataPtr, uint32 dataSize) override;
+	bool eos() const override;
+
+	int64 pos() const override;
+	int64 size() const override;
+	bool seek(int64 offset, int whence = SEEK_SET) override;
+
+private:
+	Common::Array<byte> _buf;
+	MemStream _ms;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 22d51ece94b..d0e7a82e97c 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -1,9 +1,33 @@
 MODULE := engines/twp
 
+SQUIRREL_OBJS = \
+	squirrel/sqapi.o \
+	squirrel/sqbaselib.o \
+	squirrel/sqfuncstate.o \
+	squirrel/sqdebug.o \
+	squirrel/sqlexer.o \
+	squirrel/sqobject.o \
+	squirrel/sqcompiler.o \
+	squirrel/sqstate.o \
+	squirrel/sqtable.o \
+	squirrel/sqmem.o \
+	squirrel/sqvm.o \
+	squirrel/sqclass.o \
+	squirrel/sqstdio.o \
+	squirrel/sqstdmath.o \
+	squirrel/sqstdstring.o \
+	squirrel/sqstdstream.o \
+	squirrel/sqstdblob.o \
+	squirrel/sqstdrex.o \
+	squirrel/sqstdaux.o
+
 MODULE_OBJS = \
+	$(SQUIRREL_OBJS) \
 	twp.o \
 	console.o \
-	metaengine.o
+	metaengine.o \
+	vm.o \
+	ggpack.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/squirrel/sqapi.cpp b/engines/twp/squirrel/sqapi.cpp
new file mode 100755
index 00000000000..a5766824bf3
--- /dev/null
+++ b/engines/twp/squirrel/sqapi.cpp
@@ -0,0 +1,1635 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include "sqvm.h"
+#include "sqstring.h"
+#include "sqtable.h"
+#include "sqarray.h"
+#include "sqfuncproto.h"
+#include "sqclosure.h"
+#include "squserdata.h"
+#include "sqcompiler.h"
+#include "sqfuncstate.h"
+#include "sqclass.h"
+
+static bool sq_aux_gettypedarg(HSQUIRRELVM v,SQInteger idx,SQObjectType type,SQObjectPtr **o)
+{
+    *o = &stack_get(v,idx);
+    if(sq_type(**o) != type){
+        SQObjectPtr oval = v->PrintObjVal(**o);
+        v->Raise_Error(_SC("wrong argument type, expected '%s' got '%.50s'"),IdType2Name(type),_stringval(oval));
+        return false;
+    }
+    return true;
+}
+
+#define _GETSAFE_OBJ(v,idx,type,o) { if(!sq_aux_gettypedarg(v,idx,type,&o)) return SQ_ERROR; }
+
+#define sq_aux_paramscheck(v,count) \
+{ \
+    if(sq_gettop(v) < count){ v->Raise_Error(_SC("not enough params in the stack")); return SQ_ERROR; }\
+}
+
+
+SQInteger sq_aux_invalidtype(HSQUIRRELVM v,SQObjectType type)
+{
+    SQUnsignedInteger buf_size = 100 *sizeof(SQChar);
+    scsprintf(_ss(v)->GetScratchPad(buf_size), buf_size, _SC("unexpected type %s"), IdType2Name(type));
+    return sq_throwerror(v, _ss(v)->GetScratchPad(-1));
+}
+
+HSQUIRRELVM sq_open(SQInteger initialstacksize)
+{
+    SQSharedState *ss;
+    SQVM *v;
+    sq_new(ss, SQSharedState);
+    ss->Init();
+    v = (SQVM *)SQ_MALLOC(sizeof(SQVM));
+    new (v) SQVM(ss);
+    ss->_root_vm = v;
+    if(v->Init(NULL, initialstacksize)) {
+        return v;
+    } else {
+        sq_delete(v, SQVM);
+        return NULL;
+    }
+    return v;
+}
+
+HSQUIRRELVM sq_newthread(HSQUIRRELVM friendvm, SQInteger initialstacksize)
+{
+    SQSharedState *ss;
+    SQVM *v;
+    ss=_ss(friendvm);
+
+    v= (SQVM *)SQ_MALLOC(sizeof(SQVM));
+    new (v) SQVM(ss);
+
+    if(v->Init(friendvm, initialstacksize)) {
+        friendvm->Push(v);
+        return v;
+    } else {
+        sq_delete(v, SQVM);
+        return NULL;
+    }
+}
+
+SQInteger sq_getvmstate(HSQUIRRELVM v)
+{
+    if(v->_suspended)
+        return SQ_VMSTATE_SUSPENDED;
+    else {
+        if(v->_callsstacksize != 0) return SQ_VMSTATE_RUNNING;
+        else return SQ_VMSTATE_IDLE;
+    }
+}
+
+void sq_seterrorhandler(HSQUIRRELVM v)
+{
+    SQObject o = stack_get(v, -1);
+    if(sq_isclosure(o) || sq_isnativeclosure(o) || sq_isnull(o)) {
+        v->_errorhandler = o;
+        v->Pop();
+    }
+}
+
+void sq_setnativedebughook(HSQUIRRELVM v,SQDEBUGHOOK hook)
+{
+    v->_debughook_native = hook;
+    v->_debughook_closure.Null();
+    v->_debughook = hook?true:false;
+}
+
+void sq_setdebughook(HSQUIRRELVM v)
+{
+    SQObject o = stack_get(v,-1);
+    if(sq_isclosure(o) || sq_isnativeclosure(o) || sq_isnull(o)) {
+        v->_debughook_closure = o;
+        v->_debughook_native = NULL;
+        v->_debughook = !sq_isnull(o);
+        v->Pop();
+    }
+}
+
+void sq_close(HSQUIRRELVM v)
+{
+    SQSharedState *ss = _ss(v);
+    _thread(ss->_root_vm)->Finalize();
+    sq_delete(ss, SQSharedState);
+}
+
+SQInteger sq_getversion()
+{
+    return SQUIRREL_VERSION_NUMBER;
+}
+
+SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror)
+{
+    SQObjectPtr o;
+#ifndef NO_COMPILER
+    if(Compile(v, read, p, sourcename, o, raiseerror?true:false, _ss(v)->_debuginfo)) {
+        v->Push(SQClosure::Create(_ss(v), _funcproto(o), _table(v->_roottable)->GetWeakRef(OT_TABLE)));
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+#else
+    return sq_throwerror(v,_SC("this is a no compiler build"));
+#endif
+}
+
+void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable)
+{
+    _ss(v)->_debuginfo = enable?true:false;
+}
+
+void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable)
+{
+    _ss(v)->_notifyallexceptions = enable?true:false;
+}
+
+void sq_addref(HSQUIRRELVM v,HSQOBJECT *po)
+{
+    if(!ISREFCOUNTED(sq_type(*po))) return;
+#ifdef NO_GARBAGE_COLLECTOR
+    __AddRef(po->_type,po->_unVal);
+#else
+    _ss(v)->_refs_table.AddRef(*po);
+#endif
+}
+
+SQUnsignedInteger sq_getrefcount(HSQUIRRELVM v,HSQOBJECT *po)
+{
+    if(!ISREFCOUNTED(sq_type(*po))) return 0;
+#ifdef NO_GARBAGE_COLLECTOR
+   return po->_unVal.pRefCounted->_uiRef;
+#else
+   return _ss(v)->_refs_table.GetRefCount(*po);
+#endif
+}
+
+SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po)
+{
+    if(!ISREFCOUNTED(sq_type(*po))) return SQTrue;
+#ifdef NO_GARBAGE_COLLECTOR
+    bool ret = (po->_unVal.pRefCounted->_uiRef <= 1) ? SQTrue : SQFalse;
+    __Release(po->_type,po->_unVal);
+    return ret; //the ret val doesn't work(and cannot be fixed)
+#else
+    return _ss(v)->_refs_table.Release(*po);
+#endif
+}
+
+SQUnsignedInteger sq_getvmrefcount(HSQUIRRELVM SQ_UNUSED_ARG(v), const HSQOBJECT *po)
+{
+    if (!ISREFCOUNTED(sq_type(*po))) return 0;
+    return po->_unVal.pRefCounted->_uiRef;
+}
+
+const SQChar *sq_objtostring(const HSQOBJECT *o)
+{
+    if(sq_type(*o) == OT_STRING) {
+        return _stringval(*o);
+    }
+    return NULL;
+}
+
+SQInteger sq_objtointeger(const HSQOBJECT *o)
+{
+    if(sq_isnumeric(*o)) {
+        return tointeger(*o);
+    }
+    return 0;
+}
+
+SQFloat sq_objtofloat(const HSQOBJECT *o)
+{
+    if(sq_isnumeric(*o)) {
+        return tofloat(*o);
+    }
+    return 0;
+}
+
+SQBool sq_objtobool(const HSQOBJECT *o)
+{
+    if(sq_isbool(*o)) {
+        return _integer(*o);
+    }
+    return SQFalse;
+}
+
+SQUserPointer sq_objtouserpointer(const HSQOBJECT *o)
+{
+    if(sq_isuserpointer(*o)) {
+        return _userpointer(*o);
+    }
+    return 0;
+}
+
+void sq_pushnull(HSQUIRRELVM v)
+{
+    v->PushNull();
+}
+
+void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len)
+{
+    if(s)
+        v->Push(SQObjectPtr(SQString::Create(_ss(v), s, len)));
+    else v->PushNull();
+}
+
+void sq_pushinteger(HSQUIRRELVM v,SQInteger n)
+{
+    v->Push(n);
+}
+
+void sq_pushbool(HSQUIRRELVM v,SQBool b)
+{
+    v->Push(b?true:false);
+}
+
+void sq_pushfloat(HSQUIRRELVM v,SQFloat n)
+{
+    v->Push(n);
+}
+
+void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p)
+{
+    v->Push(p);
+}
+
+void sq_pushthread(HSQUIRRELVM v, HSQUIRRELVM thread)
+{
+    v->Push(thread);
+}
+
+SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size)
+{
+    SQUserData *ud = SQUserData::Create(_ss(v), size + SQ_ALIGNMENT);
+    v->Push(ud);
+    return (SQUserPointer)sq_aligning(ud + 1);
+}
+
+void sq_newtable(HSQUIRRELVM v)
+{
+    v->Push(SQTable::Create(_ss(v), 0));
+}
+
+void sq_newtableex(HSQUIRRELVM v,SQInteger initialcapacity)
+{
+    v->Push(SQTable::Create(_ss(v), initialcapacity));
+}
+
+void sq_newarray(HSQUIRRELVM v,SQInteger size)
+{
+    v->Push(SQArray::Create(_ss(v), size));
+}
+
+SQRESULT sq_newclass(HSQUIRRELVM v,SQBool hasbase)
+{
+    SQClass *baseclass = NULL;
+    if(hasbase) {
+        SQObjectPtr &base = stack_get(v,-1);
+        if(sq_type(base) != OT_CLASS)
+            return sq_throwerror(v,_SC("invalid base type"));
+        baseclass = _class(base);
+    }
+    SQClass *newclass = SQClass::Create(_ss(v), baseclass);
+    if(baseclass) v->Pop();
+    v->Push(newclass);
+    return SQ_OK;
+}
+
+SQBool sq_instanceof(HSQUIRRELVM v)
+{
+    SQObjectPtr &inst = stack_get(v,-1);
+    SQObjectPtr &cl = stack_get(v,-2);
+    if(sq_type(inst) != OT_INSTANCE || sq_type(cl) != OT_CLASS)
+        return sq_throwerror(v,_SC("invalid param type"));
+    return _instance(inst)->InstanceOf(_class(cl))?SQTrue:SQFalse;
+}
+
+SQRESULT sq_arrayappend(HSQUIRRELVM v,SQInteger idx)
+{
+    sq_aux_paramscheck(v,2);
+    SQObjectPtr *arr;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,arr);
+    _array(*arr)->Append(v->GetUp(-1));
+    v->Pop();
+    return SQ_OK;
+}
+
+SQRESULT sq_arraypop(HSQUIRRELVM v,SQInteger idx,SQBool pushval)
+{
+    sq_aux_paramscheck(v, 1);
+    SQObjectPtr *arr;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,arr);
+    if(_array(*arr)->Size() > 0) {
+        if(pushval != 0){ v->Push(_array(*arr)->Top()); }
+        _array(*arr)->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v, _SC("empty array"));
+}
+
+SQRESULT sq_arrayresize(HSQUIRRELVM v,SQInteger idx,SQInteger newsize)
+{
+    sq_aux_paramscheck(v,1);
+    SQObjectPtr *arr;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,arr);
+    if(newsize >= 0) {
+        _array(*arr)->Resize(newsize);
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("negative size"));
+}
+
+
+SQRESULT sq_arrayreverse(HSQUIRRELVM v,SQInteger idx)
+{
+    sq_aux_paramscheck(v, 1);
+    SQObjectPtr *o;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,o);
+    SQArray *arr = _array(*o);
+    if(arr->Size() > 0) {
+        SQObjectPtr t;
+        SQInteger size = arr->Size();
+        SQInteger n = size >> 1; size -= 1;
+        for(SQInteger i = 0; i < n; i++) {
+            t = arr->_values[i];
+            arr->_values[i] = arr->_values[size-i];
+            arr->_values[size-i] = t;
+        }
+        return SQ_OK;
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_arrayremove(HSQUIRRELVM v,SQInteger idx,SQInteger itemidx)
+{
+    sq_aux_paramscheck(v, 1);
+    SQObjectPtr *arr;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,arr);
+    return _array(*arr)->Remove(itemidx) ? SQ_OK : sq_throwerror(v,_SC("index out of range"));
+}
+
+SQRESULT sq_arrayinsert(HSQUIRRELVM v,SQInteger idx,SQInteger destpos)
+{
+    sq_aux_paramscheck(v, 1);
+    SQObjectPtr *arr;
+    _GETSAFE_OBJ(v, idx, OT_ARRAY,arr);
+    SQRESULT ret = _array(*arr)->Insert(destpos, v->GetUp(-1)) ? SQ_OK : sq_throwerror(v,_SC("index out of range"));
+    v->Pop();
+    return ret;
+}
+
+void sq_newclosure(HSQUIRRELVM v,SQFUNCTION func,SQUnsignedInteger nfreevars)
+{
+    SQNativeClosure *nc = SQNativeClosure::Create(_ss(v), func,nfreevars);
+    nc->_nparamscheck = 0;
+    for(SQUnsignedInteger i = 0; i < nfreevars; i++) {
+        nc->_outervalues[i] = v->Top();
+        v->Pop();
+    }
+    v->Push(SQObjectPtr(nc));
+}
+
+SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQInteger *nparams,SQInteger *nfreevars)
+{
+    SQObject o = stack_get(v, idx);
+    if(sq_type(o) == OT_CLOSURE) {
+        SQClosure *c = _closure(o);
+        SQFunctionProto *proto = c->_function;
+        *nparams = proto->_nparameters;
+        *nfreevars = proto->_noutervalues;
+        return SQ_OK;
+    }
+    else if(sq_type(o) == OT_NATIVECLOSURE)
+    {
+        SQNativeClosure *c = _nativeclosure(o);
+        *nparams = c->_nparamscheck;
+        *nfreevars = (SQInteger)c->_noutervalues;
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("the object is not a closure"));
+}
+
+SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name)
+{
+    SQObject o = stack_get(v, idx);
+    if(sq_isnativeclosure(o)) {
+        SQNativeClosure *nc = _nativeclosure(o);
+        nc->_name = SQString::Create(_ss(v),name);
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("the object is not a nativeclosure"));
+}
+
+SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask)
+{
+    SQObject o = stack_get(v, -1);
+    if(!sq_isnativeclosure(o))
+        return sq_throwerror(v, _SC("native closure expected"));
+    SQNativeClosure *nc = _nativeclosure(o);
+    nc->_nparamscheck = nparamscheck;
+    if(typemask) {
+        SQIntVec res;
+        if(!CompileTypemask(res, typemask))
+            return sq_throwerror(v, _SC("invalid typemask"));
+        nc->_typecheck.copy(res);
+    }
+    else {
+        nc->_typecheck.resize(0);
+    }
+    if(nparamscheck == SQ_MATCHTYPEMASKSTRING) {
+        nc->_nparamscheck = nc->_typecheck.size();
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_bindenv(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(!sq_isnativeclosure(o) &&
+        !sq_isclosure(o))
+        return sq_throwerror(v,_SC("the target is not a closure"));
+    SQObjectPtr &env = stack_get(v,-1);
+    if(!sq_istable(env) &&
+        !sq_isarray(env) &&
+        !sq_isclass(env) &&
+        !sq_isinstance(env))
+        return sq_throwerror(v,_SC("invalid environment"));
+    SQWeakRef *w = _refcounted(env)->GetWeakRef(sq_type(env));
+    SQObjectPtr ret;
+    if(sq_isclosure(o)) {
+        SQClosure *c = _closure(o)->Clone();
+        __ObjRelease(c->_env);
+        c->_env = w;
+        __ObjAddRef(c->_env);
+        if(_closure(o)->_base) {
+            c->_base = _closure(o)->_base;
+            __ObjAddRef(c->_base);
+        }
+        ret = c;
+    }
+    else { //then must be a native closure
+        SQNativeClosure *c = _nativeclosure(o)->Clone();
+        __ObjRelease(c->_env);
+        c->_env = w;
+        __ObjAddRef(c->_env);
+        ret = c;
+    }
+    v->Pop();
+    v->Push(ret);
+    return SQ_OK;
+}
+
+SQRESULT sq_getclosurename(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(!sq_isnativeclosure(o) &&
+        !sq_isclosure(o))
+        return sq_throwerror(v,_SC("the target is not a closure"));
+    if(sq_isnativeclosure(o))
+    {
+        v->Push(_nativeclosure(o)->_name);
+    }
+    else { //closure
+        v->Push(_closure(o)->_function->_name);
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_setclosureroot(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &c = stack_get(v,idx);
+    SQObject o = stack_get(v, -1);
+    if(!sq_isclosure(c)) return sq_throwerror(v, _SC("closure expected"));
+    if(sq_istable(o)) {
+        _closure(c)->SetRoot(_table(o)->GetWeakRef(OT_TABLE));
+        v->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v, _SC("invalid type"));
+}
+
+SQRESULT sq_getclosureroot(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &c = stack_get(v,idx);
+    if(!sq_isclosure(c)) return sq_throwerror(v, _SC("closure expected"));
+    v->Push(_closure(c)->_root->_obj);
+    return SQ_OK;
+}
+
+SQRESULT sq_clear(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObject &o=stack_get(v,idx);
+    switch(sq_type(o)) {
+        case OT_TABLE: _table(o)->Clear();  break;
+        case OT_ARRAY: _array(o)->Resize(0); break;
+        default:
+            return sq_throwerror(v, _SC("clear only works on table and array"));
+        break;
+
+    }
+    return SQ_OK;
+}
+
+void sq_pushroottable(HSQUIRRELVM v)
+{
+    v->Push(v->_roottable);
+}
+
+void sq_pushregistrytable(HSQUIRRELVM v)
+{
+    v->Push(_ss(v)->_registry);
+}
+
+void sq_pushconsttable(HSQUIRRELVM v)
+{
+    v->Push(_ss(v)->_consts);
+}
+
+SQRESULT sq_setroottable(HSQUIRRELVM v)
+{
+    SQObject o = stack_get(v, -1);
+    if(sq_istable(o) || sq_isnull(o)) {
+        v->_roottable = o;
+        v->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v, _SC("invalid type"));
+}
+
+SQRESULT sq_setconsttable(HSQUIRRELVM v)
+{
+    SQObject o = stack_get(v, -1);
+    if(sq_istable(o)) {
+        _ss(v)->_consts = o;
+        v->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v, _SC("invalid type, expected table"));
+}
+
+void sq_setforeignptr(HSQUIRRELVM v,SQUserPointer p)
+{
+    v->_foreignptr = p;
+}
+
+SQUserPointer sq_getforeignptr(HSQUIRRELVM v)
+{
+    return v->_foreignptr;
+}
+
+void sq_setsharedforeignptr(HSQUIRRELVM v,SQUserPointer p)
+{
+    _ss(v)->_foreignptr = p;
+}
+
+SQUserPointer sq_getsharedforeignptr(HSQUIRRELVM v)
+{
+    return _ss(v)->_foreignptr;
+}
+
+void sq_setvmreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook)
+{
+    v->_releasehook = hook;
+}
+
+SQRELEASEHOOK sq_getvmreleasehook(HSQUIRRELVM v)
+{
+    return v->_releasehook;
+}
+
+void sq_setsharedreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook)
+{
+    _ss(v)->_releasehook = hook;
+}
+
+SQRELEASEHOOK sq_getsharedreleasehook(HSQUIRRELVM v)
+{
+    return _ss(v)->_releasehook;
+}
+
+void sq_push(HSQUIRRELVM v,SQInteger idx)
+{
+    v->Push(stack_get(v, idx));
+}
+
+SQObjectType sq_gettype(HSQUIRRELVM v,SQInteger idx)
+{
+    return sq_type(stack_get(v, idx));
+}
+
+SQRESULT sq_typeof(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    SQObjectPtr res;
+    if(!v->TypeOf(o,res)) {
+        return SQ_ERROR;
+    }
+    v->Push(res);
+    return SQ_OK;
+}
+
+SQRESULT sq_tostring(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    SQObjectPtr res;
+    if(!v->ToString(o,res)) {
+        return SQ_ERROR;
+    }
+    v->Push(res);
+    return SQ_OK;
+}
+
+void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool *b)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    *b = SQVM::IsFalse(o)?SQFalse:SQTrue;
+}
+
+SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    if(sq_isnumeric(o)) {
+        *i = tointeger(o);
+        return SQ_OK;
+    }
+    if(sq_isbool(o)) {
+        *i = SQVM::IsFalse(o)?SQFalse:SQTrue;
+        return SQ_OK;
+    }
+  if(sq_isnull(o)) {
+    *i = SQFalse;
+    return SQ_OK;
+  }
+    return SQ_ERROR;
+}
+
+SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    if(sq_isnumeric(o)) {
+        *f = tofloat(o);
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+}
+
+SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    if(sq_isbool(o)) {
+        *b = _integer(o);
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+}
+
+SQRESULT sq_getstringandsize(HSQUIRRELVM v,SQInteger idx,const SQChar **c,SQInteger *size)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_STRING,o);
+    *c = _stringval(*o);
+    *size = _string(*o)->_len;
+    return SQ_OK;
+}
+
+SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_STRING,o);
+    *c = _stringval(*o);
+    return SQ_OK;
+}
+
+SQRESULT sq_getthread(HSQUIRRELVM v,SQInteger idx,HSQUIRRELVM *thread)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_THREAD,o);
+    *thread = _thread(*o);
+    return SQ_OK;
+}
+
+SQRESULT sq_clone(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    v->PushNull();
+    if(!v->Clone(o, stack_get(v, -1))){
+        v->Pop();
+        return SQ_ERROR;
+    }
+    return SQ_OK;
+}
+
+SQInteger sq_getsize(HSQUIRRELVM v, SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    SQObjectType type = sq_type(o);
+    switch(type) {
+    case OT_STRING:     return _string(o)->_len;
+    case OT_TABLE:      return _table(o)->CountUsed();
+    case OT_ARRAY:      return _array(o)->Size();
+    case OT_USERDATA:   return _userdata(o)->_size;
+    case OT_INSTANCE:   return _instance(o)->_class->_udsize;
+    case OT_CLASS:      return _class(o)->_udsize;
+    default:
+        return sq_aux_invalidtype(v, type);
+    }
+}
+
+SQHash sq_gethash(HSQUIRRELVM v, SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v, idx);
+    return HashObj(o);
+}
+
+SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPointer *typetag)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_USERDATA,o);
+    (*p) = _userdataval(*o);
+    if(typetag) *typetag = _userdata(*o)->_typetag;
+    return SQ_OK;
+}
+
+SQRESULT sq_settypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer typetag)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    switch(sq_type(o)) {
+        case OT_USERDATA:   _userdata(o)->_typetag = typetag;   break;
+        case OT_CLASS:      _class(o)->_typetag = typetag;      break;
+        default:            return sq_throwerror(v,_SC("invalid object type"));
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_getobjtypetag(const HSQOBJECT *o,SQUserPointer * typetag)
+{
+  switch(sq_type(*o)) {
+    case OT_INSTANCE: *typetag = _instance(*o)->_class->_typetag; break;
+    case OT_USERDATA: *typetag = _userdata(*o)->_typetag; break;
+    case OT_CLASS:    *typetag = _class(*o)->_typetag; break;
+    default: return SQ_ERROR;
+  }
+  return SQ_OK;
+}
+
+SQRESULT sq_gettypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer *typetag)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if (SQ_FAILED(sq_getobjtypetag(&o, typetag)))
+        return SQ_ERROR;// this is not an error it should be a bool but would break backward compatibility
+    return SQ_OK;
+}
+
+SQRESULT sq_getuserpointer(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_USERPOINTER,o);
+    (*p) = _userpointer(*o);
+    return SQ_OK;
+}
+
+SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer p)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(sq_type(o) != OT_INSTANCE) return sq_throwerror(v,_SC("the object is not a class instance"));
+    _instance(o)->_userpointer = p;
+    return SQ_OK;
+}
+
+SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(sq_type(o) != OT_CLASS) return sq_throwerror(v,_SC("the object is not a class"));
+    if(_class(o)->_locked) return sq_throwerror(v,_SC("the class is locked"));
+    _class(o)->_udsize = udsize;
+    return SQ_OK;
+}
+
+
+SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p,SQUserPointer typetag)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(sq_type(o) != OT_INSTANCE) return sq_throwerror(v,_SC("the object is not a class instance"));
+    (*p) = _instance(o)->_userpointer;
+    if(typetag != 0) {
+        SQClass *cl = _instance(o)->_class;
+        do{
+            if(cl->_typetag == typetag)
+                return SQ_OK;
+            cl = cl->_base;
+        }while(cl != NULL);
+        return sq_throwerror(v,_SC("invalid type tag"));
+    }
+    return SQ_OK;
+}
+
+SQInteger sq_gettop(HSQUIRRELVM v)
+{
+    return (v->_top) - v->_stackbase;
+}
+
+void sq_settop(HSQUIRRELVM v, SQInteger newtop)
+{
+    SQInteger top = sq_gettop(v);
+    if(top > newtop)
+        sq_pop(v, top - newtop);
+    else
+        while(top++ < newtop) sq_pushnull(v);
+}
+
+void sq_pop(HSQUIRRELVM v, SQInteger nelemstopop)
+{
+    assert(v->_top >= nelemstopop);
+    v->Pop(nelemstopop);
+}
+
+void sq_poptop(HSQUIRRELVM v)
+{
+    assert(v->_top >= 1);
+    v->Pop();
+}
+
+
+void sq_remove(HSQUIRRELVM v, SQInteger idx)
+{
+    v->Remove(idx);
+}
+
+SQInteger sq_cmp(HSQUIRRELVM v)
+{
+    SQInteger res;
+    v->ObjCmp(stack_get(v, -1), stack_get(v, -2),res);
+    return res;
+}
+
+SQRESULT sq_newslot(HSQUIRRELVM v, SQInteger idx, SQBool bstatic)
+{
+    sq_aux_paramscheck(v, 3);
+    SQObjectPtr &self = stack_get(v, idx);
+    if(sq_type(self) == OT_TABLE || sq_type(self) == OT_CLASS) {
+        SQObjectPtr &key = v->GetUp(-2);
+        if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null is not a valid key"));
+        v->NewSlot(self, key, v->GetUp(-1),bstatic?true:false);
+        v->Pop(2);
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_deleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval)
+{
+    sq_aux_paramscheck(v, 2);
+    SQObjectPtr *self;
+    _GETSAFE_OBJ(v, idx, OT_TABLE,self);
+    SQObjectPtr &key = v->GetUp(-1);
+    if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null is not a valid key"));
+    SQObjectPtr res;
+    if(!v->DeleteSlot(*self, key, res)){
+        v->Pop();
+        return SQ_ERROR;
+    }
+    if(pushval) v->GetUp(-1) = res;
+    else v->Pop();
+    return SQ_OK;
+}
+
+SQRESULT sq_set(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self = stack_get(v, idx);
+    if(v->Set(self, v->GetUp(-2), v->GetUp(-1),DONT_FALL_BACK)) {
+        v->Pop(2);
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+}
+
+SQRESULT sq_rawset(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self = stack_get(v, idx);
+    SQObjectPtr &key = v->GetUp(-2);
+    if(sq_type(key) == OT_NULL) {
+        v->Pop(2);
+        return sq_throwerror(v, _SC("null key"));
+    }
+    switch(sq_type(self)) {
+    case OT_TABLE:
+        _table(self)->NewSlot(key, v->GetUp(-1));
+        v->Pop(2);
+        return SQ_OK;
+    break;
+    case OT_CLASS:
+        _class(self)->NewSlot(_ss(v), key, v->GetUp(-1),false);
+        v->Pop(2);
+        return SQ_OK;
+    break;
+    case OT_INSTANCE:
+        if(_instance(self)->Set(key, v->GetUp(-1))) {
+            v->Pop(2);
+            return SQ_OK;
+        }
+    break;
+    case OT_ARRAY:
+        if(v->Set(self, key, v->GetUp(-1),false)) {
+            v->Pop(2);
+            return SQ_OK;
+        }
+    break;
+    default:
+        v->Pop(2);
+        return sq_throwerror(v, _SC("rawset works only on array/table/class and instance"));
+    }
+    v->Raise_IdxError(v->GetUp(-2));return SQ_ERROR;
+}
+
+SQRESULT sq_newmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic)
+{
+    SQObjectPtr &self = stack_get(v, idx);
+    if(sq_type(self) != OT_CLASS) return sq_throwerror(v, _SC("new member only works with classes"));
+    SQObjectPtr &key = v->GetUp(-3);
+    if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null key"));
+    if(!v->NewSlotA(self,key,v->GetUp(-2),v->GetUp(-1),bstatic?true:false,false)) {
+        v->Pop(3);
+        return SQ_ERROR;
+    }
+    v->Pop(3);
+    return SQ_OK;
+}
+
+SQRESULT sq_rawnewmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic)
+{
+    SQObjectPtr &self = stack_get(v, idx);
+    if(sq_type(self) != OT_CLASS) return sq_throwerror(v, _SC("new member only works with classes"));
+    SQObjectPtr &key = v->GetUp(-3);
+    if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null key"));
+    if(!v->NewSlotA(self,key,v->GetUp(-2),v->GetUp(-1),bstatic?true:false,true)) {
+        v->Pop(3);
+        return SQ_ERROR;
+    }
+    v->Pop(3);
+    return SQ_OK;
+}
+
+SQRESULT sq_setdelegate(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self = stack_get(v, idx);
+    SQObjectPtr &mt = v->GetUp(-1);
+    SQObjectType type = sq_type(self);
+    switch(type) {
+    case OT_TABLE:
+        if(sq_type(mt) == OT_TABLE) {
+            if(!_table(self)->SetDelegate(_table(mt))) {
+                return sq_throwerror(v, _SC("delagate cycle"));
+            }
+            v->Pop();
+        }
+        else if(sq_type(mt)==OT_NULL) {
+            _table(self)->SetDelegate(NULL); v->Pop(); }
+        else return sq_aux_invalidtype(v,type);
+        break;
+    case OT_USERDATA:
+        if(sq_type(mt)==OT_TABLE) {
+            _userdata(self)->SetDelegate(_table(mt)); v->Pop(); }
+        else if(sq_type(mt)==OT_NULL) {
+            _userdata(self)->SetDelegate(NULL); v->Pop(); }
+        else return sq_aux_invalidtype(v, type);
+        break;
+    default:
+            return sq_aux_invalidtype(v, type);
+        break;
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_rawdeleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval)
+{
+    sq_aux_paramscheck(v, 2);
+    SQObjectPtr *self;
+    _GETSAFE_OBJ(v, idx, OT_TABLE,self);
+    SQObjectPtr &key = v->GetUp(-1);
+    SQObjectPtr t;
+    if(_table(*self)->Get(key,t)) {
+        _table(*self)->Remove(key);
+    }
+    if(pushval != 0)
+        v->GetUp(-1) = t;
+    else
+        v->Pop();
+    return SQ_OK;
+}
+
+SQRESULT sq_getdelegate(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self=stack_get(v,idx);
+    switch(sq_type(self)){
+    case OT_TABLE:
+    case OT_USERDATA:
+        if(!_delegable(self)->_delegate){
+            v->PushNull();
+            break;
+        }
+        v->Push(SQObjectPtr(_delegable(self)->_delegate));
+        break;
+    default: return sq_throwerror(v,_SC("wrong type")); break;
+    }
+    return SQ_OK;
+
+}
+
+SQRESULT sq_get(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self=stack_get(v,idx);
+    SQObjectPtr &obj = v->GetUp(-1);
+    if(v->Get(self,obj,obj,false,DONT_FALL_BACK))
+        return SQ_OK;
+    v->Pop();
+    return SQ_ERROR;
+}
+
+SQRESULT sq_rawget(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &self=stack_get(v,idx);
+    SQObjectPtr &obj = v->GetUp(-1);
+    switch(sq_type(self)) {
+    case OT_TABLE:
+        if(_table(self)->Get(obj,obj))
+            return SQ_OK;
+        break;
+    case OT_CLASS:
+        if(_class(self)->Get(obj,obj))
+            return SQ_OK;
+        break;
+    case OT_INSTANCE:
+        if(_instance(self)->Get(obj,obj))
+            return SQ_OK;
+        break;
+    case OT_ARRAY:{
+        if(sq_isnumeric(obj)){
+            if(_array(self)->Get(tointeger(obj),obj)) {
+                return SQ_OK;
+            }
+        }
+        else {
+            v->Pop();
+            return sq_throwerror(v,_SC("invalid index type for an array"));
+        }
+                  }
+        break;
+    default:
+        v->Pop();
+        return sq_throwerror(v,_SC("rawget works only on array/table/instance and class"));
+    }
+    v->Pop();
+    return sq_throwerror(v,_SC("the index doesn't exist"));
+}
+
+SQRESULT sq_getstackobj(HSQUIRRELVM v,SQInteger idx,HSQOBJECT *po)
+{
+    *po=stack_get(v,idx);
+    return SQ_OK;
+}
+
+const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx)
+{
+    SQUnsignedInteger cstksize=v->_callsstacksize;
+    SQUnsignedInteger lvl=(cstksize-level)-1;
+    SQInteger stackbase=v->_stackbase;
+    if(lvl<cstksize){
+        for(SQUnsignedInteger i=0;i<level;i++){
+            SQVM::CallInfo &ci=v->_callsstack[(cstksize-i)-1];
+            stackbase-=ci._prevstkbase;
+        }
+        SQVM::CallInfo &ci=v->_callsstack[lvl];
+        if(sq_type(ci._closure)!=OT_CLOSURE)
+            return NULL;
+        SQClosure *c=_closure(ci._closure);
+        SQFunctionProto *func=c->_function;
+        if(func->_noutervalues > (SQInteger)idx) {
+            v->Push(*_outer(c->_outervalues[idx])->_valptr);
+            return _stringval(func->_outervalues[idx]._name);
+        }
+        idx -= func->_noutervalues;
+        return func->GetLocal(v,stackbase,idx,(SQInteger)(ci._ip-func->_instructions)-1);
+    }
+    return NULL;
+}
+
+void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj)
+{
+    v->Push(SQObjectPtr(obj));
+}
+
+void sq_resetobject(HSQOBJECT *po)
+{
+    po->_unVal.pUserPointer=NULL;po->_type=OT_NULL;
+}
+
+SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err)
+{
+    v->_lasterror=SQString::Create(_ss(v),err);
+    return SQ_ERROR;
+}
+
+SQRESULT sq_throwobject(HSQUIRRELVM v)
+{
+    v->_lasterror = v->GetUp(-1);
+    v->Pop();
+    return SQ_ERROR;
+}
+
+
+void sq_reseterror(HSQUIRRELVM v)
+{
+    v->_lasterror.Null();
+}
+
+void sq_getlasterror(HSQUIRRELVM v)
+{
+    v->Push(v->_lasterror);
+}
+
+SQRESULT sq_reservestack(HSQUIRRELVM v,SQInteger nsize)
+{
+    if (((SQUnsignedInteger)v->_top + nsize) > v->_stack.size()) {
+        if(v->_nmetamethodscall) {
+            return sq_throwerror(v,_SC("cannot resize stack while in a metamethod"));
+        }
+        v->_stack.resize(v->_stack.size() + ((v->_top + nsize) - v->_stack.size()));
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_resume(HSQUIRRELVM v,SQBool retval,SQBool raiseerror)
+{
+    if (sq_type(v->GetUp(-1)) == OT_GENERATOR)
+    {
+        v->PushNull(); //retval
+        if (!v->Execute(v->GetUp(-2), 0, v->_top, v->GetUp(-1), raiseerror, SQVM::ET_RESUME_GENERATOR))
+        {v->Raise_Error(v->_lasterror); return SQ_ERROR;}
+        if(!retval)
+            v->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("only generators can be resumed"));
+}
+
+SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror)
+{
+    SQObjectPtr res;
+    if(!v->Call(v->GetUp(-(params+1)),params,v->_top-params,res,raiseerror?true:false)){
+        v->Pop(params); //pop args
+        return SQ_ERROR;
+    }
+    if(!v->_suspended)
+        v->Pop(params); //pop args
+    if(retval)
+        v->Push(res); // push result
+    return SQ_OK;
+}
+
+SQRESULT sq_tailcall(HSQUIRRELVM v, SQInteger nparams)
+{
+	SQObjectPtr &res = v->GetUp(-(nparams + 1));
+	if (sq_type(res) != OT_CLOSURE) {
+		return sq_throwerror(v, _SC("only closure can be tail called"));
+	}
+	SQClosure *clo = _closure(res);
+	if (clo->_function->_bgenerator)
+	{
+		return sq_throwerror(v, _SC("generators cannot be tail called"));
+	}
+	
+	SQInteger stackbase = (v->_top - nparams) - v->_stackbase;
+	if (!v->TailCall(clo, stackbase, nparams)) {
+		return SQ_ERROR;
+	}
+	return SQ_TAILCALL_FLAG;
+}
+
+SQRESULT sq_suspendvm(HSQUIRRELVM v)
+{
+    return v->Suspend();
+}
+
+SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool wakeupret,SQBool retval,SQBool raiseerror,SQBool throwerror)
+{
+    SQObjectPtr ret;
+    if(!v->_suspended)
+        return sq_throwerror(v,_SC("cannot resume a vm that is not running any code"));
+    SQInteger target = v->_suspended_target;
+    if(wakeupret) {
+        if(target != -1) {
+            v->GetAt(v->_stackbase+v->_suspended_target)=v->GetUp(-1); //retval
+        }
+        v->Pop();
+    } else if(target != -1) { v->GetAt(v->_stackbase+v->_suspended_target).Null(); }
+    SQObjectPtr dummy;
+    if(!v->Execute(dummy,-1,-1,ret,raiseerror,throwerror?SQVM::ET_RESUME_THROW_VM : SQVM::ET_RESUME_VM)) {
+        return SQ_ERROR;
+    }
+    if(retval)
+        v->Push(ret);
+    return SQ_OK;
+}
+
+void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook)
+{
+    SQObjectPtr &ud=stack_get(v,idx);
+    switch(sq_type(ud) ) {
+    case OT_USERDATA:   _userdata(ud)->_hook = hook;    break;
+    case OT_INSTANCE:   _instance(ud)->_hook = hook;    break;
+    case OT_CLASS:      _class(ud)->_hook = hook;       break;
+    default: return;
+    }
+}
+
+SQRELEASEHOOK sq_getreleasehook(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &ud=stack_get(v,idx);
+    switch(sq_type(ud) ) {
+    case OT_USERDATA:   return _userdata(ud)->_hook;    break;
+    case OT_INSTANCE:   return _instance(ud)->_hook;    break;
+    case OT_CLASS:      return _class(ud)->_hook;       break;
+    default: return NULL;
+    }
+}
+
+void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f)
+{
+    _ss(v)->_compilererrorhandler = f;
+}
+
+SQRESULT sq_writeclosure(HSQUIRRELVM v,SQWRITEFUNC w,SQUserPointer up)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, -1, OT_CLOSURE,o);
+    unsigned short tag = SQ_BYTECODE_STREAM_TAG;
+    if(_closure(*o)->_function->_noutervalues)
+        return sq_throwerror(v,_SC("a closure with free variables bound cannot be serialized"));
+    if(w(up,&tag,2) != 2)
+        return sq_throwerror(v,_SC("io error"));
+    if(!_closure(*o)->Save(v,up,w))
+        return SQ_ERROR;
+    return SQ_OK;
+}
+
+SQRESULT sq_readclosure(HSQUIRRELVM v,SQREADFUNC r,SQUserPointer up)
+{
+    SQObjectPtr closure;
+
+    unsigned short tag;
+    if(r(up,&tag,2) != 2)
+        return sq_throwerror(v,_SC("io error"));
+    if(tag != SQ_BYTECODE_STREAM_TAG)
+        return sq_throwerror(v,_SC("invalid stream"));
+    if(!SQClosure::Load(v,up,r,closure))
+        return SQ_ERROR;
+    v->Push(closure);
+    return SQ_OK;
+}
+
+SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize)
+{
+    return _ss(v)->GetScratchPad(minsize);
+}
+
+SQRESULT sq_resurrectunreachable(HSQUIRRELVM v)
+{
+#ifndef NO_GARBAGE_COLLECTOR
+    _ss(v)->ResurrectUnreachable(v);
+    return SQ_OK;
+#else
+    return sq_throwerror(v,_SC("sq_resurrectunreachable requires a garbage collector build"));
+#endif
+}
+
+SQInteger sq_collectgarbage(HSQUIRRELVM v)
+{
+#ifndef NO_GARBAGE_COLLECTOR
+    return _ss(v)->CollectGarbage(v);
+#else
+    return -1;
+#endif
+}
+
+SQRESULT sq_getcallee(HSQUIRRELVM v)
+{
+    if(v->_callsstacksize > 1)
+    {
+        v->Push(v->_callsstack[v->_callsstacksize - 2]._closure);
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("no closure in the calls stack"));
+}
+
+const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval)
+{
+    SQObjectPtr &self=stack_get(v,idx);
+    const SQChar *name = NULL;
+    switch(sq_type(self))
+    {
+    case OT_CLOSURE:{
+        SQClosure *clo = _closure(self);
+        SQFunctionProto *fp = clo->_function;
+        if(((SQUnsignedInteger)fp->_noutervalues) > nval) {
+            v->Push(*(_outer(clo->_outervalues[nval])->_valptr));
+            SQOuterVar &ov = fp->_outervalues[nval];
+            name = _stringval(ov._name);
+        }
+                    }
+        break;
+    case OT_NATIVECLOSURE:{
+        SQNativeClosure *clo = _nativeclosure(self);
+        if(clo->_noutervalues > nval) {
+            v->Push(clo->_outervalues[nval]);
+            name = _SC("@NATIVE");
+        }
+                          }
+        break;
+    default: break; //shutup compiler
+    }
+    return name;
+}
+
+SQRESULT sq_setfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval)
+{
+    SQObjectPtr &self=stack_get(v,idx);
+    switch(sq_type(self))
+    {
+    case OT_CLOSURE:{
+        SQFunctionProto *fp = _closure(self)->_function;
+        if(((SQUnsignedInteger)fp->_noutervalues) > nval){
+            *(_outer(_closure(self)->_outervalues[nval])->_valptr) = stack_get(v,-1);
+        }
+        else return sq_throwerror(v,_SC("invalid free var index"));
+                    }
+        break;
+    case OT_NATIVECLOSURE:
+        if(_nativeclosure(self)->_noutervalues > nval){
+            _nativeclosure(self)->_outervalues[nval] = stack_get(v,-1);
+        }
+        else return sq_throwerror(v,_SC("invalid free var index"));
+        break;
+    default:
+        return sq_aux_invalidtype(v, sq_type(self));
+    }
+    v->Pop();
+    return SQ_OK;
+}
+
+SQRESULT sq_setattributes(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_CLASS,o);
+    SQObjectPtr &key = stack_get(v,-2);
+    SQObjectPtr &val = stack_get(v,-1);
+    SQObjectPtr attrs;
+    if(sq_type(key) == OT_NULL) {
+        attrs = _class(*o)->_attributes;
+        _class(*o)->_attributes = val;
+        v->Pop(2);
+        v->Push(attrs);
+        return SQ_OK;
+    }else if(_class(*o)->GetAttributes(key,attrs)) {
+        _class(*o)->SetAttributes(key,val);
+        v->Pop(2);
+        v->Push(attrs);
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("wrong index"));
+}
+
+SQRESULT sq_getattributes(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_CLASS,o);
+    SQObjectPtr &key = stack_get(v,-1);
+    SQObjectPtr attrs;
+    if(sq_type(key) == OT_NULL) {
+        attrs = _class(*o)->_attributes;
+        v->Pop();
+        v->Push(attrs);
+        return SQ_OK;
+    }
+    else if(_class(*o)->GetAttributes(key,attrs)) {
+        v->Pop();
+        v->Push(attrs);
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("wrong index"));
+}
+
+SQRESULT sq_getmemberhandle(HSQUIRRELVM v,SQInteger idx,HSQMEMBERHANDLE *handle)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_CLASS,o);
+    SQObjectPtr &key = stack_get(v,-1);
+    SQTable *m = _class(*o)->_members;
+    SQObjectPtr val;
+    if(m->Get(key,val)) {
+        handle->_static = _isfield(val) ? SQFalse : SQTrue;
+        handle->_index = _member_idx(val);
+        v->Pop();
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("wrong index"));
+}
+
+SQRESULT _getmemberbyhandle(HSQUIRRELVM v,SQObjectPtr &self,const HSQMEMBERHANDLE *handle,SQObjectPtr *&val)
+{
+    switch(sq_type(self)) {
+        case OT_INSTANCE: {
+                SQInstance *i = _instance(self);
+                if(handle->_static) {
+                    SQClass *c = i->_class;
+                    val = &c->_methods[handle->_index].val;
+                }
+                else {
+                    val = &i->_values[handle->_index];
+
+                }
+            }
+            break;
+        case OT_CLASS: {
+                SQClass *c = _class(self);
+                if(handle->_static) {
+                    val = &c->_methods[handle->_index].val;
+                }
+                else {
+                    val = &c->_defaultvalues[handle->_index].val;
+                }
+            }
+            break;
+        default:
+            return sq_throwerror(v,_SC("wrong type(expected class or instance)"));
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_getbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle)
+{
+    SQObjectPtr &self = stack_get(v,idx);
+    SQObjectPtr *val = NULL;
+    if(SQ_FAILED(_getmemberbyhandle(v,self,handle,val))) {
+        return SQ_ERROR;
+    }
+    v->Push(_realval(*val));
+    return SQ_OK;
+}
+
+SQRESULT sq_setbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle)
+{
+    SQObjectPtr &self = stack_get(v,idx);
+    SQObjectPtr &newval = stack_get(v,-1);
+    SQObjectPtr *val = NULL;
+    if(SQ_FAILED(_getmemberbyhandle(v,self,handle,val))) {
+        return SQ_ERROR;
+    }
+    *val = newval;
+    v->Pop();
+    return SQ_OK;
+}
+
+SQRESULT sq_getbase(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_CLASS,o);
+    if(_class(*o)->_base)
+        v->Push(SQObjectPtr(_class(*o)->_base));
+    else
+        v->PushNull();
+    return SQ_OK;
+}
+
+SQRESULT sq_getclass(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_INSTANCE,o);
+    v->Push(SQObjectPtr(_instance(*o)->_class));
+    return SQ_OK;
+}
+
+SQRESULT sq_createinstance(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr *o = NULL;
+    _GETSAFE_OBJ(v, idx, OT_CLASS,o);
+    v->Push(_class(*o)->CreateInstance());
+    return SQ_OK;
+}
+
+void sq_weakref(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObject &o=stack_get(v,idx);
+    if(ISREFCOUNTED(sq_type(o))) {
+        v->Push(_refcounted(o)->GetWeakRef(sq_type(o)));
+        return;
+    }
+    v->Push(o);
+}
+
+SQRESULT sq_getweakrefval(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr &o = stack_get(v,idx);
+    if(sq_type(o) != OT_WEAKREF) {
+        return sq_throwerror(v,_SC("the object must be a weakref"));
+    }
+    v->Push(_weakref(o)->_obj);
+    return SQ_OK;
+}
+
+SQRESULT sq_getdefaultdelegate(HSQUIRRELVM v,SQObjectType t)
+{
+    SQSharedState *ss = _ss(v);
+    switch(t) {
+    case OT_TABLE: v->Push(ss->_table_default_delegate); break;
+    case OT_ARRAY: v->Push(ss->_array_default_delegate); break;
+    case OT_STRING: v->Push(ss->_string_default_delegate); break;
+    case OT_INTEGER: case OT_FLOAT: v->Push(ss->_number_default_delegate); break;
+    case OT_GENERATOR: v->Push(ss->_generator_default_delegate); break;
+    case OT_CLOSURE: case OT_NATIVECLOSURE: v->Push(ss->_closure_default_delegate); break;
+    case OT_THREAD: v->Push(ss->_thread_default_delegate); break;
+    case OT_CLASS: v->Push(ss->_class_default_delegate); break;
+    case OT_INSTANCE: v->Push(ss->_instance_default_delegate); break;
+    case OT_WEAKREF: v->Push(ss->_weakref_default_delegate); break;
+    default: return sq_throwerror(v,_SC("the type doesn't have a default delegate"));
+    }
+    return SQ_OK;
+}
+
+SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx)
+{
+    SQObjectPtr o=stack_get(v,idx),&refpos = stack_get(v,-1),realkey,val;
+    if(sq_type(o) == OT_GENERATOR) {
+        return sq_throwerror(v,_SC("cannot iterate a generator"));
+    }
+    int faketojump;
+    if(!v->FOREACH_OP(o,realkey,val,refpos,0,666,faketojump))
+        return SQ_ERROR;
+    if(faketojump != 666) {
+        v->Push(realkey);
+        v->Push(val);
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+}
+
+struct BufState{
+    const SQChar *buf;
+    SQInteger ptr;
+    SQInteger size;
+};
+
+SQInteger buf_lexfeed(SQUserPointer file)
+{
+    BufState *buf=(BufState*)file;
+    if(buf->size<(buf->ptr+1))
+        return 0;
+    return buf->buf[buf->ptr++];
+}
+
+SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror) {
+    BufState buf;
+    buf.buf = s;
+    buf.size = size;
+    buf.ptr = 0;
+    return sq_compile(v, buf_lexfeed, &buf, sourcename, raiseerror);
+}
+
+void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx)
+{
+    dest->Push(stack_get(src,idx));
+}
+
+void sq_setprintfunc(HSQUIRRELVM v, SQPRINTFUNCTION printfunc,SQPRINTFUNCTION errfunc)
+{
+    _ss(v)->_printfunc = printfunc;
+    _ss(v)->_errorfunc = errfunc;
+}
+
+SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v)
+{
+    return _ss(v)->_printfunc;
+}
+
+SQPRINTFUNCTION sq_geterrorfunc(HSQUIRRELVM v)
+{
+    return _ss(v)->_errorfunc;
+}
+
+void *sq_malloc(SQUnsignedInteger size)
+{
+    return SQ_MALLOC(size);
+}
+
+void *sq_realloc(void* p,SQUnsignedInteger oldsize,SQUnsignedInteger newsize)
+{
+    return SQ_REALLOC(p,oldsize,newsize);
+}
+
+void sq_free(void *p,SQUnsignedInteger size)
+{
+    SQ_FREE(p,size);
+}
diff --git a/engines/twp/squirrel/sqarray.h b/engines/twp/squirrel/sqarray.h
new file mode 100755
index 00000000000..7c6c204945d
--- /dev/null
+++ b/engines/twp/squirrel/sqarray.h
@@ -0,0 +1,94 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQARRAY_H_
+#define _SQARRAY_H_
+
+struct SQArray : public CHAINABLE_OBJ
+{
+private:
+    SQArray(SQSharedState *ss,SQInteger nsize){_values.resize(nsize); INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);}
+    ~SQArray()
+    {
+        REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
+    }
+public:
+    static SQArray* Create(SQSharedState *ss,SQInteger nInitialSize){
+        SQArray *newarray=(SQArray*)SQ_MALLOC(sizeof(SQArray));
+        new (newarray) SQArray(ss,nInitialSize);
+        return newarray;
+    }
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    SQObjectType GetType() {return OT_ARRAY;}
+#endif
+    void Finalize(){
+        _values.resize(0);
+    }
+    bool Get(const SQInteger nidx,SQObjectPtr &val)
+    {
+        if(nidx>=0 && nidx<(SQInteger)_values.size()){
+            SQObjectPtr &o = _values[nidx];
+            val = _realval(o);
+            return true;
+        }
+        else return false;
+    }
+    bool Set(const SQInteger nidx,const SQObjectPtr &val)
+    {
+        if(nidx>=0 && nidx<(SQInteger)_values.size()){
+            _values[nidx]=val;
+            return true;
+        }
+        else return false;
+    }
+    SQInteger Next(const SQObjectPtr &refpos,SQObjectPtr &outkey,SQObjectPtr &outval)
+    {
+        SQUnsignedInteger idx=TranslateIndex(refpos);
+        while(idx<_values.size()){
+            //first found
+            outkey=(SQInteger)idx;
+            SQObjectPtr &o = _values[idx];
+            outval = _realval(o);
+            //return idx for the next iteration
+            return ++idx;
+        }
+        //nothing to iterate anymore
+        return -1;
+    }
+    SQArray *Clone(){SQArray *anew=Create(_opt_ss(this),0); anew->_values.copy(_values); return anew; }
+    SQInteger Size() const {return _values.size();}
+    void Resize(SQInteger size)
+    {
+        SQObjectPtr _null;
+        Resize(size,_null);
+    }
+    void Resize(SQInteger size,SQObjectPtr &fill) { _values.resize(size,fill); ShrinkIfNeeded(); }
+    void Reserve(SQInteger size) { _values.reserve(size); }
+    void Append(const SQObject &o){_values.push_back(o);}
+    void Extend(const SQArray *a);
+    SQObjectPtr &Top(){return _values.top();}
+    void Pop(){_values.pop_back(); ShrinkIfNeeded(); }
+    bool Insert(SQInteger idx,const SQObject &val){
+        if(idx < 0 || idx > (SQInteger)_values.size())
+            return false;
+        _values.insert(idx,val);
+        return true;
+    }
+    void ShrinkIfNeeded() {
+        if(_values.size() <= _values.capacity()>>2) //shrink the array
+            _values.shrinktofit();
+    }
+    bool Remove(SQInteger idx){
+        if(idx < 0 || idx >= (SQInteger)_values.size())
+            return false;
+        _values.remove(idx);
+        ShrinkIfNeeded();
+        return true;
+    }
+    void Release()
+    {
+        sq_delete(this,SQArray);
+    }
+
+    SQObjectPtrVec _values;
+};
+#endif //_SQARRAY_H_
diff --git a/engines/twp/squirrel/sqbaselib.cpp b/engines/twp/squirrel/sqbaselib.cpp
new file mode 100755
index 00000000000..1461513c3a9
--- /dev/null
+++ b/engines/twp/squirrel/sqbaselib.cpp
@@ -0,0 +1,1354 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include "sqvm.h"
+#include "sqstring.h"
+#include "sqtable.h"
+#include "sqarray.h"
+#include "sqfuncproto.h"
+#include "sqclosure.h"
+#include "sqclass.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+static bool str2num(const SQChar *s,SQObjectPtr &res,SQInteger base)
+{
+    SQChar *end;
+    const SQChar *e = s;
+    bool iseintbase = base > 13; //to fix error converting hexadecimals with e like 56f0791e
+    bool isfloat = false;
+    SQChar c;
+    while((c = *e) != _SC('\0'))
+    {
+        if (c == _SC('.') || (!iseintbase && (c == _SC('E') || c == _SC('e')))) { //e and E is for scientific notation
+            isfloat = true;
+            break;
+        }
+        e++;
+    }
+    if(isfloat){
+        SQFloat r = SQFloat(scstrtod(s,&end));
+        if(s == end) return false;
+        res = r;
+    }
+    else{
+        SQInteger r = SQInteger(scstrtol(s,&end,(int)base));
+        if(s == end) return false;
+        res = r;
+    }
+    return true;
+}
+
+static SQInteger base_dummy(HSQUIRRELVM SQ_UNUSED_ARG(v))
+{
+    return 0;
+}
+
+#ifndef NO_GARBAGE_COLLECTOR
+static SQInteger base_collectgarbage(HSQUIRRELVM v)
+{
+    sq_pushinteger(v, sq_collectgarbage(v));
+    return 1;
+}
+static SQInteger base_resurectureachable(HSQUIRRELVM v)
+{
+    sq_resurrectunreachable(v);
+    return 1;
+}
+#endif
+
+static SQInteger base_getroottable(HSQUIRRELVM v)
+{
+    v->Push(v->_roottable);
+    return 1;
+}
+
+static SQInteger base_getconsttable(HSQUIRRELVM v)
+{
+    v->Push(_ss(v)->_consts);
+    return 1;
+}
+
+
+static SQInteger base_setroottable(HSQUIRRELVM v)
+{
+    SQObjectPtr o = v->_roottable;
+    if(SQ_FAILED(sq_setroottable(v))) return SQ_ERROR;
+    v->Push(o);
+    return 1;
+}
+
+static SQInteger base_setconsttable(HSQUIRRELVM v)
+{
+    SQObjectPtr o = _ss(v)->_consts;
+    if(SQ_FAILED(sq_setconsttable(v))) return SQ_ERROR;
+    v->Push(o);
+    return 1;
+}
+
+static SQInteger base_seterrorhandler(HSQUIRRELVM v)
+{
+    sq_seterrorhandler(v);
+    return 0;
+}
+
+static SQInteger base_setdebughook(HSQUIRRELVM v)
+{
+    sq_setdebughook(v);
+    return 0;
+}
+
+static SQInteger base_enabledebuginfo(HSQUIRRELVM v)
+{
+    SQObjectPtr &o=stack_get(v,2);
+
+    sq_enabledebuginfo(v,SQVM::IsFalse(o)?SQFalse:SQTrue);
+    return 0;
+}
+
+static SQInteger __getcallstackinfos(HSQUIRRELVM v,SQInteger level)
+{
+    SQStackInfos si;
+    SQInteger seq = 0;
+    const SQChar *name = NULL;
+
+    if (SQ_SUCCEEDED(sq_stackinfos(v, level, &si)))
+    {
+        const SQChar *fn = _SC("unknown");
+        const SQChar *src = _SC("unknown");
+        if(si.funcname)fn = si.funcname;
+        if(si.source)src = si.source;
+        sq_newtable(v);
+        sq_pushstring(v, _SC("func"), -1);
+        sq_pushstring(v, fn, -1);
+        sq_newslot(v, -3, SQFalse);
+        sq_pushstring(v, _SC("src"), -1);
+        sq_pushstring(v, src, -1);
+        sq_newslot(v, -3, SQFalse);
+        sq_pushstring(v, _SC("line"), -1);
+        sq_pushinteger(v, si.line);
+        sq_newslot(v, -3, SQFalse);
+        sq_pushstring(v, _SC("locals"), -1);
+        sq_newtable(v);
+        seq=0;
+        while ((name = sq_getlocal(v, level, seq))) {
+            sq_pushstring(v, name, -1);
+            sq_push(v, -2);
+            sq_newslot(v, -4, SQFalse);
+            sq_pop(v, 1);
+            seq++;
+        }
+        sq_newslot(v, -3, SQFalse);
+        return 1;
+    }
+
+    return 0;
+}
+static SQInteger base_getstackinfos(HSQUIRRELVM v)
+{
+    SQInteger level;
+    sq_getinteger(v, -1, &level);
+    return __getcallstackinfos(v,level);
+}
+
+static SQInteger base_assert(HSQUIRRELVM v)
+{
+    if(SQVM::IsFalse(stack_get(v,2))){
+        SQInteger top = sq_gettop(v);
+        if (top>2 && SQ_SUCCEEDED(sq_tostring(v,3))) {
+            const SQChar *str = 0;
+            if (SQ_SUCCEEDED(sq_getstring(v,-1,&str))) {
+                return sq_throwerror(v, str);
+            }
+        }
+        return sq_throwerror(v, _SC("assertion failed"));
+    }
+    return 0;
+}
+
+static SQInteger get_slice_params(HSQUIRRELVM v,SQInteger &sidx,SQInteger &eidx,SQObjectPtr &o)
+{
+    SQInteger top = sq_gettop(v);
+    sidx=0;
+    eidx=0;
+    o=stack_get(v,1);
+    if(top>1){
+        SQObjectPtr &start=stack_get(v,2);
+        if(sq_type(start)!=OT_NULL && sq_isnumeric(start)){
+            sidx=tointeger(start);
+        }
+    }
+    if(top>2){
+        SQObjectPtr &end=stack_get(v,3);
+        if(sq_isnumeric(end)){
+            eidx=tointeger(end);
+        }
+    }
+    else {
+        eidx = sq_getsize(v,1);
+    }
+    return 1;
+}
+
+static SQInteger base_print(HSQUIRRELVM v)
+{
+    const SQChar *str;
+    if(SQ_SUCCEEDED(sq_tostring(v,2)))
+    {
+        if(SQ_SUCCEEDED(sq_getstring(v,-1,&str))) {
+            if(_ss(v)->_printfunc) _ss(v)->_printfunc(v,_SC("%s"),str);
+            return 0;
+        }
+    }
+    return SQ_ERROR;
+}
+
+static SQInteger base_error(HSQUIRRELVM v)
+{
+    const SQChar *str;
+    if(SQ_SUCCEEDED(sq_tostring(v,2)))
+    {
+        if(SQ_SUCCEEDED(sq_getstring(v,-1,&str))) {
+            if(_ss(v)->_errorfunc) _ss(v)->_errorfunc(v,_SC("%s"),str);
+            return 0;
+        }
+    }
+    return SQ_ERROR;
+}
+
+static SQInteger base_compilestring(HSQUIRRELVM v)
+{
+    SQInteger nargs=sq_gettop(v);
+    const SQChar *src=NULL,*name=_SC("unnamedbuffer");
+    SQInteger size;
+    sq_getstring(v,2,&src);
+    size=sq_getsize(v,2);
+    if(nargs>2){
+        sq_getstring(v,3,&name);
+    }
+    if(SQ_SUCCEEDED(sq_compilebuffer(v,src,size,name,SQFalse)))
+        return 1;
+    else
+        return SQ_ERROR;
+}
+
+static SQInteger base_newthread(HSQUIRRELVM v)
+{
+    SQObjectPtr &func = stack_get(v,2);
+    SQInteger stksize = (_closure(func)->_function->_stacksize << 1) +2;
+    HSQUIRRELVM newv = sq_newthread(v, (stksize < MIN_STACK_OVERHEAD + 2)? MIN_STACK_OVERHEAD + 2 : stksize);
+    sq_move(newv,v,-2);
+    return 1;
+}
+
+static SQInteger base_suspend(HSQUIRRELVM v)
+{
+    return sq_suspendvm(v);
+}
+
+static SQInteger base_array(HSQUIRRELVM v)
+{
+    SQArray *a;
+    SQObject &size = stack_get(v,2);
+    if(sq_gettop(v) > 2) {
+        a = SQArray::Create(_ss(v),0);
+        a->Resize(tointeger(size),stack_get(v,3));
+    }
+    else {
+        a = SQArray::Create(_ss(v),tointeger(size));
+    }
+    v->Push(a);
+    return 1;
+}
+
+static SQInteger base_type(HSQUIRRELVM v)
+{
+    SQObjectPtr &o = stack_get(v,2);
+    v->Push(SQString::Create(_ss(v),GetTypeName(o),-1));
+    return 1;
+}
+
+static SQInteger base_callee(HSQUIRRELVM v)
+{
+    if(v->_callsstacksize > 1)
+    {
+        v->Push(v->_callsstack[v->_callsstacksize - 2]._closure);
+        return 1;
+    }
+    return sq_throwerror(v,_SC("no closure in the calls stack"));
+}
+
+static const SQRegFunction base_funcs[]={
+    //generic
+    {_SC("seterrorhandler"),base_seterrorhandler,2, NULL},
+    {_SC("setdebughook"),base_setdebughook,2, NULL},
+    {_SC("enabledebuginfo"),base_enabledebuginfo,2, NULL},
+    {_SC("getstackinfos"),base_getstackinfos,2, _SC(".n")},
+    {_SC("getroottable"),base_getroottable,1, NULL},
+    {_SC("setroottable"),base_setroottable,2, NULL},
+    {_SC("getconsttable"),base_getconsttable,1, NULL},
+    {_SC("setconsttable"),base_setconsttable,2, NULL},
+    {_SC("assert"),base_assert,-2, NULL},
+    {_SC("print"),base_print,2, NULL},
+    {_SC("error"),base_error,2, NULL},
+    {_SC("compilestring"),base_compilestring,-2, _SC(".ss")},
+    {_SC("newthread"),base_newthread,2, _SC(".c")},
+    {_SC("suspend"),base_suspend,-1, NULL},
+    {_SC("array"),base_array,-2, _SC(".n")},
+    {_SC("type"),base_type,2, NULL},
+    {_SC("callee"),base_callee,0,NULL},
+    {_SC("dummy"),base_dummy,0,NULL},
+#ifndef NO_GARBAGE_COLLECTOR
+    {_SC("collectgarbage"),base_collectgarbage,0, NULL},
+    {_SC("resurrectunreachable"),base_resurectureachable,0, NULL},
+#endif
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+void sq_base_register(HSQUIRRELVM v)
+{
+    SQInteger i=0;
+    sq_pushroottable(v);
+    while(base_funcs[i].name!=0) {
+        sq_pushstring(v,base_funcs[i].name,-1);
+        sq_newclosure(v,base_funcs[i].f,0);
+        sq_setnativeclosurename(v,-1,base_funcs[i].name);
+        sq_setparamscheck(v,base_funcs[i].nparamscheck,base_funcs[i].typemask);
+        sq_newslot(v,-3, SQFalse);
+        i++;
+    }
+
+    sq_pushstring(v,_SC("_versionnumber_"),-1);
+    sq_pushinteger(v,SQUIRREL_VERSION_NUMBER);
+    sq_newslot(v,-3, SQFalse);
+    sq_pushstring(v,_SC("_version_"),-1);
+    sq_pushstring(v,SQUIRREL_VERSION,-1);
+    sq_newslot(v,-3, SQFalse);
+    sq_pushstring(v,_SC("_charsize_"),-1);
+    sq_pushinteger(v,sizeof(SQChar));
+    sq_newslot(v,-3, SQFalse);
+    sq_pushstring(v,_SC("_intsize_"),-1);
+    sq_pushinteger(v,sizeof(SQInteger));
+    sq_newslot(v,-3, SQFalse);
+    sq_pushstring(v,_SC("_floatsize_"),-1);
+    sq_pushinteger(v,sizeof(SQFloat));
+    sq_newslot(v,-3, SQFalse);
+    sq_pop(v,1);
+}
+
+static SQInteger default_delegate_len(HSQUIRRELVM v)
+{
+    v->Push(SQInteger(sq_getsize(v,1)));
+    return 1;
+}
+
+static SQInteger default_delegate_tofloat(HSQUIRRELVM v)
+{
+    SQObjectPtr &o=stack_get(v,1);
+    switch(sq_type(o)){
+    case OT_STRING:{
+        SQObjectPtr res;
+        if(str2num(_stringval(o),res,10)){
+            v->Push(SQObjectPtr(tofloat(res)));
+            break;
+        }}
+        return sq_throwerror(v, _SC("cannot convert the string"));
+        break;
+    case OT_INTEGER:case OT_FLOAT:
+        v->Push(SQObjectPtr(tofloat(o)));
+        break;
+    case OT_BOOL:
+        v->Push(SQObjectPtr((SQFloat)(_integer(o)?1:0)));
+        break;
+    default:
+        v->PushNull();
+        break;
+    }
+    return 1;
+}
+
+static SQInteger default_delegate_tointeger(HSQUIRRELVM v)
+{
+    SQObjectPtr &o=stack_get(v,1);
+    SQInteger base = 10;
+    if(sq_gettop(v) > 1) {
+        sq_getinteger(v,2,&base);
+    }
+    switch(sq_type(o)){
+    case OT_STRING:{
+        SQObjectPtr res;
+        if(str2num(_stringval(o),res,base)){
+            v->Push(SQObjectPtr(tointeger(res)));
+            break;
+        }}
+        return sq_throwerror(v, _SC("cannot convert the string"));
+        break;
+    case OT_INTEGER:case OT_FLOAT:
+        v->Push(SQObjectPtr(tointeger(o)));
+        break;
+    case OT_BOOL:
+        v->Push(SQObjectPtr(_integer(o)?(SQInteger)1:(SQInteger)0));
+        break;
+    default:
+        v->PushNull();
+        break;
+    }
+    return 1;
+}
+
+static SQInteger default_delegate_tostring(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_tostring(v,1)))
+        return SQ_ERROR;
+    return 1;
+}
+
+static SQInteger obj_delegate_weakref(HSQUIRRELVM v)
+{
+    sq_weakref(v,1);
+    return 1;
+}
+
+static SQInteger obj_clear(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_clear(v,-1)) ? 1 : SQ_ERROR;
+}
+
+
+static SQInteger number_delegate_tochar(HSQUIRRELVM v)
+{
+    SQObject &o=stack_get(v,1);
+    SQChar c = (SQChar)tointeger(o);
+    v->Push(SQString::Create(_ss(v),(const SQChar *)&c,1));
+    return 1;
+}
+
+
+
+/////////////////////////////////////////////////////////////////
+//TABLE DEFAULT DELEGATE
+
+static SQInteger table_rawdelete(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_rawdeleteslot(v,1,SQTrue)))
+        return SQ_ERROR;
+    return 1;
+}
+
+
+static SQInteger container_rawexists(HSQUIRRELVM v)
+{
+    if(SQ_SUCCEEDED(sq_rawget(v,-2))) {
+        sq_pushbool(v,SQTrue);
+        return 1;
+    }
+    sq_pushbool(v,SQFalse);
+    return 1;
+}
+
+static SQInteger container_rawset(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_rawset(v,-3)) ? 1 : SQ_ERROR;
+}
+
+static SQInteger container_rawsafeget(HSQUIRRELVM v)
+{
+    if (SQ_SUCCEEDED(sq_rawget(v, -2)))
+    {
+        return 1;
+    }
+    sq_pushinteger(v, 0);
+    return 1;
+}
+
+static SQInteger container_rawget(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_rawget(v,-2))?1:SQ_ERROR;
+}
+
+static SQInteger table_setdelegate(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_setdelegate(v,-2)))
+        return SQ_ERROR;
+    sq_push(v,-1); // -1 because sq_setdelegate pops 1
+    return 1;
+}
+
+static SQInteger table_getdelegate(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_getdelegate(v,-1))?1:SQ_ERROR;
+}
+
+static SQInteger table_filter(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    SQTable *tbl = _table(o);
+    SQObjectPtr ret = SQTable::Create(_ss(v),0);
+
+    SQObjectPtr itr, key, val;
+    SQInteger nitr;
+    while((nitr = tbl->Next(false, itr, key, val)) != -1) {
+        itr = (SQInteger)nitr;
+
+        v->Push(o);
+        v->Push(key);
+        v->Push(val);
+        if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) {
+            return SQ_ERROR;
+        }
+        if(!SQVM::IsFalse(v->GetUp(-1))) {
+            _table(ret)->NewSlot(key, val);
+        }
+        v->Pop();
+    }
+
+    v->Push(ret);
+    return 1;
+}
+
+
+const SQRegFunction SQSharedState::_table_default_delegate_funcz[]={
+    {_SC("len"),default_delegate_len,1, _SC("t")},
+    {_SC("rawget"),container_rawget,2, _SC("t")},
+    {_SC("rawsafeget"), container_rawsafeget, 2, _SC("t")},
+    {_SC("rawset"),container_rawset,3, _SC("t")},
+    {_SC("rawdelete"),table_rawdelete,2, _SC("t")},
+    {_SC("rawin"),container_rawexists,2, _SC("t")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("clear"),obj_clear,1, _SC(".")},
+    {_SC("setdelegate"),table_setdelegate,2, _SC(".t|o")},
+    {_SC("getdelegate"),table_getdelegate,1, _SC(".")},
+    {_SC("filter"),table_filter,2, _SC("tc")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//ARRAY DEFAULT DELEGATE///////////////////////////////////////
+
+static SQInteger array_append(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_arrayappend(v,-2)) ? 1 : SQ_ERROR;
+}
+
+static SQInteger array_extend(HSQUIRRELVM v)
+{
+    _array(stack_get(v,1))->Extend(_array(stack_get(v,2)));
+    sq_pop(v,1);
+    return 1;
+}
+
+static SQInteger array_reverse(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_arrayreverse(v,-1)) ? 1 : SQ_ERROR;
+}
+
+static SQInteger array_pop(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_arraypop(v,1,SQTrue))?1:SQ_ERROR;
+}
+
+static SQInteger array_top(HSQUIRRELVM v)
+{
+    SQObject &o=stack_get(v,1);
+    if(_array(o)->Size()>0){
+        v->Push(_array(o)->Top());
+        return 1;
+    }
+    else return sq_throwerror(v,_SC("top() on a empty array"));
+}
+
+static SQInteger array_insert(HSQUIRRELVM v)
+{
+    SQObject &o=stack_get(v,1);
+    SQObject &idx=stack_get(v,2);
+    SQObject &val=stack_get(v,3);
+    if(!_array(o)->Insert(tointeger(idx),val))
+        return sq_throwerror(v,_SC("index out of range"));
+    sq_pop(v,2);
+    return 1;
+}
+
+static SQInteger array_remove(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v, 1);
+    SQObject &idx = stack_get(v, 2);
+    if(!sq_isnumeric(idx)) return sq_throwerror(v, _SC("wrong type"));
+    SQObjectPtr val;
+    if(_array(o)->Get(tointeger(idx), val)) {
+        _array(o)->Remove(tointeger(idx));
+        v->Push(val);
+        return 1;
+    }
+    return sq_throwerror(v, _SC("idx out of range"));
+}
+
+static SQInteger array_resize(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v, 1);
+    SQObject &nsize = stack_get(v, 2);
+    SQObjectPtr fill;
+    if(sq_isnumeric(nsize)) {
+        SQInteger sz = tointeger(nsize);
+        if (sz<0)
+          return sq_throwerror(v, _SC("resizing to negative length"));
+
+        if(sq_gettop(v) > 2)
+            fill = stack_get(v, 3);
+        _array(o)->Resize(sz,fill);
+        sq_settop(v, 1);
+        return 1;
+    }
+    return sq_throwerror(v, _SC("size must be a number"));
+}
+
+static SQInteger __map_array(SQArray *dest,SQArray *src,HSQUIRRELVM v) {
+    SQObjectPtr temp;
+    SQInteger size = src->Size();
+    SQObject &closure = stack_get(v, 2);
+    v->Push(closure);
+
+    SQInteger nArgs;
+    if(sq_type(closure) == OT_CLOSURE) {
+        nArgs = _closure(closure)->_function->_nparameters;
+    }
+    else if (sq_type(closure) == OT_NATIVECLOSURE) {
+        SQInteger nParamsCheck = _nativeclosure(closure)->_nparamscheck;
+        if (nParamsCheck > 0)
+            nArgs = nParamsCheck;
+        else // push all params when there is no check or only minimal count set
+            nArgs = 4;
+    }
+
+    for(SQInteger n = 0; n < size; n++) {
+        src->Get(n,temp);
+        v->Push(src);
+        v->Push(temp);
+        if (nArgs >= 3)
+            v->Push(SQObjectPtr(n));
+        if (nArgs >= 4)
+            v->Push(src);
+        if(SQ_FAILED(sq_call(v,nArgs,SQTrue,SQFalse))) {
+            return SQ_ERROR;
+        }
+        dest->Set(n,v->GetUp(-1));
+        v->Pop();
+    }
+    v->Pop();
+    return 0;
+}
+
+static SQInteger array_map(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    SQInteger size = _array(o)->Size();
+    SQObjectPtr ret = SQArray::Create(_ss(v),size);
+    if(SQ_FAILED(__map_array(_array(ret),_array(o),v)))
+        return SQ_ERROR;
+    v->Push(ret);
+    return 1;
+}
+
+static SQInteger array_apply(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    if(SQ_FAILED(__map_array(_array(o),_array(o),v)))
+        return SQ_ERROR;
+    sq_pop(v,1);
+    return 1;
+}
+
+static SQInteger array_reduce(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    SQArray *a = _array(o);
+    SQInteger size = a->Size();
+    SQObjectPtr res;
+    SQInteger iterStart;
+    if (sq_gettop(v)>2) {
+        res = stack_get(v,3);
+        iterStart = 0;
+    } else if (size==0) {
+        return 0;
+    } else {
+        a->Get(0,res);
+        iterStart = 1;
+    }
+    if (size > iterStart) {
+        SQObjectPtr other;
+        v->Push(stack_get(v,2));
+        for (SQInteger n = iterStart; n < size; n++) {
+            a->Get(n,other);
+            v->Push(o);
+            v->Push(res);
+            v->Push(other);
+            if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) {
+                return SQ_ERROR;
+            }
+            res = v->GetUp(-1);
+            v->Pop();
+        }
+        v->Pop();
+    }
+    v->Push(res);
+    return 1;
+}
+
+static SQInteger array_filter(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    SQArray *a = _array(o);
+    SQObjectPtr ret = SQArray::Create(_ss(v),0);
+    SQInteger size = a->Size();
+    SQObjectPtr val;
+    for(SQInteger n = 0; n < size; n++) {
+        a->Get(n,val);
+        v->Push(o);
+        v->Push(n);
+        v->Push(val);
+        if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) {
+            return SQ_ERROR;
+        }
+        if(!SQVM::IsFalse(v->GetUp(-1))) {
+            _array(ret)->Append(val);
+        }
+        v->Pop();
+    }
+    v->Push(ret);
+    return 1;
+}
+
+static SQInteger array_find(HSQUIRRELVM v)
+{
+    SQObject &o = stack_get(v,1);
+    SQObjectPtr &val = stack_get(v,2);
+    SQArray *a = _array(o);
+    SQInteger size = a->Size();
+    SQObjectPtr temp;
+    for(SQInteger n = 0; n < size; n++) {
+        bool res = false;
+        a->Get(n,temp);
+        if(SQVM::IsEqual(temp,val,res) && res) {
+            v->Push(n);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+
+static bool _sort_compare(HSQUIRRELVM v,SQObjectPtr &a,SQObjectPtr &b,SQInteger func,SQInteger &ret)
+{
+    if(func < 0) {
+        if(!v->ObjCmp(a,b,ret)) return false;
+    }
+    else {
+        SQInteger top = sq_gettop(v);
+        sq_push(v, func);
+        sq_pushroottable(v);
+        v->Push(a);
+        v->Push(b);
+        if(SQ_FAILED(sq_call(v, 3, SQTrue, SQFalse))) {
+            if(!sq_isstring( v->_lasterror))
+                v->Raise_Error(_SC("compare func failed"));
+            return false;
+        }
+        if(SQ_FAILED(sq_getinteger(v, -1, &ret))) {
+            v->Raise_Error(_SC("numeric value expected as return value of the compare function"));
+            return false;
+        }
+        sq_settop(v, top);
+        return true;
+    }
+    return true;
+}
+
+static bool _hsort_sift_down(HSQUIRRELVM v,SQArray *arr, SQInteger root, SQInteger bottom, SQInteger func)
+{
+    SQInteger maxChild;
+    SQInteger done = 0;
+    SQInteger ret;
+    SQInteger root2;
+    while (((root2 = root * 2) <= bottom) && (!done))
+    {
+        if (root2 == bottom) {
+            maxChild = root2;
+        }
+        else {
+            if(!_sort_compare(v,arr->_values[root2],arr->_values[root2 + 1],func,ret))
+                return false;
+            if (ret > 0) {
+                maxChild = root2;
+            }
+            else {
+                maxChild = root2 + 1;
+            }
+        }
+
+        if(!_sort_compare(v,arr->_values[root],arr->_values[maxChild],func,ret))
+            return false;
+        if (ret < 0) {
+            if (root == maxChild) {
+                v->Raise_Error(_SC("inconsistent compare function"));
+                return false; // We'd be swapping ourselve. The compare function is incorrect
+            }
+
+            _Swap(arr->_values[root],arr->_values[maxChild]);
+            root = maxChild;
+        }
+        else {
+            done = 1;
+        }
+    }
+    return true;
+}
+
+static bool _hsort(HSQUIRRELVM v,SQObjectPtr &arr, SQInteger SQ_UNUSED_ARG(l), SQInteger SQ_UNUSED_ARG(r),SQInteger func)
+{
+    SQArray *a = _array(arr);
+    SQInteger i;
+    SQInteger array_size = a->Size();
+    for (i = (array_size / 2); i >= 0; i--) {
+        if(!_hsort_sift_down(v,a, i, array_size - 1,func)) return false;
+    }
+
+    for (i = array_size-1; i >= 1; i--)
+    {
+        _Swap(a->_values[0],a->_values[i]);
+        if(!_hsort_sift_down(v,a, 0, i-1,func)) return false;
+    }
+    return true;
+}
+
+static SQInteger array_sort(HSQUIRRELVM v)
+{
+    SQInteger func = -1;
+    SQObjectPtr &o = stack_get(v,1);
+    if(_array(o)->Size() > 1) {
+        if(sq_gettop(v) == 2) func = 2;
+        if(!_hsort(v, o, 0, _array(o)->Size()-1, func))
+            return SQ_ERROR;
+
+    }
+    sq_settop(v,1);
+    return 1;
+}
+
+static SQInteger array_slice(HSQUIRRELVM v)
+{
+    SQInteger sidx,eidx;
+    SQObjectPtr o;
+    if(get_slice_params(v,sidx,eidx,o)==-1)return -1;
+    SQInteger alen = _array(o)->Size();
+    if(sidx < 0)sidx = alen + sidx;
+    if(eidx < 0)eidx = alen + eidx;
+    if(eidx < sidx)return sq_throwerror(v,_SC("wrong indexes"));
+    if(eidx > alen || sidx < 0)return sq_throwerror(v, _SC("slice out of range"));
+    SQArray *arr=SQArray::Create(_ss(v),eidx-sidx);
+    SQObjectPtr t;
+    SQInteger count=0;
+    for(SQInteger i=sidx;i<eidx;i++){
+        _array(o)->Get(i,t);
+        arr->Set(count++,t);
+    }
+    v->Push(arr);
+    return 1;
+
+}
+
+const SQRegFunction SQSharedState::_array_default_delegate_funcz[]={
+    {_SC("len"),default_delegate_len,1, _SC("a")},
+    {_SC("append"),array_append,2, _SC("a")},
+    {_SC("extend"),array_extend,2, _SC("aa")},
+    {_SC("push"),array_append,2, _SC("a")},
+    {_SC("pop"),array_pop,1, _SC("a")},
+    {_SC("top"),array_top,1, _SC("a")},
+    {_SC("insert"),array_insert,3, _SC("an")},
+    {_SC("remove"),array_remove,2, _SC("an")},
+    {_SC("resize"),array_resize,-2, _SC("an")},
+    {_SC("reverse"),array_reverse,1, _SC("a")},
+    {_SC("sort"),array_sort,-1, _SC("ac")},
+    {_SC("slice"),array_slice,-1, _SC("ann")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("clear"),obj_clear,1, _SC(".")},
+    {_SC("map"),array_map,2, _SC("ac")},
+    {_SC("apply"),array_apply,2, _SC("ac")},
+    {_SC("reduce"),array_reduce,-2, _SC("ac.")},
+    {_SC("filter"),array_filter,2, _SC("ac")},
+    {_SC("find"),array_find,2, _SC("a.")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//STRING DEFAULT DELEGATE//////////////////////////
+static SQInteger string_slice(HSQUIRRELVM v)
+{
+    SQInteger sidx,eidx;
+    SQObjectPtr o;
+    if(SQ_FAILED(get_slice_params(v,sidx,eidx,o)))return -1;
+    SQInteger slen = _string(o)->_len;
+    if(sidx < 0)sidx = slen + sidx;
+    if(eidx < 0)eidx = slen + eidx;
+    if(eidx < sidx) return sq_throwerror(v,_SC("wrong indexes"));
+    if(eidx > slen || sidx < 0) return sq_throwerror(v, _SC("slice out of range"));
+    v->Push(SQString::Create(_ss(v),&_stringval(o)[sidx],eidx-sidx));
+    return 1;
+}
+
+static SQInteger string_find(HSQUIRRELVM v)
+{
+    SQInteger top,start_idx=0;
+    const SQChar *str,*substr,*ret;
+    if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,&str)) && SQ_SUCCEEDED(sq_getstring(v,2,&substr))){
+        if(top>2)sq_getinteger(v,3,&start_idx);
+        if((sq_getsize(v,1)>start_idx) && (start_idx>=0)){
+            ret=scstrstr(&str[start_idx],substr);
+            if(ret){
+                sq_pushinteger(v,(SQInteger)(ret-str));
+                return 1;
+            }
+        }
+        return 0;
+    }
+    return sq_throwerror(v,_SC("invalid param"));
+}
+
+#define STRING_TOFUNCZ(func) static SQInteger string_##func(HSQUIRRELVM v) \
+{\
+    SQInteger sidx,eidx; \
+    SQObjectPtr str; \
+    if(SQ_FAILED(get_slice_params(v,sidx,eidx,str)))return -1; \
+    SQInteger slen = _string(str)->_len; \
+    if(sidx < 0)sidx = slen + sidx; \
+    if(eidx < 0)eidx = slen + eidx; \
+    if(eidx < sidx) return sq_throwerror(v,_SC("wrong indexes")); \
+    if(eidx > slen || sidx < 0) return sq_throwerror(v,_SC("slice out of range")); \
+    SQInteger len=_string(str)->_len; \
+    const SQChar *sthis=_stringval(str); \
+    SQChar *snew=(_ss(v)->GetScratchPad(sq_rsl(len))); \
+    memcpy(snew,sthis,sq_rsl(len));\
+    for(SQInteger i=sidx;i<eidx;i++) snew[i] = func(sthis[i]); \
+    v->Push(SQString::Create(_ss(v),snew,len)); \
+    return 1; \
+}
+
+
+STRING_TOFUNCZ(tolower)
+STRING_TOFUNCZ(toupper)
+
+const SQRegFunction SQSharedState::_string_default_delegate_funcz[]={
+    {_SC("len"),default_delegate_len,1, _SC("s")},
+    {_SC("tointeger"),default_delegate_tointeger,-1, _SC("sn")},
+    {_SC("tofloat"),default_delegate_tofloat,1, _SC("s")},
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("slice"),string_slice,-1, _SC("s n  n")},
+    {_SC("find"),string_find,-2, _SC("s s n")},
+    {_SC("tolower"),string_tolower,-1, _SC("s n n")},
+    {_SC("toupper"),string_toupper,-1, _SC("s n n")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//INTEGER DEFAULT DELEGATE//////////////////////////
+const SQRegFunction SQSharedState::_number_default_delegate_funcz[]={
+    {_SC("tointeger"),default_delegate_tointeger,1, _SC("n|b")},
+    {_SC("tofloat"),default_delegate_tofloat,1, _SC("n|b")},
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("tochar"),number_delegate_tochar,1, _SC("n|b")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//CLOSURE DEFAULT DELEGATE//////////////////////////
+static SQInteger closure_pcall(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_call(v,sq_gettop(v)-1,SQTrue,SQFalse))?1:SQ_ERROR;
+}
+
+static SQInteger closure_call(HSQUIRRELVM v)
+{
+	SQObjectPtr &c = stack_get(v, -1);
+	if (sq_type(c) == OT_CLOSURE && (_closure(c)->_function->_bgenerator == false))
+	{
+		return sq_tailcall(v, sq_gettop(v) - 1);
+	}
+	return SQ_SUCCEEDED(sq_call(v, sq_gettop(v) - 1, SQTrue, SQTrue)) ? 1 : SQ_ERROR;
+}
+
+static SQInteger _closure_acall(HSQUIRRELVM v,SQBool raiseerror)
+{
+    SQArray *aparams=_array(stack_get(v,2));
+    SQInteger nparams=aparams->Size();
+    v->Push(stack_get(v,1));
+    for(SQInteger i=0;i<nparams;i++)v->Push(aparams->_values[i]);
+    return SQ_SUCCEEDED(sq_call(v,nparams,SQTrue,raiseerror))?1:SQ_ERROR;
+}
+
+static SQInteger closure_acall(HSQUIRRELVM v)
+{
+    return _closure_acall(v,SQTrue);
+}
+
+static SQInteger closure_pacall(HSQUIRRELVM v)
+{
+    return _closure_acall(v,SQFalse);
+}
+
+static SQInteger closure_bindenv(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_bindenv(v,1)))
+        return SQ_ERROR;
+    return 1;
+}
+
+static SQInteger closure_getroot(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_getclosureroot(v,-1)))
+        return SQ_ERROR;
+    return 1;
+}
+
+static SQInteger closure_setroot(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_setclosureroot(v,-2)))
+        return SQ_ERROR;
+    return 1;
+}
+
+static SQInteger closure_getinfos(HSQUIRRELVM v) {
+    SQObject o = stack_get(v,1);
+    SQTable *res = SQTable::Create(_ss(v),4);
+    if(sq_type(o) == OT_CLOSURE) {
+        SQFunctionProto *f = _closure(o)->_function;
+        SQInteger nparams = f->_nparameters + (f->_varparams?1:0);
+        SQObjectPtr params = SQArray::Create(_ss(v),nparams);
+    SQObjectPtr defparams = SQArray::Create(_ss(v),f->_ndefaultparams);
+        for(SQInteger n = 0; n<f->_nparameters; n++) {
+            _array(params)->Set((SQInteger)n,f->_parameters[n]);
+        }
+    for(SQInteger j = 0; j<f->_ndefaultparams; j++) {
+            _array(defparams)->Set((SQInteger)j,_closure(o)->_defaultparams[j]);
+        }
+        if(f->_varparams) {
+            _array(params)->Set(nparams-1,SQString::Create(_ss(v),_SC("..."),-1));
+        }
+        res->NewSlot(SQString::Create(_ss(v),_SC("native"),-1),false);
+        res->NewSlot(SQString::Create(_ss(v),_SC("name"),-1),f->_name);
+        res->NewSlot(SQString::Create(_ss(v),_SC("src"),-1),f->_sourcename);
+        res->NewSlot(SQString::Create(_ss(v),_SC("parameters"),-1),params);
+        res->NewSlot(SQString::Create(_ss(v),_SC("varargs"),-1),f->_varparams);
+    res->NewSlot(SQString::Create(_ss(v),_SC("defparams"),-1),defparams);
+    }
+    else { //OT_NATIVECLOSURE
+        SQNativeClosure *nc = _nativeclosure(o);
+        res->NewSlot(SQString::Create(_ss(v),_SC("native"),-1),true);
+        res->NewSlot(SQString::Create(_ss(v),_SC("name"),-1),nc->_name);
+        res->NewSlot(SQString::Create(_ss(v),_SC("paramscheck"),-1),nc->_nparamscheck);
+        SQObjectPtr typecheck;
+        if(nc->_typecheck.size() > 0) {
+            typecheck =
+                SQArray::Create(_ss(v), nc->_typecheck.size());
+            for(SQUnsignedInteger n = 0; n<nc->_typecheck.size(); n++) {
+                    _array(typecheck)->Set((SQInteger)n,nc->_typecheck[n]);
+            }
+        }
+        res->NewSlot(SQString::Create(_ss(v),_SC("typecheck"),-1),typecheck);
+    }
+    v->Push(res);
+    return 1;
+}
+
+
+
+const SQRegFunction SQSharedState::_closure_default_delegate_funcz[]={
+    {_SC("call"),closure_call,-1, _SC("c")},
+    {_SC("pcall"),closure_pcall,-1, _SC("c")},
+    {_SC("acall"),closure_acall,2, _SC("ca")},
+    {_SC("pacall"),closure_pacall,2, _SC("ca")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("bindenv"),closure_bindenv,2, _SC("c x|y|t")},
+    {_SC("getinfos"),closure_getinfos,1, _SC("c")},
+    {_SC("getroot"),closure_getroot,1, _SC("c")},
+    {_SC("setroot"),closure_setroot,2, _SC("ct")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//GENERATOR DEFAULT DELEGATE
+static SQInteger generator_getstatus(HSQUIRRELVM v)
+{
+    SQObject &o=stack_get(v,1);
+    switch(_generator(o)->_state){
+        case SQGenerator::eSuspended:v->Push(SQString::Create(_ss(v),_SC("suspended")));break;
+        case SQGenerator::eRunning:v->Push(SQString::Create(_ss(v),_SC("running")));break;
+        case SQGenerator::eDead:v->Push(SQString::Create(_ss(v),_SC("dead")));break;
+    }
+    return 1;
+}
+
+const SQRegFunction SQSharedState::_generator_default_delegate_funcz[]={
+    {_SC("getstatus"),generator_getstatus,1, _SC("g")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+//THREAD DEFAULT DELEGATE
+static SQInteger thread_call(HSQUIRRELVM v)
+{
+    SQObjectPtr o = stack_get(v,1);
+    if(sq_type(o) == OT_THREAD) {
+        SQInteger nparams = sq_gettop(v);
+        _thread(o)->Push(_thread(o)->_roottable);
+        for(SQInteger i = 2; i<(nparams+1); i++)
+            sq_move(_thread(o),v,i);
+        if(SQ_SUCCEEDED(sq_call(_thread(o),nparams,SQTrue,SQTrue))) {
+            sq_move(v,_thread(o),-1);
+            sq_pop(_thread(o),1);
+            return 1;
+        }
+        v->_lasterror = _thread(o)->_lasterror;
+        return SQ_ERROR;
+    }
+    return sq_throwerror(v,_SC("wrong parameter"));
+}
+
+static SQInteger thread_wakeup(HSQUIRRELVM v)
+{
+    SQObjectPtr o = stack_get(v,1);
+    if(sq_type(o) == OT_THREAD) {
+        SQVM *thread = _thread(o);
+        SQInteger state = sq_getvmstate(thread);
+        if(state != SQ_VMSTATE_SUSPENDED) {
+            switch(state) {
+                case SQ_VMSTATE_IDLE:
+                    return sq_throwerror(v,_SC("cannot wakeup a idle thread"));
+                break;
+                case SQ_VMSTATE_RUNNING:
+                    return sq_throwerror(v,_SC("cannot wakeup a running thread"));
+                break;
+            }
+        }
+
+        SQInteger wakeupret = sq_gettop(v)>1?SQTrue:SQFalse;
+        if(wakeupret) {
+            sq_move(thread,v,2);
+        }
+        if(SQ_SUCCEEDED(sq_wakeupvm(thread,wakeupret,SQTrue,SQTrue,SQFalse))) {
+            sq_move(v,thread,-1);
+            sq_pop(thread,1); //pop retval
+            if(sq_getvmstate(thread) == SQ_VMSTATE_IDLE) {
+                sq_settop(thread,1); //pop roottable
+            }
+            return 1;
+        }
+        sq_settop(thread,1);
+        v->_lasterror = thread->_lasterror;
+        return SQ_ERROR;
+    }
+    return sq_throwerror(v,_SC("wrong parameter"));
+}
+
+static SQInteger thread_wakeupthrow(HSQUIRRELVM v)
+{
+    SQObjectPtr o = stack_get(v,1);
+    if(sq_type(o) == OT_THREAD) {
+        SQVM *thread = _thread(o);
+        SQInteger state = sq_getvmstate(thread);
+        if(state != SQ_VMSTATE_SUSPENDED) {
+            switch(state) {
+                case SQ_VMSTATE_IDLE:
+                    return sq_throwerror(v,_SC("cannot wakeup a idle thread"));
+                break;
+                case SQ_VMSTATE_RUNNING:
+                    return sq_throwerror(v,_SC("cannot wakeup a running thread"));
+                break;
+            }
+        }
+
+        sq_move(thread,v,2);
+        sq_throwobject(thread);
+        SQBool rethrow_error = SQTrue;
+        if(sq_gettop(v) > 2) {
+            sq_getbool(v,3,&rethrow_error);
+        }
+        if(SQ_SUCCEEDED(sq_wakeupvm(thread,SQFalse,SQTrue,SQTrue,SQTrue))) {
+            sq_move(v,thread,-1);
+            sq_pop(thread,1); //pop retval
+            if(sq_getvmstate(thread) == SQ_VMSTATE_IDLE) {
+                sq_settop(thread,1); //pop roottable
+            }
+            return 1;
+        }
+        sq_settop(thread,1);
+        if(rethrow_error) {
+            v->_lasterror = thread->_lasterror;
+            return SQ_ERROR;
+        }
+        return SQ_OK;
+    }
+    return sq_throwerror(v,_SC("wrong parameter"));
+}
+
+static SQInteger thread_getstatus(HSQUIRRELVM v)
+{
+    SQObjectPtr &o = stack_get(v,1);
+    switch(sq_getvmstate(_thread(o))) {
+        case SQ_VMSTATE_IDLE:
+            sq_pushstring(v,_SC("idle"),-1);
+        break;
+        case SQ_VMSTATE_RUNNING:
+            sq_pushstring(v,_SC("running"),-1);
+        break;
+        case SQ_VMSTATE_SUSPENDED:
+            sq_pushstring(v,_SC("suspended"),-1);
+        break;
+        default:
+            return sq_throwerror(v,_SC("internal VM error"));
+    }
+    return 1;
+}
+
+static SQInteger thread_getstackinfos(HSQUIRRELVM v)
+{
+    SQObjectPtr o = stack_get(v,1);
+    if(sq_type(o) == OT_THREAD) {
+        SQVM *thread = _thread(o);
+        SQInteger threadtop = sq_gettop(thread);
+        SQInteger level;
+        sq_getinteger(v,-1,&level);
+        SQRESULT res = __getcallstackinfos(thread,level);
+        if(SQ_FAILED(res))
+        {
+            sq_settop(thread,threadtop);
+            if(sq_type(thread->_lasterror) == OT_STRING) {
+                sq_throwerror(v,_stringval(thread->_lasterror));
+            }
+            else {
+                sq_throwerror(v,_SC("unknown error"));
+            }
+        }
+        if(res > 0) {
+            //some result
+            sq_move(v,thread,-1);
+            sq_settop(thread,threadtop);
+            return 1;
+        }
+        //no result
+        sq_settop(thread,threadtop);
+        return 0;
+
+    }
+    return sq_throwerror(v,_SC("wrong parameter"));
+}
+
+const SQRegFunction SQSharedState::_thread_default_delegate_funcz[] = {
+    {_SC("call"), thread_call, -1, _SC("v")},
+    {_SC("wakeup"), thread_wakeup, -1, _SC("v")},
+    {_SC("wakeupthrow"), thread_wakeupthrow, -2, _SC("v.b")},
+    {_SC("getstatus"), thread_getstatus, 1, _SC("v")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("getstackinfos"),thread_getstackinfos,2, _SC("vn")},
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+static SQInteger class_getattributes(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_getattributes(v,-2))?1:SQ_ERROR;
+}
+
+static SQInteger class_setattributes(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_setattributes(v,-3))?1:SQ_ERROR;
+}
+
+static SQInteger class_instance(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_createinstance(v,-1))?1:SQ_ERROR;
+}
+
+static SQInteger class_getbase(HSQUIRRELVM v)
+{
+    return SQ_SUCCEEDED(sq_getbase(v,-1))?1:SQ_ERROR;
+}
+
+static SQInteger class_newmember(HSQUIRRELVM v)
+{
+    SQInteger top = sq_gettop(v);
+    SQBool bstatic = SQFalse;
+    if(top == 5)
+    {
+        sq_tobool(v,-1,&bstatic);
+        sq_pop(v,1);
+    }
+
+    if(top < 4) {
+        sq_pushnull(v);
+    }
+    return SQ_SUCCEEDED(sq_newmember(v,-4,bstatic))?1:SQ_ERROR;
+}
+
+static SQInteger class_rawnewmember(HSQUIRRELVM v)
+{
+    SQInteger top = sq_gettop(v);
+    SQBool bstatic = SQFalse;
+    if(top == 5)
+    {
+        sq_tobool(v,-1,&bstatic);
+        sq_pop(v,1);
+    }
+
+    if(top < 4) {
+        sq_pushnull(v);
+    }
+    return SQ_SUCCEEDED(sq_rawnewmember(v,-4,bstatic))?1:SQ_ERROR;
+}
+
+const SQRegFunction SQSharedState::_class_default_delegate_funcz[] = {
+    {_SC("getattributes"), class_getattributes, 2, _SC("y.")},
+    {_SC("setattributes"), class_setattributes, 3, _SC("y..")},
+    {_SC("rawget"),container_rawget,2, _SC("y")},
+    {_SC("rawset"),container_rawset,3, _SC("y")},
+    {_SC("rawin"),container_rawexists,2, _SC("y")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {_SC("instance"),class_instance,1, _SC("y")},
+    {_SC("getbase"),class_getbase,1, _SC("y")},
+    {_SC("newmember"),class_newmember,-3, _SC("y")},
+    {_SC("rawnewmember"),class_rawnewmember,-3, _SC("y")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+
+static SQInteger instance_getclass(HSQUIRRELVM v)
+{
+    if(SQ_SUCCEEDED(sq_getclass(v,1)))
+        return 1;
+    return SQ_ERROR;
+}
+
+const SQRegFunction SQSharedState::_instance_default_delegate_funcz[] = {
+    {_SC("getclass"), instance_getclass, 1, _SC("x")},
+    {_SC("rawget"),container_rawget,2, _SC("x")},
+    {_SC("rawset"),container_rawset,3, _SC("x")},
+    {_SC("rawin"),container_rawexists,2, _SC("x")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
+
+static SQInteger weakref_ref(HSQUIRRELVM v)
+{
+    if(SQ_FAILED(sq_getweakrefval(v,1)))
+        return SQ_ERROR;
+    return 1;
+}
+
+const SQRegFunction SQSharedState::_weakref_default_delegate_funcz[] = {
+    {_SC("ref"),weakref_ref,1, _SC("r")},
+    {_SC("weakref"),obj_delegate_weakref,1, NULL },
+    {_SC("tostring"),default_delegate_tostring,1, _SC(".")},
+    {NULL,(SQFUNCTION)0,0,NULL}
+};
diff --git a/engines/twp/squirrel/sqclass.cpp b/engines/twp/squirrel/sqclass.cpp
new file mode 100755
index 00000000000..fc619616c05
--- /dev/null
+++ b/engines/twp/squirrel/sqclass.cpp
@@ -0,0 +1,210 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include "sqvm.h"
+#include "sqtable.h"
+#include "sqclass.h"
+#include "sqfuncproto.h"
+#include "sqclosure.h"
+
+
+
+SQClass::SQClass(SQSharedState *ss,SQClass *base)
+{
+    _base = base;
+    _typetag = 0;
+    _hook = NULL;
+    _udsize = 0;
+    _locked = false;
+    _constructoridx = -1;
+    if(_base) {
+        _constructoridx = _base->_constructoridx;
+        _udsize = _base->_udsize;
+        _defaultvalues.copy(base->_defaultvalues);
+        _methods.copy(base->_methods);
+        _COPY_VECTOR(_metamethods,base->_metamethods,MT_LAST);
+        __ObjAddRef(_base);
+    }
+    _members = base?base->_members->Clone() : SQTable::Create(ss,0);
+    __ObjAddRef(_members);
+
+    INIT_CHAIN();
+    ADD_TO_CHAIN(&_sharedstate->_gc_chain, this);
+}
+
+void SQClass::Finalize() {
+    _attributes.Null();
+    _NULL_SQOBJECT_VECTOR(_defaultvalues,_defaultvalues.size());
+    _methods.resize(0);
+    _NULL_SQOBJECT_VECTOR(_metamethods,MT_LAST);
+    __ObjRelease(_members);
+    if(_base) {
+        __ObjRelease(_base);
+    }
+}
+
+SQClass::~SQClass()
+{
+    REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this);
+    Finalize();
+}
+
+bool SQClass::NewSlot(SQSharedState *ss,const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic)
+{
+    SQObjectPtr temp;
+    bool belongs_to_static_table = sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE || bstatic;
+    if(_locked && !belongs_to_static_table)
+        return false; //the class already has an instance so cannot be modified
+    if(_members->Get(key,temp) && _isfield(temp)) //overrides the default value
+    {
+        _defaultvalues[_member_idx(temp)].val = val;
+        return true;
+    }
+    if(belongs_to_static_table) {
+        SQInteger mmidx;
+        if((sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE) &&
+            (mmidx = ss->GetMetaMethodIdxByName(key)) != -1) {
+            _metamethods[mmidx] = val;
+        }
+        else {
+            SQObjectPtr theval = val;
+            if(_base && sq_type(val) == OT_CLOSURE) {
+                theval = _closure(val)->Clone();
+                _closure(theval)->_base = _base;
+                __ObjAddRef(_base); //ref for the closure
+            }
+            if(sq_type(temp) == OT_NULL) {
+                bool isconstructor;
+                SQVM::IsEqual(ss->_constructoridx, key, isconstructor);
+                if(isconstructor) {
+                    _constructoridx = (SQInteger)_methods.size();
+                }
+                SQClassMember m;
+                m.val = theval;
+                _members->NewSlot(key,SQObjectPtr(_make_method_idx(_methods.size())));
+                _methods.push_back(m);
+            }
+            else {
+                _methods[_member_idx(temp)].val = theval;
+            }
+        }
+        return true;
+    }
+    SQClassMember m;
+    m.val = val;
+    _members->NewSlot(key,SQObjectPtr(_make_field_idx(_defaultvalues.size())));
+    _defaultvalues.push_back(m);
+    return true;
+}
+
+SQInstance *SQClass::CreateInstance()
+{
+    if(!_locked) Lock();
+    return SQInstance::Create(_opt_ss(this),this);
+}
+
+SQInteger SQClass::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval)
+{
+    SQObjectPtr oval;
+    SQInteger idx = _members->Next(false,refpos,outkey,oval);
+    if(idx != -1) {
+        if(_ismethod(oval)) {
+            outval = _methods[_member_idx(oval)].val;
+        }
+        else {
+            SQObjectPtr &o = _defaultvalues[_member_idx(oval)].val;
+            outval = _realval(o);
+        }
+    }
+    return idx;
+}
+
+bool SQClass::SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val)
+{
+    SQObjectPtr idx;
+    if(_members->Get(key,idx)) {
+        if(_isfield(idx))
+            _defaultvalues[_member_idx(idx)].attrs = val;
+        else
+            _methods[_member_idx(idx)].attrs = val;
+        return true;
+    }
+    return false;
+}
+
+bool SQClass::GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval)
+{
+    SQObjectPtr idx;
+    if(_members->Get(key,idx)) {
+        outval = (_isfield(idx)?_defaultvalues[_member_idx(idx)].attrs:_methods[_member_idx(idx)].attrs);
+        return true;
+    }
+    return false;
+}
+
+///////////////////////////////////////////////////////////////////////
+void SQInstance::Init(SQSharedState *ss)
+{
+    _userpointer = NULL;
+    _hook = NULL;
+    __ObjAddRef(_class);
+    _delegate = _class->_members;
+    INIT_CHAIN();
+    ADD_TO_CHAIN(&_sharedstate->_gc_chain, this);
+}
+
+SQInstance::SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize)
+{
+    _memsize = memsize;
+    _class = c;
+    SQUnsignedInteger nvalues = _class->_defaultvalues.size();
+    for(SQUnsignedInteger n = 0; n < nvalues; n++) {
+        new (&_values[n]) SQObjectPtr(_class->_defaultvalues[n].val);
+    }
+    Init(ss);
+}
+
+SQInstance::SQInstance(SQSharedState *ss, SQInstance *i, SQInteger memsize)
+{
+    _memsize = memsize;
+    _class = i->_class;
+    SQUnsignedInteger nvalues = _class->_defaultvalues.size();
+    for(SQUnsignedInteger n = 0; n < nvalues; n++) {
+        new (&_values[n]) SQObjectPtr(i->_values[n]);
+    }
+    Init(ss);
+}
+
+void SQInstance::Finalize()
+{
+    SQUnsignedInteger nvalues = _class->_defaultvalues.size();
+    __ObjRelease(_class);
+    _NULL_SQOBJECT_VECTOR(_values,nvalues);
+}
+
+SQInstance::~SQInstance()
+{
+    REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this);
+    if(_class){ Finalize(); } //if _class is null it was already finalized by the GC
+}
+
+bool SQInstance::GetMetaMethod(SQVM* SQ_UNUSED_ARG(v),SQMetaMethod mm,SQObjectPtr &res)
+{
+    if(sq_type(_class->_metamethods[mm]) != OT_NULL) {
+        res = _class->_metamethods[mm];
+        return true;
+    }
+    return false;
+}
+
+bool SQInstance::InstanceOf(SQClass *trg)
+{
+    SQClass *parent = _class;
+    while(parent != NULL) {
+        if(parent == trg)
+            return true;
+        parent = parent->_base;
+    }
+    return false;
+}
diff --git a/engines/twp/squirrel/sqclass.h b/engines/twp/squirrel/sqclass.h
new file mode 100755
index 00000000000..7d4021721bf
--- /dev/null
+++ b/engines/twp/squirrel/sqclass.h
@@ -0,0 +1,162 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQCLASS_H_
+#define _SQCLASS_H_
+
+struct SQInstance;
+
+struct SQClassMember {
+    SQObjectPtr val;
+    SQObjectPtr attrs;
+    void Null() {
+        val.Null();
+        attrs.Null();
+    }
+};
+
+typedef sqvector<SQClassMember> SQClassMemberVec;
+
+#define MEMBER_TYPE_METHOD 0x01000000
+#define MEMBER_TYPE_FIELD 0x02000000
+
+#define _ismethod(o) (_integer(o)&MEMBER_TYPE_METHOD)
+#define _isfield(o) (_integer(o)&MEMBER_TYPE_FIELD)
+#define _make_method_idx(i) ((SQInteger)(MEMBER_TYPE_METHOD|i))
+#define _make_field_idx(i) ((SQInteger)(MEMBER_TYPE_FIELD|i))
+#define _member_type(o) (_integer(o)&0xFF000000)
+#define _member_idx(o) (_integer(o)&0x00FFFFFF)
+
+struct SQClass : public CHAINABLE_OBJ
+{
+    SQClass(SQSharedState *ss,SQClass *base);
+public:
+    static SQClass* Create(SQSharedState *ss,SQClass *base) {
+        SQClass *newclass = (SQClass *)SQ_MALLOC(sizeof(SQClass));
+        new (newclass) SQClass(ss, base);
+        return newclass;
+    }
+    ~SQClass();
+    bool NewSlot(SQSharedState *ss, const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic);
+    bool Get(const SQObjectPtr &key,SQObjectPtr &val) {
+        if(_members->Get(key,val)) {
+            if(_isfield(val)) {
+                SQObjectPtr &o = _defaultvalues[_member_idx(val)].val;
+                val = _realval(o);
+            }
+            else {
+                val = _methods[_member_idx(val)].val;
+            }
+            return true;
+        }
+        return false;
+    }
+    bool GetConstructor(SQObjectPtr &ctor)
+    {
+        if(_constructoridx != -1) {
+            ctor = _methods[_constructoridx].val;
+            return true;
+        }
+        return false;
+    }
+    bool SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val);
+    bool GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval);
+    void Lock() { _locked = true; if(_base) _base->Lock(); }
+    void Release() {
+        if (_hook) { _hook(_typetag,0);}
+        sq_delete(this, SQClass);
+    }
+    void Finalize();
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable ** );
+    SQObjectType GetType() {return OT_CLASS;}
+#endif
+    SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval);
+    SQInstance *CreateInstance();
+    SQTable *_members;
+    SQClass *_base;
+    SQClassMemberVec _defaultvalues;
+    SQClassMemberVec _methods;
+    SQObjectPtr _metamethods[MT_LAST];
+    SQObjectPtr _attributes;
+    SQUserPointer _typetag;
+    SQRELEASEHOOK _hook;
+    bool _locked;
+    SQInteger _constructoridx;
+    SQInteger _udsize;
+};
+
+#define calcinstancesize(_theclass_) \
+    (_theclass_->_udsize + sq_aligning(sizeof(SQInstance) +  (sizeof(SQObjectPtr)*(_theclass_->_defaultvalues.size()>0?_theclass_->_defaultvalues.size()-1:0))))
+
+struct SQInstance : public SQDelegable
+{
+    void Init(SQSharedState *ss);
+    SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize);
+    SQInstance(SQSharedState *ss, SQInstance *c, SQInteger memsize);
+public:
+    static SQInstance* Create(SQSharedState *ss,SQClass *theclass) {
+
+        SQInteger size = calcinstancesize(theclass);
+        SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
+        new (newinst) SQInstance(ss, theclass,size);
+        if(theclass->_udsize) {
+            newinst->_userpointer = ((unsigned char *)newinst) + (size - theclass->_udsize);
+        }
+        return newinst;
+    }
+    SQInstance *Clone(SQSharedState *ss)
+    {
+        SQInteger size = calcinstancesize(_class);
+        SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
+        new (newinst) SQInstance(ss, this,size);
+        if(_class->_udsize) {
+            newinst->_userpointer = ((unsigned char *)newinst) + (size - _class->_udsize);
+        }
+        return newinst;
+    }
+    ~SQInstance();
+    bool Get(const SQObjectPtr &key,SQObjectPtr &val)  {
+        if(_class->_members->Get(key,val)) {
+            if(_isfield(val)) {
+                SQObjectPtr &o = _values[_member_idx(val)];
+                val = _realval(o);
+            }
+            else {
+                val = _class->_methods[_member_idx(val)].val;
+            }
+            return true;
+        }
+        return false;
+    }
+    bool Set(const SQObjectPtr &key,const SQObjectPtr &val) {
+        SQObjectPtr idx;
+        if(_class->_members->Get(key,idx) && _isfield(idx)) {
+            _values[_member_idx(idx)] = val;
+            return true;
+        }
+        return false;
+    }
+    void Release() {
+        _uiRef++;
+        if (_hook) { _hook(_userpointer,0);}
+        _uiRef--;
+        if(_uiRef > 0) return;
+        SQInteger size = _memsize;
+        this->~SQInstance();
+        SQ_FREE(this, size);
+    }
+    void Finalize();
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable ** );
+    SQObjectType GetType() {return OT_INSTANCE;}
+#endif
+    bool InstanceOf(SQClass *trg);
+    bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res);
+
+    SQClass *_class;
+    SQUserPointer _userpointer;
+    SQRELEASEHOOK _hook;
+    SQInteger _memsize;
+    SQObjectPtr _values[1];
+};
+
+#endif //_SQCLASS_H_
diff --git a/engines/twp/squirrel/sqclosure.h b/engines/twp/squirrel/sqclosure.h
new file mode 100755
index 00000000000..66495b94101
--- /dev/null
+++ b/engines/twp/squirrel/sqclosure.h
@@ -0,0 +1,201 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQCLOSURE_H_
+#define _SQCLOSURE_H_
+
+
+#define _CALC_CLOSURE_SIZE(func) (sizeof(SQClosure) + (func->_noutervalues*sizeof(SQObjectPtr)) + (func->_ndefaultparams*sizeof(SQObjectPtr)))
+
+struct SQFunctionProto;
+struct SQClass;
+struct SQClosure : public CHAINABLE_OBJ
+{
+private:
+    SQClosure(SQSharedState *ss,SQFunctionProto *func){_function = func; __ObjAddRef(_function); _base = NULL; INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL; _root=NULL;}
+public:
+    static SQClosure *Create(SQSharedState *ss,SQFunctionProto *func,SQWeakRef *root){
+        SQInteger size = _CALC_CLOSURE_SIZE(func);
+        SQClosure *nc=(SQClosure*)SQ_MALLOC(size);
+        new (nc) SQClosure(ss,func);
+        nc->_outervalues = (SQObjectPtr *)(nc + 1);
+        nc->_defaultparams = &nc->_outervalues[func->_noutervalues];
+        nc->_root = root;
+         __ObjAddRef(nc->_root);
+        _CONSTRUCT_VECTOR(SQObjectPtr,func->_noutervalues,nc->_outervalues);
+        _CONSTRUCT_VECTOR(SQObjectPtr,func->_ndefaultparams,nc->_defaultparams);
+        return nc;
+    }
+    void Release(){
+        SQFunctionProto *f = _function;
+        SQInteger size = _CALC_CLOSURE_SIZE(f);
+        _DESTRUCT_VECTOR(SQObjectPtr,f->_noutervalues,_outervalues);
+        _DESTRUCT_VECTOR(SQObjectPtr,f->_ndefaultparams,_defaultparams);
+        __ObjRelease(_function);
+        this->~SQClosure();
+        sq_vm_free(this,size);
+    }
+    void SetRoot(SQWeakRef *r)
+    {
+        __ObjRelease(_root);
+        _root = r;
+        __ObjAddRef(_root);
+    }
+    SQClosure *Clone()
+    {
+        SQFunctionProto *f = _function;
+        SQClosure * ret = SQClosure::Create(_opt_ss(this),f,_root);
+        ret->_env = _env;
+        if(ret->_env) __ObjAddRef(ret->_env);
+        _COPY_VECTOR(ret->_outervalues,_outervalues,f->_noutervalues);
+        _COPY_VECTOR(ret->_defaultparams,_defaultparams,f->_ndefaultparams);
+        return ret;
+    }
+    ~SQClosure();
+
+    bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write);
+    static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret);
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    void Finalize(){
+        SQFunctionProto *f = _function;
+        _NULL_SQOBJECT_VECTOR(_outervalues,f->_noutervalues);
+        _NULL_SQOBJECT_VECTOR(_defaultparams,f->_ndefaultparams);
+    }
+    SQObjectType GetType() {return OT_CLOSURE;}
+#endif
+    SQWeakRef *_env;
+    SQWeakRef *_root;
+    SQClass *_base;
+    SQFunctionProto *_function;
+    SQObjectPtr *_outervalues;
+    SQObjectPtr *_defaultparams;
+};
+
+//////////////////////////////////////////////
+struct SQOuter : public CHAINABLE_OBJ
+{
+
+private:
+    SQOuter(SQSharedState *ss, SQObjectPtr *outer){_valptr = outer; _next = NULL; INIT_CHAIN(); ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); }
+
+public:
+    static SQOuter *Create(SQSharedState *ss, SQObjectPtr *outer)
+    {
+        SQOuter *nc  = (SQOuter*)SQ_MALLOC(sizeof(SQOuter));
+        new (nc) SQOuter(ss, outer);
+        return nc;
+    }
+    ~SQOuter() { REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); }
+
+    void Release()
+    {
+        this->~SQOuter();
+        sq_vm_free(this,sizeof(SQOuter));
+    }
+
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    void Finalize() { _value.Null(); }
+    SQObjectType GetType() {return OT_OUTER;}
+#endif
+
+    SQObjectPtr *_valptr;  /* pointer to value on stack, or _value below */
+    SQInteger    _idx;     /* idx in stack array, for relocation */
+    SQObjectPtr  _value;   /* value of outer after stack frame is closed */
+    SQOuter     *_next;    /* pointer to next outer when frame is open   */
+};
+
+//////////////////////////////////////////////
+struct SQGenerator : public CHAINABLE_OBJ
+{
+    enum SQGeneratorState{eRunning,eSuspended,eDead};
+private:
+    SQGenerator(SQSharedState *ss,SQClosure *closure){_closure=closure;_state=eRunning;_ci._generator=NULL;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);}
+public:
+    static SQGenerator *Create(SQSharedState *ss,SQClosure *closure){
+        SQGenerator *nc=(SQGenerator*)SQ_MALLOC(sizeof(SQGenerator));
+        new (nc) SQGenerator(ss,closure);
+        return nc;
+    }
+    ~SQGenerator()
+    {
+        REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
+    }
+    void Kill(){
+        _state=eDead;
+        _stack.resize(0);
+        _closure.Null();}
+    void Release(){
+        sq_delete(this,SQGenerator);
+    }
+
+    bool Yield(SQVM *v,SQInteger target);
+    bool Resume(SQVM *v,SQObjectPtr &dest);
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    void Finalize(){_stack.resize(0);_closure.Null();}
+    SQObjectType GetType() {return OT_GENERATOR;}
+#endif
+    SQObjectPtr _closure;
+    SQObjectPtrVec _stack;
+    SQVM::CallInfo _ci;
+    ExceptionsTraps _etraps;
+    SQGeneratorState _state;
+};
+
+#define _CALC_NATVIVECLOSURE_SIZE(noutervalues) (sizeof(SQNativeClosure) + (noutervalues*sizeof(SQObjectPtr)))
+
+struct SQNativeClosure : public CHAINABLE_OBJ
+{
+private:
+    SQNativeClosure(SQSharedState *ss,SQFUNCTION func){_function=func;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL;}
+public:
+    static SQNativeClosure *Create(SQSharedState *ss,SQFUNCTION func,SQInteger nouters)
+    {
+        SQInteger size = _CALC_NATVIVECLOSURE_SIZE(nouters);
+        SQNativeClosure *nc=(SQNativeClosure*)SQ_MALLOC(size);
+        new (nc) SQNativeClosure(ss,func);
+        nc->_outervalues = (SQObjectPtr *)(nc + 1);
+        nc->_noutervalues = nouters;
+        _CONSTRUCT_VECTOR(SQObjectPtr,nc->_noutervalues,nc->_outervalues);
+        return nc;
+    }
+    SQNativeClosure *Clone()
+    {
+        SQNativeClosure * ret = SQNativeClosure::Create(_opt_ss(this),_function,_noutervalues);
+        ret->_env = _env;
+        if(ret->_env) __ObjAddRef(ret->_env);
+        ret->_name = _name;
+        _COPY_VECTOR(ret->_outervalues,_outervalues,_noutervalues);
+        ret->_typecheck.copy(_typecheck);
+        ret->_nparamscheck = _nparamscheck;
+        return ret;
+    }
+    ~SQNativeClosure()
+    {
+        __ObjRelease(_env);
+        REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
+    }
+    void Release(){
+        SQInteger size = _CALC_NATVIVECLOSURE_SIZE(_noutervalues);
+        _DESTRUCT_VECTOR(SQObjectPtr,_noutervalues,_outervalues);
+        this->~SQNativeClosure();
+        sq_free(this,size);
+    }
+
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    void Finalize() { _NULL_SQOBJECT_VECTOR(_outervalues,_noutervalues); }
+    SQObjectType GetType() {return OT_NATIVECLOSURE;}
+#endif
+    SQInteger _nparamscheck;
+    SQIntVec _typecheck;
+    SQObjectPtr *_outervalues;
+    SQUnsignedInteger _noutervalues;
+    SQWeakRef *_env;
+    SQFUNCTION _function;
+    SQObjectPtr _name;
+};
+
+
+
+#endif //_SQCLOSURE_H_
diff --git a/engines/twp/squirrel/sqcompiler.cpp b/engines/twp/squirrel/sqcompiler.cpp
new file mode 100755
index 00000000000..43778fd71ab
--- /dev/null
+++ b/engines/twp/squirrel/sqcompiler.cpp
@@ -0,0 +1,1586 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#ifndef NO_COMPILER
+#include <stdarg.h>
+#include <setjmp.h>
+#include "sqopcodes.h"
+#include "sqstring.h"
+#include "sqfuncproto.h"
+#include "sqcompiler.h"
+#include "sqfuncstate.h"
+#include "sqlexer.h"
+#include "sqvm.h"
+#include "sqtable.h"
+
+#define EXPR   1
+#define OBJECT 2
+#define BASE   3
+#define LOCAL  4
+#define OUTER  5
+
+struct SQExpState {
+  SQInteger  etype;       /* expr. type; one of EXPR, OBJECT, BASE, OUTER or LOCAL */
+  SQInteger  epos;        /* expr. location on stack; -1 for OBJECT and BASE */
+  bool       donot_get;   /* signal not to deref the next value */
+};
+
+#define MAX_COMPILER_ERROR_LEN 256
+
+struct SQScope {
+    SQInteger outers;
+    SQInteger stacksize;
+};
+
+#define BEGIN_SCOPE() SQScope __oldscope__ = _scope; \
+                     _scope.outers = _fs->_outers; \
+                     _scope.stacksize = _fs->GetStackSize();
+
+#define RESOLVE_OUTERS() if(_fs->GetStackSize() != _scope.stacksize) { \
+                            if(_fs->CountOuters(_scope.stacksize)) { \
+                                _fs->AddInstruction(_OP_CLOSE,0,_scope.stacksize); \
+                            } \
+                        }
+
+#define END_SCOPE_NO_CLOSE() {  if(_fs->GetStackSize() != _scope.stacksize) { \
+                            _fs->SetStackSize(_scope.stacksize); \
+                        } \
+                        _scope = __oldscope__; \
+                    }
+
+#define END_SCOPE() {   SQInteger oldouters = _fs->_outers;\
+                        if(_fs->GetStackSize() != _scope.stacksize) { \
+                            _fs->SetStackSize(_scope.stacksize); \
+                            if(oldouters != _fs->_outers) { \
+                                _fs->AddInstruction(_OP_CLOSE,0,_scope.stacksize); \
+                            } \
+                        } \
+                        _scope = __oldscope__; \
+                    }
+
+#define BEGIN_BREAKBLE_BLOCK()  SQInteger __nbreaks__=_fs->_unresolvedbreaks.size(); \
+                            SQInteger __ncontinues__=_fs->_unresolvedcontinues.size(); \
+                            _fs->_breaktargets.push_back(0);_fs->_continuetargets.push_back(0);
+
+#define END_BREAKBLE_BLOCK(continue_target) {__nbreaks__=_fs->_unresolvedbreaks.size()-__nbreaks__; \
+                    __ncontinues__=_fs->_unresolvedcontinues.size()-__ncontinues__; \
+                    if(__ncontinues__>0)ResolveContinues(_fs,__ncontinues__,continue_target); \
+                    if(__nbreaks__>0)ResolveBreaks(_fs,__nbreaks__); \
+                    _fs->_breaktargets.pop_back();_fs->_continuetargets.pop_back();}
+
+class SQCompiler
+{
+public:
+    SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, const SQChar* sourcename, bool raiseerror, bool lineinfo)
+    {
+        _vm=v;
+        _lex.Init(_ss(v), rg, up,ThrowError,this);
+        _sourcename = SQString::Create(_ss(v), sourcename);
+        _lineinfo = lineinfo;_raiseerror = raiseerror;
+        _scope.outers = 0;
+        _scope.stacksize = 0;
+        _compilererror[0] = _SC('\0');
+    }
+    static void ThrowError(void *ud, const SQChar *s) {
+        SQCompiler *c = (SQCompiler *)ud;
+        c->Error(s);
+    }
+    void Error(const SQChar *s, ...)
+    {
+        va_list vl;
+        va_start(vl, s);
+        scvsprintf(_compilererror, MAX_COMPILER_ERROR_LEN, s, vl);
+        va_end(vl);
+        longjmp(_errorjmp,1);
+    }
+    void Lex(){ _token = _lex.Lex();}
+    SQObject Expect(SQInteger tok)
+    {
+
+        if(_token != tok) {
+            if(_token == TK_CONSTRUCTOR && tok == TK_IDENTIFIER) {
+                //do nothing
+            }
+            else {
+                const SQChar *etypename;
+                if(tok > 255) {
+                    switch(tok)
+                    {
+                    case TK_IDENTIFIER:
+                        etypename = _SC("IDENTIFIER");
+                        break;
+                    case TK_STRING_LITERAL:
+                        etypename = _SC("STRING_LITERAL");
+                        break;
+                    case TK_INTEGER:
+                        etypename = _SC("INTEGER");
+                        break;
+                    case TK_FLOAT:
+                        etypename = _SC("FLOAT");
+                        break;
+                    default:
+                        etypename = _lex.Tok2Str(tok);
+                    }
+                    Error(_SC("expected '%s'"), etypename);
+                }
+                Error(_SC("expected '%c'"), tok);
+            }
+        }
+        SQObjectPtr ret;
+        switch(tok)
+        {
+        case TK_IDENTIFIER:
+            ret = _fs->CreateString(_lex._svalue);
+            break;
+        case TK_STRING_LITERAL:
+            ret = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1);
+            break;
+        case TK_INTEGER:
+            ret = SQObjectPtr(_lex._nvalue);
+            break;
+        case TK_FLOAT:
+            ret = SQObjectPtr(_lex._fvalue);
+            break;
+        }
+        Lex();
+        return ret;
+    }
+    bool IsEndOfStatement() { return ((_lex._prevtoken == _SC('\n')) || (_token == SQUIRREL_EOB) || (_token == _SC('}')) || (_token == _SC(';'))); }
+    void OptionalSemicolon()
+    {
+        if(_token == _SC(';')) { Lex(); return; }        
+    }
+    void MoveIfCurrentTargetIsLocal() {
+        SQInteger trg = _fs->TopTarget();
+        if(_fs->IsLocal(trg)) {
+            trg = _fs->PopTarget(); //pops the target and moves it
+            _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), trg);
+        }
+    }
+    bool Compile(SQObjectPtr &o)
+    {
+        _debugline = 1;
+        _debugop = 0;
+
+        SQFuncState funcstate(_ss(_vm), NULL,ThrowError,this);
+        funcstate._name = SQString::Create(_ss(_vm), _SC("main"));
+        _fs = &funcstate;
+        _fs->AddParameter(_fs->CreateString(_SC("this")));
+        _fs->AddParameter(_fs->CreateString(_SC("vargv")));
+        _fs->_varparams = true;
+        _fs->_sourcename = _sourcename;
+        SQInteger stacksize = _fs->GetStackSize();
+        if(setjmp(_errorjmp) == 0) {
+            Lex();
+            while(_token > 0){
+                Statement();
+                if(_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon();
+            }
+            _fs->SetStackSize(stacksize);
+            _fs->AddLineInfos(_lex._currentline, _lineinfo, true);
+            _fs->AddInstruction(_OP_RETURN, 0xFF);
+            _fs->SetStackSize(0);
+            o =_fs->BuildProto();
+#ifdef _DEBUG_DUMP
+            _fs->Dump(_funcproto(o));
+#endif
+        }
+        else {
+            if(_raiseerror && _ss(_vm)->_compilererrorhandler) {
+                _ss(_vm)->_compilererrorhandler(_vm, _compilererror, sq_type(_sourcename) == OT_STRING?_stringval(_sourcename):_SC("unknown"),
+                    _lex._currentline, _lex._currentcolumn);
+            }
+            _vm->_lasterror = SQString::Create(_ss(_vm), _compilererror, -1);
+            return false;
+        }
+        return true;
+    }
+    void Statements()
+    {
+        while(_token != _SC('}') && _token != TK_DEFAULT && _token != TK_CASE) {
+            Statement();
+            if(_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon();
+        }
+    }
+    void Statement(bool closeframe = true)
+    {
+        _fs->AddLineInfos(_lex._currentline, _lineinfo);
+        switch(_token){
+        case _SC(';'):  Lex();                  break;
+        case TK_IF:     IfStatement();          break;
+        case TK_WHILE:      WhileStatement();       break;
+        case TK_DO:     DoWhileStatement();     break;
+        case TK_FOR:        ForStatement();         break;
+        case TK_FOREACH:    ForEachStatement();     break;
+        case TK_SWITCH: SwitchStatement();      break;
+        case TK_LOCAL:      LocalDeclStatement();   break;
+        case TK_RETURN:
+        case TK_YIELD: {
+            SQOpcode op;
+            if(_token == TK_RETURN) {
+                op = _OP_RETURN;
+            }
+            else {
+                op = _OP_YIELD;
+                _fs->_bgenerator = true;
+            }
+            Lex();
+            if(!IsEndOfStatement()) {
+                SQInteger retexp = _fs->GetCurrentPos()+1;
+                CommaExpr();
+                if(op == _OP_RETURN && _fs->_traps > 0)
+                    _fs->AddInstruction(_OP_POPTRAP, _fs->_traps, 0);
+                _fs->_returnexp = retexp;
+                _fs->AddInstruction(op, 1, _fs->PopTarget(),_fs->GetStackSize());
+            }
+            else{
+                if(op == _OP_RETURN && _fs->_traps > 0)
+                    _fs->AddInstruction(_OP_POPTRAP, _fs->_traps ,0);
+                _fs->_returnexp = -1;
+                _fs->AddInstruction(op, 0xFF,0,_fs->GetStackSize());
+            }
+            break;}
+        case TK_BREAK:
+            if(_fs->_breaktargets.size() <= 0)Error(_SC("'break' has to be in a loop block"));
+            if(_fs->_breaktargets.top() > 0){
+                _fs->AddInstruction(_OP_POPTRAP, _fs->_breaktargets.top(), 0);
+            }
+            RESOLVE_OUTERS();
+            _fs->AddInstruction(_OP_JMP, 0, -1234);
+            _fs->_unresolvedbreaks.push_back(_fs->GetCurrentPos());
+            Lex();
+            break;
+        case TK_CONTINUE:
+            if(_fs->_continuetargets.size() <= 0)Error(_SC("'continue' has to be in a loop block"));
+            if(_fs->_continuetargets.top() > 0) {
+                _fs->AddInstruction(_OP_POPTRAP, _fs->_continuetargets.top(), 0);
+            }
+            RESOLVE_OUTERS();
+            _fs->AddInstruction(_OP_JMP, 0, -1234);
+            _fs->_unresolvedcontinues.push_back(_fs->GetCurrentPos());
+            Lex();
+            break;
+        case TK_FUNCTION:
+            FunctionStatement();
+            break;
+        case TK_CLASS:
+            ClassStatement();
+            break;
+        case TK_ENUM:
+            EnumStatement();
+            break;
+        case _SC('{'):{
+                BEGIN_SCOPE();
+                Lex();
+                Statements();
+                Expect(_SC('}'));
+                if(closeframe) {
+                    END_SCOPE();
+                }
+                else {
+                    END_SCOPE_NO_CLOSE();
+                }
+            }
+            break;
+        case TK_TRY:
+            TryCatchStatement();
+            break;
+        case TK_THROW:
+            Lex();
+            CommaExpr();
+            _fs->AddInstruction(_OP_THROW, _fs->PopTarget());
+            break;
+        case TK_CONST:
+            {
+            Lex();
+            SQObject id = Expect(TK_IDENTIFIER);
+            Expect('=');
+            SQObject val = ExpectScalar();
+            OptionalSemicolon();
+            SQTable *enums = _table(_ss(_vm)->_consts);
+            SQObjectPtr strongid = id;
+            enums->NewSlot(strongid,SQObjectPtr(val));
+            strongid.Null();
+            }
+            break;
+        default:
+            CommaExpr();
+            _fs->DiscardTarget();
+            //_fs->PopTarget();
+            break;
+        }
+        _fs->SnoozeOpt();
+    }
+    void EmitDerefOp(SQOpcode op)
+    {
+        SQInteger val = _fs->PopTarget();
+        SQInteger key = _fs->PopTarget();
+        SQInteger src = _fs->PopTarget();
+        _fs->AddInstruction(op,_fs->PushTarget(),src,key,val);
+    }
+    void Emit2ArgsOP(SQOpcode op, SQInteger p3 = 0)
+    {
+        SQInteger p2 = _fs->PopTarget(); //src in OP_GET
+        SQInteger p1 = _fs->PopTarget(); //key in OP_GET
+        _fs->AddInstruction(op,_fs->PushTarget(), p1, p2, p3);
+    }
+    void EmitCompoundArith(SQInteger tok, SQInteger etype, SQInteger pos)
+    {
+        /* Generate code depending on the expression type */
+        switch(etype) {
+        case LOCAL:{
+            SQInteger p2 = _fs->PopTarget(); //src in OP_GET
+            SQInteger p1 = _fs->PopTarget(); //key in OP_GET
+            _fs->PushTarget(p1);
+            //EmitCompArithLocal(tok, p1, p1, p2);
+            _fs->AddInstruction(ChooseArithOpByToken(tok),p1, p2, p1, 0);
+            _fs->SnoozeOpt();
+                   }
+            break;
+        case OBJECT:
+        case BASE:
+            {
+                SQInteger val = _fs->PopTarget();
+                SQInteger key = _fs->PopTarget();
+                SQInteger src = _fs->PopTarget();
+                /* _OP_COMPARITH mixes dest obj and source val in the arg1 */
+                _fs->AddInstruction(_OP_COMPARITH, _fs->PushTarget(), (src<<16)|val, key, ChooseCompArithCharByToken(tok));
+            }
+            break;
+        case OUTER:
+            {
+                SQInteger val = _fs->TopTarget();
+                SQInteger tmp = _fs->PushTarget();
+                _fs->AddInstruction(_OP_GETOUTER,   tmp, pos);
+                _fs->AddInstruction(ChooseArithOpByToken(tok), tmp, val, tmp, 0);
+                _fs->PopTarget();
+                _fs->PopTarget();
+                _fs->AddInstruction(_OP_SETOUTER, _fs->PushTarget(), pos, tmp);
+            }
+            break;
+        }
+    }
+    void CommaExpr()
+    {
+        for(Expression();_token == ',';_fs->PopTarget(), Lex(), CommaExpr());
+    }
+    void Expression()
+    {
+         SQExpState es = _es;
+        _es.etype     = EXPR;
+        _es.epos      = -1;
+        _es.donot_get = false;
+        LogicalOrExp();
+        switch(_token)  {
+        case _SC('='):
+        case TK_NEWSLOT:
+        case TK_MINUSEQ:
+        case TK_PLUSEQ:
+        case TK_MULEQ:
+        case TK_DIVEQ:
+        case TK_MODEQ:{
+            SQInteger op = _token;
+            SQInteger ds = _es.etype;
+            SQInteger pos = _es.epos;
+            if(ds == EXPR) Error(_SC("can't assign expression"));
+            else if(ds == BASE) Error(_SC("'base' cannot be modified"));
+            Lex(); Expression();
+
+            switch(op){
+            case TK_NEWSLOT:
+                if(ds == OBJECT || ds == BASE)
+                    EmitDerefOp(_OP_NEWSLOT);
+                else //if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local
+                    Error(_SC("can't 'create' a local slot"));
+                break;
+            case _SC('='): //ASSIGN
+                switch(ds) {
+                case LOCAL:
+                    {
+                        SQInteger src = _fs->PopTarget();
+                        SQInteger dst = _fs->TopTarget();
+                        _fs->AddInstruction(_OP_MOVE, dst, src);
+                    }
+                    break;
+                case OBJECT:
+                case BASE:
+                    EmitDerefOp(_OP_SET);
+                    break;
+                case OUTER:
+                    {
+                        SQInteger src = _fs->PopTarget();
+                        SQInteger dst = _fs->PushTarget();
+                        _fs->AddInstruction(_OP_SETOUTER, dst, pos, src);
+                    }
+                }
+                break;
+            case TK_MINUSEQ:
+            case TK_PLUSEQ:
+            case TK_MULEQ:
+            case TK_DIVEQ:
+            case TK_MODEQ:
+                EmitCompoundArith(op, ds, pos);
+                break;
+            }
+            }
+            break;
+        case _SC('?'): {
+            Lex();
+            _fs->AddInstruction(_OP_JZ, _fs->PopTarget());
+            SQInteger jzpos = _fs->GetCurrentPos();
+            SQInteger trg = _fs->PushTarget();
+            Expression();
+            SQInteger first_exp = _fs->PopTarget();
+            if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp);
+            SQInteger endfirstexp = _fs->GetCurrentPos();
+            _fs->AddInstruction(_OP_JMP, 0, 0);
+            Expect(_SC(':'));
+            SQInteger jmppos = _fs->GetCurrentPos();
+            Expression();
+            SQInteger second_exp = _fs->PopTarget();
+            if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp);
+            _fs->SetInstructionParam(jmppos, 1, _fs->GetCurrentPos() - jmppos);
+            _fs->SetInstructionParam(jzpos, 1, endfirstexp - jzpos + 1);
+            _fs->SnoozeOpt();
+            }
+            break;
+        }
+        _es = es;
+    }
+    template<typename T> void INVOKE_EXP(T f)
+    {
+        SQExpState es = _es;
+        _es.etype     = EXPR;
+        _es.epos      = -1;
+        _es.donot_get = false;
+        (this->*f)();
+        _es = es;
+    }
+    template<typename T> void BIN_EXP(SQOpcode op, T f,SQInteger op3 = 0)
+    {
+        Lex();
+        INVOKE_EXP(f);
+        SQInteger op1 = _fs->PopTarget();SQInteger op2 = _fs->PopTarget();
+        _fs->AddInstruction(op, _fs->PushTarget(), op1, op2, op3);
+        _es.etype = EXPR;
+    }
+    void LogicalOrExp()
+    {
+        LogicalAndExp();
+        for(;;) if(_token == TK_OR) {
+            SQInteger first_exp = _fs->PopTarget();
+            SQInteger trg = _fs->PushTarget();
+            _fs->AddInstruction(_OP_OR, trg, 0, first_exp, 0);
+            SQInteger jpos = _fs->GetCurrentPos();
+            if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp);
+            Lex(); INVOKE_EXP(&SQCompiler::LogicalOrExp);
+            _fs->SnoozeOpt();
+            SQInteger second_exp = _fs->PopTarget();
+            if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp);
+            _fs->SnoozeOpt();
+            _fs->SetInstructionParam(jpos, 1, (_fs->GetCurrentPos() - jpos));
+            _es.etype = EXPR;
+            break;
+        }else return;
+    }
+    void LogicalAndExp()
+    {
+        BitwiseOrExp();
+        for(;;) switch(_token) {
+        case TK_AND: {
+            SQInteger first_exp = _fs->PopTarget();
+            SQInteger trg = _fs->PushTarget();
+            _fs->AddInstruction(_OP_AND, trg, 0, first_exp, 0);
+            SQInteger jpos = _fs->GetCurrentPos();
+            if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp);
+            Lex(); INVOKE_EXP(&SQCompiler::LogicalAndExp);
+            _fs->SnoozeOpt();
+            SQInteger second_exp = _fs->PopTarget();
+            if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp);
+            _fs->SnoozeOpt();
+            _fs->SetInstructionParam(jpos, 1, (_fs->GetCurrentPos() - jpos));
+            _es.etype = EXPR;
+            break;
+            }
+
+        default:
+            return;
+        }
+    }
+    void BitwiseOrExp()
+    {
+        BitwiseXorExp();
+        for(;;) if(_token == _SC('|'))
+        {BIN_EXP(_OP_BITW, &SQCompiler::BitwiseXorExp,BW_OR);
+        }else return;
+    }
+    void BitwiseXorExp()
+    {
+        BitwiseAndExp();
+        for(;;) if(_token == _SC('^'))
+        {BIN_EXP(_OP_BITW, &SQCompiler::BitwiseAndExp,BW_XOR);
+        }else return;
+    }
+    void BitwiseAndExp()
+    {
+        EqExp();
+        for(;;) if(_token == _SC('&'))
+        {BIN_EXP(_OP_BITW, &SQCompiler::EqExp,BW_AND);
+        }else return;
+    }
+    void EqExp()
+    {
+        CompExp();
+        for(;;) switch(_token) {
+        case TK_EQ: BIN_EXP(_OP_EQ, &SQCompiler::CompExp); break;
+        case TK_NE: BIN_EXP(_OP_NE, &SQCompiler::CompExp); break;
+        case TK_3WAYSCMP: BIN_EXP(_OP_CMP, &SQCompiler::CompExp,CMP_3W); break;
+        default: return;
+        }
+    }
+    void CompExp()
+    {
+        ShiftExp();
+        for(;;) switch(_token) {
+        case _SC('>'): BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_G); break;
+        case _SC('<'): BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_L); break;
+        case TK_GE: BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_GE); break;
+        case TK_LE: BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_LE); break;
+        case TK_IN: BIN_EXP(_OP_EXISTS, &SQCompiler::ShiftExp); break;
+        case TK_INSTANCEOF: BIN_EXP(_OP_INSTANCEOF, &SQCompiler::ShiftExp); break;
+        default: return;
+        }
+    }
+    void ShiftExp()
+    {
+        PlusExp();
+        for(;;) switch(_token) {
+        case TK_USHIFTR: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_USHIFTR); break;
+        case TK_SHIFTL: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_SHIFTL); break;
+        case TK_SHIFTR: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_SHIFTR); break;
+        default: return;
+        }
+    }
+    SQOpcode ChooseArithOpByToken(SQInteger tok)
+    {
+        switch(tok) {
+            case TK_PLUSEQ: case '+': return _OP_ADD;
+            case TK_MINUSEQ: case '-': return _OP_SUB;
+            case TK_MULEQ: case '*': return _OP_MUL;
+            case TK_DIVEQ: case '/': return _OP_DIV;
+            case TK_MODEQ: case '%': return _OP_MOD;
+            default: assert(0);
+        }
+        return _OP_ADD;
+    }
+    SQInteger ChooseCompArithCharByToken(SQInteger tok)
+    {
+        SQInteger oper;
+        switch(tok){
+        case TK_MINUSEQ: oper = '-'; break;
+        case TK_PLUSEQ: oper = '+'; break;
+        case TK_MULEQ: oper = '*'; break;
+        case TK_DIVEQ: oper = '/'; break;
+        case TK_MODEQ: oper = '%'; break;
+        default: oper = 0; //shut up compiler
+            assert(0); break;
+        };
+        return oper;
+    }
+    void PlusExp()
+    {
+        MultExp();
+        for(;;) switch(_token) {
+        case _SC('+'): case _SC('-'):
+            BIN_EXP(ChooseArithOpByToken(_token), &SQCompiler::MultExp); break;
+        default: return;
+        }
+    }
+
+    void MultExp()
+    {
+        PrefixedExpr();
+        for(;;) switch(_token) {
+        case _SC('*'): case _SC('/'): case _SC('%'):
+            BIN_EXP(ChooseArithOpByToken(_token), &SQCompiler::PrefixedExpr); break;
+        default: return;
+        }
+    }
+    //if 'pos' != -1 the previous variable is a local variable
+    void PrefixedExpr()
+    {
+        SQInteger pos = Factor();
+        for(;;) {
+            switch(_token) {
+            case _SC('.'):
+                pos = -1;
+                Lex();
+
+                _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER)));
+                if(_es.etype==BASE) {
+                    Emit2ArgsOP(_OP_GET);
+                    pos = _fs->TopTarget();
+                    _es.etype = EXPR;
+                    _es.epos   = pos;
+                }
+                else {
+                    if(NeedGet()) {
+                        Emit2ArgsOP(_OP_GET);
+                    }
+                    _es.etype = OBJECT;
+                }
+                break;
+            case _SC('['):
+                if(_lex._prevtoken == _SC('\n')) Error(_SC("cannot brake deref/or comma needed after [exp]=exp slot declaration"));
+                Lex(); Expression(); Expect(_SC(']'));
+                pos = -1;
+                if(_es.etype==BASE) {
+                    Emit2ArgsOP(_OP_GET);
+                    pos = _fs->TopTarget();
+                    _es.etype = EXPR;
+                    _es.epos   = pos;
+                }
+                else {
+                    if(NeedGet()) {
+                        Emit2ArgsOP(_OP_GET);
+                    }
+                    _es.etype = OBJECT;
+                }
+                break;
+            case TK_MINUSMINUS:
+            case TK_PLUSPLUS:
+                {
+                    if(IsEndOfStatement()) return;
+                    SQInteger diff = (_token==TK_MINUSMINUS) ? -1 : 1;
+                    Lex();
+                    switch(_es.etype)
+                    {
+                        case EXPR: Error(_SC("can't '++' or '--' an expression")); break;
+                        case OBJECT:
+                        case BASE:
+                            if(_es.donot_get == true)  { Error(_SC("can't '++' or '--' an expression")); break; } //mmh dor this make sense?
+                            Emit2ArgsOP(_OP_PINC, diff);
+                            break;
+                        case LOCAL: {
+                            SQInteger src = _fs->PopTarget();
+                            _fs->AddInstruction(_OP_PINCL, _fs->PushTarget(), src, 0, diff);
+                                    }
+                            break;
+                        case OUTER: {
+                            SQInteger tmp1 = _fs->PushTarget();
+                            SQInteger tmp2 = _fs->PushTarget();
+                            _fs->AddInstruction(_OP_GETOUTER, tmp2, _es.epos);
+                            _fs->AddInstruction(_OP_PINCL,    tmp1, tmp2, 0, diff);
+                            _fs->AddInstruction(_OP_SETOUTER, tmp2, _es.epos, tmp2);
+                            _fs->PopTarget();
+                        }
+                    }
+                }
+                return;
+                break;
+            case _SC('('):
+                switch(_es.etype) {
+                    case OBJECT: {
+                        SQInteger key     = _fs->PopTarget();  /* location of the key */
+                        SQInteger table   = _fs->PopTarget();  /* location of the object */
+                        SQInteger closure = _fs->PushTarget(); /* location for the closure */
+                        SQInteger ttarget = _fs->PushTarget(); /* location for 'this' pointer */
+                        _fs->AddInstruction(_OP_PREPCALL, closure, key, table, ttarget);
+                        }
+                        break;
+                    case BASE:
+                        //Emit2ArgsOP(_OP_GET);
+                        _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), 0);
+                        break;
+                    case OUTER:
+                        _fs->AddInstruction(_OP_GETOUTER, _fs->PushTarget(), _es.epos);
+                        _fs->AddInstruction(_OP_MOVE,     _fs->PushTarget(), 0);
+                        break;
+                    default:
+                        _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), 0);
+                }
+                _es.etype = EXPR;
+                Lex();
+                FunctionCallArgs();
+                break;
+            default: return;
+            }
+        }
+    }
+    SQInteger Factor()
+    {
+        //_es.etype = EXPR;
+        switch(_token)
+        {
+        case TK_STRING_LITERAL:
+            _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex._svalue,_lex._longstr.size()-1)));
+            Lex();
+            break;
+        case TK_BASE:
+            Lex();
+            _fs->AddInstruction(_OP_GETBASE, _fs->PushTarget());
+            _es.etype  = BASE;
+            _es.epos   = _fs->TopTarget();
+            return (_es.epos);
+            break;
+        case TK_IDENTIFIER:
+        case TK_CONSTRUCTOR:
+        case TK_THIS:{
+                SQObject id;
+                SQObject constant;
+
+                switch(_token) {
+                    case TK_IDENTIFIER:  id = _fs->CreateString(_lex._svalue);       break;
+                    case TK_THIS:        id = _fs->CreateString(_SC("this"),4);        break;
+                    case TK_CONSTRUCTOR: id = _fs->CreateString(_SC("constructor"),11); break;
+                }
+
+                SQInteger pos = -1;
+                Lex();
+                if((pos = _fs->GetLocalVariable(id)) != -1) {
+                    /* Handle a local variable (includes 'this') */
+                    _fs->PushTarget(pos);
+                    _es.etype  = LOCAL;
+                    _es.epos   = pos;
+                }
+
+                else if((pos = _fs->GetOuterVariable(id)) != -1) {
+                    /* Handle a free var */
+                    if(NeedGet()) {
+                        _es.epos  = _fs->PushTarget();
+                        _fs->AddInstruction(_OP_GETOUTER, _es.epos, pos);
+                        /* _es.etype = EXPR; already default value */
+                    }
+                    else {
+                        _es.etype = OUTER;
+                        _es.epos  = pos;
+                    }
+                }
+
+                else if(_fs->IsConstant(id, constant)) {
+                    /* Handle named constant */
+                    SQObjectPtr constval;
+                    SQObject    constid;
+                    if(sq_type(constant) == OT_TABLE) {
+                        Expect('.');
+                        constid = Expect(TK_IDENTIFIER);
+                        if(!_table(constant)->Get(constid, constval)) {
+                            constval.Null();
+                            Error(_SC("invalid constant [%s.%s]"), _stringval(id), _stringval(constid));
+                        }
+                    }
+                    else {
+                        constval = constant;
+                    }
+                    _es.epos = _fs->PushTarget();
+
+                    /* generate direct or literal function depending on size */
+                    SQObjectType ctype = sq_type(constval);
+                    switch(ctype) {
+                        case OT_INTEGER: EmitLoadConstInt(_integer(constval),_es.epos); break;
+                        case OT_FLOAT: EmitLoadConstFloat(_float(constval),_es.epos); break;
+                        case OT_BOOL: _fs->AddInstruction(_OP_LOADBOOL, _es.epos, _integer(constval)); break;
+                        default: _fs->AddInstruction(_OP_LOAD,_es.epos,_fs->GetConstant(constval)); break;
+                    }
+                    _es.etype = EXPR;
+                }
+                else {
+                    /* Handle a non-local variable, aka a field. Push the 'this' pointer on
+                    * the virtual stack (always found in offset 0, so no instruction needs to
+                    * be generated), and push the key next. Generate an _OP_LOAD instruction
+                    * for the latter. If we are not using the variable as a dref expr, generate
+                    * the _OP_GET instruction.
+                    */
+                    _fs->PushTarget(0);
+                    _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id));
+                    if(NeedGet()) {
+                        Emit2ArgsOP(_OP_GET);
+                    }
+                    _es.etype = OBJECT;
+                }
+                return _es.epos;
+            }
+            break;
+        case TK_DOUBLE_COLON:  // "::"
+            _fs->AddInstruction(_OP_LOADROOT, _fs->PushTarget());
+            _es.etype = OBJECT;
+            _token = _SC('.'); /* hack: drop into PrefixExpr, case '.'*/
+            _es.epos = -1;
+            return _es.epos;
+            break;
+        case TK_NULL:
+            _fs->AddInstruction(_OP_LOADNULLS, _fs->PushTarget(),1);
+            Lex();
+            break;
+        case TK_INTEGER: EmitLoadConstInt(_lex._nvalue,-1); Lex();  break;
+        case TK_FLOAT: EmitLoadConstFloat(_lex._fvalue,-1); Lex(); break;
+        case TK_TRUE: case TK_FALSE:
+            _fs->AddInstruction(_OP_LOADBOOL, _fs->PushTarget(),_token == TK_TRUE?1:0);
+            Lex();
+            break;
+        case _SC('['): {
+                _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,0,NOT_ARRAY);
+                SQInteger apos = _fs->GetCurrentPos(),key = 0;
+                Lex();
+                while(_token != _SC(']')) {
+                    Expression();
+                    if(_token == _SC(',')) Lex();
+                    SQInteger val = _fs->PopTarget();
+                    SQInteger array = _fs->TopTarget();
+                    _fs->AddInstruction(_OP_APPENDARRAY, array, val, AAT_STACK);
+                    key++;
+                }
+                _fs->SetInstructionParam(apos, 1, key);
+                Lex();
+            }
+            break;
+        case _SC('{'):
+            _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE);
+            Lex();ParseTableOrClass(_SC(','),_SC('}'));
+            break;
+        case TK_FUNCTION: FunctionExp(_token);break;
+        case _SC('@'): FunctionExp(_token);break;
+        case TK_CLASS: Lex(); ClassExp();break;
+        case _SC('-'):
+            Lex();
+            switch(_token) {
+            case TK_INTEGER: EmitLoadConstInt(-_lex._nvalue,-1); Lex(); break;
+            case TK_FLOAT: EmitLoadConstFloat(-_lex._fvalue,-1); Lex(); break;
+            default: UnaryOP(_OP_NEG);
+            }
+            break;
+        case _SC('!'): Lex(); UnaryOP(_OP_NOT); break;
+        case _SC('~'):
+            Lex();
+            if(_token == TK_INTEGER)  { EmitLoadConstInt(~_lex._nvalue,-1); Lex(); break; }
+            UnaryOP(_OP_BWNOT);
+            break;
+        case TK_TYPEOF : Lex() ;UnaryOP(_OP_TYPEOF); break;
+        case TK_RESUME : Lex(); UnaryOP(_OP_RESUME); break;
+        case TK_CLONE : Lex(); UnaryOP(_OP_CLONE); break;
+        case TK_RAWCALL: Lex(); Expect('('); FunctionCallArgs(true); break;
+        case TK_MINUSMINUS :
+        case TK_PLUSPLUS :PrefixIncDec(_token); break;
+        case TK_DELETE : DeleteExpr(); break;
+        case _SC('('): Lex(); CommaExpr(); Expect(_SC(')'));
+            break;
+        case TK___LINE__: EmitLoadConstInt(_lex._currentline,-1); Lex(); break;
+        case TK___FILE__: _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_sourcename)); Lex(); break;
+        default: Error(_SC("expression expected"));
+        }
+        _es.etype = EXPR;
+        return -1;
+    }
+    void EmitLoadConstInt(SQInteger value,SQInteger target)
+    {
+        if(target < 0) {
+            target = _fs->PushTarget();
+        }
+        if(value <= INT_MAX && value > INT_MIN) { //does it fit in 32 bits?
+            _fs->AddInstruction(_OP_LOADINT, target,value);
+        }
+        else {
+            _fs->AddInstruction(_OP_LOAD, target, _fs->GetNumericConstant(value));
+        }
+    }
+    void EmitLoadConstFloat(SQFloat value,SQInteger target)
+    {
+        if(target < 0) {
+            target = _fs->PushTarget();
+        }
+        if(sizeof(SQFloat) == sizeof(SQInt32)) {
+            _fs->AddInstruction(_OP_LOADFLOAT, target,*((SQInt32 *)&value));
+        }
+        else {
+            _fs->AddInstruction(_OP_LOAD, target, _fs->GetNumericConstant(value));
+        }
+    }
+    void UnaryOP(SQOpcode op)
+    {
+        PrefixedExpr();
+        SQInteger src = _fs->PopTarget();
+        _fs->AddInstruction(op, _fs->PushTarget(), src);
+    }
+    bool NeedGet()
+    {
+        switch(_token) {
+        case _SC('='): case _SC('('): case TK_NEWSLOT: case TK_MODEQ: case TK_MULEQ:
+        case TK_DIVEQ: case TK_MINUSEQ: case TK_PLUSEQ:
+            return false;
+        case TK_PLUSPLUS: case TK_MINUSMINUS:
+            if (!IsEndOfStatement()) {
+                return false;
+            }
+        break;
+        }
+        return (!_es.donot_get || ( _es.donot_get && (_token == _SC('.') || _token == _SC('['))));
+    }
+    void FunctionCallArgs(bool rawcall = false)
+    {
+        SQInteger nargs = 1;//this
+         while(_token != _SC(')')) {
+             Expression();
+             MoveIfCurrentTargetIsLocal();
+             nargs++;
+             if(_token == _SC(',')){
+                 Lex();
+                 if(_token == ')') Error(_SC("expression expected, found ')'"));
+             }
+         }
+         Lex();
+         if (rawcall) {
+             if (nargs < 3) Error(_SC("rawcall requires at least 2 parameters (callee and this)"));
+             nargs -= 2; //removes callee and this from count
+         }
+         for(SQInteger i = 0; i < (nargs - 1); i++) _fs->PopTarget();
+         SQInteger stackbase = _fs->PopTarget();
+         SQInteger closure = _fs->PopTarget();
+         _fs->AddInstruction(_OP_CALL, _fs->PushTarget(), closure, stackbase, nargs);
+    }
+    void ParseTableOrClass(SQInteger separator,SQInteger terminator)
+    {
+        SQInteger tpos = _fs->GetCurrentPos(),nkeys = 0;
+        while(_token != terminator) {
+            bool hasattrs = false;
+            bool isstatic = false;
+            //check if is an attribute
+            if(separator == ';') {
+                if(_token == TK_ATTR_OPEN) {
+                    _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE); Lex();
+                    ParseTableOrClass(',',TK_ATTR_CLOSE);
+                    hasattrs = true;
+                }
+                if(_token == TK_STATIC) {
+                    isstatic = true;
+                    Lex();
+                }
+            }
+            switch(_token) {
+            case TK_FUNCTION:
+            case TK_CONSTRUCTOR:{
+                SQInteger tk = _token;
+                Lex();
+                SQObject id = tk == TK_FUNCTION ? Expect(TK_IDENTIFIER) : _fs->CreateString(_SC("constructor"));
+                Expect(_SC('('));
+                _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id));
+                CreateFunction(id);
+                _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0);
+                                }
+                                break;
+            case _SC('['):
+                Lex(); CommaExpr(); Expect(_SC(']'));
+                Expect(_SC('=')); Expression();
+                break;
+            case TK_STRING_LITERAL: //JSON
+                if(separator == ',') { //only works for tables
+                    _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_STRING_LITERAL)));
+                    Expect(_SC(':')); Expression();
+                    break;
+                }
+            default :
+                _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER)));
+                Expect(_SC('=')); Expression();
+            }
+            if(_token == separator) Lex();//optional comma/semicolon
+            nkeys++;
+            SQInteger val = _fs->PopTarget();
+            SQInteger key = _fs->PopTarget();
+            SQInteger attrs = hasattrs ? _fs->PopTarget():-1;
+            ((void)attrs);
+            assert((hasattrs && (attrs == key-1)) || !hasattrs);
+            unsigned char flags = (hasattrs?NEW_SLOT_ATTRIBUTES_FLAG:0)|(isstatic?NEW_SLOT_STATIC_FLAG:0);
+            SQInteger table = _fs->TopTarget(); //<<BECAUSE OF THIS NO COMMON EMIT FUNC IS POSSIBLE
+            if(separator == _SC(',')) { //hack recognizes a table from the separator
+                _fs->AddInstruction(_OP_NEWSLOT, 0xFF, table, key, val);
+            }
+            else {
+                _fs->AddInstruction(_OP_NEWSLOTA, flags, table, key, val); //this for classes only as it invokes _newmember
+            }
+        }
+        if(separator == _SC(',')) //hack recognizes a table from the separator
+            _fs->SetInstructionParam(tpos, 1, nkeys);
+        Lex();
+    }
+    void LocalDeclStatement()
+    {
+        SQObject varname;
+        Lex();
+        if( _token == TK_FUNCTION) {
+            Lex();
+            varname = Expect(TK_IDENTIFIER);
+            Expect(_SC('('));
+            CreateFunction(varname);
+            _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0);
+            _fs->PopTarget();
+            _fs->PushLocalVariable(varname);
+            return;
+        }
+
+        do {
+            varname = Expect(TK_IDENTIFIER);
+            if(_token == _SC('=')) {
+                Lex(); Expression();
+                SQInteger src = _fs->PopTarget();
+                SQInteger dest = _fs->PushTarget();
+                if(dest != src) _fs->AddInstruction(_OP_MOVE, dest, src);
+            }
+            else{
+                _fs->AddInstruction(_OP_LOADNULLS, _fs->PushTarget(),1);
+            }
+            _fs->PopTarget();
+            _fs->PushLocalVariable(varname);
+            if(_token == _SC(',')) Lex(); else break;
+        } while(1);
+    }
+    void IfBlock()
+    {
+        if (_token == _SC('{'))
+        {
+            BEGIN_SCOPE();
+            Lex();
+            Statements();
+            Expect(_SC('}'));
+            if (true) {
+                END_SCOPE();
+            }
+            else {
+                END_SCOPE_NO_CLOSE();
+            }
+        }
+        else {
+            //BEGIN_SCOPE();
+            Statement();
+            if (_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon();
+            //END_SCOPE();
+        }
+    }
+    void IfStatement()
+    {
+        SQInteger jmppos;
+        bool haselse = false;
+        Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')'));
+        _fs->AddInstruction(_OP_JZ, _fs->PopTarget());
+        SQInteger jnepos = _fs->GetCurrentPos();
+
+
+
+        IfBlock();
+        //
+        /*static int n = 0;
+        if (_token != _SC('}') && _token != TK_ELSE) {
+            printf("IF %d-----------------------!!!!!!!!!\n", n);
+            if (n == 5)
+            {
+                printf("asd");
+            }
+            n++;
+            //OptionalSemicolon();
+        }*/
+
+
+        SQInteger endifblock = _fs->GetCurrentPos();
+        if(_token == TK_ELSE){
+            haselse = true;
+            //BEGIN_SCOPE();
+            _fs->AddInstruction(_OP_JMP);
+            jmppos = _fs->GetCurrentPos();
+            Lex();
+            //Statement(); if(_lex._prevtoken != _SC('}')) OptionalSemicolon();
+            IfBlock();
+            //END_SCOPE();
+            _fs->SetInstructionParam(jmppos, 1, _fs->GetCurrentPos() - jmppos);
+        }
+        _fs->SetInstructionParam(jnepos, 1, endifblock - jnepos + (haselse?1:0));
+    }
+    void WhileStatement()
+    {
+        SQInteger jzpos, jmppos;
+        jmppos = _fs->GetCurrentPos();
+        Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')'));
+
+        BEGIN_BREAKBLE_BLOCK();
+        _fs->AddInstruction(_OP_JZ, _fs->PopTarget());
+        jzpos = _fs->GetCurrentPos();
+        BEGIN_SCOPE();
+
+        Statement();
+
+        END_SCOPE();
+        _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1);
+        _fs->SetInstructionParam(jzpos, 1, _fs->GetCurrentPos() - jzpos);
+
+        END_BREAKBLE_BLOCK(jmppos);
+    }
+    void DoWhileStatement()
+    {
+        Lex();
+        SQInteger jmptrg = _fs->GetCurrentPos();
+        BEGIN_BREAKBLE_BLOCK()
+        BEGIN_SCOPE();
+        Statement();
+        END_SCOPE();
+        if(_token == TK_WHILE) {
+            Expect(TK_WHILE);
+            SQInteger continuetrg = _fs->GetCurrentPos();
+            Expect(_SC('(')); CommaExpr(); Expect(_SC(')'));
+            _fs->AddInstruction(_OP_JZ, _fs->PopTarget(), 1);
+            _fs->AddInstruction(_OP_JMP, 0, jmptrg - _fs->GetCurrentPos() - 1);
+            END_BREAKBLE_BLOCK(continuetrg);
+        } else {
+            SQInteger continuetrg = _fs->GetCurrentPos();
+            _fs->AddInstruction(_OP_JZ, 1, 1);
+            _fs->AddInstruction(_OP_JMP, 0, jmptrg - _fs->GetCurrentPos() - 1);
+            END_BREAKBLE_BLOCK(continuetrg);
+        }
+    }
+    void ForStatement()
+    {
+        Lex();
+        BEGIN_SCOPE();
+        Expect(_SC('('));
+        if(_token == TK_LOCAL) LocalDeclStatement();
+        else if(_token != _SC(';')){
+            CommaExpr();
+            _fs->PopTarget();
+        }
+        Expect(_SC(';'));
+        _fs->SnoozeOpt();
+        SQInteger jmppos = _fs->GetCurrentPos();
+        SQInteger jzpos = -1;
+        if(_token != _SC(';')) { CommaExpr(); _fs->AddInstruction(_OP_JZ, _fs->PopTarget()); jzpos = _fs->GetCurrentPos(); }
+        Expect(_SC(';'));
+        _fs->SnoozeOpt();
+        SQInteger expstart = _fs->GetCurrentPos() + 1;
+        if(_token != _SC(')')) {
+            CommaExpr();
+            _fs->PopTarget();
+        }
+        Expect(_SC(')'));
+        _fs->SnoozeOpt();
+        SQInteger expend = _fs->GetCurrentPos();
+        SQInteger expsize = (expend - expstart) + 1;
+        SQInstructionVec exp;
+        if(expsize > 0) {
+            for(SQInteger i = 0; i < expsize; i++)
+                exp.push_back(_fs->GetInstruction(expstart + i));
+            _fs->PopInstructions(expsize);
+        }
+        BEGIN_BREAKBLE_BLOCK()
+        Statement();
+        SQInteger continuetrg = _fs->GetCurrentPos();
+        if(expsize > 0) {
+            for(SQInteger i = 0; i < expsize; i++)
+                _fs->AddInstruction(exp[i]);
+        }
+        _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1, 0);
+        if(jzpos>  0) _fs->SetInstructionParam(jzpos, 1, _fs->GetCurrentPos() - jzpos);
+        
+        END_BREAKBLE_BLOCK(continuetrg);
+
+		END_SCOPE();
+    }
+    void ForEachStatement()
+    {
+        SQObject idxname, valname;
+        Lex(); Expect(_SC('(')); valname = Expect(TK_IDENTIFIER);
+        if(_token == _SC(',')) {
+            idxname = valname;
+            Lex(); valname = Expect(TK_IDENTIFIER);
+        }
+        else{
+            idxname = _fs->CreateString(_SC("@INDEX@"));
+        }
+        Expect(TK_IN);
+
+        //save the stack size
+        BEGIN_SCOPE();
+        //put the table in the stack(evaluate the table expression)
+        Expression(); Expect(_SC(')'));
+        SQInteger container = _fs->TopTarget();
+        //push the index local var
+        SQInteger indexpos = _fs->PushLocalVariable(idxname);
+        _fs->AddInstruction(_OP_LOADNULLS, indexpos,1);
+        //push the value local var
+        SQInteger valuepos = _fs->PushLocalVariable(valname);
+        _fs->AddInstruction(_OP_LOADNULLS, valuepos,1);
+        //push reference index
+        SQInteger itrpos = _fs->PushLocalVariable(_fs->CreateString(_SC("@ITERATOR@"))); //use invalid id to make it inaccessible
+        _fs->AddInstruction(_OP_LOADNULLS, itrpos,1);
+        SQInteger jmppos = _fs->GetCurrentPos();
+        _fs->AddInstruction(_OP_FOREACH, container, 0, indexpos);
+        SQInteger foreachpos = _fs->GetCurrentPos();
+        _fs->AddInstruction(_OP_POSTFOREACH, container, 0, indexpos);
+        //generate the statement code
+        BEGIN_BREAKBLE_BLOCK()
+        Statement();
+        _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1);
+        _fs->SetInstructionParam(foreachpos, 1, _fs->GetCurrentPos() - foreachpos);
+        _fs->SetInstructionParam(foreachpos + 1, 1, _fs->GetCurrentPos() - foreachpos);
+        END_BREAKBLE_BLOCK(foreachpos - 1);
+        //restore the local variable stack(remove index,val and ref idx)
+        _fs->PopTarget();
+        END_SCOPE();
+    }
+    void SwitchStatement()
+    {
+        Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')'));
+        Expect(_SC('{'));
+        SQInteger expr = _fs->TopTarget();
+        bool bfirst = true;
+        SQInteger tonextcondjmp = -1;
+        SQInteger skipcondjmp = -1;
+        SQInteger __nbreaks__ = _fs->_unresolvedbreaks.size();
+        _fs->_breaktargets.push_back(0);
+        while(_token == TK_CASE) {
+            if(!bfirst) {
+                _fs->AddInstruction(_OP_JMP, 0, 0);
+                skipcondjmp = _fs->GetCurrentPos();
+                _fs->SetInstructionParam(tonextcondjmp, 1, _fs->GetCurrentPos() - tonextcondjmp);
+            }
+            //condition
+            Lex(); Expression(); Expect(_SC(':'));
+            SQInteger trg = _fs->PopTarget();
+            SQInteger eqtarget = trg;
+            bool local = _fs->IsLocal(trg);
+            if(local) {
+                eqtarget = _fs->PushTarget(); //we need to allocate a extra reg
+            }
+            _fs->AddInstruction(_OP_EQ, eqtarget, trg, expr);
+            _fs->AddInstruction(_OP_JZ, eqtarget, 0);
+            if(local) {
+                _fs->PopTarget();
+            }
+
+            //end condition
+            if(skipcondjmp != -1) {
+                _fs->SetInstructionParam(skipcondjmp, 1, (_fs->GetCurrentPos() - skipcondjmp));
+            }
+            tonextcondjmp = _fs->GetCurrentPos();
+            BEGIN_SCOPE();
+            Statements();
+            END_SCOPE();
+            bfirst = false;
+        }
+        if(tonextcondjmp != -1)
+            _fs->SetInstructionParam(tonextcondjmp, 1, _fs->GetCurrentPos() - tonextcondjmp);
+        if(_token == TK_DEFAULT) {
+            Lex(); Expect(_SC(':'));
+            BEGIN_SCOPE();
+            Statements();
+            END_SCOPE();
+        }
+        Expect(_SC('}'));
+        _fs->PopTarget();
+        __nbreaks__ = _fs->_unresolvedbreaks.size() - __nbreaks__;
+        if(__nbreaks__ > 0)ResolveBreaks(_fs, __nbreaks__);
+        _fs->_breaktargets.pop_back();
+    }
+    void FunctionStatement()
+    {
+        SQObject id;
+        Lex(); id = Expect(TK_IDENTIFIER);
+        _fs->PushTarget(0);
+        _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id));
+        if(_token == TK_DOUBLE_COLON) Emit2ArgsOP(_OP_GET);
+
+        while(_token == TK_DOUBLE_COLON) {
+            Lex();
+            id = Expect(TK_IDENTIFIER);
+            _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id));
+            if(_token == TK_DOUBLE_COLON) Emit2ArgsOP(_OP_GET);
+        }
+        Expect(_SC('('));
+        CreateFunction(id);
+        _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0);
+        EmitDerefOp(_OP_NEWSLOT);
+        _fs->PopTarget();
+    }
+    void ClassStatement()
+    {
+        SQExpState es;
+        Lex();
+        es = _es;
+        _es.donot_get = true;
+        PrefixedExpr();
+        if(_es.etype == EXPR) {
+            Error(_SC("invalid class name"));
+        }
+        else if(_es.etype == OBJECT || _es.etype == BASE) {
+            ClassExp();
+            EmitDerefOp(_OP_NEWSLOT);
+            _fs->PopTarget();
+        }
+        else {
+            Error(_SC("cannot create a class in a local with the syntax(class <local>)"));
+        }
+        _es = es;
+    }
+    SQObject ExpectScalar()
+    {
+        SQObject val;
+        val._type = OT_NULL; val._unVal.nInteger = 0; //shut up GCC 4.x
+        switch(_token) {
+            case TK_INTEGER:
+                val._type = OT_INTEGER;
+                val._unVal.nInteger = _lex._nvalue;
+                break;
+            case TK_FLOAT:
+                val._type = OT_FLOAT;
+                val._unVal.fFloat = _lex._fvalue;
+                break;
+            case TK_STRING_LITERAL:
+                val = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1);
+                break;
+            case TK_TRUE:
+            case TK_FALSE:
+                val._type = OT_BOOL;
+                val._unVal.nInteger = _token == TK_TRUE ? 1 : 0;
+                break;
+            case '-':
+                Lex();
+                switch(_token)
+                {
+                case TK_INTEGER:
+                    val._type = OT_INTEGER;
+                    val._unVal.nInteger = -_lex._nvalue;
+                break;
+                case TK_FLOAT:
+                    val._type = OT_FLOAT;
+                    val._unVal.fFloat = -_lex._fvalue;
+                break;
+                default:
+                    Error(_SC("scalar expected : integer, float"));
+                }
+                break;
+            default:
+                Error(_SC("scalar expected : integer, float, or string"));
+        }
+        Lex();
+        return val;
+    }
+    void EnumStatement()
+    {
+        Lex();
+        SQObject id = Expect(TK_IDENTIFIER);
+        Expect(_SC('{'));
+
+        SQObject table = _fs->CreateTable();
+        SQInteger nval = 0;
+        while(_token != _SC('}')) {
+            SQObject key = Expect(TK_IDENTIFIER);
+            SQObject val;
+            if(_token == _SC('=')) {
+                Lex();
+                val = ExpectScalar();
+            }
+            else {
+                val._type = OT_INTEGER;
+                val._unVal.nInteger = nval++;
+            }
+            _table(table)->NewSlot(SQObjectPtr(key),SQObjectPtr(val));
+            if(_token == ',') Lex();
+        }
+        SQTable *enums = _table(_ss(_vm)->_consts);
+        SQObjectPtr strongid = id;
+        enums->NewSlot(SQObjectPtr(strongid),SQObjectPtr(table));
+        strongid.Null();
+        Lex();
+    }
+    void TryCatchStatement()
+    {
+        SQObject exid;
+        Lex();
+        _fs->AddInstruction(_OP_PUSHTRAP,0,0);
+        _fs->_traps++;
+        if(_fs->_breaktargets.size()) _fs->_breaktargets.top()++;
+        if(_fs->_continuetargets.size()) _fs->_continuetargets.top()++;
+        SQInteger trappos = _fs->GetCurrentPos();
+        {
+            BEGIN_SCOPE();
+            Statement();
+            END_SCOPE();
+        }
+        _fs->_traps--;
+        _fs->AddInstruction(_OP_POPTRAP, 1, 0);
+        if(_fs->_breaktargets.size()) _fs->_breaktargets.top()--;
+        if(_fs->_continuetargets.size()) _fs->_continuetargets.top()--;
+        _fs->AddInstruction(_OP_JMP, 0, 0);
+        SQInteger jmppos = _fs->GetCurrentPos();
+        _fs->SetInstructionParam(trappos, 1, (_fs->GetCurrentPos() - trappos));
+        Expect(TK_CATCH); Expect(_SC('(')); exid = Expect(TK_IDENTIFIER); Expect(_SC(')'));
+        {
+            BEGIN_SCOPE();
+            SQInteger ex_target = _fs->PushLocalVariable(exid);
+            _fs->SetInstructionParam(trappos, 0, ex_target);
+            Statement();
+            _fs->SetInstructionParams(jmppos, 0, (_fs->GetCurrentPos() - jmppos), 0);
+            END_SCOPE();
+        }
+    }
+    void FunctionExp(SQInteger ftype)
+    {
+        Lex(); Expect(_SC('('));
+        SQObjectPtr dummy;
+        CreateFunction(dummy);
+        _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, ftype == TK_FUNCTION?0:1);
+    }
+    void ClassExp()
+    {
+        SQInteger base = -1;
+        SQInteger attrs = -1;
+        if(_token == TK_EXTENDS) {
+            Lex(); Expression();
+            base = _fs->TopTarget();
+        }
+        if(_token == TK_ATTR_OPEN) {
+            Lex();
+            _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE);
+            ParseTableOrClass(_SC(','),TK_ATTR_CLOSE);
+            attrs = _fs->TopTarget();
+        }
+        Expect(_SC('{'));
+        if(attrs != -1) _fs->PopTarget();
+        if(base != -1) _fs->PopTarget();
+        _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(), base, attrs,NOT_CLASS);
+        ParseTableOrClass(_SC(';'),_SC('}'));
+    }
+    void DeleteExpr()
+    {
+        SQExpState es;
+        Lex();
+        es = _es;
+        _es.donot_get = true;
+        PrefixedExpr();
+        if(_es.etype==EXPR) Error(_SC("can't delete an expression"));
+        if(_es.etype==OBJECT || _es.etype==BASE) {
+            Emit2ArgsOP(_OP_DELETE);
+        }
+        else {
+            Error(_SC("cannot delete an (outer) local"));
+        }
+        _es = es;
+    }
+    void PrefixIncDec(SQInteger token)
+    {
+        SQExpState  es;
+        SQInteger diff = (token==TK_MINUSMINUS) ? -1 : 1;
+        Lex();
+        es = _es;
+        _es.donot_get = true;
+        PrefixedExpr();
+        if(_es.etype==EXPR) {
+            Error(_SC("can't '++' or '--' an expression"));
+        }
+        else if(_es.etype==OBJECT || _es.etype==BASE) {
+            Emit2ArgsOP(_OP_INC, diff);
+        }
+        else if(_es.etype==LOCAL) {
+            SQInteger src = _fs->TopTarget();
+            _fs->AddInstruction(_OP_INCL, src, src, 0, diff);
+
+        }
+        else if(_es.etype==OUTER) {
+            SQInteger tmp = _fs->PushTarget();
+            _fs->AddInstruction(_OP_GETOUTER, tmp, _es.epos);
+            _fs->AddInstruction(_OP_INCL,     tmp, tmp, 0, diff);
+            _fs->AddInstruction(_OP_SETOUTER, tmp, _es.epos, tmp);
+        }
+        _es = es;
+    }
+    void CreateFunction(SQObject &name)
+    {
+        SQFuncState *funcstate = _fs->PushChildState(_ss(_vm));
+        funcstate->_name = name;
+        SQObject paramname;
+        funcstate->AddParameter(_fs->CreateString(_SC("this")));
+        funcstate->_sourcename = _sourcename;
+        SQInteger defparams = 0;
+        while(_token!=_SC(')')) {
+            if(_token == TK_VARPARAMS) {
+                if(defparams > 0) Error(_SC("function with default parameters cannot have variable number of parameters"));
+                funcstate->AddParameter(_fs->CreateString(_SC("vargv")));
+                funcstate->_varparams = true;
+                Lex();
+                if(_token != _SC(')')) Error(_SC("expected ')'"));
+                break;
+            }
+            else {
+                paramname = Expect(TK_IDENTIFIER);
+                funcstate->AddParameter(paramname);
+                if(_token == _SC('=')) {
+                    Lex();
+                    Expression();
+                    funcstate->AddDefaultParam(_fs->TopTarget());
+                    defparams++;
+                }
+                else {
+                    if(defparams > 0) Error(_SC("expected '='"));
+                }
+                if(_token == _SC(',')) Lex();
+                else if(_token != _SC(')')) Error(_SC("expected ')' or ','"));
+            }
+        }
+        Expect(_SC(')'));
+        for(SQInteger n = 0; n < defparams; n++) {
+            _fs->PopTarget();
+        }
+
+        SQFuncState *currchunk = _fs;
+        _fs = funcstate;
+        Statement(false);
+        funcstate->AddLineInfos(_lex._prevtoken == _SC('\n')?_lex._lasttokenline:_lex._currentline, _lineinfo, true);
+        funcstate->AddInstruction(_OP_RETURN, -1);
+        funcstate->SetStackSize(0);
+
+        SQFunctionProto *func = funcstate->BuildProto();
+#ifdef _DEBUG_DUMP
+        funcstate->Dump(func);
+#endif
+        _fs = currchunk;
+        _fs->_functions.push_back(func);
+        _fs->PopChildState();
+    }
+    void ResolveBreaks(SQFuncState *funcstate, SQInteger ntoresolve)
+    {
+        while(ntoresolve > 0) {
+            SQInteger pos = funcstate->_unresolvedbreaks.back();
+            funcstate->_unresolvedbreaks.pop_back();
+            //set the jmp instruction
+            funcstate->SetInstructionParams(pos, 0, funcstate->GetCurrentPos() - pos, 0);
+            ntoresolve--;
+        }
+    }
+    void ResolveContinues(SQFuncState *funcstate, SQInteger ntoresolve, SQInteger targetpos)
+    {
+        while(ntoresolve > 0) {
+            SQInteger pos = funcstate->_unresolvedcontinues.back();
+            funcstate->_unresolvedcontinues.pop_back();
+            //set the jmp instruction
+            funcstate->SetInstructionParams(pos, 0, targetpos - pos, 0);
+            ntoresolve--;
+        }
+    }
+private:
+    SQInteger _token;
+    SQFuncState *_fs;
+    SQObjectPtr _sourcename;
+    SQLexer _lex;
+    bool _lineinfo;
+    bool _raiseerror;
+    SQInteger _debugline;
+    SQInteger _debugop;
+    SQExpState   _es;
+    SQScope _scope;
+    SQChar _compilererror[MAX_COMPILER_ERROR_LEN];
+    jmp_buf _errorjmp;
+    SQVM *_vm;
+};
+
+bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo)
+{
+    SQCompiler p(vm, rg, up, sourcename, raiseerror, lineinfo);
+    return p.Compile(out);
+}
+
+#endif
diff --git a/engines/twp/squirrel/sqcompiler.h b/engines/twp/squirrel/sqcompiler.h
new file mode 100755
index 00000000000..e7da64232cd
--- /dev/null
+++ b/engines/twp/squirrel/sqcompiler.h
@@ -0,0 +1,79 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQCOMPILER_H_
+#define _SQCOMPILER_H_
+
+struct SQVM;
+
+#define TK_IDENTIFIER   258
+#define TK_STRING_LITERAL   259
+#define TK_INTEGER  260
+#define TK_FLOAT    261
+#define TK_BASE 262
+#define TK_DELETE   263
+#define TK_EQ   264
+#define TK_NE   265
+#define TK_LE   266
+#define TK_GE   267
+#define TK_SWITCH   268
+#define TK_ARROW    269
+#define TK_AND  270
+#define TK_OR   271
+#define TK_IF   272
+#define TK_ELSE 273
+#define TK_WHILE    274
+#define TK_BREAK    275
+#define TK_FOR  276
+#define TK_DO   277
+#define TK_NULL 278
+#define TK_FOREACH  279
+#define TK_IN   280
+#define TK_NEWSLOT  281
+#define TK_MODULO   282
+#define TK_LOCAL    283
+#define TK_CLONE    284
+#define TK_FUNCTION 285
+#define TK_RETURN   286
+#define TK_TYPEOF   287
+#define TK_UMINUS   288
+#define TK_PLUSEQ   289
+#define TK_MINUSEQ  290
+#define TK_CONTINUE 291
+#define TK_YIELD 292
+#define TK_TRY 293
+#define TK_CATCH 294
+#define TK_THROW 295
+#define TK_SHIFTL 296
+#define TK_SHIFTR 297
+#define TK_RESUME 298
+#define TK_DOUBLE_COLON 299
+#define TK_CASE 300
+#define TK_DEFAULT 301
+#define TK_THIS 302
+#define TK_PLUSPLUS 303
+#define TK_MINUSMINUS 304
+#define TK_3WAYSCMP 305
+#define TK_USHIFTR 306
+#define TK_CLASS 307
+#define TK_EXTENDS 308
+#define TK_CONSTRUCTOR 310
+#define TK_INSTANCEOF 311
+#define TK_VARPARAMS 312
+#define TK___LINE__ 313
+#define TK___FILE__ 314
+#define TK_TRUE 315
+#define TK_FALSE 316
+#define TK_MULEQ 317
+#define TK_DIVEQ 318
+#define TK_MODEQ 319
+#define TK_ATTR_OPEN 320
+#define TK_ATTR_CLOSE 321
+#define TK_STATIC 322
+#define TK_ENUM 323
+#define TK_CONST 324
+#define TK_RAWCALL 325
+
+
+
+typedef void(*CompilerErrorFunc)(void *ud, const SQChar *s);
+bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo);
+#endif //_SQCOMPILER_H_
diff --git a/engines/twp/squirrel/sqconfig.h b/engines/twp/squirrel/sqconfig.h
new file mode 100755
index 00000000000..58bc9793a38
--- /dev/null
+++ b/engines/twp/squirrel/sqconfig.h
@@ -0,0 +1,146 @@
+
+#ifdef _SQ64
+
+#ifdef _MSC_VER
+typedef __int64 SQInteger;
+typedef unsigned __int64 SQUnsignedInteger;
+typedef unsigned __int64 SQHash; /*should be the same size of a pointer*/
+#else
+typedef long long SQInteger;
+typedef unsigned long long SQUnsignedInteger;
+typedef unsigned long long SQHash; /*should be the same size of a pointer*/
+#endif
+typedef int SQInt32;
+typedef unsigned int SQUnsignedInteger32;
+#else
+typedef int SQInteger;
+typedef int SQInt32; /*must be 32 bits(also on 64bits processors)*/
+typedef unsigned int SQUnsignedInteger32; /*must be 32 bits(also on 64bits processors)*/
+typedef unsigned int SQUnsignedInteger;
+typedef unsigned int SQHash; /*should be the same size of a pointer*/
+#endif
+
+
+#ifdef SQUSEDOUBLE
+typedef double SQFloat;
+#else
+typedef float SQFloat;
+#endif
+
+#if defined(SQUSEDOUBLE) && !defined(_SQ64) || !defined(SQUSEDOUBLE) && defined(_SQ64)
+#ifdef _MSC_VER
+typedef __int64 SQRawObjectVal; //must be 64bits
+#else
+typedef long long SQRawObjectVal; //must be 64bits
+#endif
+#define SQ_OBJECT_RAWINIT() { _unVal.raw = 0; }
+#else
+typedef SQUnsignedInteger SQRawObjectVal; //is 32 bits on 32 bits builds and 64 bits otherwise
+#define SQ_OBJECT_RAWINIT()
+#endif
+
+#ifndef SQ_ALIGNMENT // SQ_ALIGNMENT shall be less than or equal to SQ_MALLOC alignments, and its value shall be power of 2.
+#if defined(SQUSEDOUBLE) || defined(_SQ64)
+#define SQ_ALIGNMENT 8
+#else
+#define SQ_ALIGNMENT 4
+#endif
+#endif
+
+typedef void* SQUserPointer;
+typedef SQUnsignedInteger SQBool;
+typedef SQInteger SQRESULT;
+
+#ifdef SQUNICODE
+#include <wchar.h>
+#include <wctype.h>
+
+
+typedef wchar_t SQChar;
+
+
+#define scstrcmp    wcscmp
+#ifdef _WIN32
+#define scsprintf   _snwprintf
+#else
+#define scsprintf   swprintf
+#endif
+#define scstrlen    wcslen
+#define scstrtod    wcstod
+#ifdef _SQ64
+#define scstrtol    wcstoll
+#else
+#define scstrtol    wcstol
+#endif
+#define scstrtoul   wcstoul
+#define scvsprintf  vswprintf
+#define scstrstr    wcsstr
+#define scprintf    wprintf
+
+#ifdef _WIN32
+#define WCHAR_SIZE 2
+#define WCHAR_SHIFT_MUL 1
+#define MAX_CHAR 0xFFFF
+#else
+#define WCHAR_SIZE 4
+#define WCHAR_SHIFT_MUL 2
+#define MAX_CHAR 0xFFFFFFFF
+#endif
+
+#define _SC(a) L##a
+
+
+#define scisspace   iswspace
+#define scisdigit   iswdigit
+#define scisprint   iswprint
+#define scisxdigit  iswxdigit
+#define scisalpha   iswalpha
+#define sciscntrl   iswcntrl
+#define scisalnum   iswalnum
+
+
+#define sq_rsl(l) ((l)<<WCHAR_SHIFT_MUL)
+
+#else
+typedef char SQChar;
+#define _SC(a) a
+#define scstrcmp    strcmp
+#ifdef _MSC_VER
+#define scsprintf   _snprintf
+#else
+#define scsprintf   snprintf
+#endif
+#define scstrlen    strlen
+#define scstrtod    strtod
+#ifdef _SQ64
+#ifdef _MSC_VER
+#define scstrtol    _strtoi64
+#else
+#define scstrtol    strtoll
+#endif
+#else
+#define scstrtol    strtol
+#endif
+#define scstrtoul   strtoul
+#define scvsprintf  vsnprintf
+#define scstrstr    strstr
+#define scisspace   isspace
+#define scisdigit   isdigit
+#define scisprint   isprint
+#define scisxdigit  isxdigit
+#define sciscntrl   iscntrl
+#define scisalpha   isalpha
+#define scisalnum   isalnum
+#define scprintf    printf
+#define MAX_CHAR 0xFF
+
+#define sq_rsl(l) (l)
+
+#endif
+
+#ifdef _SQ64
+#define _PRINT_INT_PREC _SC("ll")
+#define _PRINT_INT_FMT _SC("%lld")
+#else
+#define _PRINT_INT_FMT _SC("%d")
+#endif
diff --git a/engines/twp/squirrel/sqdebug.cpp b/engines/twp/squirrel/sqdebug.cpp
new file mode 100755
index 00000000000..d55b1b2e4fc
--- /dev/null
+++ b/engines/twp/squirrel/sqdebug.cpp
@@ -0,0 +1,118 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include <stdarg.h>
+#include "sqvm.h"
+#include "sqfuncproto.h"
+#include "sqclosure.h"
+#include "sqstring.h"
+
+SQRESULT sq_getfunctioninfo(HSQUIRRELVM v,SQInteger level,SQFunctionInfo *fi)
+{
+    SQInteger cssize = v->_callsstacksize;
+    if (cssize > level) {
+        SQVM::CallInfo &ci = v->_callsstack[cssize-level-1];
+        if(sq_isclosure(ci._closure)) {
+            SQClosure *c = _closure(ci._closure);
+            SQFunctionProto *proto = c->_function;
+            fi->funcid = proto;
+            fi->name = sq_type(proto->_name) == OT_STRING?_stringval(proto->_name):_SC("unknown");
+            fi->source = sq_type(proto->_sourcename) == OT_STRING?_stringval(proto->_sourcename):_SC("unknown");
+            fi->line = proto->_lineinfos[0]._line;
+            return SQ_OK;
+        }
+    }
+    return sq_throwerror(v,_SC("the object is not a closure"));
+}
+
+SQRESULT sq_stackinfos(HSQUIRRELVM v, SQInteger level, SQStackInfos *si)
+{
+    SQInteger cssize = v->_callsstacksize;
+    if (cssize > level) {
+        memset(si, 0, sizeof(SQStackInfos));
+        SQVM::CallInfo &ci = v->_callsstack[cssize-level-1];
+        switch (sq_type(ci._closure)) {
+        case OT_CLOSURE:{
+            SQFunctionProto *func = _closure(ci._closure)->_function;
+            if (sq_type(func->_name) == OT_STRING)
+                si->funcname = _stringval(func->_name);
+            if (sq_type(func->_sourcename) == OT_STRING)
+                si->source = _stringval(func->_sourcename);
+            si->line = func->GetLine(ci._ip);
+                        }
+            break;
+        case OT_NATIVECLOSURE:
+            si->source = _SC("NATIVE");
+            si->funcname = _SC("unknown");
+            if(sq_type(_nativeclosure(ci._closure)->_name) == OT_STRING)
+                si->funcname = _stringval(_nativeclosure(ci._closure)->_name);
+            si->line = -1;
+            break;
+        default: break; //shutup compiler
+        }
+        return SQ_OK;
+    }
+    return SQ_ERROR;
+}
+
+void SQVM::Raise_Error(const SQChar *s, ...)
+{
+    va_list vl;
+    va_start(vl, s);
+    SQInteger buffersize = (SQInteger)scstrlen(s)+(NUMBER_MAX_CHAR*2);
+    scvsprintf(_sp(sq_rsl(buffersize)),buffersize, s, vl);
+    va_end(vl);
+    _lasterror = SQString::Create(_ss(this),_spval,-1);
+}
+
+void SQVM::Raise_Error(const SQObjectPtr &desc)
+{
+    _lasterror = desc;
+}
+
+SQString *SQVM::PrintObjVal(const SQObjectPtr &o)
+{
+    switch(sq_type(o)) {
+    case OT_STRING: return _string(o);
+    case OT_INTEGER:
+        scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)),sq_rsl(NUMBER_MAX_CHAR), _PRINT_INT_FMT, _integer(o));
+        return SQString::Create(_ss(this), _spval);
+        break;
+    case OT_FLOAT:
+        scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)), sq_rsl(NUMBER_MAX_CHAR), _SC("%.14g"), _float(o));
+        return SQString::Create(_ss(this), _spval);
+        break;
+    default:
+        return SQString::Create(_ss(this), GetTypeName(o));
+    }
+}
+
+void SQVM::Raise_IdxError(const SQObjectPtr &o)
+{
+    SQObjectPtr oval = PrintObjVal(o);
+    Raise_Error(_SC("the index '%.50s' does not exist"), _stringval(oval));
+}
+
+void SQVM::Raise_CompareError(const SQObject &o1, const SQObject &o2)
+{
+    SQObjectPtr oval1 = PrintObjVal(o1), oval2 = PrintObjVal(o2);
+    Raise_Error(_SC("comparison between '%.50s' and '%.50s'"), _stringval(oval1), _stringval(oval2));
+}
+
+
+void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger type)
+{
+    SQObjectPtr exptypes = SQString::Create(_ss(this), _SC(""), -1);
+    SQInteger found = 0;
+    for(SQInteger i=0; i<16; i++)
+    {
+        SQInteger mask = ((SQInteger)1) << i;
+        if(typemask & (mask)) {
+            if(found>0) StringCat(exptypes,SQString::Create(_ss(this), _SC("|"), -1), exptypes);
+            found ++;
+            StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask), -1), exptypes);
+        }
+    }
+    Raise_Error(_SC("parameter %d has an invalid type '%s' ; expected: '%s'"), nparam, IdType2Name((SQObjectType)type), _stringval(exptypes));
+}
diff --git a/engines/twp/squirrel/sqfuncproto.h b/engines/twp/squirrel/sqfuncproto.h
new file mode 100755
index 00000000000..546dbabb552
--- /dev/null
+++ b/engines/twp/squirrel/sqfuncproto.h
@@ -0,0 +1,154 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQFUNCTION_H_
+#define _SQFUNCTION_H_
+
+#include "sqopcodes.h"
+
+enum SQOuterType {
+    otLOCAL = 0,
+    otOUTER = 1
+};
+
+struct SQOuterVar
+{
+
+    SQOuterVar(){}
+    SQOuterVar(const SQObjectPtr &name,const SQObjectPtr &src,SQOuterType t)
+    {
+        _name = name;
+        _src=src;
+        _type=t;
+    }
+    SQOuterVar(const SQOuterVar &ov)
+    {
+        _type=ov._type;
+        _src=ov._src;
+        _name=ov._name;
+    }
+    SQOuterType _type;
+    SQObjectPtr _name;
+    SQObjectPtr _src;
+};
+
+struct SQLocalVarInfo
+{
+    SQLocalVarInfo():_start_op(0),_end_op(0),_pos(0){}
+    SQLocalVarInfo(const SQLocalVarInfo &lvi)
+    {
+        _name=lvi._name;
+        _start_op=lvi._start_op;
+        _end_op=lvi._end_op;
+        _pos=lvi._pos;
+    }
+    SQObjectPtr _name;
+    SQUnsignedInteger _start_op;
+    SQUnsignedInteger _end_op;
+    SQUnsignedInteger _pos;
+};
+
+struct SQLineInfo { SQInteger _line;SQInteger _op; };
+
+typedef sqvector<SQOuterVar> SQOuterVarVec;
+typedef sqvector<SQLocalVarInfo> SQLocalVarInfoVec;
+typedef sqvector<SQLineInfo> SQLineInfoVec;
+
+#define _FUNC_SIZE(ni,nl,nparams,nfuncs,nouters,nlineinf,localinf,defparams) (sizeof(SQFunctionProto) \
+        +((ni-1)*sizeof(SQInstruction))+(nl*sizeof(SQObjectPtr)) \
+        +(nparams*sizeof(SQObjectPtr))+(nfuncs*sizeof(SQObjectPtr)) \
+        +(nouters*sizeof(SQOuterVar))+(nlineinf*sizeof(SQLineInfo)) \
+        +(localinf*sizeof(SQLocalVarInfo))+(defparams*sizeof(SQInteger)))
+
+
+struct SQFunctionProto : public CHAINABLE_OBJ
+{
+private:
+    SQFunctionProto(SQSharedState *ss);
+    ~SQFunctionProto();
+
+public:
+    static SQFunctionProto *Create(SQSharedState *ss,SQInteger ninstructions,
+        SQInteger nliterals,SQInteger nparameters,
+        SQInteger nfunctions,SQInteger noutervalues,
+        SQInteger nlineinfos,SQInteger nlocalvarinfos,SQInteger ndefaultparams)
+    {
+        SQFunctionProto *f;
+        //I compact the whole class and members in a single memory allocation
+        f = (SQFunctionProto *)sq_vm_malloc(_FUNC_SIZE(ninstructions,nliterals,nparameters,nfunctions,noutervalues,nlineinfos,nlocalvarinfos,ndefaultparams));
+        new (f) SQFunctionProto(ss);
+        f->_ninstructions = ninstructions;
+        f->_literals = (SQObjectPtr*)&f->_instructions[ninstructions];
+        f->_nliterals = nliterals;
+        f->_parameters = (SQObjectPtr*)&f->_literals[nliterals];
+        f->_nparameters = nparameters;
+        f->_functions = (SQObjectPtr*)&f->_parameters[nparameters];
+        f->_nfunctions = nfunctions;
+        f->_outervalues = (SQOuterVar*)&f->_functions[nfunctions];
+        f->_noutervalues = noutervalues;
+        f->_lineinfos = (SQLineInfo *)&f->_outervalues[noutervalues];
+        f->_nlineinfos = nlineinfos;
+        f->_localvarinfos = (SQLocalVarInfo *)&f->_lineinfos[nlineinfos];
+        f->_nlocalvarinfos = nlocalvarinfos;
+        f->_defaultparams = (SQInteger *)&f->_localvarinfos[nlocalvarinfos];
+        f->_ndefaultparams = ndefaultparams;
+
+        _CONSTRUCT_VECTOR(SQObjectPtr,f->_nliterals,f->_literals);
+        _CONSTRUCT_VECTOR(SQObjectPtr,f->_nparameters,f->_parameters);
+        _CONSTRUCT_VECTOR(SQObjectPtr,f->_nfunctions,f->_functions);
+        _CONSTRUCT_VECTOR(SQOuterVar,f->_noutervalues,f->_outervalues);
+        //_CONSTRUCT_VECTOR(SQLineInfo,f->_nlineinfos,f->_lineinfos); //not required are 2 integers
+        _CONSTRUCT_VECTOR(SQLocalVarInfo,f->_nlocalvarinfos,f->_localvarinfos);
+        return f;
+    }
+    void Release(){
+        _DESTRUCT_VECTOR(SQObjectPtr,_nliterals,_literals);
+        _DESTRUCT_VECTOR(SQObjectPtr,_nparameters,_parameters);
+        _DESTRUCT_VECTOR(SQObjectPtr,_nfunctions,_functions);
+        _DESTRUCT_VECTOR(SQOuterVar,_noutervalues,_outervalues);
+        //_DESTRUCT_VECTOR(SQLineInfo,_nlineinfos,_lineinfos); //not required are 2 integers
+        _DESTRUCT_VECTOR(SQLocalVarInfo,_nlocalvarinfos,_localvarinfos);
+        SQInteger size = _FUNC_SIZE(_ninstructions,_nliterals,_nparameters,_nfunctions,_noutervalues,_nlineinfos,_nlocalvarinfos,_ndefaultparams);
+        this->~SQFunctionProto();
+        sq_vm_free(this,size);
+    }
+
+    const SQChar* GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop);
+    SQInteger GetLine(SQInstruction *curr);
+    bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write);
+    static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret);
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+    void Finalize(){ _NULL_SQOBJECT_VECTOR(_literals,_nliterals); }
+    SQObjectType GetType() {return OT_FUNCPROTO;}
+#endif
+    SQObjectPtr _sourcename;
+    SQObjectPtr _name;
+    SQInteger _stacksize;
+    bool _bgenerator;
+    SQInteger _varparams;
+
+    SQInteger _nlocalvarinfos;
+    SQLocalVarInfo *_localvarinfos;
+
+    SQInteger _nlineinfos;
+    SQLineInfo *_lineinfos;
+
+    SQInteger _nliterals;
+    SQObjectPtr *_literals;
+
+    SQInteger _nparameters;
+    SQObjectPtr *_parameters;
+
+    SQInteger _nfunctions;
+    SQObjectPtr *_functions;
+
+    SQInteger _noutervalues;
+    SQOuterVar *_outervalues;
+
+    SQInteger _ndefaultparams;
+    SQInteger *_defaultparams;
+
+    SQInteger _ninstructions;
+    SQInstruction _instructions[1];
+};
+
+#endif //_SQFUNCTION_H_
diff --git a/engines/twp/squirrel/sqfuncstate.cpp b/engines/twp/squirrel/sqfuncstate.cpp
new file mode 100755
index 00000000000..9d613545c25
--- /dev/null
+++ b/engines/twp/squirrel/sqfuncstate.cpp
@@ -0,0 +1,653 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#ifndef NO_COMPILER
+#include "sqcompiler.h"
+#include "sqstring.h"
+#include "sqfuncproto.h"
+#include "sqtable.h"
+#include "sqopcodes.h"
+#include "sqfuncstate.h"
+
+#ifdef _DEBUG_DUMP
+SQInstructionDesc g_InstrDesc[]={
+    {_SC("_OP_LINE")},
+    {_SC("_OP_LOAD")},
+    {_SC("_OP_LOADINT")},
+    {_SC("_OP_LOADFLOAT")},
+    {_SC("_OP_DLOAD")},
+    {_SC("_OP_TAILCALL")},
+    {_SC("_OP_CALL")},
+    {_SC("_OP_PREPCALL")},
+    {_SC("_OP_PREPCALLK")},
+    {_SC("_OP_GETK")},
+    {_SC("_OP_MOVE")},
+    {_SC("_OP_NEWSLOT")},
+    {_SC("_OP_DELETE")},
+    {_SC("_OP_SET")},
+    {_SC("_OP_GET")},
+    {_SC("_OP_EQ")},
+    {_SC("_OP_NE")},
+    {_SC("_OP_ADD")},
+    {_SC("_OP_SUB")},
+    {_SC("_OP_MUL")},
+    {_SC("_OP_DIV")},
+    {_SC("_OP_MOD")},
+    {_SC("_OP_BITW")},
+    {_SC("_OP_RETURN")},
+    {_SC("_OP_LOADNULLS")},
+    {_SC("_OP_LOADROOT")},
+    {_SC("_OP_LOADBOOL")},
+    {_SC("_OP_DMOVE")},
+    {_SC("_OP_JMP")},
+    {_SC("_OP_JCMP")},
+    {_SC("_OP_JZ")},
+    {_SC("_OP_SETOUTER")},
+    {_SC("_OP_GETOUTER")},
+    {_SC("_OP_NEWOBJ")},
+    {_SC("_OP_APPENDARRAY")},
+    {_SC("_OP_COMPARITH")},
+    {_SC("_OP_INC")},
+    {_SC("_OP_INCL")},
+    {_SC("_OP_PINC")},
+    {_SC("_OP_PINCL")},
+    {_SC("_OP_CMP")},
+    {_SC("_OP_EXISTS")},
+    {_SC("_OP_INSTANCEOF")},
+    {_SC("_OP_AND")},
+    {_SC("_OP_OR")},
+    {_SC("_OP_NEG")},
+    {_SC("_OP_NOT")},
+    {_SC("_OP_BWNOT")},
+    {_SC("_OP_CLOSURE")},
+    {_SC("_OP_YIELD")},
+    {_SC("_OP_RESUME")},
+    {_SC("_OP_FOREACH")},
+    {_SC("_OP_POSTFOREACH")},
+    {_SC("_OP_CLONE")},
+    {_SC("_OP_TYPEOF")},
+    {_SC("_OP_PUSHTRAP")},
+    {_SC("_OP_POPTRAP")},
+    {_SC("_OP_THROW")},
+    {_SC("_OP_NEWSLOTA")},
+    {_SC("_OP_GETBASE")},
+    {_SC("_OP_CLOSE")},
+};
+#endif
+void DumpLiteral(SQObjectPtr &o)
+{
+    switch(sq_type(o)){
+        case OT_STRING: scprintf(_SC("\"%s\""),_stringval(o));break;
+        case OT_FLOAT: scprintf(_SC("{%f}"),_float(o));break;
+        case OT_INTEGER: scprintf(_SC("{") _PRINT_INT_FMT _SC("}"),_integer(o));break;
+        case OT_BOOL: scprintf(_SC("%s"),_integer(o)?_SC("true"):_SC("false"));break;
+        default: scprintf(_SC("(%s %p)"),GetTypeName(o),(void*)_rawval(o));break; break; //shut up compiler
+    }
+}
+
+SQFuncState::SQFuncState(SQSharedState *ss,SQFuncState *parent,CompilerErrorFunc efunc,void *ed)
+{
+        _nliterals = 0;
+        _literals = SQTable::Create(ss,0);
+        _strings =  SQTable::Create(ss,0);
+        _sharedstate = ss;
+        _lastline = 0;
+        _optimization = true;
+        _parent = parent;
+        _stacksize = 0;
+        _traps = 0;
+        _returnexp = 0;
+        _varparams = false;
+        _errfunc = efunc;
+        _errtarget = ed;
+        _bgenerator = false;
+        _outers = 0;
+        _ss = ss;
+
+}
+
+void SQFuncState::Error(const SQChar *err)
+{
+    _errfunc(_errtarget,err);
+}
+
+#ifdef _DEBUG_DUMP
+void SQFuncState::Dump(SQFunctionProto *func)
+{
+    SQUnsignedInteger n=0,i;
+    SQInteger si;
+    scprintf(_SC("SQInstruction sizeof %d\n"),(SQInt32)sizeof(SQInstruction));
+    scprintf(_SC("SQObject sizeof %d\n"), (SQInt32)sizeof(SQObject));
+    scprintf(_SC("--------------------------------------------------------------------\n"));
+    scprintf(_SC("*****FUNCTION [%s]\n"),sq_type(func->_name)==OT_STRING?_stringval(func->_name):_SC("unknown"));
+    scprintf(_SC("-----LITERALS\n"));
+    SQObjectPtr refidx,key,val;
+    SQInteger idx;
+    SQObjectPtrVec templiterals;
+    templiterals.resize(_nliterals);
+    while((idx=_table(_literals)->Next(false,refidx,key,val))!=-1) {
+        refidx=idx;
+        templiterals[_integer(val)]=key;
+    }
+    for(i=0;i<templiterals.size();i++){
+        scprintf(_SC("[%d] "), (SQInt32)n);
+        DumpLiteral(templiterals[i]);
+        scprintf(_SC("\n"));
+        n++;
+    }
+    scprintf(_SC("-----PARAMS\n"));
+    if(_varparams)
+        scprintf(_SC("<<VARPARAMS>>\n"));
+    n=0;
+    for(i=0;i<_parameters.size();i++){
+        scprintf(_SC("[%d] "), (SQInt32)n);
+        DumpLiteral(_parameters[i]);
+        scprintf(_SC("\n"));
+        n++;
+    }
+    scprintf(_SC("-----LOCALS\n"));
+    for(si=0;si<func->_nlocalvarinfos;si++){
+        SQLocalVarInfo lvi=func->_localvarinfos[si];
+        scprintf(_SC("[%d] %s \t%d %d\n"), (SQInt32)lvi._pos,_stringval(lvi._name), (SQInt32)lvi._start_op, (SQInt32)lvi._end_op);
+        n++;
+    }
+    scprintf(_SC("-----LINE INFO\n"));
+    for(i=0;i<_lineinfos.size();i++){
+        SQLineInfo li=_lineinfos[i];
+        scprintf(_SC("op [%d] line [%d] \n"), (SQInt32)li._op, (SQInt32)li._line);
+        n++;
+    }
+    scprintf(_SC("-----dump\n"));
+    n=0;
+    for(i=0;i<_instructions.size();i++){
+        SQInstruction &inst=_instructions[i];
+        if(inst.op==_OP_LOAD || inst.op==_OP_DLOAD || inst.op==_OP_PREPCALLK || inst.op==_OP_GETK ){
+
+            SQInteger lidx = inst._arg1;
+            scprintf(_SC("[%03d] %15s %d "), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0);
+            if(lidx >= 0xFFFFFFFF)
+                scprintf(_SC("null"));
+            else {
+                SQInteger refidx;
+                SQObjectPtr val,key,refo;
+                while(((refidx=_table(_literals)->Next(false,refo,key,val))!= -1) && (_integer(val) != lidx)) {
+                    refo = refidx;
+                }
+                DumpLiteral(key);
+            }
+            if(inst.op != _OP_DLOAD) {
+                scprintf(_SC(" %d %d \n"),inst._arg2,inst._arg3);
+            }
+            else {
+                scprintf(_SC(" %d "),inst._arg2);
+                lidx = inst._arg3;
+                if(lidx >= 0xFFFFFFFF)
+                    scprintf(_SC("null"));
+                else {
+                    SQInteger refidx;
+                    SQObjectPtr val,key,refo;
+                    while(((refidx=_table(_literals)->Next(false,refo,key,val))!= -1) && (_integer(val) != lidx)) {
+                        refo = refidx;
+                }
+                DumpLiteral(key);
+                scprintf(_SC("\n"));
+            }
+            }
+        }
+        else if(inst.op==_OP_LOADFLOAT) {
+            scprintf(_SC("[%03d] %15s %d %f %d %d\n"), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0,*((SQFloat*)&inst._arg1),inst._arg2,inst._arg3);
+        }
+    /*  else if(inst.op==_OP_ARITH){
+            scprintf(_SC("[%03d] %15s %d %d %d %c\n"),n,g_InstrDesc[inst.op].name,inst._arg0,inst._arg1,inst._arg2,inst._arg3);
+        }*/
+        else {
+            scprintf(_SC("[%03d] %15s %d %d %d %d\n"), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0,inst._arg1,inst._arg2,inst._arg3);
+        }
+        n++;
+    }
+    scprintf(_SC("-----\n"));
+    scprintf(_SC("stack size[%d]\n"), (SQInt32)func->_stacksize);
+    scprintf(_SC("--------------------------------------------------------------------\n\n"));
+}
+#endif
+
+SQInteger SQFuncState::GetNumericConstant(const SQInteger cons)
+{
+    return GetConstant(SQObjectPtr(cons));
+}
+
+SQInteger SQFuncState::GetNumericConstant(const SQFloat cons)
+{
+    return GetConstant(SQObjectPtr(cons));
+}
+
+SQInteger SQFuncState::GetConstant(const SQObject &cons)
+{
+    SQObjectPtr val;
+    if(!_table(_literals)->Get(cons,val))
+    {
+        val = _nliterals;
+        _table(_literals)->NewSlot(cons,val);
+        _nliterals++;
+        if(_nliterals > MAX_LITERALS) {
+            val.Null();
+            Error(_SC("internal compiler error: too many literals"));
+        }
+    }
+    return _integer(val);
+}
+
+void SQFuncState::SetInstructionParams(SQInteger pos,SQInteger arg0,SQInteger arg1,SQInteger arg2,SQInteger arg3)
+{
+    _instructions[pos]._arg0=(unsigned char)*((SQUnsignedInteger *)&arg0);
+    _instructions[pos]._arg1=(SQInt32)*((SQUnsignedInteger *)&arg1);
+    _instructions[pos]._arg2=(unsigned char)*((SQUnsignedInteger *)&arg2);
+    _instructions[pos]._arg3=(unsigned char)*((SQUnsignedInteger *)&arg3);
+}
+
+void SQFuncState::SetInstructionParam(SQInteger pos,SQInteger arg,SQInteger val)
+{
+    switch(arg){
+        case 0:_instructions[pos]._arg0=(unsigned char)*((SQUnsignedInteger *)&val);break;
+        case 1:case 4:_instructions[pos]._arg1=(SQInt32)*((SQUnsignedInteger *)&val);break;
+        case 2:_instructions[pos]._arg2=(unsigned char)*((SQUnsignedInteger *)&val);break;
+        case 3:_instructions[pos]._arg3=(unsigned char)*((SQUnsignedInteger *)&val);break;
+    };
+}
+
+SQInteger SQFuncState::AllocStackPos()
+{
+    SQInteger npos=_vlocals.size();
+    _vlocals.push_back(SQLocalVarInfo());
+    if(_vlocals.size()>((SQUnsignedInteger)_stacksize)) {
+        if(_stacksize>MAX_FUNC_STACKSIZE) Error(_SC("internal compiler error: too many locals"));
+        _stacksize=_vlocals.size();
+    }
+    return npos;
+}
+
+SQInteger SQFuncState::PushTarget(SQInteger n)
+{
+    if(n!=-1){
+        _targetstack.push_back(n);
+        return n;
+    }
+    n=AllocStackPos();
+    _targetstack.push_back(n);
+    return n;
+}
+
+SQInteger SQFuncState::GetUpTarget(SQInteger n){
+    return _targetstack[((_targetstack.size()-1)-n)];
+}
+
+SQInteger SQFuncState::TopTarget(){
+    return _targetstack.back();
+}
+SQInteger SQFuncState::PopTarget()
+{
+    SQUnsignedInteger npos=_targetstack.back();
+    assert(npos < _vlocals.size());
+    SQLocalVarInfo &t = _vlocals[npos];
+    if(sq_type(t._name)==OT_NULL){
+        _vlocals.pop_back();
+    }
+    _targetstack.pop_back();
+    return npos;
+}
+
+SQInteger SQFuncState::GetStackSize()
+{
+    return _vlocals.size();
+}
+
+SQInteger SQFuncState::CountOuters(SQInteger stacksize)
+{
+    SQInteger outers = 0;
+    SQInteger k = _vlocals.size() - 1;
+    while(k >= stacksize) {
+        SQLocalVarInfo &lvi = _vlocals[k];
+        k--;
+        if(lvi._end_op == UINT_MINUS_ONE) { //this means is an outer
+            outers++;
+        }
+    }
+    return outers;
+}
+
+void SQFuncState::SetStackSize(SQInteger n)
+{
+    SQInteger size=_vlocals.size();
+    while(size>n){
+        size--;
+        SQLocalVarInfo lvi = _vlocals.back();
+        if(sq_type(lvi._name)!=OT_NULL){
+            if(lvi._end_op == UINT_MINUS_ONE) { //this means is an outer
+                _outers--;
+            }
+            lvi._end_op = GetCurrentPos();
+            _localvarinfos.push_back(lvi);
+        }
+        _vlocals.pop_back();
+    }
+}
+
+bool SQFuncState::IsConstant(const SQObject &name,SQObject &e)
+{
+    SQObjectPtr val;
+    if(_table(_sharedstate->_consts)->Get(name,val)) {
+        e = val;
+        return true;
+    }
+    return false;
+}
+
+bool SQFuncState::IsLocal(SQUnsignedInteger stkpos)
+{
+    if(stkpos>=_vlocals.size())return false;
+    else if(sq_type(_vlocals[stkpos]._name)!=OT_NULL)return true;
+    return false;
+}
+
+SQInteger SQFuncState::PushLocalVariable(const SQObject &name)
+{
+    SQInteger pos=_vlocals.size();
+    SQLocalVarInfo lvi;
+    lvi._name=name;
+    lvi._start_op=GetCurrentPos()+1;
+    lvi._pos=_vlocals.size();
+    _vlocals.push_back(lvi);
+    if(_vlocals.size()>((SQUnsignedInteger)_stacksize))_stacksize=_vlocals.size();
+    return pos;
+}
+
+
+
+SQInteger SQFuncState::GetLocalVariable(const SQObject &name)
+{
+    SQInteger locals=_vlocals.size();
+    while(locals>=1){
+        SQLocalVarInfo &lvi = _vlocals[locals-1];
+        if(sq_type(lvi._name)==OT_STRING && _string(lvi._name)==_string(name)){
+            return locals-1;
+        }
+        locals--;
+    }
+    return -1;
+}
+
+void SQFuncState::MarkLocalAsOuter(SQInteger pos)
+{
+    SQLocalVarInfo &lvi = _vlocals[pos];
+    lvi._end_op = UINT_MINUS_ONE;
+    _outers++;
+}
+
+SQInteger SQFuncState::GetOuterVariable(const SQObject &name)
+{
+    SQInteger outers = _outervalues.size();
+    for(SQInteger i = 0; i<outers; i++) {
+        if(_string(_outervalues[i]._name) == _string(name))
+            return i;
+    }
+    SQInteger pos=-1;
+    if(_parent) {
+        pos = _parent->GetLocalVariable(name);
+        if(pos == -1) {
+            pos = _parent->GetOuterVariable(name);
+            if(pos != -1) {
+                _outervalues.push_back(SQOuterVar(name,SQObjectPtr(SQInteger(pos)),otOUTER)); //local
+                return _outervalues.size() - 1;
+            }
+        }
+        else {
+            _parent->MarkLocalAsOuter(pos);
+            _outervalues.push_back(SQOuterVar(name,SQObjectPtr(SQInteger(pos)),otLOCAL)); //local
+            return _outervalues.size() - 1;
+
+
+        }
+    }
+    return -1;
+}
+
+void SQFuncState::AddParameter(const SQObject &name)
+{
+    PushLocalVariable(name);
+    _parameters.push_back(name);
+}
+
+void SQFuncState::AddLineInfos(SQInteger line,bool lineop,bool force)
+{
+    if(_lastline!=line || force){
+        SQLineInfo li;
+        li._line=line;li._op=(GetCurrentPos()+1);
+        if(lineop)AddInstruction(_OP_LINE,0,line);
+        if(_lastline!=line) {
+            _lineinfos.push_back(li);
+        }
+        _lastline=line;
+    }
+}
+
+void SQFuncState::DiscardTarget()
+{
+    SQInteger discardedtarget = PopTarget();
+    SQInteger size = _instructions.size();
+    if(size > 0 && _optimization){
+        SQInstruction &pi = _instructions[size-1];//previous instruction
+        switch(pi.op) {
+        case _OP_SET:case _OP_NEWSLOT:case _OP_SETOUTER:case _OP_CALL:
+            if(pi._arg0 == discardedtarget) {
+                pi._arg0 = 0xFF;
+            }
+        }
+    }
+}
+
+void SQFuncState::AddInstruction(SQInstruction &i)
+{
+    SQInteger size = _instructions.size();
+    if(size > 0 && _optimization){ //simple optimizer
+        SQInstruction &pi = _instructions[size-1];//previous instruction
+        switch(i.op) {
+        case _OP_JZ:
+            if( pi.op == _OP_CMP && pi._arg1 < 0xFF) {
+                pi.op = _OP_JCMP;
+                pi._arg0 = (unsigned char)pi._arg1;
+                pi._arg1 = i._arg1;
+                return;
+            }
+            break;
+        case _OP_SET:
+        case _OP_NEWSLOT:
+            if(i._arg0 == i._arg3) {
+                i._arg0 = 0xFF;
+            }
+            break;
+        case _OP_SETOUTER:
+            if(i._arg0 == i._arg2) {
+                i._arg0 = 0xFF;
+            }
+            break;
+        case _OP_RETURN:
+            if( _parent && i._arg0 != MAX_FUNC_STACKSIZE && pi.op == _OP_CALL && _returnexp < size-1) {
+                pi.op = _OP_TAILCALL;
+            } else if(pi.op == _OP_CLOSE){
+                pi = i;
+                return;
+            }
+        break;
+        case _OP_GET:
+            if( pi.op == _OP_LOAD && pi._arg0 == i._arg2 && (!IsLocal(pi._arg0))){
+                pi._arg1 = pi._arg1;
+                pi._arg2 = (unsigned char)i._arg1;
+                pi.op = _OP_GETK;
+                pi._arg0 = i._arg0;
+
+                return;
+            }
+        break;
+        case _OP_PREPCALL:
+            if( pi.op == _OP_LOAD  && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0))){
+                pi.op = _OP_PREPCALLK;
+                pi._arg0 = i._arg0;
+                pi._arg1 = pi._arg1;
+                pi._arg2 = i._arg2;
+                pi._arg3 = i._arg3;
+                return;
+            }
+            break;
+        case _OP_APPENDARRAY: {
+            SQInteger aat = -1;
+            switch(pi.op) {
+            case _OP_LOAD: aat = AAT_LITERAL; break;
+            case _OP_LOADINT: aat = AAT_INT; break;
+            case _OP_LOADBOOL: aat = AAT_BOOL; break;
+            case _OP_LOADFLOAT: aat = AAT_FLOAT; break;
+            default: break;
+            }
+            if(aat != -1 && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0))){
+                pi.op = _OP_APPENDARRAY;
+                pi._arg0 = i._arg0;
+                pi._arg1 = pi._arg1;
+                pi._arg2 = (unsigned char)aat;
+                pi._arg3 = MAX_FUNC_STACKSIZE;
+                return;
+            }
+                              }
+            break;
+        case _OP_MOVE:
+            switch(pi.op) {
+            case _OP_GET: case _OP_ADD: case _OP_SUB: case _OP_MUL: case _OP_DIV: case _OP_MOD: case _OP_BITW:
+            case _OP_LOADINT: case _OP_LOADFLOAT: case _OP_LOADBOOL: case _OP_LOAD:
+
+                if(pi._arg0 == i._arg1)
+                {
+                    pi._arg0 = i._arg0;
+                    _optimization = false;
+                    //_result_elimination = false;
+                    return;
+                }
+            }
+
+            if(pi.op == _OP_MOVE)
+            {
+                pi.op = _OP_DMOVE;
+                pi._arg2 = i._arg0;
+                pi._arg3 = (unsigned char)i._arg1;
+                return;
+            }
+            break;
+        case _OP_LOAD:
+            if(pi.op == _OP_LOAD && i._arg1 < 256) {
+                pi.op = _OP_DLOAD;
+                pi._arg2 = i._arg0;
+                pi._arg3 = (unsigned char)i._arg1;
+                return;
+            }
+            break;
+        case _OP_EQ:case _OP_NE:
+            if(pi.op == _OP_LOAD && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0) ))
+            {
+                pi.op = i.op;
+                pi._arg0 = i._arg0;
+                pi._arg1 = pi._arg1;
+                pi._arg2 = i._arg2;
+                pi._arg3 = MAX_FUNC_STACKSIZE;
+                return;
+            }
+            break;
+        case _OP_LOADNULLS:
+            if((pi.op == _OP_LOADNULLS && pi._arg0+pi._arg1 == i._arg0)) {
+
+                pi._arg1 = pi._arg1 + 1;
+                pi.op = _OP_LOADNULLS;
+                return;
+            }
+            break;
+        case _OP_LINE:
+            if(pi.op == _OP_LINE) {
+                _instructions.pop_back();
+                _lineinfos.pop_back();
+            }
+            break;
+        }
+    }
+    _optimization = true;
+    _instructions.push_back(i);
+}
+
+SQObject SQFuncState::CreateString(const SQChar *s,SQInteger len)
+{
+    SQObjectPtr ns(SQString::Create(_sharedstate,s,len));
+    _table(_strings)->NewSlot(ns,(SQInteger)1);
+    return ns;
+}
+
+SQObject SQFuncState::CreateTable()
+{
+    SQObjectPtr nt(SQTable::Create(_sharedstate,0));
+    _table(_strings)->NewSlot(nt,(SQInteger)1);
+    return nt;
+}
+
+SQFunctionProto *SQFuncState::BuildProto()
+{
+
+    SQFunctionProto *f=SQFunctionProto::Create(_ss,_instructions.size(),
+        _nliterals,_parameters.size(),_functions.size(),_outervalues.size(),
+        _lineinfos.size(),_localvarinfos.size(),_defaultparams.size());
+
+    SQObjectPtr refidx,key,val;
+    SQInteger idx;
+
+    f->_stacksize = _stacksize;
+    f->_sourcename = _sourcename;
+    f->_bgenerator = _bgenerator;
+    f->_name = _name;
+
+    while((idx=_table(_literals)->Next(false,refidx,key,val))!=-1) {
+        f->_literals[_integer(val)]=key;
+        refidx=idx;
+    }
+
+    for(SQUnsignedInteger nf = 0; nf < _functions.size(); nf++) f->_functions[nf] = _functions[nf];
+    for(SQUnsignedInteger np = 0; np < _parameters.size(); np++) f->_parameters[np] = _parameters[np];
+    for(SQUnsignedInteger no = 0; no < _outervalues.size(); no++) f->_outervalues[no] = _outervalues[no];
+    for(SQUnsignedInteger nl = 0; nl < _localvarinfos.size(); nl++) f->_localvarinfos[nl] = _localvarinfos[nl];
+    for(SQUnsignedInteger ni = 0; ni < _lineinfos.size(); ni++) f->_lineinfos[ni] = _lineinfos[ni];
+    for(SQUnsignedInteger nd = 0; nd < _defaultparams.size(); nd++) f->_defaultparams[nd] = _defaultparams[nd];
+
+    memcpy(f->_instructions,&_instructions[0],_instructions.size()*sizeof(SQInstruction));
+
+    f->_varparams = _varparams;
+
+    return f;
+}
+
+SQFuncState *SQFuncState::PushChildState(SQSharedState *ss)
+{
+    SQFuncState *child = (SQFuncState *)sq_malloc(sizeof(SQFuncState));
+    new (child) SQFuncState(ss,this,_errfunc,_errtarget);
+    _childstates.push_back(child);
+    return child;
+}
+
+void SQFuncState::PopChildState()
+{
+    SQFuncState *child = _childstates.back();
+    sq_delete(child,SQFuncState);
+    _childstates.pop_back();
+}
+
+SQFuncState::~SQFuncState()
+{
+    while(_childstates.size() > 0)
+    {
+        PopChildState();
+    }
+}
+
+#endif
diff --git a/engines/twp/squirrel/sqfuncstate.h b/engines/twp/squirrel/sqfuncstate.h
new file mode 100755
index 00000000000..130aa54ff6b
--- /dev/null
+++ b/engines/twp/squirrel/sqfuncstate.h
@@ -0,0 +1,91 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQFUNCSTATE_H_
+#define _SQFUNCSTATE_H_
+///////////////////////////////////
+#include "squtils.h"
+
+struct SQFuncState
+{
+    SQFuncState(SQSharedState *ss,SQFuncState *parent,CompilerErrorFunc efunc,void *ed);
+    ~SQFuncState();
+#ifdef _DEBUG_DUMP
+    void Dump(SQFunctionProto *func);
+#endif
+    void Error(const SQChar *err);
+    SQFuncState *PushChildState(SQSharedState *ss);
+    void PopChildState();
+    void AddInstruction(SQOpcode _op,SQInteger arg0=0,SQInteger arg1=0,SQInteger arg2=0,SQInteger arg3=0){SQInstruction i(_op,arg0,arg1,arg2,arg3);AddInstruction(i);}
+    void AddInstruction(SQInstruction &i);
+    void SetInstructionParams(SQInteger pos,SQInteger arg0,SQInteger arg1,SQInteger arg2=0,SQInteger arg3=0);
+    void SetInstructionParam(SQInteger pos,SQInteger arg,SQInteger val);
+    SQInstruction &GetInstruction(SQInteger pos){return _instructions[pos];}
+    void PopInstructions(SQInteger size){for(SQInteger i=0;i<size;i++)_instructions.pop_back();}
+    void SetStackSize(SQInteger n);
+    SQInteger CountOuters(SQInteger stacksize);
+    void SnoozeOpt(){_optimization=false;}
+    void AddDefaultParam(SQInteger trg) { _defaultparams.push_back(trg); }
+    SQInteger GetDefaultParamCount() { return _defaultparams.size(); }
+    SQInteger GetCurrentPos(){return _instructions.size()-1;}
+    SQInteger GetNumericConstant(const SQInteger cons);
+    SQInteger GetNumericConstant(const SQFloat cons);
+    SQInteger PushLocalVariable(const SQObject &name);
+    void AddParameter(const SQObject &name);
+    //void AddOuterValue(const SQObject &name);
+    SQInteger GetLocalVariable(const SQObject &name);
+    void MarkLocalAsOuter(SQInteger pos);
+    SQInteger GetOuterVariable(const SQObject &name);
+    SQInteger GenerateCode();
+    SQInteger GetStackSize();
+    SQInteger CalcStackFrameSize();
+    void AddLineInfos(SQInteger line,bool lineop,bool force=false);
+    SQFunctionProto *BuildProto();
+    SQInteger AllocStackPos();
+    SQInteger PushTarget(SQInteger n=-1);
+    SQInteger PopTarget();
+    SQInteger TopTarget();
+    SQInteger GetUpTarget(SQInteger n);
+    void DiscardTarget();
+    bool IsLocal(SQUnsignedInteger stkpos);
+    SQObject CreateString(const SQChar *s,SQInteger len = -1);
+    SQObject CreateTable();
+    bool IsConstant(const SQObject &name,SQObject &e);
+    SQInteger _returnexp;
+    SQLocalVarInfoVec _vlocals;
+    SQIntVec _targetstack;
+    SQInteger _stacksize;
+    bool _varparams;
+    bool _bgenerator;
+    SQIntVec _unresolvedbreaks;
+    SQIntVec _unresolvedcontinues;
+    SQObjectPtrVec _functions;
+    SQObjectPtrVec _parameters;
+    SQOuterVarVec _outervalues;
+    SQInstructionVec _instructions;
+    SQLocalVarInfoVec _localvarinfos;
+    SQObjectPtr _literals;
+    SQObjectPtr _strings;
+    SQObjectPtr _name;
+    SQObjectPtr _sourcename;
+    SQInteger _nliterals;
+    SQLineInfoVec _lineinfos;
+    SQFuncState *_parent;
+    SQIntVec _scope_blocks;
+    SQIntVec _breaktargets;
+    SQIntVec _continuetargets;
+    SQIntVec _defaultparams;
+    SQInteger _lastline;
+    SQInteger _traps; //contains number of nested exception traps
+    SQInteger _outers;
+    bool _optimization;
+    SQSharedState *_sharedstate;
+    sqvector<SQFuncState*> _childstates;
+    SQInteger GetConstant(const SQObject &cons);
+private:
+    CompilerErrorFunc _errfunc;
+    void *_errtarget;
+    SQSharedState *_ss;
+};
+
+
+#endif //_SQFUNCSTATE_H_
+
diff --git a/engines/twp/squirrel/sqlexer.cpp b/engines/twp/squirrel/sqlexer.cpp
new file mode 100755
index 00000000000..f6a1ab645df
--- /dev/null
+++ b/engines/twp/squirrel/sqlexer.cpp
@@ -0,0 +1,564 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include "sqtable.h"
+#include "sqstring.h"
+#include "sqcompiler.h"
+#include "sqlexer.h"
+
+#define CUR_CHAR (_currdata)
+#define RETURN_TOKEN(t) { _prevtoken = _curtoken; _curtoken = t; return t;}
+#define IS_EOB() (CUR_CHAR <= SQUIRREL_EOB)
+#define NEXT() {Next();_currentcolumn++;}
+#define INIT_TEMP_STRING() { _longstr.resize(0);}
+#define APPEND_CHAR(c) { _longstr.push_back(c);}
+#define TERMINATE_BUFFER() {_longstr.push_back(_SC('\0'));}
+#define ADD_KEYWORD(key,id) _keywords->NewSlot( SQString::Create(ss, _SC(#key)) ,SQInteger(id))
+
+SQLexer::SQLexer(){}
+SQLexer::~SQLexer()
+{
+    _keywords->Release();
+}
+
+void SQLexer::Init(SQSharedState *ss, SQLEXREADFUNC rg, SQUserPointer up,CompilerErrorFunc efunc,void *ed)
+{
+    _errfunc = efunc;
+    _errtarget = ed;
+    _sharedstate = ss;
+    _keywords = SQTable::Create(ss, 37);
+    ADD_KEYWORD(while, TK_WHILE);
+    ADD_KEYWORD(do, TK_DO);
+    ADD_KEYWORD(if, TK_IF);
+    ADD_KEYWORD(else, TK_ELSE);
+    ADD_KEYWORD(break, TK_BREAK);
+    ADD_KEYWORD(continue, TK_CONTINUE);
+    ADD_KEYWORD(return, TK_RETURN);
+    ADD_KEYWORD(null, TK_NULL);
+    ADD_KEYWORD(function, TK_FUNCTION);
+    ADD_KEYWORD(script, TK_FUNCTION);
+    ADD_KEYWORD(local, TK_LOCAL);
+    ADD_KEYWORD(for, TK_FOR);
+    ADD_KEYWORD(foreach, TK_FOREACH);
+    ADD_KEYWORD(in, TK_IN);
+    ADD_KEYWORD(typeof, TK_TYPEOF);
+    ADD_KEYWORD(base, TK_BASE);
+    ADD_KEYWORD(delete, TK_DELETE);
+    ADD_KEYWORD(try, TK_TRY);
+    ADD_KEYWORD(catch, TK_CATCH);
+    ADD_KEYWORD(throw, TK_THROW);
+    ADD_KEYWORD(clone, TK_CLONE);
+    ADD_KEYWORD(yield, TK_YIELD);
+    ADD_KEYWORD(resume, TK_RESUME);
+    ADD_KEYWORD(switch, TK_SWITCH);
+    ADD_KEYWORD(case, TK_CASE);
+    ADD_KEYWORD(default, TK_DEFAULT);
+    ADD_KEYWORD(this, TK_THIS);
+    ADD_KEYWORD(class,TK_CLASS);
+    ADD_KEYWORD(extends,TK_EXTENDS);
+    ADD_KEYWORD(constructor,TK_CONSTRUCTOR);
+    ADD_KEYWORD(instanceof,TK_INSTANCEOF);
+    ADD_KEYWORD(true,TK_TRUE);
+    ADD_KEYWORD(false,TK_FALSE);
+    ADD_KEYWORD(static,TK_STATIC);
+    ADD_KEYWORD(enum,TK_ENUM);
+    ADD_KEYWORD(const,TK_CONST);
+    ADD_KEYWORD(__LINE__,TK___LINE__);
+    ADD_KEYWORD(__FILE__,TK___FILE__);
+    ADD_KEYWORD(rawcall, TK_RAWCALL);
+
+
+    _readf = rg;
+    _up = up;
+    _lasttokenline = _currentline = 1;
+    _currentcolumn = 0;
+    _prevtoken = -1;
+    _reached_eof = SQFalse;
+    Next();
+}
+
+void SQLexer::Error(const SQChar *err)
+{
+    _errfunc(_errtarget,err);
+}
+
+void SQLexer::Next()
+{
+    SQInteger t = _readf(_up);
+    if(t > MAX_CHAR) Error(_SC("Invalid character"));
+    if(t != 0) {
+        _currdata = (LexChar)t;
+        return;
+    }
+    _currdata = SQUIRREL_EOB;
+    _reached_eof = SQTrue;
+}
+
+const SQChar *SQLexer::Tok2Str(SQInteger tok)
+{
+    SQObjectPtr itr, key, val;
+    SQInteger nitr;
+    while((nitr = _keywords->Next(false,itr, key, val)) != -1) {
+        itr = (SQInteger)nitr;
+        if(((SQInteger)_integer(val)) == tok)
+            return _stringval(key);
+    }
+    return NULL;
+}
+
+void SQLexer::LexBlockComment()
+{
+    bool done = false;
+    while(!done) {
+        switch(CUR_CHAR) {
+            case _SC('*'): { NEXT(); if(CUR_CHAR == _SC('/')) { done = true; NEXT(); }}; continue;
+            case _SC('\n'): _currentline++; NEXT(); continue;
+            case SQUIRREL_EOB: Error(_SC("missing \"*/\" in comment"));
+            default: NEXT();
+        }
+    }
+}
+void SQLexer::LexLineComment()
+{
+    do { NEXT(); } while (CUR_CHAR != _SC('\n') && (!IS_EOB()));
+}
+
+SQInteger SQLexer::Lex()
+{
+    _lasttokenline = _currentline;
+    while(CUR_CHAR != SQUIRREL_EOB) {
+        switch(CUR_CHAR){
+        case _SC('\t'): case _SC('\r'): case _SC(' '): NEXT(); continue;
+        case _SC('\n'):
+            _currentline++;
+            _prevtoken=_curtoken;
+            _curtoken=_SC('\n');
+            NEXT();
+            _currentcolumn=1;
+            continue;
+        case _SC('#'): LexLineComment(); continue;
+        case _SC('/'):
+            NEXT();
+            switch(CUR_CHAR){
+            case _SC('*'):
+                NEXT();
+                LexBlockComment();
+                continue;
+            case _SC('/'):
+                LexLineComment();
+                continue;
+            case _SC('='):
+                NEXT();
+                RETURN_TOKEN(TK_DIVEQ);
+                continue;
+            case _SC('>'):
+                NEXT();
+                RETURN_TOKEN(TK_ATTR_CLOSE);
+                continue;
+            default:
+                RETURN_TOKEN('/');
+            }
+        case _SC('='):
+            NEXT();
+            if (CUR_CHAR != _SC('=')){ RETURN_TOKEN('=') }
+            else { NEXT(); RETURN_TOKEN(TK_EQ); }
+        case _SC('<'):
+            NEXT();
+            switch(CUR_CHAR) {
+            case _SC('='):
+                NEXT();
+                if(CUR_CHAR == _SC('>')) {
+                    NEXT();
+                    RETURN_TOKEN(TK_3WAYSCMP);
+                }
+                RETURN_TOKEN(TK_LE)
+                break;
+            case _SC('-'): NEXT(); RETURN_TOKEN(TK_NEWSLOT); break;
+            case _SC('<'): NEXT(); RETURN_TOKEN(TK_SHIFTL); break;
+            case _SC('/'): NEXT(); RETURN_TOKEN(TK_ATTR_OPEN); break;
+            }
+            RETURN_TOKEN('<');
+        case _SC('>'):
+            NEXT();
+            if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_GE);}
+            else if(CUR_CHAR == _SC('>')){
+                NEXT();
+                if(CUR_CHAR == _SC('>')){
+                    NEXT();
+                    RETURN_TOKEN(TK_USHIFTR);
+                }
+                RETURN_TOKEN(TK_SHIFTR);
+            }
+            else { RETURN_TOKEN('>') }
+        case _SC('!'):
+            NEXT();
+            if (CUR_CHAR != _SC('=')){ RETURN_TOKEN('!')}
+            else { NEXT(); RETURN_TOKEN(TK_NE); }
+        case _SC('@'): {
+            SQInteger stype;
+            NEXT();
+            if(CUR_CHAR != _SC('"')) {
+                RETURN_TOKEN('@');
+            }
+            if((stype=ReadString('"',true))!=-1) {
+                RETURN_TOKEN(stype);
+            }
+            Error(_SC("error parsing the string"));
+                       }
+        case _SC('"'):
+        case _SC('\''): {
+            SQInteger stype;
+            if((stype=ReadString(CUR_CHAR,false))!=-1){
+                RETURN_TOKEN(stype);
+            }
+            Error(_SC("error parsing the string"));
+            }
+        case _SC('{'): case _SC('}'): case _SC('('): case _SC(')'): case _SC('['): case _SC(']'):
+        case _SC(';'): case _SC(','): case _SC('?'): case _SC('^'): case _SC('~'):
+            {SQInteger ret = CUR_CHAR;
+            NEXT(); RETURN_TOKEN(ret); }
+        case _SC('.'):
+            NEXT();
+            if (CUR_CHAR != _SC('.')){ RETURN_TOKEN('.') }
+            NEXT();
+            if (CUR_CHAR != _SC('.')){ Error(_SC("invalid token '..'")); }
+            NEXT();
+            RETURN_TOKEN(TK_VARPARAMS);
+        case _SC('&'):
+            NEXT();
+            if (CUR_CHAR != _SC('&')){ RETURN_TOKEN('&') }
+            else { NEXT(); RETURN_TOKEN(TK_AND); }
+        case _SC('|'):
+            NEXT();
+            if (CUR_CHAR != _SC('|')){ RETURN_TOKEN('|') }
+            else { NEXT(); RETURN_TOKEN(TK_OR); }
+        case _SC(':'):
+            NEXT();
+            if (CUR_CHAR != _SC(':')){ RETURN_TOKEN(':') }
+            else { NEXT(); RETURN_TOKEN(TK_DOUBLE_COLON); }
+        case _SC('*'):
+            NEXT();
+            if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MULEQ);}
+            else RETURN_TOKEN('*');
+        case _SC('%'):
+            NEXT();
+            if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MODEQ);}
+            else RETURN_TOKEN('%');
+        case _SC('-'):
+            NEXT();
+            if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MINUSEQ);}
+            else if  (CUR_CHAR == _SC('-')){ NEXT(); RETURN_TOKEN(TK_MINUSMINUS);}
+            else RETURN_TOKEN('-');
+        case _SC('+'):
+            NEXT();
+            if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_PLUSEQ);}
+            else if (CUR_CHAR == _SC('+')){ NEXT(); RETURN_TOKEN(TK_PLUSPLUS);}
+            else RETURN_TOKEN('+');
+        case SQUIRREL_EOB:
+            return 0;
+        default:{
+                if (scisdigit(CUR_CHAR)) {
+                    SQInteger ret = ReadNumber();
+                    RETURN_TOKEN(ret);
+                }
+                else if (scisalpha(CUR_CHAR) || CUR_CHAR == _SC('_')) {
+                    SQInteger t = ReadID();
+                    RETURN_TOKEN(t);
+                }
+                else {
+                    SQInteger c = CUR_CHAR;
+                    if (sciscntrl((int)c)) Error(_SC("unexpected character(control)"));
+                    NEXT();
+                    RETURN_TOKEN(c);
+                }
+                RETURN_TOKEN(0);
+            }
+        }
+    }
+    return 0;
+}
+
+SQInteger SQLexer::GetIDType(const SQChar *s,SQInteger len)
+{
+    SQObjectPtr t;
+    if(_keywords->GetStr(s,len, t)) {
+        return SQInteger(_integer(t));
+    }
+    return TK_IDENTIFIER;
+}
+
+#ifdef SQUNICODE
+#if WCHAR_SIZE == 2
+SQInteger SQLexer::AddUTF16(SQUnsignedInteger ch)
+{
+    if (ch >= 0x10000)
+    {
+        SQUnsignedInteger code = (ch - 0x10000);
+        APPEND_CHAR((SQChar)(0xD800 | (code >> 10)));
+        APPEND_CHAR((SQChar)(0xDC00 | (code & 0x3FF)));
+        return 2;
+    }
+    else {
+        APPEND_CHAR((SQChar)ch);
+        return 1;
+    }
+}
+#endif
+#else
+SQInteger SQLexer::AddUTF8(SQUnsignedInteger ch)
+{
+    if (ch < 0x80) {
+        APPEND_CHAR((char)ch);
+        return 1;
+    }
+    if (ch < 0x800) {
+        APPEND_CHAR((SQChar)((ch >> 6) | 0xC0));
+        APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80));
+        return 2;
+    }
+    if (ch < 0x10000) {
+        APPEND_CHAR((SQChar)((ch >> 12) | 0xE0));
+        APPEND_CHAR((SQChar)(((ch >> 6) & 0x3F) | 0x80));
+        APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80));
+        return 3;
+    }
+    if (ch < 0x110000) {
+        APPEND_CHAR((SQChar)((ch >> 18) | 0xF0));
+        APPEND_CHAR((SQChar)(((ch >> 12) & 0x3F) | 0x80));
+        APPEND_CHAR((SQChar)(((ch >> 6) & 0x3F) | 0x80));
+        APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80));
+        return 4;
+    }
+    return 0;
+}
+#endif
+
+SQInteger SQLexer::ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits)
+{
+    NEXT();
+    if (!isxdigit(CUR_CHAR)) Error(_SC("hexadecimal number expected"));
+    SQInteger n = 0;
+    while (isxdigit(CUR_CHAR) && n < maxdigits) {
+        dest[n] = CUR_CHAR;
+        n++;
+        NEXT();
+    }
+    dest[n] = 0;
+    return n;
+}
+
+SQInteger SQLexer::ReadString(SQInteger ndelim,bool verbatim)
+{
+    INIT_TEMP_STRING();
+    NEXT();
+    if(IS_EOB()) return -1;
+    for(;;) {
+        while(CUR_CHAR != ndelim) {
+            SQInteger x = CUR_CHAR;
+            switch (x) {
+            case SQUIRREL_EOB:
+                Error(_SC("unfinished string"));
+                return -1;
+            case _SC('\n'):
+                if(!verbatim) Error(_SC("newline in a constant"));
+                APPEND_CHAR(CUR_CHAR); NEXT();
+                _currentline++;
+                break;
+            case _SC('\\'):
+                if(verbatim) {
+                    APPEND_CHAR('\\'); NEXT();
+                }
+                else {
+                    NEXT();
+                    switch(CUR_CHAR) {
+                    case _SC('x'):  {
+                        const SQInteger maxdigits = sizeof(SQChar) * 2;
+                        SQChar temp[maxdigits + 1];
+                        ProcessStringHexEscape(temp, maxdigits);
+                        SQChar *stemp;
+                        APPEND_CHAR((SQChar)scstrtoul(temp, &stemp, 16));
+                    }
+                    break;
+                    case _SC('U'):
+                    case _SC('u'):  {
+                        const SQInteger maxdigits = CUR_CHAR == 'u' ? 4 : 8;
+                        SQChar temp[8 + 1];
+                        ProcessStringHexEscape(temp, maxdigits);
+                        SQChar *stemp;
+#ifdef SQUNICODE
+#if WCHAR_SIZE == 2
+                        AddUTF16(scstrtoul(temp, &stemp, 16));
+#else
+                        APPEND_CHAR((SQChar)scstrtoul(temp, &stemp, 16));
+#endif
+#else
+                        AddUTF8(scstrtoul(temp, &stemp, 16));
+#endif
+                    }
+                    break;
+                    case _SC('t'): APPEND_CHAR(_SC('\t')); NEXT(); break;
+                    case _SC('a'): APPEND_CHAR(_SC('\a')); NEXT(); break;
+                    case _SC('b'): APPEND_CHAR(_SC('\b')); NEXT(); break;
+                    case _SC('n'): APPEND_CHAR(_SC('\n')); NEXT(); break;
+                    case _SC('r'): APPEND_CHAR(_SC('\r')); NEXT(); break;
+                    case _SC('v'): APPEND_CHAR(_SC('\v')); NEXT(); break;
+                    case _SC('f'): APPEND_CHAR(_SC('\f')); NEXT(); break;
+                    case _SC('0'): APPEND_CHAR(_SC('\0')); NEXT(); break;
+                    case _SC('\\'): APPEND_CHAR(_SC('\\')); NEXT(); break;
+                    case _SC('"'): APPEND_CHAR(_SC('"')); NEXT(); break;
+                    case _SC('\''): APPEND_CHAR(_SC('\'')); NEXT(); break;
+                    default:
+                        Error(_SC("unrecognised escaper char"));
+                    break;
+                    }
+                }
+                break;
+            default:
+                APPEND_CHAR(CUR_CHAR);
+                NEXT();
+            }
+        }
+        NEXT();
+        if(verbatim && CUR_CHAR == '"') { //double quotation
+            APPEND_CHAR(CUR_CHAR);
+            NEXT();
+        }
+        else {
+            break;
+        }
+    }
+    TERMINATE_BUFFER();
+    SQInteger len = _longstr.size()-1;
+    if(ndelim == _SC('\'')) {
+        if(len == 0) Error(_SC("empty constant"));
+        if(len > 1) Error(_SC("constant too long"));
+        _nvalue = _longstr[0];
+        return TK_INTEGER;
+    }
+    _svalue = &_longstr[0];
+    return TK_STRING_LITERAL;
+}
+
+void LexHexadecimal(const SQChar *s,SQUnsignedInteger *res)
+{
+    *res = 0;
+    while(*s != 0)
+    {
+        if(scisdigit(*s)) *res = (*res)*16+((*s++)-'0');
+        else if(scisxdigit(*s)) *res = (*res)*16+(toupper(*s++)-'A'+10);
+        else { assert(0); }
+    }
+}
+
+void LexInteger(const SQChar *s,SQUnsignedInteger *res)
+{
+    *res = 0;
+    while(*s != 0)
+    {
+        *res = (*res)*10+((*s++)-'0');
+    }
+}
+
+SQInteger scisodigit(SQInteger c) { return c >= _SC('0') && c <= _SC('7'); }
+
+void LexOctal(const SQChar *s,SQUnsignedInteger *res)
+{
+    *res = 0;
+    while(*s != 0)
+    {
+        if(scisodigit(*s)) *res = (*res)*8+((*s++)-'0');
+        else { assert(0); }
+    }
+}
+
+SQInteger isexponent(SQInteger c) { return c == 'e' || c=='E'; }
+
+
+#define MAX_HEX_DIGITS (sizeof(SQInteger)*2)
+SQInteger SQLexer::ReadNumber()
+{
+#define TINT 1
+#define TFLOAT 2
+#define THEX 3
+#define TSCIENTIFIC 4
+#define TOCTAL 5
+    SQInteger type = TINT, firstchar = CUR_CHAR;
+    SQChar *sTemp;
+    INIT_TEMP_STRING();
+    NEXT();
+    if(firstchar == _SC('0') && (toupper(CUR_CHAR) == _SC('X') || scisodigit(CUR_CHAR)) ) {
+        if(scisodigit(CUR_CHAR)) {
+            type = TOCTAL;
+            while(scisodigit(CUR_CHAR)) {
+                APPEND_CHAR(CUR_CHAR);
+                NEXT();
+            }
+            if(scisdigit(CUR_CHAR)) Error(_SC("invalid octal number"));
+        }
+        else {
+            NEXT();
+            type = THEX;
+            while(isxdigit(CUR_CHAR)) {
+                APPEND_CHAR(CUR_CHAR);
+                NEXT();
+            }
+            if(_longstr.size() > MAX_HEX_DIGITS) Error(_SC("too many digits for an Hex number"));
+        }
+    }
+    else {
+        APPEND_CHAR((int)firstchar);
+        while (CUR_CHAR == _SC('.') || scisdigit(CUR_CHAR) || isexponent(CUR_CHAR)) {
+            if(CUR_CHAR == _SC('.') || isexponent(CUR_CHAR)) type = TFLOAT;
+            if(isexponent(CUR_CHAR)) {
+                if(type != TFLOAT) Error(_SC("invalid numeric format"));
+                type = TSCIENTIFIC;
+                APPEND_CHAR(CUR_CHAR);
+                NEXT();
+                if(CUR_CHAR == '+' || CUR_CHAR == '-'){
+                    APPEND_CHAR(CUR_CHAR);
+                    NEXT();
+                }
+                if(!scisdigit(CUR_CHAR)) Error(_SC("exponent expected"));
+            }
+
+            APPEND_CHAR(CUR_CHAR);
+            NEXT();
+        }
+    }
+    TERMINATE_BUFFER();
+    switch(type) {
+    case TSCIENTIFIC:
+    case TFLOAT:
+        _fvalue = (SQFloat)scstrtod(&_longstr[0],&sTemp);
+        return TK_FLOAT;
+    case TINT:
+        LexInteger(&_longstr[0],(SQUnsignedInteger *)&_nvalue);
+        return TK_INTEGER;
+    case THEX:
+        LexHexadecimal(&_longstr[0],(SQUnsignedInteger *)&_nvalue);
+        return TK_INTEGER;
+    case TOCTAL:
+        LexOctal(&_longstr[0],(SQUnsignedInteger *)&_nvalue);
+        return TK_INTEGER;
+    }
+    return 0;
+}
+
+SQInteger SQLexer::ReadID()
+{
+    SQInteger res;
+    INIT_TEMP_STRING();
+    do {
+        APPEND_CHAR(CUR_CHAR);
+        NEXT();
+    } while(scisalnum(CUR_CHAR) || CUR_CHAR == _SC('_'));
+    TERMINATE_BUFFER();
+    res = GetIDType(&_longstr[0],_longstr.size() - 1);
+    if(res == TK_IDENTIFIER || res == TK_CONSTRUCTOR) {
+        _svalue = &_longstr[0];
+    }
+    return res;
+}
diff --git a/engines/twp/squirrel/sqlexer.h b/engines/twp/squirrel/sqlexer.h
new file mode 100755
index 00000000000..d731c20e479
--- /dev/null
+++ b/engines/twp/squirrel/sqlexer.h
@@ -0,0 +1,55 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQLEXER_H_
+#define _SQLEXER_H_
+
+#ifdef SQUNICODE
+typedef SQChar LexChar;
+#else
+typedef unsigned char LexChar;
+#endif
+
+struct SQLexer
+{
+    SQLexer();
+    ~SQLexer();
+    void Init(SQSharedState *ss,SQLEXREADFUNC rg,SQUserPointer up,CompilerErrorFunc efunc,void *ed);
+    void Error(const SQChar *err);
+    SQInteger Lex();
+    const SQChar *Tok2Str(SQInteger tok);
+private:
+    SQInteger GetIDType(const SQChar *s,SQInteger len);
+    SQInteger ReadString(SQInteger ndelim,bool verbatim);
+    SQInteger ReadNumber();
+    void LexBlockComment();
+    void LexLineComment();
+    SQInteger ReadID();
+    void Next();
+#ifdef SQUNICODE
+#if WCHAR_SIZE == 2
+    SQInteger AddUTF16(SQUnsignedInteger ch);
+#endif
+#else
+    SQInteger AddUTF8(SQUnsignedInteger ch);
+#endif
+    SQInteger ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits);
+    SQInteger _curtoken;
+    SQTable *_keywords;
+    SQBool _reached_eof;
+public:
+    SQInteger _prevtoken;
+    SQInteger _currentline;
+    SQInteger _lasttokenline;
+    SQInteger _currentcolumn;
+    const SQChar *_svalue;
+    SQInteger _nvalue;
+    SQFloat _fvalue;
+    SQLEXREADFUNC _readf;
+    SQUserPointer _up;
+    LexChar _currdata;
+    SQSharedState *_sharedstate;
+    sqvector<SQChar> _longstr;
+    CompilerErrorFunc _errfunc;
+    void *_errtarget;
+};
+
+#endif
diff --git a/engines/twp/squirrel/sqmem.cpp b/engines/twp/squirrel/sqmem.cpp
new file mode 100755
index 00000000000..378e254b48a
--- /dev/null
+++ b/engines/twp/squirrel/sqmem.cpp
@@ -0,0 +1,11 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#ifndef SQ_EXCLUDE_DEFAULT_MEMFUNCTIONS
+void *sq_vm_malloc(SQUnsignedInteger size){ return malloc(size); }
+
+void *sq_vm_realloc(void *p, SQUnsignedInteger SQ_UNUSED_ARG(oldsize), SQUnsignedInteger size){ return realloc(p, size); }
+
+void sq_vm_free(void *p, SQUnsignedInteger SQ_UNUSED_ARG(size)){ free(p); }
+#endif
diff --git a/engines/twp/squirrel/sqobject.cpp b/engines/twp/squirrel/sqobject.cpp
new file mode 100755
index 00000000000..7d7f297fcb0
--- /dev/null
+++ b/engines/twp/squirrel/sqobject.cpp
@@ -0,0 +1,677 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include "sqvm.h"
+#include "sqstring.h"
+#include "sqarray.h"
+#include "sqtable.h"
+#include "squserdata.h"
+#include "sqfuncproto.h"
+#include "sqclass.h"
+#include "sqclosure.h"
+
+
+const SQChar *IdType2Name(SQObjectType type)
+{
+    switch(_RAW_TYPE(type))
+    {
+    case _RT_NULL:return _SC("null");
+    case _RT_INTEGER:return _SC("integer");
+    case _RT_FLOAT:return _SC("float");
+    case _RT_BOOL:return _SC("bool");
+    case _RT_STRING:return _SC("string");
+    case _RT_TABLE:return _SC("table");
+    case _RT_ARRAY:return _SC("array");
+    case _RT_GENERATOR:return _SC("generator");
+    case _RT_CLOSURE:
+    case _RT_NATIVECLOSURE:
+        return _SC("function");
+    case _RT_USERDATA:
+    case _RT_USERPOINTER:
+        return _SC("userdata");
+    case _RT_THREAD: return _SC("thread");
+    case _RT_FUNCPROTO: return _SC("function");
+    case _RT_CLASS: return _SC("class");
+    case _RT_INSTANCE: return _SC("instance");
+    case _RT_WEAKREF: return _SC("weakref");
+    case _RT_OUTER: return _SC("outer");
+    default:
+        return NULL;
+    }
+}
+
+const SQChar *GetTypeName(const SQObjectPtr &obj1)
+{
+    return IdType2Name(sq_type(obj1));
+}
+
+SQString *SQString::Create(SQSharedState *ss,const SQChar *s,SQInteger len)
+{
+    SQString *str=ADD_STRING(ss,s,len);
+    return str;
+}
+
+void SQString::Release()
+{
+    REMOVE_STRING(_sharedstate,this);
+}
+
+SQInteger SQString::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval)
+{
+    SQInteger idx = (SQInteger)TranslateIndex(refpos);
+    while(idx < _len){
+        outkey = (SQInteger)idx;
+        outval = (SQInteger)((SQUnsignedInteger)_val[idx]);
+        //return idx for the next iteration
+        return ++idx;
+    }
+    //nothing to iterate anymore
+    return -1;
+}
+
+SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx)
+{
+    switch(sq_type(idx)){
+        case OT_NULL:
+            return 0;
+        case OT_INTEGER:
+            return (SQUnsignedInteger)_integer(idx);
+        default: assert(0); break;
+    }
+    return 0;
+}
+
+SQWeakRef *SQRefCounted::GetWeakRef(SQObjectType type)
+{
+    if(!_weakref) {
+        sq_new(_weakref,SQWeakRef);
+#if defined(SQUSEDOUBLE) && !defined(_SQ64)
+        _weakref->_obj._unVal.raw = 0; //clean the whole union on 32 bits with double
+#endif
+        _weakref->_obj._type = type;
+        _weakref->_obj._unVal.pRefCounted = this;
+    }
+    return _weakref;
+}
+
+SQRefCounted::~SQRefCounted()
+{
+    if(_weakref) {
+        _weakref->_obj._type = OT_NULL;
+        _weakref->_obj._unVal.pRefCounted = NULL;
+    }
+}
+
+void SQWeakRef::Release() {
+    if(ISREFCOUNTED(_obj._type)) {
+        _obj._unVal.pRefCounted->_weakref = NULL;
+    }
+    sq_delete(this,SQWeakRef);
+}
+
+bool SQDelegable::GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res) {
+    if(_delegate) {
+        return _delegate->Get((*_ss(v)->_metamethods)[mm],res);
+    }
+    return false;
+}
+
+bool SQDelegable::SetDelegate(SQTable *mt)
+{
+    SQTable *temp = mt;
+    if(temp == this) return false;
+    while (temp) {
+        if (temp->_delegate == this) return false; //cycle detected
+        temp = temp->_delegate;
+    }
+    if (mt) __ObjAddRef(mt);
+    __ObjRelease(_delegate);
+    _delegate = mt;
+    return true;
+}
+
+bool SQGenerator::Yield(SQVM *v,SQInteger target)
+{
+    if(_state==eSuspended) { v->Raise_Error(_SC("internal vm error, yielding dead generator"));  return false;}
+    if(_state==eDead) { v->Raise_Error(_SC("internal vm error, yielding a dead generator")); return false; }
+    SQInteger size = v->_top-v->_stackbase;
+
+    _stack.resize(size);
+    SQObject _this = v->_stack[v->_stackbase];
+    _stack._vals[0] = ISREFCOUNTED(sq_type(_this)) ? SQObjectPtr(_refcounted(_this)->GetWeakRef(sq_type(_this))) : _this;
+    for(SQInteger n =1; n<target; n++) {
+        _stack._vals[n] = v->_stack[v->_stackbase+n];
+    }
+    for(SQInteger j =0; j < size; j++)
+    {
+        v->_stack[v->_stackbase+j].Null();
+    }
+
+    _ci = *v->ci;
+    _ci._generator=NULL;
+    for(SQInteger i=0;i<_ci._etraps;i++) {
+        _etraps.push_back(v->_etraps.top());
+        v->_etraps.pop_back();
+        // store relative stack base and size in case of resume to other _top
+        SQExceptionTrap &et = _etraps.back();
+        et._stackbase -= v->_stackbase;
+        et._stacksize -= v->_stackbase;
+    }
+    _state=eSuspended;
+    return true;
+}
+
+bool SQGenerator::Resume(SQVM *v,SQObjectPtr &dest)
+{
+    if(_state==eDead){ v->Raise_Error(_SC("resuming dead generator")); return false; }
+    if(_state==eRunning){ v->Raise_Error(_SC("resuming active generator")); return false; }
+    SQInteger size = _stack.size();
+    SQInteger target = &dest - &(v->_stack._vals[v->_stackbase]);
+    assert(target>=0 && target<=255);
+    SQInteger newbase = v->_top;
+    if(!v->EnterFrame(v->_top, v->_top + size, false))
+        return false;
+    v->ci->_generator   = this;
+    v->ci->_target      = (SQInt32)target;
+    v->ci->_closure     = _ci._closure;
+    v->ci->_ip          = _ci._ip;
+    v->ci->_literals    = _ci._literals;
+    v->ci->_ncalls      = _ci._ncalls;
+    v->ci->_etraps      = _ci._etraps;
+    v->ci->_root        = _ci._root;
+
+
+    for(SQInteger i=0;i<_ci._etraps;i++) {
+        v->_etraps.push_back(_etraps.top());
+        _etraps.pop_back();
+        SQExceptionTrap &et = v->_etraps.back();
+        // restore absolute stack base and size
+        et._stackbase += newbase;
+        et._stacksize += newbase;
+    }
+    SQObject _this = _stack._vals[0];
+    v->_stack[v->_stackbase] = sq_type(_this) == OT_WEAKREF ? _weakref(_this)->_obj : _this;
+
+    for(SQInteger n = 1; n<size; n++) {
+        v->_stack[v->_stackbase+n] = _stack._vals[n];
+        _stack._vals[n].Null();
+    }
+
+    _state=eRunning;
+    if (v->_debughook)
+        v->CallDebugHook(_SC('c'));
+
+    return true;
+}
+
+void SQArray::Extend(const SQArray *a){
+    SQInteger xlen;
+    if((xlen=a->Size()))
+        for(SQInteger i=0;i<xlen;i++)
+            Append(a->_values[i]);
+}
+
+const SQChar* SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop)
+{
+    SQUnsignedInteger nvars=_nlocalvarinfos;
+    const SQChar *res=NULL;
+    if(nvars>=nseq){
+        for(SQUnsignedInteger i=0;i<nvars;i++){
+            if(_localvarinfos[i]._start_op<=nop && _localvarinfos[i]._end_op>=nop)
+            {
+                if(nseq==0){
+                    vm->Push(vm->_stack[stackbase+_localvarinfos[i]._pos]);
+                    res=_stringval(_localvarinfos[i]._name);
+                    break;
+                }
+                nseq--;
+            }
+        }
+    }
+    return res;
+}
+
+
+SQInteger SQFunctionProto::GetLine(SQInstruction *curr)
+{
+    SQInteger op = (SQInteger)(curr-_instructions);
+    SQInteger line=_lineinfos[0]._line;
+    SQInteger low = 0;
+    SQInteger high = _nlineinfos - 1;
+    SQInteger mid = 0;
+    while(low <= high)
+    {
+        mid = low + ((high - low) >> 1);
+        SQInteger curop = _lineinfos[mid]._op;
+        if(curop > op)
+        {
+            high = mid - 1;
+        }
+        else if(curop < op) {
+            if(mid < (_nlineinfos - 1)
+                && _lineinfos[mid + 1]._op >= op) {
+                break;
+            }
+            low = mid + 1;
+        }
+        else { //equal
+            break;
+        }
+    }
+
+    while(mid > 0 && _lineinfos[mid]._op >= op) mid--;
+
+    line = _lineinfos[mid]._line;
+
+    return line;
+}
+
+SQClosure::~SQClosure()
+{
+    __ObjRelease(_root);
+    __ObjRelease(_env);
+    __ObjRelease(_base);
+    REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
+}
+
+#define _CHECK_IO(exp)  { if(!exp)return false; }
+bool SafeWrite(HSQUIRRELVM v,SQWRITEFUNC write,SQUserPointer up,SQUserPointer dest,SQInteger size)
+{
+    if(write(up,dest,size) != size) {
+        v->Raise_Error(_SC("io error (write function failure)"));
+        return false;
+    }
+    return true;
+}
+
+bool SafeRead(HSQUIRRELVM v,SQREADFUNC read,SQUserPointer up,SQUserPointer dest,SQInteger size)
+{
+    if(size && read(up,dest,size) != size) {
+        v->Raise_Error(_SC("io error, read function failure, the origin stream could be corrupted/trucated"));
+        return false;
+    }
+    return true;
+}
+
+bool WriteTag(HSQUIRRELVM v,SQWRITEFUNC write,SQUserPointer up,SQUnsignedInteger32 tag)
+{
+    return SafeWrite(v,write,up,&tag,sizeof(tag));
+}
+
+bool CheckTag(HSQUIRRELVM v,SQREADFUNC read,SQUserPointer up,SQUnsignedInteger32 tag)
+{
+    SQUnsignedInteger32 t;
+    _CHECK_IO(SafeRead(v,read,up,&t,sizeof(t)));
+    if(t != tag){
+        v->Raise_Error(_SC("invalid or corrupted closure stream"));
+        return false;
+    }
+    return true;
+}
+
+bool WriteObject(HSQUIRRELVM v,SQUserPointer up,SQWRITEFUNC write,SQObjectPtr &o)
+{
+    SQUnsignedInteger32 _type = (SQUnsignedInteger32)sq_type(o);
+    _CHECK_IO(SafeWrite(v,write,up,&_type,sizeof(_type)));
+    switch(sq_type(o)){
+    case OT_STRING:
+        _CHECK_IO(SafeWrite(v,write,up,&_string(o)->_len,sizeof(SQInteger)));
+        _CHECK_IO(SafeWrite(v,write,up,_stringval(o),sq_rsl(_string(o)->_len)));
+        break;
+    case OT_BOOL:
+    case OT_INTEGER:
+        _CHECK_IO(SafeWrite(v,write,up,&_integer(o),sizeof(SQInteger)));break;
+    case OT_FLOAT:
+        _CHECK_IO(SafeWrite(v,write,up,&_float(o),sizeof(SQFloat)));break;
+    case OT_NULL:
+        break;
+    default:
+        v->Raise_Error(_SC("cannot serialize a %s"),GetTypeName(o));
+        return false;
+    }
+    return true;
+}
+
+bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o)
+{
+    SQUnsignedInteger32 _type;
+    _CHECK_IO(SafeRead(v,read,up,&_type,sizeof(_type)));
+    SQObjectType t = (SQObjectType)_type;
+    switch(t){
+    case OT_STRING:{
+        SQInteger len;
+        _CHECK_IO(SafeRead(v,read,up,&len,sizeof(SQInteger)));
+        _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(sq_rsl(len)),sq_rsl(len)));
+        o=SQString::Create(_ss(v),_ss(v)->GetScratchPad(-1),len);
+                   }
+        break;
+    case OT_INTEGER:{
+        SQInteger i;
+        _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o = i; break;
+                    }
+    case OT_BOOL:{
+        SQInteger i;
+        _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o._type = OT_BOOL; o._unVal.nInteger = i; break;
+                    }
+    case OT_FLOAT:{
+        SQFloat f;
+        _CHECK_IO(SafeRead(v,read,up,&f,sizeof(SQFloat))); o = f; break;
+                  }
+    case OT_NULL:
+        o.Null();
+        break;
+    default:
+        v->Raise_Error(_SC("cannot serialize a %s"),IdType2Name(t));
+        return false;
+    }
+    return true;
+}
+
+bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write)
+{
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_HEAD));
+    _CHECK_IO(WriteTag(v,write,up,sizeof(SQChar)));
+    _CHECK_IO(WriteTag(v,write,up,sizeof(SQInteger)));
+    _CHECK_IO(WriteTag(v,write,up,sizeof(SQFloat)));
+    _CHECK_IO(_function->Save(v,up,write));
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_TAIL));
+    return true;
+}
+
+bool SQClosure::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret)
+{
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_HEAD));
+    _CHECK_IO(CheckTag(v,read,up,sizeof(SQChar)));
+    _CHECK_IO(CheckTag(v,read,up,sizeof(SQInteger)));
+    _CHECK_IO(CheckTag(v,read,up,sizeof(SQFloat)));
+    SQObjectPtr func;
+    _CHECK_IO(SQFunctionProto::Load(v,up,read,func));
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_TAIL));
+    ret = SQClosure::Create(_ss(v),_funcproto(func),_table(v->_roottable)->GetWeakRef(OT_TABLE));
+    //FIXME: load an root for this closure
+    return true;
+}
+
+SQFunctionProto::SQFunctionProto(SQSharedState *ss)
+{
+    _stacksize=0;
+    _bgenerator=false;
+    INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);
+}
+
+SQFunctionProto::~SQFunctionProto()
+{
+    REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
+}
+
+bool SQFunctionProto::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write)
+{
+    SQInteger i,nliterals = _nliterals,nparameters = _nparameters;
+    SQInteger noutervalues = _noutervalues,nlocalvarinfos = _nlocalvarinfos;
+    SQInteger nlineinfos=_nlineinfos,ninstructions = _ninstructions,nfunctions=_nfunctions;
+    SQInteger ndefaultparams = _ndefaultparams;
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(WriteObject(v,up,write,_sourcename));
+    _CHECK_IO(WriteObject(v,up,write,_name));
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeWrite(v,write,up,&nliterals,sizeof(nliterals)));
+    _CHECK_IO(SafeWrite(v,write,up,&nparameters,sizeof(nparameters)));
+    _CHECK_IO(SafeWrite(v,write,up,&noutervalues,sizeof(noutervalues)));
+    _CHECK_IO(SafeWrite(v,write,up,&nlocalvarinfos,sizeof(nlocalvarinfos)));
+    _CHECK_IO(SafeWrite(v,write,up,&nlineinfos,sizeof(nlineinfos)));
+    _CHECK_IO(SafeWrite(v,write,up,&ndefaultparams,sizeof(ndefaultparams)));
+    _CHECK_IO(SafeWrite(v,write,up,&ninstructions,sizeof(ninstructions)));
+    _CHECK_IO(SafeWrite(v,write,up,&nfunctions,sizeof(nfunctions)));
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    for(i=0;i<nliterals;i++){
+        _CHECK_IO(WriteObject(v,up,write,_literals[i]));
+    }
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    for(i=0;i<nparameters;i++){
+        _CHECK_IO(WriteObject(v,up,write,_parameters[i]));
+    }
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    for(i=0;i<noutervalues;i++){
+        _CHECK_IO(SafeWrite(v,write,up,&_outervalues[i]._type,sizeof(SQUnsignedInteger)));
+        _CHECK_IO(WriteObject(v,up,write,_outervalues[i]._src));
+        _CHECK_IO(WriteObject(v,up,write,_outervalues[i]._name));
+    }
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    for(i=0;i<nlocalvarinfos;i++){
+        SQLocalVarInfo &lvi=_localvarinfos[i];
+        _CHECK_IO(WriteObject(v,up,write,lvi._name));
+        _CHECK_IO(SafeWrite(v,write,up,&lvi._pos,sizeof(SQUnsignedInteger)));
+        _CHECK_IO(SafeWrite(v,write,up,&lvi._start_op,sizeof(SQUnsignedInteger)));
+        _CHECK_IO(SafeWrite(v,write,up,&lvi._end_op,sizeof(SQUnsignedInteger)));
+    }
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeWrite(v,write,up,_lineinfos,sizeof(SQLineInfo)*nlineinfos));
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeWrite(v,write,up,_defaultparams,sizeof(SQInteger)*ndefaultparams));
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeWrite(v,write,up,_instructions,sizeof(SQInstruction)*ninstructions));
+
+    _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART));
+    for(i=0;i<nfunctions;i++){
+        _CHECK_IO(_funcproto(_functions[i])->Save(v,up,write));
+    }
+    _CHECK_IO(SafeWrite(v,write,up,&_stacksize,sizeof(_stacksize)));
+    _CHECK_IO(SafeWrite(v,write,up,&_bgenerator,sizeof(_bgenerator)));
+    _CHECK_IO(SafeWrite(v,write,up,&_varparams,sizeof(_varparams)));
+    return true;
+}
+
+bool SQFunctionProto::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret)
+{
+    SQInteger i, nliterals,nparameters;
+    SQInteger noutervalues ,nlocalvarinfos ;
+    SQInteger nlineinfos,ninstructions ,nfunctions,ndefaultparams ;
+    SQObjectPtr sourcename, name;
+    SQObjectPtr o;
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(ReadObject(v, up, read, sourcename));
+    _CHECK_IO(ReadObject(v, up, read, name));
+
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeRead(v,read,up, &nliterals, sizeof(nliterals)));
+    _CHECK_IO(SafeRead(v,read,up, &nparameters, sizeof(nparameters)));
+    _CHECK_IO(SafeRead(v,read,up, &noutervalues, sizeof(noutervalues)));
+    _CHECK_IO(SafeRead(v,read,up, &nlocalvarinfos, sizeof(nlocalvarinfos)));
+    _CHECK_IO(SafeRead(v,read,up, &nlineinfos, sizeof(nlineinfos)));
+    _CHECK_IO(SafeRead(v,read,up, &ndefaultparams, sizeof(ndefaultparams)));
+    _CHECK_IO(SafeRead(v,read,up, &ninstructions, sizeof(ninstructions)));
+    _CHECK_IO(SafeRead(v,read,up, &nfunctions, sizeof(nfunctions)));
+
+
+    SQFunctionProto *f = SQFunctionProto::Create(_opt_ss(v),ninstructions,nliterals,nparameters,
+            nfunctions,noutervalues,nlineinfos,nlocalvarinfos,ndefaultparams);
+    SQObjectPtr proto = f; //gets a ref in case of failure
+    f->_sourcename = sourcename;
+    f->_name = name;
+
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+
+    for(i = 0;i < nliterals; i++){
+        _CHECK_IO(ReadObject(v, up, read, o));
+        f->_literals[i] = o;
+    }
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+
+    for(i = 0; i < nparameters; i++){
+        _CHECK_IO(ReadObject(v, up, read, o));
+        f->_parameters[i] = o;
+    }
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+
+    for(i = 0; i < noutervalues; i++){
+        SQUnsignedInteger type;
+        SQObjectPtr name;
+        _CHECK_IO(SafeRead(v,read,up, &type, sizeof(SQUnsignedInteger)));
+        _CHECK_IO(ReadObject(v, up, read, o));
+        _CHECK_IO(ReadObject(v, up, read, name));
+        f->_outervalues[i] = SQOuterVar(name,o, (SQOuterType)type);
+    }
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+
+    for(i = 0; i < nlocalvarinfos; i++){
+        SQLocalVarInfo lvi;
+        _CHECK_IO(ReadObject(v, up, read, lvi._name));
+        _CHECK_IO(SafeRead(v,read,up, &lvi._pos, sizeof(SQUnsignedInteger)));
+        _CHECK_IO(SafeRead(v,read,up, &lvi._start_op, sizeof(SQUnsignedInteger)));
+        _CHECK_IO(SafeRead(v,read,up, &lvi._end_op, sizeof(SQUnsignedInteger)));
+        f->_localvarinfos[i] = lvi;
+    }
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeRead(v,read,up, f->_lineinfos, sizeof(SQLineInfo)*nlineinfos));
+
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeRead(v,read,up, f->_defaultparams, sizeof(SQInteger)*ndefaultparams));
+
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    _CHECK_IO(SafeRead(v,read,up, f->_instructions, sizeof(SQInstruction)*ninstructions));
+
+    _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
+    for(i = 0; i < nfunctions; i++){
+        _CHECK_IO(_funcproto(o)->Load(v, up, read, o));
+        f->_functions[i] = o;
+    }
+    _CHECK_IO(SafeRead(v,read,up, &f->_stacksize, sizeof(f->_stacksize)));
+    _CHECK_IO(SafeRead(v,read,up, &f->_bgenerator, sizeof(f->_bgenerator)));
+    _CHECK_IO(SafeRead(v,read,up, &f->_varparams, sizeof(f->_varparams)));
+
+    ret = f;
+    return true;
+}
+
+#ifndef NO_GARBAGE_COLLECTOR
+
+#define START_MARK()    if(!(_uiRef&MARK_FLAG)){ \
+        _uiRef|=MARK_FLAG;
+
+#define END_MARK() RemoveFromChain(&_sharedstate->_gc_chain, this); \
+        AddToChain(chain, this); }
+
+void SQVM::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        SQSharedState::MarkObject(_lasterror,chain);
+        SQSharedState::MarkObject(_errorhandler,chain);
+        SQSharedState::MarkObject(_debughook_closure,chain);
+        SQSharedState::MarkObject(_roottable, chain);
+        SQSharedState::MarkObject(temp_reg, chain);
+        for(SQUnsignedInteger i = 0; i < _stack.size(); i++) SQSharedState::MarkObject(_stack[i], chain);
+        for(SQInteger k = 0; k < _callsstacksize; k++) SQSharedState::MarkObject(_callsstack[k]._closure, chain);
+    END_MARK()
+}
+
+void SQArray::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        SQInteger len = _values.size();
+        for(SQInteger i = 0;i < len; i++) SQSharedState::MarkObject(_values[i], chain);
+    END_MARK()
+}
+void SQTable::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        if(_delegate) _delegate->Mark(chain);
+        SQInteger len = _numofnodes;
+        for(SQInteger i = 0; i < len; i++){
+            SQSharedState::MarkObject(_nodes[i].key, chain);
+            SQSharedState::MarkObject(_nodes[i].val, chain);
+        }
+    END_MARK()
+}
+
+void SQClass::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        _members->Mark(chain);
+        if(_base) _base->Mark(chain);
+        SQSharedState::MarkObject(_attributes, chain);
+        for(SQUnsignedInteger i =0; i< _defaultvalues.size(); i++) {
+            SQSharedState::MarkObject(_defaultvalues[i].val, chain);
+            SQSharedState::MarkObject(_defaultvalues[i].attrs, chain);
+        }
+        for(SQUnsignedInteger j =0; j< _methods.size(); j++) {
+            SQSharedState::MarkObject(_methods[j].val, chain);
+            SQSharedState::MarkObject(_methods[j].attrs, chain);
+        }
+        for(SQUnsignedInteger k =0; k< MT_LAST; k++) {
+            SQSharedState::MarkObject(_metamethods[k], chain);
+        }
+    END_MARK()
+}
+
+void SQInstance::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        _class->Mark(chain);
+        SQUnsignedInteger nvalues = _class->_defaultvalues.size();
+        for(SQUnsignedInteger i =0; i< nvalues; i++) {
+            SQSharedState::MarkObject(_values[i], chain);
+        }
+    END_MARK()
+}
+
+void SQGenerator::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        for(SQUnsignedInteger i = 0; i < _stack.size(); i++) SQSharedState::MarkObject(_stack[i], chain);
+        SQSharedState::MarkObject(_closure, chain);
+    END_MARK()
+}
+
+void SQFunctionProto::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        for(SQInteger i = 0; i < _nliterals; i++) SQSharedState::MarkObject(_literals[i], chain);
+        for(SQInteger k = 0; k < _nfunctions; k++) SQSharedState::MarkObject(_functions[k], chain);
+    END_MARK()
+}
+
+void SQClosure::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        if(_base) _base->Mark(chain);
+        SQFunctionProto *fp = _function;
+        fp->Mark(chain);
+        for(SQInteger i = 0; i < fp->_noutervalues; i++) SQSharedState::MarkObject(_outervalues[i], chain);
+        for(SQInteger k = 0; k < fp->_ndefaultparams; k++) SQSharedState::MarkObject(_defaultparams[k], chain);
+    END_MARK()
+}
+
+void SQNativeClosure::Mark(SQCollectable **chain)
+{
+    START_MARK()
+        for(SQUnsignedInteger i = 0; i < _noutervalues; i++) SQSharedState::MarkObject(_outervalues[i], chain);
+    END_MARK()
+}
+
+void SQOuter::Mark(SQCollectable **chain)
+{
+    START_MARK()
+    /* If the valptr points to a closed value, that value is alive */
+    if(_valptr == &_value) {
+      SQSharedState::MarkObject(_value, chain);
+    }
+    END_MARK()
+}
+
+void SQUserData::Mark(SQCollectable **chain){
+    START_MARK()
+        if(_delegate) _delegate->Mark(chain);
+    END_MARK()
+}
+
+void SQCollectable::UnMark() { _uiRef&=~MARK_FLAG; }
+
+#endif
+
diff --git a/engines/twp/squirrel/sqobject.h b/engines/twp/squirrel/sqobject.h
new file mode 100755
index 00000000000..313de69622e
--- /dev/null
+++ b/engines/twp/squirrel/sqobject.h
@@ -0,0 +1,353 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQOBJECT_H_
+#define _SQOBJECT_H_
+
+#include "squtils.h"
+
+#ifdef _SQ64
+#define UINT_MINUS_ONE (0xFFFFFFFFFFFFFFFF)
+#else
+#define UINT_MINUS_ONE (0xFFFFFFFF)
+#endif
+
+#define SQ_CLOSURESTREAM_HEAD (('S'<<24)|('Q'<<16)|('I'<<8)|('R'))
+#define SQ_CLOSURESTREAM_PART (('P'<<24)|('A'<<16)|('R'<<8)|('T'))
+#define SQ_CLOSURESTREAM_TAIL (('T'<<24)|('A'<<16)|('I'<<8)|('L'))
+
+struct SQSharedState;
+
+enum SQMetaMethod{
+    MT_ADD=0,
+    MT_SUB=1,
+    MT_MUL=2,
+    MT_DIV=3,
+    MT_UNM=4,
+    MT_MODULO=5,
+    MT_SET=6,
+    MT_GET=7,
+    MT_TYPEOF=8,
+    MT_NEXTI=9,
+    MT_CMP=10,
+    MT_CALL=11,
+    MT_CLONED=12,
+    MT_NEWSLOT=13,
+    MT_DELSLOT=14,
+    MT_TOSTRING=15,
+    MT_NEWMEMBER=16,
+    MT_INHERITED=17,
+    MT_LAST = 18
+};
+
+#define MM_ADD      _SC("_add")
+#define MM_SUB      _SC("_sub")
+#define MM_MUL      _SC("_mul")
+#define MM_DIV      _SC("_div")
+#define MM_UNM      _SC("_unm")
+#define MM_MODULO   _SC("_modulo")
+#define MM_SET      _SC("_set")
+#define MM_GET      _SC("_get")
+#define MM_TYPEOF   _SC("_typeof")
+#define MM_NEXTI    _SC("_nexti")
+#define MM_CMP      _SC("_cmp")
+#define MM_CALL     _SC("_call")
+#define MM_CLONED   _SC("_cloned")
+#define MM_NEWSLOT  _SC("_newslot")
+#define MM_DELSLOT  _SC("_delslot")
+#define MM_TOSTRING _SC("_tostring")
+#define MM_NEWMEMBER _SC("_newmember")
+#define MM_INHERITED _SC("_inherited")
+
+
+#define _CONSTRUCT_VECTOR(type,size,ptr) { \
+    for(SQInteger n = 0; n < ((SQInteger)size); n++) { \
+            new (&ptr[n]) type(); \
+        } \
+}
+
+#define _DESTRUCT_VECTOR(type,size,ptr) { \
+    for(SQInteger nl = 0; nl < ((SQInteger)size); nl++) { \
+            ptr[nl].~type(); \
+    } \
+}
+
+#define _COPY_VECTOR(dest,src,size) { \
+    for(SQInteger _n_ = 0; _n_ < ((SQInteger)size); _n_++) { \
+        dest[_n_] = src[_n_]; \
+    } \
+}
+
+#define _NULL_SQOBJECT_VECTOR(vec,size) { \
+    for(SQInteger _n_ = 0; _n_ < ((SQInteger)size); _n_++) { \
+        vec[_n_].Null(); \
+    } \
+}
+
+#define MINPOWER2 4
+
+struct SQRefCounted
+{
+    SQUnsignedInteger _uiRef;
+    struct SQWeakRef *_weakref;
+    SQRefCounted() { _uiRef = 0; _weakref = NULL; }
+    virtual ~SQRefCounted();
+    SQWeakRef *GetWeakRef(SQObjectType type);
+    virtual void Release()=0;
+
+};
+
+struct SQWeakRef : SQRefCounted
+{
+    void Release();
+    SQObject _obj;
+};
+
+#define _realval(o) (sq_type((o)) != OT_WEAKREF?(SQObject)o:_weakref(o)->_obj)
+
+struct SQObjectPtr;
+
+#define __AddRef(type,unval) if(ISREFCOUNTED(type)) \
+        { \
+            unval.pRefCounted->_uiRef++; \
+        }
+
+#define __Release(type,unval) if(ISREFCOUNTED(type) && ((--unval.pRefCounted->_uiRef)==0))  \
+        {   \
+            unval.pRefCounted->Release();   \
+        }
+
+#define __ObjRelease(obj) { \
+    if((obj)) { \
+        (obj)->_uiRef--; \
+        if((obj)->_uiRef == 0) \
+            (obj)->Release(); \
+        (obj) = NULL;   \
+    } \
+}
+
+#define __ObjAddRef(obj) { \
+    (obj)->_uiRef++; \
+}
+
+#define is_delegable(t) (sq_type(t)&SQOBJECT_DELEGABLE)
+#define raw_type(obj) _RAW_TYPE((obj)._type)
+
+#define _integer(obj) ((obj)._unVal.nInteger)
+#define _float(obj) ((obj)._unVal.fFloat)
+#define _string(obj) ((obj)._unVal.pString)
+#define _table(obj) ((obj)._unVal.pTable)
+#define _array(obj) ((obj)._unVal.pArray)
+#define _closure(obj) ((obj)._unVal.pClosure)
+#define _generator(obj) ((obj)._unVal.pGenerator)
+#define _nativeclosure(obj) ((obj)._unVal.pNativeClosure)
+#define _userdata(obj) ((obj)._unVal.pUserData)
+#define _userpointer(obj) ((obj)._unVal.pUserPointer)
+#define _thread(obj) ((obj)._unVal.pThread)
+#define _funcproto(obj) ((obj)._unVal.pFunctionProto)
+#define _class(obj) ((obj)._unVal.pClass)
+#define _instance(obj) ((obj)._unVal.pInstance)
+#define _delegable(obj) ((SQDelegable *)(obj)._unVal.pDelegable)
+#define _weakref(obj) ((obj)._unVal.pWeakRef)
+#define _outer(obj) ((obj)._unVal.pOuter)
+#define _refcounted(obj) ((obj)._unVal.pRefCounted)
+#define _rawval(obj) ((obj)._unVal.raw)
+
+#define _stringval(obj) (obj)._unVal.pString->_val
+#define _userdataval(obj) ((SQUserPointer)sq_aligning((obj)._unVal.pUserData + 1))
+
+#define tofloat(num) ((sq_type(num)==OT_FLOAT)?_float(num):(SQFloat)_integer(num))
+#define tointeger(num) ((sq_type(num)==OT_FLOAT)?(SQInteger)_float(num):_integer(num))
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+#if defined(SQUSEDOUBLE) && !defined(_SQ64) || !defined(SQUSEDOUBLE) && defined(_SQ64)
+#define SQ_REFOBJECT_INIT() SQ_OBJECT_RAWINIT()
+#else
+#define SQ_REFOBJECT_INIT()
+#endif
+
+#define _REF_TYPE_DECL(type,_class,sym) \
+    SQObjectPtr(_class * x) \
+    { \
+        SQ_OBJECT_RAWINIT() \
+        _type=type; \
+        _unVal.sym = x; \
+        assert(_unVal.pTable); \
+        _unVal.pRefCounted->_uiRef++; \
+    } \
+    inline SQObjectPtr& operator=(_class *x) \
+    {  \
+        SQObjectType tOldType; \
+        SQObjectValue unOldVal; \
+        tOldType=_type; \
+        unOldVal=_unVal; \
+        _type = type; \
+        SQ_REFOBJECT_INIT() \
+        _unVal.sym = x; \
+        _unVal.pRefCounted->_uiRef++; \
+        __Release(tOldType,unOldVal); \
+        return *this; \
+    }
+
+#define _SCALAR_TYPE_DECL(type,_class,sym) \
+    SQObjectPtr(_class x) \
+    { \
+        SQ_OBJECT_RAWINIT() \
+        _type=type; \
+        _unVal.sym = x; \
+    } \
+    inline SQObjectPtr& operator=(_class x) \
+    {  \
+        __Release(_type,_unVal); \
+        _type = type; \
+        SQ_OBJECT_RAWINIT() \
+        _unVal.sym = x; \
+        return *this; \
+    }
+struct SQObjectPtr : public SQObject
+{
+    SQObjectPtr()
+    {
+        SQ_OBJECT_RAWINIT()
+        _type=OT_NULL;
+        _unVal.pUserPointer=NULL;
+    }
+    SQObjectPtr(const SQObjectPtr &o)
+    {
+        _type = o._type;
+        _unVal = o._unVal;
+        __AddRef(_type,_unVal);
+    }
+    SQObjectPtr(const SQObject &o)
+    {
+        _type = o._type;
+        _unVal = o._unVal;
+        __AddRef(_type,_unVal);
+    }
+    _REF_TYPE_DECL(OT_TABLE,SQTable,pTable)
+    _REF_TYPE_DECL(OT_CLASS,SQClass,pClass)
+    _REF_TYPE_DECL(OT_INSTANCE,SQInstance,pInstance)
+    _REF_TYPE_DECL(OT_ARRAY,SQArray,pArray)
+    _REF_TYPE_DECL(OT_CLOSURE,SQClosure,pClosure)
+    _REF_TYPE_DECL(OT_NATIVECLOSURE,SQNativeClosure,pNativeClosure)
+    _REF_TYPE_DECL(OT_OUTER,SQOuter,pOuter)
+    _REF_TYPE_DECL(OT_GENERATOR,SQGenerator,pGenerator)
+    _REF_TYPE_DECL(OT_STRING,SQString,pString)
+    _REF_TYPE_DECL(OT_USERDATA,SQUserData,pUserData)
+    _REF_TYPE_DECL(OT_WEAKREF,SQWeakRef,pWeakRef)
+    _REF_TYPE_DECL(OT_THREAD,SQVM,pThread)
+    _REF_TYPE_DECL(OT_FUNCPROTO,SQFunctionProto,pFunctionProto)
+
+    _SCALAR_TYPE_DECL(OT_INTEGER,SQInteger,nInteger)
+    _SCALAR_TYPE_DECL(OT_FLOAT,SQFloat,fFloat)
+    _SCALAR_TYPE_DECL(OT_USERPOINTER,SQUserPointer,pUserPointer)
+
+    SQObjectPtr(bool bBool)
+    {
+        SQ_OBJECT_RAWINIT()
+        _type = OT_BOOL;
+        _unVal.nInteger = bBool?1:0;
+    }
+    inline SQObjectPtr& operator=(bool b)
+    {
+        __Release(_type,_unVal);
+        SQ_OBJECT_RAWINIT()
+        _type = OT_BOOL;
+        _unVal.nInteger = b?1:0;
+        return *this;
+    }
+
+    ~SQObjectPtr()
+    {
+        __Release(_type,_unVal);
+    }
+
+    inline SQObjectPtr& operator=(const SQObjectPtr& obj)
+    {
+        SQObjectType tOldType;
+        SQObjectValue unOldVal;
+        tOldType=_type;
+        unOldVal=_unVal;
+        _unVal = obj._unVal;
+        _type = obj._type;
+        __AddRef(_type,_unVal);
+        __Release(tOldType,unOldVal);
+        return *this;
+    }
+    inline SQObjectPtr& operator=(const SQObject& obj)
+    {
+        SQObjectType tOldType;
+        SQObjectValue unOldVal;
+        tOldType=_type;
+        unOldVal=_unVal;
+        _unVal = obj._unVal;
+        _type = obj._type;
+        __AddRef(_type,_unVal);
+        __Release(tOldType,unOldVal);
+        return *this;
+    }
+    inline void Null()
+    {
+        SQObjectType tOldType = _type;
+        SQObjectValue unOldVal = _unVal;
+        _type = OT_NULL;
+        _unVal.raw = (SQRawObjectVal)NULL;
+        __Release(tOldType ,unOldVal);
+    }
+    private:
+        SQObjectPtr(const SQChar *){} //safety
+};
+
+
+inline void _Swap(SQObject &a,SQObject &b)
+{
+    SQObjectType tOldType = a._type;
+    SQObjectValue unOldVal = a._unVal;
+    a._type = b._type;
+    a._unVal = b._unVal;
+    b._type = tOldType;
+    b._unVal = unOldVal;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+#ifndef NO_GARBAGE_COLLECTOR
+#define MARK_FLAG 0x80000000
+struct SQCollectable : public SQRefCounted {
+    SQCollectable *_next;
+    SQCollectable *_prev;
+    SQSharedState *_sharedstate;
+    virtual SQObjectType GetType()=0;
+    virtual void Release()=0;
+    virtual void Mark(SQCollectable **chain)=0;
+    void UnMark();
+    virtual void Finalize()=0;
+    static void AddToChain(SQCollectable **chain,SQCollectable *c);
+    static void RemoveFromChain(SQCollectable **chain,SQCollectable *c);
+};
+
+
+#define ADD_TO_CHAIN(chain,obj) AddToChain(chain,obj)
+#define REMOVE_FROM_CHAIN(chain,obj) {if(!(_uiRef&MARK_FLAG))RemoveFromChain(chain,obj);}
+#define CHAINABLE_OBJ SQCollectable
+#define INIT_CHAIN() {_next=NULL;_prev=NULL;_sharedstate=ss;}
+#else
+
+#define ADD_TO_CHAIN(chain,obj) ((void)0)
+#define REMOVE_FROM_CHAIN(chain,obj) ((void)0)
+#define CHAINABLE_OBJ SQRefCounted
+#define INIT_CHAIN() ((void)0)
+#endif
+
+struct SQDelegable : public CHAINABLE_OBJ {
+    bool SetDelegate(SQTable *m);
+    virtual bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res);
+    SQTable *_delegate;
+};
+
+SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx);
+typedef sqvector<SQObjectPtr> SQObjectPtrVec;
+typedef sqvector<SQInteger> SQIntVec;
+const SQChar *GetTypeName(const SQObjectPtr &obj1);
+const SQChar *IdType2Name(SQObjectType type);
+
+
+
+#endif //_SQOBJECT_H_
diff --git a/engines/twp/squirrel/sqopcodes.h b/engines/twp/squirrel/sqopcodes.h
new file mode 100755
index 00000000000..15d80e4bb05
--- /dev/null
+++ b/engines/twp/squirrel/sqopcodes.h
@@ -0,0 +1,132 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQOPCODES_H_
+#define _SQOPCODES_H_
+
+#define MAX_FUNC_STACKSIZE 0xFF
+#define MAX_LITERALS ((SQInteger)0x7FFFFFFF)
+
+enum BitWiseOP {
+    BW_AND = 0,
+    BW_OR = 2,
+    BW_XOR = 3,
+    BW_SHIFTL = 4,
+    BW_SHIFTR = 5,
+    BW_USHIFTR = 6
+};
+
+enum CmpOP {
+    CMP_G = 0,
+    CMP_GE = 2,
+    CMP_L = 3,
+    CMP_LE = 4,
+    CMP_3W = 5
+};
+
+enum NewObjectType {
+    NOT_TABLE = 0,
+    NOT_ARRAY = 1,
+    NOT_CLASS = 2
+};
+
+enum AppendArrayType {
+    AAT_STACK = 0,
+    AAT_LITERAL = 1,
+    AAT_INT = 2,
+    AAT_FLOAT = 3,
+    AAT_BOOL = 4
+};
+
+enum SQOpcode
+{
+    _OP_LINE=               0x00,
+    _OP_LOAD=               0x01,
+    _OP_LOADINT=            0x02,
+    _OP_LOADFLOAT=          0x03,
+    _OP_DLOAD=              0x04,
+    _OP_TAILCALL=           0x05,
+    _OP_CALL=               0x06,
+    _OP_PREPCALL=           0x07,
+    _OP_PREPCALLK=          0x08,
+    _OP_GETK=               0x09,
+    _OP_MOVE=               0x0A,
+    _OP_NEWSLOT=            0x0B,
+    _OP_DELETE=             0x0C,
+    _OP_SET=                0x0D,
+    _OP_GET=                0x0E,
+    _OP_EQ=                 0x0F,
+    _OP_NE=                 0x10,
+    _OP_ADD=                0x11,
+    _OP_SUB=                0x12,
+    _OP_MUL=                0x13,
+    _OP_DIV=                0x14,
+    _OP_MOD=                0x15,
+    _OP_BITW=               0x16,
+    _OP_RETURN=             0x17,
+    _OP_LOADNULLS=          0x18,
+    _OP_LOADROOT=           0x19,
+    _OP_LOADBOOL=           0x1A,
+    _OP_DMOVE=              0x1B,
+    _OP_JMP=                0x1C,
+    //_OP_JNZ=              0x1D,
+    _OP_JCMP=               0x1D,
+    _OP_JZ=                 0x1E,
+    _OP_SETOUTER=           0x1F,
+    _OP_GETOUTER=           0x20,
+    _OP_NEWOBJ=             0x21,
+    _OP_APPENDARRAY=        0x22,
+    _OP_COMPARITH=          0x23,
+    _OP_INC=                0x24,
+    _OP_INCL=               0x25,
+    _OP_PINC=               0x26,
+    _OP_PINCL=              0x27,
+    _OP_CMP=                0x28,
+    _OP_EXISTS=             0x29,
+    _OP_INSTANCEOF=         0x2A,
+    _OP_AND=                0x2B,
+    _OP_OR=                 0x2C,
+    _OP_NEG=                0x2D,
+    _OP_NOT=                0x2E,
+    _OP_BWNOT=              0x2F,
+    _OP_CLOSURE=            0x30,
+    _OP_YIELD=              0x31,
+    _OP_RESUME=             0x32,
+    _OP_FOREACH=            0x33,
+    _OP_POSTFOREACH=        0x34,
+    _OP_CLONE=              0x35,
+    _OP_TYPEOF=             0x36,
+    _OP_PUSHTRAP=           0x37,
+    _OP_POPTRAP=            0x38,
+    _OP_THROW=              0x39,
+    _OP_NEWSLOTA=           0x3A,
+    _OP_GETBASE=            0x3B,
+    _OP_CLOSE=              0x3C
+};
+
+struct SQInstructionDesc {
+    const SQChar *name;
+};
+
+struct SQInstruction
+{
+    SQInstruction(){};
+    SQInstruction(SQOpcode _op,SQInteger a0=0,SQInteger a1=0,SQInteger a2=0,SQInteger a3=0)
+    {   op = (unsigned char)_op;
+        _arg0 = (unsigned char)a0;_arg1 = (SQInt32)a1;
+        _arg2 = (unsigned char)a2;_arg3 = (unsigned char)a3;
+    }
+
+
+    SQInt32 _arg1;
+    unsigned char op;
+    unsigned char _arg0;
+    unsigned char _arg2;
+    unsigned char _arg3;
+};
+
+#include "squtils.h"
+typedef sqvector<SQInstruction> SQInstructionVec;
+
+#define NEW_SLOT_ATTRIBUTES_FLAG    0x01
+#define NEW_SLOT_STATIC_FLAG        0x02
+
+#endif // _SQOPCODES_H_
diff --git a/engines/twp/squirrel/sqpcheader.h b/engines/twp/squirrel/sqpcheader.h
new file mode 100755
index 00000000000..32a07090a92
--- /dev/null
+++ b/engines/twp/squirrel/sqpcheader.h
@@ -0,0 +1,20 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQPCHEADER_H_
+#define _SQPCHEADER_H_
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+#include <crtdbg.h>
+#endif
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <new>
+//squirrel stuff
+#include "squirrel.h"
+#include "sqobject.h"
+#include "sqstate.h"
+
+#endif //_SQPCHEADER_H_
diff --git a/engines/twp/squirrel/sqstate.cpp b/engines/twp/squirrel/sqstate.cpp
new file mode 100755
index 00000000000..c89bdc4a254
--- /dev/null
+++ b/engines/twp/squirrel/sqstate.cpp
@@ -0,0 +1,647 @@
+/*
+    see copyright notice in squirrel.h
+*/
+#include "sqpcheader.h"
+#include "sqopcodes.h"
+#include "sqvm.h"
+#include "sqfuncproto.h"
+#include "sqclosure.h"
+#include "sqstring.h"
+#include "sqtable.h"
+#include "sqarray.h"
+#include "squserdata.h"
+#include "sqclass.h"
+
+SQSharedState::SQSharedState()
+{
+    _compilererrorhandler = NULL;
+    _printfunc = NULL;
+    _errorfunc = NULL;
+    _debuginfo = false;
+    _notifyallexceptions = false;
+    _foreignptr = NULL;
+    _releasehook = NULL;
+}
+
+#define newsysstring(s) {   \
+    _systemstrings->push_back(SQString::Create(this,s));    \
+    }
+
+#define newmetamethod(s) {  \
+    _metamethods->push_back(SQString::Create(this,s));  \
+    _table(_metamethodsmap)->NewSlot(_metamethods->back(),(SQInteger)(_metamethods->size()-1)); \
+    }
+
+bool CompileTypemask(SQIntVec &res,const SQChar *typemask)
+{
+    SQInteger i = 0;
+    SQInteger mask = 0;
+    while(typemask[i] != 0) {
+        switch(typemask[i]) {
+            case 'o': mask |= _RT_NULL; break;
+            case 'i': mask |= _RT_INTEGER; break;
+            case 'f': mask |= _RT_FLOAT; break;
+            case 'n': mask |= (_RT_FLOAT | _RT_INTEGER); break;
+            case 's': mask |= _RT_STRING; break;
+            case 't': mask |= _RT_TABLE; break;
+            case 'a': mask |= _RT_ARRAY; break;
+            case 'u': mask |= _RT_USERDATA; break;
+            case 'c': mask |= (_RT_CLOSURE | _RT_NATIVECLOSURE); break;
+            case 'b': mask |= _RT_BOOL; break;
+            case 'g': mask |= _RT_GENERATOR; break;
+            case 'p': mask |= _RT_USERPOINTER; break;
+            case 'v': mask |= _RT_THREAD; break;
+            case 'x': mask |= _RT_INSTANCE; break;
+            case 'y': mask |= _RT_CLASS; break;
+            case 'r': mask |= _RT_WEAKREF; break;
+            case '.': mask = -1; res.push_back(mask); i++; mask = 0; continue;
+            case ' ': i++; continue; //ignores spaces
+            default:
+                return false;
+        }
+        i++;
+        if(typemask[i] == '|') {
+            i++;
+            if(typemask[i] == 0)
+                return false;
+            continue;
+        }
+        res.push_back(mask);
+        mask = 0;
+
+    }
+    return true;
+}
+
+SQTable *CreateDefaultDelegate(SQSharedState *ss,const SQRegFunction *funcz)
+{
+    SQInteger i=0;
+    SQTable *t=SQTable::Create(ss,0);
+    while(funcz[i].name!=0){
+        SQNativeClosure *nc = SQNativeClosure::Create(ss,funcz[i].f,0);
+        nc->_nparamscheck = funcz[i].nparamscheck;
+        nc->_name = SQString::Create(ss,funcz[i].name);
+        if(funcz[i].typemask && !CompileTypemask(nc->_typecheck,funcz[i].typemask))
+            return NULL;
+        t->NewSlot(SQString::Create(ss,funcz[i].name),nc);
+        i++;
+    }
+    return t;
+}
+
+void SQSharedState::Init()
+{
+    _scratchpad=NULL;
+    _scratchpadsize=0;
+#ifndef NO_GARBAGE_COLLECTOR
+    _gc_chain=NULL;
+#endif
+    _stringtable = (SQStringTable*)SQ_MALLOC(sizeof(SQStringTable));
+    new (_stringtable) SQStringTable(this);
+    sq_new(_metamethods,SQObjectPtrVec);
+    sq_new(_systemstrings,SQObjectPtrVec);
+    sq_new(_types,SQObjectPtrVec);
+    _metamethodsmap = SQTable::Create(this,MT_LAST-1);
+    //adding type strings to avoid memory trashing
+    //types names
+    newsysstring(_SC("null"));
+    newsysstring(_SC("table"));
+    newsysstring(_SC("array"));
+    newsysstring(_SC("closure"));
+    newsysstring(_SC("string"));
+    newsysstring(_SC("userdata"));
+    newsysstring(_SC("integer"));
+    newsysstring(_SC("float"));
+    newsysstring(_SC("userpointer"));
+    newsysstring(_SC("function"));
+    newsysstring(_SC("generator"));
+    newsysstring(_SC("thread"));
+    newsysstring(_SC("class"));
+    newsysstring(_SC("instance"));
+    newsysstring(_SC("bool"));
+    //meta methods
+    newmetamethod(MM_ADD);
+    newmetamethod(MM_SUB);
+    newmetamethod(MM_MUL);
+    newmetamethod(MM_DIV);
+    newmetamethod(MM_UNM);
+    newmetamethod(MM_MODULO);
+    newmetamethod(MM_SET);
+    newmetamethod(MM_GET);
+    newmetamethod(MM_TYPEOF);
+    newmetamethod(MM_NEXTI);
+    newmetamethod(MM_CMP);
+    newmetamethod(MM_CALL);
+    newmetamethod(MM_CLONED);
+    newmetamethod(MM_NEWSLOT);
+    newmetamethod(MM_DELSLOT);
+    newmetamethod(MM_TOSTRING);
+    newmetamethod(MM_NEWMEMBER);
+    newmetamethod(MM_INHERITED);
+
+    _constructoridx = SQString::Create(this,_SC("constructor"));
+    _registry = SQTable::Create(this,0);
+    _consts = SQTable::Create(this,0);
+    _table_default_delegate = CreateDefaultDelegate(this,_table_default_delegate_funcz);
+    _array_default_delegate = CreateDefaultDelegate(this,_array_default_delegate_funcz);
+    _string_default_delegate = CreateDefaultDelegate(this,_string_default_delegate_funcz);
+    _number_default_delegate = CreateDefaultDelegate(this,_number_default_delegate_funcz);
+    _closure_default_delegate = CreateDefaultDelegate(this,_closure_default_delegate_funcz);
+    _generator_default_delegate = CreateDefaultDelegate(this,_generator_default_delegate_funcz);
+    _thread_default_delegate = CreateDefaultDelegate(this,_thread_default_delegate_funcz);
+    _class_default_delegate = CreateDefaultDelegate(this,_class_default_delegate_funcz);
+    _instance_default_delegate = CreateDefaultDelegate(this,_instance_default_delegate_funcz);
+    _weakref_default_delegate = CreateDefaultDelegate(this,_weakref_default_delegate_funcz);
+}
+
+SQSharedState::~SQSharedState()
+{
+    if(_releasehook) { _releasehook(_foreignptr,0); _releasehook = NULL; }
+    _constructoridx.Null();
+    _table(_registry)->Finalize();
+    _table(_consts)->Finalize();
+    _table(_metamethodsmap)->Finalize();
+    _registry.Null();
+    _consts.Null();
+    _metamethodsmap.Null();
+    while(!_systemstrings->empty()) {
+        _systemstrings->back().Null();
+        _systemstrings->pop_back();
+    }
+    _thread(_root_vm)->Finalize();
+    _root_vm.Null();
+    _table_default_delegate.Null();
+    _array_default_delegate.Null();
+    _string_default_delegate.Null();
+    _number_default_delegate.Null();
+    _closure_default_delegate.Null();
+    _generator_default_delegate.Null();
+    _thread_default_delegate.Null();
+    _class_default_delegate.Null();
+    _instance_default_delegate.Null();
+    _weakref_default_delegate.Null();
+    _refs_table.Finalize();
+#ifndef NO_GARBAGE_COLLECTOR
+    SQCollectable *t = _gc_chain;
+    SQCollectable *nx = NULL;
+    if(t) {
+        t->_uiRef++;
+        while(t) {
+            t->Finalize();
+            nx = t->_next;
+            if(nx) nx->_uiRef++;
+            if(--t->_uiRef == 0)
+                t->Release();
+            t = nx;
+        }
+    }
+    assert(_gc_chain==NULL); //just to proove a theory
+    while(_gc_chain){
+        _gc_chain->_uiRef++;
+        _gc_chain->Release();
+    }
+#endif
+
+    sq_delete(_types,SQObjectPtrVec);
+    sq_delete(_systemstrings,SQObjectPtrVec);
+    sq_delete(_metamethods,SQObjectPtrVec);
+    sq_delete(_stringtable,SQStringTable);
+    if(_scratchpad)SQ_FREE(_scratchpad,_scratchpadsize);
+}
+
+
+SQInteger SQSharedState::GetMetaMethodIdxByName(const SQObjectPtr &name)
+{
+    if(sq_type(name) != OT_STRING)
+        return -1;
+    SQObjectPtr ret;
+    if(_table(_metamethodsmap)->Get(name,ret)) {
+        return _integer(ret);
+    }
+    return -1;
+}
+
+#ifndef NO_GARBAGE_COLLECTOR
+
+void SQSharedState::MarkObject(SQObjectPtr &o,SQCollectable **chain)
+{
+    switch(sq_type(o)){
+    case OT_TABLE:_table(o)->Mark(chain);break;
+    case OT_ARRAY:_array(o)->Mark(chain);break;
+    case OT_USERDATA:_userdata(o)->Mark(chain);break;
+    case OT_CLOSURE:_closure(o)->Mark(chain);break;
+    case OT_NATIVECLOSURE:_nativeclosure(o)->Mark(chain);break;
+    case OT_GENERATOR:_generator(o)->Mark(chain);break;
+    case OT_THREAD:_thread(o)->Mark(chain);break;
+    case OT_CLASS:_class(o)->Mark(chain);break;
+    case OT_INSTANCE:_instance(o)->Mark(chain);break;
+    case OT_OUTER:_outer(o)->Mark(chain);break;
+    case OT_FUNCPROTO:_funcproto(o)->Mark(chain);break;
+    default: break; //shutup compiler
+    }
+}
+
+void SQSharedState::RunMark(SQVM* SQ_UNUSED_ARG(vm),SQCollectable **tchain)
+{
+    SQVM *vms = _thread(_root_vm);
+
+    vms->Mark(tchain);
+
+    _refs_table.Mark(tchain);
+    MarkObject(_registry,tchain);
+    MarkObject(_consts,tchain);
+    MarkObject(_metamethodsmap,tchain);
+    MarkObject(_table_default_delegate,tchain);
+    MarkObject(_array_default_delegate,tchain);
+    MarkObject(_string_default_delegate,tchain);
+    MarkObject(_number_default_delegate,tchain);
+    MarkObject(_generator_default_delegate,tchain);
+    MarkObject(_thread_default_delegate,tchain);
+    MarkObject(_closure_default_delegate,tchain);
+    MarkObject(_class_default_delegate,tchain);
+    MarkObject(_instance_default_delegate,tchain);
+    MarkObject(_weakref_default_delegate,tchain);
+
+}
+
+SQInteger SQSharedState::ResurrectUnreachable(SQVM *vm)
+{
+    SQInteger n=0;
+    SQCollectable *tchain=NULL;
+
+    RunMark(vm,&tchain);
+
+    SQCollectable *resurrected = _gc_chain;
+    SQCollectable *t = resurrected;
+
+    _gc_chain = tchain;
+
+    SQArray *ret = NULL;
+    if(resurrected) {
+        ret = SQArray::Create(this,0);
+        SQCollectable *rlast = NULL;
+        while(t) {
+            rlast = t;
+            SQObjectType type = t->GetType();
+            if(type != OT_FUNCPROTO && type != OT_OUTER) {
+                SQObject sqo;
+                sqo._type = type;
+                sqo._unVal.pRefCounted = t;
+                ret->Append(sqo);
+            }
+            t = t->_next;
+            n++;
+        }
+
+        assert(rlast->_next == NULL);
+        rlast->_next = _gc_chain;
+        if(_gc_chain)
+        {
+            _gc_chain->_prev = rlast;
+        }
+        _gc_chain = resurrected;
+    }
+
+    t = _gc_chain;
+    while(t) {
+        t->UnMark();
+        t = t->_next;
+    }
+
+    if(ret) {
+        SQObjectPtr temp = ret;
+        vm->Push(temp);
+    }
+    else {
+        vm->PushNull();
+    }
+    return n;
+}
+
+SQInteger SQSharedState::CollectGarbage(SQVM *vm)
+{
+    SQInteger n = 0;
+    SQCollectable *tchain = NULL;
+
+    RunMark(vm,&tchain);
+
+    SQCollectable *t = _gc_chain;
+    SQCollectable *nx = NULL;
+    if(t) {
+        t->_uiRef++;
+        while(t) {
+            t->Finalize();
+            nx = t->_next;
+            if(nx) nx->_uiRef++;
+            if(--t->_uiRef == 0)
+                t->Release();
+            t = nx;
+            n++;
+        }
+    }
+
+    t = tchain;
+    while(t) {
+        t->UnMark();
+        t = t->_next;
+    }
+    _gc_chain = tchain;
+
+    return n;
+}
+#endif
+
+#ifndef NO_GARBAGE_COLLECTOR
+void SQCollectable::AddToChain(SQCollectable **chain,SQCollectable *c)
+{
+    c->_prev = NULL;
+    c->_next = *chain;
+    if(*chain) (*chain)->_prev = c;
+    *chain = c;
+}
+
+void SQCollectable::RemoveFromChain(SQCollectable **chain,SQCollectable *c)
+{
+    if(c->_prev) c->_prev->_next = c->_next;
+    else *chain = c->_next;
+    if(c->_next)
+        c->_next->_prev = c->_prev;
+    c->_next = NULL;
+    c->_prev = NULL;
+}
+#endif
+
+SQChar* SQSharedState::GetScratchPad(SQInteger size)
+{
+    SQInteger newsize;
+    if(size>0) {
+        if(_scratchpadsize < size) {
+            newsize = size + (size>>1);
+            _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize);
+            _scratchpadsize = newsize;
+
+        }else if(_scratchpadsize >= (size<<5)) {
+            newsize = _scratchpadsize >> 1;
+            _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize);
+            _scratchpadsize = newsize;
+        }
+    }
+    return _scratchpad;
+}
+
+RefTable::RefTable()
+{
+    AllocNodes(4);
+}
+
+void RefTable::Finalize()
+{
+    RefNode *nodes = _nodes;
+    for(SQUnsignedInteger n = 0; n < _numofslots; n++) {
+        nodes->obj.Null();
+        nodes++;
+    }
+}
+
+RefTable::~RefTable()
+{
+    SQ_FREE(_buckets,(_numofslots * sizeof(RefNode *)) + (_numofslots * sizeof(RefNode)));
+}
+
+#ifndef NO_GARBAGE_COLLECTOR
+void RefTable::Mark(SQCollectable **chain)
+{
+    RefNode *nodes = (RefNode *)_nodes;
+    for(SQUnsignedInteger n = 0; n < _numofslots; n++) {
+        if(sq_type(nodes->obj) != OT_NULL) {
+            SQSharedState::MarkObject(nodes->obj,chain);
+        }
+        nodes++;
+    }
+}
+#endif
+
+void RefTable::AddRef(SQObject &obj)
+{
+    SQHash mainpos;
+    RefNode *prev;
+    RefNode *ref = Get(obj,mainpos,&prev,true);
+    ref->refs++;
+}
+
+SQUnsignedInteger RefTable::GetRefCount(SQObject &obj)
+{
+     SQHash mainpos;
+     RefNode *prev;
+     RefNode *ref = Get(obj,mainpos,&prev,true);
+     return ref->refs;
+}
+
+
+SQBool RefTable::Release(SQObject &obj)
+{
+    SQHash mainpos;
+    RefNode *prev;
+    RefNode *ref = Get(obj,mainpos,&prev,false);
+    if(ref) {
+        if(--ref->refs == 0) {
+            SQObjectPtr o = ref->obj;
+            if(prev) {
+                prev->next = ref->next;
+            }
+            else {
+                _buckets[mainpos] = ref->next;
+            }
+            ref->next = _freelist;
+            _freelist = ref;
+            _slotused--;
+            ref->obj.Null();
+            //<<FIXME>>test for shrink?
+            return SQTrue;
+        }
+    }
+    else {
+        assert(0);
+    }
+    return SQFalse;
+}
+
+void RefTable::Resize(SQUnsignedInteger size)
+{
+    RefNode **oldbucks = _buckets;
+    RefNode *t = _nodes;
+    SQUnsignedInteger oldnumofslots = _numofslots;
+    AllocNodes(size);
+    //rehash
+    SQUnsignedInteger nfound = 0;
+    for(SQUnsignedInteger n = 0; n < oldnumofslots; n++) {
+        if(sq_type(t->obj) != OT_NULL) {
+            //add back;
+            assert(t->refs != 0);
+            RefNode *nn = Add(::HashObj(t->obj)&(_numofslots-1),t->obj);
+            nn->refs = t->refs;
+            t->obj.Null();
+            nfound++;
+        }
+        t++;
+    }
+    assert(nfound == oldnumofslots);
+    SQ_FREE(oldbucks,(oldnumofslots * sizeof(RefNode *)) + (oldnumofslots * sizeof(RefNode)));
+}
+
+RefTable::RefNode *RefTable::Add(SQHash mainpos,SQObject &obj)
+{
+    RefNode *t = _buckets[mainpos];
+    RefNode *newnode = _freelist;
+    newnode->obj = obj;
+    _buckets[mainpos] = newnode;
+    _freelist = _freelist->next;
+    newnode->next = t;
+    assert(newnode->refs == 0);
+    _slotused++;
+    return newnode;
+}
+
+RefTable::RefNode *RefTable::Get(SQObject &obj,SQHash &mainpos,RefNode **prev,bool add)
+{
+    RefNode *ref;
+    mainpos = ::HashObj(obj)&(_numofslots-1);
+    *prev = NULL;
+    for (ref = _buckets[mainpos]; ref; ) {
+        if(_rawval(ref->obj) == _rawval(obj) && sq_type(ref->obj) == sq_type(obj))
+            break;
+        *prev = ref;
+        ref = ref->next;
+    }
+    if(ref == NULL && add) {
+        if(_numofslots == _slotused) {
+            assert(_freelist == 0);
+            Resize(_numofslots*2);
+            mainpos = ::HashObj(obj)&(_numofslots-1);
+        }
+        ref = Add(mainpos,obj);
+    }
+    return ref;
+}
+
+void RefTable::AllocNodes(SQUnsignedInteger size)
+{
+    RefNode **bucks;
+    RefNode *nodes;
+    bucks = (RefNode **)SQ_MALLOC((size * sizeof(RefNode *)) + (size * sizeof(RefNode)));
+    nodes = (RefNode *)&bucks[size];
+    RefNode *temp = nodes;
+    SQUnsignedInteger n;
+    for(n = 0; n < size - 1; n++) {
+        bucks[n] = NULL;
+        temp->refs = 0;
+        new (&temp->obj) SQObjectPtr;
+        temp->next = temp+1;
+        temp++;
+    }
+    bucks[n] = NULL;
+    temp->refs = 0;
+    new (&temp->obj) SQObjectPtr;
+    temp->next = NULL;
+    _freelist = nodes;
+    _nodes = nodes;
+    _buckets = bucks;
+    _slotused = 0;
+    _numofslots = size;
+}
+//////////////////////////////////////////////////////////////////////////
+//SQStringTable
+/*
+* The following code is based on Lua 4.0 (Copyright 1994-2002 Tecgraf, PUC-Rio.)
+* http://www.lua.org/copyright.html#4
+* http://www.lua.org/source/4.0.1/src_lstring.c.html
+*/
+
+SQStringTable::SQStringTable(SQSharedState *ss)
+{
+    _sharedstate = ss;
+    AllocNodes(4);
+    _slotused = 0;
+}
+
+SQStringTable::~SQStringTable()
+{
+    SQ_FREE(_strings,sizeof(SQString*)*_numofslots);
+    _strings = NULL;
+}
+
+void SQStringTable::AllocNodes(SQInteger size)
+{
+    _numofslots = size;
+    _strings = (SQString**)SQ_MALLOC(sizeof(SQString*)*_numofslots);
+    memset(_strings,0,sizeof(SQString*)*_numofslots);
+}
+
+SQString *SQStringTable::Add(const SQChar *news,SQInteger len)
+{
+    if(len<0)
+        len = (SQInteger)scstrlen(news);
+    SQHash newhash = ::_hashstr(news,len);
+    SQHash h = newhash&(_numofslots-1);
+    SQString *s;
+    for (s = _strings[h]; s; s = s->_next){
+        if(s->_len == len && (!memcmp(news,s->_val,sq_rsl(len))))
+            return s; //found
+    }
+
+    SQString *t = (SQString *)SQ_MALLOC(sq_rsl(len)+sizeof(SQString));
+    new (t) SQString;
+    t->_sharedstate = _sharedstate;
+    memcpy(t->_val,news,sq_rsl(len));
+    t->_val[len] = _SC('\0');
+    t->_len = len;
+    t->_hash = newhash;
+    t->_next = _strings[h];
+    _strings[h] = t;
+    _slotused++;
+    if (_slotused > _numofslots)  /* too crowded? */
+        Resize(_numofslots*2);
+    return t;
+}
+
+void SQStringTable::Resize(SQInteger size)
+{
+    SQInteger oldsize=_numofslots;
+    SQString **oldtable=_strings;
+    AllocNodes(size);
+    for (SQInteger i=0; i<oldsize; i++){
+        SQString *p = oldtable[i];
+        while(p){
+            SQString *next = p->_next;
+            SQHash h = p->_hash&(_numofslots-1);
+            p->_next = _strings[h];
+            _strings[h] = p;
+            p = next;
+        }
+    }
+    SQ_FREE(oldtable,oldsize*sizeof(SQString*));
+}
+
+void SQStringTable::Remove(SQString *bs)
+{
+    SQString *s;
+    SQString *prev=NULL;
+    SQHash h = bs->_hash&(_numofslots - 1);
+
+    for (s = _strings[h]; s; ){
+        if(s == bs){
+            if(prev)
+                prev->_next = s->_next;
+            else
+                _strings[h] = s->_next;
+            _slotused--;
+            SQInteger slen = s->_len;
+            s->~SQString();
+            SQ_FREE(s,sizeof(SQString) + sq_rsl(slen));
+            return;
+        }
+        prev = s;
+        s = s->_next;
+    }
+    assert(0);//if this fail something is wrong
+}
diff --git a/engines/twp/squirrel/sqstate.h b/engines/twp/squirrel/sqstate.h
new file mode 100755
index 00000000000..2cdc8da4fae
--- /dev/null
+++ b/engines/twp/squirrel/sqstate.h
@@ -0,0 +1,136 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQSTATE_H_
+#define _SQSTATE_H_
+
+#include "squtils.h"
+#include "sqobject.h"
+struct SQString;
+struct SQTable;
+//max number of character for a printed number
+#define NUMBER_MAX_CHAR 50
+
+struct SQStringTable
+{
+    SQStringTable(SQSharedState*ss);
+    ~SQStringTable();
+    SQString *Add(const SQChar *,SQInteger len);
+    void Remove(SQString *);
+private:
+    void Resize(SQInteger size);
+    void AllocNodes(SQInteger size);
+    SQString **_strings;
+    SQUnsignedInteger _numofslots;
+    SQUnsignedInteger _slotused;
+    SQSharedState *_sharedstate;
+};
+
+struct RefTable {
+    struct RefNode {
+        SQObjectPtr obj;
+        SQUnsignedInteger refs;
+        struct RefNode *next;
+    };
+    RefTable();
+    ~RefTable();
+    void AddRef(SQObject &obj);
+    SQBool Release(SQObject &obj);
+    SQUnsignedInteger GetRefCount(SQObject &obj);
+#ifndef NO_GARBAGE_COLLECTOR
+    void Mark(SQCollectable **chain);
+#endif
+    void Finalize();
+private:
+    RefNode *Get(SQObject &obj,SQHash &mainpos,RefNode **prev,bool add);
+    RefNode *Add(SQHash mainpos,SQObject &obj);
+    void Resize(SQUnsignedInteger size);
+    void AllocNodes(SQUnsignedInteger size);
+    SQUnsignedInteger _numofslots;
+    SQUnsignedInteger _slotused;
+    RefNode *_nodes;
+    RefNode *_freelist;
+    RefNode **_buckets;
+};
+
+#define ADD_STRING(ss,str,len) ss->_stringtable->Add(str,len)
+#define REMOVE_STRING(ss,bstr) ss->_stringtable->Remove(bstr)
+
+struct SQObjectPtr;
+
+struct SQSharedState
+{
+    SQSharedState();
+    ~SQSharedState();
+    void Init();
+public:
+    SQChar* GetScratchPad(SQInteger size);
+    SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name);
+#ifndef NO_GARBAGE_COLLECTOR
+    SQInteger CollectGarbage(SQVM *vm);
+    void RunMark(SQVM *vm,SQCollectable **tchain);
+    SQInteger ResurrectUnreachable(SQVM *vm);
+    static void MarkObject(SQObjectPtr &o,SQCollectable **chain);
+#endif
+    SQObjectPtrVec *_metamethods;
+    SQObjectPtr _metamethodsmap;
+    SQObjectPtrVec *_systemstrings;
+    SQObjectPtrVec *_types;
+    SQStringTable *_stringtable;
+    RefTable _refs_table;
+    SQObjectPtr _registry;
+    SQObjectPtr _consts;
+    SQObjectPtr _constructoridx;
+#ifndef NO_GARBAGE_COLLECTOR
+    SQCollectable *_gc_chain;
+#endif
+    SQObjectPtr _root_vm;
+    SQObjectPtr _table_default_delegate;
+    static const SQRegFunction _table_default_delegate_funcz[];
+    SQObjectPtr _array_default_delegate;
+    static const SQRegFunction _array_default_delegate_funcz[];
+    SQObjectPtr _string_default_delegate;
+    static const SQRegFunction _string_default_delegate_funcz[];
+    SQObjectPtr _number_default_delegate;
+    static const SQRegFunction _number_default_delegate_funcz[];
+    SQObjectPtr _generator_default_delegate;
+    static const SQRegFunction _generator_default_delegate_funcz[];
+    SQObjectPtr _closure_default_delegate;
+    static const SQRegFunction _closure_default_delegate_funcz[];
+    SQObjectPtr _thread_default_delegate;
+    static const SQRegFunction _thread_default_delegate_funcz[];
+    SQObjectPtr _class_default_delegate;
+    static const SQRegFunction _class_default_delegate_funcz[];
+    SQObjectPtr _instance_default_delegate;
+    static const SQRegFunction _instance_default_delegate_funcz[];
+    SQObjectPtr _weakref_default_delegate;
+    static const SQRegFunction _weakref_default_delegate_funcz[];
+
+    SQCOMPILERERROR _compilererrorhandler;
+    SQPRINTFUNCTION _printfunc;
+    SQPRINTFUNCTION _errorfunc;
+    bool _debuginfo;
+    bool _notifyallexceptions;
+    SQUserPointer _foreignptr;
+    SQRELEASEHOOK _releasehook;
+private:
+    SQChar *_scratchpad;
+    SQInteger _scratchpadsize;
+};
+
+#define _sp(s) (_sharedstate->GetScratchPad(s))
+#define _spval (_sharedstate->GetScratchPad(-1))
+
+#define _table_ddel     _table(_sharedstate->_table_default_delegate)
+#define _array_ddel     _table(_sharedstate->_array_default_delegate)
+#define _string_ddel    _table(_sharedstate->_string_default_delegate)
+#define _number_ddel    _table(_sharedstate->_number_default_delegate)
+#define _generator_ddel _table(_sharedstate->_generator_default_delegate)
+#define _closure_ddel   _table(_sharedstate->_closure_default_delegate)
+#define _thread_ddel    _table(_sharedstate->_thread_default_delegate)
+#define _class_ddel     _table(_sharedstate->_class_default_delegate)
+#define _instance_ddel  _table(_sharedstate->_instance_default_delegate)
+#define _weakref_ddel   _table(_sharedstate->_weakref_default_delegate)
+
+bool CompileTypemask(SQIntVec &res,const SQChar *typemask);
+
+
+#endif //_SQSTATE_H_
diff --git a/engines/twp/squirrel/sqstdaux.cpp b/engines/twp/squirrel/sqstdaux.cpp
new file mode 100755
index 00000000000..c4451311cbc
--- /dev/null
+++ b/engines/twp/squirrel/sqstdaux.cpp
@@ -0,0 +1,130 @@
+/* see copyright notice in squirrel.h */
+#include "squirrel.h"
+#include "sqstdaux.h"
+#include <assert.h>
+
+void sqstd_printcallstack(HSQUIRRELVM v)
+{
+    SQPRINTFUNCTION pf = sq_geterrorfunc(v);
+    if(pf) {
+        SQStackInfos si;
+        SQInteger i;
+        SQFloat f;
+        const SQChar *s;
+        SQInteger level=1; //1 is to skip this function that is level 0
+        const SQChar *name=0;
+        SQInteger seq=0;
+        pf(v,_SC("\nCALLSTACK\n"));
+        while(SQ_SUCCEEDED(sq_stackinfos(v,level,&si)))
+        {
+            const SQChar *fn=_SC("unknown");
+            const SQChar *src=_SC("unknown");
+            if(si.funcname)fn=si.funcname;
+            if(si.source)src=si.source;
+            pf(v,_SC("*FUNCTION [%s()] %s line [%d]\n"),fn,src,si.line);
+            level++;
+        }
+        level=0;
+        pf(v,_SC("\nLOCALS\n"));
+
+        for(level=0;level<10;level++){
+            seq=0;
+            while((name = sq_getlocal(v,level,seq)))
+            {
+                seq++;
+                switch(sq_gettype(v,-1))
+                {
+                case OT_NULL:
+                    pf(v,_SC("[%s] NULL\n"),name);
+                    break;
+                case OT_INTEGER:
+                    sq_getinteger(v,-1,&i);
+                    pf(v,_SC("[%s] %d\n"),name,i);
+                    break;
+                case OT_FLOAT:
+                    sq_getfloat(v,-1,&f);
+                    pf(v,_SC("[%s] %.14g\n"),name,f);
+                    break;
+                case OT_USERPOINTER:
+                    pf(v,_SC("[%s] USERPOINTER\n"),name);
+                    break;
+                case OT_STRING:
+                    sq_getstring(v,-1,&s);
+                    pf(v,_SC("[%s] \"%s\"\n"),name,s);
+                    break;
+                case OT_TABLE:
+                    pf(v,_SC("[%s] TABLE\n"),name);
+                    break;
+                case OT_ARRAY:
+                    pf(v,_SC("[%s] ARRAY\n"),name);
+                    break;
+                case OT_CLOSURE:
+                    pf(v,_SC("[%s] CLOSURE\n"),name);
+                    break;
+                case OT_NATIVECLOSURE:
+                    pf(v,_SC("[%s] NATIVECLOSURE\n"),name);
+                    break;
+                case OT_GENERATOR:
+                    pf(v,_SC("[%s] GENERATOR\n"),name);
+                    break;
+                case OT_USERDATA:
+                    pf(v,_SC("[%s] USERDATA\n"),name);
+                    break;
+                case OT_THREAD:
+                    pf(v,_SC("[%s] THREAD\n"),name);
+                    break;
+                case OT_CLASS:
+                    pf(v,_SC("[%s] CLASS\n"),name);
+                    break;
+                case OT_INSTANCE:
+                    pf(v,_SC("[%s] INSTANCE\n"),name);
+                    break;
+                case OT_WEAKREF:
+                    pf(v,_SC("[%s] WEAKREF\n"),name);
+                    break;
+                case OT_BOOL:{
+                    SQBool bval;
+                    sq_getbool(v,-1,&bval);
+                    pf(v,_SC("[%s] %s\n"),name,bval == SQTrue ? _SC("true"):_SC("false"));
+                             }
+                    break;
+                default: assert(0); break;
+                }
+                sq_pop(v,1);
+            }
+        }
+    }
+}
+
+static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v)
+{
+    SQPRINTFUNCTION pf = sq_geterrorfunc(v);
+    if(pf) {
+        const SQChar *sErr = 0;
+        if(sq_gettop(v)>=1) {
+            if(SQ_SUCCEEDED(sq_getstring(v,2,&sErr)))   {
+                pf(v,_SC("\nAN ERROR HAS OCCURRED [%s]\n"),sErr);
+            }
+            else{
+                pf(v,_SC("\nAN ERROR HAS OCCURRED [unknown]\n"));
+            }
+            sqstd_printcallstack(v);
+        }
+    }
+    return 0;
+}
+
+void _sqstd_compiler_error(HSQUIRRELVM v,const SQChar *sErr,const SQChar *sSource,SQInteger line,SQInteger column)
+{
+    SQPRINTFUNCTION pf = sq_geterrorfunc(v);
+    if(pf) {
+        pf(v,_SC("%s line = (%d) column = (%d) : error %s\n"),sSource,line,column,sErr);
+    }
+}
+
+void sqstd_seterrorhandlers(HSQUIRRELVM v)
+{
+    sq_setcompilererrorhandler(v,_sqstd_compiler_error);
+    sq_newclosure(v,_sqstd_aux_printerror,0);
+    sq_seterrorhandler(v);
+}
diff --git a/engines/twp/squirrel/sqstdaux.h b/engines/twp/squirrel/sqstdaux.h
new file mode 100755
index 00000000000..7396add51d9
--- /dev/null
+++ b/engines/twp/squirrel/sqstdaux.h
@@ -0,0 +1,16 @@
+/*  see copyright notice in squirrel.h */
+#ifndef _SQSTD_AUXLIB_H_
+#define _SQSTD_AUXLIB_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SQUIRREL_API void sqstd_seterrorhandlers(HSQUIRRELVM v);
+SQUIRREL_API void sqstd_printcallstack(HSQUIRRELVM v);
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /* _SQSTD_AUXLIB_H_ */
diff --git a/engines/twp/squirrel/sqstdblob.cpp b/engines/twp/squirrel/sqstdblob.cpp
new file mode 100755
index 00000000000..a9fe2ce567b
--- /dev/null
+++ b/engines/twp/squirrel/sqstdblob.cpp
@@ -0,0 +1,283 @@
+/* see copyright notice in squirrel.h */
+#include <new>
+#include "squirrel.h"
+#include "sqstdio.h"
+#include <string.h>
+#include "sqstdblob.h"
+#include "sqstdstream.h"
+#include "sqstdblobimpl.h"
+
+#define SQSTD_BLOB_TYPE_TAG ((SQUnsignedInteger)(SQSTD_STREAM_TYPE_TAG | 0x00000002))
+
+//Blob
+
+
+#define SETUP_BLOB(v) \
+    SQBlob *self = NULL; \
+    { if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) \
+        return sq_throwerror(v,_SC("invalid type tag"));  } \
+    if(!self || !self->IsValid())  \
+        return sq_throwerror(v,_SC("the blob is invalid"));
+
+
+static SQInteger _blob_resize(HSQUIRRELVM v)
+{
+    SETUP_BLOB(v);
+    SQInteger size;
+    sq_getinteger(v,2,&size);
+    if(!self->Resize(size))
+        return sq_throwerror(v,_SC("resize failed"));
+    return 0;
+}
+
+static void __swap_dword(unsigned int *n)
+{
+    *n=(unsigned int)(((*n&0xFF000000)>>24)  |
+            ((*n&0x00FF0000)>>8)  |
+            ((*n&0x0000FF00)<<8)  |
+            ((*n&0x000000FF)<<24));
+}
+
+static void __swap_word(unsigned short *n)
+{
+    *n=(unsigned short)((*n>>8)&0x00FF)| ((*n<<8)&0xFF00);
+}
+
+static SQInteger _blob_swap4(HSQUIRRELVM v)
+{
+    SETUP_BLOB(v);
+    SQInteger num=(self->Len()-(self->Len()%4))>>2;
+    unsigned int *t=(unsigned int *)self->GetBuf();
+    for(SQInteger i = 0; i < num; i++) {
+        __swap_dword(&t[i]);
+    }
+    return 0;
+}
+
+static SQInteger _blob_swap2(HSQUIRRELVM v)
+{
+    SETUP_BLOB(v);
+    SQInteger num=(self->Len()-(self->Len()%2))>>1;
+    unsigned short *t = (unsigned short *)self->GetBuf();
+    for(SQInteger i = 0; i < num; i++) {
+        __swap_word(&t[i]);
+    }
+    return 0;
+}
+
+static SQInteger _blob__set(HSQUIRRELVM v)
+{
+    SETUP_BLOB(v);
+    SQInteger idx,val;
+    sq_getinteger(v,2,&idx);
+    sq_getinteger(v,3,&val);
+    if(idx < 0 || idx >= self->Len())
+        return sq_throwerror(v,_SC("index out of range"));
+    ((unsigned char *)self->GetBuf())[idx] = (unsigned char) val;
+    sq_push(v,3);
+    return 1;
+}
+
+static SQInteger _blob__get(HSQUIRRELVM v)
+{
+    SETUP_BLOB(v);
+    SQInteger idx;


Commit: 4e27881da2a9aae00a563968fe1a5d6dff3b0fbd
    https://github.com/scummvm/scummvm/commit/4e27881da2a9aae00a563968fe1a5d6dff3b0fbd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Test threads and breaktime

Changed paths:
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp
    engines/twp/vm.h


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index bef3b732717..9d0bee06934 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -64,7 +64,7 @@ Common::String TwpEngine::getGameId() const {
 Common::Error TwpEngine::run() {
 	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
 	initGraphics(1280, 720, &fmt);
-	_screen = new Graphics::Screen();
+	_screen = new Graphics::Screen(1280, 720, fmt);
 
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
@@ -74,36 +74,39 @@ Common::Error TwpEngine::run() {
 	Common::File f;
 	f.open("ThimbleweedPark.ggpack1");
 
-	GGPackDecoder pack;
-	pack.open(&f, key);
-
-	GGPackEntryReader r;
-	r.open(pack, "RaySheet.png");
-
-	Image::PNGDecoder d;
-	d.loadStream(r);
-	const Graphics::Surface *surface = d.getSurface();
-
-	GGPackEntryReader r2;
-	r2.open(pack, "RaySheet.json");
-	Common::Array<char> data(r2.size());
-	r2.read(&data[0], r2.size());
-	Common::String s(&data[0], r2.size());
-
-	Common::JSONValue *jRay = Common::JSON::parse(s.c_str());
-	const Common::JSONObject &jRayObj = jRay->asObject();
-	const Common::JSONObject &jFrames = jRayObj["frames"]->asObject();
-	const Common::JSONObject &jFrame = jFrames["bstand_body1"]->asObject();
-	const Common::JSONObject &jRect = jFrame["frame"]->asObject();
+	Scene scene;
+	scene.pack.open(&f, key);
+
+	const SQChar *code = R"(
+	function bounceImage() {
+		local image = createObject("RaySheet", ["fstand_head1"]);
+		local x = random(100, 1180);
+		local y = random(100, 620);
+
+		do {
+			local steps = random(100, 150);
+			local end_x = random(0, 1180);
+			local end_y = random(0, 620);
+
+			local dx = (end_x - x) / steps;
+			local dy = (end_y - y) / steps;
+
+			for (local i = 0; i < steps; i++) {
+				x += dx;
+				y += dy;
+				objectAt(image, x, y);
+				breaktime(0.01);
+			}
+		} while (1);
+	}
 
-	int x = (int)jRect["x"]->asIntegerNumber();
-	int y = (int)jRect["y"]->asIntegerNumber();
-	int w = (int)jRect["w"]->asIntegerNumber();
-	int h = (int)jRect["h"]->asIntegerNumber();
-	Common::Rect rect(x, y, x + w, y + h);
+	for (local i = 1; i <= 100; i++) {
+		startthread(bounceImage);
+	})";
 
 	Vm v;
-	v.exec("print(\"Hello world \"+random(-2.7,8.6));\ncreateObject(\"RaySheet\",[\"bstand_body1\"]);");
+	v.setScene(&scene);
+	v.exec(code);
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -113,21 +116,32 @@ Common::Error TwpEngine::run() {
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
-	_screen->copyRectToSurface(*surface, 1280 / 2, 720 / 2, rect);
-
-	_screen->update();
-
 	// Simple event handling loop
 	Common::Event e;
 	while (!shouldQuit()) {
+		const uint deltaTimeMs = 10;
 		while (g_system->getEventManager()->pollEvent(e)) {
 		}
 
+		// update threads
+		for (int i = 0; i < scene.threads.size(); i++) {
+			Thread *thread = scene.threads[i];
+			if (thread->update(deltaTimeMs)) {
+				// TODO: delete it
+			}
+		}
+
+		// update screen
+		_screen->clear(0xFF808080);
+		for (int i = 0; i < scene.entities.size(); i++) {
+			Entity &ett = scene.entities[i];
+			_screen->transBlitFrom(ett.surface, ett.rect, Common::Point(ett.x, ett.y));
+		}
 		_screen->update();
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
-		g_system->delayMillis(10);
+		g_system->delayMillis(deltaTimeMs);
 	}
 
 	return Common::kNoError;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index df5e7730328..f54be7a1f5b 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -61,11 +61,9 @@ public:
 	Common::String getGameId() const;
 
 	/**
-	 * Gets a random number
+	 * Gets the random source
 	 */
-	uint32 getRandomNumber(uint maxNum) {
-		return _randomSource.getRandomNumber(maxNum);
-	}
+	Common::RandomSource& getRandomSource() { return _randomSource; }
 
 	bool hasFeature(EngineFeature f) const override {
 		return
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 55b0fb802da..dfa0a1a6763 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -19,11 +19,13 @@
  *
  */
 
-#include <vector>
-#include <string>
+#include "twp/twp.h"
 #include "twp/vm.h"
+#include "common/array.h"
+#include "common/algorithm.h"
 #include "common/debug.h"
 #include "common/random.h"
+#include "image/png.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
@@ -34,33 +36,84 @@
 #include "squirrel/sqstdmath.h"
 #include "squirrel/sqstdio.h"
 #include "squirrel/sqstdaux.h"
+#include "squirrel/sqfuncproto.h"
+#include "squirrel/sqclosure.h"
 
 namespace Twp {
 
-static SQRESULT get(HSQUIRRELVM v, int i, std::string &value) {
+static Scene *gScene = nullptr;
+static HSQUIRRELVM gVm = nullptr;
+
+static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) {
+	SQObjectPtr string = SQString::Create(_ss(v), value);
+	return string;
+}
+
+static inline SQObject sqObj(HSQUIRRELVM v, int value) {
+	SQObject o;
+	o._type = OT_INTEGER;
+	o._unVal.nInteger = value;
+	return o;
+}
+
+static SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
 	const SQChar *val;
 	SQRESULT result = sq_getstring(v, i, &val);
 	value = val;
 	return result;
 }
 
-static void getArray(HSQUIRRELVM v, HSQOBJECT o, std::vector<std::string> &arr) {
-  sq_pushobject(v, o);
-  sq_pushnull(v);
-  while(SQ_SUCCEEDED(sq_next(v, -2))) {
-    const SQChar* str;
-    sq_getstring(v, -1, &str);
-    arr.push_back(str);
-    sq_pop(v, 2);
-  }
-  sq_pop(v, 1);
+static void getArray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr) {
+	sq_pushobject(v, o);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		const SQChar *str;
+		sq_getstring(v, -1, &str);
+		arr.push_back(str);
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 1);
 }
 
-static SQRESULT getArray(HSQUIRRELVM v, int i, std::vector<std::string> &arr) {
-  HSQOBJECT obj;
-  SQRESULT result = sq_getstackobj(v, i, &obj);
-  getArray(v, obj, arr);
-  return result;
+static SQRESULT getArray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr) {
+	HSQOBJECT obj;
+	SQRESULT result = sq_getstackobj(v, i, &obj);
+	getArray(v, obj, arr);
+	return result;
+}
+
+static Thread *thread(HSQUIRRELVM v) {
+	return *Common::find_if(gScene->threads.begin(), gScene->threads.end(), [&](Thread *t) {
+		return t->threadObj._unVal.pThread == v;
+	});
+}
+
+template<typename F>
+static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
+	Thread *t = thread(v);
+	if (!t)
+		return sq_throwerror(v, "failed to get thread");
+	t->suspend();
+	func(*t);
+	return -666;
+}
+
+// When called in a function started with startthread, execution is suspended for time seconds.
+// It is an error to call breaktime in a function that was not started with startthread.
+//
+// . code-block:: Squirrel
+// for (local x = 1; x < 4; x += 1) {
+//   playSound(soundPhoneRinging)
+//   breaktime(5.0)
+// }
+static SQInteger breaktime(HSQUIRRELVM v) {
+	SQFloat time;
+	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
+		return sq_throwerror(v, "failed to get time");
+	if (time == 0.f)
+		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
+	else
+		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
 }
 
 // Returns a random number from from to to inclusively.
@@ -69,14 +122,13 @@ static SQRESULT getArray(HSQUIRRELVM v, int i, std::vector<std::string> &arr) {
 // .. code-block:: Squirrel
 // wait_time = random(0.5, 2.0)
 static SQInteger sq_random(HSQUIRRELVM v) {
-	Common::RandomSource rnd("twp");
 	if (sq_gettype(v, 2) == OT_FLOAT || sq_gettype(v, 3) == OT_FLOAT) {
 		SQFloat min, max;
 		sq_getfloat(v, 2, &min);
 		sq_getfloat(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		float scale = rnd.getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+		float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
 		SQFloat value = min + scale * (max - min);
 		sq_pushfloat(v, value);
 	} else {
@@ -85,7 +137,7 @@ static SQInteger sq_random(HSQUIRRELVM v) {
 		sq_getinteger(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		SQInteger value = rnd.getRandomNumberRngSigned(min, max);
+		SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max);
 		sq_pushinteger(v, value);
 	}
 	return 1;
@@ -99,8 +151,8 @@ static SQInteger sq_random(HSQUIRRELVM v) {
 // They do not have verbs or local variables by default, but these can be added when the object is created so it can be used in the construction of sentences.
 static SQInteger createObject(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
-	std::string sheet;
-	std::vector<std::string> frames;
+	Common::String sheet;
+	Common::Array<Common::String> frames;
 	SQInteger framesIndex = 2;
 
 	// get sheet parameter if any
@@ -114,7 +166,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	if (numArgs >= 2) {
 		switch (sq_gettype(v, framesIndex)) {
 		case OT_STRING: {
-			std::string frame;
+			Common::String frame;
 			get(v, framesIndex, frame);
 			frames.push_back(frame);
 		} break;
@@ -126,12 +178,156 @@ static SQInteger createObject(HSQUIRRELVM v) {
 		}
 	}
 
-	debug("Create object: %s, %zu", sheet.c_str(), frames.size());
-	// var obj = gEngine.room.createObject(sheet, frames)
-	// push(v, obj.table)
+	debug("Create object: %s, %u", sheet.c_str(), frames.size());
+
+	// TODO: share resources load png to surface
+	GGPackEntryReader r;
+	r.open(gScene->pack, sheet + ".png");
+	Image::PNGDecoder d;
+	d.loadStream(r);
+	const Graphics::Surface *surface = d.getSurface();
+
+	// load sheet json
+	GGPackEntryReader r2;
+	r2.open(gScene->pack, sheet + ".json");
+	Common::Array<char> data(r2.size());
+	r2.read(&data[0], r2.size());
+	Common::String s(&data[0], r2.size());
+
+	debug(s.c_str());
+
+	// TODO: change to load each frame
+	Common::JSONValue *json = Common::JSON::parse(s.c_str());
+	const Common::JSONObject &jRect = json->asObject()["frames"]->asObject()[frames[0]]->asObject()["frame"]->asObject();
+
+	int x = (int)jRect["x"]->asIntegerNumber();
+	int y = (int)jRect["y"]->asIntegerNumber();
+	int w = (int)jRect["w"]->asIntegerNumber();
+	int h = (int)jRect["h"]->asIntegerNumber();
+
+	delete json;
+
+	// TODO: create an entity
+	Entity e;
+	e.surface.copyFrom(*surface);
+	e.rect = Common::Rect(x, y, x + w, y + h);
+
+	static int gId = 3000;
+	sq_newtable(v);
+	sq_pushstring(v, _SC("_id"), -1);
+	sq_pushinteger(v, gId++);
+	sq_newslot(v, -3, SQFalse);
+	sq_getstackobj(v, -1, &e.obj);
+	sq_addref(gVm, &e.obj);
+	sq_pop(v, 1);
+
+	gScene->entities.push_back(e);
+
+	sq_pushobject(v, e.obj);
+
+	return 1;
+}
+
+static SQInteger _startthread(HSQUIRRELVM v, bool global) {
+	SQInteger size = sq_gettop(v);
+
+	Thread *t = new Thread();
+	t->global = global;
+
+	static uint64 gThreadId = 300000;
+	sq_newtable(v);
+	sq_pushstring(v, _SC("_id"), -1);
+	sq_pushinteger(v, gThreadId++);
+	sq_newslot(v, -3, SQFalse);
+	sq_getstackobj(v, -1, &t->obj);
+	sq_addref(gVm, &t->obj);
+	sq_pop(v, 1);
+
+	sq_resetobject(&t->envObj);
+	if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj)))
+		return sq_throwerror(v, "Couldn't get environment from stack");
+	sq_addref(gVm, &t->envObj);
+
+	// create thread and store it on the stack
+	sq_newthread(gVm, 1024);
+	sq_resetobject(&t->threadObj);
+	if (SQ_FAILED(sq_getstackobj(gVm, -1, &t->threadObj)))
+		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
+	sq_addref(gVm, &t->threadObj);
+
+	for (int i = 0; i < size - 2; i++) {
+		HSQOBJECT arg;
+		sq_resetobject(&arg);
+		if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
+			return sq_throwerror(v, "Couldn't get coroutine args from stack");
+		t->args.push_back(arg);
+		sq_addref(gVm, &arg);
+	}
+
+	// get the closure
+	sq_resetobject(&t->closureObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj)))
+		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
+	sq_addref(gVm, &t->closureObj);
+
+	const SQChar *name = nullptr;
+	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
+		sq_getstring(v, -1, &name);
+
+	t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line);
+	sq_pop(gVm, 1);
+	if (name)
+		sq_pop(v, 1); // pop name
+	sq_pop(v, 1);     // pop closure
+
+	gScene->threads.push_back(t);
+
+	debug("create thread %s", t->name.c_str());
+
+	// call the closure in the thread
+	if (!t->call())
+		return sq_throwerror(v, "call failed");
+
+	sq_pushobject(v, t->obj);
 	return 1;
 }
 
+static SQInteger startthread(HSQUIRRELVM v) {
+	return _startthread(v, false);
+}
+
+static SQInteger startglobalthread(HSQUIRRELVM v) {
+	return _startthread(v, true);
+}
+
+static SQInteger objectAt(HSQUIRRELVM v) {
+	HSQOBJECT o;
+	sq_getstackobj(v, 2, &o);
+
+	SQObjectPtr id;
+	_table(o)->Get(sqObj(v, "_id"), id);
+
+	Entity *ett = Common::find_if(gScene->entities.begin(), gScene->entities.end(), [&](Entity &e) {
+		SQObjectPtr id2;
+		_table(e.obj)->Get(sqObj(v, "_id"), id2);
+		return _integer(id) == _integer(id2);
+	});
+
+	if (!ett)
+		return sq_throwerror(v, "failed to get object");
+
+	SQInteger x, y;
+	if (SQ_FAILED(sq_getinteger(v, 3, &x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sq_getinteger(v, 4, &y)))
+		return sq_throwerror(v, "failed to get y");
+	ett->x = x;
+	ett->y = y;
+	debug("Object at: %lld, %lld", x, y);
+
+	return 0;
+}
+
 static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck, const SQChar *typemask) {
 	sq_pushroottable(v);
 	sq_pushstring(v, functionName, -1);
@@ -142,18 +338,6 @@ static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQI
 	sq_pop(v, 1); // pops the root table
 }
 
-static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) {
-	SQObjectPtr string = SQString::Create(_ss(v), value);
-	return string;
-}
-
-static inline SQObject sqObj(HSQUIRRELVM v, int value) {
-	SQObject o;
-	o._type = OT_INTEGER;
-	o._unVal.nInteger = value;
-	return o;
-}
-
 static void sqExec(HSQUIRRELVM v, const char *code) {
 	SQInteger top = sq_gettop(v);
 	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
@@ -202,23 +386,37 @@ static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) {
 	debug("TWP: %s", buf);
 }
 
-Vm::Vm() {
-	v = sq_open(1024);
+Vm::Vm()
+	: _scene(nullptr) {
+	gVm = v = sq_open(1024);
 	sq_setcompilererrorhandler(v, errorHandler);
 	sq_newclosure(v, aux_printerror, 0);
 	sq_seterrorhandler(v);
 	sq_setprintfunc(v, printfunc, printfunc);
+
+	sq_pushroottable(v);
 	sqstd_register_stringlib(v);
 	sqstd_register_mathlib(v);
 	sqstd_register_iolib(v);
+	sq_pop(v, 1);
+
 	regFunc(v, sq_random, _SC("random"), 0, nullptr);
 	regFunc(v, createObject, _SC("createObject"), 0, nullptr);
+	regFunc(v, startthread, _SC("startthread"), 0, nullptr);
+	regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr);
+	regFunc(v, objectAt, _SC("objectAt"), 0, nullptr);
+	regFunc(v, breaktime, _SC("breaktime"), 0, nullptr);
 
 	SQObject platform = sqObj(v, 666);
 	_table(v->_roottable)->NewSlot(sqObj(v, _SC("PLATFORM")), SQObjectPtr(platform));
 }
 
 Vm::~Vm() {
+	if(_scene) {
+		for (int i = 0; i < _scene->threads.size(); i++) {
+			delete _scene->threads[i];
+		}
+	}
 	sq_close(v);
 }
 
@@ -226,4 +424,75 @@ void Vm::exec(const SQChar *code) {
 	sqExec(v, code);
 }
 
+void Vm::setScene(Scene *scene) {
+	gScene = _scene = scene;
+}
+
+Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
+}
+
+Thread::~Thread() {
+	for (int i = 0; i < args.size(); i++) {
+		sq_release(gVm, &args[i]);
+	}
+	sq_release(gVm, &threadObj);
+	sq_release(gVm, &envObj);
+	sq_release(gVm, &closureObj);
+}
+
+bool Thread::call() {
+	HSQUIRRELVM v = threadObj._unVal.pThread;
+	// call the closure in the thread
+	SQInteger top = sq_gettop(v);
+	sq_pushobject(v, closureObj);
+	sq_pushobject(v, envObj);
+	for (int i = 0; i < args.size(); i++) {
+		sq_pushobject(v, args[i]);
+	}
+	if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) {
+		sq_settop(v, top);
+		return false;
+	}
+	return true;
+}
+
+bool Thread::isDead() {
+	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
+	return stopRequest || state == 0;
+}
+
+bool Thread::isSuspended() {
+	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
+	return state != 1;
+}
+
+void Thread::suspend() {
+	// TODO: pauseable
+	if (!isSuspended()) {
+		sq_suspendvm(threadObj._unVal.pThread);
+	}
+}
+
+void Thread::resume() {
+	if (!isDead() && isSuspended()) {
+		sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
+	}
+}
+
+bool Thread::update(float elapsed) {
+	if (paused) {
+	} else if (waitTime > 0) {
+		waitTime -= elapsed;
+		if (waitTime <= 0) {
+			waitTime = 0;
+			resume();
+		}
+	} else if (numFrames > 0) {
+		numFrames -= 1;
+		numFrames = 0;
+		resume();
+	}
+	return isDead();
+}
+
 } // namespace Twp
diff --git a/engines/twp/vm.h b/engines/twp/vm.h
index 884ae65d530..f42951e1a41 100644
--- a/engines/twp/vm.h
+++ b/engines/twp/vm.h
@@ -22,17 +22,64 @@
 #ifndef TWPVM_H
 #define TWPVM_H
 
-#include "squirrel/squirrel.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "graphics/surface.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/ggpack.h"
 
 namespace Twp {
+class Entity {
+public:
+	HSQOBJECT obj;
+	Graphics::ManagedSurface surface;
+	Common::Rect rect;
+	int x;
+	int y;
+};
+
+class Thread {
+public:
+	Thread();
+	~Thread();
+
+	bool call();
+	bool update(float elapsed);
+	void suspend();
+	void resume();
+	bool isDead();
+	bool isSuspended();
+
+public:
+	uint64 id;
+	Common::String name;
+    bool global;
+	HSQOBJECT obj, threadObj, envObj, closureObj;
+	Common::Array<HSQOBJECT> args;
+	bool paused;
+	float waitTime;
+	int numFrames;
+	bool stopRequest;
+};
+
+class Scene {
+public:
+	Common::Array<Entity> entities;
+	Common::Array<Thread*> threads;
+	GGPackDecoder pack;
+};
+
 class Vm {
 public:
 	Vm();
 	~Vm();
+
+	void setScene(Scene* scene);
 	void exec(const SQChar *code);
 
 private:
 	HSQUIRRELVM v;
+	Scene* _scene;
 };
 } // End of namespace Twp
 


Commit: 1a05102d6bc0269dbdfb0b786049160285180c97
    https://github.com/scummvm/scummvm/commit/1a05102d6bc0269dbdfb0b786049160285180c97
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove unuseful scene

Changed paths:
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp
    engines/twp/vm.h


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 9d0bee06934..20247d817c3 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -74,8 +74,7 @@ Common::Error TwpEngine::run() {
 	Common::File f;
 	f.open("ThimbleweedPark.ggpack1");
 
-	Scene scene;
-	scene.pack.open(&f, key);
+	pack.open(&f, key);
 
 	const SQChar *code = R"(
 	function bounceImage() {
@@ -105,7 +104,6 @@ Common::Error TwpEngine::run() {
 	})";
 
 	Vm v;
-	v.setScene(&scene);
 	v.exec(code);
 
 	// Set the engine's debugger console
@@ -124,8 +122,8 @@ Common::Error TwpEngine::run() {
 		}
 
 		// update threads
-		for (int i = 0; i < scene.threads.size(); i++) {
-			Thread *thread = scene.threads[i];
+		for (int i = 0; i < threads.size(); i++) {
+			Thread *thread = threads[i];
 			if (thread->update(deltaTimeMs)) {
 				// TODO: delete it
 			}
@@ -133,8 +131,8 @@ Common::Error TwpEngine::run() {
 
 		// update screen
 		_screen->clear(0xFF808080);
-		for (int i = 0; i < scene.entities.size(); i++) {
-			Entity &ett = scene.entities[i];
+		for (int i = 0; i < entities.size(); i++) {
+			Entity &ett = entities[i];
 			_screen->transBlitFrom(ett.surface, ett.rect, Common::Point(ett.x, ett.y));
 		}
 		_screen->update();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f54be7a1f5b..d9afc8cd82d 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -22,6 +22,7 @@
 #ifndef TWP_H
 #define TWP_H
 
+#include "common/array.h"
 #include "common/scummsys.h"
 #include "common/system.h"
 #include "common/error.h"
@@ -33,8 +34,8 @@
 #include "engines/engine.h"
 #include "engines/savestate.h"
 #include "graphics/screen.h"
-
 #include "twp/detection.h"
+#include "twp/vm.h"
 
 namespace Twp {
 
@@ -49,6 +50,10 @@ protected:
 	Common::Error run() override;
 public:
 	Graphics::Screen *_screen = nullptr;
+	Common::Array<Entity> entities;
+	Common::Array<Thread*> threads;
+	GGPackDecoder pack;
+
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
 	~TwpEngine() override;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index dfa0a1a6763..b464005688b 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -41,7 +41,6 @@
 
 namespace Twp {
 
-static Scene *gScene = nullptr;
 static HSQUIRRELVM gVm = nullptr;
 
 static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) {
@@ -83,7 +82,7 @@ static SQRESULT getArray(HSQUIRRELVM v, int i, Common::Array<Common::String> &ar
 }
 
 static Thread *thread(HSQUIRRELVM v) {
-	return *Common::find_if(gScene->threads.begin(), gScene->threads.end(), [&](Thread *t) {
+	return *Common::find_if(g_engine->threads.begin(), g_engine->threads.end(), [&](Thread *t) {
 		return t->threadObj._unVal.pThread == v;
 	});
 }
@@ -182,20 +181,18 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	// TODO: share resources load png to surface
 	GGPackEntryReader r;
-	r.open(gScene->pack, sheet + ".png");
+	r.open(g_engine->pack, sheet + ".png");
 	Image::PNGDecoder d;
 	d.loadStream(r);
 	const Graphics::Surface *surface = d.getSurface();
 
 	// load sheet json
 	GGPackEntryReader r2;
-	r2.open(gScene->pack, sheet + ".json");
+	r2.open(g_engine->pack, sheet + ".json");
 	Common::Array<char> data(r2.size());
 	r2.read(&data[0], r2.size());
 	Common::String s(&data[0], r2.size());
 
-	debug(s.c_str());
-
 	// TODO: change to load each frame
 	Common::JSONValue *json = Common::JSON::parse(s.c_str());
 	const Common::JSONObject &jRect = json->asObject()["frames"]->asObject()[frames[0]]->asObject()["frame"]->asObject();
@@ -221,7 +218,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	sq_addref(gVm, &e.obj);
 	sq_pop(v, 1);
 
-	gScene->entities.push_back(e);
+	g_engine->entities.push_back(e);
 
 	sq_pushobject(v, e.obj);
 
@@ -280,7 +277,7 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 		sq_pop(v, 1); // pop name
 	sq_pop(v, 1);     // pop closure
 
-	gScene->threads.push_back(t);
+	g_engine->threads.push_back(t);
 
 	debug("create thread %s", t->name.c_str());
 
@@ -307,7 +304,7 @@ static SQInteger objectAt(HSQUIRRELVM v) {
 	SQObjectPtr id;
 	_table(o)->Get(sqObj(v, "_id"), id);
 
-	Entity *ett = Common::find_if(gScene->entities.begin(), gScene->entities.end(), [&](Entity &e) {
+	Entity *ett = Common::find_if(g_engine->entities.begin(), g_engine->entities.end(), [&](Entity &e) {
 		SQObjectPtr id2;
 		_table(e.obj)->Get(sqObj(v, "_id"), id2);
 		return _integer(id) == _integer(id2);
@@ -386,8 +383,7 @@ static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) {
 	debug("TWP: %s", buf);
 }
 
-Vm::Vm()
-	: _scene(nullptr) {
+Vm::Vm() {
 	gVm = v = sq_open(1024);
 	sq_setcompilererrorhandler(v, errorHandler);
 	sq_newclosure(v, aux_printerror, 0);
@@ -412,10 +408,8 @@ Vm::Vm()
 }
 
 Vm::~Vm() {
-	if(_scene) {
-		for (int i = 0; i < _scene->threads.size(); i++) {
-			delete _scene->threads[i];
-		}
+	for (int i = 0; i < g_engine->threads.size(); i++) {
+		delete g_engine->threads[i];
 	}
 	sq_close(v);
 }
@@ -424,10 +418,6 @@ void Vm::exec(const SQChar *code) {
 	sqExec(v, code);
 }
 
-void Vm::setScene(Scene *scene) {
-	gScene = _scene = scene;
-}
-
 Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
 }
 
diff --git a/engines/twp/vm.h b/engines/twp/vm.h
index f42951e1a41..1f7ebf972e5 100644
--- a/engines/twp/vm.h
+++ b/engines/twp/vm.h
@@ -62,24 +62,15 @@ public:
 	bool stopRequest;
 };
 
-class Scene {
-public:
-	Common::Array<Entity> entities;
-	Common::Array<Thread*> threads;
-	GGPackDecoder pack;
-};
-
 class Vm {
 public:
 	Vm();
 	~Vm();
 
-	void setScene(Scene* scene);
 	void exec(const SQChar *code);
 
 private:
 	HSQUIRRELVM v;
-	Scene* _scene;
 };
 } // End of namespace Twp
 


Commit: 1d6ce20f950ef03893d5ffed8d374079674664b6
    https://github.com/scummvm/scummvm/commit/1d6ce20f950ef03893d5ffed8d374079674664b6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add OpenGL graphics

Changed paths:
  A engines/twp/gfx.cpp
  A engines/twp/gfx.h
    engines/twp/detection_tables.h
    engines/twp/module.mk
    engines/twp/twp.cpp


diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index 900b8c6361e..8f3e3cdf041 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -31,8 +31,8 @@ const ADGameDescription gameDescriptions[] = {
 		"twp",
 		nullptr,
 		AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
-		Common::EN_ANY,
-		Common::kPlatformWindows,
+		Common::UNK_LANG,
+		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NONE)
 	},
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
new file mode 100644
index 00000000000..0cce0ff24c0
--- /dev/null
+++ b/engines/twp/gfx.cpp
@@ -0,0 +1,397 @@
+/* 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 "gfx.h"
+#include "common/debug.h"
+#include "graphics/opengl/context.h"
+#include "graphics/opengl/system_headers.h"
+
+static Texture gEmptyTexture;
+
+#ifdef DEBUG
+	#define GL_CHECK(expr)                           \
+		do {                                         \
+			(expr);                                  \
+			checkGLError(__FILE__, __LINE__, #expr); \
+		} while (false)
+	#define GL_CHECK0()                           \
+		do {                                      \
+			checkGLError(__FILE__, __LINE__, ""); \
+		} while (false)
+#else
+	#define GL_CHECK(expr) (expr)
+	#define GL_CHECK0()
+#endif
+
+static void checkGLError(const char *filename, int line, const char *info) {
+	int err = glGetError();
+	if (err != GL_NO_ERROR) {
+		const char *name = nullptr;
+		const char *desc = nullptr;
+		switch (err) {
+		case GL_INVALID_ENUM:
+			name = "GL_INVALID_ENUM";
+			desc = "An unacceptable value is specified for an enumerated argument.";
+			break;
+		case GL_INVALID_VALUE:
+			name = "GL_INVALID_VALUE";
+			desc = "A numeric argument is out of range.";
+			break;
+		case GL_INVALID_OPERATION:
+			name = "GL_INVALID_OPERATION";
+			desc = "The specified operation is not allowed in the current state.";
+			break;
+		case GL_INVALID_FRAMEBUFFER_OPERATION:
+			name = "GL_INVALID_FRAMEBUFFER_OPERATION";
+			desc = "The command is trying to render to or read from the framebuffer while the currently bound framebuffer is not framebuffer complete.";
+			break;
+		case GL_OUT_OF_MEMORY:
+			name = "GL_OUT_OF_MEMORY";
+			desc = "There is not enough memory left to execute the command.";
+			break;
+		default:
+			break;
+		}
+		if (name) {
+			debug("%s: %s at %s:%d(%s)", name, desc, filename, line, info);
+		} else {
+			debug("Code %d at %s:%d(%s)", err, filename, line, info);
+		}
+	}
+}
+
+Math::Matrix4 ortho(float left, float right, float bottom, float top, float zNear, float zFar) {
+	Math::Matrix4 result;
+	float *m = result.getData();
+	m[0] = 2.f / (right - left);
+	m[5] = 2.f / (top - bottom);
+	m[10] = -2.f / (zFar - zNear);
+	m[12] = -(right + left) / (right - left);
+	m[13] = -(top + bottom) / (top - bottom);
+	m[14] = -(zFar + zNear) / (zFar - zNear);
+	m[15] = 1;
+	return result;
+}
+
+struct Use {
+public:
+	Use(GLint program) : _prev(0), program(program) {
+		glGetIntegerv(GL_CURRENT_PROGRAM, &_prev);
+		if (_prev != program)
+			glUseProgram(program);
+	}
+
+	~Use() {
+		if (_prev != program)
+			glUseProgram(_prev);
+	}
+
+private:
+	GLint _prev, program;
+};
+
+static Use use(GLint program) {
+	Use use(program);
+	return use;
+}
+
+static GLint getFormat(int channels) {
+	switch (channels) {
+	case 3:
+		return GL_RGB;
+	case 4:
+		return GL_RGBA;
+	default:
+		error("Can't get format for %d channels", channels);
+	}
+}
+
+void Texture::load(const Graphics::Surface &surface) {
+	width = surface.w;
+	height = surface.h;
+	const void *data = surface.getPixels();
+	glGenTextures(1, &id);
+	glBindTexture(GL_TEXTURE_2D, id);
+	// set the texture wrapping/filtering options (on the currently bound texture object)
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glPixelStorei(GL_UNPACK_ALIGNMENT, surface.format.bytesPerPixel);
+	GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, getFormat(surface.format.bytesPerPixel), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));
+}
+
+void Texture::bind(const Texture *pTexture) {
+	if (pTexture && pTexture->id) {
+		GL_CHECK(glBindTexture(GL_TEXTURE_2D, pTexture->id));
+	} else {
+		GL_CHECK(glBindTexture(GL_TEXTURE_2D, 0));
+	}
+}
+
+Shader::Shader() {
+}
+
+void Shader::init(const char *vertex, const char *fragment) {
+	if (vertex) {
+		_vertex = loadShader(vertex, GL_VERTEX_SHADER);
+	}
+	if (fragment) {
+		_fragment = loadShader(fragment, GL_FRAGMENT_SHADER);
+	}
+	GL_CHECK(program = glCreateProgram());
+	GL_CHECK(glAttachShader(program, _vertex));
+	GL_CHECK(glAttachShader(program, _fragment));
+	GL_CHECK(glLinkProgram(program));
+}
+
+uint32 Shader::loadShader(const char *code, uint32 shaderType) {
+	uint32 result;
+	GL_CHECK(result = glCreateShader(shaderType));
+	GL_CHECK(glShaderSource(result, 1, &code, nullptr));
+	GL_CHECK(glCompileShader(result));
+	statusShader(result);
+	return result;
+}
+
+void Shader::statusShader(uint32 shader) {
+	int status;
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+	if (status != GL_TRUE) {
+		int logLength;
+		char message[1024];
+		glGetShaderInfoLog(shader, 1024, &logLength, &message[0]);
+		debug("%s", message);
+	}
+}
+
+int Shader::getUniformLocation(const char *name) {
+	int loc;
+	GL_CHECK(loc = glGetUniformLocation(program, name));
+	return loc;
+}
+
+void Shader::setUniform(const char *name, Math::Matrix4 value) {
+	use(program);
+	int loc = getUniformLocation(name);
+	GL_CHECK(glUniformMatrix4fv(loc, 1, GL_FALSE, value.getData()));
+}
+
+Gfx::Gfx() : _vbo(0), _ebo(0) {
+}
+
+void Gfx::init() {
+	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
+	byte pixels[] = {0xFF, 0xFF, 0xFF, 0xFF};
+	Graphics::Surface empty;
+	empty.w = 1;
+	empty.h = 1;
+	empty.format = fmt;
+	empty.setPixels(pixels);
+	gEmptyTexture.load(empty);
+	const char *vsrc = R"(#version 110
+	uniform mat4 u_transform;
+	attribute vec2 a_position;
+	attribute vec4 a_color;
+	attribute vec2 a_texCoords;
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	void main() {
+		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+		v_color = a_color;
+		v_texCoords = a_texCoords;
+	})";
+	const char* fsrc = R"(#version 110
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	uniform sampler2D u_texture;
+	void main() {
+		vec4 tex_color = texture2D(u_texture, v_texCoords);
+		gl_FragColor = v_color * tex_color;
+	})";
+	_shader.init(vsrc, fsrc);
+	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
+
+	GL_CHECK(glGenBuffers(1, &_vbo));
+	GL_CHECK(glGenBuffers(1, &_ebo));
+	GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+	GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
+	GLint p, c, t, tex, tr;
+	GL_CHECK(p = glGetAttribLocation(_shader.program, "a_position"));
+	GL_CHECK(c = glGetAttribLocation(_shader.program, "a_color"));
+	GL_CHECK(t = glGetAttribLocation(_shader.program, "a_texCoords"));
+	GL_CHECK(tex = glGetUniformLocation(_shader.program, "u_texture"));
+	GL_CHECK(tr = glGetUniformLocation(_shader.program, "u_transform"));
+	GL_CHECK(glVertexAttribPointer(p, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+	GL_CHECK(glEnableVertexAttribArray(p));
+	GL_CHECK(glVertexAttribPointer(c, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+	GL_CHECK(glEnableVertexAttribArray(c));
+	GL_CHECK(glVertexAttribPointer(t, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+	GL_CHECK(glEnableVertexAttribArray(c));
+
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void Gfx::clear(Color color) {
+	glClearColor(color.rgba.r, color.rgba.g, color.rgba.b, color.rgba.a);
+	glClear(GL_COLOR_BUFFER_BIT);
+}
+
+Math::Matrix4 Gfx::getFinalTransform(Math::Matrix4 trsf) {
+	return _mvp * trsf;
+}
+
+void Gfx::noTexture() {
+	_texture = &gEmptyTexture;
+	GL_CHECK(glBindTexture(GL_TEXTURE_2D, gEmptyTexture.id));
+}
+
+void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
+	noTexture();
+	drawPrimitives(GL_LINE_STRIP, vertices, count, trsf);
+}
+
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf) {
+	if (v_size > 0) {
+		// set blending
+		GL_CHECK(glEnable(GL_BLEND));
+		GL_CHECK(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
+		GL_CHECK(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+
+		GL_CHECK(glUseProgram(_shader.program));
+
+		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CHECK(glEnableVertexAttribArray(0));
+		GL_CHECK(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+		GL_CHECK(glEnableVertexAttribArray(2));
+		GL_CHECK(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+		GL_CHECK(glEnableVertexAttribArray(1));
+		GL_CHECK(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+
+		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
+
+		GL_CHECK(glActiveTexture(GL_TEXTURE0));
+		GL_CHECK(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		GL_CHECK(glUniform1i(0, 0));
+
+		_shader.setUniform("u_transform", getFinalTransform(trsf));
+		GL_CHECK(glDrawArrays((GLenum)primitivesType, 0, v_size));
+
+		glUseProgram(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+		GL_CHECK(glDisableVertexAttribArray(0));
+		GL_CHECK(glDisableVertexAttribArray(1));
+		GL_CHECK(glDisableVertexAttribArray(2));
+
+		glDisable(GL_BLEND);
+	}
+}
+
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf) {
+	if (i_size > 0) {
+		// set blending
+		GL_CHECK(glEnable(GL_BLEND));
+		GL_CHECK(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
+		GL_CHECK(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+
+		GL_CHECK(glUseProgram(_shader.program));
+
+		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+
+		GL_CHECK(glEnableVertexAttribArray(0));
+		GL_CHECK(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+		GL_CHECK(glEnableVertexAttribArray(2));
+		GL_CHECK(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+		GL_CHECK(glEnableVertexAttribArray(1));
+		GL_CHECK(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+
+		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
+		GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
+		GL_CHECK(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * i_size, indices, GL_STREAM_DRAW));
+
+		GL_CHECK(glActiveTexture(GL_TEXTURE0));
+		GL_CHECK(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		GL_CHECK(glUniform1i(0, 0));
+
+		_shader.setUniform("u_transform", getFinalTransform(trsf));
+		GL_CHECK(glDrawElements(primitivesType, i_size, GL_UNSIGNED_INT, NULL));
+
+		glUseProgram(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		GL_CHECK(glDisableVertexAttribArray(0));
+		GL_CHECK(glDisableVertexAttribArray(1));
+		GL_CHECK(glDisableVertexAttribArray(2));
+
+		glDisable(GL_BLEND);
+	}
+}
+
+void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf) {
+	drawPrimitives(GL_TRIANGLES, vertices, v_size, indices, i_size, trsf);
+}
+
+void Gfx::drawQuad(Math::Vector2d pos, Math::Vector2d size, Color color, Math::Matrix4 trsf) {
+	float w = size.getX();
+	float h = size.getY();
+	float x = pos.getX();
+	float y = pos.getY();
+	Vertex vertices[] = {
+		Vertex{.pos = {x + w, y + h}, .texCoords = {1, 0}, .color = color},
+		Vertex{.pos = {x + w, y}, .texCoords = {1, 1}, .color = color},
+		Vertex{.pos = {x, y}, .texCoords = {0, 1}, .color = color},
+		Vertex{.pos = {x, y + h}, .texCoords = {0, 0}, .color = color}};
+	noTexture();
+	uint32 quadIndices[] = {
+		0, 1, 3,
+		1, 2, 3};
+	draw(vertices, 4, quadIndices, 6, trsf);
+}
+
+void Gfx::drawSprite(Math::Vector2d pos, Common::Rect textRect, Texture &texture, Color color, Math::Matrix4 trsf, bool flipX, bool flipY) {
+	float l = textRect.left / (float)texture.width;
+	float r = textRect.right / (float)texture.width;
+	float t = textRect.top / (float)texture.height;
+	float b = textRect.bottom / (float)texture.height;
+	if (flipX)
+		SWAP(l, r);
+	if (flipY)
+		SWAP(t, b);
+
+	Vertex vertices[] = {
+		{.pos = {pos.getX() + textRect.width(), pos.getY() + textRect.height()}, .texCoords = {r, t}, .color = color},
+		{.pos = {pos.getX() + textRect.width(), pos.getY()}, .texCoords = {r, b}, .color = color},
+		{.pos = {pos.getX(), pos.getY()}, .texCoords = {l, b}, .color = color},
+		{.pos = {pos.getX(), pos.getY() + textRect.height()}, .texCoords = {l, t}, .color = color}};
+	_texture = &texture;
+	GL_CHECK(glBindTexture(GL_TEXTURE_2D, texture.id));
+	uint32 quadIndices[] = {
+		0, 1, 3,
+		1, 2, 3};
+	draw(vertices, 4, quadIndices, 6, trsf);
+}
+
+void Gfx::camera(float w, float h) {
+	_cameraSize = Math::Vector2d(w, h);
+	_mvp = ortho(0.f, w, 0.f, h, -1.f, 1.f);
+}
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
new file mode 100644
index 00000000000..002931b7a0f
--- /dev/null
+++ b/engines/twp/gfx.h
@@ -0,0 +1,132 @@
+/* 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 TWP_GFX_H
+#define TWP_GFX_H
+
+#include "common/array.h"
+#include "graphics/surface.h"
+#include "common/hashmap.h"
+#include "common/rect.h"
+#include "math/vector2d.h"
+#include "math/matrix4.h"
+
+struct Color {
+	union {
+		float v[4];
+		struct {
+			float r;
+			float g;
+			float b;
+			float a;
+		} rgba;
+	};
+
+	Color(float red = 1.0f, float green = 1.0f, float blue = 1.0f, float alpha = 1.0f) {
+		rgba.r = red;
+		rgba.g = green;
+		rgba.b = blue;
+		rgba.a = alpha;
+	}
+
+	static Color create(uint8 red, uint8 green, uint8 blue, uint8 alpha = 0xFF) {
+		return Color(red / 255.f, green / 255.f, blue / 255.f, alpha / 255.f);
+	}
+};
+
+// This is a point in 2D with a color and texture coordinates
+struct Vertex {
+public:
+	Math::Vector2d pos;
+	Color color;
+	Math::Vector2d texCoords;
+};
+
+class Texture {
+public:
+	void load(const Graphics::Surface& surface);
+	static void bind(const Texture *pTexture);
+
+public:
+	uint32 id;
+    int width, height;
+    uint32 fbo;
+};
+
+struct TextureSlot {
+	int id;
+	Texture texture;
+};
+
+class Shader {
+public:
+	Shader();
+
+	void init(const char *vertex, const char *fragment);
+
+	void setUniform(const char *name, Math::Matrix4 value);
+
+private:
+	uint32 loadShader(const char *code, uint32 shaderType);
+	void statusShader(uint32 shader);
+	int getUniformLocation(const char *name);
+
+public:
+	uint32 program;
+
+private:
+	uint32 _vertex;
+	uint32 _fragment;
+	Common::HashMap<int, TextureSlot> _textures;
+};
+
+typedef Common::HashMap<int, TextureSlot> Textures;
+
+class Gfx {
+public:
+	Gfx();
+
+	void init();
+
+	void camera(float w, float h);
+
+	void clear(Color color);
+	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, Math::Matrix4 transf = Math::Matrix4());
+	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 transf = Math::Matrix4());
+	void drawLines(Vertex* vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
+	void draw(Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 trsf = Math::Matrix4());
+	void drawQuad(Math::Vector2d pos, Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
+	void drawSprite(Math::Vector2d pos, Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
+
+	void noTexture();
+	Math::Matrix4 getFinalTransform(Math::Matrix4 trsf);
+
+private:
+	uint32 _vbo, _ebo;
+	Shader _shader;
+	Color _color;
+	Math::Matrix4 _mvp;
+	Math::Vector2d _cameraSize, _cameraPos;
+	Textures _textures;
+	Texture* _texture;
+};
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index d0e7a82e97c..084a28af0a7 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -28,6 +28,7 @@ MODULE_OBJS = \
 	metaengine.o \
 	vm.o \
 	ggpack.o \
+	gfx.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 20247d817c3..6557a19beda 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -28,6 +28,7 @@
 #include "twp/console.h"
 #include "twp/vm.h"
 #include "twp/ggpack.h"
+#include "twp/gfx.h"
 #include "common/scummsys.h"
 #include "common/config-manager.h"
 #include "common/debug-channels.h"
@@ -37,6 +38,7 @@
 #include "image/png.h"
 #include "engines/util.h"
 #include "graphics/palette.h"
+#include "graphics/opengl/system_headers.h"
 
 namespace Twp {
 
@@ -62,9 +64,10 @@ Common::String TwpEngine::getGameId() const {
 }
 
 Common::Error TwpEngine::run() {
-	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
-	initGraphics(1280, 720, &fmt);
-	_screen = new Graphics::Screen(1280, 720, fmt);
+	const int screenWidth = 1280;
+	const int screenHeight = 720;
+	initGraphics3d(screenWidth, screenHeight);
+	_screen = new Graphics::Screen(screenWidth, screenHeight);
 
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
@@ -99,10 +102,19 @@ Common::Error TwpEngine::run() {
 		} while (1);
 	}
 
-	for (local i = 1; i <= 100; i++) {
+	for (local i = 1; i <= 200; i++) {
 		startthread(bounceImage);
 	})";
 
+	GGPackEntryReader r;
+	r.open(g_engine->pack, "RaySheet.png");
+	Image::PNGDecoder d;
+	d.loadStream(r);
+	const Graphics::Surface *surface = d.getSurface();
+
+	Texture texture;
+	texture.load(*surface);
+
 	Vm v;
 	v.exec(code);
 
@@ -114,6 +126,9 @@ Common::Error TwpEngine::run() {
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
+	Gfx gfx;
+	gfx.init();
+	gfx.camera(screenWidth, screenHeight);
 	// Simple event handling loop
 	Common::Event e;
 	while (!shouldQuit()) {
@@ -130,12 +145,12 @@ Common::Error TwpEngine::run() {
 		}
 
 		// update screen
-		_screen->clear(0xFF808080);
+		gfx.clear(Color(0.8f, 0.8f, 0.8f));
 		for (int i = 0; i < entities.size(); i++) {
 			Entity &ett = entities[i];
-			_screen->transBlitFrom(ett.surface, ett.rect, Common::Point(ett.x, ett.y));
+			gfx.drawSprite(Math::Vector2d(ett.x, ett.y), ett.rect, texture);
 		}
-		_screen->update();
+		g_system->updateScreen();
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded


Commit: c85dd17062b73d3874688172dedef4d9380d0830
    https://github.com/scummvm/scummvm/commit/c85dd17062b73d3874688172dedef4d9380d0830
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add a resource manager

Changed paths:
  A engines/twp/resmanager.cpp
  A engines/twp/resmanager.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp
    engines/twp/vm.h


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 0cce0ff24c0..126b2c5e0fe 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -24,6 +24,8 @@
 #include "graphics/opengl/context.h"
 #include "graphics/opengl/system_headers.h"
 
+namespace Twp {
+
 static Texture gEmptyTexture;
 
 #ifdef DEBUG
@@ -395,3 +397,4 @@ void Gfx::camera(float w, float h) {
 	_cameraSize = Math::Vector2d(w, h);
 	_mvp = ortho(0.f, w, 0.f, h, -1.f, 1.f);
 }
+}
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 002931b7a0f..eab2bb81460 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -29,6 +29,8 @@
 #include "math/vector2d.h"
 #include "math/matrix4.h"
 
+namespace Twp {
+
 struct Color {
 	union {
 		float v[4];
@@ -128,5 +130,6 @@ private:
 	Textures _textures;
 	Texture* _texture;
 };
+}
 
 #endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 084a28af0a7..fdd0ef008f8 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -29,6 +29,7 @@ MODULE_OBJS = \
 	vm.o \
 	ggpack.o \
 	gfx.o \
+	resmanager.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
new file mode 100644
index 00000000000..bf28037d97d
--- /dev/null
+++ b/engines/twp/resmanager.cpp
@@ -0,0 +1,65 @@
+/* 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 "common/str.h"
+#include "image/png.h"
+#include "twp/resmanager.h"
+#include "twp/gfx.h"
+#include "twp/twp.h"
+#include "twp/ggpack.h"
+
+namespace Twp {
+
+Common::String getKey(const char *path) {
+	int len = strlen(path);
+	Common::String p(path);
+	size_t i = p.findLastOf(".");
+	p = p.substr(0, i);
+	if ((len > 4) && scumm_strnicmp(p.c_str() + 4, "_en", 3) == 0) {
+		// TODO
+		//Common::String lang = prefs(Lang);
+		Common::String lang = "en";
+		Common::String filename(path, len - 3);
+		const char* ext = path+i;
+		return Common::String::format("%s_%s%s", filename.c_str(), lang.c_str(), ext);
+	}
+	return path;
+}
+
+void ResManager::loadTexture(const Common::String& name) {
+	debug("Load texture %s", name.c_str());
+	GGPackEntryReader r;
+	r.open(g_engine->pack, name);
+	Image::PNGDecoder d;
+	d.loadStream(r);
+	const Graphics::Surface *surface = d.getSurface();
+
+	_textures[name].load(*surface);
+}
+
+Texture *ResManager::texture(const Common::String& name) {
+	Common::String key = getKey(name.c_str());
+	if (!_textures.contains(key)) {
+		loadTexture(key.c_str());
+	}
+	return &_textures[key];
+}
+}
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
new file mode 100644
index 00000000000..14dec6bac28
--- /dev/null
+++ b/engines/twp/resmanager.h
@@ -0,0 +1,45 @@
+/* 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 TWP_RESMANAGER_H
+#define TWP_RESMANAGER_H
+
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "twp/gfx.h"
+
+namespace Twp {
+
+class ResManager {
+public:
+	ResManager() {}
+
+	Texture *texture(const Common::String &name);
+
+private:
+	void loadTexture(const Common::String &name);
+
+private:
+	Common::HashMap<Common::String, Texture> _textures; // textures cache
+};
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 6557a19beda..082c81a4cd9 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -106,15 +106,6 @@ Common::Error TwpEngine::run() {
 		startthread(bounceImage);
 	})";
 
-	GGPackEntryReader r;
-	r.open(g_engine->pack, "RaySheet.png");
-	Image::PNGDecoder d;
-	d.loadStream(r);
-	const Graphics::Surface *surface = d.getSurface();
-
-	Texture texture;
-	texture.load(*surface);
-
 	Vm v;
 	v.exec(code);
 
@@ -148,7 +139,7 @@ Common::Error TwpEngine::run() {
 		gfx.clear(Color(0.8f, 0.8f, 0.8f));
 		for (int i = 0; i < entities.size(); i++) {
 			Entity &ett = entities[i];
-			gfx.drawSprite(Math::Vector2d(ett.x, ett.y), ett.rect, texture);
+			gfx.drawSprite(Math::Vector2d(ett.x, ett.y), ett.rect, *ett.texture);
 		}
 		g_system->updateScreen();
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index d9afc8cd82d..9331b099e3c 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -36,6 +36,7 @@
 #include "graphics/screen.h"
 #include "twp/detection.h"
 #include "twp/vm.h"
+#include "twp/resmanager.h"
 
 namespace Twp {
 
@@ -53,6 +54,7 @@ public:
 	Common::Array<Entity> entities;
 	Common::Array<Thread*> threads;
 	GGPackDecoder pack;
+	ResManager resManager;
 
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index b464005688b..a78f6c7c985 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -179,13 +179,6 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	debug("Create object: %s, %u", sheet.c_str(), frames.size());
 
-	// TODO: share resources load png to surface
-	GGPackEntryReader r;
-	r.open(g_engine->pack, sheet + ".png");
-	Image::PNGDecoder d;
-	d.loadStream(r);
-	const Graphics::Surface *surface = d.getSurface();
-
 	// load sheet json
 	GGPackEntryReader r2;
 	r2.open(g_engine->pack, sheet + ".json");
@@ -206,7 +199,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	// TODO: create an entity
 	Entity e;
-	e.surface.copyFrom(*surface);
+	e.texture = g_engine->resManager.texture(sheet + ".png");
 	e.rect = Common::Rect(x, y, x + w, y + h);
 
 	static int gId = 3000;
diff --git a/engines/twp/vm.h b/engines/twp/vm.h
index 1f7ebf972e5..3e4ed91de57 100644
--- a/engines/twp/vm.h
+++ b/engines/twp/vm.h
@@ -27,12 +27,13 @@
 #include "graphics/surface.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/ggpack.h"
+#include "twp/gfx.h"
 
 namespace Twp {
 class Entity {
 public:
 	HSQOBJECT obj;
-	Graphics::ManagedSurface surface;
+	Texture* texture;
 	Common::Rect rect;
 	int x;
 	int y;


Commit: b7adc07de97b4f369a1c3149644a9a894bfac97a
    https://github.com/scummvm/scummvm/commit/b7adc07de97b4f369a1c3149644a9a894bfac97a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add spritesheet

Changed paths:
  A engines/twp/spritesheet.cpp
  A engines/twp/spritesheet.h
    engines/twp/module.mk
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/vm.cpp


diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index fdd0ef008f8..2898f593668 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -30,6 +30,7 @@ MODULE_OBJS = \
 	ggpack.o \
 	gfx.o \
 	resmanager.o \
+	spritesheet.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index bf28037d97d..d4f9604cf89 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -62,4 +62,25 @@ Texture *ResManager::texture(const Common::String& name) {
 	}
 	return &_textures[key];
 }
+
+void ResManager::loadSpriteSheet(const Common::String& name) {
+	GGPackEntryReader r;
+	r.open(g_engine->pack, name);
+
+	// read all contents
+	Common::Array<char> data(r.size());
+	r.read(&data[0], r.size());
+
+	Common::String s(&data[0], r.size());
+	_spriteSheets[name].parseSpriteSheet(s);
+}
+
+SpriteSheet *ResManager::spriteSheet(const Common::String& name) {
+	Common::String key = getKey(name.c_str());
+	if (!_spriteSheets.contains(key)) {
+		loadSpriteSheet(key.c_str());
+	}
+	return &_spriteSheets[key];
+}
+
 }
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index 14dec6bac28..12a50e90273 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -25,6 +25,7 @@
 #include "common/str.h"
 #include "common/hashmap.h"
 #include "twp/gfx.h"
+#include "twp/spritesheet.h"
 
 namespace Twp {
 
@@ -33,12 +34,15 @@ public:
 	ResManager() {}
 
 	Texture *texture(const Common::String &name);
+	SpriteSheet *spriteSheet(const Common::String& name);
 
 private:
 	void loadTexture(const Common::String &name);
+	void loadSpriteSheet(const Common::String &name);
 
 private:
-	Common::HashMap<Common::String, Texture> _textures; // textures cache
+	Common::HashMap<Common::String, Texture> _textures;
+	Common::HashMap<Common::String, SpriteSheet> _spriteSheets;
 };
 } // namespace Twp
 
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
new file mode 100644
index 00000000000..2a1aa4cb56c
--- /dev/null
+++ b/engines/twp/spritesheet.cpp
@@ -0,0 +1,57 @@
+
+/* 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 "common/formats/json.h"
+#include "twp/spritesheet.h"
+
+namespace Twp {
+
+static void parseSize(const Common::JSONObject &value, Math::Vector2d &v) {
+	v.setX(value["w"]->asIntegerNumber());
+	v.setY(value["h"]->asIntegerNumber());
+}
+
+static void parseRect(const Common::JSONObject &value, Common::Rect &rect) {
+	rect.left = value["x"]->asIntegerNumber();
+	rect.top = value["y"]->asIntegerNumber();
+	rect.setWidth(value["w"]->asIntegerNumber());
+	rect.setHeight(value["h"]->asIntegerNumber());
+}
+
+static void parseFrame(const Common::String &key, const Common::JSONObject &value, SpriteSheetFrame &frame) {
+	frame.name = key;
+	parseRect(value["frame"]->asObject(), frame.frame);
+	parseRect(value["spriteSourceSize"]->asObject(), frame.spriteSourceSize);
+	parseSize(value["sourceSize"]->asObject(), frame.sourceSize);
+}
+
+void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
+	Common::JSONValue *json = Common::JSON::parse(contents.c_str());
+	const Common::JSONObject &obj = json->asObject()["frames"]->asObject();
+	for (auto it = obj.begin(); it != obj.end(); it++) {
+		parseFrame(it->_key, it->_value->asObject(), frameTable[it->_key]);
+	}
+
+	delete json;
+}
+
+} // namespace Twp
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
new file mode 100644
index 00000000000..19d82a4cea6
--- /dev/null
+++ b/engines/twp/spritesheet.h
@@ -0,0 +1,48 @@
+
+/* 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 TWP_SPRITESHEET_H
+#define TWP_SPRITESHEET_H
+
+#include "common/hashmap.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "math/vector2d.h"
+
+namespace Twp {
+
+struct SpriteSheetFrame {
+  Common::String name;
+  Common::Rect frame;
+  Common::Rect spriteSourceSize;
+  Math::Vector2d sourceSize;
+};
+
+struct SpriteSheet {
+	Common::HashMap<Common::String, SpriteSheetFrame> frameTable;
+
+	void parseSpriteSheet(const Common::String& contents);
+};
+
+}
+
+#endif // TWP_SPRITESHEET_H
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index a78f6c7c985..87182e80969 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -180,27 +180,12 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	debug("Create object: %s, %u", sheet.c_str(), frames.size());
 
 	// load sheet json
-	GGPackEntryReader r2;
-	r2.open(g_engine->pack, sheet + ".json");
-	Common::Array<char> data(r2.size());
-	r2.read(&data[0], r2.size());
-	Common::String s(&data[0], r2.size());
-
-	// TODO: change to load each frame
-	Common::JSONValue *json = Common::JSON::parse(s.c_str());
-	const Common::JSONObject &jRect = json->asObject()["frames"]->asObject()[frames[0]]->asObject()["frame"]->asObject();
-
-	int x = (int)jRect["x"]->asIntegerNumber();
-	int y = (int)jRect["y"]->asIntegerNumber();
-	int w = (int)jRect["w"]->asIntegerNumber();
-	int h = (int)jRect["h"]->asIntegerNumber();
-
-	delete json;
+	SpriteSheet* spritesheet = g_engine->resManager.spriteSheet(sheet + ".json");
 
 	// TODO: create an entity
 	Entity e;
 	e.texture = g_engine->resManager.texture(sheet + ".png");
-	e.rect = Common::Rect(x, y, x + w, y + h);
+	e.rect = spritesheet->frameTable[frames[0]].frame;
 
 	static int gId = 3000;
 	sq_newtable(v);


Commit: 3a7ea6029a9f448bb254e8224e707c621332f9c0
    https://github.com/scummvm/scummvm/commit/3a7ea6029a9f448bb254e8224e707c621332f9c0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add room

Changed paths:
  A engines/twp/room.cpp
  A engines/twp/room.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/resmanager.cpp
    engines/twp/spritesheet.cpp
    engines/twp/spritesheet.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 126b2c5e0fe..d927ee6b090 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -258,7 +258,9 @@ void Gfx::clear(Color color) {
 }
 
 Math::Matrix4 Gfx::getFinalTransform(Math::Matrix4 trsf) {
-	return _mvp * trsf;
+	Math::Matrix4 t(trsf);
+	t.transpose();
+	return t * _mvp;
 }
 
 void Gfx::noTexture() {
@@ -295,7 +297,8 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 		GL_CHECK(glBindTexture(GL_TEXTURE_2D, _texture->id));
 		GL_CHECK(glUniform1i(0, 0));
 
-		_shader.setUniform("u_transform", getFinalTransform(trsf));
+		Math::Matrix4 m = getFinalTransform(trsf);
+		_shader.setUniform("u_transform", m);
 		GL_CHECK(glDrawArrays((GLenum)primitivesType, 0, v_size));
 
 		glUseProgram(0);
@@ -353,11 +356,11 @@ void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::
 	drawPrimitives(GL_TRIANGLES, vertices, v_size, indices, i_size, trsf);
 }
 
-void Gfx::drawQuad(Math::Vector2d pos, Math::Vector2d size, Color color, Math::Matrix4 trsf) {
+void Gfx::drawQuad(Math::Vector2d size, Color color, Math::Matrix4 trsf) {
 	float w = size.getX();
 	float h = size.getY();
-	float x = pos.getX();
-	float y = pos.getY();
+	float x = 0;
+	float y = 0;
 	Vertex vertices[] = {
 		Vertex{.pos = {x + w, y + h}, .texCoords = {1, 0}, .color = color},
 		Vertex{.pos = {x + w, y}, .texCoords = {1, 1}, .color = color},
@@ -370,7 +373,7 @@ void Gfx::drawQuad(Math::Vector2d pos, Math::Vector2d size, Color color, Math::M
 	draw(vertices, 4, quadIndices, 6, trsf);
 }
 
-void Gfx::drawSprite(Math::Vector2d pos, Common::Rect textRect, Texture &texture, Color color, Math::Matrix4 trsf, bool flipX, bool flipY) {
+void Gfx::drawSprite(Common::Rect textRect, Texture &texture, Color color, Math::Matrix4 trsf, bool flipX, bool flipY) {
 	float l = textRect.left / (float)texture.width;
 	float r = textRect.right / (float)texture.width;
 	float t = textRect.top / (float)texture.height;
@@ -380,6 +383,7 @@ void Gfx::drawSprite(Math::Vector2d pos, Common::Rect textRect, Texture &texture
 	if (flipY)
 		SWAP(t, b);
 
+	Math::Vector2d pos;
 	Vertex vertices[] = {
 		{.pos = {pos.getX() + textRect.width(), pos.getY() + textRect.height()}, .texCoords = {r, t}, .color = color},
 		{.pos = {pos.getX() + textRect.width(), pos.getY()}, .texCoords = {r, b}, .color = color},
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index eab2bb81460..54d43cd5916 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -115,18 +115,21 @@ public:
 	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 transf = Math::Matrix4());
 	void drawLines(Vertex* vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
 	void draw(Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 trsf = Math::Matrix4());
-	void drawQuad(Math::Vector2d pos, Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
-	void drawSprite(Math::Vector2d pos, Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
+	void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
+	void drawSprite(Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
 
 	void noTexture();
 	Math::Matrix4 getFinalTransform(Math::Matrix4 trsf);
 
+public:
+	Math::Vector2d _cameraPos;
+
 private:
 	uint32 _vbo, _ebo;
 	Shader _shader;
 	Color _color;
 	Math::Matrix4 _mvp;
-	Math::Vector2d _cameraSize, _cameraPos;
+	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture* _texture;
 };
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 2898f593668..a83db4c051c 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -31,6 +31,7 @@ MODULE_OBJS = \
 	gfx.o \
 	resmanager.o \
 	spritesheet.o \
+	room.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index d4f9604cf89..bd34f6a4cc9 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -65,7 +65,7 @@ Texture *ResManager::texture(const Common::String& name) {
 
 void ResManager::loadSpriteSheet(const Common::String& name) {
 	GGPackEntryReader r;
-	r.open(g_engine->pack, name);
+	r.open(g_engine->pack, name + ".json");
 
 	// read all contents
 	Common::Array<char> data(r.size());
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
new file mode 100644
index 00000000000..f7cc6fe32af
--- /dev/null
+++ b/engines/twp/room.cpp
@@ -0,0 +1,81 @@
+#include "twp/twp.h"
+#include "twp/room.h"
+#include "twp/ggpack.h"
+
+namespace Twp {
+
+static Math::Vector2d parseVec2(const Common::String &s) {
+	float x, y;
+	sscanf(s.c_str(), "{%f,%f}", &x, &y);
+	return {x, y};
+}
+
+static Math::Vector2d parseParallax(const Common::JSONValue &v) {
+	if (v.isIntegerNumber()) {
+		return {(float)v.asIntegerNumber(), 1};
+	}
+	if (v.isNumber()) {
+		return {(float)v.asNumber(), 1};
+	}
+	if (v.isString()) {
+		return parseVec2(v.asString());
+	}
+	error("parseParallax expected a float, int or string, not this: %s", v.stringify().c_str());
+}
+
+void Room::load(Common::SeekableReadStream &s) {
+	GGHashMapDecoder d;
+	Common::JSONValue *value = d.open(&s);
+	const Common::JSONObject &jRoom = value->asObject();
+
+	name = jRoom["name"]->asString();
+	sheet = jRoom["sheet"]->asString();
+
+	roomSize = parseVec2(jRoom["roomsize"]->asString());
+	height = jRoom.contains("height") ? jRoom["height"]->asIntegerNumber() : roomSize.getY();
+	fullscreen = jRoom.contains("fullscreen") ? jRoom["fullscreen"]->asIntegerNumber() : 0;
+
+	// backgrounds
+	Common::StringArray backNames;
+	if (jRoom["background"]->isString()) {
+		backNames.push_back(jRoom["background"]->asString());
+	} else {
+		const Common::JSONArray &jBacks = jRoom["background"]->asArray();
+		for (int i = 0; i < jBacks.size(); i++) {
+			backNames.push_back(jBacks[i]->asString());
+		}
+	}
+
+	{
+		Layer layer;
+		layer.names.push_back(backNames);
+		layer.zsort = 0;
+		layer.parallax = Math::Vector2d(1, 1);
+		layers.push_back(layer);
+	}
+
+	// layers
+	if (jRoom.contains("layers")) {
+		const Common::JSONArray &jLayers = jRoom["layers"]->asArray();
+		for (int i = 0; i < jLayers.size(); i++) {
+			Layer layer;
+			const Common::JSONObject &jLayer = jLayers[i]->asObject();
+			if (jLayer["name"]->isArray()) {
+				const Common::JSONArray &jNames = jLayer["name"]->asArray();
+				for (int j = 0; j < jNames.size(); j++) {
+					layer.names.push_back(jNames[j]->asString());
+				}
+			} else if (jLayer["name"]->isString()) {
+				layer.names.push_back(jLayer["name"]->asString());
+			}
+			layer.parallax = parseParallax(*jLayer["parallax"]);
+			layer.zsort = jLayer["zsort"]->asIntegerNumber();
+			layers.push_back(layer);
+		}
+	}
+
+	// TODO: walkboxes, objects, scalings
+	delete value;
+}
+
+} // namespace Twp
diff --git a/engines/twp/room.h b/engines/twp/room.h
new file mode 100644
index 00000000000..b67bf8497dc
--- /dev/null
+++ b/engines/twp/room.h
@@ -0,0 +1,54 @@
+/* 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 TWP_ROOM_H
+#define TWP_ROOM_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "math/vector2d.h"
+
+namespace Twp {
+
+class Layer {
+public:
+ 	Common::Array<Common::String> names;
+    Math::Vector2d parallax;
+    int zsort;
+};
+
+class Room {
+public:
+	void load(Common::SeekableReadStream &s);
+
+public:
+	Common::String name;			// Name of the room
+	Common::String sheet;			// Name of the spritesheet to use
+	Math::Vector2d roomSize;		// Size of the room
+	int fullscreen;				// Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
+	int height;					// Height of the room (what else ?)
+	Common::Array<Layer> layers;	// Parallax layers of a room
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index 2a1aa4cb56c..9f217027079 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -51,6 +51,9 @@ void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
 		parseFrame(it->_key, it->_value->asObject(), frameTable[it->_key]);
 	}
 
+	const Common::JSONObject& jMeta = json->asObject()["meta"]->asObject();
+	meta.image = jMeta["image"]->asString();
+
 	delete json;
 }
 
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
index 19d82a4cea6..8ea0ce8e943 100644
--- a/engines/twp/spritesheet.h
+++ b/engines/twp/spritesheet.h
@@ -30,19 +30,24 @@
 
 namespace Twp {
 
+struct SpriteSheetMetadata {
+	Common::String image;
+};
+
 struct SpriteSheetFrame {
-  Common::String name;
-  Common::Rect frame;
-  Common::Rect spriteSourceSize;
-  Math::Vector2d sourceSize;
+	Common::String name;
+	Common::Rect frame;
+	Common::Rect spriteSourceSize;
+	Math::Vector2d sourceSize;
 };
 
 struct SpriteSheet {
 	Common::HashMap<Common::String, SpriteSheetFrame> frameTable;
+	SpriteSheetMetadata meta;
 
-	void parseSpriteSheet(const Common::String& contents);
+	void parseSpriteSheet(const Common::String &contents);
 };
 
-}
+} // namespace Twp
 
 #endif // TWP_SPRITESHEET_H
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 082c81a4cd9..44c982af5cb 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -44,6 +44,23 @@ namespace Twp {
 
 TwpEngine *g_engine;
 
+static Math::Vector2d getScreenSize(const Room &room) {
+	switch (room.height) {
+	case 128:
+		return {320, 180};
+	case 172:
+		return {428, 240};
+	case 256:
+		return {640, 360};
+	default:
+		return {room.roomSize.getX(), (float)room.height};
+	}
+}
+
+static bool cmpLayer(const Layer &l1, const Layer &l2) {
+	return l1.zsort > l2.zsort;
+}
+
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
 	  _gameDescription(gameDesc),
@@ -80,7 +97,8 @@ Common::Error TwpEngine::run() {
 	pack.open(&f, key);
 
 	const SQChar *code = R"(
-	function bounceImage() {
+		function
+		bounceImage() {
 		local image = createObject("RaySheet", ["fstand_head1"]);
 		local x = random(100, 1180);
 		local y = random(100, 620);
@@ -106,6 +124,12 @@ Common::Error TwpEngine::run() {
 		startthread(bounceImage);
 	})";
 
+	GGPackEntryReader r;
+	r.open(g_engine->pack, "MainStreet.wimpy");
+
+	Room room;
+	room.load(r);
+
 	Vm v;
 	v.exec(code);
 
@@ -117,6 +141,7 @@ Common::Error TwpEngine::run() {
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
+	Math::Vector2d pos;
 	Gfx gfx;
 	gfx.init();
 	gfx.camera(screenWidth, screenHeight);
@@ -124,7 +149,30 @@ Common::Error TwpEngine::run() {
 	Common::Event e;
 	while (!shouldQuit()) {
 		const uint deltaTimeMs = 10;
+		const int dx = 4;
+		const int dy = 4;
 		while (g_system->getEventManager()->pollEvent(e)) {
+			switch (e.type) {
+			case Common::EVENT_KEYDOWN:
+				switch (e.kbd.keycode) {
+				case Common::KEYCODE_LEFT:
+					pos.setX(pos.getX() - dx);
+					break;
+				case Common::KEYCODE_RIGHT:
+					pos.setX(pos.getX() + dx);
+					break;
+				case Common::KEYCODE_UP:
+					pos.setY(pos.getY() + dy);
+					break;
+				case Common::KEYCODE_DOWN:
+					pos.setY(pos.getY() - dy);
+					break;
+				default:
+					break;
+				}
+			default:
+				break;
+			}
 		}
 
 		// update threads
@@ -136,10 +184,33 @@ Common::Error TwpEngine::run() {
 		}
 
 		// update screen
-		gfx.clear(Color(0.8f, 0.8f, 0.8f));
+		Math::Vector2d screenSize = getScreenSize(room);
+		gfx.camera(screenSize.getX(), screenSize.getY());
+		gfx.clear(Color(0, 0, 0));
+
+		// draw room
+		SpriteSheet *ss = resManager.spriteSheet(room.sheet);
+		Texture *texture = resManager.texture(ss->meta.image);
+		Common::sort(room.layers.begin(), room.layers.end(), cmpLayer);
+		for (int i = 0; i < room.layers.size(); i++) {
+			float x = 0;
+			const Layer &layer = room.layers[i];
+			for (int j = 0; j < layer.names.size(); j++) {
+				const Common::String &name = layer.names[j];
+				const SpriteSheetFrame &frame = ss->frameTable[name];
+				Math::Matrix4 m;
+				m.translate(Math::Vector3d(x - pos.getX(), -pos.getY(), 0));
+				gfx.drawSprite(frame.frame, *texture, Color(), m);
+				x += frame.frame.width();
+			}
+		}
+
+		// draw entities
 		for (int i = 0; i < entities.size(); i++) {
 			Entity &ett = entities[i];
-			gfx.drawSprite(Math::Vector2d(ett.x, ett.y), ett.rect, *ett.texture);
+			Math::Matrix4 m;
+			m.translate(Math::Vector3d(ett.x - pos.getX(), ett.y - pos.getY(), 0));
+			gfx.drawSprite(ett.rect, *ett.texture, Color(), m);
 		}
 		g_system->updateScreen();
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 9331b099e3c..727404128df 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -37,6 +37,7 @@
 #include "twp/detection.h"
 #include "twp/vm.h"
 #include "twp/resmanager.h"
+#include "twp/room.h"
 
 namespace Twp {
 
@@ -55,6 +56,7 @@ public:
 	Common::Array<Thread*> threads;
 	GGPackDecoder pack;
 	ResManager resManager;
+	Common::Array<Room> rooms;
 
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 87182e80969..251142173f7 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -180,7 +180,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	debug("Create object: %s, %u", sheet.c_str(), frames.size());
 
 	// load sheet json
-	SpriteSheet* spritesheet = g_engine->resManager.spriteSheet(sheet + ".json");
+	SpriteSheet* spritesheet = g_engine->resManager.spriteSheet(sheet);
 
 	// TODO: create an entity
 	Entity e;


Commit: 42e348186a700e292b4671a272853c43ca2c9cc2
    https://github.com/scummvm/scummvm/commit/42e348186a700e292b4671a272853c43ca2c9cc2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add lighting shader

Changed paths:
  A engines/twp/lighting.cpp
  A engines/twp/lighting.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index d927ee6b090..4b6bf374633 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -19,8 +19,9 @@
  *
  */
 
-#include "gfx.h"
+#include "twp/gfx.h"
 #include "common/debug.h"
+#include "graphics/opengl/debug.h"
 #include "graphics/opengl/context.h"
 #include "graphics/opengl/system_headers.h"
 
@@ -28,58 +29,6 @@ namespace Twp {
 
 static Texture gEmptyTexture;
 
-#ifdef DEBUG
-	#define GL_CHECK(expr)                           \
-		do {                                         \
-			(expr);                                  \
-			checkGLError(__FILE__, __LINE__, #expr); \
-		} while (false)
-	#define GL_CHECK0()                           \
-		do {                                      \
-			checkGLError(__FILE__, __LINE__, ""); \
-		} while (false)
-#else
-	#define GL_CHECK(expr) (expr)
-	#define GL_CHECK0()
-#endif
-
-static void checkGLError(const char *filename, int line, const char *info) {
-	int err = glGetError();
-	if (err != GL_NO_ERROR) {
-		const char *name = nullptr;
-		const char *desc = nullptr;
-		switch (err) {
-		case GL_INVALID_ENUM:
-			name = "GL_INVALID_ENUM";
-			desc = "An unacceptable value is specified for an enumerated argument.";
-			break;
-		case GL_INVALID_VALUE:
-			name = "GL_INVALID_VALUE";
-			desc = "A numeric argument is out of range.";
-			break;
-		case GL_INVALID_OPERATION:
-			name = "GL_INVALID_OPERATION";
-			desc = "The specified operation is not allowed in the current state.";
-			break;
-		case GL_INVALID_FRAMEBUFFER_OPERATION:
-			name = "GL_INVALID_FRAMEBUFFER_OPERATION";
-			desc = "The command is trying to render to or read from the framebuffer while the currently bound framebuffer is not framebuffer complete.";
-			break;
-		case GL_OUT_OF_MEMORY:
-			name = "GL_OUT_OF_MEMORY";
-			desc = "There is not enough memory left to execute the command.";
-			break;
-		default:
-			break;
-		}
-		if (name) {
-			debug("%s: %s at %s:%d(%s)", name, desc, filename, line, info);
-		} else {
-			debug("Code %d at %s:%d(%s)", err, filename, line, info);
-		}
-	}
-}
-
 Math::Matrix4 ortho(float left, float right, float bottom, float top, float zNear, float zFar) {
 	Math::Matrix4 result;
 	float *m = result.getData();
@@ -93,28 +42,6 @@ Math::Matrix4 ortho(float left, float right, float bottom, float top, float zNea
 	return result;
 }
 
-struct Use {
-public:
-	Use(GLint program) : _prev(0), program(program) {
-		glGetIntegerv(GL_CURRENT_PROGRAM, &_prev);
-		if (_prev != program)
-			glUseProgram(program);
-	}
-
-	~Use() {
-		if (_prev != program)
-			glUseProgram(_prev);
-	}
-
-private:
-	GLint _prev, program;
-};
-
-static Use use(GLint program) {
-	Use use(program);
-	return use;
-}
-
 static GLint getFormat(int channels) {
 	switch (channels) {
 	case 3:
@@ -138,20 +65,23 @@ void Texture::load(const Graphics::Surface &surface) {
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 	glPixelStorei(GL_UNPACK_ALIGNMENT, surface.format.bytesPerPixel);
-	GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, getFormat(surface.format.bytesPerPixel), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));
+	GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, getFormat(surface.format.bytesPerPixel), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));
 }
 
 void Texture::bind(const Texture *pTexture) {
 	if (pTexture && pTexture->id) {
-		GL_CHECK(glBindTexture(GL_TEXTURE_2D, pTexture->id));
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, pTexture->id));
 	} else {
-		GL_CHECK(glBindTexture(GL_TEXTURE_2D, 0));
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
 	}
 }
 
 Shader::Shader() {
 }
 
+Shader::~Shader() {
+}
+
 void Shader::init(const char *vertex, const char *fragment) {
 	if (vertex) {
 		_vertex = loadShader(vertex, GL_VERTEX_SHADER);
@@ -159,17 +89,17 @@ void Shader::init(const char *vertex, const char *fragment) {
 	if (fragment) {
 		_fragment = loadShader(fragment, GL_FRAGMENT_SHADER);
 	}
-	GL_CHECK(program = glCreateProgram());
-	GL_CHECK(glAttachShader(program, _vertex));
-	GL_CHECK(glAttachShader(program, _fragment));
-	GL_CHECK(glLinkProgram(program));
+	GL_CALL(program = glCreateProgram());
+	GL_CALL(glAttachShader(program, _vertex));
+	GL_CALL(glAttachShader(program, _fragment));
+	GL_CALL(glLinkProgram(program));
 }
 
 uint32 Shader::loadShader(const char *code, uint32 shaderType) {
 	uint32 result;
-	GL_CHECK(result = glCreateShader(shaderType));
-	GL_CHECK(glShaderSource(result, 1, &code, nullptr));
-	GL_CHECK(glCompileShader(result));
+	GL_CALL(result = glCreateShader(shaderType));
+	GL_CALL(glShaderSource(result, 1, &code, nullptr));
+	GL_CALL(glCompileShader(result));
 	statusShader(result);
 	return result;
 }
@@ -187,14 +117,17 @@ void Shader::statusShader(uint32 shader) {
 
 int Shader::getUniformLocation(const char *name) {
 	int loc;
-	GL_CHECK(loc = glGetUniformLocation(program, name));
+	GL_CALL(loc = glGetUniformLocation(program, name));
 	return loc;
 }
 
 void Shader::setUniform(const char *name, Math::Matrix4 value) {
-	use(program);
+	GLint prev;
+	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
+	glUseProgram(program);
 	int loc = getUniformLocation(name);
-	GL_CHECK(glUniformMatrix4fv(loc, 1, GL_FALSE, value.getData()));
+	GL_CALL(glUniformMatrix4fv(loc, 1, GL_FALSE, value.getData()));
+	glUseProgram(prev);
 }
 
 Gfx::Gfx() : _vbo(0), _ebo(0) {
@@ -210,7 +143,7 @@ void Gfx::init() {
 	empty.setPixels(pixels);
 	gEmptyTexture.load(empty);
 	const char *vsrc = R"(#version 110
-	uniform mat4 u_transform;
+		uniform mat4 u_transform;
 	attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
@@ -229,25 +162,25 @@ void Gfx::init() {
 		vec4 tex_color = texture2D(u_texture, v_texCoords);
 		gl_FragColor = v_color * tex_color;
 	})";
-	_shader.init(vsrc, fsrc);
+	_defaultShader.init(vsrc, fsrc);
+	_shader = &_defaultShader;
 	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
 
-	GL_CHECK(glGenBuffers(1, &_vbo));
-	GL_CHECK(glGenBuffers(1, &_ebo));
-	GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-	GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
-	GLint p, c, t, tex, tr;
-	GL_CHECK(p = glGetAttribLocation(_shader.program, "a_position"));
-	GL_CHECK(c = glGetAttribLocation(_shader.program, "a_color"));
-	GL_CHECK(t = glGetAttribLocation(_shader.program, "a_texCoords"));
-	GL_CHECK(tex = glGetUniformLocation(_shader.program, "u_texture"));
-	GL_CHECK(tr = glGetUniformLocation(_shader.program, "u_transform"));
-	GL_CHECK(glVertexAttribPointer(p, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-	GL_CHECK(glEnableVertexAttribArray(p));
-	GL_CHECK(glVertexAttribPointer(c, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-	GL_CHECK(glEnableVertexAttribArray(c));
-	GL_CHECK(glVertexAttribPointer(t, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
-	GL_CHECK(glEnableVertexAttribArray(c));
+	GL_CALL(glGenBuffers(1, &_vbo));
+	GL_CALL(glGenBuffers(1, &_ebo));
+	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
+	GL_CALL(_posLoc = glGetAttribLocation(_defaultShader.program, "a_position"));
+	GL_CALL(_colLoc = glGetAttribLocation(_defaultShader.program, "a_color"));
+	GL_CALL(_texCoordsLoc = glGetAttribLocation(_defaultShader.program, "a_texCoords"));
+	GL_CALL(_texLoc = glGetUniformLocation(_defaultShader.program, "u_texture"));
+	GL_CALL(_trsfLoc = glGetUniformLocation(_defaultShader.program, "u_transform"));
+	GL_CALL(glVertexAttribPointer(_posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+	GL_CALL(glEnableVertexAttribArray(_posLoc));
+	GL_CALL(glVertexAttribPointer(_colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+	GL_CALL(glEnableVertexAttribArray(_colLoc));
+	GL_CALL(glVertexAttribPointer(_texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+	GL_CALL(glEnableVertexAttribArray(_texCoordsLoc));
 
 	glBindBuffer(GL_ARRAY_BUFFER, 0);
 }
@@ -265,7 +198,7 @@ Math::Matrix4 Gfx::getFinalTransform(Math::Matrix4 trsf) {
 
 void Gfx::noTexture() {
 	_texture = &gEmptyTexture;
-	GL_CHECK(glBindTexture(GL_TEXTURE_2D, gEmptyTexture.id));
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, gEmptyTexture.id));
 }
 
 void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
@@ -276,37 +209,37 @@ void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
 void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf) {
 	if (v_size > 0) {
 		// set blending
-		GL_CHECK(glEnable(GL_BLEND));
-		GL_CHECK(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
-		GL_CHECK(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+		GL_CALL(glEnable(GL_BLEND));
+		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
+		GL_CALL(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 
-		GL_CHECK(glUseProgram(_shader.program));
+		GL_CALL(glUseProgram(_shader->program));
 
-		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-		GL_CHECK(glEnableVertexAttribArray(0));
-		GL_CHECK(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-		GL_CHECK(glEnableVertexAttribArray(2));
-		GL_CHECK(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-		GL_CHECK(glEnableVertexAttribArray(1));
-		GL_CHECK(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CALL(glEnableVertexAttribArray(_posLoc));
+		GL_CALL(glVertexAttribPointer(_posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+		GL_CALL(glEnableVertexAttribArray(_colLoc));
+		GL_CALL(glVertexAttribPointer(_colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+		GL_CALL(glEnableVertexAttribArray(_texCoordsLoc));
+		GL_CALL(glVertexAttribPointer(_texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
 
-		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-		GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
+		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
 
-		GL_CHECK(glActiveTexture(GL_TEXTURE0));
-		GL_CHECK(glBindTexture(GL_TEXTURE_2D, _texture->id));
-		GL_CHECK(glUniform1i(0, 0));
+		GL_CALL(glActiveTexture(GL_TEXTURE0));
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		GL_CALL(glUniform1i(0, 0));
 
 		Math::Matrix4 m = getFinalTransform(trsf);
-		_shader.setUniform("u_transform", m);
-		GL_CHECK(glDrawArrays((GLenum)primitivesType, 0, v_size));
+		_shader->setUniform("u_transform", m);
+		GL_CALL(glDrawArrays((GLenum)primitivesType, 0, v_size));
 
 		glUseProgram(0);
 		glBindBuffer(GL_ARRAY_BUFFER, 0);
 
-		GL_CHECK(glDisableVertexAttribArray(0));
-		GL_CHECK(glDisableVertexAttribArray(1));
-		GL_CHECK(glDisableVertexAttribArray(2));
+		GL_CALL(glDisableVertexAttribArray(0));
+		GL_CALL(glDisableVertexAttribArray(1));
+		GL_CALL(glDisableVertexAttribArray(2));
 
 		glDisable(GL_BLEND);
 	}
@@ -315,38 +248,39 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf) {
 	if (i_size > 0) {
 		// set blending
-		GL_CHECK(glEnable(GL_BLEND));
-		GL_CHECK(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
-		GL_CHECK(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+		GL_CALL(glEnable(GL_BLEND));
+		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
+		GL_CALL(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 
-		GL_CHECK(glUseProgram(_shader.program));
+		GL_CALL(glUseProgram(_shader->program));
 
-		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 
-		GL_CHECK(glEnableVertexAttribArray(0));
-		GL_CHECK(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-		GL_CHECK(glEnableVertexAttribArray(2));
-		GL_CHECK(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-		GL_CHECK(glEnableVertexAttribArray(1));
-		GL_CHECK(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+		GL_CALL(glEnableVertexAttribArray(0));
+		GL_CALL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+		GL_CALL(glEnableVertexAttribArray(2));
+		GL_CALL(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+		GL_CALL(glEnableVertexAttribArray(1));
+		GL_CALL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
 
-		GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-		GL_CHECK(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
-		GL_CHECK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
-		GL_CHECK(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * i_size, indices, GL_STREAM_DRAW));
+		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
+		GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
+		GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * i_size, indices, GL_STREAM_DRAW));
 
-		GL_CHECK(glActiveTexture(GL_TEXTURE0));
-		GL_CHECK(glBindTexture(GL_TEXTURE_2D, _texture->id));
-		GL_CHECK(glUniform1i(0, 0));
+		GL_CALL(glActiveTexture(GL_TEXTURE0));
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		GL_CALL(glUniform1i(0, 0));
 
-		_shader.setUniform("u_transform", getFinalTransform(trsf));
-		GL_CHECK(glDrawElements(primitivesType, i_size, GL_UNSIGNED_INT, NULL));
+		_shader->setUniform("u_transform", getFinalTransform(trsf));
+		_shader->applyUniforms();
+		GL_CALL(glDrawElements(primitivesType, i_size, GL_UNSIGNED_INT, NULL));
 
 		glUseProgram(0);
 		glBindBuffer(GL_ARRAY_BUFFER, 0);
-		GL_CHECK(glDisableVertexAttribArray(0));
-		GL_CHECK(glDisableVertexAttribArray(1));
-		GL_CHECK(glDisableVertexAttribArray(2));
+		GL_CALL(glDisableVertexAttribArray(0));
+		GL_CALL(glDisableVertexAttribArray(1));
+		GL_CALL(glDisableVertexAttribArray(2));
 
 		glDisable(GL_BLEND);
 	}
@@ -390,15 +324,19 @@ void Gfx::drawSprite(Common::Rect textRect, Texture &texture, Color color, Math:
 		{.pos = {pos.getX(), pos.getY()}, .texCoords = {l, b}, .color = color},
 		{.pos = {pos.getX(), pos.getY() + textRect.height()}, .texCoords = {l, t}, .color = color}};
 	_texture = &texture;
-	GL_CHECK(glBindTexture(GL_TEXTURE_2D, texture.id));
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, texture.id));
 	uint32 quadIndices[] = {
 		0, 1, 3,
 		1, 2, 3};
 	draw(vertices, 4, quadIndices, 6, trsf);
 }
 
-void Gfx::camera(float w, float h) {
-	_cameraSize = Math::Vector2d(w, h);
-	_mvp = ortho(0.f, w, 0.f, h, -1.f, 1.f);
+void Gfx::camera(Math::Vector2d size) {
+	_cameraSize = size;
+	_mvp = ortho(0.f, size.getX(), 0.f, size.getY(), -1.f, 1.f);
 }
+
+void Gfx::use(Shader *shader) {
+	_shader = shader ? shader : &_defaultShader;
 }
+} // namespace Twp
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 54d43cd5916..b8bd23f5709 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -81,10 +81,12 @@ struct TextureSlot {
 class Shader {
 public:
 	Shader();
+	virtual ~Shader();
 
 	void init(const char *vertex, const char *fragment);
 
 	void setUniform(const char *name, Math::Matrix4 value);
+	virtual void applyUniforms() {}
 
 private:
 	uint32 loadShader(const char *code, uint32 shaderType);
@@ -108,7 +110,8 @@ public:
 
 	void init();
 
-	void camera(float w, float h);
+	void camera(Math::Vector2d size);
+	void use(Shader* shader);
 
 	void clear(Color color);
 	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, Math::Matrix4 transf = Math::Matrix4());
@@ -118,20 +121,22 @@ public:
 	void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
 	void drawSprite(Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
 
-	void noTexture();
+private:
 	Math::Matrix4 getFinalTransform(Math::Matrix4 trsf);
+	void noTexture();
 
 public:
 	Math::Vector2d _cameraPos;
 
 private:
 	uint32 _vbo, _ebo;
-	Shader _shader;
-	Color _color;
+	Shader _defaultShader;
+	Shader* _shader;
 	Math::Matrix4 _mvp;
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture* _texture;
+	int32 _posLoc, _colLoc, _texCoordsLoc, _texLoc, _trsfLoc;
 };
 }
 
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
new file mode 100644
index 00000000000..8af671fa2af
--- /dev/null
+++ b/engines/twp/lighting.cpp
@@ -0,0 +1,116 @@
+#include "twp/lighting.h"
+#include "graphics/opengl/debug.h"
+#include "graphics/opengl/system_headers.h"
+
+namespace Twp {
+
+Lighting::Lighting() {
+	const char *vshader = R"(#version 110
+		uniform mat4 u_transform;
+	attribute vec2 a_position;
+	attribute vec4 a_color;
+	attribute vec2 a_texCoords;
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	void main() {
+		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+		v_color = a_color;
+		v_texCoords = a_texCoords;
+	})";
+
+	const char *fshader = R"(#version 110
+	varying vec2 v_texCoords;
+	varying vec4 v_color;
+
+	uniform sampler2D u_texture;
+
+	uniform vec2 u_contentSize;
+	uniform vec3 u_ambientColor;
+	uniform vec2 u_spritePosInSheet;
+	uniform vec2 u_spriteSizeRelToSheet;
+	uniform vec2 u_spriteOffset;
+
+	uniform int u_numberLights;
+	uniform vec3 u_lightPos[50];
+	uniform vec3 u_lightColor[50];
+	uniform float u_brightness[50];
+	uniform float u_cutoffRadius[50];
+	uniform float u_halfRadius[50];
+	uniform float u_coneCosineHalfConeAngle[50];
+	uniform float u_coneFalloff[50];
+	uniform vec2 u_coneDirection[50];
+
+	void main(void) {
+		vec4 texColor = texture2D(u_texture, v_texCoords);
+
+		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
+		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+		vec3 diffuse = vec3(0, 0, 0);
+		for (int i = 0; i < u_numberLights; i++) {
+			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
+			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
+				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+				float dx_1 = 0.5 / intercept;
+				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+				float offset = 0.5 + intercept * dx_2;
+
+				float lightDist = length(lightVec);
+				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+				float falloffSelect = step(intercept, lightDist);
+				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+				float spotLight = u_brightness[i] * falloffTerm;
+
+				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
+				float halfConeRange = coneRange * u_coneFalloff[i];
+				float conePos = 1.0 - coneValue;
+				float coneFalloff = 1.0;
+				if (conePos > halfConeRange) {
+					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
+				}
+
+				diffuse += ltdiffuse * coneFalloff;
+				;
+			}
+		}
+		vec3 finalLight = (diffuse);
+		vec4 finalCol = texColor * v_color;
+		finalCol.rgb = finalCol.rgb * u_ambientColor;
+		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
+	})";
+	init(vshader, fshader);
+
+	GL_CALL(_contentSizeLoc = glGetUniformLocation(program, "u_contentSize"));
+	GL_CALL(_spriteOffsetLoc = glGetUniformLocation(program, "u_spriteOffset"));
+	GL_CALL(_spritePosInSheetLoc = glGetUniformLocation(program, "u_spritePosInSheet"));
+	GL_CALL(_spriteSizeRelToSheetLoc = glGetUniformLocation(program, "u_spriteSizeRelToSheet"));
+	GL_CALL(_numberLightsLoc = glGetUniformLocation(program, "u_numberLights"));
+	GL_CALL(_ambientColorLoc = glGetUniformLocation(program, "u_ambientColor"));
+}
+
+Lighting::~Lighting() {}
+
+void Lighting::setSpriteSheetFrame(const SpriteSheetFrame& frame, const Texture& texture) {
+	_contentSize = frame.sourceSize;
+	//_spriteOffset = {-frame.frame.width() / 2.f, -frame.frame.height() / 2.f};
+	_spriteOffset = {0, (float)frame.frame.height()};
+	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
+	_spriteSizeRelToSheet = {(float)(frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height};
+}
+
+void Lighting::applyUniforms() {
+	GL_CALL(glUniform1i(_numberLightsLoc, 0));
+	float ambientColor[] = {1,1,1};
+	GL_CALL(glUniform3fv(_ambientColorLoc, 1, ambientColor));
+	GL_CALL(glUniform2fv(_contentSizeLoc, 1, _contentSize.getData()));
+	GL_CALL(glUniform2fv(_spriteOffsetLoc, 1, _spriteOffset.getData()));
+	GL_CALL(glUniform2fv(_spritePosInSheetLoc, 1, _spritePosInSheet.getData()));
+	GL_CALL(glUniform2fv(_spriteSizeRelToSheetLoc, 1, _spriteSizeRelToSheet.getData()));
+}
+
+} // namespace Twp
diff --git a/engines/twp/lighting.h b/engines/twp/lighting.h
new file mode 100644
index 00000000000..969d0197840
--- /dev/null
+++ b/engines/twp/lighting.h
@@ -0,0 +1,56 @@
+/* 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 TWP_LIGHTING_H
+#define TWP_LIGHTING_H
+
+#include "twp/gfx.h"
+#include "twp/spritesheet.h"
+
+namespace Twp {
+
+class Lighting: public Shader {
+public:
+	Lighting();
+	virtual ~Lighting();
+
+	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture);
+
+private:
+	virtual void applyUniforms() final;
+
+private:
+	int32 _contentSizeLoc;
+	int32 _spriteOffsetLoc;
+	int32 _spritePosInSheetLoc;
+	int32 _spriteSizeRelToSheetLoc;
+	int32 _numberLightsLoc;
+	int32 _ambientColorLoc;
+
+	Math::Vector2d _contentSize;
+	Math::Vector2d _spriteOffset;
+	Math::Vector2d _spritePosInSheet;
+	Math::Vector2d _spriteSizeRelToSheet;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index a83db4c051c..2651a895776 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -32,6 +32,7 @@ MODULE_OBJS = \
 	resmanager.o \
 	spritesheet.o \
 	room.o \
+	lighting.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 44c982af5cb..844e6f879cb 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -29,6 +29,7 @@
 #include "twp/vm.h"
 #include "twp/ggpack.h"
 #include "twp/gfx.h"
+#include "twp/lighting.h"
 #include "common/scummsys.h"
 #include "common/config-manager.h"
 #include "common/debug-channels.h"
@@ -144,7 +145,8 @@ Common::Error TwpEngine::run() {
 	Math::Vector2d pos;
 	Gfx gfx;
 	gfx.init();
-	gfx.camera(screenWidth, screenHeight);
+
+	Lighting lighting;
 	// Simple event handling loop
 	Common::Event e;
 	while (!shouldQuit()) {
@@ -185,8 +187,9 @@ Common::Error TwpEngine::run() {
 
 		// update screen
 		Math::Vector2d screenSize = getScreenSize(room);
-		gfx.camera(screenSize.getX(), screenSize.getY());
+		gfx.camera(screenSize);
 		gfx.clear(Color(0, 0, 0));
+		gfx.use(&lighting);
 
 		// draw room
 		SpriteSheet *ss = resManager.spriteSheet(room.sheet);
@@ -199,13 +202,17 @@ Common::Error TwpEngine::run() {
 				const Common::String &name = layer.names[j];
 				const SpriteSheetFrame &frame = ss->frameTable[name];
 				Math::Matrix4 m;
-				m.translate(Math::Vector3d(x - pos.getX(), -pos.getY(), 0));
+				Math::Vector3d t1 = Math::Vector3d(x - pos.getX() * layer.parallax.getX(), -pos.getY() * layer.parallax.getY(), 0);
+				Math::Vector3d t2 = Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f);
+				m.translate(t1+t2);
+				lighting.setSpriteSheetFrame(frame, *texture);
 				gfx.drawSprite(frame.frame, *texture, Color(), m);
 				x += frame.frame.width();
 			}
 		}
 
 		// draw entities
+		gfx.use(NULL);
 		for (int i = 0; i < entities.size(); i++) {
 			Entity &ett = entities[i];
 			Math::Matrix4 m;


Commit: b057e67a0fb5d3e0e881a4b8c96760a6769af601
    https://github.com/scummvm/scummvm/commit/b057e67a0fb5d3e0e881a4b8c96760a6769af601
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add GGFont and text

Changed paths:
  A engines/twp/font.cpp
  A engines/twp/font.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/twp.cpp


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
new file mode 100644
index 00000000000..020fc79945a
--- /dev/null
+++ b/engines/twp/font.cpp
@@ -0,0 +1,308 @@
+/* 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 "twp/font.h"
+#include "twp/twp.h"
+#include "common/str.h"
+#include "graphics/opengl/system_headers.h"
+
+namespace Twp {
+
+enum TokenId {
+	tiWhitespace,
+	tiString,
+	tiColor,
+	tiNewLine,
+	tiEnd
+};
+
+typedef struct Token {
+	TokenId id;
+	int startOff, endOff;
+} Token;
+
+typedef struct CharInfo {
+	CodePoint chr;
+	Math::Vector2d pos;
+	Color color;
+	Glyph glyph;
+} CharInfo;
+
+typedef struct Line {
+	Common::Array<Token> tokens;
+	Common::Array<CharInfo> charInfos;
+} Line;
+
+class TokenReader {
+public:
+	TokenReader(const Common::String &text);
+	Common::String substr(Token tok);
+	bool readToken(Token &token);
+
+private:
+	char readChar();
+	TokenId readTokenId();
+
+private:
+	Common::String _text;
+	int _off;
+};
+
+static Math::Vector2d normalize(Texture *texture, Math::Vector2d v) {
+	Math::Vector2d textureSize(texture->width, texture->height);
+	return Math::Vector2d(v.getX() / textureSize.getX(), v.getY() / textureSize.getY());
+}
+
+static void addGlyphQuad(Texture *texture, Common::Array<Vertex> &vertices, CharInfo info) {
+	// Add a glyph quad to the vertex array
+
+	float left = info.glyph.bounds.left;
+	float top = info.glyph.bounds.bottom;
+	float right = info.glyph.bounds.right;
+	float bottom = info.glyph.bounds.top;
+
+	Math::Vector2d uv1 = normalize(texture, Math::Vector2d(info.glyph.textureRect.left, info.glyph.textureRect.bottom));
+	Math::Vector2d uv2 = normalize(texture, Math::Vector2d(info.glyph.textureRect.right, info.glyph.textureRect.top));
+
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + top), info.color, Math::Vector2d(uv1.getX(), uv2.getY())});
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + top), info.color, Math::Vector2d(uv2.getX(), uv2.getY())});
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + bottom), info.color, Math::Vector2d(uv1.getX(), uv1.getY())});
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + bottom), info.color, Math::Vector2d(uv1.getX(), uv1.getY())});
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + top), info.color, Math::Vector2d(uv2.getX(), uv2.getY())});
+	vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + bottom), info.color, Math::Vector2d(uv2.getX(), uv1.getY())});
+}
+
+// Skips all characters while one char from the set `token` is found.
+// Returns number of characters skipped.
+static int skipWhile(const char *s, const char *toSkip, int start = 0) {
+	int result = 0;
+	int len = strlen(s);
+	while ((start + result < len) && strchr(toSkip, s[result + start]))
+		result++;
+	return result;
+}
+
+static int skipUntil(const char *s, const char *until, int start = 0) {
+	int result = 0;
+	int len = strlen(s);
+	while ((start + result < len) && !strchr(until, s[result + start]))
+		result++;
+	return result;
+}
+
+static float width(Text &text, TokenReader &reader, Token tok) {
+	float result = 0;
+	Common::String s = reader.substr(tok);
+	for (int i = 0; i < s.size(); i++) {
+		char c = s[i];
+		result += text.getFont()->getGlyph(c).advance;
+	}
+	return result;
+}
+
+TokenReader::TokenReader(const Common::String &text) : _text(text), _off(0) {
+}
+
+Common::String TokenReader::substr(Token tok) {
+	return _text.substr(tok.startOff, tok.endOff - tok.startOff + 1);
+}
+
+char TokenReader::readChar() {
+	char result = _text[_off];
+	_off++;
+	return result;
+}
+
+TokenId TokenReader::readTokenId() {
+	const char Whitespace[] = {' ', '\t', '\v', '\r', '\f'};
+	const char Whitespace2[] = {' ', '\t', '\v', '\r', '\f', '#', '\n'};
+	if (_off < _text.size()) {
+		char c = readChar();
+		switch (c) {
+		case '\n':
+			return tiNewLine;
+		case '\t':
+		case ' ':
+			_off += skipWhile(_text.c_str(), Whitespace, _off);
+			return tiWhitespace;
+		case '#':
+			_off += 7;
+			return tiColor;
+		default:
+			_off += skipUntil(_text.c_str(), Whitespace2, _off);
+			return tiString;
+		}
+	} else {
+		return tiEnd;
+	}
+}
+
+bool TokenReader::readToken(Token &token) {
+	int start = _off;
+	TokenId id = readTokenId();
+	if (id != tiEnd) {
+		token.id = id;
+		token.startOff = start;
+		token.endOff = _off - 1;
+		return true;
+	}
+	return false;
+}
+
+GGFont::~GGFont() {}
+
+void GGFont::load(const Common::String &path) {
+	SpriteSheet *spritesheet = g_engine->resManager.spriteSheet(path);
+	int lineHeight = 0;
+	for (auto it = spritesheet->frameTable.begin(); it != spritesheet->frameTable.end(); it++) {
+		const SpriteSheetFrame &frame = it->_value;
+		Glyph glyph;
+		glyph.advance = MAX(frame.sourceSize.getX() - frame.spriteSourceSize.left - 4, 0.f);
+		glyph.bounds = Common::Rect(Common::Point(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top), frame.spriteSourceSize.width(), frame.spriteSourceSize.height());
+		lineHeight = MAX(lineHeight, (int)frame.spriteSourceSize.top);
+		glyph.textureRect = frame.frame;
+		_glyphs[it->_key.asUint64()] = glyph;
+	}
+	_lineHeight = lineHeight;
+	_name = path;
+}
+
+Glyph GGFont::getGlyph(CodePoint chr) {
+	int key = (int)chr;
+	if (_glyphs.contains(key)) {
+		return _glyphs[key];
+	}
+	return _glyphs['?'];
+}
+
+Text::Text(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth, Color color)
+	: _font(NULL), _fontName(fontName), _texture(NULL), _txt(text), _col(color), _hAlign(hAlign), _vAlign(vAlign), _maxW(maxWidth), _dirty(true) {
+	update();
+}
+
+void Text::update() {
+	if (_dirty) {
+		_dirty = false;
+		// let (_, name, _) = splitFile(self.font.path);
+		_font = g_engine->resManager.font(_fontName);
+		_texture = g_engine->resManager.texture(_font->getName() + ".png");
+
+		// Reset
+		_vertices.clear();
+		_quads.clear();
+		_bnds = Math::Vector2d();
+		Color color = _col;
+
+		// split text by tokens and split tokens by lines
+		Common::Array<Line> lines;
+		Line line1;
+		TokenReader reader(_txt);
+		float x = 0;
+		Token tok;
+		while (reader.readToken(tok)) {
+			// ignore color token width
+			float w = tok.id == tiColor || tok.id == tiNewLine ? 0.f : width(*this, reader, tok);
+			// new line if width > maxWidth or newline character
+			if (tok.id == tiNewLine || ((_maxW > 0) && (line1.tokens.size() > 0) && (x + w > _maxW))) {
+				lines.push_back(line1);
+				line1.tokens.clear();
+				x = 0;
+			}
+			if (tok.id != tiNewLine) {
+				if (line1.tokens.size() != 0 || tok.id != tiWhitespace) {
+					line1.tokens.push_back(tok);
+					x += w;
+				}
+			}
+		}
+		lines.push_back(line1);
+
+		// create quads for all characters
+		float maxW;
+		float lineHeight = _font->getLineHeight();
+		float y = -lineHeight;
+		for (int i = 0; i < lines.size(); i++) {
+			Line &line = lines[i];
+			CodePoint prevChar;
+			x = 0;
+			for (int j = 0; j < line.tokens.size(); j++) {
+				tok = line.tokens[j];
+				if (tok.id == tiColor) {
+					int iColor;
+					sscanf(reader.substr(tok).c_str() + 1, "%x", &iColor);
+					color = Color::withAlpha(Color::rgb(iColor & 0x00FFFFFF), color.rgba.a);
+				} else {
+					Common::String s = reader.substr(tok);
+					for (int k = 0; k < s.size(); k++) {
+						CodePoint c = s[k];
+						Glyph glyph = _font->getGlyph(c);
+						float kern = _font->getKerning(prevChar, c);
+						prevChar = c;
+						line.charInfos.push_back(CharInfo{c, Math::Vector2d(x + kern, y), color, glyph});
+						// self.quads.add(rect(x, y, glyph.bounds.x.float32 + glyph.bounds.w.float32, lineHeight));
+						x += (float)glyph.advance;
+					}
+				}
+			}
+			_quads.push_back(Common::Rect(Common::Point(0.0f, y), x, lineHeight));
+			maxW = MAX(maxW, x);
+			y -= lineHeight;
+		}
+
+		// Align text
+		if (_hAlign == thRight) {
+			for (int i = 0; i < lines.size(); i++) {
+				float w = maxW - _quads[i].width();
+				for (int j = 0; j < lines[i].charInfos.size(); j++) {
+					CharInfo &info = lines[i].charInfos[j];
+					info.pos.setX(info.pos.getX() + w);
+				}
+			}
+		} else if (_hAlign == thCenter) {
+			for (int i = 0; i < lines.size(); i++) {
+				float w = maxW - _quads[i].width();
+				for (int j = 0; j < lines[i].charInfos.size(); j++) {
+					CharInfo &info = lines[i].charInfos[j];
+					info.pos.setX(info.pos.getX() + w / 2.f);
+				}
+			}
+		}
+
+		// Add the glyphs to the vertices
+		for (int i = 0; i < lines.size(); i++) {
+			for (int j = 0; j < lines[i].charInfos.size(); j++) {
+				const CharInfo &info = lines[i].charInfos[j];
+				addGlyphQuad(_texture, _vertices, info);
+			}
+		}
+
+		_bnds = Math::Vector2d(maxW, lines.size() * _font->getLineHeight());
+	}
+}
+
+void Text::draw(Gfx &gfx, Math::Matrix4 trsf) {
+	if (_font && _txt.size() > 0) {
+		update();
+		gfx.drawPrimitives(GL_TRIANGLES, _vertices.begin(), _vertices.size(), trsf, _texture);
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/font.h b/engines/twp/font.h
new file mode 100644
index 00000000000..3b9b3893a8b
--- /dev/null
+++ b/engines/twp/font.h
@@ -0,0 +1,140 @@
+/* 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 TWP_FONT_H
+#define TWP_FONT_H
+
+#include "twp/spritesheet.h"
+#include "twp/gfx.h"
+#include "common/rect.h"
+#include "common/hashmap.h"
+#include "common/str.h"
+
+namespace Twp {
+typedef int32 CodePoint;
+
+// represents a glyph: a part of an image for a specific font character
+struct Glyph {
+	int advance;              // Offset to move horizontally to the next character.
+	Common::Rect bounds;      // Bounding rectangle of the glyph, in coordinates relative to the baseline.
+	Common::Rect textureRect; // Texture coordinates of the glyph inside the font's texture.
+};
+
+class Font {
+public:
+	virtual ~Font() {}
+
+	virtual int getLineHeight() = 0;
+	virtual float getKerning(CodePoint prev, CodePoint next) = 0;
+	virtual Glyph getGlyph(CodePoint chr) = 0;
+	virtual Common::String getName() = 0;
+};
+
+// Represents a bitmap font from a spritesheet.
+class GGFont : public Font {
+public:
+	virtual ~GGFont();
+	void load(const Common::String &path);
+
+	virtual int getLineHeight() override final { return _lineHeight; }
+	virtual float getKerning(CodePoint prev, CodePoint next) override final { return 0.f; }
+	virtual Glyph getGlyph(CodePoint chr) override final;
+	virtual Common::String getName() override final { return _name; }
+
+private:
+	Common::HashMap<int, Glyph> _glyphs;
+	int _lineHeight;
+	Common::String _name;
+};
+
+enum TextHAlignment {
+	thLeft,
+	thCenter,
+	thRight
+};
+enum TextVAlignment {
+	tvTop,
+	tvCenter,
+	tvBottom
+};
+
+// This class allows to render a text.
+//
+// A text can contains color in hexadecimal with this format: #RRGGBB
+class Text {
+public:
+	Text(const Common::String& fontName, const Common::String& text, TextHAlignment hAlign = thCenter, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f, Color color = Color());
+
+	void setText(const Common::String& text) {
+		_txt = text;
+		_dirty = true;
+	}
+	Common::String getText() { return _txt; }
+
+	void setColor(Color c) {
+		_col = c;
+		_dirty = true;
+	}
+	Color getColor() { return _col; }
+
+	void setMaxWidth(float maxW) {
+		_maxW = maxW;
+		_dirty = true;
+	}
+	float getMaxWidth() { return _maxW; }
+
+	void setHAlign(TextHAlignment align) {
+		_hAlign = align;
+		_dirty = true;
+	}
+	TextHAlignment getHAlign() { return _hAlign; }
+
+	void setVAlign(TextVAlignment align) {
+		_vAlign = align;
+		_dirty = true;
+	}
+	TextVAlignment getVAlign() { return _vAlign; }
+
+	Font* getFont() { return _font; }
+
+	void draw(Gfx& gfx, Math::Matrix4 trsf = Math::Matrix4());
+
+private:
+	void update();
+
+private:
+	Font* _font;
+	Common::String _fontName;
+	Texture* _texture;
+	Common::String _txt;
+	Color _col;
+	TextHAlignment _hAlign;
+	TextVAlignment _vAlign;
+	Common::Array<Vertex> _vertices;
+	Math::Vector2d _bnds;
+	float _maxW;
+	Common::Array<Common::Rect> _quads;
+	bool _dirty;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 4b6bf374633..8e2809a65ba 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -206,8 +206,11 @@ void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
 	drawPrimitives(GL_LINE_STRIP, vertices, count, trsf);
 }
 
-void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf) {
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture* texture) {
 	if (v_size > 0) {
+		_texture = texture ? texture: &gEmptyTexture;
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+
 		// set blending
 		GL_CALL(glEnable(GL_BLEND));
 		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
@@ -245,8 +248,11 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 	}
 }
 
-void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf) {
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture* texture) {
 	if (i_size > 0) {
+		_texture = texture ? texture: &gEmptyTexture;
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+
 		// set blending
 		GL_CALL(glEnable(GL_BLEND));
 		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
@@ -286,8 +292,8 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 	}
 }
 
-void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf) {
-	drawPrimitives(GL_TRIANGLES, vertices, v_size, indices, i_size, trsf);
+void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture* texture) {
+	drawPrimitives(GL_TRIANGLES, vertices, v_size, indices, i_size, trsf, texture);
 }
 
 void Gfx::drawQuad(Math::Vector2d size, Color color, Math::Matrix4 trsf) {
@@ -323,12 +329,10 @@ void Gfx::drawSprite(Common::Rect textRect, Texture &texture, Color color, Math:
 		{.pos = {pos.getX() + textRect.width(), pos.getY()}, .texCoords = {r, b}, .color = color},
 		{.pos = {pos.getX(), pos.getY()}, .texCoords = {l, b}, .color = color},
 		{.pos = {pos.getX(), pos.getY() + textRect.height()}, .texCoords = {l, t}, .color = color}};
-	_texture = &texture;
-	GL_CALL(glBindTexture(GL_TEXTURE_2D, texture.id));
 	uint32 quadIndices[] = {
 		0, 1, 3,
 		1, 2, 3};
-	draw(vertices, 4, quadIndices, 6, trsf);
+	draw(vertices, 4, quadIndices, 6, trsf, &texture);
 }
 
 void Gfx::camera(Math::Vector2d size) {
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index b8bd23f5709..db746f6e982 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -49,6 +49,16 @@ struct Color {
 		rgba.a = alpha;
 	}
 
+	static Color withAlpha(Color c, float alpha = 1.0f) {
+		Color result = c;
+		result.rgba.a = alpha;
+		return result;
+	}
+
+	static Color rgb(int c) {
+  		return Color((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF));
+	}
+
 	static Color create(uint8 red, uint8 green, uint8 blue, uint8 alpha = 0xFF) {
 		return Color(red / 255.f, green / 255.f, blue / 255.f, alpha / 255.f);
 	}
@@ -114,10 +124,10 @@ public:
 	void use(Shader* shader);
 
 	void clear(Color color);
-	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, Math::Matrix4 transf = Math::Matrix4());
-	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 transf = Math::Matrix4());
+	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, Math::Matrix4 transf = Math::Matrix4(), Texture* texture = NULL);
+	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 transf = Math::Matrix4(), Texture* texture = NULL);
 	void drawLines(Vertex* vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
-	void draw(Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 trsf = Math::Matrix4());
+	void draw(Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 trsf = Math::Matrix4(), Texture* texture = NULL);
 	void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
 	void drawSprite(Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
 
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 2651a895776..22d87a85bb8 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -33,6 +33,7 @@ MODULE_OBJS = \
 	spritesheet.o \
 	room.o \
 	lighting.o \
+	font.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index bd34f6a4cc9..4e93567d4f5 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -35,16 +35,16 @@ Common::String getKey(const char *path) {
 	p = p.substr(0, i);
 	if ((len > 4) && scumm_strnicmp(p.c_str() + 4, "_en", 3) == 0) {
 		// TODO
-		//Common::String lang = prefs(Lang);
+		// Common::String lang = prefs(Lang);
 		Common::String lang = "en";
 		Common::String filename(path, len - 3);
-		const char* ext = path+i;
+		const char *ext = path + i;
 		return Common::String::format("%s_%s%s", filename.c_str(), lang.c_str(), ext);
 	}
 	return path;
 }
 
-void ResManager::loadTexture(const Common::String& name) {
+void ResManager::loadTexture(const Common::String &name) {
 	debug("Load texture %s", name.c_str());
 	GGPackEntryReader r;
 	r.open(g_engine->pack, name);
@@ -55,7 +55,7 @@ void ResManager::loadTexture(const Common::String& name) {
 	_textures[name].load(*surface);
 }
 
-Texture *ResManager::texture(const Common::String& name) {
+Texture *ResManager::texture(const Common::String &name) {
 	Common::String key = getKey(name.c_str());
 	if (!_textures.contains(key)) {
 		loadTexture(key.c_str());
@@ -63,7 +63,7 @@ Texture *ResManager::texture(const Common::String& name) {
 	return &_textures[key];
 }
 
-void ResManager::loadSpriteSheet(const Common::String& name) {
+void ResManager::loadSpriteSheet(const Common::String &name) {
 	GGPackEntryReader r;
 	r.open(g_engine->pack, name + ".json");
 
@@ -75,7 +75,23 @@ void ResManager::loadSpriteSheet(const Common::String& name) {
 	_spriteSheets[name].parseSpriteSheet(s);
 }
 
-SpriteSheet *ResManager::spriteSheet(const Common::String& name) {
+void ResManager::loadFont(const Common::String &name) {
+	if (name == "sayline") {
+		debug("Load font %s", name.c_str());
+		// TODO: Common::String resName = prefs(RetroFonts)? "FontRetroSheet.json": "FontModernSheet.json";
+		_fontModernSheet.load("FontModernSheet");
+		_fonts[name] = &_fontModernSheet;
+	} else if (name == "C64Font") {
+		debug("Load font %s", name.c_str());
+		_fontC64TermSheet.load("FontC64TermSheet");
+		_fonts[name] = &_fontC64TermSheet;
+	} else {
+		// TODO:
+		assert(false);
+	}
+}
+
+SpriteSheet *ResManager::spriteSheet(const Common::String &name) {
 	Common::String key = getKey(name.c_str());
 	if (!_spriteSheets.contains(key)) {
 		loadSpriteSheet(key.c_str());
@@ -83,4 +99,12 @@ SpriteSheet *ResManager::spriteSheet(const Common::String& name) {
 	return &_spriteSheets[key];
 }
 
+Font *ResManager::font(const Common::String &name) {
+	Common::String key = getKey(name.c_str());
+	if (!_fonts.contains(key)) {
+		loadFont(key.c_str());
+	}
+	return _fonts[key];
 }
+
+} // namespace Twp
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index 12a50e90273..cb48c086722 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -25,6 +25,7 @@
 #include "common/str.h"
 #include "common/hashmap.h"
 #include "twp/gfx.h"
+#include "twp/font.h"
 #include "twp/spritesheet.h"
 
 namespace Twp {
@@ -35,14 +36,20 @@ public:
 
 	Texture *texture(const Common::String &name);
 	SpriteSheet *spriteSheet(const Common::String& name);
+	Font *font(const Common::String& name);
 
 private:
 	void loadTexture(const Common::String &name);
 	void loadSpriteSheet(const Common::String &name);
+	void loadFont(const Common::String &name);
 
 private:
 	Common::HashMap<Common::String, Texture> _textures;
 	Common::HashMap<Common::String, SpriteSheet> _spriteSheets;
+	Common::HashMap<Common::String, Font*> _fonts;
+	GGFont _fontModernSheet;
+	GGFont _fontRetroSheet;
+	GGFont _fontC64TermSheet;
 };
 } // namespace Twp
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 844e6f879cb..d3e7b3efe32 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -30,6 +30,7 @@
 #include "twp/ggpack.h"
 #include "twp/gfx.h"
 #include "twp/lighting.h"
+#include "twp/font.h"
 #include "common/scummsys.h"
 #include "common/config-manager.h"
 #include "common/debug-channels.h"


Commit: da03d0b291465185459ff5486c33900299e13240
    https://github.com/scummvm/scummvm/commit/da03d0b291465185459ff5486c33900299e13240
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add general function stubs

Changed paths:
  A engines/twp/genlib.cpp
  A engines/twp/objlib.cpp
  A engines/twp/scenegraph.cpp
  A engines/twp/sqgame.cpp
  A engines/twp/sqgame.h
  A engines/twp/squtil.cpp
  A engines/twp/squtil.h
  A engines/twp/syslib.cpp
  A engines/twp/thread.cpp
  A engines/twp/thread.h
    engines/twp/font.cpp
    engines/twp/ggpack.h
    engines/twp/module.mk
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp
    engines/twp/vm.h


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 020fc79945a..84e89a328b3 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -170,7 +170,7 @@ bool TokenReader::readToken(Token &token) {
 GGFont::~GGFont() {}
 
 void GGFont::load(const Common::String &path) {
-	SpriteSheet *spritesheet = g_engine->resManager.spriteSheet(path);
+	SpriteSheet *spritesheet = g_engine->_resManager.spriteSheet(path);
 	int lineHeight = 0;
 	for (auto it = spritesheet->frameTable.begin(); it != spritesheet->frameTable.end(); it++) {
 		const SpriteSheetFrame &frame = it->_value;
@@ -201,9 +201,8 @@ Text::Text(const Common::String &fontName, const Common::String &text, TextHAlig
 void Text::update() {
 	if (_dirty) {
 		_dirty = false;
-		// let (_, name, _) = splitFile(self.font.path);
-		_font = g_engine->resManager.font(_fontName);
-		_texture = g_engine->resManager.texture(_font->getName() + ".png");
+		_font = g_engine->_resManager.font(_fontName);
+		_texture = g_engine->_resManager.texture(_font->getName() + ".png");
 
 		// Reset
 		_vertices.clear();
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
new file mode 100644
index 00000000000..c71fef08b42
--- /dev/null
+++ b/engines/twp/genlib.cpp
@@ -0,0 +1,552 @@
+/* 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 "twp/sqgame.h"
+#include "twp/twp.h"
+#include "twp/squtil.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/squirrel/sqvm.h"
+#include "twp/squirrel/sqobject.h"
+#include "twp/squirrel/sqstring.h"
+#include "twp/squirrel/sqstate.h"
+#include "twp/squirrel/sqtable.h"
+#include "twp/squirrel/sqstdstring.h"
+#include "twp/squirrel/sqstdmath.h"
+#include "twp/squirrel/sqstdio.h"
+#include "twp/squirrel/sqstdaux.h"
+#include "twp/squirrel/sqfuncproto.h"
+#include "twp/squirrel/sqclosure.h"
+
+namespace Twp {
+
+struct GetArray {
+	GetArray(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
+
+	void operator()(HSQOBJECT &o) {
+		_objs.push_back(o);
+	}
+
+private:
+	Common::Array<HSQOBJECT> &_objs;
+};
+
+template<typename T>
+static void shuffle(Common::Array<T> &array) {
+	if (array.size() > 1) {
+		Common::RandomSource &rnd = g_engine->getRandomSource();
+		for (size_t i = 0; i < array.size() - 1; i++) {
+			size_t j = i + rnd.getRandomNumber(RAND_MAX) / (RAND_MAX / (array.size() - i) + 1);
+			T &t = array[j];
+			array[j] = array[i];
+			array[i] = t;
+		}
+	}
+}
+
+static SQInteger activeVerb(HSQUIRRELVM v) {
+	// TODO: activeVerb
+	// push(v, gEngine.hud.verb.id);
+	// return 1;
+	warning("activeVerb not implemented");
+	return 0;
+}
+
+static SQInteger adhocalytics(HSQUIRRELVM v) {
+	warning("adhocalytics not implemented");
+	return 0;
+}
+
+static SQInteger arrayShuffle(HSQUIRRELVM v) {
+	if (sq_gettype(v, 2) != OT_ARRAY)
+		return sq_throwerror(v, "An array is expected");
+	HSQOBJECT obj;
+	sq_getstackobj(v, 2, &obj);
+	Common::Array<HSQOBJECT> arr;
+	GetArray g(arr);
+	sqgetitems(obj, g);
+	shuffle(arr);
+
+	sq_newarray(v, 0);
+	for (auto it = arr.begin(); it != arr.end(); it++) {
+		push(v, *it);
+		sq_arrayappend(v, -2);
+	}
+	return 1;
+}
+
+// Returns TRUE if the specified entry exists in the assets.
+static SQInteger assetExists(HSQUIRRELVM v) {
+	const SQChar *filename;
+	if (SQ_FAILED(sq_getstring(v, 2, &filename)))
+		return sq_throwerror(v, "failed to get filename");
+	push(v, g_engine->_pack.assetExists(filename));
+	return 1;
+}
+
+// Moves the camera to the specified x, y location.
+// If a spot is specified, will move to the x, y specified by that spot.
+// .. code-block:: Squirrel
+// cameraAt(450, 128)
+//
+// enterRoomFromDoor(Bridge.startRight)
+// actorAt(ray, Bridge.startLeft)
+// actorAt(reyes, Bridge.startRight)
+// cameraAt(Bridge.bridgeBody)
+static SQInteger cameraAt(HSQUIRRELVM v) {
+	// TODO: cameraAt
+	warning("cameraAt not implemented");
+	return 0;
+}
+
+// Sets how far the camera can pan.
+static SQInteger cameraBounds(HSQUIRRELVM v) {
+	// TODO: cameraBounds
+	warning("cameraBounds not implemented");
+	return 0;
+}
+
+static SQInteger cameraFollow(HSQUIRRELVM v) {
+	// TODO: cameraFollow
+	warning("cameraFollow not implemented");
+	return 0;
+}
+
+// Moves the camera to the specified room.
+//
+// Does not move any of the actors.
+//
+// .. code-block:: Squirrel
+// aStreetPhoneBook =
+// {
+//     name = "phone book"
+//     verbLookAt = function()
+//     {
+//         cameraInRoom(PhoneBook)
+//      }
+// }
+static SQInteger cameraInRoom(HSQUIRRELVM v) {
+	// TODO: cameraInRoom
+	warning("cameraInRoom not implemented");
+	return 0;
+}
+
+// Pans the camera to the specified x, y location over the duration using the transition method.
+// Transition methods are: EASE_IN, EASE_INOUT, EASE_OUT, LINEAR, SLOW_EASE_IN, SLOW_EASE_OUT.
+//
+// .. code-block:: Squirrel
+// cameraPanTo(450, 128, pan_time, EASE_INOUT)
+// inputOff()
+// actorWalkTo(currentActor, Highway.detectiveSpot1)
+// breakwhilewalking(currentActor)
+// cameraPanTo(currentActor, 2.0)
+static SQInteger cameraPanTo(HSQUIRRELVM v) {
+	// TODO: cameraPanTo
+	warning("cameraPanTo not implemented");
+	return 0;
+}
+
+// Returns the current camera position x, y.
+static SQInteger cameraPos(HSQUIRRELVM v) {
+	// TODO: cameraPos
+	warning("cameraPos not implemented");
+	return 0;
+	// push(v, g_engine->cameraPos())
+	// return 1;
+}
+
+// Converts an integer to a char.
+static SQInteger sqChr(HSQUIRRELVM v) {
+	int value;
+	get(v, 2, value);
+	Common::String s;
+	s += char(value);
+	push(v, s);
+	return 1;
+}
+
+// Returns x coordinates of the mouse in screen coordinates.
+static SQInteger cursorPosX(HSQUIRRELVM v) {
+	// TODO: cursorPosX
+	warning("cursorPosX not implemented");
+	return 0;
+
+	//   let scrPos = winToScreen(mousePos())
+	//   push(v, scrPos.x)
+	//   return 1;
+}
+
+// Returns y coordinates of the mouse in screen coordinates.
+static SQInteger cursorPosY(HSQUIRRELVM v) {
+	// TODO: cursorPosY
+	warning("cursorPosY not implemented");
+	return 0;
+	//   let scrPos = winToScreen(mousePos())
+	//   push(v, scrPos.y)
+	//   return 1;
+}
+
+static SQInteger distance(HSQUIRRELVM v) {
+	// TODO: distance
+	warning("distance not implemented");
+	return 0;
+}
+
+static SQInteger findScreenPosition(HSQUIRRELVM v) {
+	// TODO: findScreenPosition
+	warning("findScreenPosition not implemented");
+	return 0;
+}
+
+static SQInteger frameCounter(HSQUIRRELVM v) {
+	// TODO: frameCounter
+	warning("frameCounter not implemented");
+	return 0;
+}
+
+static SQInteger getUserPref(HSQUIRRELVM v) {
+	// TODO: getUserPref
+	warning("getUserPref not implemented");
+	return 0;
+}
+
+static SQInteger getPrivatePref(HSQUIRRELVM v) {
+	// TODO: getPrivatePref
+	warning("getPrivatePref not implemented");
+	return 0;
+}
+
+static SQInteger incutscene(HSQUIRRELVM v) {
+	// TODO: incutscene
+	warning("incutscene not implemented");
+	return 0;
+}
+
+static SQInteger indialog(HSQUIRRELVM v) {
+	// TODO: indialog
+	warning("indialog not implemented");
+	return 0;
+}
+
+static SQInteger integer(HSQUIRRELVM v) {
+	float f = 0.f;
+	if (SQ_FAILED(get(v, 2, f)))
+		return sq_throwerror(v, "failed to get float value");
+	push(v, static_cast<int>(f));
+	return 1;
+}
+
+static SQInteger is_oftype(HSQUIRRELVM v, bool pred(SQObjectType)) {
+	push(v, pred(sq_gettype(v, 2)));
+	return 1;
+}
+
+static SQInteger in_array(HSQUIRRELVM v) {
+	HSQOBJECT obj;
+	sq_resetobject(&obj);
+	if (SQ_FAILED(get(v, 2, obj)))
+		return sq_throwerror(v, "Failed to get object");
+	HSQOBJECT arr;
+	sq_resetobject(&arr);
+	if (SQ_FAILED(get(v, 3, arr)))
+		return sq_throwerror(v, "Failed to get array");
+
+	Common::Array<HSQOBJECT> objs;
+	sq_pushobject(v, arr);
+	sq_pushnull(v); // null iterator
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		HSQOBJECT tmp;
+		sq_getstackobj(v, -1, &tmp);
+		objs.push_back(tmp);
+		sq_pop(v, 2); // pops key and val before the nex iteration
+	}
+	sq_pop(v, 1); // pops the null iterator
+
+	for (auto it = objs.begin(); it != objs.end(); it++) {
+		sq_pushobject(v, obj);
+		sq_pushobject(v, *it);
+		if (sq_cmp(v) == 0) {
+			sq_pop(v, 2);
+			push(v, 1);
+			return 1;
+		}
+		sq_pop(v, 2);
+	}
+
+	sq_pushinteger(v, 0);
+	return 1;
+}
+
+static SQInteger is_array(HSQUIRRELVM v) {
+	return is_oftype(v, [](SQObjectType type) { return type == OT_ARRAY; });
+}
+
+static SQInteger is_function(HSQUIRRELVM v) {
+	return is_oftype(v, [](SQObjectType type) { return (type == OT_CLOSURE) || (type == OT_NATIVECLOSURE); });
+}
+
+static SQInteger is_string(HSQUIRRELVM v) {
+	return is_oftype(v, [](SQObjectType type) { return type == OT_STRING; });
+}
+
+static SQInteger is_table(HSQUIRRELVM v) {
+	return is_oftype(v, [](SQObjectType type) { return type == OT_TABLE; });
+}
+
+// Returns a random number from from to to inclusively.
+// The number is a pseudo-random number and the game will produce the same sequence of numbers unless primed using randomSeed(seed).
+//
+// .. code-block:: Squirrel
+// wait_time = random(0.5, 2.0)
+static SQInteger sqrandom(HSQUIRRELVM v) {
+	if (sq_gettype(v, 2) == OT_FLOAT || sq_gettype(v, 3) == OT_FLOAT) {
+		SQFloat min, max;
+		sq_getfloat(v, 2, &min);
+		sq_getfloat(v, 3, &max);
+		if (min > max)
+			SWAP(min, max);
+		float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+		SQFloat value = min + scale * (max - min);
+		sq_pushfloat(v, value);
+	} else {
+		SQInteger min, max;
+		sq_getinteger(v, 2, &min);
+		sq_getinteger(v, 3, &max);
+		if (min > max)
+			SWAP(min, max);
+		SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max);
+		sq_pushinteger(v, value);
+	}
+	return 1;
+}
+
+static SQInteger loadArray(HSQUIRRELVM v) {
+	// TODO: loadArray
+	warning("loadArray not implemented");
+	return 0;
+}
+
+static SQInteger markAchievement(HSQUIRRELVM v) {
+	// TODO: markAchievement
+	warning("markAchievement not implemented");
+	return 0;
+}
+
+static SQInteger markProgress(HSQUIRRELVM v) {
+	// TODO: markProgress
+	warning("markProgress not implemented");
+	return 0;
+}
+
+static SQInteger markStat(HSQUIRRELVM v) {
+	// TODO: markStat
+	warning("markStat not implemented");
+	return 0;
+}
+
+static SQInteger ord(HSQUIRRELVM v) {
+	// TODO: ord
+	warning("ord not implemented");
+	return 0;
+}
+
+static SQInteger pushSentence(HSQUIRRELVM v) {
+	// TODO: pushSentence
+	warning("pushSentence not implemented");
+	return 0;
+}
+
+static SQInteger randomFrom(HSQUIRRELVM v) {
+	// TODO: randomFrom
+	warning("randomFrom not implemented");
+	return 0;
+}
+
+static SQInteger randomOdds(HSQUIRRELVM v) {
+	// TODO: randomOdds
+	warning("randomOdds not implemented");
+	return 0;
+}
+
+static SQInteger randomseed(HSQUIRRELVM v) {
+	// TODO: randomseed
+	warning("randomseed not implemented");
+	return 0;
+}
+
+static SQInteger refreshUI(HSQUIRRELVM v) {
+	// TODO: refreshUI
+	warning("refreshUI not implemented");
+	return 0;
+}
+
+static SQInteger screenSize(HSQUIRRELVM v) {
+	// TODO: screenSize
+	warning("screenSize not implemented");
+	return 0;
+}
+
+static SQInteger setDebugger(HSQUIRRELVM v) {
+	// TODO: setDebugger
+	warning("setDebugger not implemented");
+	return 0;
+}
+
+static SQInteger setPrivatePref(HSQUIRRELVM v) {
+	// TODO: setPrivatePref
+	warning("setPrivatePref not implemented");
+	return 0;
+}
+
+static SQInteger setUserPref(HSQUIRRELVM v) {
+	// TODO: setUserPref
+	warning("setUserPref not implemented");
+	return 0;
+}
+
+static SQInteger setVerb(HSQUIRRELVM v) {
+	// TODO: setVerb
+	warning("setVerb not implemented");
+	return 0;
+}
+
+static SQInteger startDialog(HSQUIRRELVM v) {
+	// TODO: startDialog
+	warning("startDialog not implemented");
+	return 0;
+}
+
+static SQInteger stopSentence(HSQUIRRELVM v) {
+	// TODO: stopSentence
+	warning("stopSentence not implemented");
+	return 0;
+}
+
+static SQInteger strcount(HSQUIRRELVM v) {
+	// TODO: strcount
+	warning("strcount not implemented");
+	return 0;
+}
+
+static SQInteger strcrc(HSQUIRRELVM v) {
+	// TODO: strcrc
+	warning("strcrc not implemented");
+	return 0;
+}
+
+static SQInteger strfind(HSQUIRRELVM v) {
+	// TODO: strfind
+	warning("strfind not implemented");
+	return 0;
+}
+
+static SQInteger strfirst(HSQUIRRELVM v) {
+	// TODO: strfirst
+	warning("strfirst not implemented");
+	return 0;
+}
+
+static SQInteger strlast(HSQUIRRELVM v) {
+	// TODO: strlast
+	warning("strlast not implemented");
+	return 0;
+}
+
+static SQInteger strlines(HSQUIRRELVM v) {
+	// TODO: strlines
+	warning("strlines not implemented");
+	return 0;
+}
+
+static SQInteger strreplace(HSQUIRRELVM v) {
+	// TODO: strreplace
+	warning("strreplace not implemented");
+	return 0;
+}
+
+static SQInteger strsplit(HSQUIRRELVM v) {
+	// TODO: strsplit
+	warning("strsplit not implemented");
+	return 0;
+}
+
+static SQInteger translate(HSQUIRRELVM v) {
+	// TODO: translate
+	warning("translate not implemented");
+	return 0;
+}
+
+void sqgame_register_genlib(HSQUIRRELVM v) {
+	regFunc(v, activeVerb, _SC("activeVerb"));
+	regFunc(v, adhocalytics, _SC("adhocalytics"));
+	regFunc(v, arrayShuffle, _SC("arrayShuffle"));
+	regFunc(v, assetExists, _SC("assetExists"));
+	regFunc(v, cameraAt, _SC("cameraAt"));
+	regFunc(v, cameraBounds, _SC("cameraBounds"));
+	regFunc(v, cameraFollow, _SC("cameraFollow"));
+	regFunc(v, cameraInRoom, _SC("cameraInRoom"));
+	regFunc(v, cameraPanTo, _SC("cameraPanTo"));
+	regFunc(v, cameraPos, _SC("cameraPos"));
+	regFunc(v, sqChr, _SC("chr"));
+	regFunc(v, cursorPosX, _SC("cursorPosX"));
+	regFunc(v, cursorPosY, _SC("cursorPosY"));
+	regFunc(v, distance, _SC("distance"));
+	regFunc(v, findScreenPosition, _SC("findScreenPosition"));
+	regFunc(v, frameCounter, _SC("frameCounter"));
+	regFunc(v, getUserPref, _SC("getUserPref"));
+	regFunc(v, getPrivatePref, _SC("getPrivatePref"));
+	regFunc(v, incutscene, _SC("incutscene"));
+	regFunc(v, indialog, _SC("indialog"));
+	regFunc(v, integer, _SC("integer"));
+	regFunc(v, in_array, _SC("in_array"));
+	regFunc(v, is_array, _SC("is_array"));
+	regFunc(v, is_function, _SC("is_function"));
+	regFunc(v, is_string, _SC("is_string"));
+	regFunc(v, is_table, _SC("is_table"));
+	regFunc(v, sqrandom, _SC("random"));
+	regFunc(v, loadArray, _SC("loadArray"));
+	regFunc(v, markAchievement, _SC("markAchievement"));
+	regFunc(v, markProgress, _SC("markProgress"));
+	regFunc(v, markStat, _SC("markStat"));
+	regFunc(v, ord, _SC("ord"));
+	regFunc(v, pushSentence, _SC("pushSentence"));
+	regFunc(v, randomFrom, _SC("randomFrom"));
+	regFunc(v, randomOdds, _SC("randomOdds"));
+	regFunc(v, randomseed, _SC("randomseed"));
+	regFunc(v, refreshUI, _SC("refreshUI"));
+	regFunc(v, screenSize, _SC("screenSize"));
+	regFunc(v, setDebugger, _SC("setDebugger"));
+	regFunc(v, setPrivatePref, _SC("setPrivatePref"));
+	regFunc(v, setUserPref, _SC("setUserPref"));
+	regFunc(v, setVerb, _SC("setVerb"));
+	regFunc(v, startDialog, _SC("startDialog"));
+	regFunc(v, stopSentence, _SC("stopSentence"));
+	regFunc(v, strcount, _SC("strcount"));
+	regFunc(v, strcrc, _SC("strcrc"));
+	regFunc(v, strfind, _SC("strfind"));
+	regFunc(v, strfirst, _SC("strfirst"));
+	regFunc(v, strlast, _SC("strlast"));
+	regFunc(v, strlines, _SC("strlines"));
+	regFunc(v, strreplace, _SC("strreplace"));
+	regFunc(v, strsplit, _SC("strsplit"));
+	regFunc(v, translate, _SC("translate"));
+}
+
+} // namespace Twp
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index 9725576619e..d2b5640fb34 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -122,6 +122,8 @@ public:
 
 	bool open(Common::SeekableReadStream *s, const XorKey& key);
 
+	bool assetExists(const char* asset) { return _entries.contains(asset); }
+
 private:
 	XorKey _key;
 	GGPackEntries _entries;
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 22d87a85bb8..7319a99ded8 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -34,6 +34,12 @@ MODULE_OBJS = \
 	room.o \
 	lighting.o \
 	font.o \
+	sqgame.o \
+	syslib.o \
+	objlib.o \
+	genlib.o \
+	squtil.o \
+	thread.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
new file mode 100644
index 00000000000..3b4bb892b79
--- /dev/null
+++ b/engines/twp/objlib.cpp
@@ -0,0 +1,121 @@
+/* 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 "twp/twp.h"
+#include "twp/sqgame.h"
+#include "twp/squtil.h"
+#include "squirrel/squirrel.h"
+#include "squirrel/sqvm.h"
+#include "squirrel/sqobject.h"
+#include "squirrel/sqstring.h"
+#include "squirrel/sqstate.h"
+#include "squirrel/sqtable.h"
+#include "squirrel/sqstdstring.h"
+#include "squirrel/sqstdmath.h"
+#include "squirrel/sqstdio.h"
+#include "squirrel/sqstdaux.h"
+#include "squirrel/sqfuncproto.h"
+#include "squirrel/sqclosure.h"
+
+namespace Twp {
+
+extern TwpEngine *g_engine;
+
+// Creates a new, room local object using sheet as the sprite sheet and image as the image name.
+// This object is deleted when the room exits.
+// If sheet parameter not provided, use room's sprite sheet instead.
+// If image is an array, then use that as a sequence of frames for animation.
+// Objects created at runtime can be passed to all the object commands.
+// They do not have verbs or local variables by default, but these can be added when the object is created so it can be used in the construction of sentences.
+static SQInteger createObject(HSQUIRRELVM v) {
+	SQInteger numArgs = sq_gettop(v);
+	Common::String sheet;
+	Common::Array<Common::String> frames;
+	SQInteger framesIndex = 2;
+
+	// get sheet parameter if any
+	if (numArgs == 3) {
+		if (SQ_FAILED(get(v, 2, sheet)))
+			return sq_throwerror(v, "failed to get sheet");
+		framesIndex = 3;
+	}
+
+	// get frames parameter if any
+	if (numArgs >= 2) {
+		switch (sq_gettype(v, framesIndex)) {
+		case OT_STRING: {
+			Common::String frame;
+			get(v, framesIndex, frame);
+			frames.push_back(frame);
+		} break;
+		case OT_ARRAY:
+			sqgetarray(v, framesIndex, frames);
+			break;
+		default:
+			return sq_throwerror(v, "Invalid parameter 2: expecting a string or an array");
+		}
+	}
+
+	debug("Create object: %s, %u", sheet.c_str(), frames.size());
+
+	Object *obj = g_engine->_room->createObject(sheet, frames);
+
+	sq_pushobject(v, obj->table);
+
+	return 1;
+}
+
+static SQInteger objectAt(HSQUIRRELVM v) {
+	HSQOBJECT o;
+	sq_getstackobj(v, 2, &o);
+
+	SQInteger id;
+	getf(o, "_id", id);
+
+	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
+		SQObjectPtr id2;
+		_table(o->table)->Get(sqobj(v, "_id"), id2);
+		return id == _integer(id2);
+	});
+
+	if (!pObj)
+		return sq_throwerror(v, "failed to get object");
+
+	// TODO:
+	// Object* obj = *pObj;
+	// SQInteger x, y;
+	// if (SQ_FAILED(sq_getinteger(v, 3, &x)))
+	// 	return sq_throwerror(v, "failed to get x");
+	// if (SQ_FAILED(sq_getinteger(v, 4, &y)))
+	// 	return sq_throwerror(v, "failed to get y");
+	// obj->x = x;
+	// obj->y = y;
+	// debug("Object at: %lld, %lld", x, y);
+
+	return 0;
+}
+
+void sqgame_register_objlib(HSQUIRRELVM v) {
+	regFunc(v, createObject, _SC("createObject"), 0, nullptr);
+	regFunc(v, objectAt, _SC("objectAt"), 0, nullptr);
+}
+
+} // namespace Twp
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 4e93567d4f5..faeee2444ae 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -47,7 +47,7 @@ Common::String getKey(const char *path) {
 void ResManager::loadTexture(const Common::String &name) {
 	debug("Load texture %s", name.c_str());
 	GGPackEntryReader r;
-	r.open(g_engine->pack, name);
+	r.open(g_engine->_pack, name);
 	Image::PNGDecoder d;
 	d.loadStream(r);
 	const Graphics::Surface *surface = d.getSurface();
@@ -65,7 +65,7 @@ Texture *ResManager::texture(const Common::String &name) {
 
 void ResManager::loadSpriteSheet(const Common::String &name) {
 	GGPackEntryReader r;
-	r.open(g_engine->pack, name + ".json");
+	r.open(g_engine->_pack, name + ".json");
 
 	// read all contents
 	Common::Array<char> data(r.size());
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index f7cc6fe32af..1fbc84c1081 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -1,6 +1,7 @@
 #include "twp/twp.h"
 #include "twp/room.h"
 #include "twp/ggpack.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
@@ -10,6 +11,39 @@ static Math::Vector2d parseVec2(const Common::String &s) {
 	return {x, y};
 }
 
+static Common::Rect parseRect(const Common::String &s) {
+	float x1, y1;
+	float x2, y2;
+	sscanf(s.c_str(), "{{%f,%f},{%f,%f}}", &x1, &y1, &x2, &y2);
+	return Common::Rect(x1, y1, x2, y2);
+}
+
+static bool toBool(const Common::JSONObject &jNode, const Common::String &key) {
+	return jNode.contains(key) && jNode[key]->asIntegerNumber() == 1;
+}
+
+static ObjectType toObjectType(const Common::JSONObject &jObject) {
+	if (toBool(jObject, "prop"))
+		return otProp;
+	if (toBool(jObject, "spot"))
+		return otSpot;
+	if (toBool(jObject, "trigger"))
+		return otTrigger;
+	return otNone;
+}
+
+static Direction parseUseDir(const Common::String &s) {
+	if (s == "DIR_FRONT")
+		return dFront;
+	if (s == "DIR_BACK")
+		return dBack;
+	if (s == "DIR_LEFT")
+		return dLeft;
+	if (s == "DIR_RIGHT")
+		return dRight;
+	error("invalid use direction: %s", s.c_str());
+}
+
 static Math::Vector2d parseParallax(const Common::JSONValue &v) {
 	if (v.isIntegerNumber()) {
 		return {(float)v.asIntegerNumber(), 1};
@@ -23,17 +57,98 @@ static Math::Vector2d parseParallax(const Common::JSONValue &v) {
 	error("parseParallax expected a float, int or string, not this: %s", v.stringify().c_str());
 }
 
+static Walkbox parseWalkbox(const Common::String &text) {
+	Common::Array<Math::Vector2d> points;
+	int i = 1;
+	int endPos;
+	do {
+		uint32 commaPos = text.find(',', i);
+		long x = strtol(text.substr(i, commaPos - i).c_str(), nullptr, 10);
+		endPos = text.find('}', commaPos + 1);
+		long y = std::strtol(text.substr(commaPos + 1, endPos - commaPos - 1).c_str(), nullptr, 10);
+		i = endPos + 3;
+		points.push_back({(float)x, (float)y});
+	} while ((text.size() - 1) != endPos);
+	return Walkbox(points);
+}
+
+static float parseFps(const Common::JSONValue &jFps) {
+	if (jFps.isNumber())
+		return jFps.asNumber();
+	if (jFps.isIntegerNumber())
+		return jFps.asIntegerNumber();
+	error("fps should be a number: %s", jFps.stringify().c_str());
+}
+
+static ObjectAnimation parseObjectAnimation(const Common::JSONObject &jAnim) {
+	ObjectAnimation result;
+	if (jAnim.contains("sheet"))
+		result.sheet = jAnim["sheet"]->asString();
+	result.name = jAnim["name"]->asString();
+	result.loop = toBool(jAnim, "loop");
+	result.fps = jAnim.contains("fps") ? parseFps(*jAnim["fps"]) : 0.f;
+	result.flags = jAnim.contains("flags") && jAnim["flags"]->isIntegerNumber() ? jAnim["flags"]->asIntegerNumber() : 0;
+	if (jAnim.contains("frames") && jAnim["frames"]->isArray()) {
+		const Common::JSONArray &jFrames = jAnim["frames"]->asArray();
+		for (auto it = jFrames.begin(); it != jFrames.end(); it++) {
+			Common::String name = (*it)->asString();
+			result.frames.push_back(name);
+		}
+	}
+
+	if (jAnim.contains("layers") && jAnim["layers"]->isArray()) {
+		const Common::JSONArray &jLayers = jAnim["layers"]->asArray();
+		for (auto it = jLayers.begin(); it != jLayers.end(); it++) {
+			ObjectAnimation layer = parseObjectAnimation((*it)->asObject());
+			result.layers.push_back(layer);
+		}
+	}
+
+	if (jAnim.contains("triggers") && jAnim["triggers"]->isArray()) {
+		const Common::JSONArray &jTriggers = jAnim["triggers"]->asArray();
+		for (auto it = jTriggers.begin(); it != jTriggers.end(); it++) {
+			result.triggers.push_back((*it)->asString());
+		}
+	}
+
+	if (jAnim.contains("offsets") && jAnim["offsets"]->isArray()) {
+		const Common::JSONArray &jOffsets = jAnim["offsets"]->asArray();
+		for (auto it = jOffsets.begin(); it != jOffsets.end(); it++) {
+			result.offsets.push_back(parseVec2((*it)->asString()));
+		}
+	}
+	return result;
+}
+
+static void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims) {
+	for (auto it = jAnims.begin(); it != jAnims.end(); it++) {
+		anims.push_back(parseObjectAnimation((*it)->asObject()));
+	}
+}
+
+static Scaling parseScaling(const Common::JSONArray &jScalings) {
+	float scale;
+	int y;
+	Scaling result;
+	for (auto it = jScalings.begin(); it != jScalings.end(); it++) {
+		const Common::String &v = (*it)->asString();
+		sscanf(v.c_str(), "%f@%d", &scale, &y);
+		result.values.push_back(ScalingValue{scale, y});
+	}
+	return result;
+}
+
 void Room::load(Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
 	const Common::JSONObject &jRoom = value->asObject();
 
-	name = jRoom["name"]->asString();
-	sheet = jRoom["sheet"]->asString();
+	_name = jRoom["name"]->asString();
+	_sheet = jRoom["sheet"]->asString();
 
-	roomSize = parseVec2(jRoom["roomsize"]->asString());
-	height = jRoom.contains("height") ? jRoom["height"]->asIntegerNumber() : roomSize.getY();
-	fullscreen = jRoom.contains("fullscreen") ? jRoom["fullscreen"]->asIntegerNumber() : 0;
+	_roomSize = parseVec2(jRoom["roomsize"]->asString());
+	_height = jRoom.contains("height") ? jRoom["height"]->asIntegerNumber() : _roomSize.getY();
+	_fullscreen = jRoom.contains("fullscreen") ? jRoom["fullscreen"]->asIntegerNumber() : 0;
 
 	// backgrounds
 	Common::StringArray backNames;
@@ -51,7 +166,7 @@ void Room::load(Common::SeekableReadStream &s) {
 		layer.names.push_back(backNames);
 		layer.zsort = 0;
 		layer.parallax = Math::Vector2d(1, 1);
-		layers.push_back(layer);
+		_layers.push_back(layer);
 	}
 
 	// layers
@@ -70,12 +185,141 @@ void Room::load(Common::SeekableReadStream &s) {
 			}
 			layer.parallax = parseParallax(*jLayer["parallax"]);
 			layer.zsort = jLayer["zsort"]->asIntegerNumber();
-			layers.push_back(layer);
+			_layers.push_back(layer);
 		}
 	}
 
-	// TODO: walkboxes, objects, scalings
+	// walkboxes
+	if (jRoom.contains("walkboxes")) {
+		const Common::JSONArray &jWalkboxes = jRoom["walkboxes"]->asArray();
+		for (auto it = jWalkboxes.begin(); it != jWalkboxes.end(); it++) {
+			const Common::JSONObject &jWalkbox = (*it)->asObject();
+			Walkbox walkbox = parseWalkbox(jWalkbox["polygon"]->asString());
+			if (jWalkbox.contains("name") && jWalkbox["name"]->isString()) {
+				walkbox.name = jWalkbox["name"]->asString();
+			}
+			_walkboxes.push_back(walkbox);
+		}
+	}
+
+	// objects
+	if (jRoom.contains("objects")) {
+		const Common::JSONArray &jobjects = jRoom["objects"]->asArray();
+		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
+			const Common::JSONObject &jObject = (*it)->asObject();
+			Object obj;
+			obj.state = -1;
+			//   Node objNode(obj.key)
+			// objNode.pos = Math::Vector2d(parseVec2(jObject["pos"]->asString()));
+			//   objNode.zOrder = jObject["zsort"].getInt().int32
+			//   obj.node = objNode
+			//   obj.nodeAnim = newAnim(obj)
+			//   obj.node.addChild obj.nodeAnim
+			obj.key = jObject["name"]->asString();
+			obj.usePos = parseVec2(jObject["usepos"]->asString());
+			if (jObject.contains("usedir")) {
+				obj.useDir = parseUseDir(jObject["usedir"]->asString());
+			} else {
+				obj.useDir = dNone;
+			}
+			obj.hotspot = parseRect(jObject["hotspot"]->asString());
+			obj.objType = toObjectType(jObject);
+			if (jObject.contains("parent"))
+				jObject["parent"]->asString();
+			obj.room = this;
+			if (jObject.contains("animations")) {
+				parseObjectAnimations(jObject["animations"]->asArray(), obj.anims);
+			}
+			//   obj.layer = result.layer(0);
+			//   result.layer(0).objects.add(obj);
+		}
+	}
+
+	// scalings
+	if (jRoom.contains("scaling")) {
+		const Common::JSONArray &jScalings = jRoom["scaling"]->asArray();
+		if (jScalings[0]->isString()) {
+			_scalings.push_back(parseScaling(jScalings));
+		} else {
+			for (auto it = jScalings.begin(); it != jScalings.end(); it++) {
+				const Common::JSONObject &jScaling = (*it)->asObject();
+				Scaling scaling = parseScaling(jScaling["scaling"]->asArray());
+				if (jScaling.contains("trigger") && jScaling["trigger"]->isString())
+					scaling.trigger = jScaling["trigger"]->asString();
+				_scalings.push_back(scaling);
+			}
+		}
+		_scaling = _scalings[0];
+	}
+
 	delete value;
 }
 
+Math::Vector2d Room::getScreenSize() {
+	switch (_height) {
+	case 128:
+		return {320, 180};
+	case 172:
+		return {428, 240};
+	case 256:
+		return {640, 360};
+	default:
+		return {_roomSize.getX(), (float)_height};
+	}
+}
+
+Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
+	Object *obj = new Object();
+	obj->temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->table);
+	sq_addref(v, &obj->table);
+	sq_pop(v, 1);
+
+	// assign an id
+	static int gId = 3000;
+	setId(obj->table, gId++);
+	Common::String name = frames.size() > 0 ? frames[0] : "noname";
+	setf(obj->table, "name", name);
+	obj->key = name;
+	debug("Create object with new table: %s #%d", obj->name.c_str(), obj->getId());
+
+	obj->room = this;
+	obj->sheet = sheet;
+	obj->touchable = false;
+
+	// create anim if any
+	if (frames.size() > 0) {
+		ObjectAnimation objAnim;
+		objAnim.name = "state0";
+		objAnim.frames.push_back(frames);
+		obj->anims.push_back(objAnim);
+	}
+
+	// TODO: adds object to the scenegraph
+	// obj->node.zOrder = 1
+	//   layer(0).objects.add(obj)
+	//   layer(0).node.addChild obj.node
+	//   obj->layer = self.layer(0)
+	// TODO: obj->setState(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
+int Object::getId() {
+	SQInteger result=0;
+	getf(table, "_id", result);
+	return (int)result;
+}
+
+Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
+	: _polygon(polygon), _visible(visible) {
+}
+
 } // namespace Twp
diff --git a/engines/twp/room.h b/engines/twp/room.h
index b67bf8497dc..f6ec9cdc7d2 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -31,22 +31,102 @@ namespace Twp {
 
 class Layer {
 public:
- 	Common::Array<Common::String> names;
-    Math::Vector2d parallax;
-    int zsort;
+	Common::Array<Common::String> names;
+	Math::Vector2d parallax;
+	int zsort;
+};
+
+// Represents an area where an actor can or cannot walk
+class Walkbox {
+public:
+	Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
+
+public:
+	Common::String name;
+
+private:
+	Common::Array<Math::Vector2d> _polygon;
+	bool _visible;
+};
+
+enum Direction {
+	dNone = 0,
+	dRight = 1,
+	dLeft = 2,
+	dFront = 4,
+	dBack = 8
+};
+
+enum ObjectType {
+	otNone,
+	otProp,
+	otSpot,
+	otTrigger
+};
+
+struct ObjectAnimation {
+	Common::String name;
+	Common::String sheet;
+	Common::StringArray frames;
+	Common::Array<ObjectAnimation> layers;
+	Common::StringArray triggers;
+	Common::Array<Math::Vector2d> offsets;
+	bool loop;
+	float fps;
+	int flags;
+	int frameIndex;
+};
+
+struct ScalingValue {
+	float scale;
+	int y;
+};
+
+struct Scaling {
+	Common::Array<ScalingValue> values;
+	Common::String trigger;
+};
+
+class Room;
+class Object {
+public:
+	int getId();
+
+public:
+	HSQOBJECT table;
+	Common::String name;
+	Common::String sheet;
+	Common::String key; // key used to identify this object by script
+	int state;
+	Math::Vector2d usePos;
+	Direction useDir;
+	Common::Rect hotspot;
+	ObjectType objType;
+	Room *room;
+	Common::Array<ObjectAnimation> anims;
+	bool temporary;
+	bool touchable;
 };
 
 class Room {
 public:
 	void load(Common::SeekableReadStream &s);
 
+	Object* createObject(const Common::String& sheet, const Common::Array<Common::String>& frames);
+
+	Math::Vector2d getScreenSize();
+	Math::Vector2d roomToScreen(Math::Vector2d pos);
+
 public:
-	Common::String name;			// Name of the room
-	Common::String sheet;			// Name of the spritesheet to use
-	Math::Vector2d roomSize;		// Size of the room
-	int fullscreen;				// Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
-	int height;					// Height of the room (what else ?)
-	Common::Array<Layer> layers;	// Parallax layers of a room
+	Common::String _name;              // Name of the room
+	Common::String _sheet;             // Name of the spritesheet to use
+	Math::Vector2d _roomSize;          // Size of the room
+	int _fullscreen;                   // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
+	int _height;                       // Height of the room (what else ?)
+	Common::Array<Layer> _layers;      // Parallax layers of a room
+	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
+	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
+    Scaling _scaling;				  // Defines the scaling of the actor in the room
 };
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
new file mode 100644
index 00000000000..754aa8a7665
--- /dev/null
+++ b/engines/twp/scenegraph.cpp
@@ -0,0 +1,119 @@
+/* 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 "math/matrix3.h"
+#include "scenegraph.h"
+
+namespace Twp {
+
+Node::Node(const Common::String &name)
+	: _name(name), _color(1.f, 1.f, 1.f, 1.f), _computedColor(1.f, 1.f, 1.f, 1.f), _visible(true), _rotation(0.f), _rotationOffset(0.f) {
+}
+
+Node::~Node() {}
+
+void Node::addChild(Node *child) {
+	if (child->_parent) {
+		child->_pos -= getAbsPos();
+		for (int i = 0; i < _children.size(); i++) {
+			if (_children[i] == child) {
+				child->_parent->_children.erase(child->_parent->_children.begin() + i);
+				break;
+			}
+		}
+	}
+	_children.push_back(child);
+	child->_parent = this;
+	child->updateColor();
+	child->updateAlpha();
+}
+
+void Node::updateColor() {
+	Color parentColor = !_parent ? Color(1.f, 1.f, 1.f) : _parent->_computedColor;
+	updateColor(parentColor);
+}
+
+void Node::updateAlpha() {
+	float parentOpacity = !_parent ? 1.f : _parent->_computedColor.rgba.a;
+	updateAlpha(parentOpacity);
+}
+
+void Node::updateColor(Color parentColor) {
+	_computedColor.rgba.r = _color.rgba.r * parentColor.rgba.r;
+	_computedColor.rgba.g = _color.rgba.g * parentColor.rgba.g;
+	_computedColor.rgba.b = _color.rgba.b * parentColor.rgba.b;
+	onColorUpdated(_computedColor);
+	for (int i = 0; i < _children.size(); i++) {
+		Node *child = _children[i];
+		child->updateColor(_computedColor);
+	}
+}
+
+void Node::updateAlpha(float parentAlpha) {
+	_computedColor.rgba.a = _color.rgba.a * parentAlpha;
+	onColorUpdated(_computedColor);
+	for (int i = 0; i < _children.size(); i++) {
+		Node *child = _children[i];
+		child->updateAlpha(_computedColor.rgba.a);
+	}
+}
+
+static bool cmpNodes()(const Node* x, const Node* y)
+	return x->() < y.getZSort();
+}
+
+void Node::draw(Math::Matrix4 parent) {
+	// Draws `self` node.
+	if (_visible) {
+		Math::Matrix4 trsf = getTrsf(parent);
+		Math::Matrix4 myTrsf(trsf);
+		myTrsf.translate(Math::Vector3d(-_anchor.getX(), _anchor.getY(), 0.f));
+		//_children.sort(proc(x, y : Node) : int = cmp(y.getZSort, x.getZSort));
+		Common::sort(_children.begin(), _children.end(), );
+		drawCore(myTrsf);
+		for (int i = 0; i < _children.size(); i++) {
+			Node *child = _children[i];
+			child->draw(trsf);
+		}
+	}
+}
+
+Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
+  // Gets the full transformation for this node.
+  return parentTrsf * getLocalTrsf();
+}
+
+Math::Matrix4 Node::getLocalTrsf() {
+  // Gets the location transformation = translation * rotation * scale.
+	// TODO: scale
+	Math::Vector2d p = _pos + _offset + _shakeOffset;
+	Math::Matrix4 m1;
+	m1.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
+	Math::Matrix3 mRot;
+	mRot.buildAroundZ(Math::Angle(-_rotation+_rotationOffset));
+	Math::Matrix4 m2;
+	m2.setRotation(mRot);
+	 Math::Matrix4 m3;
+	 m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
+	return m1*m2*m3;
+}
+
+} // namespace Twp
diff --git a/engines/twp/sqgame.cpp b/engines/twp/sqgame.cpp
new file mode 100644
index 00000000000..d8147151978
--- /dev/null
+++ b/engines/twp/sqgame.cpp
@@ -0,0 +1,36 @@
+/* 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 "twp/sqgame.h"
+
+namespace Twp {
+
+void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck, const SQChar *typemask) {
+	sq_pushroottable(v);
+	sq_pushstring(v, functionName, -1);
+	sq_newclosure(v, f, 0); // create a new function
+	sq_setparamscheck(v, nparamscheck, typemask);
+	sq_setnativeclosurename(v, -1, functionName);
+	sq_newslot(v, -3, SQFalse);
+	sq_pop(v, 1); // pops the root table
+}
+
+}
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
new file mode 100644
index 00000000000..12292e7b10d
--- /dev/null
+++ b/engines/twp/sqgame.h
@@ -0,0 +1,36 @@
+/* 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 TWP_SQGAME_H
+#define TWP_SQGAME_H
+
+#include <stddef.h>
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck = 0, const SQChar *typemask = NULL);
+void sqgame_register_syslib(HSQUIRRELVM v);
+void sqgame_register_objlib(HSQUIRRELVM v);
+void sqgame_register_genlib(HSQUIRRELVM v);
+
+} // namespace Twp
+#endif
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
new file mode 100644
index 00000000000..8b7bc1ad24c
--- /dev/null
+++ b/engines/twp/squtil.cpp
@@ -0,0 +1,131 @@
+/* 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 "twp/squtil.h"
+#include "squirrel/squirrel.h"
+#include "squirrel/sqvm.h"
+#include "squirrel/sqobject.h"
+#include "squirrel/sqstring.h"
+#include "squirrel/sqstate.h"
+#include "squirrel/sqtable.h"
+#include "squirrel/sqstdstring.h"
+#include "squirrel/sqstdmath.h"
+#include "squirrel/sqstdio.h"
+#include "squirrel/sqstdaux.h"
+#include "squirrel/sqfuncproto.h"
+#include "squirrel/sqclosure.h"
+
+namespace Twp {
+
+template<>
+void push(HSQUIRRELVM v, int value) {
+	sq_pushinteger(v, value);
+}
+
+template<>
+void push(HSQUIRRELVM v, bool value) {
+	sq_pushinteger(v, value ? 1 : 0);
+}
+
+template<>
+void push(HSQUIRRELVM v, Common::String value) {
+	sq_pushstring(v, value.c_str(), value.size());
+}
+
+template<>
+void push(HSQUIRRELVM v, HSQOBJECT value) {
+	sq_pushobject(v, value);
+}
+
+template<>
+HSQOBJECT sqobj(HSQUIRRELVM v, int value) {
+	SQObject o;
+	o._type = OT_INTEGER;
+	o._unVal.nInteger = value;
+	return o;
+}
+
+template<>
+HSQOBJECT sqobj(HSQUIRRELVM v, const SQChar *value) {
+	SQObject o;
+	o._type = OT_STRING;
+	o._unVal.pString = SQString::Create(_ss(v), value);
+	return o;
+}
+
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, SQInteger &value) {
+	return sq_getinteger(v, i, &value);
+}
+
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, int &value) {
+	SQInteger itg;
+	SQRESULT result = sq_getinteger(v, i, &itg);
+	value = static_cast<int>(itg);
+	return result;
+}
+
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, float &value) {
+	SQFloat f;
+	SQRESULT result = sq_getfloat(v, i, &f);
+	value = static_cast<float>(f);
+	return result;
+}
+
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
+	const SQChar *s;
+	SQRESULT result = sq_getstring(v, i, &s);
+	value = s;
+	return result;
+}
+
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, HSQOBJECT &value) {
+	return sq_getstackobj(v, i, &value);
+}
+
+void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr) {
+	sq_pushobject(v, o);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		const SQChar *str;
+		sq_getstring(v, -1, &str);
+		arr.push_back(str);
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 1);
+}
+
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr) {
+	HSQOBJECT obj;
+	SQRESULT result = sq_getstackobj(v, i, &obj);
+	sqgetarray(v, obj, arr);
+	return result;
+}
+
+void setId(HSQOBJECT &o, int id) {
+	setf(o, "_id", id);
+}
+
+} // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
new file mode 100644
index 00000000000..8cc3f342034
--- /dev/null
+++ b/engines/twp/squtil.h
@@ -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/>.
+ *
+ */
+
+#ifndef TWP_SQUTIL_H
+#define TWP_SQUTIL_H
+
+#include "squirrel/squirrel.h"
+#include "common/str.h"
+#include "twp/twp.h"
+#include "twp/vm.h"
+
+namespace Twp {
+
+template<typename T>
+HSQOBJECT sqobj(HSQUIRRELVM v, T value);
+
+template<typename T>
+void push(HSQUIRRELVM v, T value);
+
+template<typename T>
+SQRESULT get(HSQUIRRELVM v, int index, T &value);
+
+// set field
+template<typename T>
+void setf(HSQOBJECT &o, const Common::String &key, T obj) {
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sq_pushobject(v, o);
+	sq_pushstring(v, key.c_str(), -1);
+	push(v, obj);
+	sq_rawset(v, -3);
+	sq_settop(v, top);
+}
+
+template<typename T>
+void getf(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, T &value) {
+	sq_pushobject(v, o);
+	sq_pushstring(v, name.c_str(), -1);
+	if (SQ_FAILED(sq_get(v, -2)))
+		sq_pop(v, 1);
+	else {
+		get(v, -1, value);
+		sq_pop(v, 2);
+	}
+}
+
+template<typename T>
+void getf(HSQOBJECT o, const Common::String &name, T &value) {
+	HSQUIRRELVM v = g_engine->getVm();
+	getf(v, o, name, value);
+}
+
+void setId(HSQOBJECT &o, int id);
+
+void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr);
+
+template <typename TFunc>
+void sqgetitems(HSQOBJECT o, TFunc func) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sq_pushobject(v, o);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		HSQOBJECT obj;
+		get(v, -1, obj);
+		func(obj);
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 2);
+}
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
new file mode 100644
index 00000000000..76fd06eb912
--- /dev/null
+++ b/engines/twp/syslib.cpp
@@ -0,0 +1,153 @@
+/* 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 "twp/sqgame.h"
+#include "twp/twp.h"
+#include "twp/thread.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/squirrel/sqvm.h"
+#include "twp/squirrel/sqobject.h"
+#include "twp/squirrel/sqstring.h"
+#include "twp/squirrel/sqstate.h"
+#include "twp/squirrel/sqtable.h"
+#include "twp/squirrel/sqstdstring.h"
+#include "twp/squirrel/sqstdmath.h"
+#include "twp/squirrel/sqstdio.h"
+#include "twp/squirrel/sqstdaux.h"
+#include "twp/squirrel/sqfuncproto.h"
+#include "twp/squirrel/sqclosure.h"
+
+namespace Twp {
+
+static Thread *thread(HSQUIRRELVM v) {
+	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](Thread *t) {
+		return t->threadObj._unVal.pThread == v;
+	});
+}
+
+template<typename F>
+static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
+	Thread *t = thread(v);
+	if (!t)
+		return sq_throwerror(v, "failed to get thread");
+	t->suspend();
+	func(*t);
+	return -666;
+}
+
+// When called in a function started with startthread, execution is suspended for time seconds.
+// It is an error to call breaktime in a function that was not started with startthread.
+//
+// . code-block:: Squirrel
+// for (local x = 1; x < 4; x += 1) {
+//   playSound(soundPhoneRinging)
+//   breaktime(5.0)
+// }
+static SQInteger breaktime(HSQUIRRELVM v) {
+	SQFloat time;
+	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
+		return sq_throwerror(v, "failed to get time");
+	if (time == 0.f)
+		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
+	else
+		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
+}
+
+static SQInteger _startthread(HSQUIRRELVM v, bool global) {
+	HSQUIRRELVM vm = g_engine->getVm();
+	SQInteger size = sq_gettop(v);
+
+	Thread *t = new Thread();
+	t->global = global;
+
+	static uint64 gThreadId = 300000;
+	sq_newtable(v);
+	sq_pushstring(v, _SC("_id"), -1);
+	sq_pushinteger(v, gThreadId++);
+	sq_newslot(v, -3, SQFalse);
+	sq_getstackobj(v, -1, &t->obj);
+	sq_addref(vm, &t->obj);
+	sq_pop(v, 1);
+
+	sq_resetobject(&t->envObj);
+	if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj)))
+		return sq_throwerror(v, "Couldn't get environment from stack");
+	sq_addref(vm, &t->envObj);
+
+	// create thread and store it on the stack
+	sq_newthread(vm, 1024);
+	sq_resetobject(&t->threadObj);
+	if (SQ_FAILED(sq_getstackobj(vm, -1, &t->threadObj)))
+		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
+	sq_addref(vm, &t->threadObj);
+
+	for (int i = 0; i < size - 2; i++) {
+		HSQOBJECT arg;
+		sq_resetobject(&arg);
+		if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
+			return sq_throwerror(v, "Couldn't get coroutine args from stack");
+		t->args.push_back(arg);
+		sq_addref(vm, &arg);
+	}
+
+	// get the closure
+	sq_resetobject(&t->closureObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj)))
+		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
+	sq_addref(vm, &t->closureObj);
+
+	const SQChar *name = nullptr;
+	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
+		sq_getstring(v, -1, &name);
+
+	t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line);
+	sq_pop(vm, 1);
+	if (name)
+		sq_pop(v, 1); // pop name
+	sq_pop(v, 1);     // pop closure
+
+	g_engine->_threads.push_back(t);
+
+	debug("create thread %s", t->name.c_str());
+
+	// call the closure in the thread
+	if (!t->call())
+		return sq_throwerror(v, "call failed");
+
+	sq_pushobject(v, t->obj);
+	return 1;
+}
+
+static SQInteger startthread(HSQUIRRELVM v) {
+	return _startthread(v, false);
+}
+
+static SQInteger startglobalthread(HSQUIRRELVM v) {
+	return _startthread(v, true);
+}
+
+void sqgame_register_syslib(HSQUIRRELVM v) {
+	regFunc(v, startthread, _SC("startthread"), 0, nullptr);
+	regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr);
+	regFunc(v, breaktime, _SC("breaktime"), 0, nullptr);
+}
+
+}
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
new file mode 100644
index 00000000000..e280b99e743
--- /dev/null
+++ b/engines/twp/thread.cpp
@@ -0,0 +1,95 @@
+/* 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 "twp/twp.h"
+#include "twp/thread.h"
+
+namespace Twp{
+
+Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
+}
+
+Thread::~Thread() {
+	HSQUIRRELVM v = g_engine->getVm();
+	for (int i = 0; i < args.size(); i++) {
+		sq_release(v, &args[i]);
+	}
+	sq_release(v, &threadObj);
+	sq_release(v, &envObj);
+	sq_release(v, &closureObj);
+}
+
+bool Thread::call() {
+	HSQUIRRELVM v = threadObj._unVal.pThread;
+	// call the closure in the thread
+	SQInteger top = sq_gettop(v);
+	sq_pushobject(v, closureObj);
+	sq_pushobject(v, envObj);
+	for (int i = 0; i < args.size(); i++) {
+		sq_pushobject(v, args[i]);
+	}
+	if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) {
+		sq_settop(v, top);
+		return false;
+	}
+	return true;
+}
+
+bool Thread::isDead() {
+	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
+	return stopRequest || state == 0;
+}
+
+bool Thread::isSuspended() {
+	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
+	return state != 1;
+}
+
+void Thread::suspend() {
+	// TODO: pauseable
+	if (!isSuspended()) {
+		sq_suspendvm(threadObj._unVal.pThread);
+	}
+}
+
+void Thread::resume() {
+	if (!isDead() && isSuspended()) {
+		sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
+	}
+}
+
+bool Thread::update(float elapsed) {
+	if (paused) {
+	} else if (waitTime > 0) {
+		waitTime -= elapsed;
+		if (waitTime <= 0) {
+			waitTime = 0;
+			resume();
+		}
+	} else if (numFrames > 0) {
+		numFrames -= 1;
+		numFrames = 0;
+		resume();
+	}
+	return isDead();
+}
+
+}
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
new file mode 100644
index 00000000000..f80f77e1ac4
--- /dev/null
+++ b/engines/twp/thread.h
@@ -0,0 +1,56 @@
+/* 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 TWP_THREAD_H
+#define TWP_THREAD_H
+
+#include "common/array.h"
+#include "common/str.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+class Thread {
+public:
+	Thread();
+	~Thread();
+
+	bool call();
+	bool update(float elapsed);
+	void suspend();
+	void resume();
+	bool isDead();
+	bool isSuspended();
+
+public:
+	uint64 id;
+	Common::String name;
+    bool global;
+	HSQOBJECT obj, threadObj, envObj, closureObj;
+	Common::Array<HSQOBJECT> args;
+	bool paused;
+	float waitTime;
+	int numFrames;
+	bool stopRequest;
+};
+}
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d3e7b3efe32..27cb2c11fce 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,10 +19,15 @@
  *
  */
 
-#include <vector>
-#include <vector>
-#include <filesystem>
-#include <fstream>
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/stream.h"
+#include "common/file.h"
+#include "common/config-manager.h"
+#include "common/events.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+#include "graphics/opengl/system_headers.h"
 #include "twp/twp.h"
 #include "twp/detection.h"
 #include "twp/console.h"
@@ -31,33 +36,14 @@
 #include "twp/gfx.h"
 #include "twp/lighting.h"
 #include "twp/font.h"
-#include "common/scummsys.h"
-#include "common/config-manager.h"
-#include "common/debug-channels.h"
-#include "common/events.h"
-#include "common/system.h"
-#include "common/file.h"
-#include "image/png.h"
-#include "engines/util.h"
-#include "graphics/palette.h"
-#include "graphics/opengl/system_headers.h"
+#include "twp/thread.h"
 
 namespace Twp {
 
-TwpEngine *g_engine;
+#define SCREEN_WIDTH	1280
+#define SCREEN_HEIGHT	720
 
-static Math::Vector2d getScreenSize(const Room &room) {
-	switch (room.height) {
-	case 128:
-		return {320, 180};
-	case 172:
-		return {428, 240};
-	case 256:
-		return {640, 360};
-	default:
-		return {room.roomSize.getX(), (float)room.height};
-	}
-}
+TwpEngine *g_engine;
 
 static bool cmpLayer(const Layer &l1, const Layer &l2) {
 	return l1.zsort > l2.zsort;
@@ -83,10 +69,8 @@ Common::String TwpEngine::getGameId() const {
 }
 
 Common::Error TwpEngine::run() {
-	const int screenWidth = 1280;
-	const int screenHeight = 720;
-	initGraphics3d(screenWidth, screenHeight);
-	_screen = new Graphics::Screen(screenWidth, screenHeight);
+	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
+	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
 
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
@@ -96,7 +80,7 @@ Common::Error TwpEngine::run() {
 	Common::File f;
 	f.open("ThimbleweedPark.ggpack1");
 
-	pack.open(&f, key);
+	_pack.open(&f, key);
 
 	const SQChar *code = R"(
 		function
@@ -127,13 +111,12 @@ Common::Error TwpEngine::run() {
 	})";
 
 	GGPackEntryReader r;
-	r.open(g_engine->pack, "MainStreet.wimpy");
+	r.open(g_engine->_pack, "MainStreet.wimpy");
 
 	Room room;
 	room.load(r);
 
-	Vm v;
-	v.exec(code);
+	_vm.exec(code);
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -144,8 +127,7 @@ Common::Error TwpEngine::run() {
 		(void)loadGameState(saveSlot);
 
 	Math::Vector2d pos;
-	Gfx gfx;
-	gfx.init();
+	_gfx.init();
 
 	Lighting lighting;
 	// Simple event handling loop
@@ -179,26 +161,26 @@ Common::Error TwpEngine::run() {
 		}
 
 		// update threads
-		for (int i = 0; i < threads.size(); i++) {
-			Thread *thread = threads[i];
+		for (int i = 0; i < _threads.size(); i++) {
+			Thread *thread = _threads[i];
 			if (thread->update(deltaTimeMs)) {
 				// TODO: delete it
 			}
 		}
 
 		// update screen
-		Math::Vector2d screenSize = getScreenSize(room);
-		gfx.camera(screenSize);
-		gfx.clear(Color(0, 0, 0));
-		gfx.use(&lighting);
+		Math::Vector2d screenSize = room.getScreenSize();
+		_gfx.camera(screenSize);
+		_gfx.clear(Color(0, 0, 0));
+		_gfx.use(&lighting);
 
 		// draw room
-		SpriteSheet *ss = resManager.spriteSheet(room.sheet);
-		Texture *texture = resManager.texture(ss->meta.image);
-		Common::sort(room.layers.begin(), room.layers.end(), cmpLayer);
-		for (int i = 0; i < room.layers.size(); i++) {
+		SpriteSheet *ss = _resManager.spriteSheet(room._sheet);
+		Texture *texture = _resManager.texture(ss->meta.image);
+		Common::sort(room._layers.begin(), room._layers.end(), cmpLayer);
+		for (int i = 0; i < room._layers.size(); i++) {
 			float x = 0;
-			const Layer &layer = room.layers[i];
+			const Layer &layer = room._layers[i];
 			for (int j = 0; j < layer.names.size(); j++) {
 				const Common::String &name = layer.names[j];
 				const SpriteSheetFrame &frame = ss->frameTable[name];
@@ -207,19 +189,19 @@ Common::Error TwpEngine::run() {
 				Math::Vector3d t2 = Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f);
 				m.translate(t1+t2);
 				lighting.setSpriteSheetFrame(frame, *texture);
-				gfx.drawSprite(frame.frame, *texture, Color(), m);
+				_gfx.drawSprite(frame.frame, *texture, Color(), m);
 				x += frame.frame.width();
 			}
 		}
 
-		// draw entities
-		gfx.use(NULL);
-		for (int i = 0; i < entities.size(); i++) {
-			Entity &ett = entities[i];
-			Math::Matrix4 m;
-			m.translate(Math::Vector3d(ett.x - pos.getX(), ett.y - pos.getY(), 0));
-			gfx.drawSprite(ett.rect, *ett.texture, Color(), m);
-		}
+		// TODO: entities
+		_gfx.use(NULL);
+		// for (int i = 0; i < objects.size(); i++) {
+		// 	Object &obj = *objects[i];
+		// 	Math::Matrix4 m;
+		// 	m.translate(Math::Vector3d(obj.x - pos.getX(), obj.y - pos.getY(), 0));
+		// 	_gfx.drawSprite(obj.rect, *obj.texture, Color(), m);
+		// }
 		g_system->updateScreen();
 
 		// Delay for a bit. All events loops should have a delay
@@ -241,4 +223,14 @@ Common::Error TwpEngine::syncGame(Common::Serializer &s) {
 	return Common::kNoError;
 }
 
+Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
+	Math::Vector2d screenSize = _room->getScreenSize();
+	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx._cameraPos) / screenSize;
+}
+
+Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
+  Math::Vector2d screenSize = _room->getScreenSize();
+  return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx._cameraPos;
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 727404128df..b880a4ac8f5 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -22,12 +22,9 @@
 #ifndef TWP_H
 #define TWP_H
 
-#include "common/array.h"
-#include "common/scummsys.h"
 #include "common/system.h"
 #include "common/error.h"
 #include "common/fs.h"
-#include "common/hash-str.h"
 #include "common/random.h"
 #include "common/serializer.h"
 #include "common/util.h"
@@ -38,9 +35,12 @@
 #include "twp/vm.h"
 #include "twp/resmanager.h"
 #include "twp/room.h"
+#include "twp/ggpack.h"
+#include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
+class Thread;
 struct TwpGameDescription;
 
 class TwpEngine : public Engine {
@@ -52,11 +52,12 @@ protected:
 	Common::Error run() override;
 public:
 	Graphics::Screen *_screen = nullptr;
-	Common::Array<Entity> entities;
-	Common::Array<Thread*> threads;
-	GGPackDecoder pack;
-	ResManager resManager;
-	Common::Array<Room> rooms;
+	Common::Array<Object*> _objects;
+	Common::Array<Thread*> _threads;
+	GGPackDecoder _pack;
+	ResManager _resManager;
+	Common::Array<Room> _rooms;
+	Room* _room;
 
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
@@ -74,6 +75,8 @@ public:
 	 */
 	Common::RandomSource& getRandomSource() { return _randomSource; }
 
+	HSQUIRRELVM getVm() { return _vm.get(); }
+
 	bool hasFeature(EngineFeature f) const override {
 		return
 		    (f == kSupportsLoadingDuringRuntime) ||
@@ -102,6 +105,13 @@ public:
 		Common::Serializer s(stream, nullptr);
 		return syncGame(s);
 	}
+
+	Math::Vector2d roomToScreen(Math::Vector2d pos);
+	Math::Vector2d screenToRoom(Math::Vector2d pos);
+
+private:
+	Gfx _gfx;
+	Vm _vm;
 };
 
 extern TwpEngine *g_engine;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 251142173f7..34adb88a078 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -21,6 +21,9 @@
 
 #include "twp/twp.h"
 #include "twp/vm.h"
+#include "twp/sqgame.h"
+#include "twp/squtil.h"
+#include "twp/thread.h"
 #include "common/array.h"
 #include "common/algorithm.h"
 #include "common/debug.h"
@@ -43,276 +46,6 @@ namespace Twp {
 
 static HSQUIRRELVM gVm = nullptr;
 
-static SQObjectPtr sqObj(HSQUIRRELVM v, const char *value) {
-	SQObjectPtr string = SQString::Create(_ss(v), value);
-	return string;
-}
-
-static inline SQObject sqObj(HSQUIRRELVM v, int value) {
-	SQObject o;
-	o._type = OT_INTEGER;
-	o._unVal.nInteger = value;
-	return o;
-}
-
-static SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
-	const SQChar *val;
-	SQRESULT result = sq_getstring(v, i, &val);
-	value = val;
-	return result;
-}
-
-static void getArray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr) {
-	sq_pushobject(v, o);
-	sq_pushnull(v);
-	while (SQ_SUCCEEDED(sq_next(v, -2))) {
-		const SQChar *str;
-		sq_getstring(v, -1, &str);
-		arr.push_back(str);
-		sq_pop(v, 2);
-	}
-	sq_pop(v, 1);
-}
-
-static SQRESULT getArray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr) {
-	HSQOBJECT obj;
-	SQRESULT result = sq_getstackobj(v, i, &obj);
-	getArray(v, obj, arr);
-	return result;
-}
-
-static Thread *thread(HSQUIRRELVM v) {
-	return *Common::find_if(g_engine->threads.begin(), g_engine->threads.end(), [&](Thread *t) {
-		return t->threadObj._unVal.pThread == v;
-	});
-}
-
-template<typename F>
-static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
-	Thread *t = thread(v);
-	if (!t)
-		return sq_throwerror(v, "failed to get thread");
-	t->suspend();
-	func(*t);
-	return -666;
-}
-
-// When called in a function started with startthread, execution is suspended for time seconds.
-// It is an error to call breaktime in a function that was not started with startthread.
-//
-// . code-block:: Squirrel
-// for (local x = 1; x < 4; x += 1) {
-//   playSound(soundPhoneRinging)
-//   breaktime(5.0)
-// }
-static SQInteger breaktime(HSQUIRRELVM v) {
-	SQFloat time;
-	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
-		return sq_throwerror(v, "failed to get time");
-	if (time == 0.f)
-		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
-	else
-		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
-}
-
-// Returns a random number from from to to inclusively.
-// The number is a pseudo-random number and the game will produce the same sequence of numbers unless primed using randomSeed(seed).
-//
-// .. code-block:: Squirrel
-// wait_time = random(0.5, 2.0)
-static SQInteger sq_random(HSQUIRRELVM v) {
-	if (sq_gettype(v, 2) == OT_FLOAT || sq_gettype(v, 3) == OT_FLOAT) {
-		SQFloat min, max;
-		sq_getfloat(v, 2, &min);
-		sq_getfloat(v, 3, &max);
-		if (min > max)
-			SWAP(min, max);
-		float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
-		SQFloat value = min + scale * (max - min);
-		sq_pushfloat(v, value);
-	} else {
-		SQInteger min, max;
-		sq_getinteger(v, 2, &min);
-		sq_getinteger(v, 3, &max);
-		if (min > max)
-			SWAP(min, max);
-		SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max);
-		sq_pushinteger(v, value);
-	}
-	return 1;
-}
-
-// Creates a new, room local object using sheet as the sprite sheet and image as the image name.
-// This object is deleted when the room exits.
-// If sheet parameter not provided, use room's sprite sheet instead.
-// If image is an array, then use that as a sequence of frames for animation.
-// Objects created at runtime can be passed to all the object commands.
-// They do not have verbs or local variables by default, but these can be added when the object is created so it can be used in the construction of sentences.
-static SQInteger createObject(HSQUIRRELVM v) {
-	SQInteger numArgs = sq_gettop(v);
-	Common::String sheet;
-	Common::Array<Common::String> frames;
-	SQInteger framesIndex = 2;
-
-	// get sheet parameter if any
-	if (numArgs == 3) {
-		if (SQ_FAILED(get(v, 2, sheet)))
-			return sq_throwerror(v, "failed to get sheet");
-		framesIndex = 3;
-	}
-
-	// get frames parameter if any
-	if (numArgs >= 2) {
-		switch (sq_gettype(v, framesIndex)) {
-		case OT_STRING: {
-			Common::String frame;
-			get(v, framesIndex, frame);
-			frames.push_back(frame);
-		} break;
-		case OT_ARRAY:
-			getArray(v, framesIndex, frames);
-			break;
-		default:
-			return sq_throwerror(v, "Invalid parameter 2: expecting a string or an array");
-		}
-	}
-
-	debug("Create object: %s, %u", sheet.c_str(), frames.size());
-
-	// load sheet json
-	SpriteSheet* spritesheet = g_engine->resManager.spriteSheet(sheet);
-
-	// TODO: create an entity
-	Entity e;
-	e.texture = g_engine->resManager.texture(sheet + ".png");
-	e.rect = spritesheet->frameTable[frames[0]].frame;
-
-	static int gId = 3000;
-	sq_newtable(v);
-	sq_pushstring(v, _SC("_id"), -1);
-	sq_pushinteger(v, gId++);
-	sq_newslot(v, -3, SQFalse);
-	sq_getstackobj(v, -1, &e.obj);
-	sq_addref(gVm, &e.obj);
-	sq_pop(v, 1);
-
-	g_engine->entities.push_back(e);
-
-	sq_pushobject(v, e.obj);
-
-	return 1;
-}
-
-static SQInteger _startthread(HSQUIRRELVM v, bool global) {
-	SQInteger size = sq_gettop(v);
-
-	Thread *t = new Thread();
-	t->global = global;
-
-	static uint64 gThreadId = 300000;
-	sq_newtable(v);
-	sq_pushstring(v, _SC("_id"), -1);
-	sq_pushinteger(v, gThreadId++);
-	sq_newslot(v, -3, SQFalse);
-	sq_getstackobj(v, -1, &t->obj);
-	sq_addref(gVm, &t->obj);
-	sq_pop(v, 1);
-
-	sq_resetobject(&t->envObj);
-	if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj)))
-		return sq_throwerror(v, "Couldn't get environment from stack");
-	sq_addref(gVm, &t->envObj);
-
-	// create thread and store it on the stack
-	sq_newthread(gVm, 1024);
-	sq_resetobject(&t->threadObj);
-	if (SQ_FAILED(sq_getstackobj(gVm, -1, &t->threadObj)))
-		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(gVm, &t->threadObj);
-
-	for (int i = 0; i < size - 2; i++) {
-		HSQOBJECT arg;
-		sq_resetobject(&arg);
-		if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
-			return sq_throwerror(v, "Couldn't get coroutine args from stack");
-		t->args.push_back(arg);
-		sq_addref(gVm, &arg);
-	}
-
-	// get the closure
-	sq_resetobject(&t->closureObj);
-	if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj)))
-		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(gVm, &t->closureObj);
-
-	const SQChar *name = nullptr;
-	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
-		sq_getstring(v, -1, &name);
-
-	t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line);
-	sq_pop(gVm, 1);
-	if (name)
-		sq_pop(v, 1); // pop name
-	sq_pop(v, 1);     // pop closure
-
-	g_engine->threads.push_back(t);
-
-	debug("create thread %s", t->name.c_str());
-
-	// call the closure in the thread
-	if (!t->call())
-		return sq_throwerror(v, "call failed");
-
-	sq_pushobject(v, t->obj);
-	return 1;
-}
-
-static SQInteger startthread(HSQUIRRELVM v) {
-	return _startthread(v, false);
-}
-
-static SQInteger startglobalthread(HSQUIRRELVM v) {
-	return _startthread(v, true);
-}
-
-static SQInteger objectAt(HSQUIRRELVM v) {
-	HSQOBJECT o;
-	sq_getstackobj(v, 2, &o);
-
-	SQObjectPtr id;
-	_table(o)->Get(sqObj(v, "_id"), id);
-
-	Entity *ett = Common::find_if(g_engine->entities.begin(), g_engine->entities.end(), [&](Entity &e) {
-		SQObjectPtr id2;
-		_table(e.obj)->Get(sqObj(v, "_id"), id2);
-		return _integer(id) == _integer(id2);
-	});
-
-	if (!ett)
-		return sq_throwerror(v, "failed to get object");
-
-	SQInteger x, y;
-	if (SQ_FAILED(sq_getinteger(v, 3, &x)))
-		return sq_throwerror(v, "failed to get x");
-	if (SQ_FAILED(sq_getinteger(v, 4, &y)))
-		return sq_throwerror(v, "failed to get y");
-	ett->x = x;
-	ett->y = y;
-	debug("Object at: %lld, %lld", x, y);
-
-	return 0;
-}
-
-static void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck, const SQChar *typemask) {
-	sq_pushroottable(v);
-	sq_pushstring(v, functionName, -1);
-	sq_newclosure(v, f, 0); // create a new function
-	sq_setparamscheck(v, nparamscheck, typemask);
-	sq_setnativeclosurename(v, -1, functionName);
-	sq_newslot(v, -3, SQFalse);
-	sq_pop(v, 1); // pops the root table
-}
-
 static void sqExec(HSQUIRRELVM v, const char *code) {
 	SQInteger top = sq_gettop(v);
 	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
@@ -374,20 +107,17 @@ Vm::Vm() {
 	sqstd_register_iolib(v);
 	sq_pop(v, 1);
 
-	regFunc(v, sq_random, _SC("random"), 0, nullptr);
-	regFunc(v, createObject, _SC("createObject"), 0, nullptr);
-	regFunc(v, startthread, _SC("startthread"), 0, nullptr);
-	regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr);
-	regFunc(v, objectAt, _SC("objectAt"), 0, nullptr);
-	regFunc(v, breaktime, _SC("breaktime"), 0, nullptr);
+	sqgame_register_syslib(v);
+	sqgame_register_genlib(v);
+	sqgame_register_objlib(v);
 
-	SQObject platform = sqObj(v, 666);
-	_table(v->_roottable)->NewSlot(sqObj(v, _SC("PLATFORM")), SQObjectPtr(platform));
+	SQObject platform = sqobj(v, 666);
+	_table(v->_roottable)->NewSlot(sqobj(v, _SC("PLATFORM")), SQObjectPtr(platform));
 }
 
 Vm::~Vm() {
-	for (int i = 0; i < g_engine->threads.size(); i++) {
-		delete g_engine->threads[i];
+	for (int i = 0; i < g_engine->_threads.size(); i++) {
+		delete g_engine->_threads[i];
 	}
 	sq_close(v);
 }
@@ -395,72 +125,4 @@ Vm::~Vm() {
 void Vm::exec(const SQChar *code) {
 	sqExec(v, code);
 }
-
-Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
-}
-
-Thread::~Thread() {
-	for (int i = 0; i < args.size(); i++) {
-		sq_release(gVm, &args[i]);
-	}
-	sq_release(gVm, &threadObj);
-	sq_release(gVm, &envObj);
-	sq_release(gVm, &closureObj);
-}
-
-bool Thread::call() {
-	HSQUIRRELVM v = threadObj._unVal.pThread;
-	// call the closure in the thread
-	SQInteger top = sq_gettop(v);
-	sq_pushobject(v, closureObj);
-	sq_pushobject(v, envObj);
-	for (int i = 0; i < args.size(); i++) {
-		sq_pushobject(v, args[i]);
-	}
-	if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) {
-		sq_settop(v, top);
-		return false;
-	}
-	return true;
-}
-
-bool Thread::isDead() {
-	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
-	return stopRequest || state == 0;
-}
-
-bool Thread::isSuspended() {
-	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
-	return state != 1;
-}
-
-void Thread::suspend() {
-	// TODO: pauseable
-	if (!isSuspended()) {
-		sq_suspendvm(threadObj._unVal.pThread);
-	}
-}
-
-void Thread::resume() {
-	if (!isDead() && isSuspended()) {
-		sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
-	}
-}
-
-bool Thread::update(float elapsed) {
-	if (paused) {
-	} else if (waitTime > 0) {
-		waitTime -= elapsed;
-		if (waitTime <= 0) {
-			waitTime = 0;
-			resume();
-		}
-	} else if (numFrames > 0) {
-		numFrames -= 1;
-		numFrames = 0;
-		resume();
-	}
-	return isDead();
-}
-
 } // namespace Twp
diff --git a/engines/twp/vm.h b/engines/twp/vm.h
index 3e4ed91de57..466430a3fa9 100644
--- a/engines/twp/vm.h
+++ b/engines/twp/vm.h
@@ -22,46 +22,9 @@
 #ifndef TWPVM_H
 #define TWPVM_H
 
-#include "common/array.h"
-#include "common/rect.h"
-#include "graphics/surface.h"
 #include "twp/squirrel/squirrel.h"
-#include "twp/ggpack.h"
-#include "twp/gfx.h"
 
 namespace Twp {
-class Entity {
-public:
-	HSQOBJECT obj;
-	Texture* texture;
-	Common::Rect rect;
-	int x;
-	int y;
-};
-
-class Thread {
-public:
-	Thread();
-	~Thread();
-
-	bool call();
-	bool update(float elapsed);
-	void suspend();
-	void resume();
-	bool isDead();
-	bool isSuspended();
-
-public:
-	uint64 id;
-	Common::String name;
-    bool global;
-	HSQOBJECT obj, threadObj, envObj, closureObj;
-	Common::Array<HSQOBJECT> args;
-	bool paused;
-	float waitTime;
-	int numFrames;
-	bool stopRequest;
-};
 
 class Vm {
 public:
@@ -70,6 +33,8 @@ public:
 
 	void exec(const SQChar *code);
 
+	HSQUIRRELVM get() { return v; };
+
 private:
 	HSQUIRRELVM v;
 };


Commit: 06de3f2eef3de31b8608bb311d237585475a9c64
    https://github.com/scummvm/scummvm/commit/06de3f2eef3de31b8608bb311d237585475a9c64
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add object function stubs

Changed paths:
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/squtil.cpp


diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 3b4bb892b79..7e3cadef8e6 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -75,14 +75,380 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	}
 
 	debug("Create object: %s, %u", sheet.c_str(), frames.size());
-
 	Object *obj = g_engine->_room->createObject(sheet, frames);
-
 	sq_pushobject(v, obj->table);
 
 	return 1;
 }
 
+static SQInteger createTextObject(HSQUIRRELVM v) {
+	// Creates a text object of the given size.
+	// TextObjects can be passed to all the object commands, but like objects created with createObject they don't have verbs or local variables by default.
+	// If alignment specified, it should take the form of: verticalAlign| horizonalAlign [| horiztonalWidth ].
+	// Valid values for verticalAlign are ALIGN_TOP|ALIGN_BOTTOM. Valid values for horizonalAlign are ALIGN_LEFT|ALIGN_CENTER|ALIGN_RIGHT.
+	// If the optional horiztonalWidth parameter is present, it will wrap the text to that width.
+	const SQChar *fontName;
+	if (SQ_FAILED(get(v, 2, fontName)))
+		return sq_throwerror(v, "failed to get fontName");
+	const SQChar *text;
+	if (SQ_FAILED(get(v, 3, text)))
+		return sq_throwerror(v, "failed to get text");
+	TextHAlignment thAlign = thCenter;
+	TextVAlignment tvAlign = tvCenter;
+	float maxWidth = 0.0f;
+	if (sq_gettop(v) == 4) {
+		int align;
+		if (SQ_FAILED(get(v, 4, align)))
+			return sq_throwerror(v, "failed to get align");
+		uint64 hAlign = align & 0x0000000070000000;
+		uint64 vAlign = align & 0xFFFFFFFFA1000000;
+		maxWidth = (align & 0x00000000000FFFFF);
+		switch (hAlign) {
+		case 0x0000000010000000:
+			thAlign = thLeft;
+		case 0x0000000020000000:
+			thAlign = thCenter;
+		case 0x0000000040000000:
+			thAlign = thRight;
+		default:
+			return sq_throwerror(v, "failed to get halign");
+		}
+		switch (vAlign) {
+		case 0xFFFFFFFF80000000:
+			tvAlign = tvTop;
+		case 0x0000000001000000:
+			tvAlign = tvBottom;
+		default:
+			tvAlign = tvTop;
+		}
+	}
+	debug("Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
+	Object *obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
+	push(v, obj->table);
+	return 1;
+}
+
+// Deletes object permanently from the game.
+//
+// .. code-block:: Squirrel
+// local drip = createObject("drip")
+// local time = 1.5
+// objectAt(drip, 432, 125)
+// objectOffsetTo(drip, 0, -103, time, SLOW_EASE_IN)
+// breaktime(time)
+// playObjectSound(randomfrom(soundDrip1, soundDrip2, soundDrip3), radioStudioBucket)
+// deleteObject(drip)
+static SQInteger deleteObject(HSQUIRRELVM v) {
+	// TODO: deleteObject
+	warning("deleteObject not implemented");
+	return 0;
+}
+
+// Returns the object that is at the specified coordinates.
+// If there is no object at those coordinates, it returns NULL.
+// Used for determining what the player is clicking on now (e.g. for the phone).
+//
+// .. code-block:: Squirrel
+// local button = findObjectAt(x,y)
+// if (button == null)
+//     return NO
+// if (objectState(button) == OFF) {
+//     if (button == Phone.phoneReceiver) {    ... }
+// }
+static SQInteger findObjectAt(HSQUIRRELVM v) {
+	// TODO: findObjectAt
+	warning("findObjectAt not implemented");
+	return 0;
+}
+
+static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
+	// TODO: isInventoryOnScreen
+	warning("isInventoryOnScreen not implemented");
+	return 0;
+}
+
+static SQInteger isObject(HSQUIRRELVM v) {
+	// TODO: isObject
+	warning("isObject not implemented");
+	return 0;
+}
+
+static SQInteger jiggleInventory(HSQUIRRELVM v) {
+	// TODO: jiggleInventory
+	warning("jiggleInventory not implemented");
+	return 0;
+}
+
+static SQInteger jiggleObject(HSQUIRRELVM v) {
+	// TODO: jiggleObject
+	warning("jiggleObject not implemented");
+	return 0;
+}
+
+static SQInteger loopObjectState(HSQUIRRELVM v) {
+	// TODO: loopObjectState
+	warning("loopObjectState not implemented");
+	return 0;
+}
+
+static SQInteger objectAlpha(HSQUIRRELVM v) {
+	// TODO: objectAlpha
+	warning("objectAlpha not implemented");
+	return 0;
+}
+
+static SQInteger objectAlphaTo(HSQUIRRELVM v) {
+	// TODO: objectAlphaTo
+	warning("objectAlphaTo not implemented");
+	return 0;
+}
+
+static SQInteger objectBumperCycle(HSQUIRRELVM v) {
+	// TODO: objectBumperCycle
+	warning("objectBumperCycle not implemented");
+	return 0;
+}
+
+static SQInteger objectCenter(HSQUIRRELVM v) {
+	// TODO: objectCenter
+	warning("objectCenter not implemented");
+	return 0;
+}
+
+static SQInteger objectColor(HSQUIRRELVM v) {
+	// TODO: objectColor
+	warning("objectColor not implemented");
+	return 0;
+}
+
+static SQInteger objectDependentOn(HSQUIRRELVM v) {
+	// TODO: objectDependentOn
+	warning("objectDependentOn not implemented");
+	return 0;
+}
+
+static SQInteger objectFPS(HSQUIRRELVM v) {
+	// TODO: objectFPS
+	warning("objectFPS not implemented");
+	return 0;
+}
+
+static SQInteger objectHidden(HSQUIRRELVM v) {
+	// TODO: objectHidden
+	warning("objectHidden not implemented");
+	return 0;
+}
+
+static SQInteger objectHotspot(HSQUIRRELVM v) {
+	// TODO: objectHotspot
+	warning("objectHotspot not implemented");
+	return 0;
+}
+
+static SQInteger objectIcon(HSQUIRRELVM v) {
+	// TODO: objectIcon
+	warning("objectIcon not implemented");
+	return 0;
+}
+
+static SQInteger objectLit(HSQUIRRELVM v) {
+	// TODO: objectLit
+	warning("objectLit not implemented");
+	return 0;
+}
+
+static SQInteger objectMoveTo(HSQUIRRELVM v) {
+	// TODO: objectMoveTo
+	warning("objectMoveTo not implemented");
+	return 0;
+}
+
+static SQInteger objectOffset(HSQUIRRELVM v) {
+	// TODO: objectOffset
+	warning("objectOffset not implemented");
+	return 0;
+}
+
+static SQInteger objectOffsetTo(HSQUIRRELVM v) {
+	// TODO: objectOffsetTo
+	warning("objectOffsetTo not implemented");
+	return 0;
+}
+
+static SQInteger objectOwner(HSQUIRRELVM v) {
+	// TODO: objectOwner
+	warning("objectOwner not implemented");
+	return 0;
+}
+
+static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
+	// TODO: objectParallaxLayer
+	warning("objectParallaxLayer not implemented");
+	return 0;
+}
+
+static SQInteger objectParent(HSQUIRRELVM v) {
+	// TODO: objectParent
+	warning("objectParent not implemented");
+	return 0;
+}
+
+static SQInteger objectPosX(HSQUIRRELVM v) {
+	// TODO: objectPosX
+	warning("objectPosX not implemented");
+	return 0;
+}
+
+static SQInteger objectPosY(HSQUIRRELVM v) {
+	// TODO: objectPosY
+	warning("objectPosY not implemented");
+	return 0;
+}
+
+static SQInteger objectRenderOffset(HSQUIRRELVM v) {
+	// TODO: objectRenderOffset
+	warning("objectRenderOffset not implemented");
+	return 0;
+}
+
+static SQInteger objectRoom(HSQUIRRELVM v) {
+	// TODO: objectRoom
+	warning("objectRoom not implemented");
+	return 0;
+}
+
+static SQInteger objectRotate(HSQUIRRELVM v) {
+	// TODO: objectRotate
+	warning("objectRotate not implemented");
+	return 0;
+}
+
+static SQInteger objectRotateTo(HSQUIRRELVM v) {
+	// TODO: objectRotateTo
+	warning("objectRotateTo not implemented");
+	return 0;
+}
+
+static SQInteger objectScale(HSQUIRRELVM v) {
+	// TODO: objectScale
+	warning("objectScale not implemented");
+	return 0;
+}
+
+static SQInteger objectScaleTo(HSQUIRRELVM v) {
+	// TODO: objectScaleTo
+	warning("objectScaleTo not implemented");
+	return 0;
+}
+
+static SQInteger objectScreenSpace(HSQUIRRELVM v) {
+	// TODO: objectScreenSpace
+	warning("objectScreenSpace not implemented");
+	return 0;
+}
+
+static SQInteger objectShader(HSQUIRRELVM v) {
+	// TODO: objectShader
+	warning("objectShader not implemented");
+	return 0;
+}
+
+static SQInteger objectState(HSQUIRRELVM v) {
+	// TODO: objectState
+	warning("objectState not implemented");
+	return 0;
+}
+
+static SQInteger objectTouchable(HSQUIRRELVM v) {
+	// TODO: objectTouchable
+	warning("objectTouchable not implemented");
+	return 0;
+}
+
+static SQInteger objectSort(HSQUIRRELVM v) {
+	// TODO: objectSort
+	warning("objectSort not implemented");
+	return 0;
+}
+
+static SQInteger objectUsePos(HSQUIRRELVM v) {
+	// TODO: objectUsePos
+	warning("objectUsePos not implemented");
+	return 0;
+}
+
+static SQInteger objectUsePosX(HSQUIRRELVM v) {
+	// TODO: objectUsePosX
+	warning("objectUsePosX not implemented");
+	return 0;
+}
+
+static SQInteger objectUsePosY(HSQUIRRELVM v) {
+	// TODO: objectUsePosY
+	warning("objectUsePosY not implemented");
+	return 0;
+}
+
+static SQInteger objectValidUsePos(HSQUIRRELVM v) {
+	// TODO: objectValidUsePos
+	warning("objectValidUsePos not implemented");
+	return 0;
+}
+
+static SQInteger objectValidVerb(HSQUIRRELVM v) {
+	// TODO: objectValidVerb
+	warning("objectValidVerb not implemented");
+	return 0;
+}
+
+static SQInteger pickupObject(HSQUIRRELVM v) {
+	// TODO: pickupObject
+	warning("pickupObject not implemented");
+	return 0;
+}
+
+static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
+	// TODO: pickupReplacementObject
+	warning("pickupReplacementObject not implemented");
+	return 0;
+}
+
+static SQInteger playObjectState(HSQUIRRELVM v) {
+	// TODO: playObjectState
+	warning("playObjectState not implemented");
+	return 0;
+}
+
+static SQInteger popInventory(HSQUIRRELVM v) {
+	// TODO: popInventory
+	warning("popInventory not implemented");
+	return 0;
+}
+
+static SQInteger removeInventory(HSQUIRRELVM v) {
+	// TODO: removeInventory
+	warning("removeInventory not implemented");
+	return 0;
+}
+
+static SQInteger setDefaultObject(HSQUIRRELVM v) {
+	// TODO: setDefaultObject
+	warning("setDefaultObject not implemented");
+	return 0;
+}
+
+static SQInteger shakeObject(HSQUIRRELVM v) {
+	// TODO: shakeObject
+	warning("shakeObject not implemented");
+	return 0;
+}
+
+static SQInteger stopObjectMotors(HSQUIRRELVM v) {
+	// TODO: stopObjectMotors
+	warning("stopObjectMotors not implemented");
+	return 0;
+}
+
 static SQInteger objectAt(HSQUIRRELVM v) {
 	HSQOBJECT o;
 	sq_getstackobj(v, 2, &o);
@@ -114,8 +480,61 @@ static SQInteger objectAt(HSQUIRRELVM v) {
 }
 
 void sqgame_register_objlib(HSQUIRRELVM v) {
-	regFunc(v, createObject, _SC("createObject"), 0, nullptr);
-	regFunc(v, objectAt, _SC("objectAt"), 0, nullptr);
+	regFunc(v, createObject, _SC("createObject"));
+	regFunc(v, createTextObject, _SC("createTextObject"));
+	regFunc(v, deleteObject, _SC("deleteObject"));
+	regFunc(v, findObjectAt, _SC("findObjectAt"));
+	regFunc(v, isInventoryOnScreen, _SC("isInventoryOnScreen"));
+	regFunc(v, isObject, _SC("is_object"));
+	regFunc(v, isObject, _SC("isObject"));
+	regFunc(v, jiggleInventory, _SC("jiggleInventory"));
+	regFunc(v, jiggleObject, _SC("jiggleObject"));
+	regFunc(v, loopObjectState, _SC("loopObjectState"));
+	regFunc(v, objectAlpha, _SC("objectAlpha"));
+	regFunc(v, objectAlphaTo, _SC("objectAlphaTo"));
+	regFunc(v, objectAt, _SC("objectAt"));
+	regFunc(v, objectBumperCycle, _SC("objectBumperCycle"));
+	regFunc(v, objectCenter, _SC("objectCenter"));
+	regFunc(v, objectColor, _SC("objectColor"));
+	regFunc(v, objectDependentOn, _SC("objectDependentOn"));
+	regFunc(v, objectFPS, _SC("objectFPS"));
+	regFunc(v, objectHidden, _SC("objectHidden"));
+	regFunc(v, objectHotspot, _SC("objectHotspot"));
+	regFunc(v, objectIcon, _SC("objectIcon"));
+	regFunc(v, objectLit, _SC("objectLit"));
+	regFunc(v, objectMoveTo, _SC("objectMoveTo"));
+	regFunc(v, objectOwner, _SC("objectOwner"));
+	regFunc(v, objectOffset, _SC("objectOffset"));
+	regFunc(v, objectOffsetTo, _SC("objectOffsetTo"));
+	regFunc(v, objectParallaxLayer, _SC("objectParallaxLayer"));
+	regFunc(v, objectParent, _SC("objectParent"));
+	regFunc(v, objectPosX, _SC("objectPosX"));
+	regFunc(v, objectPosY, _SC("objectPosY"));
+	regFunc(v, objectRenderOffset, _SC("objectRenderOffset"));
+	regFunc(v, objectRoom, _SC("objectRoom"));
+	regFunc(v, objectRotate, _SC("objectRotate"));
+	regFunc(v, objectRotateTo, _SC("objectRotateTo"));
+	regFunc(v, objectScale, _SC("objectScale"));
+	regFunc(v, objectScaleTo, _SC("objectScaleTo"));
+	regFunc(v, objectScreenSpace, _SC("objectScreenSpace"));
+	regFunc(v, objectShader, _SC("objectShader"));
+	regFunc(v, objectSort, _SC("objectSort"));
+	regFunc(v, objectState, _SC("objectState"));
+	regFunc(v, objectTouchable, _SC("objectTouchable"));
+	regFunc(v, objectUsePos, _SC("objectUsePos"));
+	regFunc(v, objectUsePosX, _SC("objectUsePosX"));
+	regFunc(v, objectUsePosY, _SC("objectUsePosY"));
+	regFunc(v, objectValidUsePos, _SC("objectValidUsePos"));
+	regFunc(v, objectValidVerb, _SC("objectValidVerb"));
+	regFunc(v, pickupObject, _SC("pickupObject"));
+	regFunc(v, pickupReplacementObject, _SC("pickupReplacementObject"));
+	regFunc(v, playObjectState, _SC("playObjectState"));
+	regFunc(v, popInventory, _SC("popInventory"));
+	regFunc(v, removeInventory, _SC("removeInventory"));
+	regFunc(v, setDefaultObject, _SC("setDefaultObject"));
+	regFunc(v, shakeObject, _SC("shakeObject"));
+	regFunc(v, stopObjectMotors, _SC("stopObjectMotors"));
+	regFunc(v, objectAt, _SC("objectAt"));
 }
 
 } // namespace Twp
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 1fbc84c1081..c318a7efee3 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -2,6 +2,7 @@
 #include "twp/room.h"
 #include "twp/ggpack.h"
 #include "twp/squtil.h"
+#include "twp/font.h"
 
 namespace Twp {
 
@@ -268,6 +269,8 @@ Math::Vector2d Room::getScreenSize() {
 	}
 }
 
+static int gId = 3000;
+
 Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
 	Object *obj = new Object();
 	obj->temporary = true;
@@ -281,7 +284,6 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	sq_pop(v, 1);
 
 	// assign an id
-	static int gId = 3000;
 	setId(obj->table, gId++);
 	Common::String name = frames.size() > 0 ? frames[0] : "noname";
 	setf(obj->table, "name", name);
@@ -312,8 +314,55 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	return obj;
 }
 
+Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
+	Object *obj = new Object();
+	obj->temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->table);
+	sq_addref(v, &obj->table);
+	sq_pop(v, 1);
+
+	// assign an id
+	setId(obj->table, gId++);
+	debug("Create object with new table: %s #%d}", obj->name.c_str(), obj->getId());
+	obj->name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
+	obj->touchable = false;
+
+	Text txt(fontName, text, hAlign, vAlign, maxWidth);
+
+	// TODO:
+	//   let node = newTextNode(text)
+	//   var v = 0.5f
+	//   case vAlign:
+	//   of tvTop:
+	//     v = 0f
+	//   of tvCenter:
+	//     v = 0.5f
+	//   of tvBottom:
+	//     v = 1f
+	//   case hAlign:
+	//   of thLeft:
+	//     node.setAnchorNorm(vec2(0f, v))
+	//   of thCenter:
+	//     node.setAnchorNorm(vec2(0.5f, v))
+	//   of thRight:
+	//     node.setAnchorNorm(vec2(1f, v))
+	//   obj.node = node
+	//   self.layer(0).objects.add(obj);
+	//   self.layer(0).node.addChild obj.node;
+	//   obj.layer = self.layer(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
 int Object::getId() {
-	SQInteger result=0;
+	SQInteger result = 0;
 	getf(table, "_id", result);
 	return (int)result;
 }
diff --git a/engines/twp/room.h b/engines/twp/room.h
index f6ec9cdc7d2..3f101bf4c3a 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -113,6 +113,7 @@ public:
 	void load(Common::SeekableReadStream &s);
 
 	Object* createObject(const Common::String& sheet, const Common::Array<Common::String>& frames);
+	Object *createTextObject(const Common::String& fontName, const Common::String& text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
 
 	Math::Vector2d getScreenSize();
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 8b7bc1ad24c..fdb36533019 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -100,6 +100,11 @@ SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
 	return result;
 }
 
+template<>
+SQRESULT get(HSQUIRRELVM v, int i, const SQChar* &value) {
+	return sq_getstring(v, i, &value);
+}
+
 template<>
 SQRESULT get(HSQUIRRELVM v, int i, HSQOBJECT &value) {
 	return sq_getstackobj(v, i, &value);


Commit: 36319a142b9fd9ca135863724a0204194f9db155
    https://github.com/scummvm/scummvm/commit/36319a142b9fd9ca135863724a0204194f9db155
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add sys function stubs

Changed paths:
    engines/twp/syslib.cpp


diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 76fd06eb912..df5cc49e238 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -34,6 +34,7 @@
 #include "twp/squirrel/sqstdaux.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
+#include "common/debug.h"
 
 namespace Twp {
 
@@ -43,34 +44,6 @@ static Thread *thread(HSQUIRRELVM v) {
 	});
 }
 
-template<typename F>
-static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
-	Thread *t = thread(v);
-	if (!t)
-		return sq_throwerror(v, "failed to get thread");
-	t->suspend();
-	func(*t);
-	return -666;
-}
-
-// When called in a function started with startthread, execution is suspended for time seconds.
-// It is an error to call breaktime in a function that was not started with startthread.
-//
-// . code-block:: Squirrel
-// for (local x = 1; x < 4; x += 1) {
-//   playSound(soundPhoneRinging)
-//   breaktime(5.0)
-// }
-static SQInteger breaktime(HSQUIRRELVM v) {
-	SQFloat time;
-	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
-		return sq_throwerror(v, "failed to get time");
-	if (time == 0.f)
-		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
-	else
-		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
-}
-
 static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	HSQUIRRELVM vm = g_engine->getVm();
 	SQInteger size = sq_gettop(v);
@@ -136,6 +109,213 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	return 1;
 }
 
+template<typename F>
+static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
+	Thread *t = thread(v);
+	if (!t)
+		return sq_throwerror(v, "failed to get thread");
+	t->suspend();
+	func(*t);
+	return -666;
+}
+
+static SQInteger activeController(HSQUIRRELVM v) {
+	warning("TODO: activeController: not implemented");
+	// TODO: change harcoded mouse
+	sq_pushinteger(v, 1);
+	return 1;
+}
+
+static SQInteger addCallback(HSQUIRRELVM v) {
+	warning("TODO: addCallback: not implemented");
+	return 0;
+}
+
+// Registers a folder that assets can appear in.
+//
+// Only used for development builds where the assets are not bundled up.
+// Use in the Boot.nut process.
+// Not necessary for release.
+static SQInteger addFolder(HSQUIRRELVM v) {
+	return 0;
+}
+
+// When called in a function started with startthread, execution is suspended for count frames.
+// It is an error to call breakhere in a function that was not started with startthread.
+// Particularly useful instead of breaktime if you just want to wait 1 frame, since not all machines run at the same speed.
+// . code-block:: Squirrel
+// while(isSoundPlaying(soundPhoneBusy)) {
+//   breakhere(5)
+//}
+static SQInteger breakhere(HSQUIRRELVM v) {
+	warning("TODO: breakhere: not implemented");
+	return 0;
+}
+
+// When called in a function started with startthread, execution is suspended for time seconds.
+// It is an error to call breaktime in a function that was not started with startthread.
+//
+// . code-block:: Squirrel
+// for (local x = 1; x < 4; x += 1) {
+//   playSound(soundPhoneRinging)
+//   breaktime(5.0)
+// }
+static SQInteger breaktime(HSQUIRRELVM v) {
+	SQFloat time;
+	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
+		return sq_throwerror(v, "failed to get time");
+	if (time == 0.f)
+		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
+	else
+		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
+}
+
+static SQInteger breakwhileanimating(HSQUIRRELVM v) {
+	warning("TODO: breakwhileanimating: not implemented");
+	return 0;
+}
+
+static SQInteger breakwhilecamera(HSQUIRRELVM v) {
+	warning("TODO: breakwhilecamera: not implemented");
+	return 0;
+}
+
+static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
+	warning("TODO: breakwhilecutscene: not implemented");
+	return 0;
+}
+
+static SQInteger breakwhiledialog(HSQUIRRELVM v) {
+	warning("TODO: breakwhiledialog: not implemented");
+	return 0;
+}
+
+static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
+	warning("TODO: breakwhileinputoff: not implemented");
+	return 0;
+}
+static SQInteger breakwhilerunning(HSQUIRRELVM v) {
+	warning("TODO: breakwhilerunning: not implemented");
+	return 0;
+}
+static SQInteger breakwhiletalking(HSQUIRRELVM v) {
+	warning("TODO: breakwhiletalking: not implemented");
+	return 0;
+}
+static SQInteger breakwhilewalking(HSQUIRRELVM v) {
+	warning("TODO: breakwhilewalking: not implemented");
+	return 0;
+}
+static SQInteger breakwhilesound(HSQUIRRELVM v) {
+	warning("TODO: breakwhilesound: not implemented");
+	return 0;
+}
+
+static SQInteger cutscene(HSQUIRRELVM v) {
+	warning("TODO: cutscene: not implemented");
+	return 0;
+}
+
+static SQInteger cutsceneOverride(HSQUIRRELVM v) {
+	warning("TODO: cutsceneOverride: not implemented");
+	return 0;
+}
+
+static SQInteger dumpvar(HSQUIRRELVM v) {
+	warning("TODO: dumpvar: not implemented");
+	return 0;
+}
+
+static SQInteger exCommand(HSQUIRRELVM v) {
+	warning("TODO: exCommand: not implemented");
+	return 0;
+}
+
+static SQInteger gameTime(HSQUIRRELVM v) {
+	warning("TODO: gameTime: not implemented");
+	return 0;
+}
+
+static SQInteger sysInclude(HSQUIRRELVM v) {
+	warning("TODO: sysInclude: not implemented");
+	return 0;
+}
+
+static SQInteger inputController(HSQUIRRELVM v) {
+	warning("TODO: inputController: not implemented");
+	return 0;
+}
+
+static SQInteger inputHUD(HSQUIRRELVM v) {
+	warning("TODO: inputHUD: not implemented");
+	return 0;
+}
+
+static SQInteger inputOff(HSQUIRRELVM v) {
+	warning("TODO: inputOff: not implemented");
+	return 0;
+}
+
+static SQInteger inputOn(HSQUIRRELVM v) {
+	warning("TODO: inputOn: not implemented");
+	return 0;
+}
+
+static SQInteger inputSilentOff(HSQUIRRELVM v) {
+	warning("TODO: inputSilentOff: not implemented");
+	return 0;
+}
+
+static SQInteger sysInputState(HSQUIRRELVM v) {
+	warning("TODO: sysInputState: not implemented");
+	return 0;
+}
+
+static SQInteger inputVerbs(HSQUIRRELVM v) {
+	warning("TODO: inputVerbs: not implemented");
+	return 0;
+}
+
+static SQInteger isInputOn(HSQUIRRELVM v) {
+	warning("TODO: isInputOn: not implemented");
+	return 0;
+}
+
+static SQInteger logEvent(HSQUIRRELVM v) {
+	warning("TODO: logEvent: not implemented");
+	return 0;
+}
+
+static SQInteger logInfo(HSQUIRRELVM v) {
+	warning("TODO: logInfo: not implemented");
+	return 0;
+}
+
+static SQInteger logWarning(HSQUIRRELVM v) {
+	warning("TODO: logWarning: not implemented");
+	return 0;
+}
+
+static SQInteger microTime(HSQUIRRELVM v) {
+	warning("TODO: microTime: not implemented");
+	return 0;
+}
+
+static SQInteger moveCursorTo(HSQUIRRELVM v) {
+	warning("TODO: moveCursorTo: not implemented");
+	return 0;
+}
+
+static SQInteger removeCallback(HSQUIRRELVM v) {
+	warning("TODO: removeCallback: not implemented");
+	return 0;
+}
+
+static SQInteger setAmbientLight(HSQUIRRELVM v) {
+	warning("TODO: setAmbientLight: not implemented");
+	return 0;
+}
+
 static SQInteger startthread(HSQUIRRELVM v) {
 	return _startthread(v, false);
 }
@@ -144,10 +324,65 @@ static SQInteger startglobalthread(HSQUIRRELVM v) {
 	return _startthread(v, true);
 }
 
-void sqgame_register_syslib(HSQUIRRELVM v) {
-	regFunc(v, startthread, _SC("startthread"), 0, nullptr);
-	regFunc(v, startglobalthread, _SC("startglobalthread"), 0, nullptr);
-	regFunc(v, breaktime, _SC("breaktime"), 0, nullptr);
+static SQInteger stopthread(HSQUIRRELVM v) {
+	warning("TODO: stopthread: not implemented");
+	return 0;
+}
+
+static SQInteger threadid(HSQUIRRELVM v) {
+	warning("TODO: threadid: not implemented");
+	return 0;
 }
 
+static SQInteger threadpauseable(HSQUIRRELVM v) {
+	warning("TODO: threadpauseable: not implemented");
+	return 0;
 }
+
+void sqgame_register_syslib(HSQUIRRELVM v) {
+	regFunc(v, startthread, _SC("startthread"));
+	regFunc(v, startglobalthread, _SC("startglobalthread"));
+	regFunc(v, breaktime, _SC("breaktime"));
+	regFunc(v, activeController, _SC("activeController"));
+	regFunc(v, addCallback, _SC("addCallback"));
+	regFunc(v, addFolder, _SC("addFolder"));
+	regFunc(v, breakhere, _SC("breakhere"));
+	regFunc(v, breaktime, _SC("breaktime"));
+	regFunc(v, breakwhileanimating, _SC("breakwhileanimating"));
+	regFunc(v, breakwhilecamera, _SC("breakwhilecamera"));
+	regFunc(v, breakwhilecutscene, _SC("breakwhilecutscene"));
+	regFunc(v, breakwhiledialog, _SC("breakwhiledialog"));
+	regFunc(v, breakwhileinputoff, _SC("breakwhileinputoff"));
+	regFunc(v, breakwhilerunning, _SC("breakwhilerunning"));
+	regFunc(v, breakwhilesound, _SC("breakwhilesound"));
+	regFunc(v, breakwhiletalking, _SC("breakwhiletalking"));
+	regFunc(v, breakwhilewalking, _SC("breakwhilewalking"));
+	regFunc(v, cutscene, _SC("cutscene"));
+	regFunc(v, cutsceneOverride, _SC("cutsceneOverride"));
+	regFunc(v, dumpvar, _SC("dumpvar"));
+	regFunc(v, exCommand, _SC("exCommand"));
+	regFunc(v, gameTime, _SC("gameTime"));
+	regFunc(v, sysInclude, _SC("include"));
+	regFunc(v, inputController, _SC("inputController"));
+	regFunc(v, inputHUD, _SC("inputHUD"));
+	regFunc(v, inputOff, _SC("inputOff"));
+	regFunc(v, inputOn, _SC("inputOn"));
+	regFunc(v, inputSilentOff, _SC("inputSilentOff"));
+	regFunc(v, sysInputState, _SC("inputState"));
+	regFunc(v, inputVerbs, _SC("inputVerbs"));
+	regFunc(v, isInputOn, _SC("isInputOn"));
+	regFunc(v, logEvent, _SC("logEvent"));
+	regFunc(v, logInfo, _SC("logInfo"));
+	regFunc(v, logWarning, _SC("logWarning"));
+	regFunc(v, microTime, _SC("microTime"));
+	regFunc(v, moveCursorTo, _SC("moveCursorTo"));
+	regFunc(v, removeCallback, _SC("removeCallback"));
+	regFunc(v, setAmbientLight, _SC("setAmbientLight"));
+	regFunc(v, startglobalthread, _SC("startglobalthread"));
+	regFunc(v, startthread, _SC("startthread"));
+	regFunc(v, stopthread, _SC("stopthread"));
+	regFunc(v, threadid, _SC("threadid"));
+	regFunc(v, threadpauseable, _SC("threadpauseable"));
+}
+
+} // namespace Twp


Commit: 2fec67e5ec8d0bba9abaa21f5ff494bf210608f6
    https://github.com/scummvm/scummvm/commit/2fec67e5ec8d0bba9abaa21f5ff494bf210608f6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add scenegraph

Changed paths:
  A engines/twp/ids.h
  A engines/twp/rectf.cpp
  A engines/twp/rectf.h
  A engines/twp/scenegraph.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 8e2809a65ba..5104b6aad3a 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -340,6 +340,10 @@ void Gfx::camera(Math::Vector2d size) {
 	_mvp = ortho(0.f, size.getX(), 0.f, size.getY(), -1.f, 1.f);
 }
 
+Math::Vector2d Gfx::camera() const {
+	return _cameraSize;
+}
+
 void Gfx::use(Shader *shader) {
 	_shader = shader ? shader : &_defaultShader;
 }
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index db746f6e982..85312b88480 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -121,6 +121,9 @@ public:
 	void init();
 
 	void camera(Math::Vector2d size);
+	Math::Vector2d camera() const;
+	Math::Vector2d cameraPos() const { return _cameraPos; }
+
 	void use(Shader* shader);
 
 	void clear(Color color);
@@ -135,14 +138,12 @@ private:
 	Math::Matrix4 getFinalTransform(Math::Matrix4 trsf);
 	void noTexture();
 
-public:
-	Math::Vector2d _cameraPos;
-
 private:
 	uint32 _vbo, _ebo;
 	Shader _defaultShader;
 	Shader* _shader;
 	Math::Matrix4 _mvp;
+	Math::Vector2d _cameraPos;
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture* _texture;
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
new file mode 100644
index 00000000000..4b0e8fdfe9b
--- /dev/null
+++ b/engines/twp/ids.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/>.
+ *
+ */
+
+#ifndef TWP_LIGHTING_H
+#define TWP_LIGHTING_H
+
+#define START_ACTORID    1000
+#define END_ACTORID      2000
+#define START_ROOMID     2000
+#define END_ROOMID       3000
+#define START_OBJECTID   3000
+#define END_OBJECTID     100000
+#define START_LIGHTID    100000
+#define END_LIGHTID      200000
+#define START_SOUNDDEFID 200000
+#define END_SOUNDDEFID   250000
+#define START_SOUNDID    250000
+#define END_SOUNDID      300000
+#define START_THREADID   300000
+#define END_THREADID     8000000
+#define START_CALLBACKID 8000000
+#define END_CALLBACKID   10000000
+
+namespace Twp {
+
+static inline bool isBetween(int id, int startId, int endId) {
+  return id >= startId && id < endId;
+}
+
+bool isThread(int id) {
+  return isBetween(id, START_THREADID, END_THREADID);
+}
+
+bool isRoom(int id) {
+  return isBetween(id, START_ROOMID, END_THREADID);
+}
+
+bool isActor(int id) {
+  return isBetween(id, START_ACTORID, END_ACTORID);
+}
+
+bool isObject(int id) {
+  return isBetween(id, START_OBJECTID, END_OBJECTID);
+}
+
+bool isSound(int id) {
+  return isBetween(id, START_SOUNDID, END_SOUNDID);
+}
+
+bool isLight(int id) {
+  return isBetween(id, START_LIGHTID, END_LIGHTID);
+}
+
+bool isCallback(int id) {
+  return isBetween(id, START_CALLBACKID, END_CALLBACKID);
+}
+
+}
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 7319a99ded8..89d982c41ff 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -40,6 +40,8 @@ MODULE_OBJS = \
 	genlib.o \
 	squtil.o \
 	thread.o \
+	rectf.o \
+	scenegraph.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 7e3cadef8e6..0e7a81dce2c 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -76,7 +76,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	debug("Create object: %s, %u", sheet.c_str(), frames.size());
 	Object *obj = g_engine->_room->createObject(sheet, frames);
-	sq_pushobject(v, obj->table);
+	sq_pushobject(v, obj->_table);
 
 	return 1;
 }
@@ -124,7 +124,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 	}
 	debug("Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
 	Object *obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
-	push(v, obj->table);
+	push(v, obj->_table);
 	return 1;
 }
 
@@ -458,7 +458,7 @@ static SQInteger objectAt(HSQUIRRELVM v) {
 
 	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
 		SQObjectPtr id2;
-		_table(o->table)->Get(sqobj(v, "_id"), id2);
+		_table(o->_table)->Get(sqobj(v, "_id"), id2);
 		return id == _integer(id2);
 	});
 
diff --git a/engines/twp/rectf.cpp b/engines/twp/rectf.cpp
new file mode 100644
index 00000000000..891b604e8f1
--- /dev/null
+++ b/engines/twp/rectf.cpp
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "twp/rectf.h"
+
+namespace Twp {
+
+Rectf::Rectf(float x, float y, float w, float h) {
+	r.x = x;
+	r.y = y;
+	r.w = w;
+	r.h = h;
+}
+
+Rectf Rectf::fromPosAndSize(Math::Vector2d pos, Math::Vector2d size) {
+	return {pos.getX(), pos.getY(), size.getX(), size.getY()};
+}
+
+Rectf Rectf::operator/(Math::Vector2d v) {
+	return Rectf(r.x / v.getX(), r.y / v.getY(), r.w / v.getX(), r.h / v.getY());
+}
+
+} // namespace Twp
diff --git a/engines/twp/rectf.h b/engines/twp/rectf.h
new file mode 100644
index 00000000000..18c34d497be
--- /dev/null
+++ b/engines/twp/rectf.h
@@ -0,0 +1,49 @@
+/* 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 TWP_RECTF_H
+#define TWP_RECTF_H
+
+#include "common/array.h"
+#include "math/vector2d.h"
+
+namespace Twp {
+
+struct Rectf {
+public:
+	Rectf(float x, float y, float w, float h);
+
+	static Rectf fromPosAndSize(Math::Vector2d pos, Math::Vector2d size);
+	Rectf operator/(Math::Vector2d v);
+
+	union {
+		float v[4];
+		struct {
+			float x;
+			float y;
+			float w;
+			float h;
+		} r;
+	};
+};
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index faeee2444ae..2381053d85c 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -87,7 +87,7 @@ void ResManager::loadFont(const Common::String &name) {
 		_fonts[name] = &_fontC64TermSheet;
 	} else {
 		// TODO:
-		assert(false);
+		error("Loading font %s not implemented", name.c_str());
 	}
 }
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index c318a7efee3..5dea40253b2 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -1,8 +1,32 @@
+/* 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 "twp/twp.h"
 #include "twp/room.h"
 #include "twp/ggpack.h"
 #include "twp/squtil.h"
 #include "twp/font.h"
+#include "twp/scenegraph.h"
+#include "twp/ids.h"
+#include "common/algorithm.h"
 
 namespace Twp {
 
@@ -163,10 +187,10 @@ void Room::load(Common::SeekableReadStream &s) {
 	}
 
 	{
-		Layer layer;
-		layer.names.push_back(backNames);
-		layer.zsort = 0;
-		layer.parallax = Math::Vector2d(1, 1);
+		Layer *layer = new Layer();
+		layer->_names.push_back(backNames);
+		layer->_zsort = 0;
+		layer->_parallax = Math::Vector2d(1, 1);
 		_layers.push_back(layer);
 	}
 
@@ -174,18 +198,18 @@ void Room::load(Common::SeekableReadStream &s) {
 	if (jRoom.contains("layers")) {
 		const Common::JSONArray &jLayers = jRoom["layers"]->asArray();
 		for (int i = 0; i < jLayers.size(); i++) {
-			Layer layer;
+			Layer *layer = new Layer();
 			const Common::JSONObject &jLayer = jLayers[i]->asObject();
 			if (jLayer["name"]->isArray()) {
 				const Common::JSONArray &jNames = jLayer["name"]->asArray();
 				for (int j = 0; j < jNames.size(); j++) {
-					layer.names.push_back(jNames[j]->asString());
+					layer->_names.push_back(jNames[j]->asString());
 				}
 			} else if (jLayer["name"]->isString()) {
-				layer.names.push_back(jLayer["name"]->asString());
+				layer->_names.push_back(jLayer["name"]->asString());
 			}
-			layer.parallax = parseParallax(*jLayer["parallax"]);
-			layer.zsort = jLayer["zsort"]->asIntegerNumber();
+			layer->_parallax = parseParallax(*jLayer["parallax"]);
+			layer->_zsort = jLayer["zsort"]->asIntegerNumber();
 			_layers.push_back(layer);
 		}
 	}
@@ -209,30 +233,30 @@ void Room::load(Common::SeekableReadStream &s) {
 		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
 			const Common::JSONObject &jObject = (*it)->asObject();
 			Object obj;
-			obj.state = -1;
-			//   Node objNode(obj.key)
-			// objNode.pos = Math::Vector2d(parseVec2(jObject["pos"]->asString()));
-			//   objNode.zOrder = jObject["zsort"].getInt().int32
-			//   obj.node = objNode
-			//   obj.nodeAnim = newAnim(obj)
-			//   obj.node.addChild obj.nodeAnim
-			obj.key = jObject["name"]->asString();
-			obj.usePos = parseVec2(jObject["usepos"]->asString());
+			obj._state = -1;
+			Node *objNode = new Node(obj._key);
+			objNode->_pos = Math::Vector2d(parseVec2(jObject["pos"]->asString()));
+			objNode->_zOrder = jObject["zsort"]->asIntegerNumber();
+			obj._node = objNode;
+			obj._nodeAnim = new Anim(&obj);
+			obj._node->addChild(obj._nodeAnim);
+			obj._key = jObject["name"]->asString();
+			obj._usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
-				obj.useDir = parseUseDir(jObject["usedir"]->asString());
+				obj._useDir = parseUseDir(jObject["usedir"]->asString());
 			} else {
-				obj.useDir = dNone;
+				obj._useDir = dNone;
 			}
-			obj.hotspot = parseRect(jObject["hotspot"]->asString());
-			obj.objType = toObjectType(jObject);
+			obj._hotspot = parseRect(jObject["hotspot"]->asString());
+			obj._objType = toObjectType(jObject);
 			if (jObject.contains("parent"))
 				jObject["parent"]->asString();
-			obj.room = this;
+			obj._room = this;
 			if (jObject.contains("animations")) {
-				parseObjectAnimations(jObject["animations"]->asArray(), obj.anims);
+				parseObjectAnimations(jObject["animations"]->asArray(), obj._anims);
 			}
-			//   obj.layer = result.layer(0);
-			//   result.layer(0).objects.add(obj);
+			obj._layer = layer(0);
+			layer(0)->_objects.push_back(&obj);
 		}
 	}
 
@@ -256,6 +280,15 @@ void Room::load(Common::SeekableReadStream &s) {
 	delete value;
 }
 
+Layer *Room::layer(int zsort) {
+	for (int i = 0; i < _layers.size(); i++) {
+		Layer *l = _layers[i];
+		if (l->_zsort == zsort)
+			return l;
+	}
+	return NULL;
+}
+
 Math::Vector2d Room::getScreenSize() {
 	switch (_height) {
 	case 128:
@@ -269,45 +302,53 @@ Math::Vector2d Room::getScreenSize() {
 	}
 }
 
-static int gId = 3000;
+static int gObjectId = START_OBJECTID;
+
+Object::Object()
+: _state(-1),
+_talkOffset(0, 90) {
+  _node = new Node("newObj");
+  _nodeAnim = new Anim(this);
+  _node->addChild(_nodeAnim);
+  sq_resetobject(&_table);
+}
 
 Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
 	Object *obj = new Object();
-	obj->temporary = true;
+	obj->_temporary = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
 
 	// create a table for this object
 	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->table);
-	sq_addref(v, &obj->table);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
 	sq_pop(v, 1);
 
 	// assign an id
-	setId(obj->table, gId++);
+	setId(obj->_table, gObjectId++);
 	Common::String name = frames.size() > 0 ? frames[0] : "noname";
-	setf(obj->table, "name", name);
-	obj->key = name;
-	debug("Create object with new table: %s #%d", obj->name.c_str(), obj->getId());
+	setf(obj->_table, "name", name);
+	obj->_key = name;
+	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 
-	obj->room = this;
-	obj->sheet = sheet;
-	obj->touchable = false;
+	obj->_room = this;
+	obj->_sheet = sheet;
+	obj->_touchable = false;
 
 	// create anim if any
 	if (frames.size() > 0) {
 		ObjectAnimation objAnim;
 		objAnim.name = "state0";
 		objAnim.frames.push_back(frames);
-		obj->anims.push_back(objAnim);
+		obj->_anims.push_back(objAnim);
 	}
 
-	// TODO: adds object to the scenegraph
-	// obj->node.zOrder = 1
-	//   layer(0).objects.add(obj)
-	//   layer(0).node.addChild obj.node
-	//   obj->layer = self.layer(0)
-	// TODO: obj->setState(0);
+	obj->_node->_zOrder = 1;
+	layer(0)->_objects.push_back(obj);
+	layer(0)->_node->addChild(obj->_node);
+	obj->_layer = layer(0);
+	obj->setState(0);
 
 	g_engine->_objects.push_back(obj);
 
@@ -316,21 +357,21 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 
 Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
 	Object *obj = new Object();
-	obj->temporary = true;
+	obj->_temporary = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
 
 	// create a table for this object
 	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->table);
-	sq_addref(v, &obj->table);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
 	sq_pop(v, 1);
 
 	// assign an id
-	setId(obj->table, gId++);
-	debug("Create object with new table: %s #%d}", obj->name.c_str(), obj->getId());
-	obj->name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
-	obj->touchable = false;
+	setId(obj->_table, gObjectId++);
+	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
+	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
+	obj->_touchable = false;
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
@@ -363,10 +404,130 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 
 int Object::getId() {
 	SQInteger result = 0;
-	getf(table, "_id", result);
+	getf(_table, "_id", result);
 	return (int)result;
 }
 
+// Changes the `state` of an object, although this can just be a internal state,
+//
+// it is typically used to change the object's image as it moves from it's current state to another.
+// Behind the scenes, states as just simple ints. State0, State1, etc.
+// Symbols like `CLOSED` and `OPEN` and just pre-defined to be 0 or 1.
+// State 0 is assumed to be the natural state of the object, which is why `OPEN` is 1 and `CLOSED` is 0 and not the other way around.
+// This can be a little confusing at first.
+// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
+// `GONE` is a unique in that setting an object to `GONE` both sets its graphical state to 1, and makes it untouchable.
+// Once an object is set to `GONE`, if you want to make it visible and touchable again, you have to set both:
+//
+// .. code-block:: Squirrel
+// objectState(coin, HERE)
+// objectTouchable(coin, YES)
+void Object::setState(int state, bool instant) {
+	play(state, false, instant);
+	_state = state;
+}
+
+void Object::play(int state, bool loop, bool instant) {
+	play(Common::String::format("state%d", state), loop, instant);
+	_state = state;
+}
+
+void Object::play(const Common::String &state, bool loop, bool instant) {
+	if (state == "eyes_right") {
+		showLayer("eyes_front", false);
+		showLayer("eyes_left", false);
+		showLayer("eyes_right", true);
+	} else if (state == "eyes_left") {
+		showLayer("eyes_front", false);
+		showLayer("eyes_left", true);
+		showLayer("eyes_right", false);
+	} else if (state == "eyes_front") {
+		showLayer("eyes_front", true);
+		showLayer("eyes_left", false);
+		showLayer("eyes_right", false);
+	} else {
+		_animName = state;
+		_animLoop = loop;
+		if (!playCore(state, loop, instant))
+			playCore(state + suffix(), loop, instant);
+	}
+}
+
+bool Object::playCore(const Common::String& state, bool loop, bool instant) {
+  for(int i=0;i<_anims.size();i++) {
+    ObjectAnimation& anim = _anims[i];
+    if(anim.name == state) {
+      _animFlags = anim.flags;
+      _nodeAnim->setAnim(&anim, _fps, loop, instant);
+      return true;
+	}
+  }
+
+  // if not found, clear the previous animation
+  if (!isActor(getId())) {
+    _nodeAnim->clearFrames();
+    _nodeAnim->clear();
+  }
+  return false;
+}
+
+void Object::showLayer(const Common::String &layer, bool visible) {
+	Common::String* s = Common::find(_hiddenLayers.begin(), _hiddenLayers.end(), layer);
+	if (visible) {
+		if (s)
+			_hiddenLayers.remove_at(s - &_hiddenLayers[0]);
+	} else {
+		if (!s)
+			_hiddenLayers.push_back(layer);
+	}
+	if (_node != NULL) {
+		for (int i = 0; i < _node->getChildren().size(); i++) {
+			Node *node = _node->getChildren()[i];
+			if (node->getName() == layer) {
+				node->setVisible(visible);
+			}
+		}
+	}
+}
+
+Facing Object::getFacing() const {
+  if (_facingLockValue != 0)
+    return (Facing)_facingLockValue;
+  else if (_facingMap.contains(_facing))
+    return _facingMap[_facing];
+  return _facing;
+}
+
+void Object::trig(const Common::String& name) {
+  // debug fmt"Trigger object #{self.id} ({self.name}) sound '{name}'"
+  int trigNum;
+  sscanf(name.c_str(), "@%d", &trigNum);
+  if (trigNum != 0) {
+    if (_triggers.contains(trigNum)) {
+      _triggers[trigNum]->trig();
+	} else {
+      warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+	}
+  } else {
+	error("todo: trig %s", name.c_str());
+    // TODO: gEventMgr.trig(name.substr(1));
+  }
+}
+
+Common::String Object::suffix() const {
+  switch(getFacing()) {
+  case FACE_BACK:
+    return "_back";
+  case FACE_FRONT:
+    return "_front";
+  case FACE_LEFT:
+    // there is no animation with `left` suffix but use left and flip the sprite
+    return "_right";
+  case FACE_RIGHT:
+    return "_right";
+  }
+}
+
 Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 3f101bf4c3a..0e7622f2b3b 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -26,14 +26,21 @@
 #include "common/rect.h"
 #include "common/stream.h"
 #include "math/vector2d.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/font.h"
 
 namespace Twp {
 
+class Node;
+class Object;
+
 class Layer {
 public:
-	Common::Array<Common::String> names;
-	Math::Vector2d parallax;
-	int zsort;
+	Common::Array<Common::String> _names;
+	Common::Array<Object *> _objects;
+	Math::Vector2d _parallax;
+	int _zsort;
+	Node *_node;
 };
 
 // Represents an area where an actor can or cannot walk
@@ -87,47 +94,93 @@ struct Scaling {
 	Common::String trigger;
 };
 
+class Anim;
 class Room;
+class Node;
+
+enum Facing {
+	FACE_RIGHT = 1,
+	FACE_LEFT = 2,
+	FACE_FRONT = 4,
+	FACE_BACK = 8
+};
+
+class Trigger {
+public:
+	virtual ~Trigger() {}
+	virtual void trig() = 0;
+};
+
 class Object {
 public:
+	Object();
+
 	int getId();
 
+	void setState(int state, bool instant = false);
+	void play(int state, bool loop = false, bool instant = false);
+	// Plays an animation specified by the `state`.
+	void play(const Common::String &state, bool loop = false, bool instant = false);
+	void showLayer(const Common::String &layer, bool visible);
+	Facing getFacing() const;
+	void trig(const Common::String &name);
+
+private:
+	Common::String suffix() const;
+	// Plays an animation specified by the state
+	bool playCore(const Common::String &state, bool loop = false, bool instant = false);
+
 public:
-	HSQOBJECT table;
-	Common::String name;
-	Common::String sheet;
-	Common::String key; // key used to identify this object by script
-	int state;
-	Math::Vector2d usePos;
-	Direction useDir;
-	Common::Rect hotspot;
-	ObjectType objType;
-	Room *room;
-	Common::Array<ObjectAnimation> anims;
-	bool temporary;
-	bool touchable;
+	HSQOBJECT _table;
+	Common::String _name;
+	Common::String _sheet;
+	Common::String _key; // key used to identify this object by script
+	int _state;
+	Math::Vector2d _usePos;
+	Direction _useDir;
+	Common::Rect _hotspot;
+	ObjectType _objType;
+	Room *_room;
+	Common::Array<ObjectAnimation> _anims;
+	bool _temporary;
+	bool _touchable;
+	Node *_node;
+	Anim *_nodeAnim;
+	Layer *_layer;
+	Common::StringArray _hiddenLayers;
+	Common::String _animName;
+	int _animFlags;
+	bool _animLoop;
+	Common::HashMap<Facing, Facing, Common::Hash<int> > _facingMap;
+	Facing _facing;
+	int _facingLockValue;
+	float _fps;
+	Common::HashMap<int, Trigger*> _triggers;
+	Math::Vector2d _talkOffset;
 };
 
 class Room {
 public:
 	void load(Common::SeekableReadStream &s);
 
-	Object* createObject(const Common::String& sheet, const Common::Array<Common::String>& frames);
-	Object *createTextObject(const Common::String& fontName, const Common::String& text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
+	Object *createObject(const Common::String &sheet, const Common::Array<Common::String> &frames);
+	Object *createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
 
 	Math::Vector2d getScreenSize();
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 
+	Layer *layer(int zsort);
+
 public:
 	Common::String _name;              // Name of the room
 	Common::String _sheet;             // Name of the spritesheet to use
 	Math::Vector2d _roomSize;          // Size of the room
 	int _fullscreen;                   // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
 	int _height;                       // Height of the room (what else ?)
-	Common::Array<Layer> _layers;      // Parallax layers of a room
+	Common::Array<Layer *> _layers;    // Parallax layers of a room
 	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
 	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
-    Scaling _scaling;				  // Defines the scaling of the actor in the room
+	Scaling _scaling;                  // Defines the scaling of the actor in the room
 };
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 754aa8a7665..5d765b60e8c 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -20,12 +20,31 @@
  */
 
 #include "math/matrix3.h"
-#include "scenegraph.h"
+#include "math/vector3d.h"
+#include "common/algorithm.h"
+#include "twp/scenegraph.h"
+#include "twp/twp.h"
+#include "twp/gfx.h"
 
 namespace Twp {
 
-Node::Node(const Common::String &name)
-	: _name(name), _color(1.f, 1.f, 1.f, 1.f), _computedColor(1.f, 1.f, 1.f, 1.f), _visible(true), _rotation(0.f), _rotationOffset(0.f) {
+#define DEFAULT_FPS 10.f
+
+static float _getFps(float fps, float animFps) {
+	if (fps != 0.f)
+		return fps;
+	return animFps == 0.f ? DEFAULT_FPS : animFps;
+}
+
+Node::Node(const Common::String &name, bool visible, Math::Vector2d scale, Color color)
+	: _name(name),
+	  _parent(NULL),
+	  _color(color),
+	  _computedColor(color),
+	  _visible(visible),
+	  _rotation(0.f),
+	  _rotationOffset(0.f),
+	  _scale(scale) {
 }
 
 Node::~Node() {}
@@ -46,6 +65,55 @@ void Node::addChild(Node *child) {
 	child->updateAlpha();
 }
 
+const Node *Node::getRoot() const {
+	const Node *result = this;
+	while (result->_parent != NULL) {
+		result = result->_parent;
+	}
+	return result;
+}
+
+int Node::find(Node *other) {
+	for (int i = 0; i < _children.size(); i++) {
+		if (_children[i] == other) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+void Node::removeChild(Node *node) {
+	int i = find(node);
+	if (i != -1) {
+		_children.remove_at(i);
+	}
+}
+
+void Node::clear() {
+	_children.clear();
+}
+
+void Node::remove() {
+	if (_parent != NULL)
+		_parent->removeChild(this);
+}
+
+void Node::setColor(Color c) {
+	_color.rgba.r = c.rgba.r;
+	_color.rgba.g = c.rgba.g;
+	_color.rgba.b = c.rgba.b;
+	_computedColor.rgba.r = c.rgba.r;
+	_computedColor.rgba.g = c.rgba.g;
+	_computedColor.rgba.b = c.rgba.b;
+	updateColor();
+}
+
+void Node::setAlpha(float alpha) {
+	_color.rgba.a = alpha;
+	_computedColor.rgba.a = alpha;
+	updateAlpha();
+}
+
 void Node::updateColor() {
 	Color parentColor = !_parent ? Color(1.f, 1.f, 1.f) : _parent->_computedColor;
 	updateColor(parentColor);
@@ -76,18 +144,30 @@ void Node::updateAlpha(float parentAlpha) {
 	}
 }
 
-static bool cmpNodes()(const Node* x, const Node* y)
-	return x->() < y.getZSort();
+void Node::setAnchor(Math::Vector2d anchor) {
+	if (_anchor != anchor) {
+		_anchorNorm = anchor / _size;
+		_anchor = anchor;
+	}
+}
+
+void Node::setSize(Math::Vector2d size) {
+	if (_size != size) {
+		_size = size;
+		_anchor = size * _anchorNorm;
+	}
+}
+
+static bool cmpNodes(const Node *x, const Node *y) {
+	return x->getZSort() < y->getZSort();
 }
 
 void Node::draw(Math::Matrix4 parent) {
-	// Draws `self` node.
 	if (_visible) {
 		Math::Matrix4 trsf = getTrsf(parent);
 		Math::Matrix4 myTrsf(trsf);
 		myTrsf.translate(Math::Vector3d(-_anchor.getX(), _anchor.getY(), 0.f));
-		//_children.sort(proc(x, y : Node) : int = cmp(y.getZSort, x.getZSort));
-		Common::sort(_children.begin(), _children.end(), );
+		Common::sort(_children.begin(), _children.end(), cmpNodes);
 		drawCore(myTrsf);
 		for (int i = 0; i < _children.size(); i++) {
 			Node *child = _children[i];
@@ -96,24 +176,158 @@ void Node::draw(Math::Matrix4 parent) {
 	}
 }
 
+Math::Vector2d Node::getAbsPos() const {
+	return !_parent ? _pos : _parent->getAbsPos() + _pos;
+}
+
 Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
-  // Gets the full transformation for this node.
-  return parentTrsf * getLocalTrsf();
+	return parentTrsf * getLocalTrsf();
 }
 
 Math::Matrix4 Node::getLocalTrsf() {
-  // Gets the location transformation = translation * rotation * scale.
 	// TODO: scale
 	Math::Vector2d p = _pos + _offset + _shakeOffset;
 	Math::Matrix4 m1;
 	m1.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 	Math::Matrix3 mRot;
-	mRot.buildAroundZ(Math::Angle(-_rotation+_rotationOffset));
+	mRot.buildAroundZ(Math::Angle(-_rotation + _rotationOffset));
 	Math::Matrix4 m2;
 	m2.setRotation(mRot);
-	 Math::Matrix4 m3;
-	 m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
-	return m1*m2*m3;
+	Math::Matrix4 m3;
+	m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
+	return m1 * m2 * m3;
+}
+
+Rectf Node::getRect() const {
+	Math::Vector2d size = _size * _scale;
+	return Rectf::fromPosAndSize(getAbsPos(), Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm * _size);
+}
+
+OverlayNode::OverlayNode()
+	: Node("overlay"),
+	  _ovlColor(0, 0, 0, 0) {
+}
+
+OverlayNode::~OverlayNode() {}
+
+void OverlayNode::drawCore(Math::Matrix4 trsf) {
+	Gfx &gfx = g_engine->getGfx();
+	gfx.drawQuad(gfx.camera(), _ovlColor);
+}
+
+ParallaxNode::ParallaxNode(const Math::Vector2d &parallax, const Common::String &textture, const Common::Array<SpriteSheetFrame> &frames)
+	: Node("parallax"),
+	  _parallax(parallax),
+	  _texture(textture),
+	  _frames(frames) {
+}
+
+ParallaxNode::~ParallaxNode() {}
+
+Math::Matrix4 ParallaxNode::getTrsf(Math::Matrix4 parentTrsf) {
+	Gfx &gfx = g_engine->getGfx();
+	Math::Matrix4 trsf = Node::getTrsf(parentTrsf);
+	Math::Vector2d camPos = gfx.cameraPos();
+	Math::Vector2d p = Math::Vector2d(-camPos.getX() * _parallax.getX(), -camPos.getY() * _parallax.getY());
+	trsf.translate(Math::Vector3d(p.getX(), p.getY(), 0.0f));
+	return trsf;
+}
+
+static Common::Rect div(const Common::Rect &r, const Math::Vector2d &v) {
+	return Common::Rect(r.left / v.getX(), r.top / v.getY(), r.width() / v.getX(), r.height() / v.getY());
+}
+
+void ParallaxNode::drawCore(Math::Matrix4 trsf) {
+	Gfx &gfx = g_engine->getGfx();
+	Texture *texture = g_engine->_resManager.texture(_texture);
+	Math::Matrix4 t = trsf;
+	for (int i = 0; i < _frames.size(); i++) {
+		const SpriteSheetFrame &frame = _frames[i];
+		Math::Matrix4 myTrsf = t;
+		myTrsf.translate(Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
+		gfx.drawSprite(div(frame.frame, Math::Vector2d(texture->width, texture->height)), *texture, getColor(), myTrsf);
+		t = trsf;
+		t.translate(Math::Vector3d(frame.frame.width(), 0.0f, 0.0f));
+	}
+}
+
+Anim::Anim(Object *obj)
+	: Node("anim") {
+	_zOrder = 1000;
+}
+
+void Anim::clearFrames() {
+	_frames.clear();
+}
+
+void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool instant) {
+	_anim = anim;
+	_disabled = false;
+	setName(anim->name);
+	_sheet = anim->sheet;
+	_frames = anim->frames;
+	_frameIndex = instant && _frames.size() > 0 ? _frames.size() - 1 : 0;
+	_frameDuration = 1.0 / _getFps(fps, anim->fps);
+	_loop = loop || anim->loop;
+	_instant = instant;
+
+	clear();
+	for (int i = 0; i < _anim->layers.size(); i++) {
+		const ObjectAnimation &layer = _anim->layers[i];
+		Anim *node = new Anim(_obj);
+		node->setAnim(&layer, fps, loop, instant);
+		addChild(node);
+	}
+}
+
+void Anim::trigSound() {
+  if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
+    const Common::String& trigger = _anim->triggers[_frameIndex];
+    if (trigger.size() > 0) {
+      _obj->trig(trigger);
+	}
+  }
+}
+
+void Anim::update(float elapsed) {
+  if (_anim)
+    setVisible(Common::find(_obj->_hiddenLayers.begin(), _obj->_hiddenLayers.end(), _anim->name)==NULL);
+    if (_instant)
+      disable();
+    else if (_frames.size() != 0) {
+      _elapsed += elapsed;
+      if(_elapsed > _frameDuration) {
+        _elapsed = 0;
+        if (_frameIndex < _frames.size() - 1) {
+          _frameIndex++;
+          trigSound();
+		} else if (_loop) {
+          _frameIndex = 0;
+          trigSound();
+		} else {
+          disable();
+		}
+	  }
+      if(_anim->offsets.size() > 0) {
+        Math::Vector2d off = _frameIndex < _anim->offsets.size()? _anim->offsets[_frameIndex]: Math::Vector2d();
+        if(_obj->getFacing() == FACE_LEFT) {
+          off.setX(-off.getX());
+		}
+        _offset = off;
+	  }
+	} else if (_children.size() != 0) {
+      bool disabled = true;
+	  for (int i = 0; i < _children.size(); i++) {
+		Anim* layer = static_cast<Anim*>(_children[i]);
+        layer->update(elapsed);
+        disabled = disabled && layer->_disabled;
+	  }
+      if(disabled) {
+        disable();
+	  }
+	} else {
+      disable();
+	}
 }
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
new file mode 100644
index 00000000000..1b2953ede8d
--- /dev/null
+++ b/engines/twp/scenegraph.h
@@ -0,0 +1,165 @@
+
+/* 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 TWP_SCENEGRAPH_H
+#define TWP_SCENEGRAPH_H
+
+#include "math/vector2d.h"
+#include "math/matrix4.h"
+#include "common/array.h"
+#include "common/str.h"
+#include "twp/gfx.h"
+#include "twp/rectf.h"
+#include "twp/room.h"
+#include "twp/spritesheet.h"
+
+namespace Twp {
+
+// Represents a node in a scene graph.
+class Node {
+public:
+	Node(const Common::String& name, bool visible = true, Math::Vector2d scale = Math::Vector2d(1, 1), Color color = Color());
+	virtual ~Node();
+
+	void setName(const Common::String& name) { _name = name; }
+	const Common::String& getName() const { return _name; }
+
+	void setVisible(bool visible) { _visible = visible; }
+	bool isVisible() const { return _visible; }
+
+	// Adds new child in current node.
+	//
+	// Arguments:
+	// - `child`: child node to add.
+	void addChild(Node *child);
+	void removeChild(Node* node);
+	void clear();
+	// Removes this node from its parent.
+	void remove();
+	const Common::Array<Node *>& getChildren() const { return _children; }
+
+	Node* getParent() const { return _parent; }
+	const Node* getRoot() const;
+	// Finds a node in its children and returns its position.
+	int find(Node* other);
+
+	// Gets the absolute position for this node.
+	Math::Vector2d getAbsPos() const;
+
+	// Gets the full transformation for this node.
+	virtual Math::Matrix4 getTrsf(Math::Matrix4 parentTrsf);
+
+	void setColor(Color color);
+	Color getColor() const { return _color; }
+	Color getComputedColor() const { return _computedColor; }
+
+	void setAlpha(float alpha);
+	Color getAlpha() const { return _color.rgba.a; }
+
+	virtual int getZSort() const { return _zOrder; }
+	virtual Math::Vector2d getScale() const { return _scale; }
+
+	void setAnchor(Math::Vector2d anchor);
+	void setSize(Math::Vector2d size);
+	virtual Rectf getRect() const;
+
+	void draw(Math::Matrix4 parent = Math::Matrix4());
+
+public:
+	Math::Vector2d _pos;
+	float _zOrder;
+
+protected:
+	virtual void onColorUpdated(Color c) {}
+	virtual void drawCore(Math::Matrix4 trsf) {}
+
+private:
+	// Gets the location transformation = translation * rotation * scale.
+	Math::Matrix4 getLocalTrsf();
+	void updateColor();
+	void updateColor(Color parentColor);
+	void updateAlpha();
+	void updateAlpha(float parentAlpha);
+
+protected:
+	Common::String _name;
+	Node *_parent;
+	Common::Array<Node *> _children;
+	Math::Vector2d _offset, _shakeOffset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
+	Color _color, _computedColor;
+	bool _visible;
+	float _rotation, _rotationOffset;
+};
+
+class OverlayNode final: public Node {
+public:
+	OverlayNode();
+	virtual ~OverlayNode();
+
+protected:
+	void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Color _ovlColor;
+};
+
+class ParallaxNode final: public Node {
+public:
+	ParallaxNode(const Math::Vector2d& parallax, const Common::String& texture, const Common::Array<SpriteSheetFrame>& frames);
+	virtual ~ParallaxNode();
+
+	Math::Matrix4 getTrsf(Math::Matrix4 parentTrsf) override final;
+
+protected:
+	void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Math::Vector2d _parallax;
+	Common::String _texture;
+	Common::Array<SpriteSheetFrame> _frames;
+};
+
+class Anim : public Node {
+public:
+	Anim(Object* obj);
+
+	void clearFrames();
+	void setAnim(const ObjectAnimation* anim, float fps = 0.f, bool loop = false, bool instant = false);
+	void update(float elapsed);
+	void disable() { _disabled = true; }
+	void trigSound();
+
+private:
+	Common::String _sheet;
+    const ObjectAnimation* _anim;
+    bool _disabled;
+    Common::Array<Common::String> _frames;
+    int _frameIndex;
+    float _elapsed;
+    float _frameDuration;
+    bool _loop, _instant;
+    Object* _obj;
+};
+
+} // End of namespace Twp
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 27cb2c11fce..aad3e3dd6e0 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -45,8 +45,8 @@ namespace Twp {
 
 TwpEngine *g_engine;
 
-static bool cmpLayer(const Layer &l1, const Layer &l2) {
-	return l1.zsort > l2.zsort;
+static bool cmpLayer(const Layer *l1, const Layer *l2) {
+	return l1->_zsort > l2->_zsort;
 }
 
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
@@ -82,42 +82,12 @@ Common::Error TwpEngine::run() {
 
 	_pack.open(&f, key);
 
-	const SQChar *code = R"(
-		function
-		bounceImage() {
-		local image = createObject("RaySheet", ["fstand_head1"]);
-		local x = random(100, 1180);
-		local y = random(100, 620);
-
-		do {
-			local steps = random(100, 150);
-			local end_x = random(0, 1180);
-			local end_y = random(0, 620);
-
-			local dx = (end_x - x) / steps;
-			local dy = (end_y - y) / steps;
-
-			for (local i = 0; i < steps; i++) {
-				x += dx;
-				y += dy;
-				objectAt(image, x, y);
-				breaktime(0.01);
-			}
-		} while (1);
-	}
-
-	for (local i = 1; i <= 200; i++) {
-		startthread(bounceImage);
-	})";
-
 	GGPackEntryReader r;
 	r.open(g_engine->_pack, "MainStreet.wimpy");
 
 	Room room;
 	room.load(r);
 
-	_vm.exec(code);
-
 	// Set the engine's debugger console
 	setDebugger(new Console());
 
@@ -180,12 +150,12 @@ Common::Error TwpEngine::run() {
 		Common::sort(room._layers.begin(), room._layers.end(), cmpLayer);
 		for (int i = 0; i < room._layers.size(); i++) {
 			float x = 0;
-			const Layer &layer = room._layers[i];
-			for (int j = 0; j < layer.names.size(); j++) {
-				const Common::String &name = layer.names[j];
+			const Layer* layer = room._layers[i];
+			for (int j = 0; j < layer->_names.size(); j++) {
+				const Common::String &name = layer->_names[j];
 				const SpriteSheetFrame &frame = ss->frameTable[name];
 				Math::Matrix4 m;
-				Math::Vector3d t1 = Math::Vector3d(x - pos.getX() * layer.parallax.getX(), -pos.getY() * layer.parallax.getY(), 0);
+				Math::Vector3d t1 = Math::Vector3d(x - pos.getX() * layer->_parallax.getX(), -pos.getY() * layer->_parallax.getY(), 0);
 				Math::Vector3d t2 = Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f);
 				m.translate(t1+t2);
 				lighting.setSpriteSheetFrame(frame, *texture);
@@ -225,12 +195,12 @@ Common::Error TwpEngine::syncGame(Common::Serializer &s) {
 
 Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
 	Math::Vector2d screenSize = _room->getScreenSize();
-	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx._cameraPos) / screenSize;
+	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx.cameraPos()) / screenSize;
 }
 
 Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
   Math::Vector2d screenSize = _room->getScreenSize();
-  return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx._cameraPos;
+  return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
 }
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index b880a4ac8f5..ce6631b3eef 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -47,9 +47,11 @@ class TwpEngine : public Engine {
 private:
 	const ADGameDescription *_gameDescription;
 	Common::RandomSource _randomSource;
+
 protected:
 	// Engine APIs
 	Common::Error run() override;
+
 public:
 	Graphics::Screen *_screen = nullptr;
 	Common::Array<Object*> _objects;
@@ -57,7 +59,7 @@ public:
 	GGPackDecoder _pack;
 	ResManager _resManager;
 	Common::Array<Room> _rooms;
-	Room* _room;
+	Room* _room = nullptr;
 
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
@@ -76,6 +78,7 @@ public:
 	Common::RandomSource& getRandomSource() { return _randomSource; }
 
 	HSQUIRRELVM getVm() { return _vm.get(); }
+	inline Gfx& getGfx() { return _gfx; }
 
 	bool hasFeature(EngineFeature f) const override {
 		return


Commit: 1d63a8390e104a4f6af38056a744f7975aa322e5
    https://github.com/scummvm/scummvm/commit/1d63a8390e104a4f6af38056a744f7975aa322e5
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Split room and object

Changed paths:
  A engines/twp/ids.cpp
  A engines/twp/object.cpp
  A engines/twp/object.h
    engines/twp/ids.h
    engines/twp/module.mk
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
new file mode 100644
index 00000000000..84fbadea75e
--- /dev/null
+++ b/engines/twp/ids.cpp
@@ -0,0 +1,58 @@
+/* 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 "ids.h"
+
+namespace Twp {
+
+static inline bool isBetween(int id, int startId, int endId) {
+  return id >= startId && id < endId;
+}
+
+bool isThread(int id) {
+  return isBetween(id, START_THREADID, END_THREADID);
+}
+
+bool isRoom(int id) {
+  return isBetween(id, START_ROOMID, END_THREADID);
+}
+
+bool isActor(int id) {
+  return isBetween(id, START_ACTORID, END_ACTORID);
+}
+
+bool isObject(int id) {
+  return isBetween(id, START_OBJECTID, END_OBJECTID);
+}
+
+bool isSound(int id) {
+  return isBetween(id, START_SOUNDID, END_SOUNDID);
+}
+
+bool isLight(int id) {
+  return isBetween(id, START_LIGHTID, END_LIGHTID);
+}
+
+bool isCallback(int id) {
+  return isBetween(id, START_CALLBACKID, END_CALLBACKID);
+}
+
+}
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index 4b0e8fdfe9b..b64ba0c894f 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TWP_LIGHTING_H
-#define TWP_LIGHTING_H
+#ifndef TWP_IDS_H
+#define TWP_IDS_H
 
 #define START_ACTORID    1000
 #define END_ACTORID      2000
@@ -41,37 +41,13 @@
 
 namespace Twp {
 
-static inline bool isBetween(int id, int startId, int endId) {
-  return id >= startId && id < endId;
-}
-
-bool isThread(int id) {
-  return isBetween(id, START_THREADID, END_THREADID);
-}
-
-bool isRoom(int id) {
-  return isBetween(id, START_ROOMID, END_THREADID);
-}
-
-bool isActor(int id) {
-  return isBetween(id, START_ACTORID, END_ACTORID);
-}
-
-bool isObject(int id) {
-  return isBetween(id, START_OBJECTID, END_OBJECTID);
-}
-
-bool isSound(int id) {
-  return isBetween(id, START_SOUNDID, END_SOUNDID);
-}
-
-bool isLight(int id) {
-  return isBetween(id, START_LIGHTID, END_LIGHTID);
-}
-
-bool isCallback(int id) {
-  return isBetween(id, START_CALLBACKID, END_CALLBACKID);
-}
+bool isThread(int id);
+bool isRoom(int id);
+bool isActor(int id);
+bool isObject(int id);
+bool isSound(int id);
+bool isLight(int id);
+bool isCallback(int id);
 
 }
 
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 89d982c41ff..7b2873644e2 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -42,6 +42,8 @@ MODULE_OBJS = \
 	thread.o \
 	rectf.o \
 	scenegraph.o \
+	object.o \
+	ids.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
new file mode 100644
index 00000000000..cf2e5857a7a
--- /dev/null
+++ b/engines/twp/object.cpp
@@ -0,0 +1,256 @@
+/* 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 "twp/twp.h"
+#include "twp/ids.h"
+#include "twp/object.h"
+#include "twp/scenegraph.h"
+#include "twp/squtil.h"
+
+namespace Twp {
+
+static int gObjectId = START_OBJECTID;
+
+Object::Object()
+	: _state(-1),
+	  _talkOffset(0, 90) {
+	_node = new Node("newObj");
+	_nodeAnim = new Anim(this);
+	_node->addChild(_nodeAnim);
+	sq_resetobject(&_table);
+}
+
+Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
+	Object *obj = new Object();
+	obj->_temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
+	sq_pop(v, 1);
+
+	// assign an id
+	setId(obj->_table, gObjectId++);
+	Common::String name = frames.size() > 0 ? frames[0] : "noname";
+	setf(obj->_table, "name", name);
+	obj->_key = name;
+	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
+
+	obj->_room = this;
+	obj->_sheet = sheet;
+	obj->_touchable = false;
+
+	// create anim if any
+	if (frames.size() > 0) {
+		ObjectAnimation objAnim;
+		objAnim.name = "state0";
+		objAnim.frames.push_back(frames);
+		obj->_anims.push_back(objAnim);
+	}
+
+	obj->_node->_zOrder = 1;
+	layer(0)->_objects.push_back(obj);
+	layer(0)->_node->addChild(obj->_node);
+	obj->_layer = layer(0);
+	obj->setState(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
+Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
+	Object *obj = new Object();
+	obj->_temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
+	sq_pop(v, 1);
+
+	// assign an id
+	setId(obj->_table, gObjectId++);
+	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
+	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
+	obj->_touchable = false;
+
+	Text txt(fontName, text, hAlign, vAlign, maxWidth);
+
+	// TODO:
+	//   let node = newTextNode(text)
+	//   var v = 0.5f
+	//   case vAlign:
+	//   of tvTop:
+	//     v = 0f
+	//   of tvCenter:
+	//     v = 0.5f
+	//   of tvBottom:
+	//     v = 1f
+	//   case hAlign:
+	//   of thLeft:
+	//     node.setAnchorNorm(vec2(0f, v))
+	//   of thCenter:
+	//     node.setAnchorNorm(vec2(0.5f, v))
+	//   of thRight:
+	//     node.setAnchorNorm(vec2(1f, v))
+	//   obj.node = node
+	//   self.layer(0).objects.add(obj);
+	//   self.layer(0).node.addChild obj.node;
+	//   obj.layer = self.layer(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
+int Object::getId() {
+	SQInteger result = 0;
+	getf(_table, "_id", result);
+	return (int)result;
+}
+
+// Changes the `state` of an object, although this can just be a internal state,
+//
+// it is typically used to change the object's image as it moves from it's current state to another.
+// Behind the scenes, states as just simple ints. State0, State1, etc.
+// Symbols like `CLOSED` and `OPEN` and just pre-defined to be 0 or 1.
+// State 0 is assumed to be the natural state of the object, which is why `OPEN` is 1 and `CLOSED` is 0 and not the other way around.
+// This can be a little confusing at first.
+// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
+// `GONE` is a unique in that setting an object to `GONE` both sets its graphical state to 1, and makes it untouchable.
+// Once an object is set to `GONE`, if you want to make it visible and touchable again, you have to set both:
+//
+// .. code-block:: Squirrel
+// objectState(coin, HERE)
+// objectTouchable(coin, YES)
+void Object::setState(int state, bool instant) {
+	play(state, false, instant);
+	_state = state;
+}
+
+void Object::play(int state, bool loop, bool instant) {
+	play(Common::String::format("state%d", state), loop, instant);
+	_state = state;
+}
+
+void Object::play(const Common::String &state, bool loop, bool instant) {
+	if (state == "eyes_right") {
+		showLayer("eyes_front", false);
+		showLayer("eyes_left", false);
+		showLayer("eyes_right", true);
+	} else if (state == "eyes_left") {
+		showLayer("eyes_front", false);
+		showLayer("eyes_left", true);
+		showLayer("eyes_right", false);
+	} else if (state == "eyes_front") {
+		showLayer("eyes_front", true);
+		showLayer("eyes_left", false);
+		showLayer("eyes_right", false);
+	} else {
+		_animName = state;
+		_animLoop = loop;
+		if (!playCore(state, loop, instant))
+			playCore(state + suffix(), loop, instant);
+	}
+}
+
+bool Object::playCore(const Common::String &state, bool loop, bool instant) {
+	for (int i = 0; i < _anims.size(); i++) {
+		ObjectAnimation &anim = _anims[i];
+		if (anim.name == state) {
+			_animFlags = anim.flags;
+			_nodeAnim->setAnim(&anim, _fps, loop, instant);
+			return true;
+		}
+	}
+
+	// if not found, clear the previous animation
+	if (!isActor(getId())) {
+		_nodeAnim->clearFrames();
+		_nodeAnim->clear();
+	}
+	return false;
+}
+
+void Object::showLayer(const Common::String &layer, bool visible) {
+	Common::String *s = Common::find(_hiddenLayers.begin(), _hiddenLayers.end(), layer);
+	if (visible) {
+		if (s)
+			_hiddenLayers.remove_at(s - &_hiddenLayers[0]);
+	} else {
+		if (!s)
+			_hiddenLayers.push_back(layer);
+	}
+	if (_node != NULL) {
+		for (int i = 0; i < _node->getChildren().size(); i++) {
+			Node *node = _node->getChildren()[i];
+			if (node->getName() == layer) {
+				node->setVisible(visible);
+			}
+		}
+	}
+}
+
+Facing Object::getFacing() const {
+	if (_facingLockValue != 0)
+		return (Facing)_facingLockValue;
+	else if (_facingMap.contains(_facing))
+		return _facingMap[_facing];
+	return _facing;
+}
+
+void Object::trig(const Common::String &name) {
+	// debug fmt"Trigger object #{self.id} ({self.name}) sound '{name}'"
+	int trigNum;
+	sscanf(name.c_str(), "@%d", &trigNum);
+	if (trigNum != 0) {
+		if (_triggers.contains(trigNum)) {
+			_triggers[trigNum]->trig();
+		} else {
+			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+		}
+	} else {
+		error("todo: trig %s", name.c_str());
+		// TODO: gEventMgr.trig(name.substr(1));
+	}
+}
+
+Common::String Object::suffix() const {
+	switch (getFacing()) {
+	case FACE_BACK:
+		return "_back";
+	case FACE_FRONT:
+		return "_front";
+	case FACE_LEFT:
+		// there is no animation with `left` suffix but use left and flip the sprite
+		return "_right";
+	case FACE_RIGHT:
+		return "_right";
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
new file mode 100644
index 00000000000..da4d2e3894f
--- /dev/null
+++ b/engines/twp/object.h
@@ -0,0 +1,129 @@
+/* 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 TWP_OBJECT_H
+#define TWP_OBJECT_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "math/vector2d.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+enum ObjectType {
+	otNone,
+	otProp,
+	otSpot,
+	otTrigger
+};
+
+enum Direction {
+	dNone = 0,
+	dRight = 1,
+	dLeft = 2,
+	dFront = 4,
+	dBack = 8
+};
+
+enum Facing {
+	FACE_RIGHT = 1,
+	FACE_LEFT = 2,
+	FACE_FRONT = 4,
+	FACE_BACK = 8
+};
+
+struct ObjectAnimation {
+	Common::String name;
+	Common::String sheet;
+	Common::StringArray frames;
+	Common::Array<ObjectAnimation> layers;
+	Common::StringArray triggers;
+	Common::Array<Math::Vector2d> offsets;
+	bool loop;
+	float fps;
+	int flags;
+	int frameIndex;
+};
+
+class Trigger {
+public:
+	virtual ~Trigger() {}
+	virtual void trig() = 0;
+};
+
+class Anim;
+class Room;
+class Node;
+class Layer;
+
+class Object {
+public:
+	Object();
+
+	int getId();
+
+	void setState(int state, bool instant = false);
+	void play(int state, bool loop = false, bool instant = false);
+	// Plays an animation specified by the `state`.
+	void play(const Common::String &state, bool loop = false, bool instant = false);
+	void showLayer(const Common::String &layer, bool visible);
+	Facing getFacing() const;
+	void trig(const Common::String &name);
+
+private:
+	Common::String suffix() const;
+	// Plays an animation specified by the state
+	bool playCore(const Common::String &state, bool loop = false, bool instant = false);
+
+public:
+	HSQOBJECT _table;
+	Common::String _name;
+	Common::String _sheet;
+	Common::String _key; // key used to identify this object by script
+	int _state;
+	Math::Vector2d _usePos;
+	Direction _useDir;
+	Common::Rect _hotspot;
+	ObjectType _objType;
+	Room *_room;
+	Common::Array<ObjectAnimation> _anims;
+	bool _temporary;
+	bool _touchable;
+	Node *_node;
+	Anim *_nodeAnim;
+	Layer *_layer;
+	Common::StringArray _hiddenLayers;
+	Common::String _animName;
+	int _animFlags;
+	bool _animLoop;
+	Common::HashMap<Facing, Facing, Common::Hash<int> > _facingMap;
+	Facing _facing;
+	int _facingLockValue;
+	float _fps;
+	Common::HashMap<int, Trigger *> _triggers;
+	Math::Vector2d _talkOffset;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 0e7a81dce2c..74d0c92897a 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -22,6 +22,7 @@
 #include "twp/twp.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
+#include "twp/object.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 5dea40253b2..5ed02d459de 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -26,6 +26,7 @@
 #include "twp/font.h"
 #include "twp/scenegraph.h"
 #include "twp/ids.h"
+#include "twp/object.h"
 #include "common/algorithm.h"
 
 namespace Twp {
@@ -221,7 +222,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			const Common::JSONObject &jWalkbox = (*it)->asObject();
 			Walkbox walkbox = parseWalkbox(jWalkbox["polygon"]->asString());
 			if (jWalkbox.contains("name") && jWalkbox["name"]->isString()) {
-				walkbox.name = jWalkbox["name"]->asString();
+				walkbox._name = jWalkbox["name"]->asString();
 			}
 			_walkboxes.push_back(walkbox);
 		}
@@ -302,232 +303,6 @@ Math::Vector2d Room::getScreenSize() {
 	}
 }
 
-static int gObjectId = START_OBJECTID;
-
-Object::Object()
-: _state(-1),
-_talkOffset(0, 90) {
-  _node = new Node("newObj");
-  _nodeAnim = new Anim(this);
-  _node->addChild(_nodeAnim);
-  sq_resetobject(&_table);
-}
-
-Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
-	Object *obj = new Object();
-	obj->_temporary = true;
-
-	HSQUIRRELVM v = g_engine->getVm();
-
-	// create a table for this object
-	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->_table);
-	sq_addref(v, &obj->_table);
-	sq_pop(v, 1);
-
-	// assign an id
-	setId(obj->_table, gObjectId++);
-	Common::String name = frames.size() > 0 ? frames[0] : "noname";
-	setf(obj->_table, "name", name);
-	obj->_key = name;
-	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
-
-	obj->_room = this;
-	obj->_sheet = sheet;
-	obj->_touchable = false;
-
-	// create anim if any
-	if (frames.size() > 0) {
-		ObjectAnimation objAnim;
-		objAnim.name = "state0";
-		objAnim.frames.push_back(frames);
-		obj->_anims.push_back(objAnim);
-	}
-
-	obj->_node->_zOrder = 1;
-	layer(0)->_objects.push_back(obj);
-	layer(0)->_node->addChild(obj->_node);
-	obj->_layer = layer(0);
-	obj->setState(0);
-
-	g_engine->_objects.push_back(obj);
-
-	return obj;
-}
-
-Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
-	Object *obj = new Object();
-	obj->_temporary = true;
-
-	HSQUIRRELVM v = g_engine->getVm();
-
-	// create a table for this object
-	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->_table);
-	sq_addref(v, &obj->_table);
-	sq_pop(v, 1);
-
-	// assign an id
-	setId(obj->_table, gObjectId++);
-	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
-	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
-	obj->_touchable = false;
-
-	Text txt(fontName, text, hAlign, vAlign, maxWidth);
-
-	// TODO:
-	//   let node = newTextNode(text)
-	//   var v = 0.5f
-	//   case vAlign:
-	//   of tvTop:
-	//     v = 0f
-	//   of tvCenter:
-	//     v = 0.5f
-	//   of tvBottom:
-	//     v = 1f
-	//   case hAlign:
-	//   of thLeft:
-	//     node.setAnchorNorm(vec2(0f, v))
-	//   of thCenter:
-	//     node.setAnchorNorm(vec2(0.5f, v))
-	//   of thRight:
-	//     node.setAnchorNorm(vec2(1f, v))
-	//   obj.node = node
-	//   self.layer(0).objects.add(obj);
-	//   self.layer(0).node.addChild obj.node;
-	//   obj.layer = self.layer(0);
-
-	g_engine->_objects.push_back(obj);
-
-	return obj;
-}
-
-int Object::getId() {
-	SQInteger result = 0;
-	getf(_table, "_id", result);
-	return (int)result;
-}
-
-// Changes the `state` of an object, although this can just be a internal state,
-//
-// it is typically used to change the object's image as it moves from it's current state to another.
-// Behind the scenes, states as just simple ints. State0, State1, etc.
-// Symbols like `CLOSED` and `OPEN` and just pre-defined to be 0 or 1.
-// State 0 is assumed to be the natural state of the object, which is why `OPEN` is 1 and `CLOSED` is 0 and not the other way around.
-// This can be a little confusing at first.
-// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
-// `GONE` is a unique in that setting an object to `GONE` both sets its graphical state to 1, and makes it untouchable.
-// Once an object is set to `GONE`, if you want to make it visible and touchable again, you have to set both:
-//
-// .. code-block:: Squirrel
-// objectState(coin, HERE)
-// objectTouchable(coin, YES)
-void Object::setState(int state, bool instant) {
-	play(state, false, instant);
-	_state = state;
-}
-
-void Object::play(int state, bool loop, bool instant) {
-	play(Common::String::format("state%d", state), loop, instant);
-	_state = state;
-}
-
-void Object::play(const Common::String &state, bool loop, bool instant) {
-	if (state == "eyes_right") {
-		showLayer("eyes_front", false);
-		showLayer("eyes_left", false);
-		showLayer("eyes_right", true);
-	} else if (state == "eyes_left") {
-		showLayer("eyes_front", false);
-		showLayer("eyes_left", true);
-		showLayer("eyes_right", false);
-	} else if (state == "eyes_front") {
-		showLayer("eyes_front", true);
-		showLayer("eyes_left", false);
-		showLayer("eyes_right", false);
-	} else {
-		_animName = state;
-		_animLoop = loop;
-		if (!playCore(state, loop, instant))
-			playCore(state + suffix(), loop, instant);
-	}
-}
-
-bool Object::playCore(const Common::String& state, bool loop, bool instant) {
-  for(int i=0;i<_anims.size();i++) {
-    ObjectAnimation& anim = _anims[i];
-    if(anim.name == state) {
-      _animFlags = anim.flags;
-      _nodeAnim->setAnim(&anim, _fps, loop, instant);
-      return true;
-	}
-  }
-
-  // if not found, clear the previous animation
-  if (!isActor(getId())) {
-    _nodeAnim->clearFrames();
-    _nodeAnim->clear();
-  }
-  return false;
-}
-
-void Object::showLayer(const Common::String &layer, bool visible) {
-	Common::String* s = Common::find(_hiddenLayers.begin(), _hiddenLayers.end(), layer);
-	if (visible) {
-		if (s)
-			_hiddenLayers.remove_at(s - &_hiddenLayers[0]);
-	} else {
-		if (!s)
-			_hiddenLayers.push_back(layer);
-	}
-	if (_node != NULL) {
-		for (int i = 0; i < _node->getChildren().size(); i++) {
-			Node *node = _node->getChildren()[i];
-			if (node->getName() == layer) {
-				node->setVisible(visible);
-			}
-		}
-	}
-}
-
-Facing Object::getFacing() const {
-  if (_facingLockValue != 0)
-    return (Facing)_facingLockValue;
-  else if (_facingMap.contains(_facing))
-    return _facingMap[_facing];
-  return _facing;
-}
-
-void Object::trig(const Common::String& name) {
-  // debug fmt"Trigger object #{self.id} ({self.name}) sound '{name}'"
-  int trigNum;
-  sscanf(name.c_str(), "@%d", &trigNum);
-  if (trigNum != 0) {
-    if (_triggers.contains(trigNum)) {
-      _triggers[trigNum]->trig();
-	} else {
-      warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
-	}
-  } else {
-	error("todo: trig %s", name.c_str());
-    // TODO: gEventMgr.trig(name.substr(1));
-  }
-}
-
-Common::String Object::suffix() const {
-  switch(getFacing()) {
-  case FACE_BACK:
-    return "_back";
-  case FACE_FRONT:
-    return "_front";
-  case FACE_LEFT:
-    // there is no animation with `left` suffix but use left and flip the sprite
-    return "_right";
-  case FACE_RIGHT:
-    return "_right";
-  }
-}
-
 Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 0e7622f2b3b..334329bfc0a 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -49,41 +49,13 @@ public:
 	Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
 
 public:
-	Common::String name;
+	Common::String _name;
 
 private:
 	Common::Array<Math::Vector2d> _polygon;
 	bool _visible;
 };
 
-enum Direction {
-	dNone = 0,
-	dRight = 1,
-	dLeft = 2,
-	dFront = 4,
-	dBack = 8
-};
-
-enum ObjectType {
-	otNone,
-	otProp,
-	otSpot,
-	otTrigger
-};
-
-struct ObjectAnimation {
-	Common::String name;
-	Common::String sheet;
-	Common::StringArray frames;
-	Common::Array<ObjectAnimation> layers;
-	Common::StringArray triggers;
-	Common::Array<Math::Vector2d> offsets;
-	bool loop;
-	float fps;
-	int flags;
-	int frameIndex;
-};
-
 struct ScalingValue {
 	float scale;
 	int y;
@@ -94,69 +66,23 @@ struct Scaling {
 	Common::String trigger;
 };
 
-class Anim;
-class Room;
-class Node;
-
-enum Facing {
-	FACE_RIGHT = 1,
-	FACE_LEFT = 2,
-	FACE_FRONT = 4,
-	FACE_BACK = 8
+struct Light {
+    Color color;
+    Math::Vector2d pos;
+    float brightness;     // light brightness 1.0f...100.f
+    float coneDirection;  // cone direction 0...360.f
+    float coneAngle;      // cone angle 0...360.f
+    float coneFalloff;    // cone falloff 0.f...1.0f
+    float cutOffRadius;   // cutoff raduus
+    float halfRadius;     // cone half radius 0.0f...1.0f
+    bool on;
+    int id;
 };
 
-class Trigger {
-public:
-	virtual ~Trigger() {}
-	virtual void trig() = 0;
-};
-
-class Object {
-public:
-	Object();
-
-	int getId();
-
-	void setState(int state, bool instant = false);
-	void play(int state, bool loop = false, bool instant = false);
-	// Plays an animation specified by the `state`.
-	void play(const Common::String &state, bool loop = false, bool instant = false);
-	void showLayer(const Common::String &layer, bool visible);
-	Facing getFacing() const;
-	void trig(const Common::String &name);
-
-private:
-	Common::String suffix() const;
-	// Plays an animation specified by the state
-	bool playCore(const Common::String &state, bool loop = false, bool instant = false);
-
-public:
-	HSQOBJECT _table;
-	Common::String _name;
-	Common::String _sheet;
-	Common::String _key; // key used to identify this object by script
-	int _state;
-	Math::Vector2d _usePos;
-	Direction _useDir;
-	Common::Rect _hotspot;
-	ObjectType _objType;
-	Room *_room;
-	Common::Array<ObjectAnimation> _anims;
-	bool _temporary;
-	bool _touchable;
-	Node *_node;
-	Anim *_nodeAnim;
-	Layer *_layer;
-	Common::StringArray _hiddenLayers;
-	Common::String _animName;
-	int _animFlags;
-	bool _animLoop;
-	Common::HashMap<Facing, Facing, Common::Hash<int> > _facingMap;
-	Facing _facing;
-	int _facingLockValue;
-	float _fps;
-	Common::HashMap<int, Trigger*> _triggers;
-	Math::Vector2d _talkOffset;
+struct Lights {
+	int _numLights;			// Number of lights
+	Light _lights[50];
+	Color _ambientLight;	// Ambient light color
 };
 
 class Room {
@@ -172,15 +98,21 @@ public:
 	Layer *layer(int zsort);
 
 public:
-	Common::String _name;              // Name of the room
-	Common::String _sheet;             // Name of the spritesheet to use
-	Math::Vector2d _roomSize;          // Size of the room
-	int _fullscreen;                   // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
-	int _height;                       // Height of the room (what else ?)
-	Common::Array<Layer *> _layers;    // Parallax layers of a room
-	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
-	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
-	Scaling _scaling;                  // Defines the scaling of the actor in the room
+	Common::String _name;               	// Name of the room
+	Common::String _sheet;              	// Name of the spritesheet to use
+	Math::Vector2d _roomSize;           	// Size of the room
+	int _fullscreen;                    	// Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
+	int _height;                        	// Height of the room (what else ?)
+	Common::Array<Layer *> _layers;     	// Parallax layers of a room
+	Common::Array<Walkbox> _walkboxes;  	// Represents the areas where an actor can or cannot walk
+	Common::Array<Scaling> _scalings;   	// Defines the scaling of the actor in the room
+	Scaling _scaling;                   	// Defines the scaling of the actor in the room
+	HSQOBJECT _table;						// Squirrel table representing this room
+	bool _entering;							// Indicates whether or not an actor is entering this room
+	Lights _lights;							// Lights of the room
+	Common::Array<Walkbox> _mergedPolygon;
+	Common::Array<Object*> _triggers;		// Triggers currently enabled in the room
+	bool _pseudo;
 };
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 5d765b60e8c..f6d2da5be78 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -25,6 +25,7 @@
 #include "twp/scenegraph.h"
 #include "twp/twp.h"
 #include "twp/gfx.h"
+#include "twp/object.h"
 
 namespace Twp {
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 1b2953ede8d..4eea3df89d3 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -138,6 +138,8 @@ private:
 	Common::Array<SpriteSheetFrame> _frames;
 };
 
+struct ObjectAnimation;
+
 class Anim : public Node {
 public:
 	Anim(Object* obj);


Commit: 3658cbe47d1867ba21ea2d59a40d9f06db57c306
    https://github.com/scummvm/scummvm/commit/3658cbe47d1867ba21ea2d59a40d9f06db57c306
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add bnut, defineRoom, etc.

Changed paths:
    engines/twp/genlib.cpp
    engines/twp/gfx.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/ids.cpp
    engines/twp/ids.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index c71fef08b42..2b164426118 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -21,6 +21,8 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/room.h"
+#include "twp/object.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
@@ -86,7 +88,7 @@ static SQInteger arrayShuffle(HSQUIRRELVM v) {
 
 	sq_newarray(v, 0);
 	for (auto it = arr.begin(); it != arr.end(); it++) {
-		push(v, *it);
+		sqpush(v, *it);
 		sq_arrayappend(v, -2);
 	}
 	return 1;
@@ -97,7 +99,7 @@ static SQInteger assetExists(HSQUIRRELVM v) {
 	const SQChar *filename;
 	if (SQ_FAILED(sq_getstring(v, 2, &filename)))
 		return sq_throwerror(v, "failed to get filename");
-	push(v, g_engine->_pack.assetExists(filename));
+	sqpush(v, g_engine->_pack.assetExists(filename));
 	return 1;
 }
 
@@ -143,8 +145,16 @@ static SQInteger cameraFollow(HSQUIRRELVM v) {
 //      }
 // }
 static SQInteger cameraInRoom(HSQUIRRELVM v) {
-	// TODO: cameraInRoom
-	warning("cameraInRoom not implemented");
+	Room *room = sqroom(v, 2);
+	if (room) {
+		g_engine->setRoom(room);
+	} else {
+		Object *obj = sqobj(v, 2);
+		if (!obj || !obj->_room) {
+			return sq_throwerror(v, "failed to get room");
+		}
+		g_engine->setRoom(obj->_room);
+	}
 	return 0;
 }
 
@@ -175,10 +185,10 @@ static SQInteger cameraPos(HSQUIRRELVM v) {
 // Converts an integer to a char.
 static SQInteger sqChr(HSQUIRRELVM v) {
 	int value;
-	get(v, 2, value);
+	sqget(v, 2, value);
 	Common::String s;
 	s += char(value);
-	push(v, s);
+	sqpush(v, s);
 	return 1;
 }
 
@@ -247,25 +257,25 @@ static SQInteger indialog(HSQUIRRELVM v) {
 
 static SQInteger integer(HSQUIRRELVM v) {
 	float f = 0.f;
-	if (SQ_FAILED(get(v, 2, f)))
+	if (SQ_FAILED(sqget(v, 2, f)))
 		return sq_throwerror(v, "failed to get float value");
-	push(v, static_cast<int>(f));
+	sqpush(v, static_cast<int>(f));
 	return 1;
 }
 
 static SQInteger is_oftype(HSQUIRRELVM v, bool pred(SQObjectType)) {
-	push(v, pred(sq_gettype(v, 2)));
+	sqpush(v, pred(sq_gettype(v, 2)));
 	return 1;
 }
 
 static SQInteger in_array(HSQUIRRELVM v) {
 	HSQOBJECT obj;
 	sq_resetobject(&obj);
-	if (SQ_FAILED(get(v, 2, obj)))
+	if (SQ_FAILED(sqget(v, 2, obj)))
 		return sq_throwerror(v, "Failed to get object");
 	HSQOBJECT arr;
 	sq_resetobject(&arr);
-	if (SQ_FAILED(get(v, 3, arr)))
+	if (SQ_FAILED(sqget(v, 3, arr)))
 		return sq_throwerror(v, "Failed to get array");
 
 	Common::Array<HSQOBJECT> objs;
@@ -284,7 +294,7 @@ static SQInteger in_array(HSQUIRRELVM v) {
 		sq_pushobject(v, *it);
 		if (sq_cmp(v) == 0) {
 			sq_pop(v, 2);
-			push(v, 1);
+			sqpush(v, 1);
 			return 1;
 		}
 		sq_pop(v, 2);
@@ -493,7 +503,29 @@ static SQInteger translate(HSQUIRRELVM v) {
 	return 0;
 }
 
+// TODO: move this function
+static SQInteger defineRoom(HSQUIRRELVM v) {
+	// This command is used during the game's boot process.
+	// `defineRoom` is called once for every room in the game, passing it the room's room object.
+	// If the room has not been defined, it can not be referenced.
+	// `defineRoom` is typically called in the the DefineRooms.nut file which loads and defines every room in the game.
+	HSQOBJECT table;
+	sq_resetobject(&table);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &table)))
+		return sq_throwerror(v, "failed to get room table");
+	Common::String name;
+	sqgetf(v, table, "name", name);
+	if (name.size() == 0)
+		sqgetf(v, table, "background", name);
+	Room *room = g_engine->defineRoom(name, table);
+	debug("Define room: %s", name.c_str());
+	g_engine->_rooms.push_back(room);
+	sqpush(v, room->_table);
+	return 1;
+}
+
 void sqgame_register_genlib(HSQUIRRELVM v) {
+	regFunc(v, defineRoom, _SC("defineRoom"));
 	regFunc(v, activeVerb, _SC("activeVerb"));
 	regFunc(v, adhocalytics, _SC("adhocalytics"));
 	regFunc(v, arrayShuffle, _SC("arrayShuffle"));
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 85312b88480..8be0e36c303 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -123,6 +123,7 @@ public:
 	void camera(Math::Vector2d size);
 	Math::Vector2d camera() const;
 	Math::Vector2d cameraPos() const { return _cameraPos; }
+	void cameraPos(Math::Vector2d pos) { _cameraPos = pos; }
 
 	void use(Shader* shader);
 
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index fd7c27387ae..ebe4558ff08 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -34,6 +34,323 @@ namespace Twp {
 #define GGP_KEYS 8
 #define GGP_ENDOFFSETS 0xFFFFFFFF
 
+static const byte bnutKey[] = {
+	0x04, 0x1f, 0x5a, 0xac, 0x5f, 0x79, 0x10, 0xaf, 0x04, 0x1d, 0x46, 0x3a,
+	0x5f, 0x08, 0xee, 0xcb, 0xb5, 0x29, 0x06, 0x2e, 0xf9, 0x4b, 0xca, 0x44, 0x5e,
+	0xb3, 0xac, 0x81, 0xaf, 0x87, 0x04, 0x36, 0x60, 0xf4, 0x86, 0x93, 0x62, 0x0d,
+	0x77, 0x6a, 0xba, 0x74, 0x9e, 0xb3, 0xd0, 0xbb, 0xfa, 0xd2, 0x87, 0x87, 0x38,
+	0x9b, 0x10, 0x78, 0xe2, 0x11, 0x7e, 0xcb, 0x57, 0xf1, 0x18, 0xbc, 0x7e, 0xf1,
+	0x71, 0xac, 0x38, 0xc7, 0x38, 0x05, 0x99, 0xeb, 0xcf, 0xe0, 0xd6, 0x1b, 0x9d,
+	0x63, 0xfc, 0xa2, 0x23, 0xeb, 0x65, 0x58, 0xcf, 0xee, 0xe9, 0x68, 0x93, 0x9c,
+	0xd7, 0xd4, 0x33, 0x4a, 0xa1, 0xb8, 0x61, 0x8e, 0x59, 0x50, 0x8a, 0x29, 0xdf,
+	0xba, 0x6d, 0xa9, 0xd6, 0x7b, 0x70, 0xc9, 0x5b, 0xc2, 0xb1, 0x9e, 0x74, 0x6f,
+	0xdd, 0x72, 0x48, 0xd1, 0xc3, 0x6e, 0x12, 0x32, 0xa7, 0xfa, 0xd6, 0x13, 0x57,
+	0xe4, 0xd1, 0x36, 0xfe, 0x41, 0xb1, 0x5f, 0xba, 0x16, 0x88, 0xd9, 0xef, 0xb5,
+	0x7f, 0xba, 0x58, 0xe8, 0x4d, 0xe6, 0xe2, 0xcf, 0x66, 0xd5, 0x37, 0x89, 0xf3,
+	0x13, 0x1c, 0x94, 0x84, 0x8a, 0x7a, 0xf3, 0x86, 0x81, 0x3d, 0x14, 0x75, 0x63,
+	0xe0, 0x70, 0xd1, 0x3f, 0xc3, 0xef, 0xd1, 0x13, 0x42, 0x10, 0xd9, 0xc7, 0x85,
+	0xcb, 0xac, 0xfa, 0x18, 0x34, 0x60, 0x80, 0x39, 0xdf, 0x14, 0xa6, 0xf4, 0x06,
+	0x62, 0x3a, 0xef, 0x6b, 0x96, 0x4d, 0x05, 0xfe, 0x9b, 0xb4, 0x94, 0x3e, 0xb9,
+	0x53, 0x95, 0x5f, 0xff, 0x1a, 0x70, 0x2f, 0x80, 0xca, 0x8c, 0xb8, 0x41, 0x7f,
+	0xa8, 0xa4, 0xed, 0xed, 0xeb, 0x63, 0x4d, 0x68, 0xbc, 0x0e, 0x79, 0xa5, 0x52,
+	0x09, 0xce, 0x41, 0x0a, 0x6b, 0x9d, 0x04, 0x7b, 0x1d, 0xa8, 0xe5, 0x49, 0xc2,
+	0x4d, 0xcc, 0xbc, 0x5c, 0x7e, 0x1a, 0x0f, 0xab, 0x77, 0xe9, 0x89, 0x53, 0x2e,
+	0x13, 0x4c, 0xc5, 0x88, 0xf3, 0xef, 0xd9, 0x50, 0xec, 0xcd, 0x3d, 0xe8, 0xad,
+	0x1a, 0x91, 0xbb, 0x31, 0xba, 0x4e, 0x79, 0x89, 0xd0, 0x6a, 0x00, 0x58, 0x18,
+	0xbb, 0x1b, 0x21, 0xf4, 0x1e, 0xed, 0x43, 0x47, 0x57, 0x2b, 0xbf, 0x04, 0xb6,
+	0x0c, 0xbf, 0x85, 0x7d, 0xff, 0xd6, 0x9c, 0x04, 0x21, 0x07, 0x69, 0x99, 0x6c,
+	0x87, 0xf0, 0x27, 0xaf, 0x41, 0x69, 0x9d, 0x41, 0x1d, 0x56, 0x0c, 0x73, 0x00,
+	0x55, 0x8c, 0xc9, 0x92, 0xb9, 0xe7, 0xe7, 0xc1, 0xda, 0xf3, 0x4c, 0x08, 0x27,
+	0xbe, 0xc1, 0x6e, 0x00, 0x6b, 0x8f, 0x50, 0x44, 0xde, 0x72, 0xde, 0xab, 0x19,
+	0x4f, 0x66, 0xd5, 0x64, 0xa8, 0x53, 0x1e, 0x2e, 0xca, 0xf2, 0x36, 0xb5, 0xcc,
+	0xfa, 0x73, 0x67, 0x37, 0xc9, 0xe4, 0x06, 0x84, 0x6e, 0x25, 0x8e, 0x49, 0x6a,
+	0xf4, 0xd5, 0x31, 0x36, 0x87, 0xf7, 0xb0, 0x82, 0x9a, 0x6e, 0x72, 0x42, 0x4a,
+	0x03, 0x97, 0x69, 0xa3, 0x68, 0xa0, 0x72, 0xfa, 0xa0, 0x27, 0xa3, 0xfb, 0x25,
+	0x51, 0x0f, 0x81, 0xc8, 0x02, 0x5f, 0x28, 0x55, 0xd5, 0x50, 0xa3, 0xfe, 0xc9,
+	0xfb, 0xcd, 0x74, 0xbc, 0xd7, 0x80, 0xd4, 0x98, 0x7e, 0x28, 0x47, 0x4d, 0x32,
+	0x16, 0x67, 0x84, 0x1d, 0x94, 0x63, 0x10, 0x5a, 0xbc, 0xe1, 0x23, 0xb7, 0x08,
+	0xa6, 0x45, 0x36, 0xa8, 0xf7, 0x05, 0x58, 0x96, 0xbc, 0x6b, 0x19, 0x6a, 0x68,
+	0x33, 0xba, 0xed, 0x9b, 0xbb, 0x40, 0x6e, 0x84, 0xb7, 0xbf, 0xd7, 0x07, 0xaa,
+	0x56, 0x7e, 0xa3, 0x14, 0xf8, 0xbc, 0x10, 0x6a, 0xf0, 0x3e, 0xa9, 0xc9, 0x21,
+	0x1d, 0x8f, 0x69, 0x10, 0xae, 0x89, 0xd2, 0xa3, 0x1a, 0xac, 0xd1, 0xa6, 0xac,
+	0xbf, 0x27, 0x11, 0xec, 0x5b, 0x06, 0x0a, 0x6f, 0xd4, 0x3c, 0xac, 0x6c, 0xda,
+	0x2b, 0x84, 0x0d, 0xdd, 0x7f, 0xdf, 0x8a, 0x34, 0x9e, 0xc5, 0xf6, 0xfd, 0xdd,
+	0xc3, 0xd6, 0xc1, 0x77, 0x6b, 0x76, 0xdf, 0x3d, 0x9b, 0xfb, 0xa5, 0x0d, 0x81,
+	0x37, 0x3c, 0x03, 0x59, 0x3f, 0x4c, 0x71, 0xfc, 0xbd, 0x5b, 0x8f, 0x18, 0x05,
+	0xf4, 0xf1, 0x3e, 0xe8, 0x8c, 0xbb, 0xa0, 0x4b, 0x24, 0x96, 0x97, 0x76, 0xac,
+	0x60, 0xe6, 0x2c, 0x2d, 0xa6, 0xc0, 0x7e, 0xa5, 0xd2, 0x89, 0x5f, 0xb0, 0x23,
+	0x5e, 0xf6, 0xeb, 0x5f, 0x56, 0xb6, 0x17, 0x4a, 0x85, 0x2e, 0xff, 0xd5, 0xc4,
+	0x9f, 0x1a, 0x15, 0x32, 0x52, 0xf0, 0xf9, 0x79, 0xa7, 0x3c, 0xba, 0xd0, 0xec,
+	0xd5, 0x10, 0xc3, 0xf2, 0x4c, 0x29, 0xb4, 0x60, 0x54, 0x5a, 0x20, 0xc4, 0xe5,
+	0x92, 0xa1, 0x6e, 0x1e, 0x92, 0xb7, 0xfc, 0xee, 0xdd, 0x45, 0xb3, 0x71, 0x6f,
+	0xdc, 0x94, 0x1a, 0x30, 0xba, 0x29, 0x95, 0x26, 0x90, 0x22, 0x69, 0x6c, 0x06,
+	0xa2, 0x5f, 0xe6, 0xfe, 0x59, 0xa5, 0xb0, 0x9a, 0x51, 0xb1, 0x5a, 0xff, 0x88,
+	0xe6, 0xd5, 0xd7, 0x42, 0xf8, 0x2a, 0x97, 0x1a, 0x0f, 0xf0, 0x85, 0xdc, 0xac,
+	0x7b, 0x76, 0x15, 0x4e, 0xd4, 0x5a, 0x66, 0xd4, 0x6b, 0x78, 0x9c, 0xa0, 0x8d,
+	0x79, 0x7f, 0x7f, 0x96, 0x26, 0x6d, 0x4b, 0xe5, 0xf0, 0xa5, 0x4c, 0x93, 0x33,
+	0x63, 0x62, 0xe3, 0x38, 0xaf, 0xe4, 0x77, 0xff, 0xf4, 0x45, 0x47, 0xb9, 0x53,
+	0x7a, 0x51, 0x17, 0xb4, 0x1e, 0x10, 0x44, 0xa1, 0x51, 0xad, 0xb2, 0xd1, 0x64,
+	0xf6, 0x98, 0x85, 0x37, 0x12, 0x36, 0x95, 0x5c, 0xf0, 0xde, 0x49, 0x02, 0x83,
+	0xb2, 0xe8, 0x94, 0xb0, 0x2c, 0x10, 0x1a, 0x01, 0x61, 0xcb, 0x66, 0x21, 0x05,
+	0x5d, 0xef, 0x08, 0x2d, 0xdd, 0x7b, 0xf0, 0xd7, 0x8a, 0x7e, 0x0e, 0x2a, 0xda,
+	0x45, 0x00, 0x7c, 0x51, 0xd1, 0x07, 0x17, 0x17, 0x83, 0xf4, 0xba, 0x47, 0x7c,
+	0xe3, 0xe0, 0x07, 0xc1, 0xa9, 0xc3, 0x5c, 0x21, 0x0e, 0x1f, 0xb9, 0xd6, 0xba,
+	0xb6, 0x5c, 0xec, 0xef, 0x96, 0x58, 0x64, 0xfa, 0x1c, 0x71, 0x17, 0x6a, 0xb6,
+	0xaa, 0x5b, 0xfd, 0x6b, 0x9e, 0x67, 0x5e, 0x1b, 0x92, 0x77, 0x90, 0x86, 0x45,
+	0xae, 0x27, 0x0e, 0x8e, 0xbd, 0x3c, 0x3b, 0xa3, 0x47, 0x1d, 0x02, 0x38, 0x02,
+	0xc7, 0xc4, 0x4f, 0x9d, 0x14, 0x17, 0xc9, 0x64, 0xb3, 0x47, 0xbf, 0xa5, 0xda,
+	0x9b, 0x92, 0xcf, 0xbd, 0x57, 0xc2, 0x5e, 0xbc, 0x83, 0x82, 0x7a, 0x5f, 0x70,
+	0x07, 0x58, 0x02, 0xf6, 0xa3, 0x67, 0x4e, 0x7e, 0x94, 0x2b, 0x6e, 0xc3, 0x9c,
+	0xe6, 0xfd, 0x57, 0x50, 0xfb, 0xc0, 0xe6, 0x30, 0x12, 0x18, 0x3c, 0x7a, 0xfb,
+	0x35, 0x07, 0xbe, 0x53, 0x0a, 0x5c, 0x2f, 0xe7, 0x03, 0xfb, 0xbf, 0xda, 0x78,
+	0x51, 0xe9, 0x88, 0xdf, 0x41, 0x4b, 0x28, 0xc3, 0xa1, 0xfa, 0x34, 0x77, 0x86,
+	0x94, 0x88, 0x8a, 0x3f, 0x15, 0x4b, 0xf9, 0x21, 0x69, 0x90, 0x26, 0x07, 0xc5,
+	0xbb, 0x8a, 0x97, 0xb5, 0xa4, 0x2c, 0x46, 0xf4, 0x7d, 0xcf, 0x19, 0xfe, 0x73,
+	0xee, 0x2f, 0x65, 0x16, 0x68, 0x01, 0xe6, 0x78, 0xfa, 0x67, 0x3b, 0x17, 0x70,
+	0x59, 0xad, 0x7b, 0x9a, 0x7a, 0x70, 0x9d, 0xfe, 0x54, 0xad, 0x0d, 0x52, 0x63,
+	0x5e, 0xd2, 0xa7, 0xd3, 0xdd, 0x0f, 0x65, 0x08, 0x3a, 0x6a, 0xfa, 0xe1, 0x4e,
+	0x2c, 0x51, 0xbc, 0x93, 0x26, 0x03, 0x37, 0xb1, 0x5a, 0x4e, 0xbb, 0xd7, 0x55,
+	0xc8, 0xb9, 0xcf, 0x5d, 0xd3, 0xb2, 0xcf, 0x4e, 0xcf, 0xf7, 0x0f, 0x43, 0x11,
+	0x34, 0x1f, 0xf7, 0x96, 0xae, 0xf4, 0xe9, 0x76, 0x46, 0xc7, 0x42, 0x19, 0x44,
+	0x9d, 0x75, 0x2b, 0xd4, 0xa4, 0xaa, 0x50, 0x4f, 0x43, 0xdb, 0x96, 0x3a, 0xef,
+	0xba, 0xae, 0x0e, 0xbe, 0x59, 0xd9, 0xbc, 0xbd, 0x87, 0xa3, 0xee, 0x00, 0xfa,
+	0x51, 0x0d, 0x7d, 0x31, 0x1a, 0x07, 0x98, 0x16, 0x18, 0xcc, 0x7d, 0x65, 0xfc,
+	0x9c, 0x31, 0xd0, 0x84, 0x03, 0x66, 0xde, 0xac, 0x9f, 0x11, 0x96, 0xa6, 0xa6,
+	0xbc, 0xe0, 0x89, 0x2a, 0x9a, 0xa5, 0xcb, 0x1a, 0x5f, 0xbb, 0x70, 0x07, 0xcc,
+	0x83, 0xfe, 0xab, 0x0c, 0x4e, 0x37, 0x2a, 0xc1, 0x83, 0x84, 0x15, 0xdc, 0x81,
+	0x32, 0x32, 0x2f, 0x45, 0x5f, 0xfc, 0xc3, 0xca, 0xb1, 0xeb, 0xea, 0x33, 0xcc,
+	0x74, 0x13, 0xa9, 0x80, 0xce, 0x60, 0x05, 0xc4, 0x7a, 0xf7, 0x2d, 0x66, 0x80,
+	0x1b, 0x3d, 0x7f, 0x78, 0xf9, 0x6d, 0xa7, 0x4f, 0x42, 0xac, 0xec, 0xc5, 0x7c,
+	0x0e, 0x82, 0xb8, 0x18, 0xec, 0x3a, 0x23, 0x42, 0xc3, 0xb4, 0xe4, 0x7b, 0xe3,
+	0x53, 0x3f, 0xe7, 0xc9, 0xf3, 0x26, 0x66, 0x46, 0x5c, 0x35, 0x64, 0x67, 0x74,
+	0x7e, 0x70, 0x14, 0x36, 0x09, 0x8e, 0x74, 0x65, 0x19, 0x4b, 0x17, 0x00, 0x2f,
+	0x94, 0xd1, 0x73, 0xcf, 0x46, 0x66, 0x92, 0x04, 0x85, 0xeb, 0x46, 0xa4, 0xcc,
+	0xe6, 0x03, 0x53, 0xc7, 0x3a, 0x00, 0x48, 0xe3, 0xc4, 0x24, 0xd1, 0xa1, 0xc4,
+	0xc1, 0x96, 0xad, 0xfd, 0x03, 0xa0, 0xb2, 0x9a, 0x26, 0x19, 0xea, 0xd6, 0x6a,
+	0xd0, 0x77, 0x5a, 0xc6, 0x82, 0x74, 0x64, 0x5d, 0xda, 0xc9, 0xac, 0xb3, 0x33,
+	0xc1, 0x06, 0x9f, 0x23, 0x5c, 0xc5, 0xff, 0xb8, 0x65, 0xe5, 0x2d, 0x7f, 0x42,
+	0xe7, 0x34, 0x48, 0x8d, 0x7b, 0xc2, 0xab, 0x66, 0xdf, 0xdb, 0x49, 0x85, 0x09,
+	0x51, 0xd2, 0x11, 0x86, 0xf6, 0xc9, 0x33, 0x1f, 0x8e, 0x09, 0x69, 0x41, 0x86,
+	0x06, 0x9a, 0x19, 0x66, 0xd3, 0xed, 0x80, 0x06, 0xe6, 0x58, 0x9c, 0x82, 0x9d,
+	0xfa, 0x42, 0xee, 0x80, 0x29, 0x1d, 0xc8, 0x43, 0x49, 0x8f, 0x31, 0x91, 0x61,
+	0x5a, 0xda, 0x8a, 0x6e, 0xfd, 0xbf, 0x07, 0x76, 0xdf, 0x95, 0xa4, 0x5b, 0x2d,
+	0x03, 0x7b, 0x74, 0x82, 0x93, 0xaf, 0xdb, 0x4b, 0x66, 0xae, 0x86, 0xe7, 0xa9,
+	0x36, 0x17, 0x91, 0xcb, 0x13, 0x74, 0xfd, 0x6a, 0xae, 0x15, 0xc4, 0x11, 0x46,
+	0x1f, 0x7f, 0xa2, 0xfa, 0x34, 0xb5, 0x94, 0x8d, 0x07, 0x75, 0xdc, 0xe8, 0xb1,
+	0xc1, 0xac, 0x5e, 0xc5, 0xb0, 0xdd, 0xb1, 0x25, 0x52, 0x2c, 0xd8, 0x92, 0x51,
+	0x27, 0x7d, 0x04, 0x5c, 0xe4, 0x48, 0xbe, 0x43, 0x76, 0xdd, 0x20, 0xe1, 0x20,
+	0xa5, 0x3a, 0xbc, 0x46, 0x4f, 0x24, 0x6d, 0x27, 0x15, 0x0f, 0xc0, 0x4b, 0xbe,
+	0x19, 0x9a, 0xbd, 0x66, 0xe4, 0x9e, 0xf8, 0x00, 0xe4, 0x8e, 0xa1, 0x96, 0x7b,
+	0x71, 0xf9, 0x56, 0xc6, 0x78, 0x0f, 0x4c, 0x35, 0xa0, 0xb8, 0xef, 0xdb, 0x17,
+	0x3f, 0x5c, 0x8a, 0x5e, 0xdc, 0x64, 0x9c, 0x33, 0xe5, 0x47, 0x92, 0x2b, 0xc5,
+	0x3d, 0x5e, 0x21, 0xaf, 0xb9, 0x7f, 0x52, 0x11, 0x6b, 0xd0, 0x43, 0xca, 0x09,
+	0x20, 0x69, 0x1b, 0xcd, 0x80, 0x86, 0x5d, 0xbc, 0x5f, 0xd3, 0x77, 0xac, 0x57,
+	0xe3, 0x0c, 0x02, 0xc3, 0x41, 0x77, 0x3e, 0x18, 0xde, 0x77, 0x38, 0xf0, 0x2d,
+	0xa9, 0x26, 0xe5, 0x0a, 0xb8, 0x64, 0x22, 0x5e, 0x56, 0xf2, 0xba, 0x83, 0xe9,
+	0xbc, 0xb6, 0x67, 0x04, 0x9c, 0xf0, 0x72, 0x9b, 0x1f, 0xa1, 0x28, 0xf5, 0x0a,
+	0xbb, 0x8d, 0x60, 0x9b, 0xf7, 0x4b, 0xa6, 0x8e, 0xac, 0x95, 0x42, 0xe5, 0x65,
+	0xc8, 0x51, 0x67, 0x30, 0xd6, 0x4a, 0xe4, 0xb7, 0x62, 0x6c, 0x3e, 0x10, 0xaa,
+	0xfa, 0x27, 0x53, 0x27, 0x28, 0xa9, 0xef, 0xf6, 0xd2, 0x6a, 0xbc, 0xf4, 0xf2,
+	0xac, 0xcf, 0xab, 0xcf, 0x15, 0x10, 0xef, 0xf5, 0x33, 0x8c, 0x46, 0xe8, 0xbc,
+	0x8a, 0x0b, 0x96, 0x99, 0x5f, 0x51, 0x90, 0xa0, 0x01, 0x87, 0xf7, 0x24, 0x5c,
+	0xe0, 0x36, 0x2d, 0x67, 0x70, 0x75, 0x86, 0xf4, 0x15, 0xc8, 0x7b, 0x4b, 0x1a,
+	0x2a, 0x5e, 0x74, 0x9c, 0x2c, 0xcd, 0x57, 0xab, 0x6b, 0xb5, 0x85, 0x2f, 0xc5,
+	0x14, 0xd2, 0x90, 0x4a, 0x81, 0xaa, 0x14, 0xf4, 0x6d, 0x20, 0x06, 0x16, 0x26, 0xc5,
+	0x9a, 0x94, 0x9e, 0x3d, 0x92, 0xd6, 0xf0, 0x92, 0xa0, 0x7d, 0x9d, 0x46, 0x89,
+	0xd2, 0x9a, 0x2a, 0x0e, 0x02, 0x0a, 0xf0, 0x8a, 0x0a, 0xca, 0x81, 0x59, 0x73,
+	0xb0, 0x0e, 0xff, 0xbd, 0x92, 0xe8, 0x04, 0x9b, 0x08, 0x10, 0x9f, 0xe4, 0xf1,
+	0x8c, 0x19, 0x43, 0xb6, 0x7f, 0xef, 0xb4, 0x50, 0xf5, 0xb4, 0xae, 0x0a, 0x81,
+	0xbc, 0x1f, 0x06, 0x89, 0x79, 0x1b, 0x80, 0x5b, 0xa2, 0x53, 0xd4, 0x06, 0x18,
+	0x46, 0x41, 0xea, 0x89, 0x39, 0x6a, 0x0b, 0xd0, 0xe7, 0x9f, 0x29, 0x22, 0xf8,
+	0xe0, 0x90, 0xea, 0x32, 0x9d, 0xaf, 0x6f, 0x70, 0x3d, 0x69, 0x3a, 0x55, 0x64,
+	0x2e, 0x38, 0xbf, 0xf4, 0xc8, 0xa1, 0xfd, 0xcf, 0xf5, 0x97, 0xbf, 0x61, 0xb8,
+	0x8d, 0xd1, 0xe1, 0x6e, 0x6d, 0x86, 0x51, 0xa2, 0x77, 0xf5, 0x49, 0xa0, 0xea,
+	0xe5, 0x77, 0xcb, 0x64, 0x88, 0xe5, 0xae, 0x09, 0xea, 0xf8, 0xd4, 0x65, 0x27,
+	0x3c, 0x57, 0x12, 0x5e, 0xe0, 0x39, 0x18, 0x68, 0x02, 0x74, 0x16, 0x47, 0xab,
+	0xd3, 0x25, 0x5f, 0x98, 0x7e, 0x76, 0x67, 0xbd, 0x56, 0xc1, 0x1d, 0x89, 0x05,
+	0x5d, 0xbb, 0xea, 0xd3, 0x2e, 0x2c, 0x0e, 0x39, 0x41, 0xfd, 0xee, 0x37, 0x38,
+	0x14, 0x8b, 0x65, 0x66, 0x22, 0xf6, 0xca, 0xb9, 0xd9, 0x11, 0x6f, 0x5b, 0xdd,
+	0x16, 0xb1, 0x17, 0x7b, 0xda, 0x59, 0x7b, 0x1b, 0xd0, 0x6d, 0xc1, 0x74, 0xcf,
+	0xc3, 0x6e, 0x84, 0x94, 0x5a, 0xb6, 0x3e, 0x05, 0x67, 0xa5, 0x00, 0x3a, 0x31,
+	0xfe, 0xcb, 0x3c, 0x9c, 0xe1, 0x30, 0x8a, 0x86, 0x2e, 0x0a, 0x5f, 0xd4, 0xac,
+	0xf1, 0xb4, 0x4a, 0xe9, 0x69, 0x06, 0x1e, 0xde, 0xdc, 0xd8, 0x4a, 0x59, 0x4d,
+	0xf7, 0xa4, 0x1a, 0xc8, 0x80, 0xae, 0xba, 0x9b, 0xa1, 0x2d, 0x4f, 0x47, 0x21,
+	0x7b, 0xd0, 0x33, 0xa0, 0x9c, 0x38, 0x25, 0x9d, 0x12, 0x6c, 0x70, 0x3c, 0x70,
+	0xdc, 0xed, 0xc4, 0xaf, 0xeb, 0xaa, 0xe9, 0x42, 0x0e, 0x63, 0xce, 0xea, 0xb6,
+	0xb4, 0xc8, 0x4a, 0xee, 0x0a, 0xe3, 0x3a, 0xc3, 0x5e, 0x25, 0xdb, 0x66, 0xa0,
+	0x94, 0x6d, 0x13, 0xf3, 0xf7, 0xe2, 0xae, 0x9d, 0x5f, 0x31, 0x32, 0xbc, 0x64,
+	0x6a, 0xc9, 0xb8, 0x2e, 0x8e, 0xba, 0x7b, 0x3a, 0x1b, 0x06, 0x62, 0xd8, 0x69,
+	0xd0, 0xf1, 0x76, 0xb7, 0x7f, 0x49, 0x9f, 0x03, 0xa5, 0x5a, 0xc1, 0x9c, 0x9d,
+	0xd6, 0xb1, 0x77, 0xf6, 0xeb, 0xee, 0x45, 0x93, 0xb0, 0xa7, 0x40, 0x8d, 0x5b,
+	0x7e, 0xc8, 0xde, 0x37, 0x09, 0xb1, 0xbe, 0x57, 0x1c, 0x59, 0xcb, 0x09, 0xc8,
+	0x7b, 0xeb, 0x0b, 0x21, 0xc6, 0xf1, 0x80, 0xc2, 0x2b, 0x01, 0xa0, 0x11, 0xf9,
+	0xb3, 0x32, 0x42, 0xa9, 0xf8, 0xb9, 0x1c, 0x79, 0xbf, 0x70, 0x7d, 0xba, 0x56,
+	0xf5, 0x9e, 0xe9, 0x92, 0xc7, 0x16, 0x7f, 0xad, 0x71, 0x9f, 0x87, 0xf1, 0x82,
+	0x9b, 0xf4, 0x19, 0x43, 0x54, 0xaf, 0x71, 0x26, 0x05, 0x71, 0xc3, 0xff, 0x9c,
+	0x56, 0xf1, 0xf5, 0x3c, 0x2c, 0x60, 0x36, 0x84, 0x8f, 0x1b, 0x6c, 0x91, 0xb6,
+	0xb3, 0xf2, 0xc3, 0x09, 0xe6, 0xc5, 0x42, 0x79, 0x06, 0x3b, 0x3f, 0x8f, 0x17,
+	0x75, 0xfa, 0x40, 0xb5, 0x95, 0x86, 0x87, 0xbb, 0xaf, 0x4e, 0xb0, 0xa4, 0x7f,
+	0x56, 0x73, 0xaf, 0xdf, 0x41, 0xc7, 0xc4, 0xf6, 0x16, 0x73, 0x18, 0x30, 0xc1,
+	0x64, 0x92, 0xf0, 0x5a, 0xc1, 0xeb, 0x06, 0x28, 0xbf, 0x97, 0xe2, 0x63, 0x33,
+	0x66, 0x85, 0xbc, 0xec, 0xe9, 0x3a, 0x9c, 0xbd, 0x95, 0x08, 0x2f, 0x5c, 0xa9,
+	0xe5, 0x1e, 0xed, 0xcf, 0xab, 0x1f, 0x5b, 0x83, 0xcb, 0x2e, 0x6d, 0x36, 0xc2,
+	0x97, 0x93, 0x31, 0x4e, 0xba, 0x84, 0x3a, 0x5f, 0x8c, 0x88, 0xf8, 0xcf, 0xdf,
+	0x95, 0x16, 0xd0, 0x61, 0x1b, 0x29, 0x28, 0x65, 0x9e, 0x52, 0x1f, 0x64, 0x08,
+	0xa2, 0x33, 0x41, 0xbb, 0x3f, 0x90, 0x4f, 0x67, 0x4a, 0x42, 0x13, 0xa1, 0x7f,
+	0x26, 0xc8, 0x2e, 0x99, 0xa4, 0xa6, 0x23, 0x7a, 0x1f, 0xe1, 0xad, 0x0e, 0x27,
+	0x72, 0xb0, 0xea, 0x73, 0x98, 0x11, 0xb6, 0x90, 0x1d, 0xd2, 0x50, 0x59, 0x58,
+	0xe2, 0xac, 0x25, 0xf6, 0xb1, 0x7c, 0xc2, 0x77, 0x85, 0x93, 0x24, 0x78, 0x8e,
+	0x09, 0xdb, 0xb6, 0xbb, 0x1d, 0x49, 0xce, 0x49, 0xde, 0x90, 0xc4, 0x1f, 0x88,
+	0x4c, 0x49, 0xdc, 0xaf, 0x04, 0xbb, 0xac, 0x1c, 0x60, 0xeb, 0xdd, 0x7a, 0x1a,
+	0xa1, 0x35, 0xaf, 0xb5, 0xe0, 0x00, 0x0a, 0xef, 0xe8, 0xeb, 0x21, 0xf6, 0xff,
+	0x93, 0x77, 0x5d, 0xef, 0xdb, 0xe0, 0xcc, 0x4d, 0x00, 0xa4, 0x79, 0x7f, 0x6d,
+	0x65, 0x5a, 0x7a, 0x4e, 0xc0, 0x16, 0x4e, 0xe6, 0xb5, 0xbf, 0x63, 0xa0, 0xc6,
+	0x40, 0x80, 0xe6, 0xe0, 0x97, 0x8f, 0xb4, 0xfd, 0xc2, 0x21, 0xeb, 0x86, 0x7a,
+	0xb2, 0x65, 0x78, 0xa1, 0xac, 0xcc, 0x4d, 0x7b, 0x2c, 0x2a, 0x97, 0x9f, 0x87,
+	0x3f, 0x6c, 0xaa, 0x8a, 0x95, 0x85, 0xb9, 0x84, 0x54, 0x24, 0x93, 0x04, 0xd5,
+	0xb8, 0xa8, 0x62, 0xf8, 0x41, 0xbd, 0xfe, 0x35, 0x73, 0x64, 0x4a, 0x73, 0x9d,
+	0x2c, 0x88, 0x9a, 0x47, 0x9c, 0x53, 0xea, 0x9d, 0xdf, 0xde, 0x09, 0x8e, 0x3f,
+	0xfe, 0xdd, 0xb2, 0x53, 0xe8, 0x1f, 0xef, 0xf7, 0xba, 0xaa, 0xda, 0xfe, 0xbb,
+	0x96, 0xdd, 0xa8, 0x61, 0x08, 0x1c, 0xd5, 0x16, 0xc7, 0x18, 0xf6, 0xb9, 0x8d,
+	0x10, 0xc6, 0xe6, 0x22, 0x2a, 0xbc, 0xf5, 0x29, 0x55, 0x3e, 0xc2, 0xb8, 0xeb,
+	0x96, 0x32, 0xa9, 0x18, 0xa4, 0x70, 0xb2, 0xfd, 0x58, 0xfa, 0x35, 0x94, 0xdc,
+	0x5e, 0xd7, 0x51, 0x94, 0xc4, 0xd2, 0x8c, 0xc6, 0x2a, 0x0a, 0xa1, 0x18, 0x71,
+	0xe0, 0xd3, 0xbd, 0x1b, 0xc9, 0xf3, 0x6d, 0xd0, 0x9d, 0xc5, 0x18, 0xa5, 0xaf,
+	0x1d, 0x59, 0x1b, 0xa0, 0xcb, 0xac, 0xe9, 0xf3, 0x3b, 0x5e, 0x79, 0x5f, 0xda,
+	0x47, 0x32, 0xe7, 0x3a, 0x39, 0x7a, 0xaa, 0xf5, 0x37, 0x79, 0x7a, 0x51, 0x65,
+	0x9e, 0x2e, 0xa6, 0xc1, 0xe2, 0xb6, 0x82, 0x95, 0xce, 0xf3, 0x89, 0xaa, 0x3d,
+	0x43, 0x1e, 0x3d, 0xe1, 0xdd, 0xc2, 0xb2, 0x87, 0xe2, 0x3e, 0x0a, 0x19, 0x2f,
+	0x82, 0x14, 0x66, 0xf3, 0x4b, 0x65, 0xa3, 0x03, 0x86, 0x08, 0xb0, 0xef, 0x77,
+	0xf4, 0x1a, 0xd4, 0x98, 0x41, 0xe4, 0xef, 0x41, 0x19, 0x05, 0xf5, 0x0c, 0x6e,
+	0xaf, 0x23, 0xf5, 0x70, 0x54, 0x17, 0x43, 0xbd, 0x69, 0x5c, 0x29, 0x55, 0xe7,
+	0xc6, 0x2b, 0x54, 0x96, 0x83, 0x42, 0x5d, 0x08, 0xec, 0xbe, 0xd6, 0x65, 0x1f,
+	0xed, 0xd8, 0xec, 0x4d, 0x7f, 0xe4, 0x34, 0xd2, 0xa6, 0x56, 0x35, 0x09, 0x00,
+	0x99, 0x0a, 0xdd, 0x35, 0x1e, 0x73, 0x1e, 0x5e, 0x0d, 0x1b, 0x98, 0x2c, 0xb8,
+	0x9e, 0xe3, 0xc3, 0xd3, 0x48, 0xfa, 0x6a, 0x65, 0x9e, 0x8e, 0xe4, 0x7b, 0x9d,
+	0x17, 0x9a, 0xa1, 0xc3, 0xaf, 0x81, 0x11, 0xd1, 0x9d, 0xb5, 0xa8, 0x4d, 0xb2,
+	0xe7, 0x9c, 0xca, 0x57, 0x7c, 0xee, 0xe6, 0x61, 0x71, 0xa0, 0x16, 0xaa, 0xff,
+	0xa5, 0x23, 0x60, 0x02, 0x9c, 0x72, 0x1a, 0x19, 0x3b, 0x07, 0xaf, 0x8d, 0x8c,
+	0x47, 0xc6, 0xf2, 0xda, 0x7f, 0x2c, 0x8e, 0x68, 0x28, 0xe8, 0x7b, 0xe6, 0xe7,
+	0x0e, 0x8e, 0xa3, 0x55, 0x56, 0xf5, 0x82, 0x83, 0x6b, 0xa2, 0xae, 0x3b, 0x03,
+	0x6b, 0x0c, 0x07, 0xb3, 0xed, 0x73, 0xf2, 0x7f, 0x33, 0x9c, 0x10, 0x32, 0x1a,
+	0xda, 0xbe, 0x73, 0x71, 0x89, 0xa9, 0x92, 0xe2, 0x47, 0x8f, 0x26, 0x94, 0xe4,
+	0xc7, 0x57, 0x8c, 0x19, 0x81, 0xfa, 0xd7, 0xfb, 0xcd, 0x61, 0x68, 0x2d, 0x5c,
+	0xef, 0xc1, 0xd7, 0x08, 0x6e, 0x53, 0x59, 0x15, 0x1f, 0xa6, 0xbe, 0x34, 0x95,
+	0x37, 0xf2, 0x4b, 0x41, 0x23, 0xb2, 0xf6, 0xcc, 0x88, 0x46, 0xcb, 0x27, 0xa6,
+	0xf0, 0x60, 0x63, 0xc8, 0x22, 0x0c, 0x40, 0xe4, 0x72, 0x70, 0x86, 0x03, 0x4e,
+	0xb4, 0x16, 0x61, 0x6e, 0x90, 0xbf, 0x53, 0x0c, 0x11, 0xd1, 0xec, 0xd5, 0x18,
+	0x72, 0x5d, 0x9c, 0xa1, 0xb2, 0x1f, 0xd4, 0xc8, 0x5f, 0xd2, 0xbb, 0x8a, 0x98,
+	0xe3, 0x56, 0x4e, 0x23, 0xf0, 0x20, 0x23, 0x7f, 0xeb, 0x80, 0xc6, 0xb8, 0xff,
+	0xfd, 0x69, 0xfc, 0x34, 0x33, 0x35, 0xf0, 0xd4, 0x84, 0x8b, 0xc3, 0x8c, 0x8e,
+	0x0d, 0xfe, 0x51, 0x60, 0x5f, 0x21, 0x08, 0x6a, 0xac, 0xee, 0xe3, 0x37, 0xea,
+	0x82, 0x8b, 0xdc, 0xdd, 0x27, 0x02, 0x2f, 0xdc, 0xeb, 0x46, 0xb7, 0x55, 0xf0,
+	0xb5, 0x65, 0x11, 0x81, 0x33, 0x07, 0x37, 0xe8, 0x0b, 0x77, 0x11, 0x01, 0x98,
+	0x97, 0x91, 0x8d, 0xa3, 0xfc, 0x91, 0x46, 0x8b, 0x6b, 0xdd, 0xb6, 0x26, 0x07,
+	0xf1, 0xc7, 0x68, 0x47, 0x81, 0x19, 0xc7, 0xa8, 0xbc, 0x16, 0x5c, 0x26, 0x4f,
+	0xc5, 0xca, 0x6b, 0x2a, 0x61, 0xde, 0xc4, 0x05, 0xa9, 0xfa, 0xd6, 0xa1, 0xf5,
+	0x31, 0x15, 0xce, 0x0c, 0x30, 0xef, 0x2f, 0xb6, 0xe3, 0xcb, 0xbf, 0x12, 0xd3,
+	0xb4, 0x12, 0xa8, 0x51, 0xd4, 0x5b, 0x3d, 0x53, 0xa1, 0x31, 0x6a, 0x20, 0xd9,
+	0x10, 0xea, 0xad, 0x2e, 0xa1, 0xb5, 0xc5, 0xc0, 0xb8, 0xd0, 0xeb, 0x4e, 0x21,
+	0xe1, 0xff, 0x2b, 0x19, 0x85, 0xa1, 0xf2, 0x9d, 0x61, 0x58, 0xf8, 0x65, 0xf8,
+	0x71, 0x83, 0xae, 0x42, 0xa7, 0xbf, 0xbb, 0xf8, 0x87, 0x6b, 0x19, 0xb0, 0x91,
+	0x57, 0xa4, 0xac, 0x1d, 0x8d, 0x4c, 0x70, 0x03, 0x50, 0x96, 0x18, 0xf8, 0xc9,
+	0xe4, 0x67, 0xb9, 0x7a, 0x75, 0x9e, 0xea, 0x79, 0x2a, 0x12, 0xbb, 0x5d, 0x0d,
+	0x7b, 0x4e, 0xf6, 0x91, 0x0c, 0xdb, 0xbf, 0x99, 0x46, 0x8f, 0x14, 0x39, 0x8b,
+	0x38, 0x22, 0x3f, 0x75, 0xa2, 0xa1, 0x6f, 0xe3, 0xbe, 0x43, 0x91, 0xd7, 0x87,
+	0xea, 0x2b, 0x02, 0xa3, 0x9c, 0x0d, 0x1b, 0xcb, 0x36, 0x91, 0xf2, 0xeb, 0xe2,
+	0x9e, 0x47, 0x09, 0x49, 0x71, 0x5a, 0xe6, 0x88, 0x1a, 0x42, 0x64, 0xe3, 0xc7,
+	0xfd, 0xad, 0x3a, 0xc2, 0x4b, 0x0f, 0x3e, 0x3b, 0x3a, 0xa8, 0x63, 0xc5, 0x7f,
+	0xc6, 0x94, 0xa2, 0x8a, 0x0b, 0xa8, 0xbe, 0x58, 0x52, 0x96, 0x7b, 0x05, 0x54,
+	0x6a, 0x31, 0x28, 0x09, 0xf6, 0x72, 0xde, 0xcf, 0x48, 0x11, 0xa2, 0x4b, 0xde,
+	0xe1, 0xe2, 0x12, 0x9b, 0x3d, 0x51, 0x06, 0x77, 0x4a, 0xfc, 0x81, 0xf8, 0xfe,
+	0x1f, 0x33, 0x63, 0xdf, 0xb4, 0xcb, 0xb1, 0x8b, 0xcf, 0x57, 0x43, 0xfa, 0xac,
+	0x6b, 0x54, 0x85, 0x83, 0x03, 0x32, 0xa5, 0x22, 0x28, 0x2b, 0x7b, 0x38, 0x54,
+	0x47, 0x13, 0x71, 0x96, 0xba, 0x67, 0x45, 0x74, 0x17, 0x25, 0x8f, 0x93, 0x44,
+	0x30, 0xdf, 0x17, 0x31, 0xe6, 0x90, 0xca, 0x47, 0x26, 0x98, 0xff, 0xa0, 0x0f,
+	0x21, 0x33, 0xfb, 0x89, 0xdb, 0x78, 0x6d, 0x84, 0x0f, 0x91, 0xd7, 0x02, 0x72,
+	0xdc, 0x1a, 0xd5, 0xbe, 0xf3, 0x0c, 0x28, 0x00, 0x10, 0x28, 0xbe, 0xb2, 0x34,
+	0x08, 0xfc, 0x88, 0x06, 0x93, 0xa8, 0x09, 0x7b, 0xf4, 0x6e, 0xe4, 0x39, 0x50,
+	0x25, 0xff, 0xbb, 0x36, 0xd6, 0x4e, 0x34, 0xd5, 0x78, 0x69, 0x34, 0xf0, 0x7b,
+	0xda, 0x0c, 0x91, 0x0b, 0x3a, 0xf0, 0x7f, 0x10, 0xfa, 0xe1, 0x70, 0xf6, 0x01,
+	0x47, 0xfd, 0x32, 0xf9, 0x60, 0x59, 0x7f, 0x68, 0x5c, 0xc0, 0xeb, 0x81, 0x44,
+	0xb0, 0x94, 0xb7, 0x9e, 0xd9, 0x32, 0x15, 0xd1, 0xd4, 0x53, 0x50, 0x48, 0x95,
+	0x68, 0x97, 0x57, 0xa1, 0x83, 0x30, 0xe1, 0xff, 0xdd, 0xc4, 0xb5, 0xef, 0x19,
+	0x74, 0xbc, 0x5c, 0x49, 0xb3, 0xd3, 0x12, 0xb3, 0xd2, 0x78, 0xae, 0x54, 0x59,
+	0x27, 0x58, 0x20, 0x1b, 0x81, 0x32, 0x0e, 0x08, 0x32, 0x8e, 0xd6, 0xc0, 0x8e,
+	0xe2, 0x77, 0xeb, 0x00, 0x7b, 0x7a, 0x4a, 0x22, 0xf3, 0xf8, 0x8c, 0xab, 0x63,
+	0x5d, 0xe0, 0x0b, 0x57, 0xe8, 0x4e, 0xd7, 0x85, 0xf9, 0xc5, 0xe6, 0x3d, 0x09,
+	0xf3, 0x37, 0x9e, 0x77, 0xd7, 0x99, 0x8c, 0x09, 0x17, 0x08, 0x64, 0x2c, 0x12,
+	0xd7, 0x6c, 0x01, 0xb3, 0x91, 0x0b, 0x18, 0x0b, 0x9f, 0xbd, 0xf9, 0x9c, 0xa9,
+	0xc4, 0x66, 0x75, 0xbd, 0x9e, 0x61, 0x01, 0x31, 0x39, 0xef, 0x9f, 0x48, 0xe7,
+	0x99, 0xf1, 0x96, 0x12, 0xb7, 0xf3, 0x22, 0xaf, 0x6b, 0xd2, 0x50, 0x73, 0x6d,
+	0x79, 0x7c, 0xc8, 0x8c, 0xeb, 0x65, 0x7c, 0xef, 0x52, 0x4f, 0x3d, 0x1f, 0xdb,
+	0xe6, 0xc8, 0xc4, 0x3e, 0xe4, 0xdb, 0x31, 0x1f, 0x71, 0xee, 0xa1, 0x17, 0x4d,
+	0xa5, 0x3e, 0x90, 0xae, 0x03, 0xe5, 0x53, 0xe9, 0x5e, 0xaa, 0xe2, 0xd2, 0x16,
+	0x1d, 0x86, 0xe4, 0x04, 0x49, 0x27, 0x20, 0xb2, 0x80, 0xde, 0xb4, 0x4f, 0xd8,
+	0x4a, 0x62, 0x60, 0x74, 0xa5, 0x9e, 0x36, 0x82, 0x94, 0x74, 0x22, 0x07, 0xc2,
+	0x2f, 0x69, 0x5c, 0x2b, 0xba, 0xf9, 0x76, 0x38, 0xc3, 0xd8, 0xe9, 0x8e, 0xa4,
+	0x60, 0xaa, 0xa5, 0x02, 0x2b, 0x97, 0x1a, 0x31, 0xce, 0xb6, 0xc4, 0x5d, 0x48,
+	0x3f, 0x88, 0x69, 0x38, 0x5b, 0xec, 0x96, 0xd1, 0xa9, 0x6c, 0xe2, 0x8d, 0x84,
+	0x8f, 0x35, 0xa3, 0x06, 0xb8, 0x3d, 0x39, 0xca, 0xbc, 0x14, 0x58, 0x74, 0x37,
+	0xa6, 0xa3, 0xf1, 0x9d, 0xd4, 0xc6, 0x7e, 0x12, 0x57, 0xae, 0x69, 0xfa, 0x65,
+	0x7d, 0x99, 0x4f, 0x15, 0x3b, 0xce, 0xb4, 0x82, 0x4d, 0xa8, 0x4d, 0xb1, 0xa5,
+	0x69, 0x4b, 0x32, 0xbd, 0x79, 0x49, 0x88, 0x44, 0xac, 0x59, 0x49, 0x95, 0x46,
+	0xdc, 0x04, 0xeb, 0xba, 0x14, 0xe5, 0x56, 0x34, 0x7a, 0x19, 0x6b, 0xdd, 0xd0,
+	0xf1, 0xbc, 0xd7, 0xf4, 0xc9, 0x0d, 0x0d, 0x7a, 0xa6, 0x43, 0xad, 0xf8, 0xa0,
+	0x1b, 0x70, 0x1e, 0x05, 0x9f, 0x9f, 0x8c, 0x38, 0x58, 0xd4, 0x62, 0x5d, 0x43,
+	0x83, 0x96, 0xb7, 0x83, 0x37, 0x09, 0xcb, 0xdb, 0x9c, 0x57, 0x4c, 0xd9, 0x40,
+	0x71, 0xb5, 0xd1, 0x18, 0xeb, 0xb6, 0x90, 0xc3, 0x15, 0x4f, 0xcc, 0x91, 0xcb,
+	0xbd, 0x5c, 0x41, 0x0c, 0x17, 0x2c, 0x8d, 0x4c, 0xb6, 0xee, 0x66, 0x88, 0x58,
+	0x90, 0xfe, 0x1d, 0x03, 0x70, 0x89, 0x58, 0xaa, 0x50, 0xc2, 0xcc, 0x91, 0x0a,
+	0x2f, 0x66, 0x70, 0x03, 0x24, 0x41, 0x59, 0x60, 0x88, 0x3a, 0x59, 0xfa, 0x59,
+	0x42, 0xdf, 0x11, 0xf0, 0x75, 0xe0, 0xc3, 0x04, 0xee, 0xb6, 0x3a, 0x70, 0x57,
+	0xcc, 0xa7, 0xb3, 0x42, 0xfa, 0xf1, 0x3b, 0x39, 0xb0, 0x2c, 0x60, 0x97, 0xd4,
+	0xcb, 0x31, 0x14, 0x6b, 0x94, 0xf1, 0x21, 0xfa, 0x57, 0x5f, 0xf8, 0xaa, 0x5c,
+	0xb9, 0x71, 0x2d, 0xa0, 0x7d, 0x96, 0x62, 0xbe, 0xf4, 0xf4, 0xb9, 0xae, 0x6f,
+	0xb6, 0x56, 0x30, 0x4e, 0x73, 0xf8, 0x05, 0xc5, 0xc1, 0x97, 0xe3, 0x5d, 0x91,
+	0xcf, 0x25, 0x3b, 0x39, 0xff, 0x44, 0x22, 0x50, 0x2f, 0x09, 0x39, 0x3c, 0xb4,
+	0x7e, 0x8c, 0x8c, 0x95, 0xa8, 0x53, 0x05, 0xab, 0xf6, 0xf5, 0x7e, 0x8b, 0x77,
+	0xca, 0x6b, 0x23, 0xce, 0xa3, 0x3a, 0x25, 0xe5, 0x0a, 0x60, 0x26, 0x63, 0x48,
+	0x31, 0x85, 0xde, 0x2a, 0x73, 0x0c, 0xcf, 0x83, 0xb0, 0x57, 0x48, 0x70, 0x4e,
+	0xf7, 0x67, 0x5d, 0x60, 0x55, 0x89, 0xca, 0x39, 0x58, 0x3e, 0xcd, 0xc4, 0xc5,
+	0xff, 0x55, 0x95, 0x68, 0xa0, 0x34, 0xbe, 0xf6, 0x86, 0xda, 0x62, 0xb0, 0x98,
+	0xe1, 0x0a, 0xc7, 0xf5, 0x4c, 0xb9, 0x82, 0x6a, 0x8e, 0xc1, 0x20, 0xa1, 0x4c,
+	0x52, 0x7d, 0x1a, 0xe1, 0xd7, 0x76, 0xd9, 0xa5, 0x00, 0x0d, 0xe3, 0xed, 0x9d,
+	0x4c, 0x69, 0xd0, 0x30, 0xe9, 0xc1, 0xae, 0x40, 0xac, 0x18, 0x71, 0x6e, 0x93,
+	0xe8, 0x91, 0x48, 0xb2, 0x2a, 0xd8, 0xe0, 0xdb, 0x4a, 0xe9, 0x1b, 0x02, 0xde,
+	0x81, 0xc9, 0x5d, 0x15, 0x7b, 0x77, 0x4d, 0xf0, 0x94, 0x07, 0xfe, 0x32, 0xf3,
+	0xfb, 0xd0, 0x18, 0x66, 0x95, 0x73, 0xc8, 0x6c, 0x9f, 0xd4, 0x89, 0xf6, 0x09,
+	0xba, 0xb7, 0xec, 0x37, 0x2f, 0x74, 0x71, 0x17, 0x9f, 0x1d, 0xe6, 0xf5, 0xc1,
+	0x82, 0xaf, 0x0d, 0x38, 0xd5, 0xb7, 0xe4, 0xf8, 0x5d, 0xb3, 0x55, 0x6a, 0xf2,
+	0x29, 0x6d, 0x4d, 0x28, 0x2f, 0xf7, 0x62, 0x48, 0xf3, 0xd5, 0xc5, 0x03, 0xfd,
+	0x14, 0x1c, 0xe4, 0x36, 0xbd, 0x72, 0x88, 0xa7, 0x6b, 0xdf, 0x8f, 0x02, 0xa1,
+	0xef, 0x50, 0xe1, 0xd8, 0xbf, 0x4f, 0xcf, 0x59, 0xe7, 0x6f, 0x09, 0xd0, 0x1a,
+	0xe6, 0x04, 0x9c, 0x8e, 0xf3, 0xae, 0xe6, 0x1c, 0x51, 0x74, 0x78, 0x26, 0x69,
+	0x06, 0x4a, 0x3b, 0x4a, 0xdc, 0xdb, 0x7b, 0x59, 0x76, 0x44, 0xbb, 0xc7, 0x66,
+	0x0d, 0x67, 0xc5, 0x0e, 0xbd, 0x2b, 0x78, 0x29, 0x33, 0x50, 0xad, 0x0b, 0xb7,
+	0xe6, 0x4d, 0xa7, 0xce, 0xa5, 0x8e, 0x53, 0xd0, 0x93, 0xee, 0x4d, 0x3a, 0x65,
+	0xdf, 0xfc, 0xc3, 0xa6, 0x40, 0xf6, 0x30, 0x98, 0x92, 0xc5, 0xe6, 0xdc, 0xe7,
+	0x69, 0x44, 0xd4, 0x99, 0x9b, 0xad, 0xc3, 0xbf, 0xd7, 0x67, 0xb9, 0x5e, 0x16,
+	0x26, 0x30, 0x61, 0x5e, 0x66, 0x75, 0x4a, 0xfa, 0x7b, 0xc7, 0xa5, 0x88, 0x69,
+	0x40, 0x3f, 0xb7, 0x74, 0x77, 0x1a, 0xcc, 0x75, 0xe1, 0x1e, 0xe9, 0xf2, 0xfb,
+	0x8f, 0x0c, 0xeb, 0x28, 0x42, 0xd8, 0x74, 0x5a, 0x8e, 0x15, 0x07, 0x72, 0x02,
+	0xf8, 0x96, 0x67, 0xdb, 0x23, 0x61, 0x28, 0x33, 0x6a, 0x8d, 0x42, 0xfa, 0x5a,
+	0x70, 0x40, 0xa4, 0x7a, 0xac, 0xd5, 0xa6, 0x4e, 0x02, 0xbb, 0xa1, 0x1f, 0xe2,
+	0x58, 0x5a, 0x7c, 0xc5, 0x25, 0x71, 0x7e, 0x3a, 0xce, 0xcc, 0xcf, 0xc1, 0x18,
+	0xd7, 0x68, 0x5a, 0x85, 0x6d, 0x1c, 0xdb, 0xb9, 0x94, 0xd7, 0x04, 0x9a, 0xeb,
+	0xa5, 0x53, 0x39, 0xe3, 0x75, 0x98, 0x73, 0xb0, 0x9a, 0x47, 0x55, 0xfc, 0x15,
+	0x90, 0x43, 0x60, 0x94, 0x2f, 0xd7, 0x92, 0x38, 0x03, 0x8a, 0x5e, 0x18, 0x5f,
+	0x13, 0x55, 0x89, 0x22, 0x92, 0x47, 0x1a, 0x88, 0x6b, 0x71, 0x61, 0xbe, 0xf3,
+	0x75, 0x77, 0x37, 0xcc, 0x0d, 0x44, 0xc0, 0x0f, 0x7e, 0x7a, 0x54, 0x2e, 0xcd,
+	0x62, 0xaf, 0x97, 0xb3, 0x58, 0xa4, 0x18, 0x50, 0x17, 0x2e, 0x04, 0xa1, 0xb4,
+	0x48, 0x19, 0xb2, 0x6b, 0x7c, 0x1f, 0x93, 0x1c, 0x3c, 0x50, 0x0b, 0x93, 0xf2,
+	0x7a, 0x0e, 0x89, 0xd8, 0xe5, 0xef, 0x6e, 0x88, 0xf4, 0x54, 0xe3, 0x52, 0x8d,
+	0xec, 0x09, 0x51, 0x39, 0xe2, 0x7b, 0x3a, 0x0b, 0x0f, 0x1b, 0xfa, 0x40, 0xf2,
+	0x8f, 0xfd, 0xa9, 0x5f, 0x8a, 0x4e, 0xcb, 0xf9, 0xca, 0x63, 0xc3, 0x60, 0xe4,
+	0xb5, 0xeb, 0x2a, 0xc1, 0x11, 0x3d, 0x92, 0xe7, 0xb1, 0x79, 0xf1, 0x77, 0x08,
+	0x08, 0xef, 0xfa, 0x5f, 0x9e, 0x79, 0xda, 0x24, 0xa1, 0x71, 0xb1, 0xfc, 0x4c,
+	0x26, 0x44, 0x9d, 0x91, 0x89, 0x4d, 0x5e, 0xc2, 0x1c, 0xfb, 0x89, 0xee, 0xa9, 0x39};
+
 static bool _readPlo(Common::SeekableReadStream *s, Common::Array<int> &offsets) {
 	uint32 ploIndex = s->readUint32LE();
 	int64 pos = s->pos();
@@ -301,7 +618,7 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 GGPackEntryReader::GGPackEntryReader() {}
 
 bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
-	if(!pack._entries.contains(entry))
+	if (!pack._entries.contains(entry))
 		return false;
 	GGPackEntry e = pack._entries[entry];
 	pack._s->seek(e.offset);
@@ -337,4 +654,25 @@ bool GGPackEntryReader::seek(int64 offset, int whence) {
 	return _ms.seek(offset, whence);
 }
 
+GGBnutReader::GGBnutReader() {}
+
+bool GGBnutReader::open(Common::SeekableReadStream *s) {
+	_s = s;
+	int64 size = s->size();
+	_cursor = size & 0xFF;
+	return true;
+}
+
+uint32 GGBnutReader::read(void *dataPtr, uint32 dataSize) {
+	byte *p = (byte *)dataPtr;
+	uint32 result = _s->read(dataPtr, dataSize);
+	for (uint32 i = 0; i < result; i++) {
+		p[i] = p[i] ^ bnutKey[_cursor];
+		_cursor = (_cursor + 1) % sizeof(bnutKey);
+	}
+	return result;
+}
+
+bool GGBnutReader::eos() const { return _s->eos(); }
+
 } // namespace Twp
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index d2b5640fb34..a9a6acf0f80 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -130,6 +130,20 @@ private:
 	Common::SeekableReadStream *_s;
 };
 
+class GGBnutReader: public Common::ReadStream {
+public:
+	GGBnutReader();
+
+	bool open(Common::SeekableReadStream *s);
+
+	uint32 read(void *dataPtr, uint32 dataSize) override;
+	bool eos() const override;
+
+private:
+	Common::SeekableReadStream *_s = nullptr;
+	int _cursor = 0;
+};
+
 class GGPackEntryReader: public Common::SeekableReadStream {
 public:
 	GGPackEntryReader();
diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
index 84fbadea75e..77d17a3a659 100644
--- a/engines/twp/ids.cpp
+++ b/engines/twp/ids.cpp
@@ -23,36 +23,84 @@
 
 namespace Twp {
 
+static int gRoomId = START_ROOMID;
+static int gActorId = START_ACTORID;
+static int gObjId = START_OBJECTID;
+static int gSoundDefId = START_SOUNDDEFID;
+static int gSoundId = START_SOUNDID;
+static int gThreadId = START_THREADID;
+static int gCallbackId = START_CALLBACKID;
+static int gLightId = START_LIGHTID;
+
 static inline bool isBetween(int id, int startId, int endId) {
-  return id >= startId && id < endId;
+	return id >= startId && id < endId;
 }
 
 bool isThread(int id) {
-  return isBetween(id, START_THREADID, END_THREADID);
+	return isBetween(id, START_THREADID, END_THREADID);
 }
 
 bool isRoom(int id) {
-  return isBetween(id, START_ROOMID, END_THREADID);
+	return isBetween(id, START_ROOMID, END_THREADID);
 }
 
 bool isActor(int id) {
-  return isBetween(id, START_ACTORID, END_ACTORID);
+	return isBetween(id, START_ACTORID, END_ACTORID);
 }
 
 bool isObject(int id) {
-  return isBetween(id, START_OBJECTID, END_OBJECTID);
+	return isBetween(id, START_OBJECTID, END_OBJECTID);
 }
 
 bool isSound(int id) {
-  return isBetween(id, START_SOUNDID, END_SOUNDID);
+	return isBetween(id, START_SOUNDID, END_SOUNDID);
 }
 
 bool isLight(int id) {
-  return isBetween(id, START_LIGHTID, END_LIGHTID);
+	return isBetween(id, START_LIGHTID, END_LIGHTID);
 }
 
 bool isCallback(int id) {
-  return isBetween(id, START_CALLBACKID, END_CALLBACKID);
+	return isBetween(id, START_CALLBACKID, END_CALLBACKID);
+}
+
+int newRoomId() {
+	return gRoomId++;
+}
+
+int newObjId() {
+	return gObjId++;
+}
+
+int newActorId() {
+	return gActorId++;
+}
+
+int newSoundDefId() {
+	return gSoundDefId++;
+}
+
+int newSoundId() {
+	return gSoundId++;
+}
+
+int newThreadId() {
+	return gThreadId++;
+}
+
+int newCallbackId() {
+	return gCallbackId++;
 }
 
+void setCallbackId(int id) {
+	gCallbackId = id;
+}
+
+int getCallbackId() {
+	return gCallbackId;
+}
+
+int newLightId() {
+	return gLightId++;
+}
 }
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index b64ba0c894f..01cd189d770 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -39,6 +39,24 @@
 #define START_CALLBACKID 8000000
 #define END_CALLBACKID   10000000
 
+#define FAR_LOOK 		8
+#define VERB_WALKTO 	1
+#define VERB_LOOKAT 	2
+#define VERB_TALKTO 	3
+#define VERB_PICKUP 	4
+#define VERB_OPEN 		5
+#define VERB_CLOSE 		6
+#define VERB_PUSH 		7
+#define VERB_PULL 		8
+#define VERB_GIVE 		9
+#define VERB_USE 		10
+#define VERB_DIALOG 	13
+
+#define GONE 			4
+#define USE_WITH 		2
+#define USE_ON 			4
+#define USE_IN 			32
+
 namespace Twp {
 
 bool isThread(int id);
@@ -49,6 +67,17 @@ bool isSound(int id);
 bool isLight(int id);
 bool isCallback(int id);
 
+int newRoomId();
+int newObjId();
+int newActorId();
+int newSoundDefId();
+int newSoundId();
+int newThreadId();
+int newCallbackId();
+int newLightId();
+void setCallbackId(int id);
+int getCallbackId();
+
 }
 
 #endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index cf2e5857a7a..584058589e6 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -30,14 +30,17 @@ namespace Twp {
 static int gObjectId = START_OBJECTID;
 
 Object::Object()
-	: _state(-1),
-	  _talkOffset(0, 90) {
+	: _talkOffset(0, 90) {
 	_node = new Node("newObj");
 	_nodeAnim = new Anim(this);
 	_node->addChild(_nodeAnim);
 	sq_resetobject(&_table);
 }
 
+Object::Object(HSQOBJECT o, const Common::String &key)
+	: _talkOffset(0, 90), _table(o), _key(key) {
+}
+
 Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
 	Object *obj = new Object();
 	obj->_temporary = true;
@@ -53,7 +56,7 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	// assign an id
 	setId(obj->_table, gObjectId++);
 	Common::String name = frames.size() > 0 ? frames[0] : "noname";
-	setf(obj->_table, "name", name);
+	sqsetf(obj->_table, "name", name);
 	obj->_key = name;
 	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 
@@ -69,7 +72,7 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 		obj->_anims.push_back(objAnim);
 	}
 
-	obj->_node->_zOrder = 1;
+	obj->_node->setZSort(1);
 	layer(0)->_objects.push_back(obj);
 	layer(0)->_node->addChild(obj->_node);
 	obj->_layer = layer(0);
@@ -129,24 +132,10 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 
 int Object::getId() {
 	SQInteger result = 0;
-	getf(_table, "_id", result);
+	sqgetf(_table, "_id", result);
 	return (int)result;
 }
 
-// Changes the `state` of an object, although this can just be a internal state,
-//
-// it is typically used to change the object's image as it moves from it's current state to another.
-// Behind the scenes, states as just simple ints. State0, State1, etc.
-// Symbols like `CLOSED` and `OPEN` and just pre-defined to be 0 or 1.
-// State 0 is assumed to be the natural state of the object, which is why `OPEN` is 1 and `CLOSED` is 0 and not the other way around.
-// This can be a little confusing at first.
-// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
-// `GONE` is a unique in that setting an object to `GONE` both sets its graphical state to 1, and makes it untouchable.
-// Once an object is set to `GONE`, if you want to make it visible and touchable again, you have to set both:
-//
-// .. code-block:: Squirrel
-// objectState(coin, HERE)
-// objectTouchable(coin, YES)
 void Object::setState(int state, bool instant) {
 	play(state, false, instant);
 	_state = state;
@@ -253,4 +242,125 @@ Common::String Object::suffix() const {
 	}
 }
 
+void Object::setPop(int count) {
+	_popCount = count;
+	_popElapsed = 0.f;
+}
+
+float Object::popScale() const {
+	return 0.5f + 0.5f * sin(-M_PI / 2.f + _popElapsed * 4.f * M_PI);
+}
+
+int Object::defaultVerbId() {
+	int result = VERB_LOOKAT;
+	if (sqrawexists(_table, "defaultVerb"))
+		sqgetf(_table, "defaultVerb", result);
+	else if (isActor(getId())) {
+		result = sqrawexists(_table, "verbTalkTo") ? VERB_TALKTO : VERB_WALKTO;
+	}
+	return result;
+}
+
+Math::Vector2d Object::getUsePos() {
+	return isActor(getId()) ? _node->getPos() + _node->getOffset() : _node->getPos() + _node->getOffset() + _usePos;
+}
+
+bool Object::touchable() {
+	if (_objType == otNone) {
+		if (_state == GONE) {
+			return false;
+		} else if (_node && !_node->isVisible()) {
+			return false;
+		} else if (sqrawexists(_table, "_touchable")) {
+			bool result;
+			sqgetf(_table, "_touchable", result);
+			return result;
+		} else if (sqrawexists(_table, "initTouchable")) {
+			bool result;
+			sqgetf(_table, "initTouchable", result);
+			return result;
+		} else {
+			return true;
+		}
+	}
+	return false;
+}
+
+void Object::setTouchable(bool value) {
+	if (sqrawexists(_table, "_touchable"))
+		sqsetf(_table, "_touchable", value);
+	else
+		sqnewf(_table, "_touchable", value);
+}
+
+void Object::setIcon(int fps, const Common::StringArray &icons) {
+	_icons = icons;
+	_iconFps = fps;
+	_iconIndex = 0;
+	_iconElapsed = 0.f;
+}
+
+void Object::setIcon(const Common::String &icon) {
+	Common::StringArray icons;
+	icons.push_back(icon);
+	setIcon(0, icons);
+	sqsetf(_table, "icon", icon);
+}
+
+Common::String Object::getIcon() {
+	if (_icons.size() > 0)
+		return _icons[_iconIndex];
+	HSQOBJECT iconTable;
+	sqgetf(_table, "icon", iconTable);
+	if (iconTable._type == OT_NULL) {
+		warning("object table is null");
+		return "";
+	}
+	if (iconTable._type == OT_STRING) {
+		Common::String result = sq_objtostring(&iconTable);
+		setIcon(result);
+		return result;
+	}
+	if (iconTable._type == OT_ARRAY) {
+		int i = 0;
+		int fps = 0;
+		Common::StringArray icons;
+		sqgetitems(iconTable, [&](HSQOBJECT &item) {
+			if (i == 0) {
+				fps = sq_objtointeger(&item);
+			} else {
+				Common::String icon = sq_objtostring(&item);
+				icons.push_back(icon);
+			}
+			i++;
+		});
+		setIcon(fps, icons);
+		return getIcon();
+	}
+	return "";
+}
+
+int Object::getFlags() {
+	int result = 0;
+	if (sqrawexists(_table, "flags"))
+		sqgetf(_table, "flags", result);
+	return result;
+}
+
+void Object::setRoom(Room *room) {
+	if (_room != room) {
+		stopObjectMotors();
+		_room = room;
+	}
+}
+
+void Object::delObject() {
+	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
+    _node->getParent()->removeChild(_node);
+}
+
+void Object::stopObjectMotors() {
+	debug("TODO: stopObjectMotors");
+}
+
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index da4d2e3894f..84ce1742c03 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -79,17 +79,55 @@ class Layer;
 class Object {
 public:
 	Object();
+	Object(HSQOBJECT o, const Common::String& key);
 
 	int getId();
 
+	// Changes the `state` of an object, although this can just be a internal state,
+	//
+	// it is typically used to change the object's image as it moves from it's current state to another.
+	// Behind the scenes, states as just simple ints. State0, State1, etc.
+	// Symbols like `CLOSED` and `OPEN` and just pre-defined to be 0 or 1.
+	// State 0 is assumed to be the natural state of the object, which is why `OPEN` is 1 and `CLOSED` is 0 and not the other way around.
+	// This can be a little confusing at first.
+	// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
+	// `GONE` is a unique in that setting an object to `GONE` both sets its graphical state to 1, and makes it untouchable.
+	// Once an object is set to `GONE`, if you want to make it visible and touchable again, you have to set both:
+	//
+	// .. code-block:: Squirrel
+	// objectState(coin, HERE)
+	// objectTouchable(coin, YES)
 	void setState(int state, bool instant = false);
+	bool touchable();
+	void setTouchable(bool value);
+
 	void play(int state, bool loop = false, bool instant = false);
 	// Plays an animation specified by the `state`.
 	void play(const Common::String &state, bool loop = false, bool instant = false);
+
 	void showLayer(const Common::String &layer, bool visible);
 	Facing getFacing() const;
 	void trig(const Common::String &name);
 
+	void setPop(int count);
+	int getPop() const { return _popCount; }
+	float popScale() const;
+
+	int defaultVerbId();
+
+	Math::Vector2d getUsePos();
+
+	void setIcon(int fps, const Common::StringArray& icons);
+	void setIcon(const Common::String& icon);
+	Common::String getIcon();
+
+	int getFlags();
+
+	void setRoom(Room* room);
+
+	void delObject();
+	void stopObjectMotors();
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
@@ -98,30 +136,52 @@ private:
 public:
 	HSQOBJECT _table;
 	Common::String _name;
-	Common::String _sheet;
-	Common::String _key; // key used to identify this object by script
-	int _state;
-	Math::Vector2d _usePos;
-	Direction _useDir;
+	Common::String _parent;
+	Common::String _sheet;								// Spritesheet to use when a sprite is displayed in the room: "raw" means raw texture, empty string means use room texture
+	Common::String _key;								// key used to identify this object by script
+	Common::String _costumeName, _costumeSheet;
+	int _state = -1;
+	Math::Vector2d _usePos;								// use position
+	Direction _useDir = dNone;
 	Common::Rect _hotspot;
-	ObjectType _objType;
-	Room *_room;
+	ObjectType _objType = otNone;
+	Room *_room = nullptr;
 	Common::Array<ObjectAnimation> _anims;
-	bool _temporary;
-	bool _touchable;
-	Node *_node;
-	Anim *_nodeAnim;
-	Layer *_layer;
+	bool _temporary = false;
+	bool _touchable = false;
+	Node *_node = nullptr;
+	Node *_sayNode = nullptr;
+	Anim *_nodeAnim = nullptr;
+	Layer *_layer = nullptr;
 	Common::StringArray _hiddenLayers;
 	Common::String _animName;
-	int _animFlags;
-	bool _animLoop;
+	int _animFlags = 0;
+	bool _animLoop = false;
 	Common::HashMap<Facing, Facing, Common::Hash<int> > _facingMap;
-	Facing _facing;
-	int _facingLockValue;
-	float _fps;
+	Facing _facing = FACE_FRONT;
+	int _facingLockValue = 0;
+	float _fps = 0.f;
 	Common::HashMap<int, Trigger *> _triggers;
 	Math::Vector2d _talkOffset;
+	Math::Vector2d _walkSpeed;
+	bool _triggerActive = false;
+	bool _useWalkboxes = false;
+	float _volume = 1.f;
+	Color _talkColor;
+	Common::HashMap<Common::String, Common::String> _animNames;
+	bool _lit = false;
+	Object* _owner = nullptr;
+	Common::Array<Object*> _inventory;
+	int _inventoryOffset = 0;
+	Common::StringArray _icons;
+	int _iconFps = 0;
+	int _iconIndex = 0;
+	float _iconElapsed  = 0.f;
+	HSQOBJECT _enter, _leave;
+	int _dependentState = 0;
+    Object* _dependentObj;
+    float _popElapsed = 0.f;
+    int _popCount = 0;
 };
 
 } // namespace Twp
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 74d0c92897a..26107c4080e 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -23,6 +23,7 @@
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/object.h"
+#include "twp/room.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
@@ -54,7 +55,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	// get sheet parameter if any
 	if (numArgs == 3) {
-		if (SQ_FAILED(get(v, 2, sheet)))
+		if (SQ_FAILED(sqget(v, 2, sheet)))
 			return sq_throwerror(v, "failed to get sheet");
 		framesIndex = 3;
 	}
@@ -64,7 +65,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 		switch (sq_gettype(v, framesIndex)) {
 		case OT_STRING: {
 			Common::String frame;
-			get(v, framesIndex, frame);
+			sqget(v, framesIndex, frame);
 			frames.push_back(frame);
 		} break;
 		case OT_ARRAY:
@@ -89,17 +90,17 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 	// Valid values for verticalAlign are ALIGN_TOP|ALIGN_BOTTOM. Valid values for horizonalAlign are ALIGN_LEFT|ALIGN_CENTER|ALIGN_RIGHT.
 	// If the optional horiztonalWidth parameter is present, it will wrap the text to that width.
 	const SQChar *fontName;
-	if (SQ_FAILED(get(v, 2, fontName)))
+	if (SQ_FAILED(sqget(v, 2, fontName)))
 		return sq_throwerror(v, "failed to get fontName");
 	const SQChar *text;
-	if (SQ_FAILED(get(v, 3, text)))
+	if (SQ_FAILED(sqget(v, 3, text)))
 		return sq_throwerror(v, "failed to get text");
 	TextHAlignment thAlign = thCenter;
 	TextVAlignment tvAlign = tvCenter;
 	float maxWidth = 0.0f;
 	if (sq_gettop(v) == 4) {
 		int align;
-		if (SQ_FAILED(get(v, 4, align)))
+		if (SQ_FAILED(sqget(v, 4, align)))
 			return sq_throwerror(v, "failed to get align");
 		uint64 hAlign = align & 0x0000000070000000;
 		uint64 vAlign = align & 0xFFFFFFFFA1000000;
@@ -125,7 +126,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 	}
 	debug("Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
 	Object *obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
-	push(v, obj->_table);
+	sqpush(v, obj->_table);
 	return 1;
 }
 
@@ -455,11 +456,11 @@ static SQInteger objectAt(HSQUIRRELVM v) {
 	sq_getstackobj(v, 2, &o);
 
 	SQInteger id;
-	getf(o, "_id", id);
+	sqgetf(o, "_id", id);
 
 	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
 		SQObjectPtr id2;
-		_table(o->_table)->Get(sqobj(v, "_id"), id2);
+		_table(o->_table)->Get(sqtoobj(v, "_id"), id2);
 		return id == _integer(id2);
 	});
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 5ed02d459de..157ebfe8e94 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -164,9 +164,23 @@ static Scaling parseScaling(const Common::JSONArray &jScalings) {
 	return result;
 }
 
+Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
+	setId(_table, newRoomId());
+	_name = name;
+	_scene = new Scene();
+}
+
+Room::~Room() {
+	for (int i = 0; i < _layers.size(); i++) {
+		delete _layers[i];
+	}
+	delete _scene;
+}
+
 void Room::load(Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
+	debug("Room: %s", value->stringify().c_str());
 	const Common::JSONObject &jRoom = value->asObject();
 
 	_name = jRoom["name"]->asString();
@@ -188,10 +202,7 @@ void Room::load(Common::SeekableReadStream &s) {
 	}
 
 	{
-		Layer *layer = new Layer();
-		layer->_names.push_back(backNames);
-		layer->_zsort = 0;
-		layer->_parallax = Math::Vector2d(1, 1);
+		Layer *layer = new Layer(backNames, Math::Vector2d(1, 1), 0);
 		_layers.push_back(layer);
 	}
 
@@ -199,18 +210,20 @@ void Room::load(Common::SeekableReadStream &s) {
 	if (jRoom.contains("layers")) {
 		const Common::JSONArray &jLayers = jRoom["layers"]->asArray();
 		for (int i = 0; i < jLayers.size(); i++) {
-			Layer *layer = new Layer();
+			Common::StringArray names;
 			const Common::JSONObject &jLayer = jLayers[i]->asObject();
 			if (jLayer["name"]->isArray()) {
 				const Common::JSONArray &jNames = jLayer["name"]->asArray();
 				for (int j = 0; j < jNames.size(); j++) {
-					layer->_names.push_back(jNames[j]->asString());
+					names.push_back(jNames[j]->asString());
 				}
 			} else if (jLayer["name"]->isString()) {
-				layer->_names.push_back(jLayer["name"]->asString());
+				names.push_back(jLayer["name"]->asString());
 			}
-			layer->_parallax = parseParallax(*jLayer["parallax"]);
-			layer->_zsort = jLayer["zsort"]->asIntegerNumber();
+			Math::Vector2d parallax = parseParallax(*jLayer["parallax"]);
+			int zsort = jLayer["zsort"]->asIntegerNumber();
+
+			Layer *layer = new Layer(names, parallax, zsort);
 			_layers.push_back(layer);
 		}
 	}
@@ -233,31 +246,31 @@ void Room::load(Common::SeekableReadStream &s) {
 		const Common::JSONArray &jobjects = jRoom["objects"]->asArray();
 		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
 			const Common::JSONObject &jObject = (*it)->asObject();
-			Object obj;
-			obj._state = -1;
-			Node *objNode = new Node(obj._key);
-			objNode->_pos = Math::Vector2d(parseVec2(jObject["pos"]->asString()));
-			objNode->_zOrder = jObject["zsort"]->asIntegerNumber();
-			obj._node = objNode;
-			obj._nodeAnim = new Anim(&obj);
-			obj._node->addChild(obj._nodeAnim);
-			obj._key = jObject["name"]->asString();
-			obj._usePos = parseVec2(jObject["usepos"]->asString());
+			Object *obj = new Object();
+			obj->_state = -1;
+			Node *objNode = new Node(obj->_key);
+			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
+			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
+			obj->_node = objNode;
+			obj->_nodeAnim = new Anim(obj);
+			obj->_node->addChild(obj->_nodeAnim);
+			obj->_key = jObject["name"]->asString();
+			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
-				obj._useDir = parseUseDir(jObject["usedir"]->asString());
+				obj->_useDir = parseUseDir(jObject["usedir"]->asString());
 			} else {
-				obj._useDir = dNone;
+				obj->_useDir = dNone;
 			}
-			obj._hotspot = parseRect(jObject["hotspot"]->asString());
-			obj._objType = toObjectType(jObject);
+			obj->_hotspot = parseRect(jObject["hotspot"]->asString());
+			obj->_objType = toObjectType(jObject);
 			if (jObject.contains("parent"))
 				jObject["parent"]->asString();
-			obj._room = this;
+			obj->_room = this;
 			if (jObject.contains("animations")) {
-				parseObjectAnimations(jObject["animations"]->asArray(), obj._anims);
+				parseObjectAnimations(jObject["animations"]->asArray(), obj->_anims);
 			}
-			obj._layer = layer(0);
-			layer(0)->_objects.push_back(&obj);
+			obj->_layer = layer(0);
+			layer(0)->_objects.push_back(obj);
 		}
 	}
 
@@ -303,6 +316,30 @@ Math::Vector2d Room::getScreenSize() {
 	}
 }
 
+Object *Room::getObj(const Common::String &key) {
+	for (int i = 0; i < _layers.size(); i++) {
+		Layer *layer = _layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[j];
+			if (obj->_key == key)
+				return obj;
+		}
+	}
+	return nullptr;
+}
+
+Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
+	_names.push_back(name);
+	_parallax = parallax;
+	_zsort = zsort;
+}
+
+Layer::Layer(const Common::StringArray &name, Math::Vector2d parallax, int zsort) {
+	_names.push_back(name);
+	_parallax = parallax;
+	_zsort = zsort;
+}
+
 Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 334329bfc0a..a30cf500c7b 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -35,12 +35,16 @@ class Node;
 class Object;
 
 class Layer {
+public:
+	Layer(const Common::String &name, Math::Vector2d parallax, int zsort);
+	Layer(const Common::StringArray &names, Math::Vector2d parallax, int zsort);
+
 public:
 	Common::Array<Common::String> _names;
 	Common::Array<Object *> _objects;
 	Math::Vector2d _parallax;
-	int _zsort;
-	Node *_node;
+	int _zsort = 0;
+	Node *_node = nullptr;
 };
 
 // Represents an area where an actor can or cannot walk
@@ -67,26 +71,30 @@ struct Scaling {
 };
 
 struct Light {
-    Color color;
-    Math::Vector2d pos;
-    float brightness;     // light brightness 1.0f...100.f
-    float coneDirection;  // cone direction 0...360.f
-    float coneAngle;      // cone angle 0...360.f
-    float coneFalloff;    // cone falloff 0.f...1.0f
-    float cutOffRadius;   // cutoff raduus
-    float halfRadius;     // cone half radius 0.0f...1.0f
-    bool on;
-    int id;
+	Color color;
+	Math::Vector2d pos;
+	float brightness;    // light brightness 1.0f...100.f
+	float coneDirection; // cone direction 0...360.f
+	float coneAngle;     // cone angle 0...360.f
+	float coneFalloff;   // cone falloff 0.f...1.0f
+	float cutOffRadius;  // cutoff raduus
+	float halfRadius;    // cone half radius 0.0f...1.0f
+	bool on;
+	int id;
 };
 
 struct Lights {
-	int _numLights;			// Number of lights
+	int _numLights; // Number of lights
 	Light _lights[50];
-	Color _ambientLight;	// Ambient light color
+	Color _ambientLight; // Ambient light color
 };
 
+class Scene;
 class Room {
 public:
+	Room(const Common::String &name, HSQOBJECT &table);
+	~Room();
+
 	void load(Common::SeekableReadStream &s);
 
 	Object *createObject(const Common::String &sheet, const Common::Array<Common::String> &frames);
@@ -96,23 +104,26 @@ public:
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 
 	Layer *layer(int zsort);
+	Object *getObj(const Common::String& key);
 
 public:
-	Common::String _name;               	// Name of the room
-	Common::String _sheet;              	// Name of the spritesheet to use
-	Math::Vector2d _roomSize;           	// Size of the room
-	int _fullscreen;                    	// Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
-	int _height;                        	// Height of the room (what else ?)
-	Common::Array<Layer *> _layers;     	// Parallax layers of a room
-	Common::Array<Walkbox> _walkboxes;  	// Represents the areas where an actor can or cannot walk
-	Common::Array<Scaling> _scalings;   	// Defines the scaling of the actor in the room
-	Scaling _scaling;                   	// Defines the scaling of the actor in the room
-	HSQOBJECT _table;						// Squirrel table representing this room
-	bool _entering;							// Indicates whether or not an actor is entering this room
-	Lights _lights;							// Lights of the room
+	Common::String _name;              // Name of the room
+	Common::String _sheet;             // Name of the spritesheet to use
+	Math::Vector2d _roomSize;          // Size of the room
+	int _fullscreen = 0;               // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
+	int _height = 0;                   // Height of the room (what else ?)
+	Common::Array<Layer *> _layers;    // Parallax layers of a room
+	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
+	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
+	Scaling _scaling;                  // Defines the scaling of the actor in the room
+	HSQOBJECT _table;                  // Squirrel table representing this room
+	bool _entering = false;            // Indicates whether or not an actor is entering this room
+	Lights _lights;                    // Lights of the room
 	Common::Array<Walkbox> _mergedPolygon;
-	Common::Array<Object*> _triggers;		// Triggers currently enabled in the room
-	bool _pseudo;
+	Common::Array<Object *> _triggers; // Triggers currently enabled in the room
+	bool _pseudo = false;
+	Common::Array<Object *> _objects;
+	Scene* _scene = nullptr;
 };
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index f6d2da5be78..e0b23fccf51 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -159,8 +159,8 @@ void Node::setSize(Math::Vector2d size) {
 	}
 }
 
-static bool cmpNodes(const Node *x, const Node *y) {
-	return x->getZSort() < y->getZSort();
+static int cmpNodes(const Node *x, const Node *y) {
+	return y->getZSort() < x->getZSort();
 }
 
 void Node::draw(Math::Matrix4 parent) {
@@ -216,10 +216,10 @@ void OverlayNode::drawCore(Math::Matrix4 trsf) {
 	gfx.drawQuad(gfx.camera(), _ovlColor);
 }
 
-ParallaxNode::ParallaxNode(const Math::Vector2d &parallax, const Common::String &textture, const Common::Array<SpriteSheetFrame> &frames)
+ParallaxNode::ParallaxNode(const Math::Vector2d &parallax, const Common::String &sheet, const Common::StringArray &frames)
 	: Node("parallax"),
 	  _parallax(parallax),
-	  _texture(textture),
+	  _sheet(sheet),
 	  _frames(frames) {
 }
 
@@ -234,26 +234,25 @@ Math::Matrix4 ParallaxNode::getTrsf(Math::Matrix4 parentTrsf) {
 	return trsf;
 }
 
-static Common::Rect div(const Common::Rect &r, const Math::Vector2d &v) {
-	return Common::Rect(r.left / v.getX(), r.top / v.getY(), r.width() / v.getX(), r.height() / v.getY());
-}
-
 void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	Gfx &gfx = g_engine->getGfx();
-	Texture *texture = g_engine->_resManager.texture(_texture);
+	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 	Math::Matrix4 t = trsf;
+	float x = 0.f;
 	for (int i = 0; i < _frames.size(); i++) {
-		const SpriteSheetFrame &frame = _frames[i];
+		const SpriteSheetFrame &frame = sheet->frameTable[_frames[i]];
 		Math::Matrix4 myTrsf = t;
-		myTrsf.translate(Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
-		gfx.drawSprite(div(frame.frame, Math::Vector2d(texture->width, texture->height)), *texture, getColor(), myTrsf);
+		myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
+		gfx.drawSprite(frame.frame, *texture, getColor(), myTrsf);
 		t = trsf;
-		t.translate(Math::Vector3d(frame.frame.width(), 0.0f, 0.0f));
+		x += frame.frame.width();
 	}
 }
 
 Anim::Anim(Object *obj)
 	: Node("anim") {
+	_obj = obj;
 	_zOrder = 1000;
 }
 
@@ -265,7 +264,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_anim = anim;
 	_disabled = false;
 	setName(anim->name);
-	_sheet = anim->sheet;
+	_sheet = anim->sheet.size() == 0 ? _obj->_room->_sheet : anim->sheet;
 	_frames = anim->frames;
 	_frameIndex = instant && _frames.size() > 0 ? _frames.size() - 1 : 0;
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
@@ -282,53 +281,71 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 }
 
 void Anim::trigSound() {
-  if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
-    const Common::String& trigger = _anim->triggers[_frameIndex];
-    if (trigger.size() > 0) {
-      _obj->trig(trigger);
+	if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
+		const Common::String &trigger = _anim->triggers[_frameIndex];
+		if (trigger.size() > 0) {
+			_obj->trig(trigger);
+		}
 	}
-  }
 }
 
 void Anim::update(float elapsed) {
-  if (_anim)
-    setVisible(Common::find(_obj->_hiddenLayers.begin(), _obj->_hiddenLayers.end(), _anim->name)==NULL);
-    if (_instant)
-      disable();
-    else if (_frames.size() != 0) {
-      _elapsed += elapsed;
-      if(_elapsed > _frameDuration) {
-        _elapsed = 0;
-        if (_frameIndex < _frames.size() - 1) {
-          _frameIndex++;
-          trigSound();
-		} else if (_loop) {
-          _frameIndex = 0;
-          trigSound();
-		} else {
-          disable();
+	if (_anim)
+		setVisible(Common::find(_obj->_hiddenLayers.begin(), _obj->_hiddenLayers.end(), _anim->name) == NULL);
+	if (_instant)
+		disable();
+	else if (_frames.size() != 0) {
+		_elapsed += elapsed;
+		if (_elapsed > _frameDuration) {
+			_elapsed = 0;
+			if (_frameIndex < _frames.size() - 1) {
+				_frameIndex++;
+				trigSound();
+			} else if (_loop) {
+				_frameIndex = 0;
+				trigSound();
+			} else {
+				disable();
+			}
 		}
-	  }
-      if(_anim->offsets.size() > 0) {
-        Math::Vector2d off = _frameIndex < _anim->offsets.size()? _anim->offsets[_frameIndex]: Math::Vector2d();
-        if(_obj->getFacing() == FACE_LEFT) {
-          off.setX(-off.getX());
+		if (_anim->offsets.size() > 0) {
+			Math::Vector2d off = _frameIndex < _anim->offsets.size() ? _anim->offsets[_frameIndex] : Math::Vector2d();
+			if (_obj->getFacing() == FACE_LEFT) {
+				off.setX(-off.getX());
+			}
+			_offset = off;
 		}
-        _offset = off;
-	  }
 	} else if (_children.size() != 0) {
-      bool disabled = true;
-	  for (int i = 0; i < _children.size(); i++) {
-		Anim* layer = static_cast<Anim*>(_children[i]);
-        layer->update(elapsed);
-        disabled = disabled && layer->_disabled;
-	  }
-      if(disabled) {
-        disable();
-	  }
+		bool disabled = true;
+		for (int i = 0; i < _children.size(); i++) {
+			Anim *layer = static_cast<Anim *>(_children[i]);
+			layer->update(elapsed);
+			disabled = disabled && layer->_disabled;
+		}
+		if (disabled) {
+			disable();
+		}
 	} else {
-      disable();
+		disable();
+	}
+}
+
+void Anim::drawCore(Math::Matrix4 trsf) {
+	if (_frameIndex < _frames.size()) {
+		const Common::String &frame = _frames[_frameIndex];
+		bool flipX = _obj->getFacing() == FACE_LEFT;
+		SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+		const SpriteSheetFrame &sf = sheet->frameTable[frame];
+		Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+		float x = flipX? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left: 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
+		float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
+		Math::Vector3d pos(int(-x), int(y), 0.f);
+		trsf.translate(pos);
+		g_engine->getGfx().drawSprite(sf.frame, *texture, getColor(), trsf, flipX);
 	}
 }
 
+Scene::Scene() : Node("Scene") {}
+Scene::~Scene() {}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 4eea3df89d3..069e6afa6d7 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -62,6 +62,10 @@ public:
 	// Finds a node in its children and returns its position.
 	int find(Node* other);
 
+	// Gets the local position for this node (relative to its parent)
+	void setPos(const Math::Vector2d& pos) { _pos = pos; }
+	Math::Vector2d getPos() const { return _pos; }
+	Math::Vector2d getOffset() const { return _offset; }
 	// Gets the absolute position for this node.
 	Math::Vector2d getAbsPos() const;
 
@@ -75,6 +79,7 @@ public:
 	void setAlpha(float alpha);
 	Color getAlpha() const { return _color.rgba.a; }
 
+	void setZSort(int zsort) { _zOrder = zsort; }
 	virtual int getZSort() const { return _zOrder; }
 	virtual Math::Vector2d getScale() const { return _scale; }
 
@@ -84,10 +89,6 @@ public:
 
 	void draw(Math::Matrix4 parent = Math::Matrix4());
 
-public:
-	Math::Vector2d _pos;
-	float _zOrder;
-
 protected:
 	virtual void onColorUpdated(Color c) {}
 	virtual void drawCore(Math::Matrix4 trsf) {}
@@ -102,12 +103,14 @@ private:
 
 protected:
 	Common::String _name;
-	Node *_parent;
+	Math::Vector2d _pos;
+	float _zOrder = 0.f;
+	Node *_parent = nullptr;
 	Common::Array<Node *> _children;
 	Math::Vector2d _offset, _shakeOffset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
 	Color _color, _computedColor;
-	bool _visible;
-	float _rotation, _rotationOffset;
+	bool _visible = false;
+	float _rotation = 0.f, _rotationOffset = 0.f;
 };
 
 class OverlayNode final: public Node {
@@ -124,7 +127,7 @@ private:
 
 class ParallaxNode final: public Node {
 public:
-	ParallaxNode(const Math::Vector2d& parallax, const Common::String& texture, const Common::Array<SpriteSheetFrame>& frames);
+	ParallaxNode(const Math::Vector2d& parallax, const Common::String& sheet, const Common::StringArray& frames);
 	virtual ~ParallaxNode();
 
 	Math::Matrix4 getTrsf(Math::Matrix4 parentTrsf) override final;
@@ -134,8 +137,8 @@ protected:
 
 private:
 	Math::Vector2d _parallax;
-	Common::String _texture;
-	Common::Array<SpriteSheetFrame> _frames;
+	Common::String _sheet;
+	Common::StringArray _frames;
 };
 
 struct ObjectAnimation;
@@ -150,6 +153,9 @@ public:
 	void disable() { _disabled = true; }
 	void trigSound();
 
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+
 private:
 	Common::String _sheet;
     const ObjectAnimation* _anim;
@@ -162,6 +168,12 @@ private:
     Object* _obj;
 };
 
+class Scene: public Node {
+public:
+	Scene();
+	virtual ~Scene() final;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index fdb36533019..da93988d5f7 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -20,6 +20,8 @@
  */
 
 #include "twp/squtil.h"
+#include "twp/room.h"
+#include "twp/object.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
@@ -36,27 +38,27 @@
 namespace Twp {
 
 template<>
-void push(HSQUIRRELVM v, int value) {
+void sqpush(HSQUIRRELVM v, int value) {
 	sq_pushinteger(v, value);
 }
 
 template<>
-void push(HSQUIRRELVM v, bool value) {
+void sqpush(HSQUIRRELVM v, bool value) {
 	sq_pushinteger(v, value ? 1 : 0);
 }
 
 template<>
-void push(HSQUIRRELVM v, Common::String value) {
+void sqpush(HSQUIRRELVM v, Common::String value) {
 	sq_pushstring(v, value.c_str(), value.size());
 }
 
 template<>
-void push(HSQUIRRELVM v, HSQOBJECT value) {
+void sqpush(HSQUIRRELVM v, HSQOBJECT value) {
 	sq_pushobject(v, value);
 }
 
 template<>
-HSQOBJECT sqobj(HSQUIRRELVM v, int value) {
+HSQOBJECT sqtoobj(HSQUIRRELVM v, int value) {
 	SQObject o;
 	o._type = OT_INTEGER;
 	o._unVal.nInteger = value;
@@ -64,7 +66,7 @@ HSQOBJECT sqobj(HSQUIRRELVM v, int value) {
 }
 
 template<>
-HSQOBJECT sqobj(HSQUIRRELVM v, const SQChar *value) {
+HSQOBJECT sqtoobj(HSQUIRRELVM v, const SQChar *value) {
 	SQObject o;
 	o._type = OT_STRING;
 	o._unVal.pString = SQString::Create(_ss(v), value);
@@ -72,12 +74,12 @@ HSQOBJECT sqobj(HSQUIRRELVM v, const SQChar *value) {
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, SQInteger &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, SQInteger &value) {
 	return sq_getinteger(v, i, &value);
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, int &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, int &value) {
 	SQInteger itg;
 	SQRESULT result = sq_getinteger(v, i, &itg);
 	value = static_cast<int>(itg);
@@ -85,7 +87,15 @@ SQRESULT get(HSQUIRRELVM v, int i, int &value) {
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, float &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, bool &value) {
+	SQInteger itg;
+	SQRESULT result = sq_getinteger(v, i, &itg);
+	value = itg != 0;
+	return result;
+}
+
+template<>
+SQRESULT sqget(HSQUIRRELVM v, int i, float &value) {
 	SQFloat f;
 	SQRESULT result = sq_getfloat(v, i, &f);
 	value = static_cast<float>(f);
@@ -93,7 +103,7 @@ SQRESULT get(HSQUIRRELVM v, int i, float &value) {
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, Common::String &value) {
 	const SQChar *s;
 	SQRESULT result = sq_getstring(v, i, &s);
 	value = s;
@@ -101,12 +111,12 @@ SQRESULT get(HSQUIRRELVM v, int i, Common::String &value) {
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, const SQChar* &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, const SQChar *&value) {
 	return sq_getstring(v, i, &value);
 }
 
 template<>
-SQRESULT get(HSQUIRRELVM v, int i, HSQOBJECT &value) {
+SQRESULT sqget(HSQUIRRELVM v, int i, HSQOBJECT &value) {
 	return sq_getstackobj(v, i, &value);
 }
 
@@ -130,7 +140,127 @@ SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr) {
 }
 
 void setId(HSQOBJECT &o, int id) {
-	setf(o, "_id", id);
+	sqsetf(o, "_id", id);
+}
+
+bool sqrawexists(HSQOBJECT obj, const Common::String &name) {
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sqpush(v, obj);
+	sq_pushstring(v, name.c_str(), -1);
+	if (SQ_SUCCEEDED(sq_rawget(v, -2))) {
+		SQObjectType oType = sq_gettype(v, -1);
+		sq_settop(v, top);
+		return oType != OT_NULL;
+	}
+	sq_settop(v, top);
+	return false;
+}
+
+void sqsetdelegate(HSQOBJECT obj, HSQOBJECT del) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sqpush(v, obj);
+	sqpush(v, del);
+	sq_setdelegate(v, -2);
+	sq_pop(v, 1);
+}
+
+HSQOBJECT sqrootTbl(HSQUIRRELVM v) {
+	HSQOBJECT result;
+	sq_resetobject(&result);
+	sq_pushroottable(v);
+	sq_getstackobj(v, -1, &result);
+	sq_pop(v, 1);
+	return result;
+}
+
+static int getId(HSQOBJECT table) {
+	SQInteger result = 0;
+	sqgetf(table, "_id", result);
+	return (int)result;
+}
+
+Room *sqroom(HSQOBJECT table) {
+	int id = getId(table);
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		if (getId(room->_table) == id)
+			return room;
+	}
+	return nullptr;
+}
+
+Room *sqroom(HSQUIRRELVM v, int i) {
+	HSQOBJECT table;
+	if (SQ_SUCCEEDED(sqget(v, i, table))) {
+		return sqroom(table);
+	}
+	return nullptr;
+}
+
+Object *sqobj(HSQOBJECT table) {
+	int id = getId(table);
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		for (int j = 0; j < room->_layers.size(); i++) {
+			Layer *layer = room->_layers[i];
+			for (int k = 0; k < layer->_objects.size(); k++) {
+				Object *obj = layer->_objects[i];
+				if (getId(obj->_table) == id)
+					return obj;
+			}
+		}
+	}
+	return nullptr;
+}
+
+Object *sqobj(HSQUIRRELVM v, int i) {
+	HSQOBJECT table;
+	sq_getstackobj(v, i, &table);
+	return sqobj(table);
+}
+
+int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
+	SQInteger top = sq_gettop(v);
+	sqpush(v, obj);
+	sq_pushstring(v, name.c_str(), -1);
+	if (SQ_FAILED(sq_get(v, -2))) {
+		sq_settop(v, top);
+		debug("can't find %s function", name.c_str());
+		return 0;
+	}
+	SQInteger nparams, nfreevars;
+	sq_getclosureinfo(v, -1, &nparams, &nfreevars);
+	debug("%s function found with %lld parameters", name.c_str(), nparams);
+	sq_settop(v, top);
+	return nparams;
+}
+
+static void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name) {
+	sq_pushobject(v, o);
+	sq_pushstring(v, name.c_str(), -1);
+	sq_get(v, -2);
+}
+
+static void sqcall(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, int numArgs, HSQOBJECT *args) {
+	SQInteger top = sq_gettop(v);
+	sqpushfunc(v, o, name);
+
+	sq_pushobject(v, o);
+	for (int i = 0; i < numArgs; i++) {
+		sq_pushobject(v, args[i]);
+	}
+	sq_call(v, 1 + numArgs, SQFalse, SQTrue);
+	sq_settop(v, top);
+}
+
+void sqcall(HSQOBJECT o, const Common::String &name, int numArgs, HSQOBJECT *args) {
+	sqcall(g_engine->getVm(), o, name, numArgs, args);
+}
+
+void sqcall(const Common::String &name, int numArgs, HSQOBJECT *args) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sqcall(v, sqrootTbl(v), name, numArgs, args);
 }
 
 } // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 8cc3f342034..3b3e09e1e92 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -30,42 +30,42 @@
 namespace Twp {
 
 template<typename T>
-HSQOBJECT sqobj(HSQUIRRELVM v, T value);
+HSQOBJECT sqtoobj(HSQUIRRELVM v, T value);
 
 template<typename T>
-void push(HSQUIRRELVM v, T value);
+void sqpush(HSQUIRRELVM v, T value);
 
 template<typename T>
-SQRESULT get(HSQUIRRELVM v, int index, T &value);
+SQRESULT sqget(HSQUIRRELVM v, int index, T &value);
 
 // set field
 template<typename T>
-void setf(HSQOBJECT &o, const Common::String &key, T obj) {
+void sqsetf(HSQOBJECT o, const Common::String &key, T obj) {
 	HSQUIRRELVM v = g_engine->getVm();
 	SQInteger top = sq_gettop(v);
 	sq_pushobject(v, o);
 	sq_pushstring(v, key.c_str(), -1);
-	push(v, obj);
+	sqpush(v, obj);
 	sq_rawset(v, -3);
 	sq_settop(v, top);
 }
 
 template<typename T>
-void getf(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, T &value) {
+void sqgetf(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, T &value) {
 	sq_pushobject(v, o);
 	sq_pushstring(v, name.c_str(), -1);
 	if (SQ_FAILED(sq_get(v, -2)))
 		sq_pop(v, 1);
 	else {
-		get(v, -1, value);
+		sqget(v, -1, value);
 		sq_pop(v, 2);
 	}
 }
 
 template<typename T>
-void getf(HSQOBJECT o, const Common::String &name, T &value) {
+void sqgetf(HSQOBJECT o, const Common::String &name, T &value) {
 	HSQUIRRELVM v = g_engine->getVm();
-	getf(v, o, name, value);
+	sqgetf(v, o, name, value);
 }
 
 void setId(HSQOBJECT &o, int id);
@@ -73,20 +73,62 @@ void setId(HSQOBJECT &o, int id);
 void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
 SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr);
 
-template <typename TFunc>
+template<typename TFunc>
 void sqgetitems(HSQOBJECT o, TFunc func) {
 	HSQUIRRELVM v = g_engine->getVm();
 	sq_pushobject(v, o);
 	sq_pushnull(v);
 	while (SQ_SUCCEEDED(sq_next(v, -2))) {
 		HSQOBJECT obj;
-		get(v, -1, obj);
+		sqget(v, -1, obj);
 		func(obj);
 		sq_pop(v, 2);
 	}
 	sq_pop(v, 2);
 }
 
+template<typename TFunc>
+void sqgetpairs(HSQOBJECT obj, TFunc func) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sq_pushobject(v, obj);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		Common::String key;
+		HSQOBJECT o;
+		sqget(v, -1, o);
+		sqget(v, -2, key);
+		func(key, o);
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 2);
+}
+
+template<typename T>
+void sqnewf(HSQOBJECT o, const Common::String &key, T obj) {
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sq_pushobject(v, o);
+	sq_pushstring(v, key.c_str(), -1);
+	sqpush(v, obj);
+	sq_newslot(v, -3, SQFalse);
+	sq_settop(v, top);
+}
+
+bool sqrawexists(HSQOBJECT obj, const Common::String &name);
+void sqsetdelegate(HSQOBJECT obj, HSQOBJECT del);
+HSQOBJECT sqrootTbl(HSQUIRRELVM v);
+int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String& name);
+void sqcall(const Common::String &name, int numArgs = 0, HSQOBJECT *args = nullptr);
+void sqcall(HSQOBJECT o, const Common::String& name, int numArgs = 0, HSQOBJECT* args = nullptr);
+
+class Room;
+class Object;
+
+Room *sqroom(HSQOBJECT table);
+Room *sqroom(HSQUIRRELVM v, int i);
+Object *sqobj(HSQOBJECT table);
+Object *sqobj(HSQUIRRELVM v, int i);
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index df5cc49e238..b1ae1d47d7a 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -40,7 +40,7 @@ namespace Twp {
 
 static Thread *thread(HSQUIRRELVM v) {
 	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](Thread *t) {
-		return t->threadObj._unVal.pThread == v;
+		return t->_threadObj._unVal.pThread == v;
 	});
 }
 
@@ -49,49 +49,49 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	SQInteger size = sq_gettop(v);
 
 	Thread *t = new Thread();
-	t->global = global;
+	t->_global = global;
 
 	static uint64 gThreadId = 300000;
 	sq_newtable(v);
 	sq_pushstring(v, _SC("_id"), -1);
 	sq_pushinteger(v, gThreadId++);
 	sq_newslot(v, -3, SQFalse);
-	sq_getstackobj(v, -1, &t->obj);
-	sq_addref(vm, &t->obj);
+	sq_getstackobj(v, -1, &t->_obj);
+	sq_addref(vm, &t->_obj);
 	sq_pop(v, 1);
 
-	sq_resetobject(&t->envObj);
-	if (SQ_FAILED(sq_getstackobj(v, 1, &t->envObj)))
+	sq_resetobject(&t->_envObj);
+	if (SQ_FAILED(sq_getstackobj(v, 1, &t->_envObj)))
 		return sq_throwerror(v, "Couldn't get environment from stack");
-	sq_addref(vm, &t->envObj);
+	sq_addref(vm, &t->_envObj);
 
 	// create thread and store it on the stack
 	sq_newthread(vm, 1024);
-	sq_resetobject(&t->threadObj);
-	if (SQ_FAILED(sq_getstackobj(vm, -1, &t->threadObj)))
+	sq_resetobject(&t->_threadObj);
+	if (SQ_FAILED(sq_getstackobj(vm, -1, &t->_threadObj)))
 		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(vm, &t->threadObj);
+	sq_addref(vm, &t->_threadObj);
 
 	for (int i = 0; i < size - 2; i++) {
 		HSQOBJECT arg;
 		sq_resetobject(&arg);
 		if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
 			return sq_throwerror(v, "Couldn't get coroutine args from stack");
-		t->args.push_back(arg);
+		t->_args.push_back(arg);
 		sq_addref(vm, &arg);
 	}
 
 	// get the closure
-	sq_resetobject(&t->closureObj);
-	if (SQ_FAILED(sq_getstackobj(v, 2, &t->closureObj)))
+	sq_resetobject(&t->_closureObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &t->_closureObj)))
 		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(vm, &t->closureObj);
+	sq_addref(vm, &t->_closureObj);
 
 	const SQChar *name = nullptr;
 	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
 		sq_getstring(v, -1, &name);
 
-	t->name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->closureObj)->_function->_sourcename), _closure(t->closureObj)->_function->_lineinfos->_line);
+	t->_name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->_closureObj)->_function->_sourcename), _closure(t->_closureObj)->_function->_lineinfos->_line);
 	sq_pop(vm, 1);
 	if (name)
 		sq_pop(v, 1); // pop name
@@ -99,13 +99,13 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 
 	g_engine->_threads.push_back(t);
 
-	debug("create thread %s", t->name.c_str());
+	debug("create thread %s", t->_name.c_str());
 
 	// call the closure in the thread
 	if (!t->call())
 		return sq_throwerror(v, "call failed");
 
-	sq_pushobject(v, t->obj);
+	sq_pushobject(v, t->_obj);
 	return 1;
 }
 
@@ -165,9 +165,9 @@ static SQInteger breaktime(HSQUIRRELVM v) {
 	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
 		return sq_throwerror(v, "failed to get time");
 	if (time == 0.f)
-		return breakfunc(v, [](Thread &t) { t.numFrames = 1; });
+		return breakfunc(v, [](Thread &t) { t._numFrames = 1; });
 	else
-		return breakfunc(v, [&](Thread &t) { t.waitTime = time; });
+		return breakfunc(v, [&](Thread &t) { t._waitTime = time; });
 }
 
 static SQInteger breakwhileanimating(HSQUIRRELVM v) {
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index e280b99e743..d3a97629c18 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -22,31 +22,30 @@
 #include "twp/twp.h"
 #include "twp/thread.h"
 
-namespace Twp{
+namespace Twp {
 
-Thread::Thread() : paused(false), waitTime(0), numFrames(0), stopRequest(false) {
-}
+Thread::Thread() {}
 
 Thread::~Thread() {
 	HSQUIRRELVM v = g_engine->getVm();
-	for (int i = 0; i < args.size(); i++) {
-		sq_release(v, &args[i]);
+	for (int i = 0; i < _args.size(); i++) {
+		sq_release(v, &_args[i]);
 	}
-	sq_release(v, &threadObj);
-	sq_release(v, &envObj);
-	sq_release(v, &closureObj);
+	sq_release(v, &_threadObj);
+	sq_release(v, &_envObj);
+	sq_release(v, &_closureObj);
 }
 
 bool Thread::call() {
-	HSQUIRRELVM v = threadObj._unVal.pThread;
+	HSQUIRRELVM v = _threadObj._unVal.pThread;
 	// call the closure in the thread
 	SQInteger top = sq_gettop(v);
-	sq_pushobject(v, closureObj);
-	sq_pushobject(v, envObj);
-	for (int i = 0; i < args.size(); i++) {
-		sq_pushobject(v, args[i]);
+	sq_pushobject(v, _closureObj);
+	sq_pushobject(v, _envObj);
+	for (int i = 0; i < _args.size(); i++) {
+		sq_pushobject(v, _args[i]);
 	}
-	if (SQ_FAILED(sq_call(v, 1 + args.size(), SQFalse, SQTrue))) {
+	if (SQ_FAILED(sq_call(v, 1 + _args.size(), SQFalse, SQTrue))) {
 		sq_settop(v, top);
 		return false;
 	}
@@ -54,42 +53,47 @@ bool Thread::call() {
 }
 
 bool Thread::isDead() {
-	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
-	return stopRequest || state == 0;
+	SQInteger state = sq_getvmstate(_threadObj._unVal.pThread);
+	return _stopRequest || state == 0;
 }
 
 bool Thread::isSuspended() {
-	SQInteger state = sq_getvmstate(threadObj._unVal.pThread);
+	SQInteger state = sq_getvmstate(_threadObj._unVal.pThread);
 	return state != 1;
 }
 
 void Thread::suspend() {
 	// TODO: pauseable
 	if (!isSuspended()) {
-		sq_suspendvm(threadObj._unVal.pThread);
+		sq_suspendvm(_threadObj._unVal.pThread);
 	}
 }
 
 void Thread::resume() {
 	if (!isDead() && isSuspended()) {
-		sq_wakeupvm(threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
+		sq_wakeupvm(_threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
 	}
 }
 
 bool Thread::update(float elapsed) {
-	if (paused) {
-	} else if (waitTime > 0) {
-		waitTime -= elapsed;
-		if (waitTime <= 0) {
-			waitTime = 0;
+	if (_paused) {
+	} else if (_waitTime > 0) {
+		_waitTime -= elapsed;
+		if (_waitTime <= 0) {
+			_waitTime = 0;
 			resume();
 		}
-	} else if (numFrames > 0) {
-		numFrames -= 1;
-		numFrames = 0;
+	} else if (_numFrames > 0) {
+		_numFrames -= 1;
+		_numFrames = 0;
 		resume();
 	}
 	return isDead();
 }
 
+void Thread::stop() {
+	_stopRequest = true;
+	suspend();
 }
+
+} // namespace Twp
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index f80f77e1ac4..386873dd499 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -39,17 +39,18 @@ public:
 	void resume();
 	bool isDead();
 	bool isSuspended();
+	void stop();
 
 public:
-	uint64 id;
-	Common::String name;
-    bool global;
-	HSQOBJECT obj, threadObj, envObj, closureObj;
-	Common::Array<HSQOBJECT> args;
-	bool paused;
-	float waitTime;
-	int numFrames;
-	bool stopRequest;
+	uint64 _id=0;
+	Common::String _name;
+    bool _global = false;
+	HSQOBJECT _obj, _threadObj, _envObj, _closureObj;
+	Common::Array<HSQOBJECT> _args;
+	bool _paused=false;
+	float _waitTime = 0.f;
+	int _numFrames = 0;
+	bool _stopRequest = false;
 };
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index aad3e3dd6e0..b5050e74c22 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -37,18 +37,18 @@
 #include "twp/lighting.h"
 #include "twp/font.h"
 #include "twp/thread.h"
+#include "twp/scenegraph.h"
+#include "twp/squtil.h"
+#include "twp/object.h"
+#include "twp/ids.h"
 
 namespace Twp {
 
-#define SCREEN_WIDTH	1280
-#define SCREEN_HEIGHT	720
+#define SCREEN_WIDTH 1280
+#define SCREEN_HEIGHT 720
 
 TwpEngine *g_engine;
 
-static bool cmpLayer(const Layer *l1, const Layer *l2) {
-	return l1->_zsort > l2->_zsort;
-}
-
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
 	  _gameDescription(gameDesc),
@@ -68,10 +68,39 @@ Common::String TwpEngine::getGameId() const {
 	return _gameDescription->gameId;
 }
 
+void TwpEngine::update(float elapsedMs) {
+	// update threads
+	for (int i = 0; i < _threads.size(); i++) {
+		Thread *thread = _threads[i];
+		if (thread->update(elapsedMs)) {
+			// TODO: delete it
+		}
+	}
+}
+
+void TwpEngine::draw() {
+	if (_room) {
+		Math::Vector2d screenSize = _room->getScreenSize();
+		_gfx.camera(screenSize);
+	}
+	_gfx.clear(Color(0, 0, 0));
+	_gfx.use(NULL);
+	_scene->draw();
+
+	g_system->updateScreen();
+}
+
 Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
 
+	// Set the engine's debugger console
+	setDebugger(new Console());
+
+	_gfx.init();
+	_lighting = new Lighting();
+	_scene = new Scene();
+
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
@@ -79,48 +108,47 @@ Common::Error TwpEngine::run() {
 
 	Common::File f;
 	f.open("ThimbleweedPark.ggpack1");
-
 	_pack.open(&f, key);
 
-	GGPackEntryReader r;
-	r.open(g_engine->_pack, "MainStreet.wimpy");
-
-	Room room;
-	room.load(r);
-
-	// Set the engine's debugger console
-	setDebugger(new Console());
-
 	// If a savegame was selected from the launcher, load it
 	int saveSlot = ConfMan.getInt("save_slot");
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
-	Math::Vector2d pos;
-	_gfx.init();
+	const SQChar *code = R"(
+	MainStreet <- {
+		background = "MainStreet"
+		enter = function(enter_door) {
+		}
+	}
+	defineRoom(MainStreet);
+	cameraInRoom(MainStreet);
+	)";
+
+	_vm.exec(code);
 
-	Lighting lighting;
 	// Simple event handling loop
 	Common::Event e;
+	uint deltaTimeMs = 0;
 	while (!shouldQuit()) {
-		const uint deltaTimeMs = 10;
 		const int dx = 4;
 		const int dy = 4;
+		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
 			switch (e.type) {
 			case Common::EVENT_KEYDOWN:
 				switch (e.kbd.keycode) {
 				case Common::KEYCODE_LEFT:
-					pos.setX(pos.getX() - dx);
+					camPos.setX(camPos.getX() - dx);
 					break;
 				case Common::KEYCODE_RIGHT:
-					pos.setX(pos.getX() + dx);
+					camPos.setX(camPos.getX() + dx);
 					break;
 				case Common::KEYCODE_UP:
-					pos.setY(pos.getY() + dy);
+					camPos.setY(camPos.getY() + dy);
 					break;
 				case Common::KEYCODE_DOWN:
-					pos.setY(pos.getY() - dy);
+					camPos.setY(camPos.getY() - dy);
 					break;
 				default:
 					break;
@@ -130,52 +158,15 @@ Common::Error TwpEngine::run() {
 			}
 		}
 
-		// update threads
-		for (int i = 0; i < _threads.size(); i++) {
-			Thread *thread = _threads[i];
-			if (thread->update(deltaTimeMs)) {
-				// TODO: delete it
-			}
-		}
+		_gfx.cameraPos(camPos);
 
-		// update screen
-		Math::Vector2d screenSize = room.getScreenSize();
-		_gfx.camera(screenSize);
-		_gfx.clear(Color(0, 0, 0));
-		_gfx.use(&lighting);
-
-		// draw room
-		SpriteSheet *ss = _resManager.spriteSheet(room._sheet);
-		Texture *texture = _resManager.texture(ss->meta.image);
-		Common::sort(room._layers.begin(), room._layers.end(), cmpLayer);
-		for (int i = 0; i < room._layers.size(); i++) {
-			float x = 0;
-			const Layer* layer = room._layers[i];
-			for (int j = 0; j < layer->_names.size(); j++) {
-				const Common::String &name = layer->_names[j];
-				const SpriteSheetFrame &frame = ss->frameTable[name];
-				Math::Matrix4 m;
-				Math::Vector3d t1 = Math::Vector3d(x - pos.getX() * layer->_parallax.getX(), -pos.getY() * layer->_parallax.getY(), 0);
-				Math::Vector3d t2 = Math::Vector3d(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f);
-				m.translate(t1+t2);
-				lighting.setSpriteSheetFrame(frame, *texture);
-				_gfx.drawSprite(frame.frame, *texture, Color(), m);
-				x += frame.frame.width();
-			}
-		}
-
-		// TODO: entities
-		_gfx.use(NULL);
-		// for (int i = 0; i < objects.size(); i++) {
-		// 	Object &obj = *objects[i];
-		// 	Math::Matrix4 m;
-		// 	m.translate(Math::Vector3d(obj.x - pos.getX(), obj.y - pos.getY(), 0));
-		// 	_gfx.drawSprite(obj.rect, *obj.texture, Color(), m);
-		// }
-		g_system->updateScreen();
+		// update threads
+		update(deltaTimeMs);
+		draw();
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
+		deltaTimeMs = 10;
 		g_system->delayMillis(deltaTimeMs);
 	}
 
@@ -199,8 +190,283 @@ Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
 }
 
 Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
-  Math::Vector2d screenSize = _room->getScreenSize();
-  return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
+	Math::Vector2d screenSize = _room->getScreenSize();
+	return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
+}
+
+Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
+	HSQUIRRELVM v = _vm.get();
+	debug("Load room: %s", name.c_str());
+	Room *result;
+	if (name == "Void") {
+		error("TODO: room Void");
+		// result = new Room(name, table);
+		// result.scene = new Scene();
+		// Layer* layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
+		// layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "background");
+		// result->_layers.push_back(layer);
+		// // TODO:; result->scene.addChild(layer->node);
+		// sqsetf(sqrootTbl(v), name, result->table);
+	} else {
+		result = new Room(name, table);
+		Common::String background;
+		sqgetf(table, "background", background);
+		GGPackEntryReader entry;
+		entry.open(_pack, background + ".wimpy");
+		result->load(entry);
+		result->_pseudo = pseudo;
+		for (int i = 0; i < result->_layers.size(); i++) {
+			Layer *layer = result->_layers[i];
+			// create layer node
+			ParallaxNode *layerNode = new ParallaxNode(layer->_parallax, result->_sheet, layer->_names);
+			layerNode->setZSort(layer->_zsort);
+			layerNode->setName(Common::String::format("Layer %s(%d)", layer->_names[0].c_str(), layer->_zsort));
+			layer->_node = layerNode;
+			result->_scene->addChild(layerNode);
+
+			for (int j = 0; j < layer->_objects.size(); j++) {
+				Object *obj = layer->_objects[j];
+				if (!sqrawexists(table, obj->_key)) {
+					// this object does not exist, so create it
+					sq_newtable(v);
+					sq_getstackobj(v, -1, &obj->_table);
+					sq_addref(v, &obj->_table);
+					sq_pop(v, 1);
+
+					// assign an id
+					setId(obj->_table, newObjId());
+					// info fmt"Create object with new table: {obj.name} #{obj.id}"
+
+					// adds the object to the room table
+					sqsetf(result->_table, obj->_key, obj->_table);
+					obj->setRoom(result);
+					obj->setState(0, true);
+
+					if (obj->_objType == otNone)
+						obj->_touchable = false;
+				} else if (obj->_objType == otNone) {
+					obj->_touchable = true;
+				}
+
+				layerNode->addChild(obj->_node);
+			}
+		}
+	}
+
+	// assign parent node
+	for (int i = 0; i < result->_layers.size(); i++) {
+		Layer *layer = result->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[j];
+			if (obj->_parent.size() > 0) {
+				Object *parent = result->getObj(obj->_parent);
+				if (!parent) {
+					warning("parent: '%s' not found", obj->_parent.c_str());
+				} else {
+					parent->_node->addChild(obj->_node);
+				}
+			}
+		}
+	}
+
+	sqgetpairs(result->_table, [&](const Common::String &k, HSQOBJECT &oTable) {
+		if (oTable._type == OT_TABLE) {
+			if (pseudo) {
+				// if it's a pseudo room we need to clone each object
+				sq_pushobject(v, oTable);
+				sq_clone(v, -1);
+				sq_getstackobj(v, -1, &oTable);
+				sq_addref(v, &oTable);
+				sq_pop(v, 2);
+				sqsetf(result->_table, k, oTable);
+			}
+
+			if (sqrawexists(oTable, "icon")) {
+				// Add inventory object to root table
+				debug("Add %s to inventory", k.c_str());
+				sqsetf(sqrootTbl(v), k, oTable);
+
+				// set room as delegate
+				sqsetdelegate(oTable, table);
+
+				// declare flags if does not exist
+				if (!sqrawexists(oTable, "flags"))
+					sqsetf(oTable, "flags", 0);
+				Object *obj = new Object(oTable, k);
+				setId(obj->_table, newObjId());
+				obj->_node = new Node(k);
+				obj->_nodeAnim = new Anim(obj);
+				obj->_node->addChild(obj->_nodeAnim);
+				obj->setRoom(result);
+				// set room as delegate
+				sqsetdelegate(obj->_table, table);
+			} else {
+				Object *obj = result->getObj(k);
+				if (!obj) {
+					debug("object: %s not found in wimpy", k.c_str());
+					if (sqrawexists(oTable, "name")) {
+						obj = new Object();
+						obj->_key = k;
+						obj->_layer = result->layer(0);
+						result->layer(0)->_objects.push_back(obj);
+					} else {
+						return;
+					}
+				}
+
+				sqgetf(result->_table, k, obj->_table);
+				setId(obj->_table, newObjId());
+				debug("Create object: %s #%d", k.c_str(), obj->getId());
+
+				// add it to the root table if not a pseudo room
+				if (!pseudo)
+					sqsetf(sqrootTbl(v), k, obj->_table);
+
+				if (sqrawexists(obj->_table, "initState")) {
+					// info fmt"initState {obj.key}"
+					int state;
+					sqgetf(obj->_table, "initState", state);
+					obj->setState(state, true);
+				} else {
+					obj->setState(0, true);
+				}
+				obj->setRoom(result);
+
+				// set room as delegate
+				sqsetdelegate(obj->_table, table);
+
+				// declare flags if does not exist
+				if (!sqrawexists(obj->_table, "flags"))
+					sqsetf(obj->_table, "flags", 0);
+			}
+		}
+	});
+
+	// declare the room in the root table
+	setId(result->_table, newRoomId());
+	sqsetf(sqrootTbl(v), name, result->_table);
+
+	return result;
+}
+
+void TwpEngine::enterRoom(Room *room, Object *door) {
+	HSQUIRRELVM v = getVm();
+	// Called when the room is entered.
+	debug("call enter room function of %s", room->_name.c_str());
+
+	// exit current room
+	// exitRoom(_room);
+	//_fadeEffect.effect = None;
+
+	// sets the current room for scripts
+	sqsetf(sqrootTbl(v), "currentRoom", room->_table);
+
+	if (_room)
+		_room->_scene->remove();
+	_room = room;
+	_scene->addChild(_room->_scene);
+	//   _room->numLights = 0;
+	//   _room->overlay = Transparent;
+	//   _camera.bounds = rectFromMinMax(vec2(0f,0f), vec2f(room.roomSize));
+	//   if (_actor)
+	//     _hud.verb = _hud.actorSlot(_actor).verbs[0];
+
+	//   // move current actor to the new room
+	//   var camPos: Vec2f
+	//   if not gEngine.actor.isNil:
+	//     self.cancelSentence(nil)
+	//     if not door.isNil:
+	//       let facing = getOppositeFacing(door.getDoorFacing())
+	//       gEngine.actor.room = room
+	//       if not door.isNil:
+	//         gEngine.actor.setFacing(facing)
+	//         gEngine.actor.node.pos = door.getUsePos
+	//       camPos = gEngine.actor.node.pos
+
+	//   self.camera.room = room
+	//   self.camera.at = camPos
+
+	//   // call actor enter function and objects enter function
+	//   self.actorEnter()
+	//   for layer in room.layers:
+	//     for obj in layer.objects:
+	//       if rawExists(obj.table, "enter"):
+	//         call(self.v, obj.table, "enter")
+
+	//   // call room enter function with the door as a parameter if requested
+	//   let nparams = paramCount(self.v, self.room.table, "enter")
+	//   if nparams == 2:
+	//     if door.isNil:
+	//       var doorTable: HSQOBJECT
+	//       sq_resetobject(doorTable)
+	//       call(self.v, self.room.table, "enter", [doorTable])
+	//     else:
+	//       call(self.v, self.room.table, "enter", [door.table])
+	//   else:
+	//     call(self.v, self.room.table, "enter")
+
+	//   # call global function enteredRoom with the room as argument
+	//   call("enteredRoom", [room.table])
+}
+
+void TwpEngine::exitRoom(Room *nextRoom) {
+	HSQUIRRELVM v = getVm();
+	// TODO: _audio.stopAll()
+	if (_room) {
+		_room->_triggers.clear();
+
+		actorExit();
+
+		// call room exit function with the next room as a parameter if requested
+		int nparams = sqparamCount(v, _room->_table, "exit");
+		HSQOBJECT args[] = {nextRoom->_table};
+		if (nparams == 2) {
+			sqcall(_room->_table, "exit", 1, args);
+		} else {
+			sqcall(_room->_table, "exit");
+		}
+
+		// delete all temporary objects
+		for (int i = 0; i < _room->_layers.size(); i++) {
+			Layer *layer = _room->_layers[i];
+			for (int j = 0; j < _room->_layers.size(); j++) {
+				Object *obj = layer->_objects[i];
+				if (obj->_temporary) {
+					obj->delObject();
+				} else if (isActor(obj->getId()) && _actor != obj) {
+					obj->stopObjectMotors();
+				}
+			}
+		}
+
+		// call global function enteredRoom with the room as argument
+		sqcall("exitedRoom", 1, args);
+
+		// stop all local threads
+		for (int i = 0; i < _threads.size(); i++) {
+			Thread *thread = _threads[i];
+			if (!thread->_global) {
+				thread->stop();
+			}
+		}
+
+		// stop all lights
+		_room->_lights._numLights = 0;
+	}
+}
+
+void TwpEngine::setRoom(Room *room) {
+	if (room && _room != room)
+		enterRoom(room);
+}
+
+void TwpEngine::actorExit() {
+	if (!_actor && _room) {
+		if (sqrawexists(_room->_table, "actorExit")) {
+			HSQOBJECT args[] = {_actor->_table};
+			sqcall(_room->_table, "actorExit", 1, args);
+		}
+	}
 }
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index ce6631b3eef..b695073cb8d 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -34,13 +34,16 @@
 #include "twp/detection.h"
 #include "twp/vm.h"
 #include "twp/resmanager.h"
-#include "twp/room.h"
 #include "twp/ggpack.h"
 #include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
+class Lighting;
 class Thread;
+class Scene;
+class Room;
+class Object;
 struct TwpGameDescription;
 
 class TwpEngine : public Engine {
@@ -52,15 +55,6 @@ protected:
 	// Engine APIs
 	Common::Error run() override;
 
-public:
-	Graphics::Screen *_screen = nullptr;
-	Common::Array<Object*> _objects;
-	Common::Array<Thread*> _threads;
-	GGPackDecoder _pack;
-	ResManager _resManager;
-	Common::Array<Room> _rooms;
-	Room* _room = nullptr;
-
 public:
 	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
 	~TwpEngine() override;
@@ -112,6 +106,37 @@ public:
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
+	void setActor(Object* actor) { _actor = actor; }
+	Room* defineRoom(const Common::String& name, HSQOBJECT table, bool pseudo = false);
+	void setRoom(Room *room);
+
+private:
+	void update(float elapsedMs);
+	void draw();
+	void enterRoom(Room *room, Object *door = nullptr);
+	void exitRoom(Room *nextRoom);
+	void actorExit();
+
+public:
+	Graphics::Screen *_screen = nullptr;
+	GGPackDecoder _pack;
+	ResManager _resManager;
+	Common::Array<Room*> _rooms;
+	Common::Array<Object*> _actors;
+	Common::Array<Object*> _objects;
+	Common::Array<Thread*> _threads;
+	Object* _actor = nullptr;
+	Object* followActor = nullptr;
+	Room* _room = nullptr;
+	float _time = 0.f;						// time in seconds
+	Object* _noun1 = nullptr;
+	Object* _noun2 = nullptr;
+	HSQOBJECT _defaultObj;
+	bool _walkFastState = false;
+	int _frameCounter = 0;
+	Lighting* _lighting = nullptr;
+	Scene* _scene = nullptr;
+
 private:
 	Gfx _gfx;
 	Vm _vm;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 34adb88a078..c489ec27fda 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -111,8 +111,9 @@ Vm::Vm() {
 	sqgame_register_genlib(v);
 	sqgame_register_objlib(v);
 
-	SQObject platform = sqobj(v, 666);
-	_table(v->_roottable)->NewSlot(sqobj(v, _SC("PLATFORM")), SQObjectPtr(platform));
+	// TODO: constants
+	SQObject platform = sqtoobj(v, 666);
+	_table(v->_roottable)->NewSlot(sqtoobj(v, _SC("PLATFORM")), SQObjectPtr(platform));
 }
 
 Vm::~Vm() {


Commit: d8883990f9a32d09d4682562d189731eac1017f6
    https://github.com/scummvm/scummvm/commit/d8883990f9a32d09d4682562d189731eac1017f6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add include

Changed paths:
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp


diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index da93988d5f7..51dd5eff0ac 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -263,4 +263,19 @@ void sqcall(const Common::String &name, int numArgs, HSQOBJECT *args) {
 	sqcall(v, sqrootTbl(v), name, numArgs, args);
 }
 
+void sqexec(HSQUIRRELVM v, const char *code) {
+	SQInteger top = sq_gettop(v);
+	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
+		sqstd_printcallstack(v);
+		return;
+	}
+	sq_pushroottable(v);
+	if (SQ_FAILED(sq_call(v, 1, SQFalse, SQTrue))) {
+		sqstd_printcallstack(v);
+		sq_pop(v, 1); // removes the closure
+		return;
+	}
+	sq_settop(v, top);
+}
+
 } // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 3b3e09e1e92..420d9d36292 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -120,6 +120,7 @@ HSQOBJECT sqrootTbl(HSQUIRRELVM v);
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String& name);
 void sqcall(const Common::String &name, int numArgs = 0, HSQOBJECT *args = nullptr);
 void sqcall(HSQOBJECT o, const Common::String& name, int numArgs = 0, HSQOBJECT* args = nullptr);
+void sqexec(HSQUIRRELVM v, const char *code);
 
 class Room;
 class Object;
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index b1ae1d47d7a..770065dd884 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -21,6 +21,7 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
@@ -236,9 +237,15 @@ static SQInteger gameTime(HSQUIRRELVM v) {
 	return 0;
 }
 
-static SQInteger sysInclude(HSQUIRRELVM v) {
+static SQInteger include(HSQUIRRELVM v) {
 	warning("TODO: sysInclude: not implemented");
 	return 0;
+	const SQChar *filename;
+	if (SQ_FAILED(sqget(v, 2, filename))) {
+		return sq_throwerror(v, "failed to get filename");
+	}
+	g_engine->execNutEntry(v, filename);
+	return 0;
 }
 
 static SQInteger inputController(HSQUIRRELVM v) {
@@ -362,7 +369,7 @@ void sqgame_register_syslib(HSQUIRRELVM v) {
 	regFunc(v, dumpvar, _SC("dumpvar"));
 	regFunc(v, exCommand, _SC("exCommand"));
 	regFunc(v, gameTime, _SC("gameTime"));
-	regFunc(v, sysInclude, _SC("include"));
+	regFunc(v, include, _SC("include"));
 	regFunc(v, inputController, _SC("inputController"));
 	regFunc(v, inputHUD, _SC("inputHUD"));
 	regFunc(v, inputOff, _SC("inputOff"));
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index b5050e74c22..f50f5510d69 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -469,4 +469,31 @@ void TwpEngine::actorExit() {
 	}
 }
 
+void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
+	GGPackEntryReader reader;
+	reader.open(_pack, entry);
+	GGBnutReader nut;
+	nut.open(&reader);
+	Common::String code = nut.readString();
+	sqexec(v, code.c_str());
+}
+
+void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
+	if (_pack.assetExists(entry.c_str())) {
+		GGPackEntryReader reader;
+		debug("read existing '%s'", entry.c_str());
+		reader.open(_pack, entry);
+		Common::String code = reader.readString();
+		sqexec(v, code.c_str());
+	} else {
+		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
+		debug("read existing '%s'", newEntry.c_str());
+		if (_pack.assetExists(newEntry.c_str())) {
+			execBnutEntry(v, newEntry);
+		} else {
+			error("'%s' and '%s' have not been found", entry.c_str(), newEntry.c_str());
+		}
+	}
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index b695073cb8d..e13bf20886f 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -110,6 +110,9 @@ public:
 	Room* defineRoom(const Common::String& name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
 
+	void execNutEntry(HSQUIRRELVM v, const Common::String& entry);
+	void execBnutEntry(HSQUIRRELVM v,const Common::String &entry);
+
 private:
 	void update(float elapsedMs);
 	void draw();
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index c489ec27fda..939a3b6dc41 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -46,21 +46,6 @@ namespace Twp {
 
 static HSQUIRRELVM gVm = nullptr;
 
-static void sqExec(HSQUIRRELVM v, const char *code) {
-	SQInteger top = sq_gettop(v);
-	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
-		sqstd_printcallstack(v);
-		return;
-	}
-	sq_pushroottable(v);
-	if (SQ_FAILED(sq_call(v, 1, SQFalse, SQTrue))) {
-		sqstd_printcallstack(v);
-		sq_pop(v, 1); // removes the closure
-		return;
-	}
-	sq_settop(v, top);
-}
-
 static void errorHandler(HSQUIRRELVM v, const SQChar *desc, const SQChar *source, SQInteger line,
 						 SQInteger column) {
 	debug("TWP: desc %s, source: %s (%lld,%lld)", desc, source, line, column);
@@ -124,6 +109,6 @@ Vm::~Vm() {
 }
 
 void Vm::exec(const SQChar *code) {
-	sqExec(v, code);
+	sqexec(v, code);
 }
 } // namespace Twp


Commit: 6991c88a1b4cb6274bcba5c2dd7a49341135cfc5
    https://github.com/scummvm/scummvm/commit/6991c88a1b4cb6274bcba5c2dd7a49341135cfc5
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add camera and interpolations

Changed paths:
  A engines/twp/camera.cpp
  A engines/twp/camera.h
  A engines/twp/util.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/rectf.cpp
    engines/twp/rectf.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
new file mode 100644
index 00000000000..524e15b9d40
--- /dev/null
+++ b/engines/twp/camera.cpp
@@ -0,0 +1,117 @@
+/* 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 "twp/twp.h"
+#include "twp/camera.h"
+#include "twp/room.h"
+#include "twp/object.h"
+#include "twp/util.h"
+#include "twp/scenegraph.h"
+
+namespace Twp {
+
+void Camera::clamp(Math::Vector2d at) {
+	if (_room) {
+		Math::Vector2d roomSize = _room->_roomSize;
+		Math::Vector2d screenSize = _room->getScreenSize();
+
+		_pos.setX(Twp::clamp(at.getX(), screenSize.getX() / 2.f + _bounds.left(), screenSize.getX() / 2 + _bounds.right()));
+		_pos.setY(Twp::clamp(at.getY(), _bounds.bottom(), _bounds.top() - screenSize.getY() / 2));
+		//_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(roomSize.getX() - screenSize.getX() / 2.f, 0.f)));
+		_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(10000.f, 0.f)));
+		_pos.setY(Twp::clamp(_pos.getY(), screenSize.getY() / 2, MAX(roomSize.getY() - screenSize.getY() / 2, 0.f)));
+	}
+}
+
+void Camera::setAtCore(Math::Vector2d at) {
+	Math::Vector2d screenSize = _room->getScreenSize();
+	_pos = at;
+	clamp(_pos);
+	g_engine->getGfx().cameraPos(_pos - screenSize / 2.f);
+}
+
+void Camera::setAt(Math::Vector2d at) {
+	setAtCore(at);
+	_target = _pos;
+	_time = 0;
+	_moving = false;
+}
+
+void Camera::panTo(Math::Vector2d target, float time, InterpolationKind interpolation) {
+  if (!_moving) {
+    _moving = true;
+    _init = _pos;
+    _elapsed = 0.f;
+  }
+  _function = easing(interpolation);
+  _target = target;
+  _time = time;
+}
+
+void Camera::update(Room* room, Object* follow, float elapsed) {
+  _room = room;
+  _elapsed += elapsed;
+  bool isMoving = _elapsed < _time;
+
+  if(_moving && !isMoving) {
+    _moving = false;
+    _time = 0.f;
+    setAt(_target);
+  }
+
+  if(isMoving) {
+    float t = _elapsed / _time;
+    Math::Vector2d d = _target - _init;
+    Math::Vector2d pos = _init + (d * _function.func(t));
+    setAtCore(pos);
+    return;
+  }
+
+  if (follow && follow->_node->isVisible() && follow->_room == room) {
+    Math::Vector2d screen = room->getScreenSize();
+    Math::Vector2d pos = follow->_node->getPos();
+    Math::Vector2d margin(screen.getX() / 6.f, screen.getY() / 6.f);
+    Math::Vector2d cameraPos = getAt();
+
+    Math::Vector2d d = pos - cameraPos;
+    Math::Vector2d delta = d * elapsed;
+    bool sameActor = _follow == follow;
+
+    float x, y;
+    if (sameActor && (pos.getX() > (cameraPos.getX() + margin.getX())))
+      x = pos.getX() - margin.getX();
+    else if(sameActor && (pos.getX() < (cameraPos.getX() - margin.getX())))
+      x = pos.getX() + margin.getX();
+    else
+      x = cameraPos.getX() + (d.getX() > 0? MIN(delta.getX(), d.getX()): MAX(delta.getX(), d.getX()));
+    if (sameActor && (pos.getY() > (cameraPos.getY() + margin.getY())))
+      y = pos.getY() - margin.getY();
+    else if (sameActor && (pos.getY() < (cameraPos.getY() - margin.getY())))
+      y = pos.getY() + margin.getY();
+    else
+      y = cameraPos.getY() + d.getY() > 0? MIN(delta.getY(), d.getY()): MAX(delta.getY(), d.getY());
+    setAtCore(Math::Vector2d(x, y));
+    if (!sameActor && (fabs(pos.getX() - x) < 1.f) && (fabs(pos.getY() - y) < 1.f))
+      _follow = follow;
+  }
+}
+
+} // namespace Twp
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
new file mode 100644
index 00000000000..2978b1bb90a
--- /dev/null
+++ b/engines/twp/camera.h
@@ -0,0 +1,125 @@
+/* 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 TWP_CAMERA_H
+#define TWP_CAMERA_H
+
+#include "common/func.h"
+#include "math/vector2d.h"
+#include "twp/rectf.h"
+
+namespace Twp {
+
+class Object;
+class Room;
+
+typedef float EasingFunc(float t);
+
+typedef struct EasingFunc_t {
+	EasingFunc *func;
+} EasingFunc_t;
+
+enum InterpolationKind {
+	IK_LINEAR = 0,
+	IK_EASEIN = 1,
+	IK_EASEINOUT = 2,
+	IK_EASEOUT = 3,
+	IK_SLOWEASEIN = 4,
+	IK_SLOWEASEOUT = 5
+};
+
+struct InterpolationMethod {
+	InterpolationKind kind = IK_LINEAR;
+	bool loop = false;
+	bool swing = false;
+};
+
+static float linear(float t) { return t; }
+
+static float easeIn(float t) {
+	return t * t * t * t;
+}
+
+static float easeOut(float t) {
+	float f = (t - 1.0f);
+	return f * f * f * (1.0f - t) + 1.0f;
+}
+
+static float easeInOut(float t) {
+	if (t < 0.5f)
+		return 8.0f * t * t * t * t;
+	float f = (t - 1.0f);
+	return -8.f * f * f * f * f + 1.f;
+}
+
+inline EasingFunc_t easing(InterpolationKind kind) {
+	switch (kind) {
+	case IK_LINEAR:
+		return {&linear};
+	case IK_EASEIN:
+		return {&easeIn};
+	case IK_EASEINOUT:
+		return {&easeInOut};
+	case IK_EASEOUT:
+		return {&easeOut};
+	case IK_SLOWEASEIN:
+		return {&easeIn};
+	case IK_SLOWEASEOUT:
+		return {&easeOut};
+	}
+	error("Invalid interpolation kind: %d", kind);
+	return {&linear};
+}
+
+class Camera {
+public:
+	void setAt(Math::Vector2d at);
+	inline Math::Vector2d getAt() const { return _pos; }
+
+	inline void setBounds(const Rectf &bounds) { _bounds = bounds; }
+	inline Rectf getBounds() const { return _bounds; }
+
+	inline void setRoom(Room *room) { _room = room; }
+	inline Room *getRoom(Room *room) const { return _room; }
+
+	inline bool isMoving() const { return _moving; }
+	void panTo(Math::Vector2d target, float time, InterpolationKind interpolation);
+
+	void update(Room *room, Object *follow, float elapsed);
+
+private:
+	void clamp(Math::Vector2d at);
+	void setAtCore(Math::Vector2d at);
+
+private:
+	Math::Vector2d _pos;
+	Rectf _bounds = Rectf::fromMinMax(Math::Vector2d(-10000,-10000), Math::Vector2d(10000,10000));
+	bool _moving = false;
+	Math::Vector2d _init, _target;
+	float _elapsed = 0.f;
+	float _time = 0.f;
+	Room *_room = nullptr;
+	Object *_follow = nullptr;
+	EasingFunc_t _function = {&linear};
+};
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 2b164426118..2b5ab9b1276 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -23,6 +23,7 @@
 #include "twp/twp.h"
 #include "twp/room.h"
 #include "twp/object.h"
+#include "twp/scenegraph.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
@@ -168,8 +169,59 @@ static SQInteger cameraInRoom(HSQUIRRELVM v) {
 // breakwhilewalking(currentActor)
 // cameraPanTo(currentActor, 2.0)
 static SQInteger cameraPanTo(HSQUIRRELVM v) {
-	// TODO: cameraPanTo
-	warning("cameraPanTo not implemented");
+	SQInteger numArgs = sq_gettop(v);
+	Math::Vector2d pos;
+	float duration = 0.f;
+	InterpolationKind interpolation = IK_LINEAR;
+	if (numArgs == 3) {
+		Object *obj = sqobj(v, 2);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object/actor");
+		pos = obj->getUsePos();
+		if (SQ_FAILED(sqget(v, 3, duration)))
+			return sq_throwerror(v, "failed to get duration");
+	} else if (numArgs == 4) {
+		if (sq_gettype(v, 2) == OT_INTEGER) {
+			int x;
+			if (SQ_FAILED(sqget(v, 2, x)))
+				return sq_throwerror(v, "failed to get x");
+			if (SQ_FAILED(sqget(v, 3, duration)))
+				return sq_throwerror(v, "failed to get duration");
+			int im;
+			if (SQ_FAILED(sqget(v, 4, im)))
+				return sq_throwerror(v, "failed to get interpolation method");
+			pos = Math::Vector2d(x, g_engine->getGfx().cameraPos().getY());
+			interpolation = (InterpolationKind)im;
+		} else {
+			Object *obj = sqobj(v, 2);
+			if (SQ_FAILED(sqget(v, 3, duration)))
+				return sq_throwerror(v, "failed to get duration");
+			int im;
+			if (SQ_FAILED(sqget(v, 4, im)))
+				return sq_throwerror(v, "failed to get interpolation method");
+			pos = obj->_node->getAbsPos();
+			interpolation = (InterpolationKind)im;
+		}
+	} else if (numArgs == 5) {
+		int x, y;
+		if (SQ_FAILED(sqget(v, 2, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 3, y)))
+			return sq_throwerror(v, "failed to get y");
+		if (SQ_FAILED(sqget(v, 4, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		int im;
+		if (SQ_FAILED (sqget(v, 5, im)))
+      return sq_throwerror(v, "failed to get interpolation method");
+		pos = Math::Vector2d(x, y);
+		interpolation = (InterpolationKind)im;
+	} else {
+		return sq_throwerror(v, Common::String::format("invalid argument number: %d", numArgs).c_str());
+	}
+	Math::Vector2d halfScreen(g_engine->_room->getScreenSize() / 2.f);
+	debug("cameraPanTo: {pos}, dur={duration}, method={interpolation}");
+	g_engine->follow(nullptr);
+	g_engine->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
 	return 0;
 }
 
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 7b2873644e2..769e1635594 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -44,6 +44,7 @@ MODULE_OBJS = \
 	scenegraph.o \
 	object.o \
 	ids.o \
+	camera.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/rectf.cpp b/engines/twp/rectf.cpp
index 891b604e8f1..c94dbb50d3e 100644
--- a/engines/twp/rectf.cpp
+++ b/engines/twp/rectf.cpp
@@ -23,6 +23,8 @@
 
 namespace Twp {
 
+Rectf::Rectf() : Rectf(0, 0, 0, 0) {}
+
 Rectf::Rectf(float x, float y, float w, float h) {
 	r.x = x;
 	r.y = y;
@@ -34,6 +36,10 @@ Rectf Rectf::fromPosAndSize(Math::Vector2d pos, Math::Vector2d size) {
 	return {pos.getX(), pos.getY(), size.getX(), size.getY()};
 }
 
+Rectf Rectf::fromMinMax(Math::Vector2d min, Math::Vector2d max) {
+	return {min.getX(), min.getY(), max.getX() - min.getX() + 1, max.getY() - min.getY() + 1};
+}
+
 Rectf Rectf::operator/(Math::Vector2d v) {
 	return Rectf(r.x / v.getX(), r.y / v.getY(), r.w / v.getX(), r.h / v.getY());
 }
diff --git a/engines/twp/rectf.h b/engines/twp/rectf.h
index 18c34d497be..8eb303eabf7 100644
--- a/engines/twp/rectf.h
+++ b/engines/twp/rectf.h
@@ -29,9 +29,12 @@ namespace Twp {
 
 struct Rectf {
 public:
+	Rectf();
 	Rectf(float x, float y, float w, float h);
 
 	static Rectf fromPosAndSize(Math::Vector2d pos, Math::Vector2d size);
+	static Rectf fromMinMax(Math::Vector2d min, Math::Vector2d max);
+
 	Rectf operator/(Math::Vector2d v);
 
 	union {
@@ -43,6 +46,11 @@ public:
 			float h;
 		} r;
 	};
+
+	inline float left() { return r.x; }
+	inline float right() { return r.x + r.w; }
+	inline float top() { return r.y + r.h; }
+	inline float bottom() { return r.y; }
 };
 } // namespace Twp
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f50f5510d69..8c28c314197 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -69,6 +69,9 @@ Common::String TwpEngine::getGameId() const {
 }
 
 void TwpEngine::update(float elapsedMs) {
+	// update camera
+	_camera.update(_room, _followActor, elapsedMs);
+
 	// update threads
 	for (int i = 0; i < _threads.size(); i++) {
 		Thread *thread = _threads[i];
@@ -118,11 +121,12 @@ Common::Error TwpEngine::run() {
 	const SQChar *code = R"(
 	MainStreet <- {
 		background = "MainStreet"
-		enter = function(enter_door) {
-		}
+		enter = function(enter_door) {}
 	}
-	defineRoom(MainStreet);
-	cameraInRoom(MainStreet);
+	defineRoom(MainStreet)
+	cameraInRoom(MainStreet)
+	cameraAt(0,128)
+	cameraPanTo(2820, 128, 5000, 2)
 	)";
 
 	_vm.exec(code);
@@ -355,8 +359,8 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	debug("call enter room function of %s", room->_name.c_str());
 
 	// exit current room
-	// exitRoom(_room);
-	//_fadeEffect.effect = None;
+	exitRoom(_room);
+	// TODO: _fadeEffect.effect = None;
 
 	// sets the current room for scripts
 	sqsetf(sqrootTbl(v), "currentRoom", room->_table);
@@ -365,14 +369,14 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 		_room->_scene->remove();
 	_room = room;
 	_scene->addChild(_room->_scene);
-	//   _room->numLights = 0;
-	//   _room->overlay = Transparent;
-	//   _camera.bounds = rectFromMinMax(vec2(0f,0f), vec2f(room.roomSize));
+	_room->_lights._numLights = 0;
+	// TODO:   _room->overlay = Transparent;
+	//_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
 	//   if (_actor)
 	//     _hud.verb = _hud.actorSlot(_actor).verbs[0];
 
-	//   // move current actor to the new room
-	//   var camPos: Vec2f
+	//   TODO: move current actor to the new room
+	Math::Vector2d camPos;
 	//   if not gEngine.actor.isNil:
 	//     self.cancelSentence(nil)
 	//     if not door.isNil:
@@ -383,11 +387,11 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	//         gEngine.actor.node.pos = door.getUsePos
 	//       camPos = gEngine.actor.node.pos
 
-	//   self.camera.room = room
-	//   self.camera.at = camPos
+	_camera.setRoom(room);
+	_camera.setAt(camPos);
 
 	//   // call actor enter function and objects enter function
-	//   self.actorEnter()
+	//   actorEnter();
 	//   for layer in room.layers:
 	//     for obj in layer.objects:
 	//       if rawExists(obj.table, "enter"):
@@ -496,4 +500,20 @@ void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 	}
 }
 
+void TwpEngine::cameraAt(Math::Vector2d at) {
+	_camera.setRoom(_room);
+	_camera.setAt(at);
+}
+
+void TwpEngine::follow(Object *actor) {
+	_followActor = actor;
+	if (actor) {
+		Math::Vector2d pos = actor->_node->getPos();
+		Room *oldRoom = _room;
+		setRoom(actor->_room);
+		if (oldRoom != actor->_room)
+			cameraAt(pos);
+	}
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index e13bf20886f..5215384f507 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -36,6 +36,7 @@
 #include "twp/resmanager.h"
 #include "twp/ggpack.h"
 #include "twp/squirrel/squirrel.h"
+#include "twp/camera.h"
 
 namespace Twp {
 
@@ -110,6 +111,9 @@ public:
 	Room* defineRoom(const Common::String& name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
 
+	void cameraAt(Math::Vector2d at);
+	void follow(Object* actor);
+
 	void execNutEntry(HSQUIRRELVM v, const Common::String& entry);
 	void execBnutEntry(HSQUIRRELVM v,const Common::String &entry);
 
@@ -129,7 +133,7 @@ public:
 	Common::Array<Object*> _objects;
 	Common::Array<Thread*> _threads;
 	Object* _actor = nullptr;
-	Object* followActor = nullptr;
+	Object* _followActor = nullptr;
 	Room* _room = nullptr;
 	float _time = 0.f;						// time in seconds
 	Object* _noun1 = nullptr;
@@ -139,6 +143,7 @@ public:
 	int _frameCounter = 0;
 	Lighting* _lighting = nullptr;
 	Scene* _scene = nullptr;
+	Camera _camera;
 
 private:
 	Gfx _gfx;
diff --git a/engines/twp/util.h b/engines/twp/util.h
new file mode 100644
index 00000000000..da9cae9ca20
--- /dev/null
+++ b/engines/twp/util.h
@@ -0,0 +1,44 @@
+/* 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 TWP_UTIL_H
+#define TWP_UTIL_H
+
+#include "math/vector2d.h"
+
+namespace Twp {
+
+template<typename T>
+T clamp(T x, T a, T b) {
+	if (x < a)
+		return a;
+	if (x > b)
+		return b;
+	return x;
+}
+
+Math::Vector2d operator*(Math::Vector2d v, float f) {
+	return Math::Vector2d(v.getX() * f, v.getY() * f);
+}
+
+} // namespace Twp
+
+#endif


Commit: ae5776941f1266c955f7a6412e414775e38ddab6
    https://github.com/scummvm/scummvm/commit/ae5776941f1266c955f7a6412e414775e38ddab6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add actorlib stubs

Changed paths:
  A engines/twp/actorlib.cpp
    engines/twp/module.mk
    engines/twp/sqgame.h
    engines/twp/vm.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
new file mode 100644
index 00000000000..e4e88977f98
--- /dev/null
+++ b/engines/twp/actorlib.cpp
@@ -0,0 +1,327 @@
+/* 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 "twp/sqgame.h"
+#include "twp/twp.h"
+#include "twp/room.h"
+#include "twp/object.h"
+#include "twp/squtil.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+static SQInteger actorAlpha(HSQUIRRELVM v) {
+	warning("TODO: actorAlpha not implemented");
+	return 0;
+}
+
+static SQInteger actorAnimationFlags(HSQUIRRELVM v) {
+	warning("TODO: actorAnimationFlags not implemented");
+	return 0;
+}
+
+static SQInteger actorAnimationNames(HSQUIRRELVM v) {
+	warning("TODO: actorAnimationNames not implemented");
+	return 0;
+}
+
+static SQInteger actorAt(HSQUIRRELVM v) {
+	warning("TODO: actorAt not implemented");
+	return 0;
+}
+
+static SQInteger actorBlinkRate(HSQUIRRELVM v) {
+	warning("TODO: actorBlinkRate not implemented");
+	return 0;
+}
+
+static SQInteger actorColor(HSQUIRRELVM v) {
+	warning("TODO: actorColor not implemented");
+	return 0;
+}
+
+static SQInteger actorCostume(HSQUIRRELVM v) {
+	warning("TODO: actorCostume not implemented");
+	return 0;
+}
+
+static SQInteger actorDistanceTo(HSQUIRRELVM v) {
+	warning("TODO: actorDistanceTo not implemented");
+	return 0;
+}
+
+static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
+	warning("TODO: actorDistanceWithin not implemented");
+	return 0;
+}
+
+static SQInteger actorFace(HSQUIRRELVM v) {
+	warning("TODO: actorFace not implemented");
+	return 0;
+}
+
+static SQInteger actorHidden(HSQUIRRELVM v) {
+	warning("TODO: actorHidden not implemented");
+	return 0;
+}
+
+static SQInteger actorInTrigger(HSQUIRRELVM v) {
+	warning("TODO: actorInTrigger not implemented");
+	return 0;
+}
+
+static SQInteger actorInWalkbox(HSQUIRRELVM v) {
+	warning("TODO: actorInWalkbox not implemented");
+	return 0;
+}
+
+static SQInteger actorRoom(HSQUIRRELVM v) {
+	warning("TODO: actorRoom not implemented");
+	return 0;
+}
+
+static SQInteger actorHideLayer(HSQUIRRELVM v) {
+	warning("TODO: actorHideLayer not implemented");
+	return 0;
+}
+
+static SQInteger actorShowLayer(HSQUIRRELVM v) {
+	warning("TODO: actorShowLayer not implemented");
+	return 0;
+}
+
+static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
+	warning("TODO: actorSlotSelectable not implemented");
+	return 0;
+}
+
+static SQInteger actorLockFacing(HSQUIRRELVM v) {
+	warning("TODO: actorLockFacing not implemented");
+	return 0;
+}
+
+static SQInteger actorPosX(HSQUIRRELVM v) {
+	warning("TODO: actorPosX not implemented");
+	return 0;
+}
+
+static SQInteger actorPosY(HSQUIRRELVM v) {
+	warning("TODO: actorPosY not implemented");
+	return 0;
+}
+
+static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
+	warning("TODO: actorPlayAnimation not implemented");
+	return 0;
+}
+
+static SQInteger actorRenderOffset(HSQUIRRELVM v) {
+	warning("TODO: actorRenderOffset not implemented");
+	return 0;
+}
+
+static SQInteger actorStand(HSQUIRRELVM v) {
+	warning("TODO: actorStand not implemented");
+	return 0;
+}
+
+static SQInteger actorStopWalking(HSQUIRRELVM v) {
+	warning("TODO: actorStopWalking not implemented");
+	return 0;
+}
+
+static SQInteger actorTalkColors(HSQUIRRELVM v) {
+	warning("TODO: actorTalkColors not implemented");
+	return 0;
+}
+
+static SQInteger actorTalking(HSQUIRRELVM v) {
+	warning("TODO: actorTalking not implemented");
+	return 0;
+}
+
+static SQInteger actorTurnTo(HSQUIRRELVM v) {
+	warning("TODO: actorTurnTo not implemented");
+	return 0;
+}
+
+static SQInteger actorTalkOffset(HSQUIRRELVM v) {
+	warning("TODO: actorTalkOffset not implemented");
+	return 0;
+}
+
+static SQInteger actorUsePos(HSQUIRRELVM v) {
+	warning("TODO: actorUsePos not implemented");
+	return 0;
+}
+
+static SQInteger actorUseWalkboxes(HSQUIRRELVM v) {
+	warning("TODO: actorUseWalkboxes not implemented");
+	return 0;
+}
+
+static SQInteger actorVolume(HSQUIRRELVM v) {
+	warning("TODO: actorVolume not implemented");
+	return 0;
+}
+
+static SQInteger actorWalkForward(HSQUIRRELVM v) {
+	warning("TODO: actorWalkForward not implemented");
+	return 0;
+}
+
+static SQInteger actorWalking(HSQUIRRELVM v) {
+	warning("TODO: actorWalking not implemented");
+	return 0;
+}
+
+static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
+	warning("TODO: actorWalkSpeed not implemented");
+	return 0;
+}
+
+static SQInteger actorWalkTo(HSQUIRRELVM v) {
+	warning("TODO: actorWalkTo not implemented");
+	return 0;
+}
+
+static SQInteger addSelectableActor(HSQUIRRELVM v) {
+	warning("TODO: addSelectableActor not implemented");
+	return 0;
+}
+
+static SQInteger createActor(HSQUIRRELVM v) {
+	warning("TODO: createActor not implemented");
+	return 0;
+}
+
+static SQInteger flashSelectableActor(HSQUIRRELVM v) {
+	warning("TODO: flashSelectableActor not implemented");
+	return 0;
+}
+
+static SQInteger sayLine(HSQUIRRELVM v) {
+	warning("TODO: sayLine not implemented");
+	return 0;
+}
+
+static SQInteger sayLineAt(HSQUIRRELVM v) {
+	warning("TODO: sayLineAt not implemented");
+	return 0;
+}
+
+static SQInteger isActorOnScreen(HSQUIRRELVM v) {
+	warning("TODO: isActorOnScreen not implemented");
+	return 0;
+}
+
+static SQInteger isActorSelectable(HSQUIRRELVM v) {
+	warning("TODO: isActorSelectable not implemented");
+	return 0;
+}
+
+static SQInteger is_actor(HSQUIRRELVM v) {
+	warning("TODO: is_actor not implemented");
+	return 0;
+}
+
+static SQInteger masterActorArray(HSQUIRRELVM v) {
+	warning("TODO: masterActorArray not implemented");
+	return 0;
+}
+
+static SQInteger mumbleLine(HSQUIRRELVM v) {
+	warning("TODO: mumbleLine not implemented");
+	return 0;
+}
+
+static SQInteger stopTalking(HSQUIRRELVM v) {
+	warning("TODO: stopTalking not implemented");
+	return 0;
+}
+
+static SQInteger selectActor(HSQUIRRELVM v) {
+	warning("TODO: selectActor not implemented");
+	return 0;
+}
+
+static SQInteger triggerActors(HSQUIRRELVM v) {
+	warning("TODO: triggerActors not implemented");
+	return 0;
+}
+
+static SQInteger verbUIColors(HSQUIRRELVM v) {
+	warning("TODO: verbUIColors not implemented");
+	return 0;
+}
+
+void sqgame_register_actorlib(HSQUIRRELVM v) {
+  regFunc(v, actorAnimationFlags, "actorAnimationFlags");
+  regFunc(v, actorAnimationNames, "actorAnimationNames");
+  regFunc(v, actorAlpha, "actorAlpha");
+  regFunc(v, actorAt, "actorAt");
+  regFunc(v, actorBlinkRate, "actorBlinkRate");
+  regFunc(v, actorColor, "actorColor");
+  regFunc(v, actorCostume, "actorCostume");
+  regFunc(v, actorDistanceTo, "actorDistanceTo");
+  regFunc(v, actorDistanceWithin, "actorDistanceWithin");
+  regFunc(v, actorFace, "actorFace");
+  regFunc(v, actorHidden, "actorHidden");
+  regFunc(v, actorHideLayer, "actorHideLayer");
+  regFunc(v, actorInTrigger, "actorInTrigger");
+  regFunc(v, actorInWalkbox, "actorInWalkbox");
+  regFunc(v, actorLockFacing, "actorLockFacing");
+  regFunc(v, actorPlayAnimation, "actorPlayAnimation");
+  regFunc(v, actorPosX, "actorPosX");
+  regFunc(v, actorPosY, "actorPosY");
+  regFunc(v, actorRenderOffset, "actorRenderOffset");
+  regFunc(v, actorRoom, "actorRoom");
+  regFunc(v, actorShowLayer, "actorShowLayer");
+  regFunc(v, actorSlotSelectable, "actorSlotSelectable");
+  regFunc(v, actorStand, "actorStand");
+  regFunc(v, actorStopWalking, "actorStopWalking");
+  regFunc(v, actorTalkColors, "actorTalkColors");
+  regFunc(v, actorTalking, "actorTalking");
+  regFunc(v, actorTalkOffset, "actorTalkOffset");
+  regFunc(v, actorTurnTo, "actorTurnTo");
+  regFunc(v, actorUsePos, "actorUsePos");
+  regFunc(v, actorUseWalkboxes, "actorUseWalkboxes");
+  regFunc(v, actorVolume, "actorVolume");
+  regFunc(v, actorWalking, "actorWalking");
+  regFunc(v, actorWalkForward, "actorWalkForward");
+  regFunc(v, actorWalkSpeed, "actorWalkSpeed");
+  regFunc(v, actorWalkTo, "actorWalkTo");
+  regFunc(v, addSelectableActor, "addSelectableActor");
+  regFunc(v, createActor, "createActor");
+  regFunc(v, flashSelectableActor, "flashSelectableActor");
+  regFunc(v, is_actor, "is_actor");
+  regFunc(v, isActorOnScreen, "isActorOnScreen");
+  regFunc(v, isActorSelectable, "isActorSelectable");
+  regFunc(v, mumbleLine, "mumbleLine");
+  regFunc(v, masterActorArray, "masterActorArray");
+  regFunc(v, sayLine, "sayLine");
+  regFunc(v, sayLineAt, "sayLineAt");
+  regFunc(v, selectActor, "selectActor");
+  regFunc(v, stopTalking, "stopTalking");
+  regFunc(v, triggerActors, "triggerActors");
+  regFunc(v, verbUIColors, "verbUIColors");
+}
+} // namespace Twp
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 769e1635594..aab7deb19f4 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -45,6 +45,7 @@ MODULE_OBJS = \
 	object.o \
 	ids.o \
 	camera.o \
+	actorlib.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
index 12292e7b10d..07119a5476e 100644
--- a/engines/twp/sqgame.h
+++ b/engines/twp/sqgame.h
@@ -31,6 +31,7 @@ void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger
 void sqgame_register_syslib(HSQUIRRELVM v);
 void sqgame_register_objlib(HSQUIRRELVM v);
 void sqgame_register_genlib(HSQUIRRELVM v);
+void sqgame_register_actorlib(HSQUIRRELVM v);
 
 } // namespace Twp
 #endif
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 939a3b6dc41..0c345993fed 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -95,6 +95,7 @@ Vm::Vm() {
 	sqgame_register_syslib(v);
 	sqgame_register_genlib(v);
 	sqgame_register_objlib(v);
+	sqgame_register_actorlib(v);
 
 	// TODO: constants
 	SQObject platform = sqtoobj(v, 666);


Commit: 764ffa65bd327a6c46d6b8d6f716e51e44a6d464
    https://github.com/scummvm/scummvm/commit/764ffa65bd327a6c46d6b8d6f716e51e44a6d464
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add roomlib stubs

Changed paths:
  A engines/twp/roomlib.cpp
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/sqgame.h
    engines/twp/vm.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 2b5ab9b1276..27bd92258ae 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -216,7 +216,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 		pos = Math::Vector2d(x, y);
 		interpolation = (InterpolationKind)im;
 	} else {
-		return sq_throwerror(v, Common::String::format("invalid argument number: %d", numArgs).c_str());
+		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
 	Math::Vector2d halfScreen(g_engine->_room->getScreenSize() / 2.f);
 	debug("cameraPanTo: {pos}, dur={duration}, method={interpolation}");
@@ -555,29 +555,7 @@ static SQInteger translate(HSQUIRRELVM v) {
 	return 0;
 }
 
-// TODO: move this function
-static SQInteger defineRoom(HSQUIRRELVM v) {
-	// This command is used during the game's boot process.
-	// `defineRoom` is called once for every room in the game, passing it the room's room object.
-	// If the room has not been defined, it can not be referenced.
-	// `defineRoom` is typically called in the the DefineRooms.nut file which loads and defines every room in the game.
-	HSQOBJECT table;
-	sq_resetobject(&table);
-	if (SQ_FAILED(sq_getstackobj(v, 2, &table)))
-		return sq_throwerror(v, "failed to get room table");
-	Common::String name;
-	sqgetf(v, table, "name", name);
-	if (name.size() == 0)
-		sqgetf(v, table, "background", name);
-	Room *room = g_engine->defineRoom(name, table);
-	debug("Define room: %s", name.c_str());
-	g_engine->_rooms.push_back(room);
-	sqpush(v, room->_table);
-	return 1;
-}
-
 void sqgame_register_genlib(HSQUIRRELVM v) {
-	regFunc(v, defineRoom, _SC("defineRoom"));
 	regFunc(v, activeVerb, _SC("activeVerb"));
 	regFunc(v, adhocalytics, _SC("adhocalytics"));
 	regFunc(v, arrayShuffle, _SC("arrayShuffle"));
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index aab7deb19f4..7aacb5fff78 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -46,6 +46,7 @@ MODULE_OBJS = \
 	ids.o \
 	camera.o \
 	actorlib.o \
+	roomlib.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
new file mode 100644
index 00000000000..ffa815e9055
--- /dev/null
+++ b/engines/twp/roomlib.cpp
@@ -0,0 +1,204 @@
+/* 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 "twp/sqgame.h"
+#include "twp/twp.h"
+#include "twp/room.h"
+#include "twp/object.h"
+#include "twp/squtil.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+static SQInteger addTrigger(HSQUIRRELVM v) {
+	warning("TODO: addTrigger not implemented");
+	return 0;
+}
+
+static SQInteger clampInWalkbox(HSQUIRRELVM v) {
+	warning("TODO: clampInWalkbox not implemented");
+	return 0;
+}
+
+static SQInteger createLight(HSQUIRRELVM v) {
+	warning("TODO: createLight not implemented");
+	return 0;
+}
+
+static SQInteger enableTrigger(HSQUIRRELVM v) {
+	warning("TODO: enableTrigger not implemented");
+	return 0;
+}
+
+static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
+	warning("TODO: enterRoomFromDoor not implemented");
+	return 0;
+}
+
+static SQInteger lightBrightness(HSQUIRRELVM v) {
+	warning("TODO: lightBrightness not implemented");
+	return 0;
+}
+
+static SQInteger lightConeDirection(HSQUIRRELVM v) {
+	warning("TODO: lightConeDirection not implemented");
+	return 0;
+}
+
+static SQInteger lightConeAngle(HSQUIRRELVM v) {
+	warning("TODO: lightConeAngle not implemented");
+	return 0;
+}
+
+static SQInteger lightConeFalloff(HSQUIRRELVM v) {
+	warning("TODO: lightConeFalloff not implemented");
+	return 0;
+}
+
+static SQInteger lightCutOffRadius(HSQUIRRELVM v) {
+	warning("TODO: lightCutOffRadius not implemented");
+	return 0;
+}
+
+static SQInteger lightHalfRadius(HSQUIRRELVM v) {
+	warning("TODO: lightHalfRadius not implemented");
+	return 0;
+}
+
+static SQInteger lightTurnOn(HSQUIRRELVM v) {
+	warning("TODO: lightTurnOn not implemented");
+	return 0;
+}
+
+static SQInteger lightZRange(HSQUIRRELVM v) {
+	warning("TODO: lightZRange not implemented");
+	return 0;
+}
+
+static SQInteger defineRoom(HSQUIRRELVM v) {
+	// This command is used during the game's boot process.
+	// `defineRoom` is called once for every room in the game, passing it the room's room object.
+	// If the room has not been defined, it can not be referenced.
+	// `defineRoom` is typically called in the the DefineRooms.nut file which loads and defines every room in the game.
+	HSQOBJECT table;
+	sq_resetobject(&table);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &table)))
+		return sq_throwerror(v, "failed to get room table");
+	Common::String name;
+	sqgetf(v, table, "name", name);
+	if (name.size() == 0)
+		sqgetf(v, table, "background", name);
+	Room *room = g_engine->defineRoom(name, table);
+	debug("Define room: %s", name.c_str());
+	g_engine->_rooms.push_back(room);
+	sqpush(v, room->_table);
+	return 1;
+}
+
+static SQInteger definePseudoRoom(HSQUIRRELVM v) {
+	warning("TODO: definePseudoRoom not implemented");
+	return 0;
+}
+
+static SQInteger findRoom(HSQUIRRELVM v) {
+	warning("TODO: findRoom not implemented");
+	return 0;
+}
+
+static SQInteger masterRoomArray(HSQUIRRELVM v) {
+	warning("TODO: masterRoomArray not implemented");
+	return 0;
+}
+
+static SQInteger removeTrigger(HSQUIRRELVM v) {
+	warning("TODO: removeTrigger not implemented");
+	return 0;
+}
+
+static SQInteger roomActors(HSQUIRRELVM v) {
+	warning("TODO: roomActors not implemented");
+	return 0;
+}
+
+static SQInteger roomEffect(HSQUIRRELVM v) {
+	warning("TODO: roomEffect not implemented");
+	return 0;
+}
+
+static SQInteger roomFade(HSQUIRRELVM v) {
+	warning("TODO: roomFade not implemented");
+	return 0;
+}
+
+static SQInteger roomLayer(HSQUIRRELVM v) {
+	warning("TODO: roomLayer not implemented");
+	return 0;
+}
+
+static SQInteger roomOverlayColor(HSQUIRRELVM v) {
+	warning("TODO: roomOverlayColor not implemented");
+	return 0;
+}
+
+static SQInteger roomRotateTo(HSQUIRRELVM v) {
+	warning("TODO: roomRotateTo not implemented");
+	return 0;
+}
+
+static SQInteger roomSize(HSQUIRRELVM v) {
+	warning("TODO: roomSize not implemented");
+	return 0;
+}
+
+static SQInteger walkboxHidden(HSQUIRRELVM v) {
+	warning("TODO: walkboxHidden not implemented");
+	return 0;
+}
+
+void sqgame_register_roomlib(HSQUIRRELVM v) {
+	regFunc(v, addTrigger, "addTrigger");
+	regFunc(v, clampInWalkbox, "clampInWalkbox");
+	regFunc(v, createLight, "createLight");
+	regFunc(v, defineRoom, "defineRoom");
+	regFunc(v, definePseudoRoom, "definePseudoRoom");
+	regFunc(v, enableTrigger, "enableTrigger");
+	regFunc(v, enterRoomFromDoor, "enterRoomFromDoor");
+	regFunc(v, findRoom, "findRoom");
+	regFunc(v, lightBrightness, "lightBrightness");
+	regFunc(v, lightConeAngle, "lightConeAngle");
+	regFunc(v, lightConeDirection, "lightConeDirection");
+	regFunc(v, lightConeFalloff, "lightConeFalloff");
+	regFunc(v, lightCutOffRadius, "lightCutOffRadius");
+	regFunc(v, lightHalfRadius, "lightHalfRadius");
+	regFunc(v, lightTurnOn, "lightTurnOn");
+	regFunc(v, lightZRange, "lightZRange");
+	regFunc(v, masterRoomArray, "masterRoomArray");
+	regFunc(v, removeTrigger, "removeTrigger");
+	regFunc(v, roomActors, "roomActors");
+	regFunc(v, roomEffect, "roomEffect");
+	regFunc(v, roomFade, "roomFade");
+	regFunc(v, roomLayer, "roomLayer");
+	regFunc(v, roomRotateTo, "roomRotateTo");
+	regFunc(v, roomSize, "roomSize");
+	regFunc(v, roomOverlayColor, "roomOverlayColor");
+	regFunc(v, walkboxHidden, "walkboxHidden");
+}
+} // namespace Twp
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
index 07119a5476e..c4c99411172 100644
--- a/engines/twp/sqgame.h
+++ b/engines/twp/sqgame.h
@@ -32,6 +32,7 @@ void sqgame_register_syslib(HSQUIRRELVM v);
 void sqgame_register_objlib(HSQUIRRELVM v);
 void sqgame_register_genlib(HSQUIRRELVM v);
 void sqgame_register_actorlib(HSQUIRRELVM v);
+void sqgame_register_roomlib(HSQUIRRELVM v);
 
 } // namespace Twp
 #endif
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 0c345993fed..fccbd1cedb7 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -96,6 +96,7 @@ Vm::Vm() {
 	sqgame_register_genlib(v);
 	sqgame_register_objlib(v);
 	sqgame_register_actorlib(v);
+	sqgame_register_roomlib(v);
 
 	// TODO: constants
 	SQObject platform = sqtoobj(v, 666);


Commit: 618c0010168eae4d297aa66fb6d814581f0044eb
    https://github.com/scummvm/scummvm/commit/618c0010168eae4d297aa66fb6d814581f0044eb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add soundlib stubs

Changed paths:
  A engines/twp/soundlib.cpp
    engines/twp/module.mk
    engines/twp/sqgame.h
    engines/twp/vm.cpp


diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 7aacb5fff78..c706c48ccea 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -47,6 +47,7 @@ MODULE_OBJS = \
 	camera.o \
 	actorlib.o \
 	roomlib.o \
+	soundlib.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
new file mode 100644
index 00000000000..3f7d2a49224
--- /dev/null
+++ b/engines/twp/soundlib.cpp
@@ -0,0 +1,153 @@
+/* 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 "twp/sqgame.h"
+#include "twp/twp.h"
+#include "twp/room.h"
+#include "twp/object.h"
+#include "twp/squtil.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+static SQInteger actorSound(HSQUIRRELVM v) {
+	warning("TODO: actorSound not implemented");
+	return 0;
+}
+
+static SQInteger defineSound(HSQUIRRELVM v) {
+	warning("TODO: defineSound not implemented");
+	return 0;
+}
+
+static SQInteger fadeOutSound(HSQUIRRELVM v) {
+	warning("TODO: fadeOutSound not implemented");
+	return 0;
+}
+
+static SQInteger isSoundPlaying(HSQUIRRELVM v) {
+	warning("TODO: isSoundPlaying not implemented");
+	return 0;
+}
+
+static SQInteger playObjectSound(HSQUIRRELVM v) {
+	warning("TODO: playObjectSound not implemented");
+	return 0;
+}
+
+static SQInteger playSound(HSQUIRRELVM v) {
+	warning("TODO: playSound not implemented");
+	return 0;
+}
+
+static SQInteger playSoundVolume(HSQUIRRELVM v) {
+	warning("TODO: playSoundVolume not implemented");
+	return 0;
+}
+
+static SQInteger loadSound(HSQUIRRELVM v) {
+	warning("TODO: loadSound not implemented");
+	return 0;
+}
+
+static SQInteger loopMusic(HSQUIRRELVM v) {
+	warning("TODO: loopMusic not implemented");
+	return 0;
+}
+
+static SQInteger loopObjectSound(HSQUIRRELVM v) {
+	warning("TODO: loopObjectSound not implemented");
+	return 0;
+}
+
+static SQInteger loopSound(HSQUIRRELVM v) {
+	warning("TODO: loopSound not implemented");
+	return 0;
+}
+
+static SQInteger masterSoundVolume(HSQUIRRELVM v) {
+	warning("TODO: masterSoundVolume not implemented");
+	return 0;
+}
+
+static SQInteger musicMixVolume(HSQUIRRELVM v) {
+	warning("TODO: musicMixVolume not implemented");
+	return 0;
+}
+
+static SQInteger playMusic(HSQUIRRELVM v) {
+	warning("TODO: playMusic not implemented");
+	return 0;
+}
+
+static SQInteger soundMixVolume(HSQUIRRELVM v) {
+	warning("TODO: soundMixVolume not implemented");
+	return 0;
+}
+
+static SQInteger soundVolume(HSQUIRRELVM v) {
+	warning("TODO: soundVolume not implemented");
+	return 0;
+}
+
+static SQInteger stopAllSounds(HSQUIRRELVM v) {
+	warning("TODO: stopAllSounds not implemented");
+	return 0;
+}
+
+static SQInteger stopMusic(HSQUIRRELVM v) {
+	warning("TODO: stopMusic not implemented");
+	return 0;
+}
+
+static SQInteger stopSound(HSQUIRRELVM v) {
+	warning("TODO: stopSound not implemented");
+	return 0;
+}
+
+static SQInteger talkieMixVolume(HSQUIRRELVM v) {
+	warning("TODO: talkieMixVolume not implemented");
+	return 0;
+}
+
+void sqgame_register_soundlib(HSQUIRRELVM v) {
+  regFunc(v, actorSound, "actorSound");
+  regFunc(v, defineSound, "defineSound");
+  regFunc(v, fadeOutSound, "fadeOutSound");
+  regFunc(v, isSoundPlaying, "isSoundPlaying");
+  regFunc(v, loadSound, "loadSound");
+  regFunc(v, loopMusic, "loopMusic");
+  regFunc(v, loopObjectSound, "loopObjectSound");
+  regFunc(v, loopSound, "loopSound");
+  regFunc(v, masterSoundVolume, "masterSoundVolume");
+  regFunc(v, musicMixVolume, "musicMixVolume");
+  regFunc(v, playMusic, "playMusic");
+  regFunc(v, playObjectSound, "playObjectSound");
+  regFunc(v, playSound, "playSound");
+  regFunc(v, playSoundVolume, "playSoundVolume");
+  regFunc(v, soundMixVolume, "soundMixVolume");
+  regFunc(v, soundVolume, "soundVolume");
+  regFunc(v, stopAllSounds, "stopAllSounds");
+  regFunc(v, stopMusic, "stopMusic");
+  regFunc(v, stopSound, "stopSound");
+  regFunc(v, talkieMixVolume, "talkieMixVolume");
+}
+} // namespace Twp
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
index c4c99411172..6ca14c8da0e 100644
--- a/engines/twp/sqgame.h
+++ b/engines/twp/sqgame.h
@@ -33,6 +33,7 @@ void sqgame_register_objlib(HSQUIRRELVM v);
 void sqgame_register_genlib(HSQUIRRELVM v);
 void sqgame_register_actorlib(HSQUIRRELVM v);
 void sqgame_register_roomlib(HSQUIRRELVM v);
+void sqgame_register_soundlib(HSQUIRRELVM v);
 
 } // namespace Twp
 #endif
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index fccbd1cedb7..d104cfde663 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -97,6 +97,7 @@ Vm::Vm() {
 	sqgame_register_objlib(v);
 	sqgame_register_actorlib(v);
 	sqgame_register_roomlib(v);
+	sqgame_register_soundlib(v);
 
 	// TODO: constants
 	SQObject platform = sqtoobj(v, 666);


Commit: b6613e04ec25177c442de758033ab53138f94524
    https://github.com/scummvm/scummvm/commit/b6613e04ec25177c442de758033ab53138f94524
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add several function implementations

Changed paths:
  A engines/twp/callback.cpp
  A engines/twp/callback.h
  A engines/twp/prefs.cpp
  A engines/twp/prefs.h
  A engines/twp/tsv.cpp
  A engines/twp/tsv.h
    engines/twp/genlib.cpp
    engines/twp/ggpack.cpp
    engines/twp/ids.cpp
    engines/twp/ids.h
    engines/twp/module.mk
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/sqgame.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp


diff --git a/engines/twp/callback.cpp b/engines/twp/callback.cpp
new file mode 100644
index 00000000000..08e3dd08453
--- /dev/null
+++ b/engines/twp/callback.cpp
@@ -0,0 +1,38 @@
+/* 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 "twp/callback.h"
+
+namespace Twp {
+
+void Callback::call() {
+	sqcall(name, args);
+}
+
+bool Callback::update(float elapsed) {
+	self.elapsed += elapsed;
+	bool result = self.elapsed > self.duration;
+	if (result)
+		call();
+	return result;
+}
+
+} // namespace Twp
diff --git a/engines/twp/callback.h b/engines/twp/callback.h
new file mode 100644
index 00000000000..5cb65095224
--- /dev/null
+++ b/engines/twp/callback.h
@@ -0,0 +1,45 @@
+/* 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 TWP_CALLBACK_H
+#define TWP_CALLBACK_H
+
+#include "common/array.h"
+#include "common/str.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+class Callback {
+public:
+	int id = 0;
+	Common::String name;
+	Common::Array<HSQOBJECT> args;
+	float duration = 0.f;
+	float elapsed = 0.f;
+
+public:
+	void call();
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 27bd92258ae..d011cde01cc 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -211,8 +211,8 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
 		int im;
-		if (SQ_FAILED (sqget(v, 5, im)))
-      return sq_throwerror(v, "failed to get interpolation method");
+		if (SQ_FAILED(sqget(v, 5, im)))
+			return sq_throwerror(v, "failed to get interpolation method");
 		pos = Math::Vector2d(x, y);
 		interpolation = (InterpolationKind)im;
 	} else {
@@ -284,9 +284,21 @@ static SQInteger frameCounter(HSQUIRRELVM v) {
 }
 
 static SQInteger getUserPref(HSQUIRRELVM v) {
-	// TODO: getUserPref
-	warning("getUserPref not implemented");
-	return 0;
+	Common::String key;
+	if (SQ_FAILED(sqget(v, 2, key))) {
+		return sq_throwerror(v, "failed to get key");
+	}
+	if (g_engine->getPrefs().hasPrefs(key)) {
+		sqpush(v, g_engine->getPrefs().prefsAsJson(key));
+		return 1;
+	}
+	if (sq_gettop(v) == 3) {
+		HSQOBJECT obj;
+		sq_getstackobj(v, 3, &obj);
+		sqpush(v, obj);
+		return 1;
+	}
+	return sq_throwerror(v, "invalid argument in getUserPref");
 }
 
 static SQInteger getPrivatePref(HSQUIRRELVM v) {
@@ -399,10 +411,22 @@ static SQInteger sqrandom(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Returns an array of all the lines of the given `filename`.
 static SQInteger loadArray(HSQUIRRELVM v) {
-	// TODO: loadArray
-	warning("loadArray not implemented");
-	return 0;
+	const SQChar *orgFilename = nullptr;
+	if (SQ_FAILED(sqget(v, 2, orgFilename)))
+		return sq_throwerror(v, "failed to get filename");
+	debug("loadArray: %s", orgFilename);
+	Common::String filename = g_engine->getPrefs().getKey(orgFilename);
+	GGPackEntryReader entry;
+	entry.open(g_engine->_pack, g_engine->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
+	sq_newarray(v, 0);
+	while (!entry.eos()) {
+		Common::String line = entry.readLine();
+		sq_pushstring(v, line.c_str(), -1);
+		sq_arrayappend(v, -2);
+	}
+	return 1;
 }
 
 static SQInteger markAchievement(HSQUIRRELVM v) {
@@ -543,16 +567,33 @@ static SQInteger strreplace(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Splits the string `str` into substrings using a string separator.
 static SQInteger strsplit(HSQUIRRELVM v) {
-	// TODO: strsplit
-	warning("strsplit not implemented");
-	return 0;
+	Common::String str;
+	const SQChar *delimiter;
+	if (SQ_FAILED(sqget(v, 2, str)))
+		return sq_throwerror(v, "Failed to get str");
+	if (SQ_FAILED(sqget(v, 3, delimiter)))
+		return sq_throwerror(v, "Failed to get delimiter");
+	sq_newarray(v, 0);
+	char *s = str.begin();
+	char *tok = strtok(s, delimiter);
+	while (tok) {
+		sq_pushstring(v, tok, -1);
+		sq_arrayappend(v, -2);
+		tok = strtok(NULL, delimiter);
+	}
+	return 1;
 }
 
 static SQInteger translate(HSQUIRRELVM v) {
-	// TODO: translate
-	warning("translate not implemented");
-	return 0;
+	const SQChar *text;
+	if (SQ_FAILED(sqget(v, 2, text)))
+		return sq_throwerror(v, "Failed to get text");
+	Common::String newText = g_engine->getTextDb().getText(text);
+	// debug("translate({text}): {newText}");
+	sqpush(v, newText);
+	return 1;
 }
 
 void sqgame_register_genlib(HSQUIRRELVM v) {
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index ebe4558ff08..3acf5422097 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -608,7 +608,7 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 		int offset = (int)file["offset"]->asIntegerNumber();
 		int size = (int)file["size"]->asIntegerNumber();
 		_entries[filename] = GGPackEntry{offset, size};
-		debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
+		//debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
 	}
 	delete value;
 
diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
index 77d17a3a659..9f2787584d0 100644
--- a/engines/twp/ids.cpp
+++ b/engines/twp/ids.cpp
@@ -103,4 +103,17 @@ int getCallbackId() {
 int newLightId() {
 	return gLightId++;
 }
+
+Facing getOppositeFacing(Facing facing) {
+	switch (facing) {
+	case FACE_FRONT:
+		return FACE_BACK;
+	case FACE_BACK:
+		return FACE_FRONT;
+	case FACE_LEFT:
+		return FACE_RIGHT;
+	case FACE_RIGHT:
+		return FACE_LEFT;
+	}
+}
 }
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index 01cd189d770..4dd91c5d571 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -22,43 +22,221 @@
 #ifndef TWP_IDS_H
 #define TWP_IDS_H
 
-#define START_ACTORID    1000
-#define END_ACTORID      2000
-#define START_ROOMID     2000
-#define END_ROOMID       3000
-#define START_OBJECTID   3000
-#define END_OBJECTID     100000
-#define START_LIGHTID    100000
-#define END_LIGHTID      200000
+#define START_ACTORID 1000
+#define END_ACTORID 2000
+#define START_ROOMID 2000
+#define END_ROOMID 3000
+#define START_OBJECTID 3000
+#define END_OBJECTID 100000
+#define START_LIGHTID 100000
+#define END_LIGHTID 200000
 #define START_SOUNDDEFID 200000
-#define END_SOUNDDEFID   250000
-#define START_SOUNDID    250000
-#define END_SOUNDID      300000
-#define START_THREADID   300000
-#define END_THREADID     8000000
+#define END_SOUNDDEFID 250000
+#define START_SOUNDID 250000
+#define END_SOUNDID 300000
+#define START_THREADID 300000
+#define END_THREADID 8000000
 #define START_CALLBACKID 8000000
-#define END_CALLBACKID   10000000
+#define END_CALLBACKID 10000000
 
-#define FAR_LOOK 		8
-#define VERB_WALKTO 	1
-#define VERB_LOOKAT 	2
-#define VERB_TALKTO 	3
-#define VERB_PICKUP 	4
-#define VERB_OPEN 		5
-#define VERB_CLOSE 		6
-#define VERB_PUSH 		7
-#define VERB_PULL 		8
-#define VERB_GIVE 		9
-#define VERB_USE 		10
-#define VERB_DIALOG 	13
+#define ALL  1
+#define HERE  0
+#define GONE  4
+#define OFF  0
+#define ON  1
+#define FULL  0
+#define EMPTY  1
+#define OPEN  1
+#define CLOSED  0
+#define FALSE  0
+#define TRUE  1
+#define MOUSE  1
+#define CONTROLLER  2
+#define DIRECTDRIVE  3
+#define TOUCH  4
+#define REMOTE  5
+#define FADE_IN  0
+#define FADE_OUT  1
+#define FADE_WOBBLE  2
+#define FADE_WOBBLE_TO_SEPIA  3
+#define FACE_FLIP  16
+#define DIR_FRONT  4
+#define DIR_BACK  8
+#define DIR_LEFT  2
+#define DIR_RIGHT  1
+#define LINEAR  0
+#define EASE_IN  1
+#define EASE_INOUT  2
+#define EASE_OUT  3
+#define SLOW_EASE_IN  4
+#define SLOW_EASE_OUT  5
+#define LOOPING  0x10
+#define SWING  0X20
+#define ALIGN_LEFT    0x0000000010000000
+#define ALIGN_CENTER  0x0000000020000000
+#define ALIGN_RIGHT   0x0000000040000000
+#define ALIGN_TOP     0xFFFFFFFF80000000
+#define ALIGN_BOTTOM  0x0000000001000000
+#define LESS_SPACING  0x0000000000200000
+#define EX_ALLOW_SAVEGAMES  0x01
+#define EX_POP_CHARACTER_SELECTION  0x02
+#define EX_CAMERA_TRACKING  0x03
+#define EX_BUTTON_HOVER_SOUND  0x04
+#define EX_RESTART  0x06
+#define EX_IDLE_TIME  0x07
+#define EX_AUTOSAVE  0x08
+#define EX_AUTOSAVE_STATE  0x09
+#define EX_DISABLE_SAVESYSTEM  0x0A
+#define EX_SHOW_OPTIONS  11
+#define EX_OPTIONS_MUSIC  12
+#define EX_FORCE_TALKIE_TEXT  13
+#define GRASS_BACKANDFORTH  0x00
+#define DOOR  0x40
+#define DOOR_LEFT  0x140
+#define DOOR_RIGHT  0x240
+#define DOOR_BACK  0x440
+#define DOOR_FRONT  0x840
+#define FAR_LOOK  0x8
+#define USE_WITH  2
+#define USE_ON  4
+#define USE_IN  32
+#define GIVEABLE  0x1000
+#define TALKABLE  0x2000
+#define IMMEDIATE  0x4000
+#define FEMALE  0x80000
+#define MALE  0x100000
+#define PERSON  0x200000
+#define REACH_HIGH  0x8000
+#define REACH_MED  0x10000
+#define REACH_LOW  0x20000
+#define REACH_NONE  0x40000
+#define VERB_WALKTO  1
+#define VERB_LOOKAT  2
+#define VERB_TALKTO  3
+#define VERB_PICKUP  4
+#define VERB_OPEN  5
+#define VERB_CLOSE  6
+#define VERB_PUSH  7
+#define VERB_PULL  8
+#define VERB_GIVE  9
+#define VERB_USE  10
+#define VERB_DIALOG  13
+#define VERBFLAG_INSTANT  1
+#define NO  0
+#define YES  1
+#define UNSELECTABLE  0
+#define SELECTABLE  1
+#define TEMP_UNSELECTABLE  2
+#define TEMP_SELECTABLE  3
+#define MAC  1
+#define WIN  2
+#define LINUX  3
+#define XBOX  4
+#define IOS  5
+#define ANDROID  6
+#define SWITCH  7
+#define PS4  8
+#define EFFECT_NONE          0
+#define EFFECT_SEPIA         1
+#define EFFECT_EGA           2
+#define EFFECT_VHS           3
+#define EFFECT_GHOST         4
+#define EFFECT_BLACKANDWHITE 5
+#define UI_INPUT_ON  1
+#define UI_INPUT_OFF  2
+#define UI_VERBS_ON  4
+#define UI_VERBS_OFF  8
+#define UI_HUDOBJECTS_ON  0x10
+#define UI_HUDOBJECTS_OFF  0x20
+#define UI_CURSOR_ON  0x40
+#define UI_CURSOR_OFF  0x80
 
-#define GONE 			4
-#define USE_WITH 		2
-#define USE_ON 			4
-#define USE_IN 			32
+// these codes corresponds to SDL key codes used in TWP
+#define KEY_UP  0x40000052
+#define KEY_RIGHT  0x4000004F
+#define KEY_DOWN  0x40000051
+#define KEY_LEFT  0x40000050
+#define KEY_PAD1  0x40000059
+#define KEY_PAD2  0x4000005A
+#define KEY_PAD3  0x4000005B
+#define KEY_PAD4  0x4000005C
+#define KEY_PAD5  0x4000005D
+#define KEY_PAD6  0x4000005E
+#define KEY_PAD7  0x4000005F
+#define KEY_PAD8  0x40000056
+#define KEY_PAD9  0x40000061
+#define KEY_ESCAPE  0x08
+#define KEY_TAB  0x09
+#define KEY_RETURN  0x0D
+#define KEY_BACKSPACE  0x1B
+#define KEY_SPACE  0X20
+#define KEY_A  0x61
+#define KEY_B  0x62
+#define KEY_C  0x63
+#define KEY_D  0x64
+#define KEY_E  0x65
+#define KEY_F  0x66
+#define KEY_G  0x67
+#define KEY_H  0x68
+#define KEY_I  0x69
+#define KEY_J  0x6A
+#define KEY_K  0x6B
+#define KEY_L  0x6C
+#define KEY_M  0x6D
+#define KEY_N  0x6E
+#define KEY_O  0x6F
+#define KEY_P  0x70
+#define KEY_Q  0x71
+#define KEY_R  0x72
+#define KEY_S  0x73
+#define KEY_T  0x74
+#define KEY_U  0x75
+#define KEY_V  0x76
+#define KEY_W  0x77
+#define KEY_X  0x78
+#define KEY_Y  0x79
+#define KEY_Z  0x7A
+#define KEY_0  0x30
+#define KEY_1  0x31
+#define KEY_2  0x32
+#define KEY_3  0x33
+#define KEY_4  0x34
+#define KEY_5  0x35
+#define KEY_6  0x36
+#define KEY_7  0x37
+#define KEY_8  0x38
+#define KEY_9  0x39
+#define KEY_F1  0x4000003A
+#define KEY_F2  0x4000003B
+#define KEY_F3  0x4000003C
+#define KEY_F4  0x4000003D
+#define KEY_F5  0x4000003E
+#define KEY_F6  0x4000003F
+#define KEY_F7  0x40000040
+#define KEY_F8  0x40000041
+#define KEY_F9  0x40000042
+#define KEY_F10  0x40000043
+#define KEY_F11  0x40000044
+#define KEY_F12  0x40000045
+
+#define BUTTON_A  0x3E8
+#define BUTTON_B  0x3E9
+#define BUTTON_X  0x3EA
+#define BUTTON_Y  0x3EB
+#define BUTTON_START  0x3EC
+#define BUTTON_BACK  0x3EC
+#define BUTTON_MOUSE_LEFT  0x3ED
+#define BUTTON_MOUSE_RIGHT  0x3EE
 
 namespace Twp {
 
+enum Facing {
+	FACE_RIGHT = 1,
+	FACE_LEFT = 2,
+	FACE_FRONT = 4,
+	FACE_BACK = 8
+};
+
 bool isThread(int id);
 bool isRoom(int id);
 bool isActor(int id);
@@ -78,6 +256,8 @@ int newLightId();
 void setCallbackId(int id);
 int getCallbackId();
 
-}
+Facing getOppositeFacing(Facing facing);
+
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index c706c48ccea..7f49e0a8c0e 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -48,6 +48,8 @@ MODULE_OBJS = \
 	actorlib.o \
 	roomlib.o \
 	soundlib.o \
+	prefs.o \
+	tsv.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 584058589e6..d71fda125f3 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -130,7 +130,7 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 	return obj;
 }
 
-int Object::getId() {
+int Object::getId() const {
 	SQInteger result = 0;
 	sqgetf(_table, "_id", result);
 	return (int)result;
@@ -363,4 +363,40 @@ void Object::stopObjectMotors() {
 	debug("TODO: stopObjectMotors");
 }
 
+void Object::setFacing(Facing facing) {
+  if (_facing != facing) {
+    debug("set facing: %d", facing);
+    bool update = !(((_facing == FACE_LEFT) && (facing == FACE_RIGHT)) || ((_facing == FACE_RIGHT) && (facing == FACE_LEFT)));
+    _facing = facing;
+    if (update && _nodeAnim)
+      play(_animName, _animLoop);
+  }
+}
+
+Facing Object::getDoorFacing() {
+  int flags = getFlags();
+  if (flags & DOOR_LEFT)
+    return FACE_LEFT;
+  else if (flags & DOOR_RIGHT)
+    return FACE_RIGHT;
+  else if (flags & DOOR_FRONT)
+    return FACE_FRONT;
+  else
+    return FACE_BACK;
+}
+
+bool Object::inInventory() {
+  return isObject(getId()) && getIcon().size() > 0;
+}
+
+bool Object::contains(Math::Vector2d pos) {
+  Math::Vector2d p = pos - _node->getPos() - _node->getOffset();
+  return _hotspot.contains(p.getX(), p.getY());
+}
+
+void Object::dependentOn(Object* dependentObj, int state) {
+  _dependentState = state;
+  _dependentObj = dependentObj;
+}
+
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 84ce1742c03..18dafe5e15d 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -27,6 +27,7 @@
 #include "common/str.h"
 #include "math/vector2d.h"
 #include "twp/squirrel/squirrel.h"
+#include "twp/ids.h"
 
 namespace Twp {
 
@@ -45,13 +46,6 @@ enum Direction {
 	dBack = 8
 };
 
-enum Facing {
-	FACE_RIGHT = 1,
-	FACE_LEFT = 2,
-	FACE_FRONT = 4,
-	FACE_BACK = 8
-};
-
 struct ObjectAnimation {
 	Common::String name;
 	Common::String sheet;
@@ -71,6 +65,17 @@ public:
 	virtual void trig() = 0;
 };
 
+struct VerbId {
+	int id = 0;
+};
+
+struct Sentence {
+    VerbId verb;
+    Object* noun1 = nullptr;
+	Object* noun2 = nullptr;
+	bool enabled = false;
+};
+
 class Anim;
 class Room;
 class Node;
@@ -81,7 +86,7 @@ public:
 	Object();
 	Object(HSQOBJECT o, const Common::String& key);
 
-	int getId();
+	int getId() const;
 
 	// Changes the `state` of an object, although this can just be a internal state,
 	//
@@ -114,19 +119,22 @@ public:
 	float popScale() const;
 
 	int defaultVerbId();
+	void setFacing(Facing facing);
 
 	Math::Vector2d getUsePos();
+	Facing getDoorFacing();
 
 	void setIcon(int fps, const Common::StringArray& icons);
 	void setIcon(const Common::String& icon);
 	Common::String getIcon();
+	bool inInventory();
 
 	int getFlags();
-
+	bool contains(Math::Vector2d pos);
 	void setRoom(Room* room);
-
 	void delObject();
 	void stopObjectMotors();
+	void dependentOn(Object* dependentObj, int state);
 
 private:
 	Common::String suffix() const;
@@ -182,6 +190,7 @@ public:
     Object* _dependentObj;
     float _popElapsed = 0.f;
     int _popCount = 0;
+	Sentence _exec;
 };
 
 } // namespace Twp
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 26107c4080e..d30852b0c02 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -24,6 +24,7 @@
 #include "twp/squtil.h"
 #include "twp/object.h"
 #include "twp/room.h"
+#include "twp/scenegraph.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
@@ -141,8 +142,8 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 // playObjectSound(randomfrom(soundDrip1, soundDrip2, soundDrip3), radioStudioBucket)
 // deleteObject(drip)
 static SQInteger deleteObject(HSQUIRRELVM v) {
-	// TODO: deleteObject
-	warning("deleteObject not implemented");
+	Object *obj = sqobj(v, 2);
+	obj->delObject();
 	return 0;
 }
 
@@ -158,9 +159,17 @@ static SQInteger deleteObject(HSQUIRRELVM v) {
 //     if (button == Phone.phoneReceiver) {    ... }
 // }
 static SQInteger findObjectAt(HSQUIRRELVM v) {
-	// TODO: findObjectAt
-	warning("findObjectAt not implemented");
-	return 0;
+	int x, y;
+	if (SQ_FAILED(sqget(v, 2, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 3, y)))
+		return sq_throwerror(v, "failed to get y");
+	Object *obj = g_engine->objAt(Math::Vector2d(x, y));
+	if (!obj)
+		sq_pushnull(v);
+	else
+		sqpush(v, obj->_table);
+	return 1;
 }
 
 static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
@@ -169,10 +178,14 @@ static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns true if the object is actually an object and not something else.
+//
+// .. code-block:: Squirrel
+// if (isObject(obj) && objectValidUsePos(obj) && objectTouchable(obj)) {
 static SQInteger isObject(HSQUIRRELVM v) {
-	// TODO: isObject
-	warning("isObject not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	sqpush(v, obj && isObject(obj->getId()));
+	return 1;
 }
 
 static SQInteger jiggleInventory(HSQUIRRELVM v) {
@@ -187,15 +200,46 @@ static SQInteger jiggleObject(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Works exactly the same as playObjectState, but plays the animation as a continuous loop, playing the specified animation.
+//
+// .. code-block:: Squirrel
+// loopObjectState(aStreetFire, 0)
+// loopObjectState(flies, 3)
 static SQInteger loopObjectState(HSQUIRRELVM v) {
-	// TODO: loopObjectState
-	warning("loopObjectState not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	if (sq_gettype(v, 3) == OT_INTEGER) {
+		int index;
+		if (SQ_FAILED(sqget(v, 3, index)))
+			return sq_throwerror(v, "failed to get state");
+		obj->play(index, true);
+	} else if (sq_gettype(v, 3) == OT_STRING) {
+		const SQChar *state;
+		if (SQ_FAILED(sqget(v, 3, state)))
+			return sq_throwerror(v, "failed to get state (string)");
+		obj->play(state, true);
+	} else {
+		return sq_throwerror(v, "failed to get state");
+	}
 	return 0;
 }
 
+// Sets an object's alpha (transparency) in the range of 0.0 to 1.0.
+// Setting an object's color will set it's alpha back to 1.0, ie completely opaque.
+//
+// .. code-block:: Squirrel
+// objectAlpha(cloud, 0.5)
 static SQInteger objectAlpha(HSQUIRRELVM v) {
-	// TODO: objectAlpha
-	warning("objectAlpha not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		float alpha = 0.0f;
+		if (SQ_FAILED(sq_getfloat(v, 3, &alpha)))
+			return sq_throwerror(v, "failed to get alpha");
+		// TODO: if (obj->_alphaTo)
+		//  obj->_alphaTo->disable();
+		obj->_node->setAlpha(alpha);
+	}
 	return 0;
 }
 
@@ -205,6 +249,36 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+static SQInteger objectAt(HSQUIRRELVM v) {
+	HSQOBJECT o;
+	sq_getstackobj(v, 2, &o);
+
+	SQInteger id;
+	sqgetf(o, "_id", id);
+
+	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
+		SQObjectPtr id2;
+		_table(o->_table)->Get(sqtoobj(v, "_id"), id2);
+		return id == _integer(id2);
+	});
+
+	if (!pObj)
+		return sq_throwerror(v, "failed to get object");
+
+	// TODO:
+	// Object* obj = *pObj;
+	// SQInteger x, y;
+	// if (SQ_FAILED(sq_getinteger(v, 3, &x)))
+	// 	return sq_throwerror(v, "failed to get x");
+	// if (SQ_FAILED(sq_getinteger(v, 4, &y)))
+	// 	return sq_throwerror(v, "failed to get y");
+	// obj->x = x;
+	// obj->y = y;
+	// debug("Object at: %lld, %lld", x, y);
+
+	return 0;
+}
+
 static SQInteger objectBumperCycle(HSQUIRRELVM v) {
 	// TODO: objectBumperCycle
 	warning("objectBumperCycle not implemented");
@@ -212,38 +286,103 @@ static SQInteger objectBumperCycle(HSQUIRRELVM v) {
 }
 
 static SQInteger objectCenter(HSQUIRRELVM v) {
-	// TODO: objectCenter
-	warning("objectCenter not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	Math::Vector2d pos = obj->_node->getPos() + obj->_usePos;
+	sqpush(v, pos);
+	return 1;
 }
 
+// Sets an object's color. The color is an int in the form of 0xRRGGBB
+//
+// .. code-block:: Squirrel
+// objectColor(warningSign, 0x808000)
 static SQInteger objectColor(HSQUIRRELVM v) {
 	// TODO: objectColor
-	warning("objectColor not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		int color = 0;
+		if (SQ_FAILED(sqget(v, 3, color)))
+			return sq_throwerror(v, "failed to get color");
+		obj->_node->setColor(Color::rgb(color));
+	}
 	return 0;
 }
 
 static SQInteger objectDependentOn(HSQUIRRELVM v) {
-	// TODO: objectDependentOn
-	warning("objectDependentOn not implemented");
+	Object *child = sqobj(v, 2);
+	if (!child)
+		return sq_throwerror(v, "failed to get child object");
+	Object *parent = sqobj(v, 3);
+	if (!parent)
+		return sq_throwerror(v, "failed to get parent object");
+	int state = 0;
+	if (SQ_FAILED(sqget(v, 4, state)))
+		return sq_throwerror(v, "failed to get state");
+	child->dependentOn(parent, state);
 	return 0;
 }
 
+// Sets how many frames per second (fpsRate) the object will animate at.
+//
+// .. code-block:: Squirrel
+// objectFPS(pigeon1, 15)
 static SQInteger objectFPS(HSQUIRRELVM v) {
-	// TODO: objectFPS
-	warning("objectFPS not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		float fps = 0.0f;
+		if (SQ_FAILED(sqget(v, 3, fps)))
+			return sq_throwerror(v, "failed to get fps");
+		obj->_fps = fps;
+	}
 	return 0;
 }
 
+// Sets if an object is hidden or not. If the object is hidden, it is no longer displayed or touchable.
+//
+// .. code-block:: Squirrel
+// objectHidden(oldRags, YES)
 static SQInteger objectHidden(HSQUIRRELVM v) {
-	// TODO: objectHidden
-	warning("objectHidden not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		int hidden = 0;
+		sqget(v, 3, hidden);
+		debug("Sets object visible %s to %s", obj->_name.c_str(), hidden == 0 ? "true" : "false");
+		obj->_node->setVisible(hidden == 0);
+	}
 	return 0;
 }
 
+// Sets the touchable area of an actor or object.
+// This is a rectangle enclosed by the specified coordinates.
+// We also use this on the postalworker to enlarge his touchable area to make it easier to click on him while he's sorting mail.
+//
+// .. code-block:: Squirrel
+// objectHotspot(willie, 14, 0, 14, 62)         // Willie standing up
+// objectHotspot(willie, -28, 0, 28, 50)        // Willie lying down drunk
 static SQInteger objectHotspot(HSQUIRRELVM v) {
-	// TODO: objectHotspot
-	warning("objectHotspot not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object or actor");
+	if (sq_gettop(v) == 2) {
+		Math::Vector2d pos = obj->_node->getAbsPos();
+		sqpush(v, Rectf::fromPosAndSize(Math::Vector2d(obj->_hotspot.left + pos.getX(), obj->_hotspot.bottom + pos.getY()), Math::Vector2d(obj->_hotspot.width(), obj->_hotspot.height())));
+		return 1;
+	}
+	int left = 0;
+	int top = 0;
+	int right = 0;
+	int bottom = 0;
+	if (SQ_FAILED(sqget(v, 3, left)))
+		return sq_throwerror(v, "failed to get left");
+	if (SQ_FAILED(sqget(v, 4, top)))
+		return sq_throwerror(v, "failed to get top");
+	if (SQ_FAILED(sqget(v, 5, right)))
+		return sq_throwerror(v, "failed to get right");
+	if (SQ_FAILED(sqget(v, 6, bottom)))
+		return sq_throwerror(v, "failed to get bottom");
+	obj->_hotspot = Common::Rect(left, top, right - left + 1, bottom - top + 1);
 	return 0;
 }
 
@@ -355,46 +494,128 @@ static SQInteger objectShader(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Changes the state of an object, although this can just be a internal state,
+//
+// it is typically used to change the object's image as it moves from it's current state to another.
+// Behind the scenes, states as just simple ints. State0, State1, etc.
+// Symbols like CLOSED and OPEN and just pre-defined to be 0 or 1.
+// State 0 is assumed to be the natural state of the object, which is why OPEN is 1 and CLOSED is 0 and not the other way around.
+// This can be a little confusing at first.
+// If the state of an object has multiple frames, then the animation is played when changing state, such has opening the clock.
+// GONE is a unique in that setting an object to GONE both sets its graphical state to 1, and makes it untouchable. Once an object is set to GONE, if you want to make it visible and touchable again, you have to set both:
+//
+// .. code-block:: Squirrel
+// objectState(coin, HERE)
+// objectTouchable(coin, YES)
 static SQInteger objectState(HSQUIRRELVM v) {
-	// TODO: objectState
-	warning("objectState not implemented");
-	return 0;
+	if (sq_gettype(v, 2) == OT_NULL)
+		return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 2) {
+		sqpush(v, obj->_state);
+		return 1;
+	} else if (nArgs == 3) {
+		int state;
+		if (SQ_FAILED(sqget(v, 3, state)))
+			return sq_throwerror(v, "failed to get state");
+		obj->setState(state);
+		return 0;
+	} else {
+		return sq_throwerror(v, "invalid number of arguments");
+	}
 }
 
+// Gets or sets if an object is player touchable.
 static SQInteger objectTouchable(HSQUIRRELVM v) {
-	// TODO: objectTouchable
-	warning("objectTouchable not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 2) {
+		sqpush(v, obj->_touchable);
+		return 1;
+	}
+	if (nArgs == 3) {
+		bool touchable;
+		if (SQ_FAILED(sqget(v, 3, touchable)))
+			return sq_throwerror(v, "failed to get touchable");
+		obj->_touchable = touchable;
+		return 0;
+	}
 }
 
+//  Sets the zsort order of an object, essentially the order in which an object is drawn on the screen.
+// A sort order of 0 is the bottom of the screen.
+// Actors typically have a sort order of their Y position.
+//
+// .. code-block:: Squirrel
+// objectSort(censorBox, 0)   // Will be on top of everything.
 static SQInteger objectSort(HSQUIRRELVM v) {
-	// TODO: objectSort
-	warning("objectSort not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	int zsort;
+	if (SQ_FAILED(sqget(v, 3, zsort)))
+		return sq_throwerror(v, "failed to get zsort");
+	obj->_node->setZSort(zsort);
 	return 0;
 }
 
+// Sets the location an actor will stand at when interacting with this object.
+// Directions are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT
+//
+// .. code-block:: Squirrel
+// objectUsePos(popcornObject, -13, 0, FACE_RIGHT)
 static SQInteger objectUsePos(HSQUIRRELVM v) {
-	// TODO: objectUsePos
-	warning("objectUsePos not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	int x, y, dir;
+	if (SQ_FAILED(sqget(v, 3, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 4, y)))
+		return sq_throwerror(v, "failed to get y");
+	if (SQ_FAILED(sqget(v, 5, dir)))
+		return sq_throwerror(v, "failed to get direction");
+	obj->_usePos = Math::Vector2d(x, y);
+	obj->_useDir = (Direction)dir;
 	return 0;
 }
 
+// Returns the x of the object's use position.
+//
+// .. code-block:: Squirrel
+// objectUsePosX(dimeLoc)
 static SQInteger objectUsePosX(HSQUIRRELVM v) {
-	// TODO: objectUsePosX
-	warning("objectUsePosX not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	sqpush(v, obj->getUsePos().getX());
+	return 1;
 }
 
+// Returns the y of the object's use position.
+//
+// .. code-block:: Squirrel
+// objectUsePosY(dimeLoc)
 static SQInteger objectUsePosY(HSQUIRRELVM v) {
-	// TODO: objectUsePosY
-	warning("objectUsePosY not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	sqpush(v, obj->getUsePos().getY());
+	return 1;
 }
 
+// Returns true if the object's use position has been set (ie is not 0,0).
 static SQInteger objectValidUsePos(HSQUIRRELVM v) {
-	// TODO: objectValidUsePos
-	warning("objectValidUsePos not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	sqpush(v, obj->_usePos != Math::Vector2d());
+	return 1;
 }
 
 static SQInteger objectValidVerb(HSQUIRRELVM v) {
@@ -415,9 +636,28 @@ static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
 	return 0;
 }
 
+// The only difference between objectState and playObjectState is if they are called during the enter code.
+// objectState will set the image to the last frame of the state's animation, where as, playObjectState will play the full animation.
+//
+// .. code-block:: Squirrel
+// playObjectState(Mansion.windowShutters, OPEN)
 static SQInteger playObjectState(HSQUIRRELVM v) {
-	// TODO: playObjectState
-	warning("playObjectState not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return 0;
+	if (sq_gettype(v, 3) == OT_INTEGER) {
+		SQInteger index;
+		if (SQ_FAILED(sq_getinteger(v, 3, &index)))
+			return sq_throwerror(v, "failed to get state");
+		obj->play(index);
+	} else if (sq_gettype(v, 3) == OT_STRING) {
+		Common::String state;
+		if (SQ_FAILED(sqget(v, 3, state)))
+			return sq_throwerror(v, "failed to get state");
+		obj->play(state);
+	} else {
+		return sq_throwerror(v, "failed to get state");
+	}
 	return 0;
 }
 
@@ -446,38 +686,10 @@ static SQInteger shakeObject(HSQUIRRELVM v) {
 }
 
 static SQInteger stopObjectMotors(HSQUIRRELVM v) {
-	// TODO: stopObjectMotors
-	warning("stopObjectMotors not implemented");
-	return 0;
-}
-
-static SQInteger objectAt(HSQUIRRELVM v) {
-	HSQOBJECT o;
-	sq_getstackobj(v, 2, &o);
-
-	SQInteger id;
-	sqgetf(o, "_id", id);
-
-	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
-		SQObjectPtr id2;
-		_table(o->_table)->Get(sqtoobj(v, "_id"), id2);
-		return id == _integer(id2);
-	});
-
-	if (!pObj)
+	Object *obj = sqobj(v, 2);
+	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-
-	// TODO:
-	// Object* obj = *pObj;
-	// SQInteger x, y;
-	// if (SQ_FAILED(sq_getinteger(v, 3, &x)))
-	// 	return sq_throwerror(v, "failed to get x");
-	// if (SQ_FAILED(sq_getinteger(v, 4, &y)))
-	// 	return sq_throwerror(v, "failed to get y");
-	// obj->x = x;
-	// obj->y = y;
-	// debug("Object at: %lld, %lld", x, y);
-
+	obj->stopObjectMotors();
 	return 0;
 }
 
diff --git a/engines/twp/prefs.cpp b/engines/twp/prefs.cpp
new file mode 100644
index 00000000000..8f0c830e482
--- /dev/null
+++ b/engines/twp/prefs.cpp
@@ -0,0 +1,106 @@
+/* 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 "twp/prefs.h"
+
+namespace Twp {
+
+Preferences::Preferences() {
+	_node = new Common::JSONValue(Common::JSONObject());
+}
+
+Common::String Preferences::prefs(const Common::String name, const Common::String &def) const {
+	const Common::JSONObject &jObj = _node->asObject();
+	return jObj.contains(name) ? jObj[name]->asString() : def;
+}
+
+float Preferences::prefs(const Common::String &name, float def) const {
+	const Common::JSONObject &jObj = _node->asObject();
+	return jObj.contains(name) ? jObj[name]->asNumber() : def;
+}
+
+bool Preferences::prefs(const Common::String &name, bool def) const {
+	const Common::JSONObject &jObj = _node->asObject();
+	return jObj.contains(name) ? jObj[name]->asIntegerNumber() != 0 : def;
+}
+
+int Preferences::prefs(const Common::String &name, int def) const {
+	const Common::JSONObject &jObj = _node->asObject();
+	return jObj.contains(name) ? jObj[name]->asIntegerNumber() : def;
+}
+
+void Preferences::setPrefs(const Common::String &name, const Common::String &value) {
+	Common::JSONObject jObj = _node->asObject();
+	jObj[name] = new Common::JSONValue(value);
+	delete _node;
+	_node = new Common::JSONValue(jObj);
+	savePrefs();
+}
+
+void Preferences::setPrefs(const Common::String &name, float value) {
+	Common::JSONObject jObj = _node->asObject();
+	jObj[name] = new Common::JSONValue(value);
+	delete _node;
+	_node = new Common::JSONValue(jObj);
+	savePrefs();
+}
+
+void Preferences::setPrefs(const Common::String &name, int value) {
+	Common::JSONObject jObj = _node->asObject();
+	jObj[name] = new Common::JSONValue((long long int)value);
+	delete _node;
+	_node = new Common::JSONValue(jObj);
+	savePrefs();
+}
+
+void Preferences::setPrefs(const Common::String &name, bool value) {
+	Common::JSONObject jObj = _node->asObject();
+	jObj[name] = new Common::JSONValue((long long int)(value ? 1 : 0));
+	delete _node;
+	_node = new Common::JSONValue(jObj);
+	savePrefs();
+}
+
+bool Preferences::hasPrefs(const Common::String& name) {
+  return _node->asObject().contains(name);
+}
+
+Common::JSONValue* Preferences::prefsAsJson(const Common::String& name) {
+  return _node->asObject()[name];
+}
+
+void Preferences::savePrefs() {
+	// TODO: savePrefs()
+}
+
+Common::String Preferences::getKey(const Common::String& path){
+  size_t i = path.findLastOf(".");
+  Common::String name = path.substr(0, i);
+  Common::String ext = path.substr(i+1);
+  if (name.hasSuffix("_en")) {
+    // TODO: const Common::String& lang = prefs(Lang);
+	Common::String lang = "en";
+    return Common::String::format("%s_%s%s", name.substr(0, name.size()-4).c_str(), lang.c_str(), ext.c_str());
+  }
+  return path;
+}
+
+} // namespace Twp
diff --git a/engines/twp/prefs.h b/engines/twp/prefs.h
new file mode 100644
index 00000000000..f9b973a0abe
--- /dev/null
+++ b/engines/twp/prefs.h
@@ -0,0 +1,62 @@
+/* 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 TWP_PREFS_H
+#define TWP_PREFS_H
+
+#include "common/formats/json.h"
+
+namespace Twp {
+
+struct TempPref {
+    float gameSpeedFactor = 1.f;
+    bool forceTalkieText = false;
+};
+
+class Preferences {
+public:
+	Preferences();
+
+    bool hasPrefs(const Common::String& name);
+	Common::JSONValue* prefsAsJson(const Common::String& name);
+
+	Common::String prefs(const Common::String name, const Common::String& def) const;
+	float prefs(const Common::String& name, float def) const;
+	bool prefs(const Common::String& name, bool def) const;
+	int prefs(const Common::String& name, int def) const;
+
+	void setPrefs(const Common::String &name, const Common::String &value);
+	void setPrefs(const Common::String &name, float value);
+	void setPrefs(const Common::String &name, int value);
+	void setPrefs(const Common::String &name, bool value);
+
+	void savePrefs();
+
+	Common::String getKey(const Common::String& path);
+
+private:
+	Common::JSONValue* _node = nullptr;
+    TempPref _tmp;
+};
+
+}
+
+#endif
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 157ebfe8e94..a36b3854825 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -133,7 +133,7 @@ static ObjectAnimation parseObjectAnimation(const Common::JSONObject &jAnim) {
 	if (jAnim.contains("triggers") && jAnim["triggers"]->isArray()) {
 		const Common::JSONArray &jTriggers = jAnim["triggers"]->asArray();
 		for (auto it = jTriggers.begin(); it != jTriggers.end(); it++) {
-			result.triggers.push_back((*it)->asString());
+			result.triggers.push_back((*it)->isString() ? (*it)->asString() : "null");
 		}
 	}
 
@@ -180,7 +180,7 @@ Room::~Room() {
 void Room::load(Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
-	debug("Room: %s", value->stringify().c_str());
+	// debug("Room: %s", value->stringify().c_str());
 	const Common::JSONObject &jRoom = value->asObject();
 
 	_name = jRoom["name"]->asString();
@@ -328,6 +328,13 @@ Object *Room::getObj(const Common::String &key) {
 	return nullptr;
 }
 
+Light *Room::createLight(Color color, Math::Vector2d pos) {
+	Light *result = &_lights._lights[_lights._numLights];
+	result->color = color;
+	result->pos = pos;
+	_lights._numLights++;
+}
+
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
 	_names.push_back(name);
 	_parallax = parallax;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index a30cf500c7b..691c54a8023 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -106,6 +106,8 @@ public:
 	Layer *layer(int zsort);
 	Object *getObj(const Common::String& key);
 
+	Light *createLight(Color color, Math::Vector2d pos);
+
 public:
 	Common::String _name;              // Name of the room
 	Common::String _sheet;             // Name of the spritesheet to use
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index ffa815e9055..0fbdc194b6b 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -24,12 +24,26 @@
 #include "twp/room.h"
 #include "twp/object.h"
 #include "twp/squtil.h"
+#include "twp/scenegraph.h"
 #include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
 static SQInteger addTrigger(HSQUIRRELVM v) {
-	warning("TODO: addTrigger not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	sq_resetobject(&obj->_enter);
+	sq_resetobject(&obj->_leave);
+	if (SQ_FAILED(sqget(v, 3, obj->_enter)))
+		return sq_throwerror(v, "failed to get enter");
+	sq_addref(g_engine->getVm(), &obj->_enter);
+	if (nArgs == 4)
+		if (SQ_FAILED(sqget(v, 4, obj->_leave)))
+			return sq_throwerror(v, "failed to get leave");
+	sq_addref(g_engine->getVm(), &obj->_leave);
+	g_engine->_room->_triggers.push_back(obj);
 	return 0;
 }
 
@@ -39,8 +53,19 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 }
 
 static SQInteger createLight(HSQUIRRELVM v) {
-	warning("TODO: createLight not implemented");
-	return 0;
+	int color;
+	if (SQ_FAILED(sqget(v, 2, color)))
+		return sq_throwerror(v, "failed to get color");
+	int x;
+	if (SQ_FAILED(sqget(v, 3, x)))
+		return sq_throwerror(v, "failed to get x");
+	int y;
+	if (SQ_FAILED(sqget(v, 4, y)))
+		return sq_throwerror(v, "failed to get y");
+	Light *light = g_engine->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
+	debug("createLight(%d) -> %d", color, light->id);
+	sqpush(v, light->id);
+	return 1;
 }
 
 static SQInteger enableTrigger(HSQUIRRELVM v) {
@@ -49,7 +74,10 @@ static SQInteger enableTrigger(HSQUIRRELVM v) {
 }
 
 static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
-	warning("TODO: enterRoomFromDoor not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	g_engine->enterRoom(obj->_room, obj);
 	return 0;
 }
 
@@ -113,19 +141,73 @@ static SQInteger defineRoom(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Creates a new room called name using the specified template.
+//
+// . code-block:: Squirrel
+// for (local room_id = 1; room_id <= HOTEL_ROOMS_PER_FLOOR; room_id++) {
+//     local room = definePseudoRoom("HotelRoomA"+((floor_id*100)+room_id), HotelRoomA)
+//     local door = floor["hotelHallDoor"+room_id]
+//     ...
+// }
 static SQInteger definePseudoRoom(HSQUIRRELVM v) {
-	warning("TODO: definePseudoRoom not implemented");
-	return 0;
+	const SQChar *name;
+	if (SQ_FAILED(sqget(v, 2, name)))
+		return sq_throwerror(v, "failed to get name");
+	HSQOBJECT table;
+	sq_resetobject(&table);
+	// if this is a pseudo room, we have to clone the table
+	// to have a different instance by room
+	if (SQ_FAILED(sq_clone(v, 3)))
+		return sq_throwerror(v, "failed to clone room table");
+	if (SQ_FAILED(sq_getstackobj(v, -1, &table)))
+		return sq_throwerror(v, "failed to get room table");
+
+	Room *room = g_engine->defineRoom(name, table, true);
+	debug("Define pseudo room: %s", name);
+	g_engine->_rooms.push_back(room);
+	sqpush(v, room->_table);
+	return 1;
 }
 
+// Returns the room table for the room specified by the string roomName.
+// Useful for returning specific pseudo rooms where the name is composed of text and a variable.
+//
+// .. code-block:: Squirrel
+// local standardRoom = findRoom("HotelRoomA"+keycard.room_num)
 static SQInteger findRoom(HSQUIRRELVM v) {
-	warning("TODO: findRoom not implemented");
-	return 0;
+	Common::String name;
+	if (SQ_FAILED(sqget(v, 2, name)))
+		return sq_throwerror(v, "failed to get name");
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		if (room->_name == name) {
+			sqpush(v, room->_table);
+			return 1;
+		}
+	}
+	warning("Room '%s' not found", name.c_str());
+	sq_pushnull(v);
+	return 1;
 }
 
+// Returns an array of all the rooms that are in the game currently.
+//
+// This is useful for testing.
+//
+// .. code-block:: Squirrel
+// local roomArray = masterRoomArray()
+// foreach (room in roomArray) {
+//     enterRoomFromDoor(room)
+//     breaktime(0.10)
+// }
 static SQInteger masterRoomArray(HSQUIRRELVM v) {
-	warning("TODO: masterRoomArray not implemented");
-	return 0;
+	sq_newarray(v, 0);
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		sq_pushobject(v, room->_table);
+		sq_arrayappend(v, -2);
+	}
+	return 1;
 }
 
 static SQInteger removeTrigger(HSQUIRRELVM v) {
@@ -133,9 +215,28 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns an array of all the actors in the specified room.
+//
+// .. code-block:: Squirrel
+// local actorInBookstore = roomActors(BookStore)
+// if (actorInBookstore.len()>1) { ... }
+//
+// local spotters = roomActors(currentRoom)
+// foreach(actor in spotters) { ...}
 static SQInteger roomActors(HSQUIRRELVM v) {
-	warning("TODO: roomActors not implemented");
-	return 0;
+	Room *room = sqroom(v, 2);
+	if (!room)
+		return sq_throwerror(v, "failed to get room");
+
+	sq_newarray(v, 0);
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *actor = g_engine->_actors[i];
+		if (actor->_room == room) {
+			sqpush(v, actor->_table);
+			sq_arrayappend(v, -2);
+		}
+	}
+	return 1;
 }
 
 static SQInteger roomEffect(HSQUIRRELVM v) {
@@ -148,8 +249,20 @@ static SQInteger roomFade(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Makes all layers at the specified zsort value in room visible (YES) or invisible (NO).
+// It's also currently the only way to affect parallax layers and can be used for minor animation to turn a layer on and off.
+//
+// .. code-block:: Squirrel
+// roomLayer(GrateEntry, -2, NO)  // Make lights out layer invisible
 static SQInteger roomLayer(HSQUIRRELVM v) {
-	warning("TODO: roomLayer not implemented");
+	Room *r = sqroom(v, 2);
+	int layer;
+	SQInteger enabled;
+	if (SQ_FAILED(sqget(v, 3, layer)))
+		return sq_throwerror(v, "failed to get layer");
+	if (SQ_FAILED(sq_getinteger(v, 4, &enabled)))
+		return sq_throwerror(v, "failed to get enabled");
+	r->layer(layer)->_node->setVisible(enabled != 0);
 	return 0;
 }
 
@@ -164,8 +277,11 @@ static SQInteger roomRotateTo(HSQUIRRELVM v) {
 }
 
 static SQInteger roomSize(HSQUIRRELVM v) {
-	warning("TODO: roomSize not implemented");
-	return 0;
+	Room *room = sqroom(v, 2);
+	if (!room)
+		return sq_throwerror(v, "failed to get room");
+	sqpush(v, room->_roomSize);
+	return 1;
 }
 
 static SQInteger walkboxHidden(HSQUIRRELVM v) {
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
index 6ca14c8da0e..ff6d1105883 100644
--- a/engines/twp/sqgame.h
+++ b/engines/twp/sqgame.h
@@ -28,6 +28,7 @@
 namespace Twp {
 
 void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck = 0, const SQChar *typemask = NULL);
+void sqgame_register_constants(HSQUIRRELVM v);
 void sqgame_register_syslib(HSQUIRRELVM v);
 void sqgame_register_objlib(HSQUIRRELVM v);
 void sqgame_register_genlib(HSQUIRRELVM v);
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 51dd5eff0ac..4fafc5380d8 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -38,23 +38,79 @@
 namespace Twp {
 
 template<>
-void sqpush(HSQUIRRELVM v, int value) {
+SQInteger sqpush(HSQUIRRELVM v, int value) {
 	sq_pushinteger(v, value);
+	return 1;
 }
 
 template<>
-void sqpush(HSQUIRRELVM v, bool value) {
+SQInteger sqpush(HSQUIRRELVM v, float value) {
+	sq_pushfloat(v, value);
+	return 1;
+}
+
+template<>
+SQInteger sqpush(HSQUIRRELVM v, bool value) {
 	sq_pushinteger(v, value ? 1 : 0);
+	return 1;
 }
 
 template<>
-void sqpush(HSQUIRRELVM v, Common::String value) {
+SQInteger sqpush(HSQUIRRELVM v, Common::String value) {
 	sq_pushstring(v, value.c_str(), value.size());
+	return 1;
 }
 
 template<>
-void sqpush(HSQUIRRELVM v, HSQOBJECT value) {
+SQInteger sqpush(HSQUIRRELVM v, HSQOBJECT value) {
 	sq_pushobject(v, value);
+	return 1;
+}
+
+template<>
+SQInteger sqpush(HSQUIRRELVM v, Math::Vector2d value) {
+	sq_newtable(v);
+	sq_pushstring(v, "x", -1);
+	sq_pushinteger(v, value.getX());
+	sq_newslot(v, -3, SQFalse);
+	sq_pushstring(v, "y", -1);
+	sq_pushinteger(v, value.getY());
+	sq_newslot(v, -3, SQFalse);
+	return 1;
+}
+
+template<>
+SQInteger sqpush(HSQUIRRELVM v, Rectf value) {
+	sq_newtable(v);
+	sq_pushstring(v, "x1", -1);
+	sq_pushinteger(v, value.left());
+	sq_newslot(v, -3, SQFalse);
+	sq_pushstring(v, "y1", -1);
+	sq_pushinteger(v, value.bottom());
+	sq_newslot(v, -3, SQFalse);
+	sq_pushstring(v, "x2", -1);
+	sq_pushinteger(v, value.right());
+	sq_newslot(v, -3, SQFalse);
+	sq_pushstring(v, "y2", -1);
+	sq_pushinteger(v, value.top());
+	sq_newslot(v, -3, SQFalse);
+	return 1;
+}
+
+template<>
+SQInteger sqpush(HSQUIRRELVM v, Common::JSONValue* node) {
+  if(node->isIntegerNumber()) {
+	return sqpush(v, (int)node->asIntegerNumber());
+  } else if(node->isString()) {
+    return sqpush(v, node->asString());
+  } else if(node->isString()) {
+    return sqpush(v, (float)node->asNumber());
+  } else if(node->isNull()) {
+	sq_pushnull(v);
+	return 1;
+  } else {
+    return sq_throwerror(v, "This kind of node is not supported");
+  }
 }
 
 template<>
@@ -202,10 +258,10 @@ Object *sqobj(HSQOBJECT table) {
 	int id = getId(table);
 	for (int i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
-		for (int j = 0; j < room->_layers.size(); i++) {
-			Layer *layer = room->_layers[i];
+		for (int j = 0; j < room->_layers.size(); j++) {
+			Layer *layer = room->_layers[j];
 			for (int k = 0; k < layer->_objects.size(); k++) {
-				Object *obj = layer->_objects[i];
+				Object *obj = layer->_objects[k];
 				if (getId(obj->_table) == id)
 					return obj;
 			}
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 420d9d36292..adfadb6ef71 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -33,7 +33,7 @@ template<typename T>
 HSQOBJECT sqtoobj(HSQUIRRELVM v, T value);
 
 template<typename T>
-void sqpush(HSQUIRRELVM v, T value);
+SQInteger sqpush(HSQUIRRELVM v, T value);
 
 template<typename T>
 SQRESULT sqget(HSQUIRRELVM v, int index, T &value);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 770065dd884..ac0d9216390 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -21,6 +21,7 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/ids.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/squirrel/squirrel.h"
@@ -237,9 +238,7 @@ static SQInteger gameTime(HSQUIRRELVM v) {
 	return 0;
 }
 
-static SQInteger include(HSQUIRRELVM v) {
-	warning("TODO: sysInclude: not implemented");
-	return 0;
+static SQInteger sysInclude(HSQUIRRELVM v) {
 	const SQChar *filename;
 	if (SQ_FAILED(sqget(v, 2, filename))) {
 		return sq_throwerror(v, "failed to get filename");
@@ -369,7 +368,7 @@ void sqgame_register_syslib(HSQUIRRELVM v) {
 	regFunc(v, dumpvar, _SC("dumpvar"));
 	regFunc(v, exCommand, _SC("exCommand"));
 	regFunc(v, gameTime, _SC("gameTime"));
-	regFunc(v, include, _SC("include"));
+	regFunc(v, sysInclude, _SC("include"));
 	regFunc(v, inputController, _SC("inputController"));
 	regFunc(v, inputHUD, _SC("inputHUD"));
 	regFunc(v, inputOff, _SC("inputOff"));
@@ -392,4 +391,202 @@ void sqgame_register_syslib(HSQUIRRELVM v) {
 	regFunc(v, threadpauseable, _SC("threadpauseable"));
 }
 
+static void regConst(HSQUIRRELVM v, const char *name, int value) {
+	SQObject obj = sqtoobj(v, value);
+	_table(v->_roottable)->NewSlot(sqtoobj(v, name), SQObjectPtr(obj));
+}
+
+void sqgame_register_constants(HSQUIRRELVM v) {
+	regConst(v, "ALL", ALL);
+	regConst(v, "HERE", HERE);
+	regConst(v, "GONE", GONE);
+	regConst(v, "OFF", OFF);
+	regConst(v, "ON", ON);
+	regConst(v, "FULL", FULL);
+	regConst(v, "EMPTY", EMPTY);
+	regConst(v, "OPEN", OPEN);
+	regConst(v, "CLOSED", CLOSED);
+	regConst(v, "FALSE", FALSE);
+	regConst(v, "TRUE", TRUE);
+	regConst(v, "MOUSE", MOUSE);
+	regConst(v, "CONTROLLER", CONTROLLER);
+	regConst(v, "DIRECTDRIVE", DIRECTDRIVE);
+	regConst(v, "TOUCH", TOUCH);
+	regConst(v, "REMOTE", REMOTE);
+	regConst(v, "FADE_IN", FADE_IN);
+	regConst(v, "FADE_OUT", FADE_OUT);
+	regConst(v, "FADE_WOBBLE", FADE_WOBBLE);
+	regConst(v, "FADE_WOBBLE_TO_SEPIA", FADE_WOBBLE_TO_SEPIA);
+	regConst(v, "FACE_FRONT", FACE_FRONT);
+	regConst(v, "FACE_BACK", FACE_BACK);
+	regConst(v, "FACE_LEFT", FACE_LEFT);
+	regConst(v, "FACE_RIGHT", FACE_RIGHT);
+	regConst(v, "FACE_FLIP", FACE_FLIP);
+	regConst(v, "DIR_FRONT", DIR_FRONT);
+	regConst(v, "DIR_BACK", DIR_BACK);
+	regConst(v, "DIR_LEFT", DIR_LEFT);
+	regConst(v, "DIR_RIGHT", DIR_RIGHT);
+	regConst(v, "LINEAR", LINEAR);
+	regConst(v, "EASE_IN", EASE_IN);
+	regConst(v, "EASE_INOUT", EASE_INOUT);
+	regConst(v, "EASE_OUT", EASE_OUT);
+	regConst(v, "SLOW_EASE_IN", SLOW_EASE_IN);
+	regConst(v, "SLOW_EASE_OUT", SLOW_EASE_OUT);
+	regConst(v, "LOOPING", LOOPING);
+	regConst(v, "SWING", SWING);
+	regConst(v, "ALIGN_LEFT", ALIGN_LEFT);
+	regConst(v, "ALIGN_CENTER", ALIGN_CENTER);
+	regConst(v, "ALIGN_RIGHT", ALIGN_RIGHT);
+	regConst(v, "ALIGN_TOP", ALIGN_TOP);
+	regConst(v, "ALIGN_BOTTOM", ALIGN_BOTTOM);
+	regConst(v, "LESS_SPACING", LESS_SPACING);
+	regConst(v, "EX_ALLOW_SAVEGAMES", EX_ALLOW_SAVEGAMES);
+	regConst(v, "EX_POP_CHARACTER_SELECTION", EX_POP_CHARACTER_SELECTION);
+	regConst(v, "EX_CAMERA_TRACKING", EX_CAMERA_TRACKING);
+	regConst(v, "EX_BUTTON_HOVER_SOUND", EX_BUTTON_HOVER_SOUND);
+	regConst(v, "EX_RESTART", EX_RESTART);
+	regConst(v, "EX_IDLE_TIME", EX_IDLE_TIME);
+	regConst(v, "EX_AUTOSAVE", EX_AUTOSAVE);
+	regConst(v, "EX_AUTOSAVE_STATE", EX_AUTOSAVE_STATE);
+	regConst(v, "EX_DISABLE_SAVESYSTEM", EX_DISABLE_SAVESYSTEM);
+	regConst(v, "EX_SHOW_OPTIONS", EX_SHOW_OPTIONS);
+	regConst(v, "EX_OPTIONS_MUSIC", EX_OPTIONS_MUSIC);
+	regConst(v, "EX_FORCE_TALKIE_TEXT", EX_FORCE_TALKIE_TEXT);
+	regConst(v, "GRASS_BACKANDFORTH", GRASS_BACKANDFORTH);
+	regConst(v, "EFFECT_NONE", EFFECT_NONE);
+	regConst(v, "DOOR", DOOR);
+	regConst(v, "DOOR_LEFT", DOOR_LEFT);
+	regConst(v, "DOOR_RIGHT", DOOR_RIGHT);
+	regConst(v, "DOOR_BACK", DOOR_BACK);
+	regConst(v, "DOOR_FRONT", DOOR_FRONT);
+	regConst(v, "FAR_LOOK", FAR_LOOK);
+	regConst(v, "USE_WITH", USE_WITH);
+	regConst(v, "USE_ON", USE_ON);
+	regConst(v, "USE_IN", USE_IN);
+	regConst(v, "GIVEABLE", GIVEABLE);
+	regConst(v, "TALKABLE", TALKABLE);
+	regConst(v, "IMMEDIATE", IMMEDIATE);
+	regConst(v, "FEMALE", FEMALE);
+	regConst(v, "MALE", MALE);
+	regConst(v, "PERSON", PERSON);
+	regConst(v, "REACH_HIGH", REACH_HIGH);
+	regConst(v, "REACH_MED", REACH_MED);
+	regConst(v, "REACH_LOW", REACH_LOW);
+	regConst(v, "REACH_NONE", REACH_NONE);
+	regConst(v, "VERB_WALKTO", VERB_WALKTO);
+	regConst(v, "VERB_LOOKAT", VERB_LOOKAT);
+	regConst(v, "VERB_TALKTO", VERB_TALKTO);
+	regConst(v, "VERB_PICKUP", VERB_PICKUP);
+	regConst(v, "VERB_OPEN", VERB_OPEN);
+	regConst(v, "VERB_CLOSE", VERB_CLOSE);
+	regConst(v, "VERB_PUSH", VERB_PUSH);
+	regConst(v, "VERB_PULL", VERB_PULL);
+	regConst(v, "VERB_GIVE", VERB_GIVE);
+	regConst(v, "VERB_USE", VERB_USE);
+	regConst(v, "VERB_DIALOG", VERB_DIALOG);
+	regConst(v, "VERBFLAG_INSTANT", VERBFLAG_INSTANT);
+	regConst(v, "NO", NO);
+	regConst(v, "YES", YES);
+	regConst(v, "UNSELECTABLE", UNSELECTABLE);
+	regConst(v, "SELECTABLE", SELECTABLE);
+	regConst(v, "TEMP_UNSELECTABLE", TEMP_UNSELECTABLE);
+	regConst(v, "TEMP_SELECTABLE", TEMP_SELECTABLE);
+	regConst(v, "MAC", MAC);
+	regConst(v, "WIN", WIN);
+	regConst(v, "LINUX", LINUX);
+	regConst(v, "XBOX", XBOX);
+	regConst(v, "IOS", IOS);
+	regConst(v, "ANDROID", ANDROID);
+	regConst(v, "SWITCH", SWITCH);
+	regConst(v, "PS4", PS4);
+	regConst(v, "EFFECT_NONE", EFFECT_NONE);
+	regConst(v, "EFFECT_SEPIA", EFFECT_SEPIA);
+	regConst(v, "EFFECT_EGA", EFFECT_EGA);
+	regConst(v, "EFFECT_VHS", EFFECT_VHS);
+	regConst(v, "EFFECT_GHOST", EFFECT_GHOST);
+	regConst(v, "EFFECT_BLACKANDWHITE", EFFECT_BLACKANDWHITE);
+	regConst(v, "UI_INPUT_ON", UI_INPUT_ON);
+	regConst(v, "UI_INPUT_OFF", UI_INPUT_OFF);
+	regConst(v, "UI_VERBS_ON", UI_VERBS_ON);
+	regConst(v, "UI_VERBS_OFF", UI_VERBS_OFF);
+	regConst(v, "UI_HUDOBJECTS_ON", UI_HUDOBJECTS_ON);
+	regConst(v, "UI_HUDOBJECTS_OFF", UI_HUDOBJECTS_OFF);
+	regConst(v, "UI_CURSOR_ON", UI_CURSOR_ON);
+	regConst(v, "UI_CURSOR_OFF", UI_CURSOR_OFF);
+	regConst(v, "KEY_UP", KEY_UP);
+	regConst(v, "KEY_RIGHT", KEY_RIGHT);
+	regConst(v, "KEY_DOWN", KEY_DOWN);
+	regConst(v, "KEY_LEFT", KEY_LEFT);
+	regConst(v, "KEY_PAD1", KEY_PAD1);
+	regConst(v, "KEY_PAD2", KEY_PAD2);
+	regConst(v, "KEY_PAD3", KEY_PAD3);
+	regConst(v, "KEY_PAD4", KEY_PAD4);
+	regConst(v, "KEY_PAD5", KEY_PAD5);
+	regConst(v, "KEY_PAD6", KEY_PAD6);
+	regConst(v, "KEY_PAD7", KEY_PAD7);
+	regConst(v, "KEY_PAD8", KEY_PAD8);
+	regConst(v, "KEY_PAD9", KEY_PAD9);
+	regConst(v, "KEY_ESCAPE", KEY_ESCAPE);
+	regConst(v, "KEY_TAB", KEY_TAB);
+	regConst(v, "KEY_RETURN", KEY_RETURN);
+	regConst(v, "KEY_BACKSPACE", KEY_BACKSPACE);
+	regConst(v, "KEY_SPACE", KEY_SPACE);
+	regConst(v, "KEY_A", KEY_A);
+	regConst(v, "KEY_B", KEY_B);
+	regConst(v, "KEY_C", KEY_C);
+	regConst(v, "KEY_D", KEY_D);
+	regConst(v, "KEY_E", KEY_E);
+	regConst(v, "KEY_F", KEY_F);
+	regConst(v, "KEY_G", KEY_G);
+	regConst(v, "KEY_H", KEY_H);
+	regConst(v, "KEY_I", KEY_I);
+	regConst(v, "KEY_J", KEY_J);
+	regConst(v, "KEY_K", KEY_K);
+	regConst(v, "KEY_L", KEY_L);
+	regConst(v, "KEY_M", KEY_M);
+	regConst(v, "KEY_N", KEY_N);
+	regConst(v, "KEY_O", KEY_O);
+	regConst(v, "KEY_P", KEY_P);
+	regConst(v, "KEY_Q", KEY_Q);
+	regConst(v, "KEY_R", KEY_R);
+	regConst(v, "KEY_S", KEY_S);
+	regConst(v, "KEY_T", KEY_T);
+	regConst(v, "KEY_U", KEY_U);
+	regConst(v, "KEY_V", KEY_V);
+	regConst(v, "KEY_W", KEY_W);
+	regConst(v, "KEY_X", KEY_X);
+	regConst(v, "KEY_Y", KEY_Y);
+	regConst(v, "KEY_Z", KEY_Z);
+	regConst(v, "KEY_0", KEY_0);
+	regConst(v, "KEY_1", KEY_1);
+	regConst(v, "KEY_2", KEY_2);
+	regConst(v, "KEY_3", KEY_3);
+	regConst(v, "KEY_4", KEY_4);
+	regConst(v, "KEY_5", KEY_5);
+	regConst(v, "KEY_6", KEY_6);
+	regConst(v, "KEY_7", KEY_7);
+	regConst(v, "KEY_8", KEY_8);
+	regConst(v, "KEY_9", KEY_9);
+	regConst(v, "KEY_F1", KEY_F1);
+	regConst(v, "KEY_F2", KEY_F2);
+	regConst(v, "KEY_F3", KEY_F3);
+	regConst(v, "KEY_F4", KEY_F4);
+	regConst(v, "KEY_F5", KEY_F5);
+	regConst(v, "KEY_F6", KEY_F6);
+	regConst(v, "KEY_F7", KEY_F7);
+	regConst(v, "KEY_F8", KEY_F8);
+	regConst(v, "KEY_F9", KEY_F9);
+	regConst(v, "KEY_F10", KEY_F10);
+	regConst(v, "KEY_F11", KEY_F11);
+	regConst(v, "KEY_F12", KEY_F12);
+	regConst(v, "BUTTON_A", BUTTON_A);
+	regConst(v, "BUTTON_B", BUTTON_B);
+	regConst(v, "BUTTON_X", BUTTON_X);
+	regConst(v, "BUTTON_Y", BUTTON_Y);
+	regConst(v, "BUTTON_START", BUTTON_START);
+	regConst(v, "BUTTON_BACK", BUTTON_BACK);
+	regConst(v, "BUTTON_MOUSE_LEFT", BUTTON_MOUSE_LEFT);
+	regConst(v, "BUTTON_MOUSE_RIGHT", BUTTON_MOUSE_RIGHT);
+}
+
 } // namespace Twp
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
new file mode 100644
index 00000000000..199799c9e44
--- /dev/null
+++ b/engines/twp/tsv.cpp
@@ -0,0 +1,86 @@
+/* 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 "twp/twp.h"
+#include "twp/tsv.h"
+#include "twp/squtil.h"
+#include "twp/squirrel/squirrel.h"
+
+namespace Twp {
+
+void TextDb::parseTsv(Common::SeekableReadStream &stream) {
+	char s[128];
+	stream.readLine();
+	while(!stream.eos()) {
+		Common::String line = stream.readLine();
+		int id;
+		sscanf(line.c_str(), "%d\t%s", &id, s);
+		_texts[id] = s;
+	}
+}
+
+Common::String TextDb::getText(int id) {
+  Common::String result;
+  if (_texts.contains(id)) {
+    result = _texts[id];
+    if (result.hasSuffix("#M") || result.hasSuffix("#F"))
+      result = result.substr(0, result.size()-3);
+    // TODO: replace \" by ";
+    // result = result.replace("\\\"", "\"");
+  } else {
+    result = Common::String::format("Text %d not found", id);
+    error("Text %d not found", id);
+  }
+  return result;
+}
+
+Common::String TextDb::getText(const Common::String& text) {
+  HSQUIRRELVM v = g_engine->getVm();
+  if (text.size() > 0) {
+    if (text[0] == '@') {
+      int id = atoi(text.c_str()+1);
+      return getText(id);
+	} else if (text[0] == '^') {
+      return text.substr(1);
+	} else if (text[0] == '$') {
+      Common::String txt;
+      SQInteger top = sq_gettop(v);
+      sq_pushroottable(v);
+      Common::String code = Common::String::format("return %s", text.substr(1, text.size()-2).c_str());
+      if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "execCode", SQTrue))) {
+        error("Error executing code %s", code.c_str());
+	  } else {
+        sq_push(v, -2);
+        // call
+        if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
+          error("Error calling code %s", code.c_str());
+		} else {
+          sqget(v, -1, txt);
+          sq_settop(v, top);
+          return getText(txt);
+		}
+	  }
+	}
+  }
+  return text;
+}
+
+} // namespace Twp
diff --git a/engines/twp/tsv.h b/engines/twp/tsv.h
new file mode 100644
index 00000000000..0f0cc97c9bf
--- /dev/null
+++ b/engines/twp/tsv.h
@@ -0,0 +1,44 @@
+/* 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 TWP_TSV_H
+#define TWP_TSV_H
+
+#include "common/hashmap.h"
+#include "common/stream.h"
+
+namespace Twp {
+
+class TextDb {
+public:
+	void parseTsv(Common::SeekableReadStream& stream);
+	Common::String getText(const Common::String& text);
+
+private:
+	Common::String getText(int id);
+
+private:
+  Common::HashMap<int, Common::String> _texts;
+};
+
+}
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 8c28c314197..9fac484699c 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/debug.h"
 #include "common/scummsys.h"
 #include "common/system.h"
 #include "common/stream.h"
@@ -41,6 +42,7 @@
 #include "twp/squtil.h"
 #include "twp/object.h"
 #include "twp/ids.h"
+#include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
@@ -113,11 +115,20 @@ Common::Error TwpEngine::run() {
 	f.open("ThimbleweedPark.ggpack1");
 	_pack.open(&f, key);
 
+	// TODO: load with selected lang
+	GGPackEntryReader entry;
+	entry.open(_pack, "ThimbleweedText_en.tsv");
+	_textDb.parseTsv(entry);
+
 	// If a savegame was selected from the launcher, load it
 	int saveSlot = ConfMan.getInt("save_slot");
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
+	HSQUIRRELVM v = _vm.get();
+	execNutEntry(v, "Defines.nut");
+	execBnutEntry(v, "Boot.bnut");
+
 	const SQChar *code = R"(
 	MainStreet <- {
 		background = "MainStreet"
@@ -203,14 +214,13 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 	debug("Load room: %s", name.c_str());
 	Room *result;
 	if (name == "Void") {
-		error("TODO: room Void");
-		// result = new Room(name, table);
-		// result.scene = new Scene();
-		// Layer* layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
-		// layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "background");
-		// result->_layers.push_back(layer);
-		// // TODO:; result->scene.addChild(layer->node);
-		// sqsetf(sqrootTbl(v), name, result->table);
+		result = new Room(name, table);
+		result->_scene = new Scene();
+		Layer* layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
+		layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray());
+		result->_layers.push_back(layer);
+		result->_scene->addChild(layer->_node);
+		sqsetf(sqrootTbl(v), name, result->_table);
 	} else {
 		result = new Room(name, table);
 		Common::String background;
@@ -371,46 +381,71 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	_scene->addChild(_room->_scene);
 	_room->_lights._numLights = 0;
 	// TODO:   _room->overlay = Transparent;
-	//_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
+	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
 	//   if (_actor)
 	//     _hud.verb = _hud.actorSlot(_actor).verbs[0];
 
-	//   TODO: move current actor to the new room
+	// move current actor to the new room
 	Math::Vector2d camPos;
-	//   if not gEngine.actor.isNil:
-	//     self.cancelSentence(nil)
-	//     if not door.isNil:
-	//       let facing = getOppositeFacing(door.getDoorFacing())
-	//       gEngine.actor.room = room
-	//       if not door.isNil:
-	//         gEngine.actor.setFacing(facing)
-	//         gEngine.actor.node.pos = door.getUsePos
-	//       camPos = gEngine.actor.node.pos
+	if (_actor) {
+		cancelSentence();
+		if (door) {
+			Facing facing = getOppositeFacing(door->getDoorFacing());
+			_actor->_room = room;
+			if (door) {
+				_actor->setFacing(facing);
+				_actor->_node->setPos(door->getUsePos());
+			}
+			camPos = _actor->_node->getPos();
+		}
+	}
 
 	_camera.setRoom(room);
 	_camera.setAt(camPos);
 
-	//   // call actor enter function and objects enter function
-	//   actorEnter();
-	//   for layer in room.layers:
-	//     for obj in layer.objects:
-	//       if rawExists(obj.table, "enter"):
-	//         call(self.v, obj.table, "enter")
-
-	//   // call room enter function with the door as a parameter if requested
-	//   let nparams = paramCount(self.v, self.room.table, "enter")
-	//   if nparams == 2:
-	//     if door.isNil:
-	//       var doorTable: HSQOBJECT
-	//       sq_resetobject(doorTable)
-	//       call(self.v, self.room.table, "enter", [doorTable])
-	//     else:
-	//       call(self.v, self.room.table, "enter", [door.table])
-	//   else:
-	//     call(self.v, self.room.table, "enter")
-
-	//   # call global function enteredRoom with the room as argument
-	//   call("enteredRoom", [room.table])
+	// call actor enter function and objects enter function
+	actorEnter();
+	for (int i = 0; i < room->_layers.size(); i++) {
+		Layer *layer = room->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[i];
+			if (sqrawexists(obj->_table, "enter"))
+				sqcall(obj->_table, "enter");
+		}
+	}
+
+	// call room enter function with the door as a parameter if requested
+	int nparams = sqparamCount(v, _room->_table, "enter");
+	if (nparams == 2) {
+		if (!door) {
+			HSQOBJECT doorTable;
+			sq_resetobject(&doorTable);
+			HSQOBJECT args[] = {doorTable};
+			sqcall(_room->_table, "enter", 1, args);
+		} else {
+			HSQOBJECT args[] = {door->_table};
+			sqcall(_room->_table, "enter", 1, args);
+		}
+	} else {
+		sqcall(_room->_table, "enter");
+	}
+
+	// call global function enteredRoom with the room as argument
+	{
+		HSQOBJECT args[] = {room->_table};
+		sqcall("enteredRoom", 1, args);
+	}
+}
+
+void TwpEngine::actorEnter() {
+	if (_actor)
+		sqcall(_actor->_table, "actorEnter");
+	if (_room) {
+		if (sqrawexists(_room->_table, "actorEnter")) {
+			HSQOBJECT args[] = {_actor->_table};
+			sqcall(_room->_table, "actorEnter", 1, args);
+		}
+	}
 }
 
 void TwpEngine::exitRoom(Room *nextRoom) {
@@ -473,12 +508,21 @@ void TwpEngine::actorExit() {
 	}
 }
 
+void TwpEngine::cancelSentence(Object *actor) {
+	debug("cancelSentence");
+	if (!actor)
+		actor = _actor;
+	if (actor)
+		actor->_exec.enabled = false;
+}
+
 void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
 	GGPackEntryReader reader;
 	reader.open(_pack, entry);
 	GGBnutReader nut;
 	nut.open(&reader);
 	Common::String code = nut.readString();
+	//debug("%s", code.c_str());
 	sqexec(v, code.c_str());
 }
 
@@ -488,6 +532,7 @@ void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 		debug("read existing '%s'", entry.c_str());
 		reader.open(_pack, entry);
 		Common::String code = reader.readString();
+		//debug("%s", code.c_str());
 		sqexec(v, code.c_str());
 	} else {
 		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
@@ -516,4 +561,31 @@ void TwpEngine::follow(Object *actor) {
 	}
 }
 
+template<typename TFunc>
+void objsAt(Math::Vector2d pos, TFunc func) {
+	// TODO
+	// if g_engine->_uiInv->_obj && g_engine->_room->fullscreen == FullscreenRoom)
+	// 	func(g_engine->_uiInv._obj);
+	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+		Layer *layer = g_engine->_room->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[j];
+			if (obj != g_engine->_actor && (obj->_touchable || obj->inInventory()) && obj->_node->isVisible() && obj->_objType == otNone && obj->contains(pos))
+				func(obj);
+		}
+	}
+}
+
+Object *TwpEngine::objAt(Math::Vector2d pos) {
+	int zOrder = INT_MAX;
+	Object *result = nullptr;
+	objsAt(pos, [&](Object *obj) {
+		if (obj->_node->getZSort() < zOrder) {
+			result = obj;
+			zOrder = obj->_node->getZSort();
+		}
+	});
+	return result;
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 5215384f507..f0d759ffc52 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -37,6 +37,8 @@
 #include "twp/ggpack.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/camera.h"
+#include "twp/prefs.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -71,9 +73,11 @@ public:
 	 * Gets the random source
 	 */
 	Common::RandomSource& getRandomSource() { return _randomSource; }
+	Preferences& getPrefs() { return _prefs; }
 
 	HSQUIRRELVM getVm() { return _vm.get(); }
 	inline Gfx& getGfx() { return _gfx; }
+	inline TextDb& getTextDb() { return _textDb; }
 
 	bool hasFeature(EngineFeature f) const override {
 		return
@@ -108,8 +112,11 @@ public:
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
 	void setActor(Object* actor) { _actor = actor; }
+	Object* objAt(Math::Vector2d pos);
+
 	Room* defineRoom(const Common::String& name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
+	void enterRoom(Room *room, Object *door = nullptr);
 
 	void cameraAt(Math::Vector2d at);
 	void follow(Object* actor);
@@ -120,9 +127,10 @@ public:
 private:
 	void update(float elapsedMs);
 	void draw();
-	void enterRoom(Room *room, Object *door = nullptr);
 	void exitRoom(Room *nextRoom);
+	void actorEnter();
 	void actorExit();
+	void cancelSentence(Object* actor = nullptr);
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -144,10 +152,12 @@ public:
 	Lighting* _lighting = nullptr;
 	Scene* _scene = nullptr;
 	Camera _camera;
+	TextDb _textDb;
 
 private:
 	Gfx _gfx;
 	Vm _vm;
+	Preferences _prefs;
 };
 
 extern TwpEngine *g_engine;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index d104cfde663..ecca5187374 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -92,6 +92,7 @@ Vm::Vm() {
 	sqstd_register_iolib(v);
 	sq_pop(v, 1);
 
+	sqgame_register_constants(v);
 	sqgame_register_syslib(v);
 	sqgame_register_genlib(v);
 	sqgame_register_objlib(v);


Commit: ca61129f173d86c7268c3d9cc51b5487bca35835
    https://github.com/scummvm/scummvm/commit/ca61129f173d86c7268c3d9cc51b5487bca35835
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Implement several actor functions

Changed paths:
  A engines/twp/util.cpp
    engines/twp/actorlib.cpp
    engines/twp/genlib.cpp
    engines/twp/ids.h
    engines/twp/module.mk
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/util.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index e4e88977f98..1c1086497af 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -24,42 +24,172 @@
 #include "twp/room.h"
 #include "twp/object.h"
 #include "twp/squtil.h"
+#include "twp/util.h"
+#include "twp/scenegraph.h"
 #include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
+// Sets the transparency for an actor's image in [0.0..1.0]
 static SQInteger actorAlpha(HSQUIRRELVM v) {
-	warning("TODO: actorAlpha not implemented");
+	Object *actor = sqobj(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	float alpha;
+	if (SQ_FAILED(sqget(v, 3, alpha)))
+		return sq_throwerror(v, "failed to get alpha");
+	debug("actorAlpha(%s, %f)", actor->_key.c_str(), alpha);
+	actor->_node->setAlpha(alpha);
 	return 0;
 }
 
 static SQInteger actorAnimationFlags(HSQUIRRELVM v) {
-	warning("TODO: actorAnimationFlags not implemented");
-	return 0;
+	Object *actor = sqobj(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	sqpush(v, actor->_animFlags);
+	return 1;
 }
 
 static SQInteger actorAnimationNames(HSQUIRRELVM v) {
-	warning("TODO: actorAnimationNames not implemented");
-	return 0;
-}
-
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+
+	HSQOBJECT table;
+	if (SQ_FAILED(sqget(v, 3, table)))
+		return sq_throwerror(v, "failed to get table");
+	if (!sq_istable(table))
+		return sq_throwerror(v, "failed to get animation table");
+
+	Common::String head;
+	Common::String stand;
+	Common::String walk;
+	Common::String reach;
+	sqgetf(table, "head", head);
+	sqgetf(table, "stand", stand);
+	sqgetf(table, "walk", walk);
+	sqgetf(table, "reach", reach);
+	actor->setAnimationNames(head, stand, walk, reach);
+	return 0;
+}
+
+// Moves the specified actor to the room and x, y coordinates specified.
+// Also makes the actor face to given direction (options are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT).
+// If using a spot, moves the player to the spot as specified in a Wimpy file.
 static SQInteger actorAt(HSQUIRRELVM v) {
-	warning("TODO: actorAt not implemented");
-	return 0;
+	SQInteger numArgs = sq_gettop(v);
+	switch (numArgs) {
+	case 3: {
+		Object *actor = sqactor(v, 2);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+		Object *spot = sqobj(v, 3);
+		if (spot) {
+			Math::Vector2d pos = spot->_node->getPos() + spot->_usePos;
+			actor->setRoom(spot->_room);
+			actor->stopWalking();
+			debug("actorAt %s at %s, room '%s'", actor->_key.c_str(), spot->_key.c_str(), spot->_room->_name.c_str());
+			actor->_node->setPos(pos);
+			actor->setFacing(getFacing(spot->_useDir, actor->getFacing()));
+		} else {
+			Room *room = sqroom(v, 3);
+			if (!room)
+				return sq_throwerror(v, "failed to get spot or room");
+			debug("actorAt %s room '%s'", actor->_key.c_str(), room->_name.c_str());
+			actor->stopWalking();
+			actor->setRoom(room);
+		}
+		return 0;
+	}
+	case 4: {
+		Object *actor = sqactor(v, 2);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+		int x, y;
+		if (SQ_FAILED(sqget(v, 3, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 4, y)))
+			return sq_throwerror(v, "failed to get y");
+		debug("actorAt %s room %d, %d", actor->_key.c_str(), x, y);
+		actor->stopWalking();
+		actor->_node->setPos(Math::Vector2d(x, y));
+		return 0;
+	}
+	case 5:
+	case 6: {
+		Object *actor = sqactor(v, 2);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+		Room *room = sqroom(v, 3);
+		if (!room)
+			return sq_throwerror(v, "failed to get room");
+		int x, y;
+		if (SQ_FAILED(sqget(v, 4, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 5, y)))
+			return sq_throwerror(v, "failed to get y");
+		int dir = 0;
+		if ((numArgs == 6) && SQ_FAILED(sqget(v, 6, dir)))
+			return sq_throwerror(v, "failed to get direction");
+		debug("actorAt %s, pos = (%d,%d), dir = %d", actor->_key.c_str(), x, y, dir);
+		actor->stopWalking();
+		actor->_node->setPos(Math::Vector2d(x, y));
+		actor->setFacing(getFacing(dir, actor->getFacing()));
+		actor->setRoom(room);
+		return 0;
+	}
+	default:
+		return sq_throwerror(v, "invalid number of arguments");
+	}
 }
 
 static SQInteger actorBlinkRate(HSQUIRRELVM v) {
-	warning("TODO: actorBlinkRate not implemented");
-	return 0;
-}
-
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	float min;
+	if (SQ_FAILED(sqget(v, 3, min)))
+		return sq_throwerror(v, "failed to get min");
+	float max;
+	if (SQ_FAILED(sqget(v, 4, max)))
+		return sq_throwerror(v, "failed to get max");
+	actor->blinkRate(min, max);
+	return 0;
+}
+
+// Adjusts the colour of the actor.
+//
+// . code-block:: Squirrel
+// actorColor(coroner, 0xc0c0c0)
 static SQInteger actorColor(HSQUIRRELVM v) {
-	warning("TODO: actorColor not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	SQInteger c;
+	if (SQ_FAILED(sq_getinteger(v, 3, &c)))
+		return sq_throwerror(v, "failed to get color");
+	actor->_node->setColor(Color::rgb(c));
 	return 0;
 }
 
+// Sets the actor's costume to the (JSON) filename animation file.
+// If the actor is expected to preform the standard walk, talk, stand, reach animations, they need to exist in the file.
+// If a sheet is given, this is a sprite sheet containing all the images needed for the animation.
 static SQInteger actorCostume(HSQUIRRELVM v) {
-	warning("TODO: actorCostume not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+
+	Common::String name;
+	if (SQ_FAILED(sqget(v, 3, name)))
+		return sq_throwerror(v, "failed to get name");
+
+	Common::String sheet;
+	if (sq_gettop(v) == 4)
+		sqget(v, 4, sheet);
+	debug("Actor costume %s %s", name.c_str(), sheet.c_str());
+	actor->setCostume(name, sheet);
 	return 0;
 }
 
@@ -73,13 +203,52 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Makes the actor face a given direction.
+// Directions are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT.
+// Similar to actorTurnTo, but will not animate the change, it will instantly be in the specified direction.
 static SQInteger actorFace(HSQUIRRELVM v) {
-	warning("TODO: actorFace not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 2) {
+		Facing dir = actor->getFacing();
+		sqpush(v, (int)dir);
+		return 1;
+	}
+
+	if (sq_gettype(v, 3) == OT_INTEGER) {
+		int dir = 0;
+		if (SQ_FAILED(sqget(v, 3, dir)))
+			return sq_throwerror(v, "failed to get direction");
+		// FACE_FLIP ?
+		if (dir == 0x10) {
+			Facing facing = flip(actor->getFacing());
+			actor->setFacing(facing);
+		} else {
+			actor->setFacing((Facing)dir);
+		}
+	} else {
+		Object *actor2 = sqactor(v, 3);
+		if (!actor2)
+			return sq_throwerror(v, "failed to get actor to face to");
+		Facing facing = getFacingToFaceTo(actor, actor2);
+		actor->setFacing(facing);
+	}
 	return 0;
 }
 
 static SQInteger actorHidden(HSQUIRRELVM v) {
-	warning("TODO: actorHidden not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int hidden = 0;
+	if (SQ_FAILED(sqget(v, 3, hidden)))
+		return sq_throwerror(v, "failed to get hidden");
+	if (hidden && (g_engine->_actor == actor)) {
+		g_engine->follow(nullptr);
+	}
+	actor->_node->setVisible(hidden == 0);
 	return 0;
 }
 
@@ -94,8 +263,16 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 }
 
 static SQInteger actorRoom(HSQUIRRELVM v) {
-	warning("TODO: actorRoom not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Room *room = actor->_room;
+	if (!room) {
+		sq_pushnull(v);
+	} else {
+		sqpush(v, room->_table);
+	}
+	return 1;
 }
 
 static SQInteger actorHideLayer(HSQUIRRELVM v) {
@@ -208,9 +385,32 @@ static SQInteger addSelectableActor(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Creates a new actor from a table.
+//
+// An actor is defined in the DefineActors.nut file.
 static SQInteger createActor(HSQUIRRELVM v) {
-	warning("TODO: createActor not implemented");
-	return 0;
+	if (sq_gettype(v, 2) != OT_TABLE)
+		return sq_throwerror(v, "failed to get a table");
+
+	HSQUIRRELVM vm = g_engine->getVm();
+	Object *actor = Object::createActor();
+	sq_resetobject(&actor->_table);
+	sq_getstackobj(v, 2, &actor->_table);
+	sq_addref(vm, &actor->_table);
+	setId(actor->_table, newActorId());
+
+	Common::String key;
+	sqgetf(actor->_table, "_key", key);
+	actor->_key = key;
+
+	debug("Create actor %s %d", key.c_str(), actor->getId());
+	actor->_node = new ActorNode(actor);
+	actor->_nodeAnim = new Anim(actor);
+	actor->_node->addChild(actor->_nodeAnim);
+	g_engine->_actors.push_back(actor);
+
+	sq_pushobject(v, actor->_table);
+	return 1;
 }
 
 static SQInteger flashSelectableActor(HSQUIRRELVM v) {
@@ -228,9 +428,21 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 	return 0;
 }
 
+// returns true if the specified actor is currently in the screen.
 static SQInteger isActorOnScreen(HSQUIRRELVM v) {
-	warning("TODO: isActorOnScreen not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get actor/object");
+
+	if (obj->_room != g_engine->_room) {
+		sqpush(v, false);
+	} else {
+		Math::Vector2d pos = obj->_node->getPos() - g_engine->getGfx().cameraPos();
+		Math::Vector2d size = g_engine->getGfx().camera();
+		bool isOnScreen = Common::Rect(0.0f, 0.0f, size.getX(), size.getY()).contains(pos.getX(), pos.getY());
+		sqpush(v, isOnScreen);
+	}
+	return 1;
 }
 
 static SQInteger isActorSelectable(HSQUIRRELVM v) {
@@ -238,14 +450,23 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 	return 0;
 }
 
+// If an actor is specified, returns true otherwise returns false.
 static SQInteger is_actor(HSQUIRRELVM v) {
-	warning("TODO: is_actor not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	sqpush(v, actor != nullptr);
+	return 1;
 }
 
+// Returns an array with every single actor that has been defined in the game so far, including non-player characters.
+// See also masterRoomArray.
 static SQInteger masterActorArray(HSQUIRRELVM v) {
-	warning("TODO: masterActorArray not implemented");
-	return 0;
+	sq_newarray(v, 0);
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *actor = g_engine->_actors[i];
+		sqpush(v, actor->_table);
+		sq_arrayappend(v, -2);
+	}
+	return 1;
 }
 
 static SQInteger mumbleLine(HSQUIRRELVM v) {
@@ -258,8 +479,12 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Causes the actor to become the selected actor.
+// If they are in the same room as the last selected actor the camera will pan over to them.
+// If they are in a different room, the camera will cut to the new room.
+// The UI will change to reflect the new actor and their inventory.
 static SQInteger selectActor(HSQUIRRELVM v) {
-	warning("TODO: selectActor not implemented");
+	g_engine->setActor(sqobj(v, 2));
 	return 0;
 }
 
@@ -274,54 +499,54 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 }
 
 void sqgame_register_actorlib(HSQUIRRELVM v) {
-  regFunc(v, actorAnimationFlags, "actorAnimationFlags");
-  regFunc(v, actorAnimationNames, "actorAnimationNames");
-  regFunc(v, actorAlpha, "actorAlpha");
-  regFunc(v, actorAt, "actorAt");
-  regFunc(v, actorBlinkRate, "actorBlinkRate");
-  regFunc(v, actorColor, "actorColor");
-  regFunc(v, actorCostume, "actorCostume");
-  regFunc(v, actorDistanceTo, "actorDistanceTo");
-  regFunc(v, actorDistanceWithin, "actorDistanceWithin");
-  regFunc(v, actorFace, "actorFace");
-  regFunc(v, actorHidden, "actorHidden");
-  regFunc(v, actorHideLayer, "actorHideLayer");
-  regFunc(v, actorInTrigger, "actorInTrigger");
-  regFunc(v, actorInWalkbox, "actorInWalkbox");
-  regFunc(v, actorLockFacing, "actorLockFacing");
-  regFunc(v, actorPlayAnimation, "actorPlayAnimation");
-  regFunc(v, actorPosX, "actorPosX");
-  regFunc(v, actorPosY, "actorPosY");
-  regFunc(v, actorRenderOffset, "actorRenderOffset");
-  regFunc(v, actorRoom, "actorRoom");
-  regFunc(v, actorShowLayer, "actorShowLayer");
-  regFunc(v, actorSlotSelectable, "actorSlotSelectable");
-  regFunc(v, actorStand, "actorStand");
-  regFunc(v, actorStopWalking, "actorStopWalking");
-  regFunc(v, actorTalkColors, "actorTalkColors");
-  regFunc(v, actorTalking, "actorTalking");
-  regFunc(v, actorTalkOffset, "actorTalkOffset");
-  regFunc(v, actorTurnTo, "actorTurnTo");
-  regFunc(v, actorUsePos, "actorUsePos");
-  regFunc(v, actorUseWalkboxes, "actorUseWalkboxes");
-  regFunc(v, actorVolume, "actorVolume");
-  regFunc(v, actorWalking, "actorWalking");
-  regFunc(v, actorWalkForward, "actorWalkForward");
-  regFunc(v, actorWalkSpeed, "actorWalkSpeed");
-  regFunc(v, actorWalkTo, "actorWalkTo");
-  regFunc(v, addSelectableActor, "addSelectableActor");
-  regFunc(v, createActor, "createActor");
-  regFunc(v, flashSelectableActor, "flashSelectableActor");
-  regFunc(v, is_actor, "is_actor");
-  regFunc(v, isActorOnScreen, "isActorOnScreen");
-  regFunc(v, isActorSelectable, "isActorSelectable");
-  regFunc(v, mumbleLine, "mumbleLine");
-  regFunc(v, masterActorArray, "masterActorArray");
-  regFunc(v, sayLine, "sayLine");
-  regFunc(v, sayLineAt, "sayLineAt");
-  regFunc(v, selectActor, "selectActor");
-  regFunc(v, stopTalking, "stopTalking");
-  regFunc(v, triggerActors, "triggerActors");
-  regFunc(v, verbUIColors, "verbUIColors");
+	regFunc(v, actorAnimationFlags, "actorAnimationFlags");
+	regFunc(v, actorAnimationNames, "actorAnimationNames");
+	regFunc(v, actorAlpha, "actorAlpha");
+	regFunc(v, actorAt, "actorAt");
+	regFunc(v, actorBlinkRate, "actorBlinkRate");
+	regFunc(v, actorColor, "actorColor");
+	regFunc(v, actorCostume, "actorCostume");
+	regFunc(v, actorDistanceTo, "actorDistanceTo");
+	regFunc(v, actorDistanceWithin, "actorDistanceWithin");
+	regFunc(v, actorFace, "actorFace");
+	regFunc(v, actorHidden, "actorHidden");
+	regFunc(v, actorHideLayer, "actorHideLayer");
+	regFunc(v, actorInTrigger, "actorInTrigger");
+	regFunc(v, actorInWalkbox, "actorInWalkbox");
+	regFunc(v, actorLockFacing, "actorLockFacing");
+	regFunc(v, actorPlayAnimation, "actorPlayAnimation");
+	regFunc(v, actorPosX, "actorPosX");
+	regFunc(v, actorPosY, "actorPosY");
+	regFunc(v, actorRenderOffset, "actorRenderOffset");
+	regFunc(v, actorRoom, "actorRoom");
+	regFunc(v, actorShowLayer, "actorShowLayer");
+	regFunc(v, actorSlotSelectable, "actorSlotSelectable");
+	regFunc(v, actorStand, "actorStand");
+	regFunc(v, actorStopWalking, "actorStopWalking");
+	regFunc(v, actorTalkColors, "actorTalkColors");
+	regFunc(v, actorTalking, "actorTalking");
+	regFunc(v, actorTalkOffset, "actorTalkOffset");
+	regFunc(v, actorTurnTo, "actorTurnTo");
+	regFunc(v, actorUsePos, "actorUsePos");
+	regFunc(v, actorUseWalkboxes, "actorUseWalkboxes");
+	regFunc(v, actorVolume, "actorVolume");
+	regFunc(v, actorWalking, "actorWalking");
+	regFunc(v, actorWalkForward, "actorWalkForward");
+	regFunc(v, actorWalkSpeed, "actorWalkSpeed");
+	regFunc(v, actorWalkTo, "actorWalkTo");
+	regFunc(v, addSelectableActor, "addSelectableActor");
+	regFunc(v, createActor, "createActor");
+	regFunc(v, flashSelectableActor, "flashSelectableActor");
+	regFunc(v, is_actor, "is_actor");
+	regFunc(v, isActorOnScreen, "isActorOnScreen");
+	regFunc(v, isActorSelectable, "isActorSelectable");
+	regFunc(v, mumbleLine, "mumbleLine");
+	regFunc(v, masterActorArray, "masterActorArray");
+	regFunc(v, sayLine, "sayLine");
+	regFunc(v, sayLineAt, "sayLineAt");
+	regFunc(v, selectActor, "selectActor");
+	regFunc(v, stopTalking, "stopTalking");
+	regFunc(v, triggerActors, "triggerActors");
+	regFunc(v, verbUIColors, "verbUIColors");
 }
 } // namespace Twp
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index d011cde01cc..e8e9c7531ba 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -447,10 +447,17 @@ static SQInteger markStat(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns the internal int value of x
 static SQInteger ord(HSQUIRRELVM v) {
-	// TODO: ord
-	warning("ord not implemented");
-	return 0;
+	Common::String letter;
+	if (SQ_FAILED(sqget(v, 2, letter)))
+		return sq_throwerror(v, "Failed to get letter");
+	if (letter.size() > 0) {
+		sq_pushinteger(v, (int)(letter[0]));
+	} else {
+		sq_pushinteger(v, 0);
+	}
+	return 1;
 }
 
 static SQInteger pushSentence(HSQUIRRELVM v) {
@@ -630,7 +637,7 @@ void sqgame_register_genlib(HSQUIRRELVM v) {
 	regFunc(v, markStat, _SC("markStat"));
 	regFunc(v, ord, _SC("ord"));
 	regFunc(v, pushSentence, _SC("pushSentence"));
-	regFunc(v, randomFrom, _SC("randomFrom"));
+	regFunc(v, randomFrom, _SC("randomfrom"));
 	regFunc(v, randomOdds, _SC("randomOdds"));
 	regFunc(v, randomseed, _SC("randomseed"));
 	regFunc(v, refreshUI, _SC("refreshUI"));
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index 4dd91c5d571..f5633a221de 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -122,8 +122,8 @@
 #define VERB_USE  10
 #define VERB_DIALOG  13
 #define VERBFLAG_INSTANT  1
-#define NO  0
-#define YES  1
+#define TWP_NO  0
+#define TWP_YES  1
 #define UNSELECTABLE  0
 #define SELECTABLE  1
 #define TEMP_UNSELECTABLE  2
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 7f49e0a8c0e..e548ab354f5 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -50,6 +50,7 @@ MODULE_OBJS = \
 	soundlib.o \
 	prefs.o \
 	tsv.o \
+	util.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index d71fda125f3..1c3c1a0a405 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -24,10 +24,15 @@
 #include "twp/object.h"
 #include "twp/scenegraph.h"
 #include "twp/squtil.h"
+#include "twp/util.h"
+#include "twp/ggpack.h"
 
 namespace Twp {
 
-static int gObjectId = START_OBJECTID;
+#define STAND_ANIMNAME "stand"
+#define HEAD_ANIMNAME "head"
+#define WALK_ANIMNAME "walk"
+#define REACH_ANIMNAME "reach"
 
 Object::Object()
 	: _talkOffset(0, 90) {
@@ -41,93 +46,16 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 	: _talkOffset(0, 90), _table(o), _key(key) {
 }
 
-Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
-	Object *obj = new Object();
-	obj->_temporary = true;
-
-	HSQUIRRELVM v = g_engine->getVm();
-
-	// create a table for this object
-	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->_table);
-	sq_addref(v, &obj->_table);
-	sq_pop(v, 1);
-
-	// assign an id
-	setId(obj->_table, gObjectId++);
-	Common::String name = frames.size() > 0 ? frames[0] : "noname";
-	sqsetf(obj->_table, "name", name);
-	obj->_key = name;
-	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
-
-	obj->_room = this;
-	obj->_sheet = sheet;
-	obj->_touchable = false;
-
-	// create anim if any
-	if (frames.size() > 0) {
-		ObjectAnimation objAnim;
-		objAnim.name = "state0";
-		objAnim.frames.push_back(frames);
-		obj->_anims.push_back(objAnim);
-	}
-
-	obj->_node->setZSort(1);
-	layer(0)->_objects.push_back(obj);
-	layer(0)->_node->addChild(obj->_node);
-	obj->_layer = layer(0);
-	obj->setState(0);
-
-	g_engine->_objects.push_back(obj);
-
-	return obj;
-}
-
-Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
-	Object *obj = new Object();
-	obj->_temporary = true;
-
-	HSQUIRRELVM v = g_engine->getVm();
-
-	// create a table for this object
-	sq_newtable(v);
-	sq_getstackobj(v, -1, &obj->_table);
-	sq_addref(v, &obj->_table);
-	sq_pop(v, 1);
-
-	// assign an id
-	setId(obj->_table, gObjectId++);
-	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
-	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
-	obj->_touchable = false;
-
-	Text txt(fontName, text, hAlign, vAlign, maxWidth);
-
-	// TODO:
-	//   let node = newTextNode(text)
-	//   var v = 0.5f
-	//   case vAlign:
-	//   of tvTop:
-	//     v = 0f
-	//   of tvCenter:
-	//     v = 0.5f
-	//   of tvBottom:
-	//     v = 1f
-	//   case hAlign:
-	//   of thLeft:
-	//     node.setAnchorNorm(vec2(0f, v))
-	//   of thCenter:
-	//     node.setAnchorNorm(vec2(0.5f, v))
-	//   of thRight:
-	//     node.setAnchorNorm(vec2(1f, v))
-	//   obj.node = node
-	//   self.layer(0).objects.add(obj);
-	//   self.layer(0).node.addChild obj.node;
-	//   obj.layer = self.layer(0);
-
-	g_engine->_objects.push_back(obj);
-
-	return obj;
+Object *Object::createActor() {
+	Object *result = new Object();
+	result->_hotspot = Common::Rect(-18, 0, 37, 71);
+	result->_facing = FACE_FRONT;
+	result->_useWalkboxes = true;
+	result->showLayer("blink", false);
+	result->showLayer("eyes_left", false);
+	result->showLayer("eyes_right", false);
+	result->setHeadIndex(1);
+	return result;
 }
 
 int Object::getId() const {
@@ -186,12 +114,19 @@ bool Object::playCore(const Common::String &state, bool loop, bool instant) {
 }
 
 void Object::showLayer(const Common::String &layer, bool visible) {
-	Common::String *s = Common::find(_hiddenLayers.begin(), _hiddenLayers.end(), layer);
+	int index = -1;
+	for (int i = 0; i < _hiddenLayers.size(); i++) {
+		if (_hiddenLayers[i] == layer) {
+			index = i;
+			break;
+		}
+	}
+
 	if (visible) {
-		if (s)
-			_hiddenLayers.remove_at(s - &_hiddenLayers[0]);
+		if (index != -1)
+			_hiddenLayers.remove_at(index);
 	} else {
-		if (!s)
+		if (index == -1)
 			_hiddenLayers.push_back(layer);
 	}
 	if (_node != NULL) {
@@ -356,7 +291,7 @@ void Object::setRoom(Room *room) {
 
 void Object::delObject() {
 	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
-    _node->getParent()->removeChild(_node);
+	_node->getParent()->removeChild(_node);
 }
 
 void Object::stopObjectMotors() {
@@ -364,39 +299,111 @@ void Object::stopObjectMotors() {
 }
 
 void Object::setFacing(Facing facing) {
-  if (_facing != facing) {
-    debug("set facing: %d", facing);
-    bool update = !(((_facing == FACE_LEFT) && (facing == FACE_RIGHT)) || ((_facing == FACE_RIGHT) && (facing == FACE_LEFT)));
-    _facing = facing;
-    if (update && _nodeAnim)
-      play(_animName, _animLoop);
-  }
+	if (_facing != facing) {
+		debug("set facing: %d", facing);
+		bool update = !(((_facing == FACE_LEFT) && (facing == FACE_RIGHT)) || ((_facing == FACE_RIGHT) && (facing == FACE_LEFT)));
+		_facing = facing;
+		if (update && _nodeAnim)
+			play(_animName, _animLoop);
+	}
 }
 
 Facing Object::getDoorFacing() {
-  int flags = getFlags();
-  if (flags & DOOR_LEFT)
-    return FACE_LEFT;
-  else if (flags & DOOR_RIGHT)
-    return FACE_RIGHT;
-  else if (flags & DOOR_FRONT)
-    return FACE_FRONT;
-  else
-    return FACE_BACK;
+	int flags = getFlags();
+	if (flags & DOOR_LEFT)
+		return FACE_LEFT;
+	else if (flags & DOOR_RIGHT)
+		return FACE_RIGHT;
+	else if (flags & DOOR_FRONT)
+		return FACE_FRONT;
+	else
+		return FACE_BACK;
 }
 
 bool Object::inInventory() {
-  return isObject(getId()) && getIcon().size() > 0;
+	return isObject(getId()) && getIcon().size() > 0;
 }
 
 bool Object::contains(Math::Vector2d pos) {
-  Math::Vector2d p = pos - _node->getPos() - _node->getOffset();
-  return _hotspot.contains(p.getX(), p.getY());
+	Math::Vector2d p = pos - _node->getPos() - _node->getOffset();
+	return _hotspot.contains(p.getX(), p.getY());
+}
+
+void Object::dependentOn(Object *dependentObj, int state) {
+	_dependentState = state;
+	_dependentObj = dependentObj;
+}
+
+Common::String Object::getAnimName(const Common::String &key) {
+	if (_animNames.contains(key))
+		return _animNames[key];
+	return key;
+}
+
+void Object::setHeadIndex(int head) {
+	for (int i = 0; i <= 6; i++) {
+		showLayer(Common::String::format("%s%d", getAnimName(STAND_ANIMNAME).c_str(), i), i == head);
+	}
+}
+
+bool Object::isWalking() {
+	// TODO: return not self.walkTo.isNil and self.walkTo.enabled();
+	return false;
+}
+
+void Object::stopWalking() {
+	// TODO: stopWalking
+	//   if (_walkTo)
+	//     _walkTo->disable();
+}
+
+void Object::setAnimationNames(const Common::String &head, const Common::String &stand, const Common::String &walk, const Common::String &reach) {
+	if (head.size() > 0)
+		setHeadIndex(0);
+	_animNames[HEAD_ANIMNAME] = head;
+	showLayer(_animNames[HEAD_ANIMNAME], true);
+	setHeadIndex(1);
+	if (stand.size() > 0)
+		_animNames[STAND_ANIMNAME] = stand;
+	if (walk.size() > 0)
+		_animNames[WALK_ANIMNAME] = walk;
+	if (reach.size() > 0)
+		_animNames[REACH_ANIMNAME] = reach;
+	if (isWalking())
+		play(getAnimName(WALK_ANIMNAME), true);
+}
+
+void Object::blinkRate(float min, float max) {
+	// TODO:
+	//   if (min == 0.0 && max == 0.0)
+	//     _blink = nil;
+	//   else:
+	//     _blink = new Blink(this, min, max);
+}
+
+void Object::setCostume(const Common::String &name, const Common::String &sheet) {
+	GGPackEntryReader entry;
+	entry.open(g_engine->_pack, name + ".json");
+
+	GGHashMapDecoder dec;
+	Common::JSONValue *json = dec.open(&entry);
+	const Common::JSONObject& jCostume = json->asObject();
+
+	parseObjectAnimations(jCostume["animations"]->asArray(), _anims);
+	_costumeName = name;
+	_costumeSheet = sheet;
+	if ((sheet.size() == 0) && jCostume.contains("sheet")) {
+		_sheet = jCostume["sheet"]->asString();
+	} else {
+		_sheet = sheet;
+	}
+	stand();
+
+	delete json;
 }
 
-void Object::dependentOn(Object* dependentObj, int state) {
-  _dependentState = state;
-  _dependentObj = dependentObj;
+void Object::stand() {
+  play(getAnimName(STAND_ANIMNAME));
 }
 
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 18dafe5e15d..ffcad5a8cc1 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -28,6 +28,7 @@
 #include "math/vector2d.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/ids.h"
+#include "twp/gfx.h"
 
 namespace Twp {
 
@@ -69,6 +70,7 @@ struct VerbId {
 	int id = 0;
 };
 
+class Object;
 struct Sentence {
     VerbId verb;
     Object* noun1 = nullptr;
@@ -86,6 +88,8 @@ public:
 	Object();
 	Object(HSQOBJECT o, const Common::String& key);
 
+	static Object* createActor();
+
 	int getId() const;
 
 	// Changes the `state` of an object, although this can just be a internal state,
@@ -136,6 +140,15 @@ public:
 	void stopObjectMotors();
 	void dependentOn(Object* dependentObj, int state);
 
+	Common::String getAnimName(const Common::String &key);
+	void setHeadIndex(int head);
+	void setAnimationNames(const Common::String& head, const Common::String& stand, const Common::String& walk, const Common::String& reach);
+	bool isWalking();
+	void stopWalking();
+	void blinkRate(float min, float max);
+	void setCostume(const Common::String &name, const Common::String &sheet);
+	void stand();
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index d30852b0c02..db3edb8269c 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -109,20 +109,26 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 		switch (hAlign) {
 		case 0x0000000010000000:
 			thAlign = thLeft;
+			break;
 		case 0x0000000020000000:
 			thAlign = thCenter;
+			break;
 		case 0x0000000040000000:
 			thAlign = thRight;
+			break;
 		default:
 			return sq_throwerror(v, "failed to get halign");
 		}
 		switch (vAlign) {
 		case 0xFFFFFFFF80000000:
 			tvAlign = tvTop;
+			break;
 		case 0x0000000001000000:
 			tvAlign = tvBottom;
+			break;
 		default:
 			tvAlign = tvTop;
+			break;
 		}
 	}
 	debug("Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
@@ -382,7 +388,9 @@ static SQInteger objectHotspot(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get right");
 	if (SQ_FAILED(sqget(v, 6, bottom)))
 		return sq_throwerror(v, "failed to get bottom");
-	obj->_hotspot = Common::Rect(left, top, right - left + 1, bottom - top + 1);
+	if(bottom<top)
+		SWAP(bottom, top);
+	obj->_hotspot = Common::Rect(left, top, right, bottom);
 	return 0;
 }
 
@@ -545,6 +553,7 @@ static SQInteger objectTouchable(HSQUIRRELVM v) {
 		obj->_touchable = touchable;
 		return 0;
 	}
+	return sq_throwerror(v, "objectTouchable: invalid argument");
 }
 
 //  Sets the zsort order of an object, essentially the order in which an object is drawn on the screen.
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index a36b3854825..2885feb44c3 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -27,27 +27,11 @@
 #include "twp/scenegraph.h"
 #include "twp/ids.h"
 #include "twp/object.h"
+#include "twp/util.h"
 #include "common/algorithm.h"
 
 namespace Twp {
 
-static Math::Vector2d parseVec2(const Common::String &s) {
-	float x, y;
-	sscanf(s.c_str(), "{%f,%f}", &x, &y);
-	return {x, y};
-}
-
-static Common::Rect parseRect(const Common::String &s) {
-	float x1, y1;
-	float x2, y2;
-	sscanf(s.c_str(), "{{%f,%f},{%f,%f}}", &x1, &y1, &x2, &y2);
-	return Common::Rect(x1, y1, x2, y2);
-}
-
-static bool toBool(const Common::JSONObject &jNode, const Common::String &key) {
-	return jNode.contains(key) && jNode[key]->asIntegerNumber() == 1;
-}
-
 static ObjectType toObjectType(const Common::JSONObject &jObject) {
 	if (toBool(jObject, "prop"))
 		return otProp;
@@ -98,60 +82,6 @@ static Walkbox parseWalkbox(const Common::String &text) {
 	return Walkbox(points);
 }
 
-static float parseFps(const Common::JSONValue &jFps) {
-	if (jFps.isNumber())
-		return jFps.asNumber();
-	if (jFps.isIntegerNumber())
-		return jFps.asIntegerNumber();
-	error("fps should be a number: %s", jFps.stringify().c_str());
-}
-
-static ObjectAnimation parseObjectAnimation(const Common::JSONObject &jAnim) {
-	ObjectAnimation result;
-	if (jAnim.contains("sheet"))
-		result.sheet = jAnim["sheet"]->asString();
-	result.name = jAnim["name"]->asString();
-	result.loop = toBool(jAnim, "loop");
-	result.fps = jAnim.contains("fps") ? parseFps(*jAnim["fps"]) : 0.f;
-	result.flags = jAnim.contains("flags") && jAnim["flags"]->isIntegerNumber() ? jAnim["flags"]->asIntegerNumber() : 0;
-	if (jAnim.contains("frames") && jAnim["frames"]->isArray()) {
-		const Common::JSONArray &jFrames = jAnim["frames"]->asArray();
-		for (auto it = jFrames.begin(); it != jFrames.end(); it++) {
-			Common::String name = (*it)->asString();
-			result.frames.push_back(name);
-		}
-	}
-
-	if (jAnim.contains("layers") && jAnim["layers"]->isArray()) {
-		const Common::JSONArray &jLayers = jAnim["layers"]->asArray();
-		for (auto it = jLayers.begin(); it != jLayers.end(); it++) {
-			ObjectAnimation layer = parseObjectAnimation((*it)->asObject());
-			result.layers.push_back(layer);
-		}
-	}
-
-	if (jAnim.contains("triggers") && jAnim["triggers"]->isArray()) {
-		const Common::JSONArray &jTriggers = jAnim["triggers"]->asArray();
-		for (auto it = jTriggers.begin(); it != jTriggers.end(); it++) {
-			result.triggers.push_back((*it)->isString() ? (*it)->asString() : "null");
-		}
-	}
-
-	if (jAnim.contains("offsets") && jAnim["offsets"]->isArray()) {
-		const Common::JSONArray &jOffsets = jAnim["offsets"]->asArray();
-		for (auto it = jOffsets.begin(); it != jOffsets.end(); it++) {
-			result.offsets.push_back(parseVec2((*it)->asString()));
-		}
-	}
-	return result;
-}
-
-static void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims) {
-	for (auto it = jAnims.begin(); it != jAnims.end(); it++) {
-		anims.push_back(parseObjectAnimation((*it)->asObject()));
-	}
-}
-
 static Scaling parseScaling(const Common::JSONArray &jScalings) {
 	float scale;
 	int y;
@@ -177,6 +107,95 @@ Room::~Room() {
 	delete _scene;
 }
 
+Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
+	Object *obj = new Object();
+	obj->_temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
+	sq_pop(v, 1);
+
+	// assign an id
+	setId(obj->_table, newObjId());
+	Common::String name = frames.size() > 0 ? frames[0] : "noname";
+	sqsetf(obj->_table, "name", name);
+	obj->_key = name;
+	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
+
+	obj->_room = this;
+	obj->_sheet = sheet;
+	obj->_touchable = false;
+
+	// create anim if any
+	if (frames.size() > 0) {
+		ObjectAnimation objAnim;
+		objAnim.name = "state0";
+		objAnim.frames.push_back(frames);
+		obj->_anims.push_back(objAnim);
+	}
+
+	obj->_node->setZSort(1);
+	layer(0)->_objects.push_back(obj);
+	layer(0)->_node->addChild(obj->_node);
+	obj->_layer = layer(0);
+	obj->setState(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
+Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
+	Object *obj = new Object();
+	obj->_temporary = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+
+	// create a table for this object
+	sq_newtable(v);
+	sq_getstackobj(v, -1, &obj->_table);
+	sq_addref(v, &obj->_table);
+	sq_pop(v, 1);
+
+	// assign an id
+	setId(obj->_table, newObjId());
+	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
+	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
+	obj->_touchable = false;
+
+	Text txt(fontName, text, hAlign, vAlign, maxWidth);
+
+	// TODO:
+	//   let node = newTextNode(text)
+	//   var v = 0.5f
+	//   case vAlign:
+	//   of tvTop:
+	//     v = 0f
+	//   of tvCenter:
+	//     v = 0.5f
+	//   of tvBottom:
+	//     v = 1f
+	//   case hAlign:
+	//   of thLeft:
+	//     node.setAnchorNorm(vec2(0f, v))
+	//   of thCenter:
+	//     node.setAnchorNorm(vec2(0.5f, v))
+	//   of thRight:
+	//     node.setAnchorNorm(vec2(1f, v))
+	//   obj.node = node
+	//   self.layer(0).objects.add(obj);
+	//   self.layer(0).node.addChild obj.node;
+	//   obj.layer = self.layer(0);
+
+	g_engine->_objects.push_back(obj);
+
+	return obj;
+}
+
 void Room::load(Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
@@ -333,6 +352,11 @@ Light *Room::createLight(Color color, Math::Vector2d pos) {
 	result->color = color;
 	result->pos = pos;
 	_lights._numLights++;
+	return result;
+}
+
+float Room::getScaling(float yPos) {
+	return _scaling.getScaling(yPos);
 }
 
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
@@ -351,4 +375,23 @@ Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
 
+float Scaling::getScaling(float yPos) {
+	if (values.size() == 0)
+		return 1.0f;
+	for (int i = 0; i < values.size(); i++) {
+		ScalingValue scaling = values[i];
+		if (yPos < scaling.y) {
+			if (i == 0)
+				return values[i].scale;
+			ScalingValue prevScaling = values[i - 1];
+			float dY = scaling.y - prevScaling.y;
+			float dScale = scaling.scale - prevScaling.scale;
+			float p = (yPos - prevScaling.y) / dY;
+			float scale = prevScaling.scale + (p * dScale);
+			return scale;
+		}
+	}
+	return values[values.size()-1].scale;
+}
+
 } // namespace Twp
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 691c54a8023..0a71685b473 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -68,6 +68,8 @@ struct ScalingValue {
 struct Scaling {
 	Common::Array<ScalingValue> values;
 	Common::String trigger;
+
+	float getScaling(float yPos);
 };
 
 struct Light {
@@ -107,6 +109,7 @@ public:
 	Object *getObj(const Common::String& key);
 
 	Light *createLight(Color color, Math::Vector2d pos);
+	float getScaling(float yPos);
 
 public:
 	Common::String _name;              // Name of the room
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index e0b23fccf51..68ae51b8b9f 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -264,7 +264,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_anim = anim;
 	_disabled = false;
 	setName(anim->name);
-	_sheet = anim->sheet.size() == 0 ? _obj->_room->_sheet : anim->sheet;
+	_sheet = (anim->sheet.size() == 0 && _obj->_room) ? _obj->_room->_sheet : anim->sheet;
 	_frames = anim->frames;
 	_frameIndex = instant && _frames.size() > 0 ? _frames.size() - 1 : 0;
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
@@ -337,7 +337,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 		SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 		const SpriteSheetFrame &sf = sheet->frameTable[frame];
 		Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
-		float x = flipX? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left: 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
+		float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
 		float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
 		Math::Vector3d pos(int(-x), int(y), 0.f);
 		trsf.translate(pos);
@@ -345,6 +345,17 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 	}
 }
 
+ActorNode::ActorNode(Object *obj)
+	: Node(obj->_key), _object(obj) {
+}
+
+int ActorNode::getZSort() const { return getPos().getY(); }
+
+Math::Vector2d ActorNode::getScale() const {
+	float y = _object->_room->getScaling(_object->_node->getPos().getY());
+	return Math::Vector2d(y, y);
+}
+
 Scene::Scene() : Node("Scene") {}
 Scene::~Scene() {}
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 069e6afa6d7..4b43e108557 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -168,6 +168,17 @@ private:
     Object* _obj;
 };
 
+class ActorNode final: public Node {
+public:
+	ActorNode(Object* obj);
+
+	int getZSort() const override final;
+	Math::Vector2d getScale() const override final;
+
+private:
+	Object* _object = nullptr;
+};
+
 class Scene: public Node {
 public:
 	Scene();
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 4fafc5380d8..60a6a7ed552 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -98,19 +98,19 @@ SQInteger sqpush(HSQUIRRELVM v, Rectf value) {
 }
 
 template<>
-SQInteger sqpush(HSQUIRRELVM v, Common::JSONValue* node) {
-  if(node->isIntegerNumber()) {
-	return sqpush(v, (int)node->asIntegerNumber());
-  } else if(node->isString()) {
-    return sqpush(v, node->asString());
-  } else if(node->isString()) {
-    return sqpush(v, (float)node->asNumber());
-  } else if(node->isNull()) {
-	sq_pushnull(v);
-	return 1;
-  } else {
-    return sq_throwerror(v, "This kind of node is not supported");
-  }
+SQInteger sqpush(HSQUIRRELVM v, Common::JSONValue *node) {
+	if (node->isIntegerNumber()) {
+		return sqpush(v, (int)node->asIntegerNumber());
+	} else if (node->isString()) {
+		return sqpush(v, node->asString());
+	} else if (node->isString()) {
+		return sqpush(v, (float)node->asNumber());
+	} else if (node->isNull()) {
+		sq_pushnull(v);
+		return 1;
+	} else {
+		return sq_throwerror(v, "This kind of node is not supported");
+	}
 }
 
 template<>
@@ -256,6 +256,12 @@ Room *sqroom(HSQUIRRELVM v, int i) {
 
 Object *sqobj(HSQOBJECT table) {
 	int id = getId(table);
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+    	Object* actor = g_engine->_actors[i];
+		if (getId(actor->_table) == id)
+			return actor;
+	}
+
 	for (int i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		for (int j = 0; j < room->_layers.size(); j++) {
@@ -276,6 +282,23 @@ Object *sqobj(HSQUIRRELVM v, int i) {
 	return sqobj(table);
 }
 
+Object *sqactor(HSQOBJECT table) {
+	int id = getId(table);
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *actor = g_engine->_actors[i];
+		if (actor->getId() == id)
+			return actor;
+	}
+	return nullptr;
+}
+
+Object *sqactor(HSQUIRRELVM v, int i) {
+	HSQOBJECT table;
+	if (SQ_SUCCEEDED(sqget(v, i, table)))
+		return sqactor(table);
+	return nullptr;
+}
+
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	SQInteger top = sq_gettop(v);
 	sqpush(v, obj);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index adfadb6ef71..2c9e7edc656 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -129,6 +129,8 @@ Room *sqroom(HSQOBJECT table);
 Room *sqroom(HSQUIRRELVM v, int i);
 Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
+Object* sqactor(HSQOBJECT table);
+Object* sqactor(HSQUIRRELVM v, int i);
 
 } // namespace Twp
 
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index ac0d9216390..120d2f0ca89 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -485,8 +485,8 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "VERB_USE", VERB_USE);
 	regConst(v, "VERB_DIALOG", VERB_DIALOG);
 	regConst(v, "VERBFLAG_INSTANT", VERBFLAG_INSTANT);
-	regConst(v, "NO", NO);
-	regConst(v, "YES", YES);
+	regConst(v, "NO", TWP_NO);
+	regConst(v, "YES", TWP_YES);
 	regConst(v, "UNSELECTABLE", UNSELECTABLE);
 	regConst(v, "SELECTABLE", SELECTABLE);
 	regConst(v, "TEMP_UNSELECTABLE", TEMP_UNSELECTABLE);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 9fac484699c..bc269ce9b43 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -129,16 +129,18 @@ Common::Error TwpEngine::run() {
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
-	const SQChar *code = R"(
-	MainStreet <- {
-		background = "MainStreet"
-		enter = function(enter_door) {}
-	}
-	defineRoom(MainStreet)
-	cameraInRoom(MainStreet)
-	cameraAt(0,128)
-	cameraPanTo(2820, 128, 5000, 2)
-	)";
+	// const SQChar *code = R"(
+	// MainStreet <- {
+	// 	background = "MainStreet"
+	// 	enter = function(enter_door) {}
+	// }
+	// defineRoom(MainStreet)
+	// cameraInRoom(MainStreet)
+	// cameraAt(0,128)
+	// cameraPanTo(2820, 128, 5000, 2)
+	// )";
+
+	const SQChar *code = "cameraInRoom(StartScreen)";
 
 	_vm.exec(code);
 
@@ -228,6 +230,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 		GGPackEntryReader entry;
 		entry.open(_pack, background + ".wimpy");
 		result->load(entry);
+		result->_name = name;
 		result->_pseudo = pseudo;
 		for (int i = 0; i < result->_layers.size(); i++) {
 			Layer *layer = result->_layers[i];
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
new file mode 100644
index 00000000000..8f55291da3b
--- /dev/null
+++ b/engines/twp/util.cpp
@@ -0,0 +1,131 @@
+/* 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 "twp/util.h"
+#include "twp/scenegraph.h"
+
+namespace Twp {
+
+Math::Vector2d operator*(Math::Vector2d v, float f) {
+	return Math::Vector2d(v.getX() * f, v.getY() * f);
+}
+
+Facing getFacing(int dir, Facing facing) {
+	if (dir == 0)
+		return facing;
+	if (dir == 0x10)
+		return getOppositeFacing(facing);
+	return (Facing)dir;
+}
+
+Facing flip(Facing facing) {
+	switch (facing) {
+	case FACE_BACK:
+		return FACE_FRONT;
+	case FACE_FRONT:
+		return FACE_BACK;
+	case FACE_LEFT:
+		return FACE_RIGHT;
+	case FACE_RIGHT:
+		return FACE_LEFT;
+	}
+}
+
+Facing getFacingToFaceTo(Object *actor, Object *obj) {
+	Math::Vector2d d = obj->_node->getPos() + obj->_node->getOffset() - (actor->_node->getPos() + actor->_node->getOffset());
+	if (abs(d.getY()) > abs(d.getX())) {
+		return d.getY() > 0 ? FACE_BACK : FACE_FRONT;
+	}
+	return d.getX() > 0 ? FACE_RIGHT : FACE_LEFT;
+}
+
+static float parseFps(const Common::JSONValue &jFps) {
+	if (jFps.isNumber())
+		return jFps.asNumber();
+	if (jFps.isIntegerNumber())
+		return jFps.asIntegerNumber();
+	error("fps should be a number: %s", jFps.stringify().c_str());
+}
+
+static ObjectAnimation parseObjectAnimation(const Common::JSONObject &jAnim) {
+	ObjectAnimation result;
+	if (jAnim.contains("sheet"))
+		result.sheet = jAnim["sheet"]->asString();
+	result.name = jAnim["name"]->asString();
+	result.loop = toBool(jAnim, "loop");
+	result.fps = jAnim.contains("fps") ? parseFps(*jAnim["fps"]) : 0.f;
+	result.flags = jAnim.contains("flags") && jAnim["flags"]->isIntegerNumber() ? jAnim["flags"]->asIntegerNumber() : 0;
+	if (jAnim.contains("frames") && jAnim["frames"]->isArray()) {
+		const Common::JSONArray &jFrames = jAnim["frames"]->asArray();
+		for (auto it = jFrames.begin(); it != jFrames.end(); it++) {
+			Common::String name = (*it)->asString();
+			result.frames.push_back(name);
+		}
+	}
+
+	if (jAnim.contains("layers") && jAnim["layers"]->isArray()) {
+		const Common::JSONArray &jLayers = jAnim["layers"]->asArray();
+		for (auto it = jLayers.begin(); it != jLayers.end(); it++) {
+			ObjectAnimation layer = parseObjectAnimation((*it)->asObject());
+			result.layers.push_back(layer);
+		}
+	}
+
+	if (jAnim.contains("triggers") && jAnim["triggers"]->isArray()) {
+		const Common::JSONArray &jTriggers = jAnim["triggers"]->asArray();
+		for (auto it = jTriggers.begin(); it != jTriggers.end(); it++) {
+			result.triggers.push_back((*it)->isString() ? (*it)->asString() : "null");
+		}
+	}
+
+	if (jAnim.contains("offsets") && jAnim["offsets"]->isArray()) {
+		const Common::JSONArray &jOffsets = jAnim["offsets"]->asArray();
+		for (auto it = jOffsets.begin(); it != jOffsets.end(); it++) {
+			result.offsets.push_back(parseVec2((*it)->asString()));
+		}
+	}
+	return result;
+}
+
+bool toBool(const Common::JSONObject &jNode, const Common::String &key) {
+	return jNode.contains(key) && jNode[key]->asIntegerNumber() == 1;
+}
+
+Math::Vector2d parseVec2(const Common::String &s) {
+	float x, y;
+	sscanf(s.c_str(), "{%f,%f}", &x, &y);
+	return {x, y};
+}
+
+Common::Rect parseRect(const Common::String &s) {
+	float x1, y1;
+	float x2, y2;
+	sscanf(s.c_str(), "{{%f,%f},{%f,%f}}", &x1, &y1, &x2, &y2);
+	return Common::Rect(x1, y1, x2, y2);
+}
+
+void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims) {
+	for (auto it = jAnims.begin(); it != jAnims.end(); it++) {
+		anims.push_back(parseObjectAnimation((*it)->asObject()));
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/util.h b/engines/twp/util.h
index da9cae9ca20..bd94e09c321 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -22,7 +22,10 @@
 #ifndef TWP_UTIL_H
 #define TWP_UTIL_H
 
+#include "twp/ids.h"
+#include "twp/object.h"
 #include "math/vector2d.h"
+#include "common/formats/json.h"
 
 namespace Twp {
 
@@ -35,9 +38,16 @@ T clamp(T x, T a, T b) {
 	return x;
 }
 
-Math::Vector2d operator*(Math::Vector2d v, float f) {
-	return Math::Vector2d(v.getX() * f, v.getY() * f);
-}
+Math::Vector2d operator*(Math::Vector2d v, float f);
+
+Facing getFacing(int dir, Facing facing);
+Facing flip(Facing facing);
+Facing getFacingToFaceTo(Object *actor, Object *obj);
+
+bool toBool(const Common::JSONObject &jNode, const Common::String &key);
+Math::Vector2d parseVec2(const Common::String &s);
+Common::Rect parseRect(const Common::String &s);
+void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
 
 } // namespace Twp
 


Commit: 3523b02fcfaea4b611f19d817831cda884420b93
    https://github.com/scummvm/scummvm/commit/3523b02fcfaea4b611f19d817831cda884420b93
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add BmFont and Motor

Changed paths:
  A engines/twp/motor.cpp
  A engines/twp/motor.h
    engines/twp/camera.cpp
    engines/twp/camera.h
    engines/twp/font.cpp
    engines/twp/font.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 524e15b9d40..0b65a8cc212 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -56,62 +56,73 @@ void Camera::setAt(Math::Vector2d at) {
 }
 
 void Camera::panTo(Math::Vector2d target, float time, InterpolationKind interpolation) {
-  if (!_moving) {
-    _moving = true;
-    _init = _pos;
-    _elapsed = 0.f;
-  }
-  _function = easing(interpolation);
-  _target = target;
-  _time = time;
+	if (!_moving) {
+		_moving = true;
+		_init = _pos;
+		_elapsed = 0.f;
+	}
+	_function = easing(interpolation);
+	_target = target;
+	_time = time;
 }
 
-void Camera::update(Room* room, Object* follow, float elapsed) {
-  _room = room;
-  _elapsed += elapsed;
-  bool isMoving = _elapsed < _time;
+void Camera::update(Room *room, Object *follow, float elapsed) {
+	_room = room;
+	_elapsed += elapsed;
+	bool isMoving = _elapsed < _time;
+
+	if (_moving && !isMoving) {
+		_moving = false;
+		_time = 0.f;
+		setAt(_target);
+	}
 
-  if(_moving && !isMoving) {
-    _moving = false;
-    _time = 0.f;
-    setAt(_target);
-  }
+	if (isMoving) {
+		float t = _elapsed / _time;
+		Math::Vector2d d = _target - _init;
+		Math::Vector2d pos = _init + (d * _function.func(t));
+		setAtCore(pos);
+		return;
+	}
 
-  if(isMoving) {
-    float t = _elapsed / _time;
-    Math::Vector2d d = _target - _init;
-    Math::Vector2d pos = _init + (d * _function.func(t));
-    setAtCore(pos);
-    return;
-  }
+	if (follow && follow->_node->isVisible() && follow->_room == room) {
+		Math::Vector2d screen = room->getScreenSize();
+		Math::Vector2d pos = follow->_node->getPos();
+		Math::Vector2d margin(screen.getX() / 6.f, screen.getY() / 6.f);
+		Math::Vector2d cameraPos = getAt();
 
-  if (follow && follow->_node->isVisible() && follow->_room == room) {
-    Math::Vector2d screen = room->getScreenSize();
-    Math::Vector2d pos = follow->_node->getPos();
-    Math::Vector2d margin(screen.getX() / 6.f, screen.getY() / 6.f);
-    Math::Vector2d cameraPos = getAt();
+		Math::Vector2d d = pos - cameraPos;
+		Math::Vector2d delta = d * elapsed;
+		bool sameActor = _follow == follow;
 
-    Math::Vector2d d = pos - cameraPos;
-    Math::Vector2d delta = d * elapsed;
-    bool sameActor = _follow == follow;
+		float x, y;
+		if (sameActor && (pos.getX() > (cameraPos.getX() + margin.getX())))
+			x = pos.getX() - margin.getX();
+		else if (sameActor && (pos.getX() < (cameraPos.getX() - margin.getX())))
+			x = pos.getX() + margin.getX();
+		else
+			x = cameraPos.getX() + (d.getX() > 0 ? MIN(delta.getX(), d.getX()) : MAX(delta.getX(), d.getX()));
+		if (sameActor && (pos.getY() > (cameraPos.getY() + margin.getY())))
+			y = pos.getY() - margin.getY();
+		else if (sameActor && (pos.getY() < (cameraPos.getY() - margin.getY())))
+			y = pos.getY() + margin.getY();
+		else
+			y = cameraPos.getY() + d.getY() > 0 ? MIN(delta.getY(), d.getY()) : MAX(delta.getY(), d.getY());
+		setAtCore(Math::Vector2d(x, y));
+		if (!sameActor && (fabs(pos.getX() - x) < 1.f) && (fabs(pos.getY() - y) < 1.f))
+			_follow = follow;
+	}
+}
 
-    float x, y;
-    if (sameActor && (pos.getX() > (cameraPos.getX() + margin.getX())))
-      x = pos.getX() - margin.getX();
-    else if(sameActor && (pos.getX() < (cameraPos.getX() - margin.getX())))
-      x = pos.getX() + margin.getX();
-    else
-      x = cameraPos.getX() + (d.getX() > 0? MIN(delta.getX(), d.getX()): MAX(delta.getX(), d.getX()));
-    if (sameActor && (pos.getY() > (cameraPos.getY() + margin.getY())))
-      y = pos.getY() - margin.getY();
-    else if (sameActor && (pos.getY() < (cameraPos.getY() - margin.getY())))
-      y = pos.getY() + margin.getY();
-    else
-      y = cameraPos.getY() + d.getY() > 0? MIN(delta.getY(), d.getY()): MAX(delta.getY(), d.getY());
-    setAtCore(Math::Vector2d(x, y));
-    if (!sameActor && (fabs(pos.getX() - x) < 1.f) && (fabs(pos.getY() - y) < 1.f))
-      _follow = follow;
-  }
+InterpolationMethod intToInterpolationMethod(int value) {
+	bool loop = (value & 0x10);
+	bool swing = (value & 0x20);
+	InterpolationKind kind = (InterpolationKind)(value & 0x0F);
+	InterpolationMethod im;
+	im.kind = kind;
+	im.loop = loop;
+	im.swing = swing;
+	return im;
 }
 
 } // namespace Twp
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
index 2978b1bb90a..c60b3f23be5 100644
--- a/engines/twp/camera.h
+++ b/engines/twp/camera.h
@@ -52,6 +52,8 @@ struct InterpolationMethod {
 	bool swing = false;
 };
 
+InterpolationMethod intToInterpolationMethod(int value);
+
 static float linear(float t) { return t; }
 
 static float easeIn(float t) {
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 84e89a328b3..f9a7adc8214 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -21,6 +21,7 @@
 
 #include "twp/font.h"
 #include "twp/twp.h"
+#include "twp/ggpack.h"
 #include "common/str.h"
 #include "graphics/opengl/system_headers.h"
 
@@ -193,11 +194,73 @@ Glyph GGFont::getGlyph(CodePoint chr) {
 	return _glyphs['?'];
 }
 
+BmFont::~BmFont() {}
+
+void BmFont::load(const Common::String &name) {
+	Common::String path = name + ".fnt";
+	if (!g_engine->_pack.assetExists(path.c_str())) {
+		path = name + "Font.fnt";
+	}
+	debug("Load font %s", path.c_str());
+	GGPackEntryReader entry;
+	if (!entry.open(g_engine->_pack, path)) {
+		error("error loading font %s", path.c_str());
+	}
+	char tmp[80];
+	while (!entry.eos()) {
+		Common::String line = entry.readLine();
+		if (line.hasPrefix("common")) {
+			sscanf(line.c_str(), "common lineHeight=%d base=%d scaleW=%d scaleH=%d pages=%d packed=%d", &_lnHeight, &_base, &_scaleW, &_scaleH, &_pages, &_packed);
+		} else if (line.hasPrefix("chars")) {
+		} else if (line.hasPrefix("char")) {
+			Char c;
+			sscanf(line.c_str(), "char id=%d\tx=%d\ty=%d\twidth=%d\theight=%d\txoffset=%d\tyoffset=%d\txadvance=%d\tpage=%d\tchnl=%d\tletter=\"%s\"", &c.id, &c.x, &c.y, &c.w, &c.h, &c.xoff, &c.yoff, &c.xadv, &c.page, &c.chnl, tmp);
+			_glyphs[c.id] = Glyph{c.xadv,
+								  Common::Rect(c.xoff, _lnHeight - c.yoff - c.h, c.xoff + c.w, _lnHeight - c.yoff),
+								  Common::Rect(c.x, c.y, c.x + c.w, c.y + c.h)};
+		} else if (line.hasPrefix("kernings")) {
+		} else if (line.hasPrefix("kerning")) {
+			KerningKey key;
+			int amount = 0;
+			sscanf(line.c_str(), "kerning\tfirst=%d\tsecond=%d\tamount=%d", &key.first, &key.second, &amount);
+			_kernings[key] = amount;
+		}
+	}
+	_name = name;
+}
+
+Glyph BmFont::getGlyph(CodePoint chr) {
+	if (_glyphs.contains(chr)) {
+		return _glyphs[chr];
+	}
+	return _glyphs['?'];
+}
+
+float BmFont::getKerning(CodePoint prev, CodePoint next) {
+	return 0.f;
+}
+
+bool operator==(const KerningKey &l, const KerningKey &r) {
+	return l.first == r.first && l.second == r.second;
+}
+
 Text::Text(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth, Color color)
 	: _font(NULL), _fontName(fontName), _texture(NULL), _txt(text), _col(color), _hAlign(hAlign), _vAlign(vAlign), _maxW(maxWidth), _dirty(true) {
 	update();
 }
 
+Text::Text() {}
+
+void Text::setFont(const Common::String &fontName) {
+	_fontName = fontName;
+	_dirty = true;
+}
+
+Math::Vector2d Text::getBounds() {
+	update();
+	return _bnds;
+}
+
 void Text::update() {
 	if (_dirty) {
 		_dirty = false;
diff --git a/engines/twp/font.h b/engines/twp/font.h
index 3b9b3893a8b..b4864d20fe1 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -28,6 +28,25 @@
 #include "common/hashmap.h"
 #include "common/str.h"
 
+namespace Twp {
+
+struct KerningKey {
+	int first;
+	int second;
+};
+bool operator==(const KerningKey &l, const KerningKey &r);
+
+} // namespace Twp
+
+namespace Common {
+
+template<>
+struct Hash<Twp::KerningKey> : public Common::UnaryFunction<Twp::KerningKey, uint> {
+	uint operator()(Twp::KerningKey val) const { return (uint)(val.first ^ val.second); }
+};
+
+} // namespace Common
+
 namespace Twp {
 typedef int32 CodePoint;
 
@@ -60,11 +79,47 @@ public:
 	virtual Common::String getName() override final { return _name; }
 
 private:
-	Common::HashMap<int, Glyph> _glyphs;
+	Common::HashMap<CodePoint, Glyph> _glyphs;
 	int _lineHeight;
 	Common::String _name;
 };
 
+struct Char {
+	int id = 0;
+	int x = 0;
+	int y = 0;
+	int w = 0;
+	int h = 0;
+	int xoff = 0;
+	int yoff = 0;
+	int xadv = 0;
+	int page = 0;
+	int chnl = 0;
+	Common::String _letter;
+};
+
+class BmFont : public Font {
+public:
+	virtual ~BmFont();
+	void load(const Common::String &path);
+
+	virtual int getLineHeight() override final { return _lnHeight; }
+	virtual float getKerning(CodePoint prev, CodePoint next) override final;
+	virtual Glyph getGlyph(CodePoint chr) override final;
+	virtual Common::String getName() override final { return _name; }
+
+private:
+	Common::HashMap<CodePoint, Glyph> _glyphs;
+	Common::HashMap<KerningKey, float> _kernings;
+	int _lnHeight = 0;
+	int _base = 0;
+	int _scaleW = 0;
+	int _scaleH = 0;
+	int _pages = 0;
+	int _packed = 0;
+	Common::String _name;
+};
+
 enum TextHAlignment {
 	thLeft,
 	thCenter,
@@ -81,9 +136,10 @@ enum TextVAlignment {
 // A text can contains color in hexadecimal with this format: #RRGGBB
 class Text {
 public:
-	Text(const Common::String& fontName, const Common::String& text, TextHAlignment hAlign = thCenter, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f, Color color = Color());
+	Text();
+	Text(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thCenter, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f, Color color = Color());
 
-	void setText(const Common::String& text) {
+	void setText(const Common::String &text) {
 		_txt = text;
 		_dirty = true;
 	}
@@ -113,26 +169,28 @@ public:
 	}
 	TextVAlignment getVAlign() { return _vAlign; }
 
-	Font* getFont() { return _font; }
+	void setFont(const Common::String &fontName);
+	Font *getFont() { return _font; }
+	Math::Vector2d getBounds();
 
-	void draw(Gfx& gfx, Math::Matrix4 trsf = Math::Matrix4());
+	void draw(Gfx &gfx, Math::Matrix4 trsf = Math::Matrix4());
 
 private:
 	void update();
 
 private:
-	Font* _font;
+	Font *_font = nullptr;
 	Common::String _fontName;
-	Texture* _texture;
+	Texture *_texture = nullptr;
 	Common::String _txt;
 	Color _col;
 	TextHAlignment _hAlign;
 	TextVAlignment _vAlign;
 	Common::Array<Vertex> _vertices;
 	Math::Vector2d _bnds;
-	float _maxW;
+	float _maxW = 0;
 	Common::Array<Common::Rect> _quads;
-	bool _dirty;
+	bool _dirty = false;
 };
 
 } // namespace Twp
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index e8e9c7531ba..bdfc0ff505e 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -302,9 +302,20 @@ static SQInteger getUserPref(HSQUIRRELVM v) {
 }
 
 static SQInteger getPrivatePref(HSQUIRRELVM v) {
-	// TODO: getPrivatePref
-	warning("getPrivatePref not implemented");
-	return 0;
+	Common::String key;
+	if (SQ_FAILED(sqget(v, 2, key)))
+		return sq_throwerror(v, "failed to get key");
+	debug("TODO: getPrivatePref");
+	// else if (g_engine->getPrefs().hasPrivPref(key))
+	// 	return sqpush(v, g_engine->getPrefs().privPrefAsJson(key));
+	//else
+	if (sq_gettop(v) == 3) {
+		HSQOBJECT obj;
+		sq_getstackobj(v, 3, &obj);
+		sqpush(v, obj);
+		return 1;
+	}
+	return sq_throwerror(v, "getPrivatePref: invalid argument");
 }
 
 static SQInteger incutscene(HSQUIRRELVM v) {
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index e548ab354f5..dfc34ab9110 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -51,6 +51,7 @@ MODULE_OBJS = \
 	prefs.o \
 	tsv.o \
 	util.o \
+	motor.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
new file mode 100644
index 00000000000..599620567f2
--- /dev/null
+++ b/engines/twp/motor.cpp
@@ -0,0 +1,43 @@
+
+/* 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 "twp/motor.h"
+#include "twp/object.h"
+#include "twp/scenegraph.h"
+
+namespace Twp {
+
+OffsetTo::~OffsetTo() {}
+
+OffsetTo::OffsetTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im)
+	: _obj(obj),
+	  _tween(obj->_node->getOffset(), pos, duration, im){
+}
+
+void OffsetTo::update(float elapsed) {
+  _tween.update(elapsed);
+  _obj->_node->setOffset(_tween.current());
+  if (!_tween.running())
+    disable();
+}
+
+} // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
new file mode 100644
index 00000000000..d7b198bf606
--- /dev/null
+++ b/engines/twp/motor.h
@@ -0,0 +1,109 @@
+
+/* 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 TWP_MOTOR_H
+#define TWP_MOTOR_H
+
+#include "math/vector2d.h"
+#include "twp/camera.h"
+#include "twp/util.h"
+
+namespace Twp {
+
+template<typename T>
+struct Tween {
+public:
+	Tween(T f, T t, float duration, InterpolationMethod im)
+		: frm(f), to(t), delta(to - frm), duration(duration), value(frm), easing_f(easing(im.kind)), swing(im.swing), loop(im.loop) {
+	}
+
+	bool running() {
+		if (swing || loop)
+			return true;
+		else
+			return elapsed < duration;
+	}
+
+	void update(float el) {
+		if (enabled && running()) {
+			elapsed += el;
+			float f = clamp(elapsed / duration, 0.0f, 1.0f);
+			if (!dir_forward)
+				f = 1.0 - f;
+			if ((elapsed > duration) && (swing || loop)) {
+				elapsed = elapsed - duration;
+				if (swing)
+					dir_forward = !dir_forward;
+			}
+			if (easing_f.func) {
+				f = easing_f.func(f);
+				value = frm + f * delta;
+			}
+		} else {
+			value = to;
+		}
+	}
+
+	T current() const { return value; }
+
+public:
+	T frm, to, delta;
+	float elapsed = 0.f;
+	float duration = 0.f; // duration in ms
+	T value;
+	EasingFunc_t easing_f;
+	bool enabled = true;
+	bool dir_forward = true;
+	bool swing = false;
+	bool loop = false;
+};
+
+class Motor {
+public:
+	virtual ~Motor() {}
+	virtual void disable() {
+		_enabled = false;
+	}
+	virtual bool isEnabled() const { return _enabled; }
+	virtual void update(float elapsed) = 0;
+
+private:
+	bool _enabled = true;
+};
+
+class Object;
+class OffsetTo : public Motor {
+public:
+	virtual ~OffsetTo();
+	OffsetTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Object *_obj = nullptr;
+	Tween<Math::Vector2d> _tween;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 1c3c1a0a405..6f9a0fec34e 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -26,6 +26,7 @@
 #include "twp/squtil.h"
 #include "twp/util.h"
 #include "twp/ggpack.h"
+#include "twp/motor.h"
 
 namespace Twp {
 
@@ -387,7 +388,7 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 
 	GGHashMapDecoder dec;
 	Common::JSONValue *json = dec.open(&entry);
-	const Common::JSONObject& jCostume = json->asObject();
+	const Common::JSONObject &jCostume = json->asObject();
 
 	parseObjectAnimations(jCostume["animations"]->asArray(), _anims);
 	_costumeName = name;
@@ -403,7 +404,38 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 }
 
 void Object::stand() {
-  play(getAnimName(STAND_ANIMNAME));
+	play(getAnimName(STAND_ANIMNAME));
+}
+
+void Object::update(float elapsedSec) {
+	// TODO: update
+	//   if not self.dependentObj.isNil:
+	//     self.node.visible = self.dependentObj.getState() == self.dependentState
+	//   self.alphaTo.updateMotor(elapsedSec)
+	//   self.rotateTo.updateMotor(elapsedSec)
+	if (_moveTo)
+		_moveTo->update(elapsedSec);
+	//   self.walkTo.updateMotor(elapsedSec)
+	//   self.talking.updateMotor(elapsedSec)
+	//   self.blink.updateMotor(elapsedSec)
+	//   self.turnTo.updateMotor(elapsedSec)
+	//   self.shakeTo.updateMotor(elapsedSec)
+	//   self.jiggleTo.updateMotor(elapsedSec)
+
+	if (_nodeAnim)
+		_nodeAnim->update(elapsedSec);
+
+	//   if self.icons.len > 1 and self.iconFps > 0:
+	//     self.iconElapsed += elapsedSec
+	//     if self.iconElapsed > (1f / self.iconFps.float32):
+	//       self.iconElapsed = 0f
+	//       self.iconIndex = (self.iconIndex + 1) mod self.icons.len
+
+	//   if self.popCount > 0:
+	//       self.popElapsed += elapsedSec
+	//       if self.popElapsed > 0.5f:
+	//         dec self.popCount
+	//         self.popElapsed -= 0.5f
 }
 
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index ffcad5a8cc1..755757f5494 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -80,6 +80,7 @@ struct Sentence {
 
 class Anim;
 class Room;
+class Motor;
 class Node;
 class Layer;
 
@@ -149,6 +150,8 @@ public:
 	void setCostume(const Common::String &name, const Common::String &sheet);
 	void stand();
 
+	void update(float elapsedSec);
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
@@ -204,6 +207,7 @@ public:
     float _popElapsed = 0.f;
     int _popCount = 0;
 	Sentence _exec;
+	Motor* _moveTo = nullptr;
 };
 
 } // namespace Twp
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index db3edb8269c..a625491738b 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -23,6 +23,8 @@
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/object.h"
+#include "twp/camera.h"
+#include "twp/motor.h"
 #include "twp/room.h"
 #include "twp/scenegraph.h"
 #include "squirrel/squirrel.h"
@@ -255,34 +257,32 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Places the specified object at the given x and y coordinates in the current room.
+//
+// .. code-block:: Squirrel
+// objectAt(text, 160,90)
+// objectAt(obj, leftMargin, topLinePos)
 static SQInteger objectAt(HSQUIRRELVM v) {
-	HSQOBJECT o;
-	sq_getstackobj(v, 2, &o);
-
-	SQInteger id;
-	sqgetf(o, "_id", id);
-
-	Object **pObj = Common::find_if(g_engine->_objects.begin(), g_engine->_objects.end(), [&](Object *o) {
-		SQObjectPtr id2;
-		_table(o->_table)->Get(sqtoobj(v, "_id"), id2);
-		return id == _integer(id2);
-	});
-
-	if (!pObj)
+	Object *obj = sqobj(v, 2);
+	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-
-	// TODO:
-	// Object* obj = *pObj;
-	// SQInteger x, y;
-	// if (SQ_FAILED(sq_getinteger(v, 3, &x)))
-	// 	return sq_throwerror(v, "failed to get x");
-	// if (SQ_FAILED(sq_getinteger(v, 4, &y)))
-	// 	return sq_throwerror(v, "failed to get y");
-	// obj->x = x;
-	// obj->y = y;
-	// debug("Object at: %lld, %lld", x, y);
-
-	return 0;
+	if (sq_gettop(v) == 3) {
+		Object *spot = sqobj(v, 3);
+		if (!spot)
+			return sq_throwerror(v, "failed to get spot");
+		obj->_node->setPos(spot->getUsePos());
+		return 0;
+	}
+	if (sq_gettop(v) == 4) {
+		SQInteger x, y;
+		if (SQ_FAILED(sq_getinteger(v, 3, &x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sq_getinteger(v, 4, &y)))
+			return sq_throwerror(v, "failed to get y");
+		obj->_node->setPos(Math::Vector2d(x, y));
+		return 0;
+	}
+	return sq_throwerror(v, "invalid number of arguments");
 }
 
 static SQInteger objectBumperCycle(HSQUIRRELVM v) {
@@ -388,21 +388,63 @@ static SQInteger objectHotspot(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get right");
 	if (SQ_FAILED(sqget(v, 6, bottom)))
 		return sq_throwerror(v, "failed to get bottom");
-	if(bottom<top)
+	if (bottom < top)
 		SWAP(bottom, top);
 	obj->_hotspot = Common::Rect(left, top, right, bottom);
 	return 0;
 }
 
+// Used for inventory object, it changes the object's icon to be the new one specified.
+//
+// .. code-block:: Squirrel
+// objectIcon(obj, "glowing_spell_book")
+// objectIcon(obj, "spell_book")
 static SQInteger objectIcon(HSQUIRRELVM v) {
-	// TODO: objectIcon
-	warning("objectIcon not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	switch (sq_gettype(v, 3)) {
+	case OT_STRING: {
+		Common::String icon;
+		if (SQ_FAILED(sqget(v, 3, icon)))
+			return sq_throwerror(v, "failed to get icon");
+		obj->setIcon(icon);
+		return 0;
+	}
+	case OT_ARRAY: {
+		Common::String icon;
+		Common::StringArray icons;
+		int fps;
+		sq_push(v, 3);
+		sq_pushnull(v); // null iterator
+		if (SQ_SUCCEEDED(sq_next(v, -2)))
+			sqget(v, -1, fps);
+		sq_pop(v, 2);
+		while (SQ_SUCCEEDED(sq_next(v, -2))) {
+			sqget(v, -1, icon);
+			icons.push_back(icon);
+			sq_pop(v, 2);
+		}
+		sq_pop(v, 2); // pops the null iterator and object
+		obj->setIcon(fps, icons);
+		return 0;
+	}
+	default:
+		return sq_throwerror(v, "invalid argument type");
+	}
 }
 
+// Specifies whether the object is affected by lighting elements.
+// Note: this is currently used for actor objects, but can also be used for room objects.
+// Lighting background flat art would be hard and probably look odd.
 static SQInteger objectLit(HSQUIRRELVM v) {
-	// TODO: objectLit
-	warning("objectLit not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object or actor");
+	bool lit = false;
+	if (SQ_FAILED(sqget(v, 3, lit)))
+		return sq_throwerror(v, "failed to get lit");
+	obj->_lit = lit;
 	return 0;
 }
 
@@ -412,27 +454,75 @@ static SQInteger objectMoveTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Instantly offsets the object (image, use position, hotspot) with respect to the origin of the object.
+//
+// .. code-block:: Squirrel
+// objectOffset(coroner, 0, 0)
+// objectOffset(SewerManhole.sewerManholeDime, 0, 96)
 static SQInteger objectOffset(HSQUIRRELVM v) {
-	// TODO: objectOffset
-	warning("objectOffset not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		int x = 0;
+		int y = 0;
+		if (SQ_FAILED(sqget(v, 3, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 4, y)))
+			return sq_throwerror(v, "failed to get y");
+		if (obj->_moveTo)
+			obj->_moveTo->disable();
+		obj->_node->setOffset(Math::Vector2d(x, y));
+	}
 	return 0;
 }
 
 static SQInteger objectOffsetTo(HSQUIRRELVM v) {
-	// TODO: objectOffsetTo
-	warning("objectOffsetTo not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		int x = 0;
+		int y = 0;
+		if (SQ_FAILED(sqget(v, 3, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 4, y)))
+			return sq_throwerror(v, "failed to get y");
+		float duration = 0.5f;
+		if (sq_gettop(v) >= 5)
+			if (SQ_FAILED(sqget(v, 5, duration)))
+				return sq_throwerror(v, "failed to get duration");
+		SQInteger interpolation = 0;
+		if ((sq_gettop(v) >= 6) && (SQ_FAILED(sq_getinteger(v, 6, &interpolation))))
+			interpolation = 0;
+		Math::Vector2d destPos(x, y);
+		obj->_moveTo = new OffsetTo(duration, obj, destPos, intToInterpolationMethod(interpolation));
+	}
 	return 0;
 }
 
+// Returns the actor who owns the specified object/inventory item.
+// If there is no owner, returns false.
+//
+// .. code-block:: Squirrel
+// objectOwner(dime) == currentActor
+// !objectOwner(countyMap1)
 static SQInteger objectOwner(HSQUIRRELVM v) {
-	// TODO: objectOwner
-	warning("objectOwner not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	if (!obj->_owner)
+		sq_pushnull(v);
+	else
+		sq_pushobject(v, obj->_owner->_table);
+	return 1;
 }
 
+// Changes the object's layer.
 static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
-	// TODO: objectParallaxLayer
-	warning("objectParallaxLayer not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	int layer = 0;
+	if (SQ_FAILED(sqget(v, 3, layer)))
+		return sq_throwerror(v, "failed to get parallax layer");
+	g_engine->_room->objectParallaxLayer(obj, layer);
 	return 0;
 }
 
@@ -442,33 +532,71 @@ static SQInteger objectParent(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns the x-coordinate of the given object or actor.
 static SQInteger objectPosX(HSQUIRRELVM v) {
-	// TODO: objectPosX
-	warning("objectPosX not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	float x = obj->getUsePos().getX() + obj->_hotspot.left + obj->_hotspot.width() / 2.0f;
+	sqpush(v, (int)x);
+	return 1;
 }
 
+// Returns the y-coordinate of the given object or actor.
 static SQInteger objectPosY(HSQUIRRELVM v) {
-	// TODO: objectPosY
-	warning("objectPosY not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	float y = obj->getUsePos().getY() + obj->_hotspot.top + obj->_hotspot.height() / 2.0f;
+	sqpush(v, (int)y);
+	return 1;
 }
 
+// Sets the rendering offset of the actor to x and y.
+//
+// A rendering offset of 0,0 would cause them to be rendered from the middle of their image.
+// Actor's are typically adjusted so they are rendered from the middle of the bottom of their feet.
+// To maintain sanity, it is best if all actors have the same image size and are all adjust the same, but this is not a requirement.
 static SQInteger objectRenderOffset(HSQUIRRELVM v) {
-	// TODO: objectRenderOffset
-	warning("objectRenderOffset not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	SQInteger x, y;
+	if (SQ_FAILED(sq_getinteger(v, 3, &x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sq_getinteger(v, 4, &y)))
+		return sq_throwerror(v, "failed to get y");
+	obj->_node->setRenderOffset(Math::Vector2d(x, y));
 	return 0;
 }
 
+// Returns the room of a given object or actor.
 static SQInteger objectRoom(HSQUIRRELVM v) {
-	// TODO: objectRoom
-	warning("objectRoom not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	if (!obj->_room)
+		sq_pushnull(v);
+	else
+		sqpush(v, obj->_room->_table);
+	return 1;
 }
 
+// Sets the rotation of object to the specified amount instantly.
+//
+// .. code-block:: Squirrel
+// objectRotate(pigeonVanBackWheel, 0)
 static SQInteger objectRotate(HSQUIRRELVM v) {
-	// TODO: objectRotate
-	warning("objectRotate not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		float rotation = 0.0f;
+		if (SQ_FAILED(sqget(v, 3, rotation)))
+			return sq_throwerror(v, "failed to get rotation");
+		// TODO: obj->rotateTo
+		// if (!obj->rotateTo)
+		//   obj->rotateTo.disable();
+		obj->_node->setRotation(rotation);
+	}
 	return 0;
 }
 
@@ -478,9 +606,15 @@ static SQInteger objectRotateTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Sets how scaled the object's image will appear on screen. 1 is no scaling.
 static SQInteger objectScale(HSQUIRRELVM v) {
-	// TODO: objectScale
-	warning("objectScale not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	float scale = 0.0f;
+	if (SQ_FAILED(sqget(v, 3, scale)))
+		return sq_throwerror(v, "failed to get scale");
+	obj->_node->setScale(Math::Vector2d(scale, scale));
 	return 0;
 }
 
@@ -490,10 +624,15 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Sets the object in the screen space.
+// It means that its position is relative to the screen, not to the room.
 static SQInteger objectScreenSpace(HSQUIRRELVM v) {
-	// TODO: objectScreenSpace
-	warning("objectScreenSpace not implemented");
+	warning("TODO: objectShader not implemented");
 	return 0;
+	// Object *obj = sqobj(v, 2);
+	// if (!obj)
+	// 	return sq_throwerror(v, "failed to get object");
+	// g_engine->_screen->addChild(obj->_node);
 }
 
 static SQInteger objectShader(HSQUIRRELVM v) {
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 2381053d85c..43ba318c7f9 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -86,8 +86,9 @@ void ResManager::loadFont(const Common::String &name) {
 		_fontC64TermSheet.load("FontC64TermSheet");
 		_fonts[name] = &_fontC64TermSheet;
 	} else {
-		// TODO:
-		error("Loading font %s not implemented", name.c_str());
+		BmFont* font = new BmFont();
+		font->load(name);
+		_fonts[name] = font;
 	}
 }
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 2885feb44c3..bf3f6375bfc 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -163,33 +163,41 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 
 	// assign an id
 	setId(obj->_table, newObjId());
-	debug("Create object with new table: %s #%d}", obj->_name.c_str(), obj->getId());
+	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
 	obj->_touchable = false;
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
-	// TODO:
-	//   let node = newTextNode(text)
-	//   var v = 0.5f
-	//   case vAlign:
-	//   of tvTop:
-	//     v = 0f
-	//   of tvCenter:
-	//     v = 0.5f
-	//   of tvBottom:
-	//     v = 1f
-	//   case hAlign:
-	//   of thLeft:
-	//     node.setAnchorNorm(vec2(0f, v))
-	//   of thCenter:
-	//     node.setAnchorNorm(vec2(0.5f, v))
-	//   of thRight:
-	//     node.setAnchorNorm(vec2(1f, v))
-	//   obj.node = node
-	//   self.layer(0).objects.add(obj);
-	//   self.layer(0).node.addChild obj.node;
-	//   obj.layer = self.layer(0);
+	TextNode *node = new TextNode();
+	node->setText(txt);
+	float y = 0.5f;
+	switch (vAlign) {
+	case tvTop:
+		y = 0.f;
+		break;
+	case tvCenter:
+		y = 0.5f;
+		break;
+	case tvBottom:
+		y = 1.f;
+		break;
+	}
+	switch (hAlign) {
+	case thLeft:
+		node->setAnchorNorm(Math::Vector2d(0.f, y));
+		break;
+	case thCenter:
+		node->setAnchorNorm(Math::Vector2d(0.5f, y));
+		break;
+	case thRight:
+		node->setAnchorNorm(Math::Vector2d(1.f, y));
+		break;
+	}
+	obj->_node = node;
+	layer(0)->_objects.push_back(obj);
+	layer(0)->_node->addChild(obj->_node);
+	obj->_layer = layer(0);
 
 	g_engine->_objects.push_back(obj);
 
@@ -359,6 +367,25 @@ float Room::getScaling(float yPos) {
 	return _scaling.getScaling(yPos);
 }
 
+void Room::objectParallaxLayer(Object *obj, int zsort) {
+	Layer *l = layer(zsort);
+	if (obj->_layer != l) {
+		// removes object from old layer
+		int id = obj->getId();
+		for (int i = 0; i < obj->_layer->_objects.size(); i++) {
+			if (obj->_layer->_objects[i]->getId() == id) {
+				obj->_layer->_objects.remove_at(i);
+				break;
+			}
+		}
+		// adds object to the new one
+		l->_objects.push_back(obj);
+		// update scenegraph
+		l->_node->addChild(obj->_node);
+		obj->_layer = l;
+	}
+}
+
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
 	_names.push_back(name);
 	_parallax = parallax;
@@ -391,7 +418,7 @@ float Scaling::getScaling(float yPos) {
 			return scale;
 		}
 	}
-	return values[values.size()-1].scale;
+	return values[values.size() - 1].scale;
 }
 
 } // namespace Twp
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 0a71685b473..5ddff58ebb2 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -110,6 +110,7 @@ public:
 
 	Light *createLight(Color color, Math::Vector2d pos);
 	float getScaling(float yPos);
+	void objectParallaxLayer(Object* obj, int zsort);
 
 public:
 	Common::String _name;              // Name of the room
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 68ae51b8b9f..198d6d3a815 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -152,6 +152,13 @@ void Node::setAnchor(Math::Vector2d anchor) {
 	}
 }
 
+void Node::setAnchorNorm(Math::Vector2d anchorNorm) {
+	if (_anchorNorm != anchorNorm) {
+		_anchorNorm = anchorNorm;
+		_anchor = _size * _anchorNorm;
+	}
+}
+
 void Node::setSize(Math::Vector2d size) {
 	if (_size != size) {
 		_size = size;
@@ -264,7 +271,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_anim = anim;
 	_disabled = false;
 	setName(anim->name);
-	_sheet = (anim->sheet.size() == 0 && _obj->_room) ? _obj->_room->_sheet : anim->sheet;
+	_sheet = anim->sheet;
 	_frames = anim->frames;
 	_frameIndex = instant && _frames.size() > 0 ? _frames.size() - 1 : 0;
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
@@ -334,6 +341,12 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 	if (_frameIndex < _frames.size()) {
 		const Common::String &frame = _frames[_frameIndex];
 		bool flipX = _obj->getFacing() == FACE_LEFT;
+		if (_sheet.size() == 0) {
+			_sheet = _obj->_sheet;
+			if (_sheet.size() == 0 && _obj->_room) {
+				_sheet = _obj->_room->_sheet;
+			}
+		}
 		SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 		const SpriteSheetFrame &sf = sheet->frameTable[frame];
 		Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
@@ -356,6 +369,33 @@ Math::Vector2d ActorNode::getScale() const {
 	return Math::Vector2d(y, y);
 }
 
+TextNode::TextNode() : Node("text") {
+}
+
+TextNode::~TextNode() {}
+
+void TextNode::setText(Text text) {
+	_text = text;
+	setSize(text.getBounds());
+}
+
+void TextNode::updateBounds() {
+	setSize(_text.getBounds());
+}
+
+Rectf TextNode::getRect() const {
+	Math::Vector2d size = _size * _scale;
+	return Rectf::fromPosAndSize(getAbsPos() + Math::Vector2d(0, -size.getY()) + Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm, size);
+}
+
+void TextNode::onColorUpdated(Color color) {
+	_text.setColor(color);
+}
+
+void TextNode::drawCore(Math::Matrix4 trsf) {
+	_text.draw(g_engine->getGfx(), trsf);
+}
+
 Scene::Scene() : Node("Scene") {}
 Scene::~Scene() {}
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 4b43e108557..29a5c10037f 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -65,7 +65,16 @@ public:
 	// Gets the local position for this node (relative to its parent)
 	void setPos(const Math::Vector2d& pos) { _pos = pos; }
 	Math::Vector2d getPos() const { return _pos; }
+
+	void setOffset(const Math::Vector2d& offset) { _offset = offset; }
 	Math::Vector2d getOffset() const { return _offset; }
+
+	void setRenderOffset(const Math::Vector2d& offset) { _renderOffset = offset; }
+	Math::Vector2d getRenderOffset() const { return _renderOffset; }
+
+	void setScale(const Math::Vector2d& scale) { _scale = scale; }
+	virtual Math::Vector2d getScale() const { return _scale; }
+
 	// Gets the absolute position for this node.
 	Math::Vector2d getAbsPos() const;
 
@@ -77,13 +86,16 @@ public:
 	Color getComputedColor() const { return _computedColor; }
 
 	void setAlpha(float alpha);
-	Color getAlpha() const { return _color.rgba.a; }
+	float getAlpha() const { return _color.rgba.a; }
 
 	void setZSort(int zsort) { _zOrder = zsort; }
 	virtual int getZSort() const { return _zOrder; }
-	virtual Math::Vector2d getScale() const { return _scale; }
+
+	void setRotation(float rotation) { _rotation = rotation; }
+	virtual float getRotation() const { return _rotation; }
 
 	void setAnchor(Math::Vector2d anchor);
+	void setAnchorNorm(Math::Vector2d anchorNorm);
 	void setSize(Math::Vector2d size);
 	virtual Rectf getRect() const;
 
@@ -110,7 +122,8 @@ protected:
 	Math::Vector2d _offset, _shakeOffset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
 	Color _color, _computedColor;
 	bool _visible = false;
-	float _rotation = 0.f, _rotationOffset = 0.f;
+	float _rotation = 0.f;
+	float _rotationOffset = 0.f;
 };
 
 class OverlayNode final: public Node {
@@ -157,15 +170,16 @@ private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
 
 private:
-	Common::String _sheet;
-    const ObjectAnimation* _anim;
-    bool _disabled;
+    const ObjectAnimation* _anim = nullptr;
+    bool _disabled = false;
+    Common::String _sheet;
     Common::Array<Common::String> _frames;
-    int _frameIndex;
-    float _elapsed;
-    float _frameDuration;
-    bool _loop, _instant;
-    Object* _obj;
+    int _frameIndex = 0;
+    float _elapsed = 0.f;
+    float _frameDuration = 0.f;
+    bool _loop = false;
+	bool _instant;
+    Object* _obj = nullptr;
 };
 
 class ActorNode final: public Node {
@@ -179,7 +193,24 @@ private:
 	Object* _object = nullptr;
 };
 
-class Scene: public Node {
+class TextNode final: public Node {
+public:
+	TextNode();
+	virtual ~TextNode() final;
+
+	void setText(Text text);
+	void updateBounds();
+	virtual Rectf getRect() const override final;
+
+private:
+	virtual void onColorUpdated(Color color) override final;
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Text _text;
+};
+
+class Scene final: public Node {
 public:
 	Scene();
 	virtual ~Scene() final;
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 60a6a7ed552..e83b5f839da 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -342,9 +342,9 @@ void sqcall(const Common::String &name, int numArgs, HSQOBJECT *args) {
 	sqcall(v, sqrootTbl(v), name, numArgs, args);
 }
 
-void sqexec(HSQUIRRELVM v, const char *code) {
+void sqexec(HSQUIRRELVM v, const char *code, const char* filename) {
 	SQInteger top = sq_gettop(v);
-	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), "twp", SQTrue))) {
+	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), filename ? filename : "twp", SQTrue))) {
 		sqstd_printcallstack(v);
 		return;
 	}
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 2c9e7edc656..ac562ce31f5 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -120,7 +120,7 @@ HSQOBJECT sqrootTbl(HSQUIRRELVM v);
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String& name);
 void sqcall(const Common::String &name, int numArgs = 0, HSQOBJECT *args = nullptr);
 void sqcall(HSQOBJECT o, const Common::String& name, int numArgs = 0, HSQOBJECT* args = nullptr);
-void sqexec(HSQUIRRELVM v, const char *code);
+void sqexec(HSQUIRRELVM v, const char *code, const char* filename = nullptr);
 
 class Room;
 class Object;
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 120d2f0ca89..e53773e4f90 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -150,8 +150,20 @@ static SQInteger addFolder(HSQUIRRELVM v) {
 //   breakhere(5)
 //}
 static SQInteger breakhere(HSQUIRRELVM v) {
-	warning("TODO: breakhere: not implemented");
-	return 0;
+	SQObjectType t = sq_gettype(v, 2);
+	if (t == OT_INTEGER) {
+		int numFrames;
+		if (SQ_FAILED(sqget(v, 2, numFrames)))
+			return sq_throwerror(v, "failed to get numFrames");
+		return breakfunc(v, [&](Thread &t) { t._numFrames = numFrames; });
+	}
+	if (t == OT_FLOAT) {
+		float time;
+		if (SQ_FAILED(sqget(v, 2, time)))
+			return sq_throwerror(v, "failed to get time");
+		return breakfunc(v, [&](Thread &t) { t._waitTime = time; });
+	}
+	return sq_throwerror(v, Common::String::format("failed to get numFrames (wrong type = {%d})", t).c_str());
 }
 
 // When called in a function started with startthread, execution is suspended for time seconds.
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index bc269ce9b43..3b1f6647a62 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -81,6 +81,12 @@ void TwpEngine::update(float elapsedMs) {
 			// TODO: delete it
 		}
 	}
+
+	// update objects
+	for (int i = 0; i < _objects.size(); i++) {
+		Object *obj = _objects[i];
+		obj->update(elapsedMs);
+	}
 }
 
 void TwpEngine::draw() {
@@ -137,7 +143,11 @@ Common::Error TwpEngine::run() {
 	// defineRoom(MainStreet)
 	// cameraInRoom(MainStreet)
 	// cameraAt(0,128)
-	// cameraPanTo(2820, 128, 5000, 2)
+	// //cameraPanTo(2820, 128, 5000, 2)
+	// local cr = createTextObject("SentenceFont", "Copyright 2014-2017 Terrible Toybox, Inc. All Rights Reserved. Thimbleweed Parkxe2x84xa2 is a trademark of Terrible Toybox, Inc.", ALIGN_CENTER)
+	// objectScale(cr, 0.25/2)
+	// objectAt(cr, 160, 5)
+	// objectAlpha(cr, 0.25)
 	// )";
 
 	const SQChar *code = "cameraInRoom(StartScreen)";
@@ -525,8 +535,7 @@ void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
 	GGBnutReader nut;
 	nut.open(&reader);
 	Common::String code = nut.readString();
-	//debug("%s", code.c_str());
-	sqexec(v, code.c_str());
+	sqexec(v, code.c_str(), entry.c_str());
 }
 
 void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
@@ -536,7 +545,7 @@ void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 		reader.open(_pack, entry);
 		Common::String code = reader.readString();
 		//debug("%s", code.c_str());
-		sqexec(v, code.c_str());
+		sqexec(v, code.c_str(), entry.c_str());
 	} else {
 		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
 		debug("read existing '%s'", newEntry.c_str());


Commit: 3a6ff6331478917ed708a13ede6fe1bcb7006eab
    https://github.com/scummvm/scummvm/commit/3a6ff6331478917ed708a13ede6fe1bcb7006eab
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add motor implementations

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 5104b6aad3a..03e859b84dd 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -130,9 +130,6 @@ void Shader::setUniform(const char *name, Math::Matrix4 value) {
 	glUseProgram(prev);
 }
 
-Gfx::Gfx() : _vbo(0), _ebo(0) {
-}
-
 void Gfx::init() {
 	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
 	byte pixels[] = {0xFF, 0xFF, 0xFF, 0xFF};
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 8be0e36c303..fecae3454cf 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -116,8 +116,6 @@ typedef Common::HashMap<int, TextureSlot> Textures;
 
 class Gfx {
 public:
-	Gfx();
-
 	void init();
 
 	void camera(Math::Vector2d size);
@@ -140,15 +138,15 @@ private:
 	void noTexture();
 
 private:
-	uint32 _vbo, _ebo;
+	uint32 _vbo = 0, _ebo = 0;
 	Shader _defaultShader;
-	Shader* _shader;
+	Shader* _shader = nullptr;
 	Math::Matrix4 _mvp;
 	Math::Vector2d _cameraPos;
 	Math::Vector2d _cameraSize;
 	Textures _textures;
-	Texture* _texture;
-	int32 _posLoc, _colLoc, _texCoordsLoc, _texLoc, _trsfLoc;
+	Texture* _texture = nullptr;
+	int32 _posLoc = 0, _colLoc = 0, _texCoordsLoc = 0, _texLoc = 0, _trsfLoc = 0;
 };
 }
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 599620567f2..996353a52d0 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -30,14 +30,86 @@ OffsetTo::~OffsetTo() {}
 
 OffsetTo::OffsetTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im)
 	: _obj(obj),
-	  _tween(obj->_node->getOffset(), pos, duration, im){
+	  _tween(obj->_node->getOffset(), pos, duration, im) {
 }
 
 void OffsetTo::update(float elapsed) {
-  _tween.update(elapsed);
-  _obj->_node->setOffset(_tween.current());
-  if (!_tween.running())
-    disable();
+	_tween.update(elapsed);
+	_obj->_node->setOffset(_tween.current());
+	if (!_tween.running())
+		disable();
+}
+
+MoveTo::~MoveTo() {}
+
+MoveTo::MoveTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im)
+	: _obj(obj),
+	  _tween(obj->_node->getPos(), pos, duration, im) {
+}
+
+void MoveTo::update(float elapsed) {
+	_tween.update(elapsed);
+	_obj->_node->setPos(_tween.current());
+	if (!_tween.running())
+		disable();
+}
+
+AlphaTo::~AlphaTo() {}
+
+AlphaTo::AlphaTo(float duration, Object *obj, float to, InterpolationMethod im)
+	: _obj(obj),
+	  _tween(obj->_node->getAlpha(), to, duration, im) {
+}
+
+void AlphaTo::update(float elapsed) {
+	_tween.update(elapsed);
+	float alpha = _tween.current();
+	_obj->_node->setAlpha(alpha);
+	if (!_tween.running())
+		disable();
+}
+
+RotateTo::~RotateTo() {}
+
+RotateTo::RotateTo(float duration, Node *node, float to, InterpolationMethod im)
+	: _node(node),
+	  _tween(node->getRotation(), to, duration, im) {
+}
+
+void RotateTo::update(float elapsed) {
+	_tween.update(elapsed);
+	_node->setRotation(_tween.current());
+	if (!_tween.running())
+		disable();
+}
+
+ScaleTo::~ScaleTo() {}
+
+ScaleTo::ScaleTo(float duration, Node *node, float to, InterpolationMethod im)
+	: _node(node),
+	  _tween(node->getScale().getX(), to, duration, im) {
+}
+
+void ScaleTo::update(float elapsed) {
+	_tween.update(elapsed);
+	float x = _tween.current();
+	_node->setScale(Math::Vector2d(x, x));
+	if (!_tween.running())
+		disable();
+}
+
+Shake::~Shake() {}
+
+Shake::Shake(Node *node, float amount)
+	: _node(node),
+	  _amount(amount) {
+}
+
+void Shake::update(float elapsed) {
+	_shakeTime += 40.f * elapsed;
+	_elapsed += elapsed;
+	// TODO: check if it's necessary to create a _shakeOffset in a node
+	_node->setOffset(Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime)));
 }
 
 } // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index d7b198bf606..da7abd1d486 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -33,7 +33,7 @@ template<typename T>
 struct Tween {
 public:
 	Tween(T f, T t, float duration, InterpolationMethod im)
-		: frm(f), to(t), delta(to - frm), duration(duration), value(frm), easing_f(easing(im.kind)), swing(im.swing), loop(im.loop) {
+		: frm(f), to(t), delta(t - f), duration(duration), value(f), easing_f(easing(im.kind)), swing(im.swing), loop(im.loop) {
 	}
 
 	bool running() {
@@ -104,6 +104,74 @@ private:
 	Tween<Math::Vector2d> _tween;
 };
 
+class MoveTo : public Motor {
+public:
+	virtual ~MoveTo();
+	MoveTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Object *_obj = nullptr;
+	Tween<Math::Vector2d> _tween;
+};
+
+class AlphaTo : public Motor {
+public:
+	virtual ~AlphaTo();
+	AlphaTo(float duration, Object *obj, float to, InterpolationMethod im);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Object *_obj = nullptr;
+	Tween<float> _tween;
+};
+
+class Node;
+class RotateTo : public Motor {
+public:
+	virtual ~RotateTo();
+	RotateTo(float duration, Node *obj, float to, InterpolationMethod im);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Node *_node = nullptr;
+	Tween<float> _tween;
+};
+
+class ScaleTo : public Motor {
+public:
+	virtual ~ScaleTo();
+	ScaleTo(float duration, Node *node, float to, InterpolationMethod im);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Node *_node = nullptr;
+	Tween<float> _tween;
+};
+
+class Shake : public Motor {
+public:
+	virtual ~Shake();
+	Shake(Node *node, float amount);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Node *_node = nullptr;
+	float _amount = 0.f;
+	float _shakeTime = 0.f;
+	float _elapsed = 0.f;
+};
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 6f9a0fec34e..a153c2aa793 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -295,8 +295,20 @@ void Object::delObject() {
 	_node->getParent()->removeChild(_node);
 }
 
+static void disableMotor(Motor* motor) {
+	if(motor) motor->disable();
+}
+
 void Object::stopObjectMotors() {
-	debug("TODO: stopObjectMotors");
+	disableMotor(_alphaTo);
+	disableMotor(_rotateTo);
+	disableMotor(_moveTo);
+	disableMotor(_walkTo);
+	disableMotor(_talking);
+	disableMotor(_blink);
+	disableMotor(_turnTo);
+	disableMotor(_shakeTo);
+	disableMotor(_jiggleTo);
 }
 
 void Object::setFacing(Facing facing) {
@@ -407,20 +419,45 @@ void Object::stand() {
 	play(getAnimName(STAND_ANIMNAME));
 }
 
+#define SET_MOTOR(motorTo)     \
+	if (_##motorTo) {          \
+		_##motorTo->disable(); \
+		delete _##motorTo;     \
+	}                          \
+	_##motorTo = motorTo;
+
+void Object::setAlphaTo(Motor *alphaTo) { SET_MOTOR(alphaTo); }
+void Object::setRotateTo(Motor *rotateTo) { SET_MOTOR(rotateTo); }
+void Object::setMoveTo(Motor *moveTo) { SET_MOTOR(moveTo); }
+void Object::setWalkTo(Motor *walkTo) { SET_MOTOR(walkTo); }
+void Object::setTalking(Motor *talking) { SET_MOTOR(talking); }
+void Object::setBlink(Motor *blink) { SET_MOTOR(blink); }
+void Object::setTurnTo(Motor *turnTo) { SET_MOTOR(turnTo); }
+void Object::setShakeTo(Motor *shakeTo) { SET_MOTOR(shakeTo); }
+void Object::setJiggleTo(Motor *jiggleTo) { SET_MOTOR(jiggleTo); }
+
 void Object::update(float elapsedSec) {
 	// TODO: update
 	//   if not self.dependentObj.isNil:
 	//     self.node.visible = self.dependentObj.getState() == self.dependentState
-	//   self.alphaTo.updateMotor(elapsedSec)
-	//   self.rotateTo.updateMotor(elapsedSec)
+	if (_alphaTo)
+		_alphaTo->update(elapsedSec);
+	if (_rotateTo)
+		_rotateTo->update(elapsedSec);
 	if (_moveTo)
 		_moveTo->update(elapsedSec);
-	//   self.walkTo.updateMotor(elapsedSec)
-	//   self.talking.updateMotor(elapsedSec)
-	//   self.blink.updateMotor(elapsedSec)
-	//   self.turnTo.updateMotor(elapsedSec)
-	//   self.shakeTo.updateMotor(elapsedSec)
-	//   self.jiggleTo.updateMotor(elapsedSec)
+	if (_walkTo)
+		_walkTo->update(elapsedSec);
+	if (_talking)
+		_talking->update(elapsedSec);
+	if (_blink)
+		_blink->update(elapsedSec);
+	if (_turnTo)
+		_turnTo->update(elapsedSec);
+	if (_shakeTo)
+		_shakeTo->update(elapsedSec);
+	if (_jiggleTo)
+		_jiggleTo->update(elapsedSec);
 
 	if (_nodeAnim)
 		_nodeAnim->update(elapsedSec);
@@ -438,4 +475,19 @@ void Object::update(float elapsedSec) {
 	//         self.popElapsed -= 0.5f
 }
 
+void Object::pickupObject(Object *obj) {
+	obj->_owner = this;
+	_inventory.push_back(obj);
+
+	{
+		HSQOBJECT args[]{obj->_table, _table};
+		sqcall("onPickup", 2, args);
+	}
+
+	if (sqrawexists(obj->_table, "onPickUp")) {
+		HSQOBJECT args[]{_table};
+		sqcall(obj->_table, "onPickUp", 1, args);
+	}
+}
+
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 755757f5494..abe9e7027ab 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -152,6 +152,18 @@ public:
 
 	void update(float elapsedSec);
 
+	void setAlphaTo(Motor* alphaTo);
+	void setRotateTo(Motor* rotateTo);
+	void setMoveTo(Motor* moveTo);
+	void setWalkTo(Motor* walkTo);
+	void setTalking(Motor* talking);
+	void setBlink(Motor* blink);
+	void setTurnTo(Motor* turnTo);
+	void setShakeTo(Motor* shakeTo);
+	void setJiggleTo(Motor* jiggleTo);
+
+	void pickupObject(Object *obj);
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
@@ -207,7 +219,17 @@ public:
     float _popElapsed = 0.f;
     int _popCount = 0;
 	Sentence _exec;
+
+private:
+	Motor* _alphaTo = nullptr;
+	Motor* _rotateTo = nullptr;
 	Motor* _moveTo = nullptr;
+	Motor* _walkTo = nullptr;
+	Motor* _talking = nullptr;
+	Motor* _blink = nullptr;
+	Motor* _turnTo = nullptr;
+	Motor* _shakeTo = nullptr;
+	Motor* _jiggleTo = nullptr;
 };
 
 } // namespace Twp
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index a625491738b..915865fdd9f 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -244,16 +244,33 @@ static SQInteger objectAlpha(HSQUIRRELVM v) {
 		float alpha = 0.0f;
 		if (SQ_FAILED(sq_getfloat(v, 3, &alpha)))
 			return sq_throwerror(v, "failed to get alpha");
-		// TODO: if (obj->_alphaTo)
-		//  obj->_alphaTo->disable();
+		obj->setAlphaTo(nullptr);
 		obj->_node->setAlpha(alpha);
 	}
 	return 0;
 }
 
+// Changes an object's alpha from its current state to the specified alpha over the time period specified by time.
+//
+// If an interpolationMethod is used, the change will follow the rules of the easing method, e.g. LINEAR, EASE_INOUT.
+// See also stopObjectMotors.
 static SQInteger objectAlphaTo(HSQUIRRELVM v) {
-	// TODO: objectAlphaTo
-	warning("objectAlphaTo not implemented");
+	if (sq_gettype(v, 2) != OT_NULL) {
+		Object *obj = sqobj(v, 2);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object");
+		float alpha = 0.0f;
+		if (SQ_FAILED(sqget(v, 3, alpha)))
+			return sq_throwerror(v, "failed to get alpha");
+		alpha = clamp(alpha, 0.0f, 1.0f);
+		float t = 0.0f;
+		if (SQ_FAILED(sqget(v, 4, t)))
+			return sq_throwerror(v, "failed to get time");
+		int interpolation = 0;
+		if ((sq_gettop(v) >= 5) && (SQ_FAILED(sqget(v, 5, interpolation))))
+			interpolation = 0;
+		obj->setAlphaTo(new AlphaTo(t, obj, alpha, intToInterpolationMethod(interpolation)));
+	}
 	return 0;
 }
 
@@ -448,9 +465,36 @@ static SQInteger objectLit(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Moves the object to the specified location over the time period specified.
+//
+// If an interpolation method is used for the transition, it will use that.
+// Unlike `objectOffsetTo`, `objectMoveTo` moves the item to a x, y on the screen, not relative to the object's starting position.
+// If you want to move the object back again, you need to store where the object started.
+//
+// .. code-block:: Squirrel
+// objectMoveTo(this, 10, 20, 2.0)
+//
+// See also:
+// - `stopObjectMotors method <#stopObjectMotors.e>`_
+// - `objectOffsetTo method <#objectOffsetTo.e>`_
 static SQInteger objectMoveTo(HSQUIRRELVM v) {
-	// TODO: objectMoveTo
-	warning("objectMoveTo not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj) {
+		int x = 0;
+		int y = 0;
+		if (SQ_FAILED(sqget(v, 3, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 4, y)))
+			return sq_throwerror(v, "failed to get y");
+		float duration = 0.0f;
+		if (SQ_FAILED(sqget(v, 5, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		int interpolation = 0;
+		if ((sq_gettop(v) >= 6) && SQ_FAILED(sqget(v, 6, interpolation)))
+			interpolation = 0;
+		Math::Vector2d destPos = Math::Vector2d(x, y);
+		obj->setMoveTo(new MoveTo(duration, obj, destPos, intToInterpolationMethod(interpolation)));
+	}
 	return 0;
 }
 
@@ -468,8 +512,7 @@ static SQInteger objectOffset(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
-		if (obj->_moveTo)
-			obj->_moveTo->disable();
+		obj->setMoveTo(nullptr);
 		obj->_node->setOffset(Math::Vector2d(x, y));
 	}
 	return 0;
@@ -492,7 +535,7 @@ static SQInteger objectOffsetTo(HSQUIRRELVM v) {
 		if ((sq_gettop(v) >= 6) && (SQ_FAILED(sq_getinteger(v, 6, &interpolation))))
 			interpolation = 0;
 		Math::Vector2d destPos(x, y);
-		obj->_moveTo = new OffsetTo(duration, obj, destPos, intToInterpolationMethod(interpolation));
+		obj->setMoveTo(new OffsetTo(duration, obj, destPos, intToInterpolationMethod(interpolation)));
 	}
 	return 0;
 }
@@ -527,8 +570,14 @@ static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
 }
 
 static SQInteger objectParent(HSQUIRRELVM v) {
-	// TODO: objectParent
-	warning("objectParent not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get child");
+	Object *parent = sqobj(v, 3);
+	if (!parent)
+		return sq_throwerror(v, "failed to get parent");
+	obj->_parent = parent->_key;
+	parent->_node->addChild(obj->_node);
 	return 0;
 }
 
@@ -592,17 +641,34 @@ static SQInteger objectRotate(HSQUIRRELVM v) {
 		float rotation = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, rotation)))
 			return sq_throwerror(v, "failed to get rotation");
-		// TODO: obj->rotateTo
-		// if (!obj->rotateTo)
-		//   obj->rotateTo.disable();
+		obj->setRotateTo(nullptr);
 		obj->_node->setRotation(rotation);
 	}
 	return 0;
 }
 
+// Rotates the object from its current rotation to the desired rotation over duration time period.
+// The interpolationMethod specifies how the animation is played.
+// if `LOOPING` is used, it will continue to rotate as long as the rotation parameter is 360 or -360.
+//
+// .. code-block:: Squirrel
+// objectRotateTo(bridgeGrateTree, 45, 3.7, SLOW_EASE_IN)
+// objectRotateTo(AStreet.aStreetPhoneBook, 6, 2.0, SWING)
+// objectRotateTo(firefly, direction, 12, LOOPING)
 static SQInteger objectRotateTo(HSQUIRRELVM v) {
-	// TODO: objectRotateTo
-	warning("objectRotateTo not implemented");
+	Object *obj = sqobj(v, 2);
+	if (obj) {
+		float rotation = 0.0f;
+		if (SQ_FAILED(sqget(v, 3, rotation)))
+			return sq_throwerror(v, "failed to get rotation");
+		float duration = 0.0f;
+		if (SQ_FAILED(sqget(v, 4, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		int interpolation = 0;
+		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
+			interpolation = 0;
+		obj->setRotateTo(new RotateTo(duration, obj->_node, rotation, intToInterpolationMethod(interpolation)));
+	}
 	return 0;
 }
 
@@ -619,8 +685,19 @@ static SQInteger objectScale(HSQUIRRELVM v) {
 }
 
 static SQInteger objectScaleTo(HSQUIRRELVM v) {
-	// TODO: objectScaleTo
-	warning("objectScaleTo not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj) {
+		float scale = 0.0f;
+		if (SQ_FAILED(sqget(v, 3, scale)))
+			return sq_throwerror(v, "failed to get scale");
+		float duration = 0.0f;
+		if (SQ_FAILED(sqget(v, 4, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		int interpolation = 0;
+		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
+			interpolation = 0;
+		obj->setRotateTo(new ScaleTo(duration, obj->_node, scale, intToInterpolationMethod(interpolation)));
+	}
 	return 0;
 }
 
@@ -766,15 +843,64 @@ static SQInteger objectValidUsePos(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Returns true if this object has a verb function for the specified verb.
+// Mostly used for testing when trying to check interactions.
+// Verb options are: VERB_WALKTO, VERB_LOOKAT, VERB_PICKUP, VERB_OPEN, VERB_CLOSE, VERB_PUSH, VERB_PULL, VERB_TALKTO.
+// Cannot use DEFAULT_VERB because that is not a real verb to the system.
+//
+// .. code-block:: Squirrel
+// if (objectValidVerb(obj, VERB_PICKUP)) {
+//    logAction("PickUp", obj)
+//    pushSentence(VERB_PICKUP, obj)
+//    tries = 0
+//}
 static SQInteger objectValidVerb(HSQUIRRELVM v) {
-	// TODO: objectValidVerb
-	warning("objectValidVerb not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object or actor");
+	int verb;
+	if (SQ_FAILED(sqget(v, 3, verb)))
+		return sq_throwerror(v, "failed to get verb");
+
+	int verbId = verb;
+	if (!g_engine->_actor) {
+		// TODO:
+		// for (vb in gEngine.hud.actorSlot(gEngine.actor).verbs) {
+		//   if (vb.id == verbId) {
+		//     if (sqrawexists(obj.table, vb.fun)) {
+		//       sqpush(v, true);
+		//       return 1;
+		// 	}
+		//   }
+		// }
+	}
+	sqpush(v, false);
+	return 1;
 }
 
 static SQInteger pickupObject(HSQUIRRELVM v) {
-	// TODO: pickupObject
-	warning("pickupObject not implemented");
+	// Picks up an object and adds it to the selected actor's inventory.
+	// The object that appears in the room is not the object you pick up, this is due to the code often needing to be very different when it's held in your inventory, plus inventory objects need icons.
+	//
+	// .. code-block:: Squirrel
+	// pickupObject(Dime)
+	Object *obj = sqobj(v, 2);
+	if (!obj) {
+		HSQOBJECT o;
+		sq_getstackobj(v, 2, &o);
+		Common::String name;
+		sqgetf(o, "name", name);
+		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, name.c_str()).c_str());
+	}
+	Object *actor = nullptr;
+	if (sq_gettop(v) >= 3) {
+		actor = sqactor(v, 3);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+	}
+	if (!actor)
+		actor = g_engine->_actor;
+	actor->pickupObject(obj);
 	return 0;
 }
 
@@ -810,8 +936,13 @@ static SQInteger playObjectState(HSQUIRRELVM v) {
 }
 
 static SQInteger popInventory(HSQUIRRELVM v) {
-	// TODO: popInventory
-	warning("popInventory not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	int count;
+	if (SQ_FAILED(sqget(v, 3, count)))
+		return sq_throwerror(v, "failed to get count");
+	obj->setPop(count);
 	return 0;
 }
 
@@ -821,15 +952,28 @@ static SQInteger removeInventory(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Globally sets a default object.
+// When the player executes the sentence open painting and the painting object has no verbOpen function defined,
+// it will call the default object's verbOpen as a fallback, allowing for common failure phrase like "I can't open that.".
+// The default object can be changed at anytime, so different selectable characters can have different default responses.
 static SQInteger setDefaultObject(HSQUIRRELVM v) {
-	// TODO: setDefaultObject
-	warning("setDefaultObject not implemented");
+	HSQUIRRELVM vm = g_engine->getVm();
+	if (g_engine->_defaultObj._type != OT_NULL)
+		sq_release(vm, &g_engine->_defaultObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &g_engine->_defaultObj)))
+		return sq_throwerror(v, "failed to get default object");
+	sq_addref(vm, &g_engine->_defaultObj);
 	return 0;
 }
 
 static SQInteger shakeObject(HSQUIRRELVM v) {
-	// TODO: shakeObject
-	warning("shakeObject not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	float amount;
+	if (SQ_FAILED(sqget(v, 3, amount)))
+		return sq_throwerror(v, "failed to get amount");
+	obj->setShakeTo(new Shake(obj->_node, amount));
 	return 0;
 }
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 198d6d3a815..fb2216785dc 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -34,7 +34,7 @@ namespace Twp {
 static float _getFps(float fps, float animFps) {
 	if (fps != 0.f)
 		return fps;
-	return animFps == 0.f ? DEFAULT_FPS : animFps;
+	return (animFps < 1e-3) ? DEFAULT_FPS : animFps;
 }
 
 Node::Node(const Common::String &name, bool visible, Math::Vector2d scale, Color color)
@@ -192,15 +192,20 @@ Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
 	return parentTrsf * getLocalTrsf();
 }
 
+static void scale(Math::Matrix4& m, const Math::Vector2d &v) {
+	m(0, 0) *= v.getX();
+	m(1, 1) *= v.getY();
+}
+
 Math::Matrix4 Node::getLocalTrsf() {
-	// TODO: scale
-	Math::Vector2d p = _pos + _offset + _shakeOffset;
+	Math::Vector2d p = _pos + _offset;
 	Math::Matrix4 m1;
 	m1.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 	Math::Matrix3 mRot;
 	mRot.buildAroundZ(Math::Angle(-_rotation + _rotationOffset));
 	Math::Matrix4 m2;
 	m2.setRotation(mRot);
+	scale(m2, _scale);
 	Math::Matrix4 m3;
 	m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
 	return m1 * m2 * m3;
@@ -354,7 +359,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 		float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
 		Math::Vector3d pos(int(-x), int(y), 0.f);
 		trsf.translate(pos);
-		g_engine->getGfx().drawSprite(sf.frame, *texture, getColor(), trsf, flipX);
+		g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
 	}
 }
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 29a5c10037f..6b44396f302 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -119,7 +119,7 @@ protected:
 	float _zOrder = 0.f;
 	Node *_parent = nullptr;
 	Common::Array<Node *> _children;
-	Math::Vector2d _offset, _shakeOffset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
+	Math::Vector2d _offset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
 	Color _color, _computedColor;
 	bool _visible = false;
 	float _rotation = 0.f;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 3b1f6647a62..2159e51eb79 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -77,16 +77,23 @@ void TwpEngine::update(float elapsedMs) {
 	// update threads
 	for (int i = 0; i < _threads.size(); i++) {
 		Thread *thread = _threads[i];
-		if (thread->update(elapsedMs)) {
+		if (thread->update(elapsedMs/1000.f)) {
 			// TODO: delete it
 		}
 	}
 
 	// update objects
-	for (int i = 0; i < _objects.size(); i++) {
-		Object *obj = _objects[i];
-		obj->update(elapsedMs);
-	}
+	//for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		//Room *room = g_engine->_rooms[i];
+		Room *room = g_engine->_room;
+		for (int j = 0; j < room->_layers.size(); j++) {
+			Layer *layer = room->_layers[j];
+			for (int k = 0; k < layer->_objects.size(); k++) {
+				Object *obj = layer->_objects[k];
+				obj->update(elapsedMs/1000.f);
+			}
+		}
+	//}
 }
 
 void TwpEngine::draw() {
@@ -135,6 +142,12 @@ Common::Error TwpEngine::run() {
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
+	// GGPackEntryReader reader;
+	// reader.open(_pack, "StartScreen.bnut");
+	// GGBnutReader nut;
+	// nut.open(&reader);
+	// Common::String code1 = nut.readString();
+
 	// const SQChar *code = R"(
 	// MainStreet <- {
 	// 	background = "MainStreet"


Commit: f7f2f7c2befb664ea0e0a7ffdee93e2ff06e549d
    https://github.com/scummvm/scummvm/commit/f7f2f7c2befb664ea0e0a7ffdee93e2ff06e549d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add cutscene

Changed paths:
  A engines/twp/task.h
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.h
    engines/twp/vm.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index bdfc0ff505e..ad5109ede79 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -114,21 +114,54 @@ static SQInteger assetExists(HSQUIRRELVM v) {
 // actorAt(reyes, Bridge.startRight)
 // cameraAt(Bridge.bridgeBody)
 static SQInteger cameraAt(HSQUIRRELVM v) {
-	// TODO: cameraAt
-	warning("cameraAt not implemented");
+	SQInteger numArgs = sq_gettop(v);
+	Math::Vector2d pos;
+	if (numArgs == 3) {
+		int x, y;
+		if (SQ_FAILED(sqget(v, 2, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 3, y)))
+			return sq_throwerror(v, "failed to get y");
+		pos = Math::Vector2d(x, y);
+	} else if (numArgs == 2) {
+		Object *obj = sqobj(v, 2);
+		if (!obj)
+			return sq_throwerror(v, "failed to get spot or actor");
+		g_engine->follow(nullptr);
+		g_engine->setRoom(obj->_room);
+		pos = obj->getUsePos();
+	} else {
+		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
+	}
+	g_engine->follow(nullptr);
+	g_engine->cameraAt(pos);
 	return 0;
 }
 
 // Sets how far the camera can pan.
 static SQInteger cameraBounds(HSQUIRRELVM v) {
-	// TODO: cameraBounds
-	warning("cameraBounds not implemented");
+	int xMin, xMax, yMin, yMax;
+	if (SQ_FAILED(sqget(v, 2, xMin)))
+		return sq_throwerror(v, "failed to get xMin");
+	if (SQ_FAILED(sqget(v, 3, xMax)))
+		return sq_throwerror(v, "failed to get xMax");
+	if (SQ_FAILED(sqget(v, 4, yMin)))
+		return sq_throwerror(v, "failed to get yMin");
+	if (SQ_FAILED(sqget(v, 5, yMax)))
+		return sq_throwerror(v, "failed to get yMax");
+	g_engine->_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(xMin, yMin), Math::Vector2d(xMax, yMax)));
 	return 0;
 }
 
 static SQInteger cameraFollow(HSQUIRRELVM v) {
-	// TODO: cameraFollow
-	warning("cameraFollow not implemented");
+	Object *actor = sqactor(v, 2);
+	g_engine->follow(actor);
+	Math::Vector2d pos = actor->_node->getPos();
+	Room *oldRoom = g_engine->_room;
+	if (actor->_room)
+		g_engine->setRoom(actor->_room);
+	if (oldRoom != actor->_room)
+		g_engine->cameraAt(pos);
 	return 0;
 }
 
@@ -227,11 +260,8 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 
 // Returns the current camera position x, y.
 static SQInteger cameraPos(HSQUIRRELVM v) {
-	// TODO: cameraPos
-	warning("cameraPos not implemented");
-	return 0;
-	// push(v, g_engine->cameraPos())
-	// return 1;
+	sqpush(v, g_engine->cameraPos());
+	return 1;
 }
 
 // Converts an integer to a char.
@@ -292,13 +322,14 @@ static SQInteger getUserPref(HSQUIRRELVM v) {
 		sqpush(v, g_engine->getPrefs().prefsAsJson(key));
 		return 1;
 	}
-	if (sq_gettop(v) == 3) {
+	int numArgs = sq_gettop(v);
+	if (numArgs == 3) {
 		HSQOBJECT obj;
 		sq_getstackobj(v, 3, &obj);
 		sqpush(v, obj);
 		return 1;
 	}
-	return sq_throwerror(v, "invalid argument in getUserPref");
+	return 0;
 }
 
 static SQInteger getPrivatePref(HSQUIRRELVM v) {
@@ -308,7 +339,7 @@ static SQInteger getPrivatePref(HSQUIRRELVM v) {
 	debug("TODO: getPrivatePref");
 	// else if (g_engine->getPrefs().hasPrivPref(key))
 	// 	return sqpush(v, g_engine->getPrefs().privPrefAsJson(key));
-	//else
+	// else
 	if (sq_gettop(v) == 3) {
 		HSQOBJECT obj;
 		sq_getstackobj(v, 3, &obj);
@@ -319,9 +350,8 @@ static SQInteger getPrivatePref(HSQUIRRELVM v) {
 }
 
 static SQInteger incutscene(HSQUIRRELVM v) {
-	// TODO: incutscene
-	warning("incutscene not implemented");
-	return 0;
+	sqpush(v, g_engine->_cutscene != nullptr);
+	return 1;
 }
 
 static SQInteger indialog(HSQUIRRELVM v) {
@@ -395,6 +425,10 @@ static SQInteger is_table(HSQUIRRELVM v) {
 	return is_oftype(v, [](SQObjectType type) { return type == OT_TABLE; });
 }
 
+static float randf() {
+	return g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+}
+
 // Returns a random number from from to to inclusively.
 // The number is a pseudo-random number and the game will produce the same sequence of numbers unless primed using randomSeed(seed).
 //
@@ -407,7 +441,7 @@ static SQInteger sqrandom(HSQUIRRELVM v) {
 		sq_getfloat(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		float scale = g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+		float scale = randf();
 		SQFloat value = min + scale * (max - min);
 		sq_pushfloat(v, value);
 	} else {
@@ -477,22 +511,83 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Selects an item randomly from the given array or listed options.
+//
+// .. code-block:: Squirrel
+// local line = randomfrom(lines)
+// breakwhiletalking(willie)
+// mumbleLine(willie, line)
+//
+// local snd = randomfrom(soundBeep1, soundBeep2, soundBeep3, soundBeep4, soundBeep5, soundBeep6)
+// playObjectSound(snd, Highway.pigeonVan)
 static SQInteger randomFrom(HSQUIRRELVM v) {
-	// TODO: randomFrom
-	warning("randomFrom not implemented");
-	return 0;
+	if (sq_gettype(v, 2) == OT_ARRAY) {
+		HSQOBJECT obj;
+		sq_resetobject(&obj);
+		SQInteger size = sq_getsize(v, 2);
+		int index = g_engine->getRandomSource().getRandomNumber(size);
+		int i = 0;
+		sq_push(v, 2);  // array
+		sq_pushnull(v); // null iterator
+		while (SQ_SUCCEEDED(sq_next(v, -2))) {
+			sq_getstackobj(v, -1, &obj);
+			sq_pop(v, 2); // pops key and val before the nex iteration
+			if (index == i) {
+				sq_pop(v, 2); // pops the null iterator and array
+				sq_pushobject(v, obj);
+				return 1;
+			}
+			i++;
+		}
+		sq_pop(v, 2); // pops the null iterator and array
+		sq_pushobject(v, obj);
+	} else {
+		SQInteger size = sq_gettop(v);
+		int index = g_engine->getRandomSource().getRandomNumber(size - 1);
+		sq_push(v, 2 + index);
+	}
+	return 1;
 }
 
+// Returns TRUE or FALSE based on the percent, which needs to be from 0.0 to 1.0.
+//
+// A percent of 0.0 will always return FALSE and 1.0 will always return TRUE.
+// `randomOdds(0.3333)` will return TRUE about one third of the time.
+//
+// .. code-block:: Squirrel
+// if (randomOdds(0.5) { ... }
 static SQInteger randomOdds(HSQUIRRELVM v) {
-	// TODO: randomOdds
-	warning("randomOdds not implemented");
-	return 0;
+	float value = 0.0f;
+	if (SQ_FAILED(sqget(v, 2, value)))
+		return sq_throwerror(v, "failed to get value");
+	float rnd = randf();
+	bool res = rnd <= value;
+	sq_pushbool(v, res);
+	return 1;
 }
 
+// Initializes a new Rand state using the given seed.
+// Providing a specific seed will produce the same results for that seed each time.
+// The resulting state is independent of the default RNG's state.
 static SQInteger randomseed(HSQUIRRELVM v) {
-	// TODO: randomseed
-	warning("randomseed not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	switch (nArgs) {
+	case 1: {
+		sqpush(v, (int)g_engine->getRandomSource().getSeed());
+		return 1;
+	}
+	case 2: {
+		int seed = 0;
+		if (sq_gettype(v, 2) == OT_NULL)
+			g_engine->getRandomSource().setSeed(g_engine->getRandomSource().generateNewSeed());
+		return 0;
+		if (SQ_FAILED(sqget(v, 2, seed)))
+			return sq_throwerror(v, "failed to get seed");
+		g_engine->getRandomSource().setSeed(seed);
+		return 0;
+	}
+	}
+	return sq_throwerror(v, "invalid number of parameters for randomseed");
 }
 
 static SQInteger refreshUI(HSQUIRRELVM v) {
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index a153c2aa793..696e0151aee 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -286,6 +286,26 @@ int Object::getFlags() {
 void Object::setRoom(Room *room) {
 	if (_room != room) {
 		stopObjectMotors();
+		Room *oldRoom = _room;
+		if (oldRoom) {
+			debug("Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
+			Layer *layer = oldRoom->layer(0);
+			if (layer) {
+				int index = find(layer->_objects, this);
+				if (index != -1)
+					layer->_objects.remove_at(index);
+				if (layer)
+					layer->_node->removeChild(_node);
+			}
+		}
+		if (room && room->layer(0) && room->layer(0)->_node) {
+			debug("Add %s in room %s", _key.c_str(), room->_name.c_str());
+			Layer *layer = room->layer(0);
+			if (layer) {
+				layer->_objects.push_back(this);
+				layer->_node->addChild(_node);
+			}
+		}
 		_room = room;
 	}
 }
@@ -295,8 +315,9 @@ void Object::delObject() {
 	_node->getParent()->removeChild(_node);
 }
 
-static void disableMotor(Motor* motor) {
-	if(motor) motor->disable();
+static void disableMotor(Motor *motor) {
+	if (motor)
+		motor->disable();
 }
 
 void Object::stopObjectMotors() {
@@ -360,14 +381,12 @@ void Object::setHeadIndex(int head) {
 }
 
 bool Object::isWalking() {
-	// TODO: return not self.walkTo.isNil and self.walkTo.enabled();
-	return false;
+	return _walkTo && _walkTo->isEnabled();
 }
 
 void Object::stopWalking() {
-	// TODO: stopWalking
-	//   if (_walkTo)
-	//     _walkTo->disable();
+	  if (_walkTo)
+	    _walkTo->disable();
 }
 
 void Object::setAnimationNames(const Common::String &head, const Common::String &stand, const Common::String &walk, const Common::String &reach) {
@@ -479,14 +498,10 @@ void Object::pickupObject(Object *obj) {
 	obj->_owner = this;
 	_inventory.push_back(obj);
 
-	{
-		HSQOBJECT args[]{obj->_table, _table};
-		sqcall("onPickup", 2, args);
-	}
+	sqcall("onPickup", obj->_table, _table);
 
 	if (sqrawexists(obj->_table, "onPickUp")) {
-		HSQOBJECT args[]{_table};
-		sqcall(obj->_table, "onPickUp", 1, args);
+		sqcall(obj->_table, "onPickUp", _table);
 	}
 }
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 915865fdd9f..7800da7ebbb 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -151,7 +151,10 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 // deleteObject(drip)
 static SQInteger deleteObject(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
-	obj->delObject();
+	if(obj) {
+		obj->delObject();
+		delete obj;
+	}
 	return 0;
 }
 
@@ -322,7 +325,6 @@ static SQInteger objectCenter(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectColor(warningSign, 0x808000)
 static SQInteger objectColor(HSQUIRRELVM v) {
-	// TODO: objectColor
 	Object *obj = sqobj(v, 2);
 	if (obj) {
 		int color = 0;
@@ -862,7 +864,7 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, verb)))
 		return sq_throwerror(v, "failed to get verb");
 
-	int verbId = verb;
+	//int verbId = verb;
 	if (!g_engine->_actor) {
 		// TODO:
 		// for (vb in gEngine.hud.actorSlot(gEngine.actor).verbs) {
@@ -890,7 +892,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 		sq_getstackobj(v, 2, &o);
 		Common::String name;
 		sqgetf(o, "name", name);
-		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, name.c_str()).c_str());
+		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_engine->_textDb.getText(name).c_str()).c_str());
 	}
 	Object *actor = nullptr;
 	if (sq_gettop(v) >= 3) {
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 5ddff58ebb2..e2dce81931e 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -103,7 +103,6 @@ public:
 	Object *createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
 
 	Math::Vector2d getScreenSize();
-	Math::Vector2d roomToScreen(Math::Vector2d pos);
 
 	Layer *layer(int zsort);
 	Object *getObj(const Common::String& key);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index fb2216785dc..ebaa04e3930 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -37,14 +37,10 @@ static float _getFps(float fps, float animFps) {
 	return (animFps < 1e-3) ? DEFAULT_FPS : animFps;
 }
 
-Node::Node(const Common::String &name, bool visible, Math::Vector2d scale, Color color)
+Node::Node(const Common::String &name, Math::Vector2d scale, Color color)
 	: _name(name),
-	  _parent(NULL),
 	  _color(color),
 	  _computedColor(color),
-	  _visible(visible),
-	  _rotation(0.f),
-	  _rotationOffset(0.f),
 	  _scale(scale) {
 }
 
@@ -192,7 +188,7 @@ Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
 	return parentTrsf * getLocalTrsf();
 }
 
-static void scale(Math::Matrix4& m, const Math::Vector2d &v) {
+static void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
 	m(0, 0) *= v.getX();
 	m(1, 1) *= v.getY();
 }
@@ -352,14 +348,21 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 				_sheet = _obj->_room->_sheet;
 			}
 		}
-		SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
-		const SpriteSheetFrame &sf = sheet->frameTable[frame];
-		Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
-		float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
-		float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
-		Math::Vector3d pos(int(-x), int(y), 0.f);
-		trsf.translate(pos);
-		g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
+		if (_sheet == "raw") {
+			Texture *texture = g_engine->_resManager.texture(frame);
+			Math::Vector3d pos(-texture->width / 2.f, -texture->height / 2.f, 0.f);
+			trsf.translate(pos);
+			g_engine->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
+		} else {
+			SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+			const SpriteSheetFrame &sf = sheet->frameTable[frame];
+			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+			float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
+			float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
+			Math::Vector3d pos(int(-x), int(y), 0.f);
+			trsf.translate(pos);
+			g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
+		}
 	}
 }
 
@@ -401,7 +404,25 @@ void TextNode::drawCore(Math::Matrix4 trsf) {
 	_text.draw(g_engine->getGfx(), trsf);
 }
 
-Scene::Scene() : Node("Scene") {}
+Scene::Scene() : Node("Scene") {
+	_zOrder = -100;
+}
 Scene::~Scene() {}
 
+InputState::InputState(): Node("InputState") {}
+InputState::~InputState() {}
+
+void InputState::drawCore(Math::Matrix4 trsf) {
+	// draw cursor
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	//   if prefs(ClassicSentence) and self.hotspot:
+	//       cursorName = "hotspot_" & self.cursorName
+	const SpriteSheetFrame& sf = gameSheet->frameTable[ "cursor"];
+	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f,  - sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
+	trsf.translate(pos);
+	scale(trsf, Math::Vector2d(2.f, 2.f));
+	g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
+}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 6b44396f302..8883946d133 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -37,7 +37,7 @@ namespace Twp {
 // Represents a node in a scene graph.
 class Node {
 public:
-	Node(const Common::String& name, bool visible = true, Math::Vector2d scale = Math::Vector2d(1, 1), Color color = Color());
+	Node(const Common::String& name, Math::Vector2d scale = Math::Vector2d(1, 1), Color color = Color());
 	virtual ~Node();
 
 	void setName(const Common::String& name) { _name = name; }
@@ -121,7 +121,7 @@ protected:
 	Common::Array<Node *> _children;
 	Math::Vector2d _offset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
 	Color _color, _computedColor;
-	bool _visible = false;
+	bool _visible = true;
 	float _rotation = 0.f;
 	float _rotationOffset = 0.f;
 };
@@ -216,6 +216,15 @@ public:
 	virtual ~Scene() final;
 };
 
+class InputState final: public Node {
+public:
+	InputState();
+	virtual ~InputState() final;
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index e83b5f839da..f3e5594dad1 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -22,6 +22,7 @@
 #include "twp/squtil.h"
 #include "twp/room.h"
 #include "twp/object.h"
+#include "twp/thread.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
 #include "squirrel/sqobject.h"
@@ -257,7 +258,7 @@ Room *sqroom(HSQUIRRELVM v, int i) {
 Object *sqobj(HSQOBJECT table) {
 	int id = getId(table);
 	for (int i = 0; i < g_engine->_actors.size(); i++) {
-    	Object* actor = g_engine->_actors[i];
+		Object *actor = g_engine->_actors[i];
 		if (getId(actor->_table) == id)
 			return actor;
 	}
@@ -315,34 +316,13 @@ int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	return nparams;
 }
 
-static void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name) {
+void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char* name) {
 	sq_pushobject(v, o);
-	sq_pushstring(v, name.c_str(), -1);
+	sq_pushstring(v, name, -1);
 	sq_get(v, -2);
 }
 
-static void sqcall(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, int numArgs, HSQOBJECT *args) {
-	SQInteger top = sq_gettop(v);
-	sqpushfunc(v, o, name);
-
-	sq_pushobject(v, o);
-	for (int i = 0; i < numArgs; i++) {
-		sq_pushobject(v, args[i]);
-	}
-	sq_call(v, 1 + numArgs, SQFalse, SQTrue);
-	sq_settop(v, top);
-}
-
-void sqcall(HSQOBJECT o, const Common::String &name, int numArgs, HSQOBJECT *args) {
-	sqcall(g_engine->getVm(), o, name, numArgs, args);
-}
-
-void sqcall(const Common::String &name, int numArgs, HSQOBJECT *args) {
-	HSQUIRRELVM v = g_engine->getVm();
-	sqcall(v, sqrootTbl(v), name, numArgs, args);
-}
-
-void sqexec(HSQUIRRELVM v, const char *code, const char* filename) {
+void sqexec(HSQUIRRELVM v, const char *code, const char *filename) {
 	SQInteger top = sq_gettop(v);
 	if (SQ_FAILED(sq_compilebuffer(v, code, strlen(code), filename ? filename : "twp", SQTrue))) {
 		sqstd_printcallstack(v);
@@ -357,4 +337,27 @@ void sqexec(HSQUIRRELVM v, const char *code, const char* filename) {
 	sq_settop(v, top);
 }
 
+ThreadBase *sqthread(int id) {
+	if (g_engine->_cutscene) {
+		if (g_engine->_cutscene->getId() == id) {
+			return g_engine->_cutscene;
+		}
+	}
+
+	// let threads = g_engine->_threads;
+	for (int i = 0; i < g_engine->_threads.size(); i++) {
+		ThreadBase *t = g_engine->_threads[i];
+		if (t->getId() == id) {
+			return t;
+		}
+	}
+	return nullptr;
+}
+
+ThreadBase *sqthread(HSQUIRRELVM v) {
+	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](ThreadBase *t) {
+		return t->getThread() == v;
+	});
+}
+
 } // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index ac562ce31f5..9b7d8ece921 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -33,10 +33,18 @@ template<typename T>
 HSQOBJECT sqtoobj(HSQUIRRELVM v, T value);
 
 template<typename T>
-SQInteger sqpush(HSQUIRRELVM v, T value);
+SQRESULT sqget(HSQUIRRELVM v, int index, T &value);
+
+SQInteger sqpush(HSQUIRRELVM v);
 
 template<typename T>
-SQRESULT sqget(HSQUIRRELVM v, int index, T &value);
+SQInteger sqpush(HSQUIRRELVM v, T value);
+
+template<typename T, typename... Args>
+void sqpush(HSQUIRRELVM v, T first, Args... args) {
+	sqpush(v, first);
+	sqpush(v, args...);
+}
 
 // set field
 template<typename T>
@@ -117,10 +125,20 @@ void sqnewf(HSQOBJECT o, const Common::String &key, T obj) {
 bool sqrawexists(HSQOBJECT obj, const Common::String &name);
 void sqsetdelegate(HSQOBJECT obj, HSQOBJECT del);
 HSQOBJECT sqrootTbl(HSQUIRRELVM v);
-int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String& name);
-void sqcall(const Common::String &name, int numArgs = 0, HSQOBJECT *args = nullptr);
-void sqcall(HSQOBJECT o, const Common::String& name, int numArgs = 0, HSQOBJECT* args = nullptr);
-void sqexec(HSQUIRRELVM v, const char *code, const char* filename = nullptr);
+
+void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char *name);
+int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name);
+
+template<typename... T>
+void sqcall(const char *name, T... args);
+
+template<typename... T>
+void sqcall(HSQOBJECT o, const char *name, T... args);
+
+template<typename TResult, typename... T>
+static void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args);
+
+void sqexec(HSQUIRRELVM v, const char *code, const char *filename = nullptr);
 
 class Room;
 class Object;
@@ -129,8 +147,81 @@ Room *sqroom(HSQOBJECT table);
 Room *sqroom(HSQUIRRELVM v, int i);
 Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
-Object* sqactor(HSQOBJECT table);
-Object* sqactor(HSQUIRRELVM v, int i);
+Object *sqactor(HSQOBJECT table);
+Object *sqactor(HSQUIRRELVM v, int i);
+ThreadBase *sqthread(HSQUIRRELVM v);
+ThreadBase *sqthread(int id);
+
+template<typename... T>
+void sqcall(HSQUIRRELVM v, HSQOBJECT o, const char *name, T... args) {
+	constexpr std::size_t n = sizeof...(T);
+	SQInteger top = sq_gettop(v);
+	sqpushfunc(v, o, name);
+
+	sq_pushobject(v, o);
+	if (n > 0) {
+		sqpush(v, std::forward<T>(args)...);
+	}
+	sq_call(v, 1 + n, SQFalse, SQTrue);
+	sq_settop(v, top);
+}
+
+template<typename... T>
+void sqcall(HSQOBJECT o, const char *name, T... args) {
+	constexpr std::size_t n = sizeof...(T);
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sqpushfunc(v, o, name);
+
+	sq_pushobject(v, o);
+	if (n > 0) {
+		sqpush(v, std::forward<T>(args)...);
+	}
+	sq_call(v, 1 + n, SQFalse, SQTrue);
+	sq_settop(v, top);
+}
+
+template<typename... T>
+void sqcall(const char *name, T... args) {
+	constexpr std::size_t n = sizeof...(T);
+	HSQUIRRELVM v = g_engine->getVm();
+	HSQOBJECT o = sqrootTbl(v);
+	SQInteger top = sq_gettop(v);
+	sqpushfunc(v, o, name);
+
+	sq_pushobject(v, o);
+	if (n > 0) {
+		sqpush(v, std::forward<T>(args)...);
+	}
+	sq_call(v, 1 + n, SQFalse, SQTrue);
+	sq_settop(v, top);
+}
+
+template<typename TResult, typename... T>
+void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
+	constexpr std::size_t n = sizeof...(T);
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sqpush(v, o);
+	sq_pushstring(v, _SC(name), -1);
+	if (SQ_FAILED(sq_get(v, -2))) {
+		sq_settop(v, top);
+		error("can't find %s function", name);
+		return;
+	}
+	sq_remove(v, -2);
+
+	sqpush(v, o);
+	sqpush(v, std::forward<T>(args)...);
+	if (SQ_FAILED(sq_call(v, n + 1, SQTrue, SQTrue))) {
+		// sqstd_printcallstack(v);
+		sq_settop(v, top);
+		error("function %s call failed", name);
+		return;
+	}
+	sqget(v, -1, result);
+	sq_settop(v, top);
+}
 
 } // namespace Twp
 
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index e53773e4f90..379567926a9 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -24,6 +24,7 @@
 #include "twp/ids.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
+#include "twp/task.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
 #include "twp/squirrel/sqobject.h"
@@ -40,23 +41,17 @@
 
 namespace Twp {
 
-static Thread *thread(HSQUIRRELVM v) {
-	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](Thread *t) {
-		return t->_threadObj._unVal.pThread == v;
-	});
-}
-
 static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	HSQUIRRELVM vm = g_engine->getVm();
 	SQInteger size = sq_gettop(v);
+	int id = newThreadId();
 
-	Thread *t = new Thread();
+	Thread *t = new Thread(id);
 	t->_global = global;
 
-	static uint64 gThreadId = 300000;
 	sq_newtable(v);
 	sq_pushstring(v, _SC("_id"), -1);
-	sq_pushinteger(v, gThreadId++);
+	sq_pushinteger(v, id);
 	sq_newslot(v, -3, SQFalse);
 	sq_getstackobj(v, -1, &t->_obj);
 	sq_addref(vm, &t->_obj);
@@ -93,7 +88,7 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
 		sq_getstring(v, -1, &name);
 
-	t->_name = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->_closureObj)->_function->_sourcename), _closure(t->_closureObj)->_function->_lineinfos->_line);
+	t->setName(Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->_closureObj)->_function->_sourcename), _closure(t->_closureObj)->_function->_lineinfos->_line));
 	sq_pop(vm, 1);
 	if (name)
 		sq_pop(v, 1); // pop name
@@ -101,23 +96,23 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 
 	g_engine->_threads.push_back(t);
 
-	debug("create thread %s", t->_name.c_str());
+	debug("create thread %s", t->getName().c_str());
 
 	// call the closure in the thread
 	if (!t->call())
 		return sq_throwerror(v, "call failed");
 
-	sq_pushobject(v, t->_obj);
+	sqpush(v, t->getId());
 	return 1;
 }
 
 template<typename F>
 static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
-	Thread *t = thread(v);
+	ThreadBase *t = sqthread(v);
 	if (!t)
 		return sq_throwerror(v, "failed to get thread");
 	t->suspend();
-	func(*t);
+	func(t);
 	return -666;
 }
 
@@ -155,13 +150,13 @@ static SQInteger breakhere(HSQUIRRELVM v) {
 		int numFrames;
 		if (SQ_FAILED(sqget(v, 2, numFrames)))
 			return sq_throwerror(v, "failed to get numFrames");
-		return breakfunc(v, [&](Thread &t) { t._numFrames = numFrames; });
+		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_numFrames = numFrames; });
 	}
 	if (t == OT_FLOAT) {
 		float time;
 		if (SQ_FAILED(sqget(v, 2, time)))
 			return sq_throwerror(v, "failed to get time");
-		return breakfunc(v, [&](Thread &t) { t._waitTime = time; });
+		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_waitTime = time; });
 	}
 	return sq_throwerror(v, Common::String::format("failed to get numFrames (wrong type = {%d})", t).c_str());
 }
@@ -179,9 +174,26 @@ static SQInteger breaktime(HSQUIRRELVM v) {
 	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
 		return sq_throwerror(v, "failed to get time");
 	if (time == 0.f)
-		return breakfunc(v, [](Thread &t) { t._numFrames = 1; });
+		return breakfunc(v, [](ThreadBase *t) { ((Thread *)t)->_numFrames = 1; });
 	else
-		return breakfunc(v, [&](Thread &t) { t._waitTime = time; });
+		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_waitTime = time; });
+}
+
+template<typename Predicate>
+static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt, ...) {
+	va_list va;
+	va_start(va, fmt);
+	Common::String name = Common::String::format(fmt, va);
+	va_end(va);
+
+	ThreadBase *curThread = sqthread(v);
+	if (!curThread)
+		return sq_throwerror(v, "Current thread should be created with startthread");
+
+	debug("curThread.id: %d", curThread->getId());
+	debug("add breakwhilecond name=%s pid=%d", name.c_str(), curThread->getId());
+	g_engine->_tasks.push_back(new BreakWhileCond<Predicate>(curThread->getId(), name, pred));
+	return -666;
 }
 
 static SQInteger breakwhileanimating(HSQUIRRELVM v) {
@@ -194,9 +206,12 @@ static SQInteger breakwhilecamera(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Breaks while a cutscene is running.
+// Once the thread finishes execution, the method will continue running.
+// It is an error to call breakwhilecutscene in a function that was not started with startthread.
 static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
-	warning("TODO: breakwhilecutscene: not implemented");
-	return 0;
+	return breakwhilecond(
+		v, [] { return g_engine->_cutscene == nullptr; }, "breakwhilecutscene()");
 }
 
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
@@ -208,10 +223,43 @@ static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
 	warning("TODO: breakwhileinputoff: not implemented");
 	return 0;
 }
+
+// Breaks while the thread referenced by threadId is running.
+// Once the thread finishes execution, the method will continue running.
+// It is an error to call breakwhilerunning in a function that was not started with startthread.
+//
+// . code-block:: Squirrel
+// local waitTID = 0
+//
+//    if ( g.in_flashback && HotelElevator.requestedFloor == 13 ) {
+//     waitTID = startthread(HotelElevator.avoidPenthouse)
+//     breakwhilerunning(waitTID)
+// }
+// waitTID = 0
+// if (HotelElevator.requestedFloor >= 0) {
+//     // Continue executing other code
+// }
 static SQInteger breakwhilerunning(HSQUIRRELVM v) {
-	warning("TODO: breakwhilerunning: not implemented");
-	return 0;
+	int id = 0;
+	if (sq_gettype(v, 2) == OT_INTEGER)
+		sqget(v, 2, id);
+	debug("breakwhilerunning: %d", id);
+
+	ThreadBase *t = sqthread(id);
+	if (!t) {
+		// TODO: sound
+		// let sound = sound(id);
+		// if (!sound) {
+		// 	warning("thread and sound not found: %d", id);
+		// 	return 0;
+		// }
+		// return breakwhilecond(v, [&] { return sound(id); }, "breakwhilerunning(%d)", id);
+		return 0;
+	}
+	return breakwhilecond(
+		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
 }
+
 static SQInteger breakwhiletalking(HSQUIRRELVM v) {
 	warning("TODO: breakwhiletalking: not implemented");
 	return 0;
@@ -226,8 +274,41 @@ static SQInteger breakwhilesound(HSQUIRRELVM v) {
 }
 
 static SQInteger cutscene(HSQUIRRELVM v) {
-	warning("TODO: cutscene: not implemented");
-	return 0;
+	HSQUIRRELVM vm = g_engine->getVm();
+	SQInteger nArgs = sq_gettop(v);
+
+	HSQOBJECT envObj;
+	sq_resetobject(&envObj);
+	if (SQ_FAILED(sq_getstackobj(v, 1, &envObj)))
+		return sq_throwerror(v, "Couldn't get environment from stack");
+
+	// create thread and store it on the stack
+	sq_newthread(vm, 1024);
+	HSQOBJECT threadObj;
+	sq_resetobject(&threadObj);
+	if (SQ_FAILED(sq_getstackobj(vm, -1, &threadObj)))
+		return sq_throwerror(v, "failed to get coroutine thread from stack");
+
+	// get the closure
+	HSQOBJECT closure;
+	sq_resetobject(&closure);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &closure)))
+		return sq_throwerror(v, "failed to get cutscene closure");
+
+	// get the cutscene override closure
+	HSQOBJECT closureOverride;
+	sq_resetobject(&closureOverride);
+	if (nArgs == 3) {
+		if (SQ_FAILED(sq_getstackobj(v, 3, &closureOverride)))
+			return sq_throwerror(v, "failed to get cutscene override closure");
+	}
+
+	Cutscene *cutscene = new Cutscene(v, threadObj, closure, closureOverride, envObj);
+	g_engine->_cutscene = cutscene;
+
+	// call the closure in the thread
+	cutscene->update(0.f);
+	return breakwhilecutscene(v);
 }
 
 static SQInteger cutsceneOverride(HSQUIRRELVM v) {
@@ -342,9 +423,27 @@ static SQInteger startglobalthread(HSQUIRRELVM v) {
 	return _startthread(v, true);
 }
 
+// Stops a thread specified by threadid.
+//
+// If the thread is not running, the command does nothing.
+//
+// See also:
+// * `startthread`
+// * `startglobalthread`
 static SQInteger stopthread(HSQUIRRELVM v) {
-	warning("TODO: stopthread: not implemented");
-	return 0;
+	int id = 0;
+	if (SQ_FAILED(sqget(v, 2, id))) {
+		sqpush(v, 0);
+		return 1;
+	}
+
+	ThreadBase *t = sqthread(id);
+	if (t) {
+		t->stop();
+	}
+
+	sqpush(v, 0);
+	return 1;
 }
 
 static SQInteger threadid(HSQUIRRELVM v) {
@@ -599,6 +698,7 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "BUTTON_BACK", BUTTON_BACK);
 	regConst(v, "BUTTON_MOUSE_LEFT", BUTTON_MOUSE_LEFT);
 	regConst(v, "BUTTON_MOUSE_RIGHT", BUTTON_MOUSE_RIGHT);
+	regConst(v, "PLATFORM", 1); // TODO: choose the right platform
 }
 
 } // namespace Twp
diff --git a/engines/twp/task.h b/engines/twp/task.h
new file mode 100644
index 00000000000..082a1039d78
--- /dev/null
+++ b/engines/twp/task.h
@@ -0,0 +1,68 @@
+/* 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 TWP_TASK_H
+#define TWP_TASK_H
+
+#include "common/str.h"
+#include "twp/thread.h"
+
+namespace Twp {
+
+class Task {
+public:
+	virtual ~Task() {}
+
+	virtual bool update(float elapsed) = 0;
+};
+
+typedef bool Predicate();
+
+template<typename Predicate>
+class BreakWhileCond : public Task {
+public:
+	BreakWhileCond(int parentId, const Common::String &name, Predicate cond)
+		: _parentId(parentId),
+		  _name(name),
+		  _cond(cond) {
+	}
+	virtual ~BreakWhileCond() override final {}
+
+	virtual bool update(float elapsed) override final {
+		if (_cond())
+			return false;
+		ThreadBase *pt = sqthread(_parentId);
+		if (pt) {
+			debug("Resume task: %d", _parentId);
+			pt->resume();
+		}
+		return true;
+	}
+
+private:
+	int _parentId = 0;
+	Common::String _name;
+	Predicate _cond;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index d3a97629c18..caf514cb9b3 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -19,12 +19,38 @@
  *
  */
 
+#include "twp/ids.h"
 #include "twp/twp.h"
 #include "twp/thread.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
-Thread::Thread() {}
+bool ThreadBase::isDead() {
+	SQInteger state = sq_getvmstate(getThread());
+	return _stopRequest || state == 0;
+}
+
+bool ThreadBase::isSuspended() {
+	SQInteger state = sq_getvmstate(getThread());
+	return state != 1;
+}
+
+void ThreadBase::suspend() {
+	if (_pauseable && !isSuspended()) {
+		sq_suspendvm(getThread());
+	}
+}
+
+void ThreadBase::resume() {
+	if (!isDead() && isSuspended()) {
+		sq_wakeupvm(getThread(), SQFalse, SQFalse, SQTrue, SQFalse);
+	}
+}
+
+Thread::Thread(int id) {
+	_id = id;
+}
 
 Thread::~Thread() {
 	HSQUIRRELVM v = g_engine->getVm();
@@ -52,29 +78,6 @@ bool Thread::call() {
 	return true;
 }
 
-bool Thread::isDead() {
-	SQInteger state = sq_getvmstate(_threadObj._unVal.pThread);
-	return _stopRequest || state == 0;
-}
-
-bool Thread::isSuspended() {
-	SQInteger state = sq_getvmstate(_threadObj._unVal.pThread);
-	return state != 1;
-}
-
-void Thread::suspend() {
-	// TODO: pauseable
-	if (!isSuspended()) {
-		sq_suspendvm(_threadObj._unVal.pThread);
-	}
-}
-
-void Thread::resume() {
-	if (!isDead() && isSuspended()) {
-		sq_wakeupvm(_threadObj._unVal.pThread, SQFalse, SQFalse, SQTrue, SQFalse);
-	}
-}
-
 bool Thread::update(float elapsed) {
 	if (_paused) {
 	} else if (_waitTime > 0) {
@@ -96,4 +99,149 @@ void Thread::stop() {
 	suspend();
 }
 
+Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
+	: _name("cutscene"),
+	  _v(v),
+	  _threadObj(threadObj),
+	  _closure(closure),
+	  _closureOverride(closureOverride),
+	  _envObj(envObj) {
+
+	_pauseable = false;
+	_id = newThreadId();
+	//_inputState = g_engine->inputState.getState();
+	_actor = g_engine->_followActor;
+	//_showCursor = g_engine->inputState.showCursor;
+	_state = csStart;
+	debug("Create cutscene %d with input: 0x%X", _id, _inputState);
+	// TODO:
+	//   g_engine->_inputState.inputActive = false;
+	//   g_engine->_inputState.showCursor = false;
+	for (int i = 0; i < g_engine->_threads.size(); i++) {
+		ThreadBase *thread = g_engine->_threads[i];
+		if (thread->isGlobal())
+			thread->pause();
+	}
+	HSQUIRRELVM vm = g_engine->getVm();
+	sq_addref(vm, &_threadObj);
+	sq_addref(vm, &_closure);
+	sq_addref(vm, &_closureOverride);
+	sq_addref(vm, &_envObj);
+}
+
+Cutscene::~Cutscene() {
+	debug("destroy cutscene %d", _id);
+	HSQUIRRELVM vm = g_engine->getVm();
+	sq_release(vm, &_threadObj);
+	sq_release(vm, &_closure);
+	sq_release(vm, &_closureOverride);
+	sq_release(vm, &_envObj);
+}
+
+void Cutscene::start() {
+	_state = csCheckEnd;
+	HSQUIRRELVM thread = getThread();
+	// call the closure in the thread
+	SQInteger top = sq_gettop(thread);
+	sq_pushobject(thread, _closure);
+	sq_pushobject(thread, _envObj);
+	if (SQ_FAILED(sq_call(thread, 1, SQFalse, SQTrue))) {
+		sq_settop(thread, top);
+		error("Couldn't call cutscene");
+	}
+}
+
+void Cutscene::stop() {
+	_state = csQuit;
+	debug("End cutscene");
+	// g_engine->inputState.setState(self.inputState);
+	// g_engine->inputState.showCursor = self.showCursor;
+	// if self.showCursor:
+	// 	g_engine->inputState.inputActive = true
+	debug("Restore cutscene input: %X", _inputState);
+	g_engine->follow(g_engine->_actor);
+	for (int i = 0; i < g_engine->_threads.size(); i++) {
+		ThreadBase *thread = g_engine->_threads[i];
+		if (thread->isGlobal())
+			thread->unpause();
+	}
+	sqcall("onCutsceneEnded");
+	sq_wakeupvm(_v, SQFalse, SQFalse, SQTrue, SQFalse);
+	sq_suspendvm(getThread());
+}
+
+void Cutscene::checkEndCutsceneOverride() {
+	if (isStopped()) {
+		_state = csEnd;
+		debug("end checkEndCutsceneOverride");
+	}
+}
+
+bool Cutscene::update(float elapsed) {
+	if (_waitTime > 0)
+		_waitTime -= elapsed;
+	if (_waitTime <= 0) {
+		_waitTime = 0;
+		resume();
+	} else if (_numFrames > 0) {
+		_numFrames -= 1;
+		_numFrames = 0;
+		resume();
+	}
+
+	switch (_state) {
+	case csStart:
+		debug("startCutscene");
+		start();
+		return false;
+	case csCheckEnd:
+		checkEndCutscene();
+		return false;
+	case csOverride:
+		debug("doCutsceneOverride");
+		doCutsceneOverride();
+		return false;
+	case csCheckOverride:
+		debug("checkEndCutsceneOverride");
+		checkEndCutsceneOverride();
+		return false;
+	case csEnd:
+		debug("endCutscene");
+		stop();
+		return false;
+	case csQuit:
+		return true;
+	}
+}
+
+bool Cutscene::hasOverride() const {
+	return !sq_isnull(_closureOverride);
+}
+
+void Cutscene::doCutsceneOverride() {
+	if (hasOverride()) {
+		_state = csCheckOverride;
+		debug("start cutsceneOverride");
+		sq_pushobject(getThread(), _closureOverride);
+		sq_pushobject(getThread(), _envObj);
+		if (SQ_FAILED(sq_call(getThread(), 1, SQFalse, SQTrue)))
+			error("Couldn't call cutsceneOverride");
+		return;
+	}
+	_state = csEnd;
+}
+
+void Cutscene::checkEndCutscene() {
+	if (isStopped()) {
+		_state = csEnd;
+		debug("end cutscene: %d", getId());
+	}
+}
+
+bool Cutscene::isStopped() {
+	if (_stopped || (_state == csQuit))
+		return true;
+	return sq_getvmstate(getThread()) == 0;
+}
+
 } // namespace Twp
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index 386873dd499..164d847c504 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -28,30 +28,122 @@
 
 namespace Twp {
 
-class Thread {
+class ThreadBase {
 public:
-	Thread();
-	~Thread();
+	virtual ~ThreadBase() {}
+
+	void pause() {
+		if (_pauseable) {
+			_paused = true;
+			suspend();
+		}
+	}
+
+	void unpause() {
+		_paused = false;
+		resume();
+	}
+
+	void setName(const Common::String& name) { _name = name; }
+	Common::String getName() const { return _name; }
+
+	int getId() const { return _id; }
+	virtual HSQUIRRELVM getThread() = 0;
+
+	virtual bool isGlobal() = 0;
 
-	bool call();
-	bool update(float elapsed);
 	void suspend();
 	void resume();
+
+	virtual bool update(float elapsed) = 0;
+	virtual void stop() = 0;
+
+protected:
 	bool isDead();
 	bool isSuspended();
-	void stop();
 
 public:
-	uint64 _id=0;
-	Common::String _name;
-    bool _global = false;
-	HSQOBJECT _obj, _threadObj, _envObj, _closureObj;
-	Common::Array<HSQOBJECT> _args;
-	bool _paused=false;
 	float _waitTime = 0.f;
 	int _numFrames = 0;
+	bool _paused = false;
+	bool _pauseable = true;
+
+protected:
+	int _id = 0;
+	Common::String _name;
 	bool _stopRequest = false;
+	bool _stopped = false;
+};
+
+class Thread final : public ThreadBase {
+public:
+	Thread(int id);
+	virtual ~Thread() override final;
+
+	virtual bool isGlobal() override final { return _global; }
+	virtual HSQUIRRELVM getThread() override final { return _threadObj._unVal.pThread; }
+
+	bool call();
+	virtual bool update(float elapsed) override final;
+	virtual void stop() override final;
+
+public:
+	bool _global = false;
+	HSQOBJECT _obj, _threadObj, _envObj, _closureObj;
+	Common::Array<HSQOBJECT> _args;
+};
+
+enum CutsceneState {
+	csStart,
+	csCheckEnd,
+	csOverride,
+	csCheckOverride,
+	csEnd,
+	csQuit
+};
+
+enum InputStateFlag {
+	II_FLAGS_UI_INPUT_ON = 1,
+	II_FLAGS_UI_INPUT_OFF = 2,
+	II_FLAGS_UI_VERBS_ON = 4,
+	II_FLAGS_UI_VERBS_OFF = 8,
+	II_FLAGS_UI_HUDOBJECTS_ON = 0x10,
+	II_FLAGS_UI_HUDOBJECTS_OFF = 0x20,
+	II_FLAGS_UI_CURSOR_ON = 0x40,
+	II_FLAGS_UI_CURSOR_OFF = 0x80
 };
-}
+
+class Object;
+class Cutscene final : public ThreadBase {
+public:
+	Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
+	virtual ~Cutscene() override final;
+
+	void start();
+	virtual bool isGlobal() override final { return false; }
+	virtual HSQUIRRELVM getThread() override final { return _threadObj._unVal.pThread; }
+	virtual bool update(float elapsed) override final;
+	virtual void stop() override final;
+
+	bool hasOverride() const;
+	inline void cutsceneOverride() { _state = csOverride; }
+
+private:
+	void checkEndCutscene();
+	void checkEndCutsceneOverride();
+	void doCutsceneOverride();
+	bool isStopped();
+
+private:
+	Common::String _name;
+	HSQUIRRELVM _v;
+	HSQOBJECT _threadObj, _closure, _closureOverride, _envObj;
+	CutsceneState _state;
+	bool _showCursor = false;
+	InputStateFlag _inputState = (InputStateFlag)0;
+	Object *_actor = nullptr;
+};
+
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 2159e51eb79..79608a0511e 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -42,6 +42,7 @@
 #include "twp/squtil.h"
 #include "twp/object.h"
 #include "twp/ids.h"
+#include "twp/task.h"
 #include "twp/squirrel/squirrel.h"
 
 namespace Twp {
@@ -56,12 +57,30 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	  _gameDescription(gameDesc),
 	  _randomSource("Twp") {
 	g_engine = this;
+	sq_resetobject(&_defaultObj);
+	_screenScene.addChild(&_inputState);
 }
 
 TwpEngine::~TwpEngine() {
 	delete _screen;
 }
 
+static Math::Vector2d winToScreen(Math::Vector2d pos) {
+	return Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
+}
+
+Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
+	Math::Vector2d screenSize = _room->getScreenSize();
+	pos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
+	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx.cameraPos()) / screenSize;
+}
+
+Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
+	Math::Vector2d screenSize = _room->getScreenSize();
+	pos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
+	return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
+}
+
 uint32 TwpEngine::getFeatures() const {
 	return _gameDescription->flags;
 }
@@ -70,30 +89,103 @@ Common::String TwpEngine::getGameId() const {
 	return _gameDescription->gameId;
 }
 
-void TwpEngine::update(float elapsedMs) {
+bool TwpEngine::clickedAtHandled(Math::Vector2d roomPos) {
+	bool result = false;
+	int x = roomPos.getX();
+	int y = roomPos.getY();
+	if (sqrawexists(_room->_table, "clickedAt")) {
+		debug("clickedAt %d, %d", x, y);
+		sqcallfunc(result, _room->_table, "clickedAt", x, y);
+	}
+	if (!result) {
+		if (_actor && sqrawexists(_actor->_table, "clickedAt")) {
+			sqcallfunc(result, _actor->_table, "clickedAt", x, y);
+		}
+	}
+	return result;
+}
+
+void TwpEngine::clickedAt(Math::Vector2d scrPos) {
+	// TODO: update this
+	if (_room) {
+		Math::Vector2d roomPos = screenToRoom(scrPos);
+		//Object *obj = objAt(roomPos);
+
+		if (_cursor.leftDown) {
+			// button left: execute selected verb
+			clickedAtHandled(roomPos);
+			// if (!handled && obj) {
+			//     sqcall("onVerbClick");
+			//     handled = execSentence(nullptr, 1, _noun1, _noun2);
+			//   }
+			//   if not handled:
+			//     if (not self.actor.isNil and scrPos.y > 172) {
+			//       self.actor.walk(room_pos)
+			//       self.hud.verb = self.hud.actorSlot(self.actor).verbs[0]
+			// 	}
+			// Just clicking on the ground
+			//     cancelSentence(self.actor)
+		}
+		// else if _cursor.rightDown) {
+		//   // button right: execute default verb
+		//   if not obj.isNil:
+		//     discard self.execSentence(nil, obj.defaultVerbId, self.noun1, self.noun2)
+		// } else if (self.walkFastState and self.mouseState.pressed() and not self.actor.isNil and scrPos.y > 172) {
+		//   self.actor.walk(room_pos);
+		// }
+	}
+}
+
+void TwpEngine::update(float elapsed) {
+	_time += elapsed;
+
+	// update mouse pos
+	Math::Vector2d scrPos = winToScreen(_cursor.pos);
+	//_inputState.visible = _inputState.showCursor; // TODO: || _dlg.state == WaitingForChoice;
+	_inputState.setPos(scrPos);
+	// TODO:
+	// _sentence.pos = scrPos;
+	// _dlg.mousePos = scrPos;
+	if (_room) {
+		if (_cursor.leftDown)
+			clickedAt(_cursor.pos);
+	}
+
 	// update camera
-	_camera.update(_room, _followActor, elapsedMs);
+	_camera.update(_room, _followActor, elapsed);
+
+	// update tasks
+	for (auto it = _tasks.begin(); it != _tasks.end();) {
+		Task *task = *it;
+		if (task->update(elapsed)) {
+			it = _tasks.erase(it);
+			delete task;
+			continue;
+		}
+		it++;
+	}
 
 	// update threads
-	for (int i = 0; i < _threads.size(); i++) {
-		Thread *thread = _threads[i];
-		if (thread->update(elapsedMs/1000.f)) {
-			// TODO: delete it
+	for (auto it = _threads.begin(); it != _threads.end();) {
+		ThreadBase *thread = *it;
+		if (thread->update(elapsed)) {
+			it = _threads.erase(it);
+			delete thread;
+			continue;
 		}
+		it++;
 	}
 
 	// update objects
-	//for (int i = 0; i < g_engine->_rooms.size(); i++) {
-		//Room *room = g_engine->_rooms[i];
-		Room *room = g_engine->_room;
-		for (int j = 0; j < room->_layers.size(); j++) {
-			Layer *layer = room->_layers[j];
+	if (_room) {
+		for (int j = 0; j < _room->_layers.size(); j++) {
+			Layer *layer = _room->_layers[j];
 			for (int k = 0; k < layer->_objects.size(); k++) {
 				Object *obj = layer->_objects[k];
-				obj->update(elapsedMs/1000.f);
+				obj->update(elapsed);
 			}
 		}
-	//}
+	}
 }
 
 void TwpEngine::draw() {
@@ -103,7 +195,10 @@ void TwpEngine::draw() {
 	}
 	_gfx.clear(Color(0, 0, 0));
 	_gfx.use(NULL);
-	_scene->draw();
+	_scene.draw();
+
+	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+	_screenScene.draw();
 
 	g_system->updateScreen();
 }
@@ -117,7 +212,6 @@ Common::Error TwpEngine::run() {
 
 	_gfx.init();
 	_lighting = new Lighting();
-	_scene = new Scene();
 
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
 	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
@@ -142,28 +236,15 @@ Common::Error TwpEngine::run() {
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
-	// GGPackEntryReader reader;
-	// reader.open(_pack, "StartScreen.bnut");
-	// GGBnutReader nut;
-	// nut.open(&reader);
-	// Common::String code1 = nut.readString();
-
-	// const SQChar *code = R"(
-	// MainStreet <- {
-	// 	background = "MainStreet"
-	// 	enter = function(enter_door) {}
-	// }
-	// defineRoom(MainStreet)
-	// cameraInRoom(MainStreet)
-	// cameraAt(0,128)
-	// //cameraPanTo(2820, 128, 5000, 2)
-	// local cr = createTextObject("SentenceFont", "Copyright 2014-2017 Terrible Toybox, Inc. All Rights Reserved. Thimbleweed Parkxe2x84xa2 is a trademark of Terrible Toybox, Inc.", ALIGN_CENTER)
-	// objectScale(cr, 0.25/2)
-	// objectAt(cr, 160, 5)
-	// objectAlpha(cr, 0.25)
-	// )";
-
-	const SQChar *code = "cameraInRoom(StartScreen)";
+	GGPackEntryReader reader;
+	if (reader.open(_pack, "EasyHardMode.bnut")) {
+		GGBnutReader nut;
+		nut.open(&reader);
+		Common::String code1 = nut.readString();
+	}
+
+	// const SQChar *code = "cameraInRoom(StartScreen)";
+	const SQChar *code = "start(1)";
 
 	_vm.exec(code);
 
@@ -193,6 +274,21 @@ Common::Error TwpEngine::run() {
 				default:
 					break;
 				}
+			case Common::EVENT_MOUSEMOVE:
+				_cursor.pos = Math::Vector2d(e.mouse.x, e.mouse.y);
+				break;
+			case Common::EVENT_LBUTTONDOWN:
+				_cursor.leftDown = true;
+				break;
+			case Common::EVENT_LBUTTONUP:
+				_cursor.leftDown = false;
+				break;
+			case Common::EVENT_RBUTTONDOWN:
+				_cursor.rightDown = true;
+				break;
+			case Common::EVENT_RBUTTONUP:
+				_cursor.rightDown = false;
+				break;
 			default:
 				break;
 			}
@@ -200,8 +296,7 @@ Common::Error TwpEngine::run() {
 
 		_gfx.cameraPos(camPos);
 
-		// update threads
-		update(deltaTimeMs);
+		update(deltaTimeMs / 1000.f);
 		draw();
 
 		// Delay for a bit. All events loops should have a delay
@@ -224,16 +319,6 @@ Common::Error TwpEngine::syncGame(Common::Serializer &s) {
 	return Common::kNoError;
 }
 
-Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
-	Math::Vector2d screenSize = _room->getScreenSize();
-	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx.cameraPos()) / screenSize;
-}
-
-Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
-	Math::Vector2d screenSize = _room->getScreenSize();
-	return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
-}
-
 Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
 	HSQUIRRELVM v = _vm.get();
 	debug("Load room: %s", name.c_str());
@@ -241,7 +326,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 	if (name == "Void") {
 		result = new Room(name, table);
 		result->_scene = new Scene();
-		Layer* layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
+		Layer *layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
 		layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray());
 		result->_layers.push_back(layer);
 		result->_scene->addChild(layer->_node);
@@ -404,7 +489,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	if (_room)
 		_room->_scene->remove();
 	_room = room;
-	_scene->addChild(_room->_scene);
+	_scene.addChild(_room->_scene);
 	_room->_lights._numLights = 0;
 	// TODO:   _room->overlay = Transparent;
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
@@ -446,21 +531,16 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 		if (!door) {
 			HSQOBJECT doorTable;
 			sq_resetobject(&doorTable);
-			HSQOBJECT args[] = {doorTable};
-			sqcall(_room->_table, "enter", 1, args);
+			sqcall(_room->_table, "enter", doorTable);
 		} else {
-			HSQOBJECT args[] = {door->_table};
-			sqcall(_room->_table, "enter", 1, args);
+			sqcall(_room->_table, "enter", door->_table);
 		}
 	} else {
 		sqcall(_room->_table, "enter");
 	}
 
 	// call global function enteredRoom with the room as argument
-	{
-		HSQOBJECT args[] = {room->_table};
-		sqcall("enteredRoom", 1, args);
-	}
+	sqcall("enteredRoom", room->_table);
 }
 
 void TwpEngine::actorEnter() {
@@ -468,8 +548,7 @@ void TwpEngine::actorEnter() {
 		sqcall(_actor->_table, "actorEnter");
 	if (_room) {
 		if (sqrawexists(_room->_table, "actorEnter")) {
-			HSQOBJECT args[] = {_actor->_table};
-			sqcall(_room->_table, "actorEnter", 1, args);
+			sqcall(_room->_table, "actorEnter", _actor->_table);
 		}
 	}
 }
@@ -484,9 +563,8 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 
 		// call room exit function with the next room as a parameter if requested
 		int nparams = sqparamCount(v, _room->_table, "exit");
-		HSQOBJECT args[] = {nextRoom->_table};
 		if (nparams == 2) {
-			sqcall(_room->_table, "exit", 1, args);
+			sqcall(_room->_table, "exit", nextRoom->_table);
 		} else {
 			sqcall(_room->_table, "exit");
 		}
@@ -498,19 +576,20 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 				Object *obj = layer->_objects[i];
 				if (obj->_temporary) {
 					obj->delObject();
+					delete obj;
 				} else if (isActor(obj->getId()) && _actor != obj) {
 					obj->stopObjectMotors();
 				}
 			}
 		}
 
-		// call global function enteredRoom with the room as argument
-		sqcall("exitedRoom", 1, args);
+		// call global function exitedRoom with the room as argument
+		sqcall("exitedRoom", _room->_table);
 
 		// stop all local threads
 		for (int i = 0; i < _threads.size(); i++) {
-			Thread *thread = _threads[i];
-			if (!thread->_global) {
+			ThreadBase *thread = _threads[i];
+			if (!thread->isGlobal()) {
 				thread->stop();
 			}
 		}
@@ -528,8 +607,7 @@ void TwpEngine::setRoom(Room *room) {
 void TwpEngine::actorExit() {
 	if (!_actor && _room) {
 		if (sqrawexists(_room->_table, "actorExit")) {
-			HSQOBJECT args[] = {_actor->_table};
-			sqcall(_room->_table, "actorExit", 1, args);
+			sqcall(_room->_table, "actorExit", _actor->_table);
 		}
 	}
 }
@@ -557,7 +635,7 @@ void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 		debug("read existing '%s'", entry.c_str());
 		reader.open(_pack, entry);
 		Common::String code = reader.readString();
-		//debug("%s", code.c_str());
+		// debug("%s", code.c_str());
 		sqexec(v, code.c_str(), entry.c_str());
 	} else {
 		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
@@ -575,6 +653,14 @@ void TwpEngine::cameraAt(Math::Vector2d at) {
 	_camera.setAt(at);
 }
 
+Math::Vector2d TwpEngine::cameraPos() {
+	if (_room) {
+		Math::Vector2d screenSize = _room->getScreenSize();
+		return _camera.getAt() + screenSize / 2.0f;
+	}
+	return _camera.getAt();
+}
+
 void TwpEngine::follow(Object *actor) {
 	_followActor = actor;
 	if (actor) {
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f0d759ffc52..a9d169a3ed2 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -39,13 +39,17 @@
 #include "twp/camera.h"
 #include "twp/prefs.h"
 #include "twp/tsv.h"
+#include "twp/scenegraph.h"
 
 namespace Twp {
 
 class Lighting;
-class Thread;
+class Task;
+class ThreadBase;
+class Cutscene;
 class Scene;
 class Room;
+class InputState;
 class Object;
 struct TwpGameDescription;
 
@@ -72,18 +76,17 @@ public:
 	/**
 	 * Gets the random source
 	 */
-	Common::RandomSource& getRandomSource() { return _randomSource; }
-	Preferences& getPrefs() { return _prefs; }
+	Common::RandomSource &getRandomSource() { return _randomSource; }
+	Preferences &getPrefs() { return _prefs; }
 
 	HSQUIRRELVM getVm() { return _vm.get(); }
-	inline Gfx& getGfx() { return _gfx; }
-	inline TextDb& getTextDb() { return _textDb; }
+	inline Gfx &getGfx() { return _gfx; }
+	inline TextDb &getTextDb() { return _textDb; }
 
 	bool hasFeature(EngineFeature f) const override {
-		return
-		    (f == kSupportsLoadingDuringRuntime) ||
-		    (f == kSupportsSavingDuringRuntime) ||
-		    (f == kSupportsReturnToLauncher);
+		return (f == kSupportsLoadingDuringRuntime) ||
+			   (f == kSupportsSavingDuringRuntime) ||
+			   (f == kSupportsReturnToLauncher);
 	};
 
 	bool canLoadGameStateCurrently() override {
@@ -111,18 +114,20 @@ public:
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
-	void setActor(Object* actor) { _actor = actor; }
-	Object* objAt(Math::Vector2d pos);
+	void setActor(Object *actor) { _actor = actor; }
+	Object *objAt(Math::Vector2d pos);
 
-	Room* defineRoom(const Common::String& name, HSQOBJECT table, bool pseudo = false);
+	Room *defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
 	void enterRoom(Room *room, Object *door = nullptr);
 
 	void cameraAt(Math::Vector2d at);
-	void follow(Object* actor);
+	// Returns the camera position: the position of the middle of the screen.
+	Math::Vector2d cameraPos();
+	void follow(Object *actor);
 
-	void execNutEntry(HSQUIRRELVM v, const Common::String& entry);
-	void execBnutEntry(HSQUIRRELVM v,const Common::String &entry);
+	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
+	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
 
 private:
 	void update(float elapsedMs);
@@ -130,29 +135,40 @@ private:
 	void exitRoom(Room *nextRoom);
 	void actorEnter();
 	void actorExit();
-	void cancelSentence(Object* actor = nullptr);
+	void cancelSentence(Object *actor = nullptr);
+	void clickedAt(Math::Vector2d scrPos);
+	bool clickedAtHandled(Math::Vector2d roomPos);
 
 public:
 	Graphics::Screen *_screen = nullptr;
 	GGPackDecoder _pack;
 	ResManager _resManager;
-	Common::Array<Room*> _rooms;
-	Common::Array<Object*> _actors;
-	Common::Array<Object*> _objects;
-	Common::Array<Thread*> _threads;
-	Object* _actor = nullptr;
-	Object* _followActor = nullptr;
-	Room* _room = nullptr;
-	float _time = 0.f;						// time in seconds
-	Object* _noun1 = nullptr;
-	Object* _noun2 = nullptr;
+	Common::Array<Room *> _rooms;
+	Common::Array<Object *> _actors;
+	Common::Array<Object *> _objects;
+	Common::Array<ThreadBase *> _threads;
+	Common::Array<Task *> _tasks;
+	Object *_actor = nullptr;
+	Object *_followActor = nullptr;
+	Room *_room = nullptr;
+	float _time = 0.f; // time in seconds
+	Object *_noun1 = nullptr;
+	Object *_noun2 = nullptr;
 	HSQOBJECT _defaultObj;
 	bool _walkFastState = false;
 	int _frameCounter = 0;
-	Lighting* _lighting = nullptr;
-	Scene* _scene = nullptr;
+	Lighting *_lighting = nullptr;
+	Cutscene *_cutscene = nullptr;
+	Scene _scene;
+	Scene _screenScene;
+	InputState _inputState;
 	Camera _camera;
 	TextDb _textDb;
+	struct Cursor {
+		Math::Vector2d pos;
+		bool leftDown = false;
+		bool rightDown = false;
+	} _cursor;
 
 private:
 	Gfx _gfx;
diff --git a/engines/twp/util.h b/engines/twp/util.h
index bd94e09c321..932d2994fba 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -49,6 +49,18 @@ Math::Vector2d parseVec2(const Common::String &s);
 Common::Rect parseRect(const Common::String &s);
 void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
 
+template<typename T>
+int find(Common::Array<T>& array, const T& o) {
+	int index = -1;
+	for (int i = 0; i < array.size(); i++) {
+		if (array[i] == o) {
+			index = i;
+			break;
+		}
+	}
+	return index;
+}
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index ecca5187374..25ec0a66ff1 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -99,16 +99,9 @@ Vm::Vm() {
 	sqgame_register_actorlib(v);
 	sqgame_register_roomlib(v);
 	sqgame_register_soundlib(v);
-
-	// TODO: constants
-	SQObject platform = sqtoobj(v, 666);
-	_table(v->_roottable)->NewSlot(sqtoobj(v, _SC("PLATFORM")), SQObjectPtr(platform));
 }
 
 Vm::~Vm() {
-	for (int i = 0; i < g_engine->_threads.size(); i++) {
-		delete g_engine->_threads[i];
-	}
 	sq_close(v);
 }
 


Commit: ed36a07d0e4e3842d00f9c2d453cab31da1f9526
    https://github.com/scummvm/scummvm/commit/ed36a07d0e4e3842d00f9c2d453cab31da1f9526
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add yack parser

Changed paths:
  A engines/twp/yack.cpp
  A engines/twp/yack.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/module.mk
    engines/twp/twp.cpp


diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 3acf5422097..6187cd6dd49 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -496,6 +496,10 @@ bool MemStream::seek(int64 offset, int whence) {
 		_pos = offset;
 		return true;
 	}
+	if (whence == SEEK_CUR) {
+		_pos += offset;
+		return true;
+	}
 	_pos = _bufSize + offset;
 	return true;
 }
@@ -608,7 +612,7 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 		int offset = (int)file["offset"]->asIntegerNumber();
 		int size = (int)file["size"]->asIntegerNumber();
 		_entries[filename] = GGPackEntry{offset, size};
-		//debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
+		// debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
 	}
 	delete value;
 
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index a9a6acf0f80..52d5a2c4afc 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -48,9 +48,9 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    const byte* _buf;
-	int64 _bufSize;
-	int64 _pos;
+    const byte* _buf = nullptr;
+	int64 _bufSize = 0;
+	int64 _pos = 0;
 };
 
 class XorStream: public Common::SeekableReadStream {
@@ -66,10 +66,10 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    Common::SeekableReadStream *_s;
-    int _previous;
-    int _start;
-    int _size;
+    Common::SeekableReadStream *_s = nullptr;
+    int _previous = 0;
+    int _start = 0;
+    int _size = 0;
     XorKey _key;
 };
 
@@ -86,9 +86,9 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    Common::SeekableReadStream *_s;
-    int64 _start;
-    int64 _size;
+    Common::SeekableReadStream *_s = nullptr;
+    int64 _start = 0;
+    int64 _size = 0;
 };
 
 class GGHashMapDecoder {
@@ -104,7 +104,7 @@ private:
 	Common::JSONValue* readArray();
 
 private:
-	Common::SeekableReadStream *_stream;
+	Common::SeekableReadStream *_stream = nullptr;
 	Common::Array<int> _offsets;
 };
 
@@ -127,7 +127,7 @@ public:
 private:
 	XorKey _key;
 	GGPackEntries _entries;
-	Common::SeekableReadStream *_s;
+	Common::SeekableReadStream *_s = nullptr;
 };
 
 class GGBnutReader: public Common::ReadStream {
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index dfc34ab9110..a1cdb665252 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -52,6 +52,7 @@ MODULE_OBJS = \
 	tsv.o \
 	util.o \
 	motor.o \
+	yack.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 79608a0511e..ac0f9fe84e7 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -44,6 +44,7 @@
 #include "twp/ids.h"
 #include "twp/task.h"
 #include "twp/squirrel/squirrel.h"
+#include "twp/yack.h"
 
 namespace Twp {
 
@@ -237,10 +238,11 @@ Common::Error TwpEngine::run() {
 	execBnutEntry(v, "Boot.bnut");
 
 	GGPackEntryReader reader;
-	if (reader.open(_pack, "EasyHardMode.bnut")) {
-		GGBnutReader nut;
-		nut.open(&reader);
-		Common::String code1 = nut.readString();
+	if (reader.open(_pack, "Opening.byack")) {
+		YackParser parser;
+		unique_ptr<YCompilationUnit> cu(parser.parse(&reader));
+		YackDump dump;
+		cu->accept(dump);
 	}
 
 	// const SQChar *code = "cameraInRoom(StartScreen)";
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
new file mode 100644
index 00000000000..0ec95593aef
--- /dev/null
+++ b/engines/twp/yack.cpp
@@ -0,0 +1,503 @@
+/* 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 "twp/yack.h"
+#include "common/util.h"
+
+namespace Twp {
+
+Common::String Token::toString() const {
+	switch (id) {
+	case TokenId::Assign:
+		return "Assign";
+	case TokenId::NewLine:
+		return "NewLine";
+	case TokenId::Colon:
+		return "Colon";
+	case TokenId::Code:
+		return "Code";
+	case TokenId::Comment:
+		return "Comment";
+	case TokenId::End:
+		return "End";
+	case TokenId::Goto:
+		return "Goto";
+	case TokenId::Identifier:
+		return "Identifier";
+	case TokenId::None:
+		return "None";
+	case TokenId::Int:
+		return "Integer";
+	case TokenId::Float:
+		return "Float";
+	case TokenId::Condition:
+		return "Condition";
+	case TokenId::String:
+		return "String";
+	case TokenId::Whitespace:
+		return "Whitespace";
+	default:
+		return "?";
+	}
+}
+
+YackTokenReader::Iterator::Iterator(YackTokenReader &reader, int64 pos)
+	: _reader(&reader),
+	  _pos(pos) {
+	operator++();
+}
+
+YackTokenReader::Iterator::Iterator(const Iterator &it)
+	: _reader(it._reader), _pos(it._pos), _token(it._token) {
+}
+
+YackTokenReader::Iterator &YackTokenReader::Iterator::operator++() {
+	_reader->_stream->seek(_pos);
+	_reader->readToken(_token);
+	_pos = _reader->_stream->pos();
+	return *this;
+}
+
+YackTokenReader::Iterator YackTokenReader::Iterator::operator++(int) {
+	Iterator tmp(*this);
+	operator++();
+	return tmp;
+}
+
+Token &YackTokenReader::Iterator::operator*() {
+	return _token;
+}
+
+const Token &YackTokenReader::Iterator::operator*() const {
+	return _token;
+}
+
+Token *YackTokenReader::Iterator::operator->() {
+	return &_token;
+}
+
+void YackTokenReader::open(Common::SeekableReadStream *stream) {
+	_stream = stream;
+	_line = 1;
+}
+
+byte YackTokenReader::peek() {
+	byte b = _stream->readByte();
+	_stream->seek(-1, SEEK_CUR);
+	return b;
+}
+
+void YackTokenReader::ignore(int64 n, int delim) {
+	int64 i = 0;
+	byte b;
+	while ((i < n) && (b = _stream->readByte() != delim)) {
+		i++;
+	}
+}
+
+TokenId YackTokenReader::readCode() {
+	byte c;
+	byte previousChar = 0;
+	while ((c = peek()) != '\n' && c != '\0') {
+		ignore();
+		if (previousChar == ' ' && c == '[' && peek() != ' ') {
+			_stream->seek(-1, SEEK_CUR);
+			return TokenId::Code;
+		}
+		previousChar = c;
+	}
+	return TokenId::Code;
+}
+
+TokenId YackTokenReader::readDollar() {
+	char c;
+	while ((c = peek()) != '[' && c != ' ' && c != '\n' && c != '\0') {
+		ignore();
+	}
+	return TokenId::Dollar;
+}
+
+TokenId YackTokenReader::readCondition() {
+	while (peek() != ']') {
+		ignore();
+	}
+	ignore();
+	return TokenId::Condition;
+}
+
+TokenId YackTokenReader::readNumber() {
+	bool isFloat = false;
+	while (Common::isDigit(peek())) {
+		ignore();
+	}
+	if (peek() == '.') {
+		ignore();
+		isFloat = true;
+	}
+	while (Common::isDigit(peek())) {
+		ignore();
+	}
+	return isFloat ? TokenId::Float : TokenId::Int;
+}
+
+TokenId YackTokenReader::readComment() {
+	ignore(INT_MAX, '\n');
+	_stream->seek(_stream->pos() - 1);
+	return TokenId::Comment;
+}
+
+TokenId YackTokenReader::readString() {
+	ignore(INT_MAX, '\"');
+	return TokenId::String;
+}
+
+TokenId YackTokenReader::readIdentifier(char c) {
+	Common::String id;
+	id += c;
+	while (Common::isAlnum(peek()) || peek() == '_') {
+		c = _stream->readByte();
+		id += c;
+	}
+	if (id == "waitwhile") {
+		readCode();
+		return TokenId::WaitWhile;
+	}
+	return TokenId::Identifier;
+}
+
+TokenId YackTokenReader::readTokenId() {
+	char c;
+	_stream->read(&c, 1);
+	if (_stream->eos()) {
+		return TokenId::End;
+	}
+
+	switch (c) {
+	case '\0':
+		return TokenId::End;
+	case '\n':
+		_line++;
+		return TokenId::NewLine;
+	case '\t':
+	case ' ':
+		while (Common::isSpace(peek()) && peek() != '\n')
+			ignore();
+		return TokenId::Whitespace;
+	case '!':
+		return readCode();
+	case ':':
+		return TokenId::Colon;
+	case '$':
+		return readDollar();
+	case '[':
+		return readCondition();
+	case '=':
+		return TokenId::Assign;
+	case '\"':
+		return readString();
+	case '#':
+	case ';':
+		return readComment();
+	default:
+		if (c == '-' && peek() == '>') {
+			ignore();
+			return TokenId::Goto;
+		}
+		if (c == '-' || Common::isDigit(c)) {
+			return readNumber();
+		} else if (Common::isAlpha(c)) {
+			return readIdentifier(c);
+		}
+		error("unknown character: %c", c);
+		return TokenId::None;
+	}
+}
+
+bool YackTokenReader::readToken(Token &token) {
+	int64 start = _stream->pos();
+	int line = _line;
+	auto id = readTokenId();
+	while (id == TokenId::Whitespace || id == TokenId::Comment || id == TokenId::NewLine || id == TokenId::None) {
+		start = _stream->pos();
+		line = _line;
+		id = readTokenId();
+	}
+	int64 end = _stream->pos();
+	token.id = id;
+	token.start = start;
+	token.end = end;
+	token.line = line;
+	return true;
+}
+
+Common::String YackTokenReader::readText(int64 pos, int64 size) {
+	Common::String out;
+	_stream->seek(pos);
+	char c;
+	for (int i = 0; i < size; i++) {
+		c = _stream->readByte();
+		out += c;
+	}
+	return out;
+}
+
+Common::String YackTokenReader::readText(const Token &token) {
+	return readText(token.start, token.end - token.start);
+}
+
+YackTokenReader::iterator YackTokenReader::begin() {
+	return Iterator(*this, 0);
+}
+
+YackTokenReader::iterator YackTokenReader::end() {
+	int64 pos = _stream->size();
+	return Iterator(*this, pos);
+}
+
+bool YackParser::match(const std::initializer_list<TokenId> &ids) {
+	auto it = _it;
+	for (auto id : ids) {
+		if (it->id != id)
+			return false;
+		it++;
+	}
+	return true;
+}
+
+void YCodeCond::accept(YackVisitor &v) { v.visit(*this); }
+void YOnce::accept(YackVisitor &v) { v.visit(*this); }
+void YShowOnce::accept(YackVisitor &v) { v.visit(*this); }
+void YOnceEver::accept(YackVisitor &v) { v.visit(*this); }
+void YTempOnce::accept(YackVisitor &v) { v.visit(*this); }
+void YGoto::accept(YackVisitor &v) { v.visit(*this); }
+void YChoice::accept(YackVisitor &v) { v.visit(*this); }
+void YSay::accept(YackVisitor &v) { v.visit(*this); }
+void YPause::accept(YackVisitor &v) { v.visit(*this); }
+void YStatement::accept(YackVisitor &v) { v.visit(*this); }
+void YWaitWhile::accept(YackVisitor &v) { v.visit(*this); }
+void YAllowObjects::accept(YackVisitor &v) { v.visit(*this); }
+void YLimit::accept(YackVisitor &v) { v.visit(*this); }
+void YDialog::accept(YackVisitor &v) { v.visit(*this); }
+void YLabel::accept(YackVisitor &v) { v.visit(*this); }
+void YParrot::accept(YackVisitor &v) { v.visit(*this); }
+void YShutup::accept(YackVisitor &v) { v.visit(*this); }
+void YCodeExp::accept(YackVisitor &v) { v.visit(*this); }
+void YWaitFor::accept(YackVisitor &v) { v.visit(*this); }
+void YOverride::accept(YackVisitor &v) { v.visit(*this); }
+void YCompilationUnit::accept(YackVisitor &v) { v.visit(*this); }
+
+YLabel::YLabel(int line) { _line = line; }
+
+YLabel::~YLabel() {
+	for (size_t i = 0; i < _stmts.size(); i++) {
+		delete _stmts[i];
+	}
+}
+
+YCompilationUnit::~YCompilationUnit() {
+	for (size_t i = 0; i < _labels.size(); i++) {
+		delete _labels[i];
+	}
+}
+
+YLabel *YackParser::parseLabel() {
+	unique_ptr<YLabel> pLabel;
+	// :
+	_it++;
+	// label
+	pLabel.reset(new YLabel(_it->line));
+	pLabel->_name = _reader.readText(*_it++);
+	do {
+		if (match({TokenId::Colon}) || match({TokenId::End}))
+			break;
+		YStatement *pStatement = parseStatement();
+		pLabel->_stmts.push_back(pStatement);
+	} while (true);
+
+	return pLabel.release();
+}
+YStatement *YackParser::parseStatement() {
+	unique_ptr<YStatement> pStatement;
+	pStatement.reset(new YStatement());
+
+	// expression
+	pStatement->_exp.reset(parseExpression());
+	// conditions
+	while (match({TokenId::Condition})) {
+		pStatement->_conds.push_back(parseCondition());
+	}
+	return pStatement.release();
+}
+YCond *YackParser::parseCondition() {
+	auto text = _reader.readText(*_it);
+	auto conditionText = text.substr(1, text.size() - 2);
+	auto line = _it->line;
+	if (conditionText == "once") {
+		return new YOnce(line);
+	} else if (conditionText == "showonce") {
+		return new YShowOnce(line);
+	} else if (conditionText == "onceever") {
+		return new YOnceEver(line);
+	} else if (conditionText == "temponce") {
+		return new YTempOnce(line);
+	}
+	auto pCondition = new YCodeCond(line);
+	pCondition->_code = conditionText;
+	return pCondition;
+}
+YExp *YackParser::parseExpression() {
+	if (match({TokenId::Identifier, TokenId::Colon, TokenId::String}))
+		return parseSayExpression();
+	if (match({TokenId::WaitWhile}))
+		return parseWaitWhileExpression();
+	if (match({TokenId::Identifier}))
+		return parseInstructionExpression();
+	if (match({TokenId::Goto}))
+		return parseGotoExpression();
+	if (match({TokenId::Int}))
+		return parseChoiceExpression();
+	if (match({TokenId::Code}))
+		return parseCodeExpression();
+	return nullptr;
+}
+YSay *YackParser::parseSayExpression() {
+	auto actor = _reader.readText(*_it++);
+	_it++;
+	auto text = _reader.readText(*_it);
+	_it++;
+	auto pExp = new YSay();
+	pExp->_actor = actor;
+	pExp->_text = text.substr(1, text.size() - 2);
+	return pExp;
+}
+YExp *YackParser::parseWaitWhileExpression() {
+	auto waitwhile = _reader.readText(*_it++);
+	auto code = waitwhile.substr(10);
+	auto pExp = new YWaitWhile();
+	pExp->_cond = code;
+	return pExp;
+}
+YExp *YackParser::parseInstructionExpression() {
+	auto identifier = _reader.readText(*_it++);
+	if (identifier == "shutup") {
+		return new YShutup();
+	} else if (identifier == "pause") {
+		// pause number
+		auto time = atof(_reader.readText(*_it++).c_str());
+		auto pExp = new YPause();
+		pExp->_time = time;
+		return pExp;
+	} else if (identifier == "waitfor") {
+		// waitfor [actor]
+		auto pExp = new YWaitFor();
+		if (_it->id == TokenId::Identifier) {
+			auto actor = _reader.readText(*_it++);
+			pExp->_actor = actor;
+		}
+		return pExp;
+	} else if (identifier == "parrot") {
+		// parrot [active]
+		auto pExp = new YParrot();
+		if (_it->id == TokenId::Identifier) {
+			auto active = _reader.readText(*_it++);
+			pExp->_active = active == "yes";
+		}
+		return pExp;
+	} else if (identifier == "dialog") {
+		// dialog [actor]
+		auto pExp = new YDialog();
+		if (_it->id == TokenId::Identifier) {
+			auto actor = _reader.readText(*_it++);
+			pExp->_actor = actor;
+		}
+		return pExp;
+	} else if (identifier == "override") {
+		// override [node]
+		auto pExp = new YOverride();
+		if (_it->id == TokenId::Identifier) {
+			auto node = _reader.readText(*_it++);
+			pExp->_node = node;
+		}
+		return pExp;
+	} else if (identifier == "allowobjects") {
+		// allowobjects [allow]
+		auto pExp = new YAllowObjects();
+		if (_it->id == TokenId::Identifier) {
+			auto node = _reader.readText(*_it++);
+			pExp->_active = node == "YES";
+		}
+		return pExp;
+	} else if (identifier == "limit") {
+		// limit [number]
+		auto pExp = new YLimit();
+		if (_it->id == TokenId::Int) {
+			auto node = _reader.readText(*_it++);
+			pExp->_max = std::strtol(node.c_str(), nullptr, 10);
+		}
+		return pExp;
+	}
+	error("Unknown instruction: %s", identifier.c_str());
+}
+YGoto *YackParser::parseGotoExpression() {
+	_it++;
+	int line = _it->line;
+	auto name = _reader.readText(*_it++);
+	auto pExp = new YGoto(line);
+	pExp->_name = name;
+	return pExp;
+}
+YCodeExp *YackParser::parseCodeExpression() {
+	auto code = _reader.readText(*_it++);
+	auto pExp = new YCodeExp();
+	pExp->_code = code.substr(1);
+	return pExp;
+}
+YChoice *YackParser::parseChoiceExpression() {
+	auto number = atol(_reader.readText(*_it).c_str());
+	_it++;
+	Common::String text;
+	if (_it->id == TokenId::Dollar) {
+		text = _reader.readText(*_it);
+	} else {
+		text = _reader.readText(*_it);
+		text = text.substr(1, text.size() - 2);
+	}
+
+	_it++;
+	auto pExp = new YChoice();
+	pExp->_number = number;
+	pExp->_text = text;
+	pExp->_goto.reset(parseGotoExpression());
+	return pExp;
+}
+
+YCompilationUnit *YackParser::parse(Common::SeekableReadStream *stream) {
+	_reader.open(stream);
+	_it = _reader.begin();
+	auto pCu = unique_ptr<YCompilationUnit>();
+	pCu.reset(new YCompilationUnit());
+	while (!match({TokenId::End})) {
+		pCu->_labels.push_back(parseLabel());
+	}
+	return pCu.release();
+}
+
+} // namespace Twp
diff --git a/engines/twp/yack.h b/engines/twp/yack.h
new file mode 100644
index 00000000000..5bd4a1a2186
--- /dev/null
+++ b/engines/twp/yack.h
@@ -0,0 +1,456 @@
+/* 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 TWP_YACK_H
+#define TWP_YACK_H
+
+#include "common/array.h"
+#include "common/str.h"
+#include "common/stream.h"
+#include "common/debug.h"
+
+namespace Twp {
+
+template<typename T, class DL = Common::DefaultDeleter<T> >
+using unique_ptr = Common::ScopedPtr<T, DL>;
+
+enum class TokenId {
+	None,
+	NewLine,
+	Identifier,
+	WaitWhile,
+	Int,
+	Float,
+	Whitespace,
+	Colon,
+	Condition,
+	String,
+	Assign,
+	Comment,
+	Goto,
+	Code,
+	Dollar,
+	End
+};
+
+// enumeration that lists all errors that can occur
+enum YackError {
+	ERR_NONE,              // no error
+	ERR_INVALIDTOKEN,      // invalid token
+	ERR_STRINGEXPECTED,    // string expected
+	ERR_COLONEXPECTED,     // `:` expected
+	ERR_COMMAEXPECTED,     // `,` expected
+	ERR_BRACKETRIEXPECTED, // `]` expected
+	ERR_CURLYRIEXPECTED,   // `}` expected
+	ERR_QUOTEEXPECTED,     // `"` or `'` expected
+	ERR_EOC_EXPECTED,      // `*/` expected
+	ERR_EOFEXPECTED,       // EOF expected
+	ERR_EXPREXPECTED       // expr expected
+};
+
+class YackVisitor;
+class YackNode {
+public:
+	virtual ~YackNode() {}
+
+	virtual void accept(YackVisitor &v) = 0;
+};
+
+// Represents a condition
+class YCond : public YackNode {
+public:
+	virtual ~YCond() {}
+
+public:
+	int _line;
+};
+
+class YCodeCond : public YCond {
+public:
+	YCodeCond(int line) { _line = line; }
+	virtual ~YCodeCond() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _code;
+};
+
+class YOnce : public YCond {
+public:
+	YOnce(int line) { _line = line; }
+	virtual ~YOnce() {}
+	virtual void accept(YackVisitor &v) override;
+};
+
+class YShowOnce : public YCond {
+public:
+	YShowOnce(int line) { _line = line; }
+	virtual ~YShowOnce() {}
+	virtual void accept(YackVisitor &v) override;
+};
+
+class YOnceEver : public YCond {
+public:
+	YOnceEver(int line) { _line = line; }
+	virtual ~YOnceEver() {}
+	virtual void accept(YackVisitor &v) override;
+};
+
+class YTempOnce : public YCond {
+public:
+	YTempOnce(int line) { _line = line; }
+	virtual ~YTempOnce() {}
+	virtual void accept(YackVisitor &v) override;
+};
+
+// Expression
+class YExp : public YackNode {
+public:
+	YExp() {}
+	virtual ~YExp() {}
+};
+
+class YGoto : public YExp {
+public:
+	YGoto(int line) { _line = line; }
+	virtual ~YGoto() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _name;
+	int _line = 0;
+};
+
+class YCodeExp : public YExp {
+public:
+	virtual ~YCodeExp() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _code;
+};
+
+class YChoice : public YExp {
+public:
+	virtual ~YChoice() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	int _number = 0;
+	Common::String _text;
+	unique_ptr<YGoto> _goto;
+};
+
+class YSay : public YExp {
+public:
+	virtual ~YSay() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _actor;
+	Common::String _text;
+};
+
+class YPause : public YExp {
+public:
+	virtual ~YPause() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	int _time = 0;
+};
+
+class YParrot : public YExp {
+public:
+	virtual ~YParrot() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	bool _active = true;
+};
+
+class YDialog : public YExp {
+public:
+	virtual ~YDialog() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _actor;
+};
+
+class YOverride : public YExp {
+public:
+	virtual ~YOverride() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _node;
+};
+
+class YShutup : public YExp {
+public:
+	virtual ~YShutup() {}
+	virtual void accept(YackVisitor &v) override;
+};
+
+class YAllowObjects : public YExp {
+public:
+	virtual ~YAllowObjects() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	bool _active = true;
+};
+
+class YLimit : public YExp {
+public:
+	virtual ~YLimit() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	int _max = 8;
+};
+
+class YWaitWhile : public YExp {
+public:
+	virtual ~YWaitWhile() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _cond;
+};
+
+class YWaitFor : public YExp {
+public:
+	virtual ~YWaitFor() {}
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _actor;
+};
+
+//
+
+class YStatement : public YackNode {
+public:
+	virtual ~YStatement() override {}
+
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	unique_ptr<YExp> _exp;
+	Common::Array<YCond *> _conds;
+};
+
+class YLabel : public YackNode {
+public:
+	YLabel(int line);
+	virtual ~YLabel() override;
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::String _name;
+	Common::Array<YStatement *> _stmts;
+	int _line = 0;
+};
+
+class YCompilationUnit : public YackNode {
+public:
+	virtual ~YCompilationUnit() override;
+	virtual void accept(YackVisitor &v) override;
+
+public:
+	Common::Array<YLabel*> _labels;
+};
+
+class YackVisitor {
+public:
+	virtual ~YackVisitor() {}
+
+	virtual void visit(const YCompilationUnit &node) { defaultVisit(node); }
+	virtual void visit(const YStatement &node) { defaultVisit(node); }
+	virtual void visit(const YWaitFor &node) { defaultVisit(node); }
+	virtual void visit(const YWaitWhile &node) { defaultVisit(node); }
+	virtual void visit(const YLimit &node) { defaultVisit(node); }
+	virtual void visit(const YAllowObjects &node) { defaultVisit(node); }
+	virtual void visit(const YShutup &node) { defaultVisit(node); }
+	virtual void visit(const YOverride &node) { defaultVisit(node); }
+	virtual void visit(const YDialog &node) { defaultVisit(node); }
+	virtual void visit(const YParrot &node) { defaultVisit(node); }
+	virtual void visit(const YPause &node) { defaultVisit(node); }
+	virtual void visit(const YSay &node) { defaultVisit(node); }
+	virtual void visit(const YChoice &node) { defaultVisit(node); }
+	virtual void visit(const YCodeExp &node) { defaultVisit(node); }
+	virtual void visit(const YGoto &node) { defaultVisit(node); }
+	virtual void visit(const YTempOnce &node) { defaultVisit(node); }
+	virtual void visit(const YOnceEver &node) { defaultVisit(node); }
+	virtual void visit(const YShowOnce &node) { defaultVisit(node); }
+	virtual void visit(const YOnce &node) { defaultVisit(node); }
+	virtual void visit(const YCodeCond &node) { defaultVisit(node); }
+	virtual void visit(const YLabel &node) { defaultVisit(node); }
+
+	virtual void defaultVisit(const YackNode &node) {}
+};
+
+struct Token {
+	TokenId id;
+	int64 start;
+	int64 end;
+	int line;
+
+	Common::String toString() const;
+};
+
+class YackTokenReader {
+public:
+	class Iterator {
+	public:
+		using value_type = Token;
+		using difference_type = ptrdiff_t;
+		using pointer = Token *;
+		using reference = Token &;
+
+	private:
+		YackTokenReader *_reader = nullptr;
+		int64 _pos = 0;
+		Token _token;
+
+	public:
+		Iterator() {}
+		Iterator(YackTokenReader &reader, int64 pos);
+		Iterator(const Iterator &it);
+		Iterator &operator++();
+		Iterator operator++(int);
+
+		Iterator &operator=(const Iterator &rhs) {
+			_pos = rhs._pos;
+			_token = rhs._token;
+			_reader = rhs._reader;
+			return *this;
+		}
+		bool operator==(const Iterator &rhs) const { return _pos == rhs._pos; }
+		bool operator!=(const Iterator &rhs) const { return _pos != rhs._pos; }
+		Token &operator*();
+		const Token &operator*() const;
+		Token *operator->();
+	};
+
+	using iterator = Iterator;
+
+public:
+	void open(Common::SeekableReadStream *stream);
+
+	iterator begin();
+	iterator end();
+
+private:
+	bool readToken(Token &token);
+	Common::String readText(const Token &token);
+	Common::String readText(int64 pos, int64 size);
+	TokenId readTokenId();
+	TokenId readCode();
+	TokenId readCondition();
+	TokenId readDollar();
+	TokenId readNumber();
+	TokenId readComment();
+	TokenId readString();
+	TokenId readIdentifier(char c);
+	byte peek();
+	void ignore(int64 n = 1, int delim = EOF);
+
+private:
+	Common::SeekableReadStream *_stream = nullptr;
+	int _line = 0;
+
+	friend class YackParser;
+};
+
+class YackParser {
+public:
+	YackParser() {}
+	YCompilationUnit* parse(Common::SeekableReadStream *stream);
+
+private:
+	bool match(const std::initializer_list<TokenId> &ids);
+	YLabel* parseLabel();
+	YStatement *parseStatement();
+	YCond *parseCondition();
+	YExp *parseExpression();
+	YSay *parseSayExpression();
+	YExp *parseWaitWhileExpression();
+	YExp *parseInstructionExpression();
+	YGoto *parseGotoExpression();
+	YCodeExp *parseCodeExpression();
+	YChoice *parseChoiceExpression();
+
+private:
+	YackTokenReader _reader;
+	YackTokenReader::iterator _it;
+};
+
+class YackDump : public YackVisitor {
+public:
+	virtual ~YackDump() {}
+
+	virtual void visit(const YCompilationUnit &node) {
+		debug("CompilationUnit:");
+		for (const auto &cond : node._labels) {
+			cond->accept(*this);
+		}
+	}
+	virtual void visit(const YLabel &node) {
+		debug("Label: %s [%d]", node._name.c_str(), node._line);
+		for (const auto &stmt : node._stmts) {
+			stmt->accept(*this);
+		}
+	}
+	virtual void visit(const YStatement &node) {
+		debug("Statement:");
+		for (const auto &cond : node._conds) {
+			cond->accept(*this);
+		}
+		node._exp->accept(*this);
+	}
+	virtual void visit(const YWaitFor &node) { debug("WaifFor %s", node._actor.c_str()); }
+	virtual void visit(const YWaitWhile &node) { debug("WaitWhile %s", node._cond.c_str()); }
+	virtual void visit(const YLimit &node) { debug("Limit: %d", node._max); }
+	virtual void visit(const YAllowObjects &node) { debug("AllowObjects"); }
+	virtual void visit(const YShutup &node) { debug("Shutup"); }
+	virtual void visit(const YOverride &node) { defaultVisit(node); }
+	virtual void visit(const YDialog &node) { debug("Dialog: %s", node._actor.c_str()); }
+	virtual void visit(const YParrot &node) { debug("Parrot: %s", node._active ? "YES" : "NO"); }
+	virtual void visit(const YPause &node) { debug("Pause: %d", node._time); }
+	virtual void visit(const YSay &node) { debug("Say: actor: %s, text = %s", node._actor.c_str(), node._text.c_str()); }
+	virtual void visit(const YChoice &node) { debug("Choice %d: %s -> %s", node._number, node._text.c_str(), node._goto->_name.c_str()); }
+	virtual void visit(const YCodeExp &node) { debug("Code: %s", node._code.c_str()); }
+	virtual void visit(const YGoto &node) { debug("Goto: %s", node._name.c_str()); }
+	virtual void visit(const YTempOnce &node) { debug("TempOnce"); }
+	virtual void visit(const YOnceEver &node) { debug("OnceEver"); }
+	virtual void visit(const YShowOnce &node) { debug("ShowOnce"); }
+	virtual void visit(const YOnce &node) { debug("Once"); }
+	virtual void visit(const YCodeCond &node) { debug("Cond: %s", node._code.c_str()); }
+
+	virtual void defaultVisit(const YackNode &node) {}
+};
+
+} // namespace Twp
+
+#endif


Commit: f3ac588ba20552454951eeb42224a8b18a887610
    https://github.com/scummvm/scummvm/commit/f3ac588ba20552454951eeb42224a8b18a887610
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix breakwhilecutscene

Changed paths:
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp


diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index f3e5594dad1..cbfbb2a343b 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -344,7 +344,6 @@ ThreadBase *sqthread(int id) {
 		}
 	}
 
-	// let threads = g_engine->_threads;
 	for (int i = 0; i < g_engine->_threads.size(); i++) {
 		ThreadBase *t = g_engine->_threads[i];
 		if (t->getId() == id) {
@@ -355,6 +354,12 @@ ThreadBase *sqthread(int id) {
 }
 
 ThreadBase *sqthread(HSQUIRRELVM v) {
+	if (g_engine->_cutscene) {
+		if (g_engine->_cutscene->getThread() == v) {
+			return g_engine->_cutscene;
+		}
+	}
+
 	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](ThreadBase *t) {
 		return t->getThread() == v;
 	});
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 379567926a9..ed6c63d56f0 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -211,7 +211,7 @@ static SQInteger breakwhilecamera(HSQUIRRELVM v) {
 // It is an error to call breakwhilecutscene in a function that was not started with startthread.
 static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 	return breakwhilecond(
-		v, [] { return g_engine->_cutscene == nullptr; }, "breakwhilecutscene()");
+		v, [] { return g_engine->_cutscene != nullptr; }, "breakwhilecutscene()");
 }
 
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index caf514cb9b3..15a0dd357fa 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -53,6 +53,7 @@ Thread::Thread(int id) {
 }
 
 Thread::~Thread() {
+	debug("delete thread %d, %s, global: %s", _id, _name.c_str(), _global?"yes":"no");
 	HSQUIRRELVM v = g_engine->getVm();
 	for (int i = 0; i < _args.size(); i++) {
 		sq_release(v, &_args[i]);


Commit: 76797f8c634580c449862fc59526671e5afb92f9
    https://github.com/scummvm/scummvm/commit/76797f8c634580c449862fc59526671e5afb92f9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add dialog

Changed paths:
  A engines/twp/dialog.cpp
  A engines/twp/dialog.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/objlib.cpp
    engines/twp/rectf.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/yack.cpp
    engines/twp/yack.h


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
new file mode 100644
index 00000000000..e0224ad7396
--- /dev/null
+++ b/engines/twp/dialog.cpp
@@ -0,0 +1,371 @@
+/* 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 "twp/twp.h"
+#include "twp/util.h"
+#include "twp/squtil.h"
+#include "twp/dialog.h"
+#include "common/debug.h"
+
+namespace Twp {
+
+CondStateVisitor::CondStateVisitor(Dialog *dlg, DialogSelMode mode) : _dlg(dlg), _mode(mode) {
+}
+
+DialogConditionState CondStateVisitor::createState(int line, DialogConditionMode mode) {
+	return DialogConditionState{.mode = mode, .line = line, .dialog = _dlg->_context.dialogName, .actorKey = _dlg->_context.actor};
+}
+
+void CondStateVisitor::visit(const YOnce &node) {
+	if (_mode == DialogSelMode::Choose)
+		_dlg->_states.push_back(createState(node._line, DialogConditionMode::Once));
+}
+
+void CondStateVisitor::visit(const YShowOnce &node) {
+	if (_mode == DialogSelMode::Show)
+		_dlg->_states.push_back(createState(node._line, DialogConditionMode::ShowOnce));
+}
+
+void CondStateVisitor::visit(const YOnceEver &node) {
+	if (_mode == DialogSelMode::Choose)
+		_dlg->_states.push_back(createState(node._line, DialogConditionMode::OnceEver));
+}
+
+void CondStateVisitor::visit(const YTempOnce &node) {
+	if (_mode == DialogSelMode::Show)
+		_dlg->_states.push_back(createState(node._line, DialogConditionMode::TempOnce));
+}
+
+ExpVisitor::ExpVisitor(Dialog *dialog) : _dialog(dialog) {}
+ExpVisitor::~ExpVisitor() {}
+
+void ExpVisitor::visit(const YCodeExp &node) {
+	debug("execute code {node.code}");
+	sqexec(g_engine->getVm(), node._code.c_str(), "dialog");
+}
+
+void ExpVisitor::visit(const YGoto &node) {
+	debug("execute goto {node.name}");
+	_dialog->selectLabel(node._line, node._name);
+}
+
+void ExpVisitor::visit(const YShutup &node) {
+	debug("shutup");
+	_dialog->_tgt->shutup();
+}
+
+void ExpVisitor::visit(const YPause &node) {
+	debug("pause %d", node._time);
+	_dialog->_action.reset(_dialog->_tgt->pause(node._time));
+}
+
+void ExpVisitor::visit(const YWaitFor &node) {
+	debug("TODO: waitFor {node.actor}");
+}
+
+void ExpVisitor::visit(const YParrot &node) {
+	_dialog->_context.parrot = node._active;
+}
+
+void ExpVisitor::visit(const YDialog &node) {
+	_dialog->_context.actor = node._actor;
+}
+
+void ExpVisitor::visit(const YOverride &node) {
+	warning("TODO: override %s", node._node.c_str());
+}
+
+void ExpVisitor::visit(const YAllowObjects &node) {
+	warning("TODO: allowObjects");
+}
+
+void ExpVisitor::visit(const YWaitWhile &node) {
+	debug("wait while");
+	_dialog->_action.reset(_dialog->_tgt->waitWhile(node._cond));
+}
+
+void ExpVisitor::visit(const YLimit &node) {
+	debug("limit");
+	_dialog->_context.limit = node._max;
+}
+
+void ExpVisitor::visit(const YSay &node) {
+	_dialog->_action.reset(_dialog->_tgt->say(node._actor, node._text));
+}
+
+CondVisitor::CondVisitor(Dialog *dialog) : _dialog(dialog) {}
+CondVisitor::~CondVisitor() {}
+
+void CondVisitor::visit(const YCodeCond &node) {
+	_accepted = _dialog->isCond(node._code);
+}
+
+void CondVisitor::visit(const YOnce &node) {
+	_accepted = _dialog->isOnce(node._line);
+}
+
+void CondVisitor::visit(const YShowOnce &node) {
+	_accepted = _dialog->isShowOnce(node._line);
+}
+
+void CondVisitor::visit(const YOnceEver &node) {
+	_accepted = _dialog->isOnceEver(node._line);
+}
+
+void CondVisitor::visit(const YTempOnce &node) {
+	_accepted = _dialog->isTempOnce(node._line);
+}
+
+DialogSlot::DialogSlot() : Node("DialogSlot") {}
+
+Dialog::Dialog() : Node("Dialog") {}
+Dialog::~Dialog() {}
+
+void Dialog::start(const Common::String &actor, const Common::String &name, const Common::String &node) {
+	_context = DialogContext{.actor = actor, .dialogName = name, .parrot = true, .limit = MAXCHOICES};
+	// keepIf(self.states, proc(x: DialogConditionState): bool = x.mode != TempOnce);
+	Common::String path = name + ".byack";
+	debug("start dialog %s", path.c_str());
+	GGPackEntryReader reader;
+	reader.open(g_engine->_pack, path);
+	YackParser parser;
+	_cu.reset(parser.parse(&reader));
+	selectLabel(0, node);
+	update(0);
+}
+
+void Dialog::update(float dt) {
+	switch (_state) {
+	case DialogState::None:
+		break;
+	case DialogState::Active:
+		running(dt);
+		break;
+	case DialogState::WaitingForChoice: {
+		Color color = _tgt->actorColor(_context.actor);
+		Color colorHover = _tgt->actorColorHover(_context.actor);
+		for (size_t j = 0; j < MAXDIALOGSLOTS; j++) {
+			DialogSlot *slot = &_slots[j];
+			if (slot) {
+				Rectf rect = Rectf::fromPosAndSize(slot->getPos() - Math::Vector2d(0.f, slot->_text.getBounds().getY()), slot->_text.getBounds());
+				bool over = slot && rect.contains(_mousePos);
+				if (rect.r.w > (SCREEN_WIDTH - SLOTMARGIN)) {
+					if (over) {
+						if ((rect.r.w + slot->getPos().getX()) > (SCREEN_WIDTH - SLOTMARGIN)) {
+							slot->setPos(Math::Vector2d(slot->getPos().getX() - SLIDINGSPEED * dt, slot->getPos().getY()));
+							if ((rect.r.w + slot->getPos().getX()) < (SCREEN_WIDTH - SLOTMARGIN)) {
+								slot->setPos(Math::Vector2d((SCREEN_WIDTH - SLOTMARGIN) - rect.r.w, slot->getPos().getY()));
+							}
+						}
+					} else if (slot->getPos().getX() < SLOTMARGIN) {
+						slot->setPos(Math::Vector2d(slot->getPos().getX() + SLIDINGSPEED * dt, slot->getPos().getY()));
+						if (slot->getPos().getX() > SLOTMARGIN) {
+							slot->setPos(Math::Vector2d(SLOTMARGIN, slot->getPos().getY()));
+						}
+					}
+				}
+				slot->_text.setColor(over ? colorHover : color);
+				// if (over && mbLeft in mouseBtns())
+				//   choose(i);
+			}
+			break;
+		}
+	}
+	}
+}
+
+bool Dialog::isOnce(int line) const {
+	for (size_t i = 0; i < _states.size(); i++) {
+		const DialogConditionState &state = _states[i];
+		if (state.mode == Once && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
+			debug("isOnce %d: false", line);
+			return false;
+		}
+	}
+	debug("isOnce %d: true", line);
+	return true;
+}
+
+bool Dialog::isShowOnce(int line) const {
+	for (size_t i = 0; i < _states.size(); i++) {
+		const DialogConditionState &state = _states[i];
+		if (state.mode == ShowOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
+			debug("isShowOnce %d: false", line);
+			return false;
+		}
+	}
+	debug("isShowOnce %d: true", line);
+	return true;
+}
+
+bool Dialog::isOnceEver(int line) const {
+	for (size_t i = 0; i < _states.size(); i++) {
+		const DialogConditionState &state = _states[i];
+		if (state.mode == OnceEver && state.dialog == _context.dialogName && state.line == line) {
+			debug("isOnceEver %d: false", line);
+			return false;
+		}
+	}
+	debug("isOnceEver %d: true", line);
+	return true;
+}
+
+bool Dialog::isTempOnce(int line) const {
+	for (size_t i = 0; i < _states.size(); i++) {
+		const DialogConditionState &state = _states[i];
+		if (state.mode == TempOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
+			debug("isTempOnce %d: false", line);
+			return false;
+		}
+	}
+	debug("isTempOnce %d: true", line);
+	return true;
+}
+
+bool Dialog::isCond(const Common::String &cond) const {
+	bool result = _tgt->execCond(cond);
+	debug("isCond '%s': %s", cond.c_str(), result ? "TRUE" : "FALSE");
+	return result;
+}
+
+YLabel *Dialog::label(int line, const Common::String &name) const {
+	for (size_t i = 0; i < _cu->_labels.size(); i++) {
+		YLabel *label = _cu->_labels[i];
+		if ((label->_name == name) && (label->_line >= line)) {
+			return label;
+		}
+	}
+	line = 0;
+	for (size_t i = 0; i < _cu->_labels.size(); i++) {
+		YLabel *label = _cu->_labels[i];
+		if ((label->_name == name) && (label->_line >= line)) {
+			return label;
+		}
+	}
+	return nullptr;
+}
+
+void Dialog::selectLabel(int line, const Common::String &name) {
+	debug("select label %s", name.c_str());
+	_lbl = label(line, name);
+	_currentStatement = 0;
+	clearSlots();
+	_state = _lbl ? None : Active;
+}
+
+void Dialog::gotoNextLabel() {
+	if (_lbl) {
+		int i = Twp::find(_cu->_labels, _lbl);
+		if ((i != -1) && (i != _cu->_labels.size() - 1)) {
+			YLabel *label = _cu->_labels[i + 1];
+			selectLabel(label->_line, label->_name);
+		} else {
+			_state = None;
+		}
+	}
+}
+
+void Dialog::updateChoiceStates() {
+	_state = WaitingForChoice;
+	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
+		DialogSlot *slot = &_slots[i];
+		if (slot->_isValid) {
+			for (size_t j = 0; j < slot->_stmt->_conds.size(); j++) {
+				YCond *cond = slot->_stmt->_conds[i];
+				CondStateVisitor v(this, DialogSelMode::Show);
+				cond->accept(v);
+			}
+		}
+	}
+}
+
+void Dialog::run(YStatement *stmt) {
+	if (acceptConditions(stmt)) {
+		ExpVisitor visitor(this);
+		stmt->_exp->accept(visitor);
+		IsGoto isGoto;
+		stmt->_exp->accept(isGoto);
+		if (isGoto._isGoto)
+			return;
+	}
+	_currentStatement++;
+}
+
+bool Dialog::acceptConditions(YStatement *stmt) {
+	CondVisitor vis(this);
+	for (size_t i = 0; i < stmt->_conds.size(); i++) {
+		YCond *cond = stmt->_conds[i];
+		cond->accept(vis);
+		if (!vis._accepted) {
+			return false;
+		}
+	}
+	return true;
+}
+void Dialog::running(float dt) {}
+
+static Common::String remove(const Common::String &txt, char startC, char endC) {
+	if (txt[0] == startC) {
+		uint32 i = txt.find(endC);
+		if (i != Common::String::npos) {
+			return txt.substr(i);
+		}
+	}
+	return txt;
+}
+
+static Common::String text(const Common::String &txt) {
+	Common::String result = g_engine->getTextDb().getText(txt);
+	result = remove(result, '(', ')');
+	result = remove(result, '{', '}');
+	return result;
+}
+
+void Dialog::addSlot(YStatement *stmt) {
+	YChoice *choice = (YChoice *)stmt->_exp.get();
+	if ((!_slots[choice->_number - 1]._isValid) && (numSlots() < _context.limit)) {
+		DialogSlot *slot = &_slots[choice->_number - 1];
+		slot->_text.setText(Common::String::format("● %s", text(choice->_text).c_str()));
+		slot->_stmt = stmt;
+		slot->_dlg = this;
+		slot->setPos(Math::Vector2d(SLOTMARGIN, SLOTMARGIN + slot->_text.getBounds().getY() * (MAXCHOICES - numSlots())));
+	}
+}
+
+int Dialog::numSlots() const {
+	int num = 0;
+	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
+		if (_slots[i]._isValid)
+			num++;
+	}
+	return num;
+}
+
+void Dialog::clearSlots() {
+	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
+		_slots[i]._isValid = false;
+	}
+}
+
+void Dialog::drawCore(Math::Matrix4 trsf) {
+}
+
+} // namespace Twp
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
new file mode 100644
index 00000000000..7cb411e285c
--- /dev/null
+++ b/engines/twp/dialog.h
@@ -0,0 +1,212 @@
+/* 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 TWP_DIALOG_H
+#define TWP_DIALOG_H
+
+#include "common/array.h"
+#include "common/str.h"
+#include "twp/yack.h"
+#include "twp/scenegraph.h"
+#include "twp/font.h"
+#include "twp/motor.h"
+
+#define MAXDIALOGSLOTS 9
+#define MAXCHOICES 6
+#define SLIDINGSPEED 25.f
+#define SLOTMARGIN 8.f
+
+namespace Twp {
+
+class Dialog;
+class DialogSlot : public Node {
+public:
+	DialogSlot();
+	virtual ~DialogSlot() override {}
+
+public:
+	bool _isValid = false;
+	Text _text;
+	YStatement *_stmt = nullptr;
+	Dialog *_dlg = nullptr;
+};
+
+struct DialogContext {
+	Common::String actor;
+	Common::String dialogName;
+	bool parrot;
+	int limit;
+};
+
+enum DialogState {
+	None,
+	Active,
+	WaitingForChoice
+};
+
+enum DialogConditionMode {
+	Once,
+	ShowOnce,
+	OnceEver,
+	ShowOnceEver,
+	TempOnce
+};
+
+enum DialogSelMode {
+	Choose,
+	Show
+};
+
+struct DialogConditionState {
+	DialogConditionMode mode;
+	Common::String actorKey, dialog;
+	int line;
+};
+
+class DialogTarget {
+public:
+	virtual ~DialogTarget() {}
+
+	virtual Color actorColor(const Common::String &actor) = 0;
+	virtual Color actorColorHover(const Common::String &actor) = 0;
+	virtual Motor *say(const Common::String &actor, const Common::String &text) = 0;
+	virtual Motor *waitWhile(const Common::String &cond) = 0;
+	virtual void shutup() = 0;
+	virtual Motor *pause(float time) = 0;
+	virtual bool execCond(const Common::String &cond) = 0;
+};
+
+class CondStateVisitor : public YackVisitor {
+public:
+	CondStateVisitor(Dialog *dlg, DialogSelMode mode);
+	DialogConditionState createState(int line, DialogConditionMode mode);
+
+private:
+	void visit(const YOnce &node) override;
+	void visit(const YShowOnce &node) override;
+	void visit(const YOnceEver &node) override;
+	void visit(const YTempOnce &node) override;
+
+private:
+	DialogSelMode _mode;
+	Dialog *_dlg = nullptr;
+};
+
+class IsGoto : public YackVisitor {
+public:
+	virtual ~IsGoto() override {}
+	void visit(const YGoto &node) override { _isGoto = true; }
+
+public:
+	bool _isGoto = false;
+};
+
+class ExpVisitor : public YackVisitor {
+public:
+	ExpVisitor(Dialog *dialog);
+	virtual ~ExpVisitor() override;
+
+private:
+	void visit(const YCodeExp &node) override;
+	void visit(const YGoto &node) override;
+	void visit(const YSay &node) override;
+	void visit(const YPause &node) override;
+	void visit(const YParrot &node) override;
+	void visit(const YDialog &node) override;
+	void visit(const YOverride &node) override;
+	void visit(const YAllowObjects &node) override;
+	void visit(const YLimit &node) override;
+	void visit(const YWaitWhile &node) override;
+	void visit(const YWaitFor &node) override;
+	void visit(const YShutup &node) override;
+
+private:
+	Dialog *_dialog = nullptr;
+};
+
+class CondVisitor : public YackVisitor {
+public:
+	CondVisitor(Dialog *dialog);
+	virtual ~CondVisitor() override;
+
+private:
+	void visit(const YCodeCond &node) override;
+	void visit(const YOnce &node) override;
+	void visit(const YShowOnce &node) override;
+	void visit(const YOnceEver &node) override;
+	void visit(const YTempOnce &node) override;
+
+public:
+	bool _accepted = true;
+
+private:
+	Dialog *_dialog = nullptr;
+};
+
+class Dialog : public Node {
+public:
+	Dialog();
+	virtual ~Dialog() override;
+
+	DialogState getState() const { return _state; }
+
+	void start(const Common::String &actor, const Common::String &name, const Common::String &node);
+	void selectLabel(int line, const Common::String &name);
+	bool isOnce(int line) const;
+	bool isShowOnce(int line) const;
+	bool isOnceEver(int line) const;
+	bool isTempOnce(int line) const;
+	bool isCond(const Common::String &cond) const;
+
+private:
+	YLabel *label(int line, const Common::String &name) const;
+	void gotoNextLabel();
+	bool choicesReady() const { return numSlots() > 0; }
+	void updateChoiceStates();
+	void run(YStatement *stmt);
+	bool acceptConditions(YStatement *stmt);
+	void running(float dt);
+
+	void addSlot(YStatement *stmt);
+	int numSlots() const;
+	void clearSlots();
+
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+	void update(float dt);
+
+public:
+	Common::Array<DialogConditionState> _states;
+	DialogContext _context;
+	unique_ptr<DialogTarget> _tgt;
+	unique_ptr<Motor> _action;
+
+private:
+	DialogState _state = DialogState::None;
+	int _currentStatement = 0;
+	unique_ptr<YCompilationUnit> _cu;
+	YLabel *_lbl = nullptr;
+	DialogSlot _slots[MAXDIALOGSLOTS];
+	Math::Vector2d _mousePos;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index ad5109ede79..f06a5387564 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -355,9 +355,8 @@ static SQInteger incutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger indialog(HSQUIRRELVM v) {
-	// TODO: indialog
-	warning("indialog not implemented");
-	return 0;
+	sqpush(v, g_engine->_dialog.getState() != DialogState::None);
+	return 1;
 }
 
 static SQInteger integer(HSQUIRRELVM v) {
@@ -627,8 +626,19 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 }
 
 static SQInteger startDialog(HSQUIRRELVM v) {
-	// TODO: startDialog
-	warning("startDialog not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	Common::String dialog;
+	if (SQ_FAILED(sqget(v, 2, dialog)))
+		return sq_throwerror(v, "failed to get dialog");
+
+	Common::String node = "start";
+	if (nArgs == 3) {
+		if (SQ_FAILED(sqget(v, 3, node))) {
+			return sq_throwerror(v, "failed to get node");
+		}
+	}
+	Common::String actor = g_engine->_actor ? g_engine->_actor->_key : "";
+	g_engine->_dialog.start(actor, dialog, node);
 	return 0;
 }
 
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index a1cdb665252..c75d8da6568 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -53,6 +53,7 @@ MODULE_OBJS = \
 	util.o \
 	motor.o \
 	yack.o \
+	dialog.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 7800da7ebbb..c705b6819ac 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -373,7 +373,7 @@ static SQInteger objectHidden(HSQUIRRELVM v) {
 	if (obj) {
 		int hidden = 0;
 		sqget(v, 3, hidden);
-		debug("Sets object visible %s to %s", obj->_name.c_str(), hidden == 0 ? "true" : "false");
+		debug("Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(),hidden == 0 ? "true" : "false");
 		obj->_node->setVisible(hidden == 0);
 	}
 	return 0;
diff --git a/engines/twp/rectf.h b/engines/twp/rectf.h
index 8eb303eabf7..dd1f16bc544 100644
--- a/engines/twp/rectf.h
+++ b/engines/twp/rectf.h
@@ -51,6 +51,10 @@ public:
 	inline float right() { return r.x + r.w; }
 	inline float top() { return r.y + r.h; }
 	inline float bottom() { return r.y; }
+
+	bool contains(Math::Vector2d pos) {
+		return pos.getX() >= r.x && pos.getX() <= (r.x + r.w) && pos.getY() >= r.y && pos.getY() <= r.y + r.h;
+	}
 };
 } // namespace Twp
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index ac0f9fe84e7..385eeffc9fa 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -48,9 +48,6 @@
 
 namespace Twp {
 
-#define SCREEN_WIDTH 1280
-#define SCREEN_HEIGHT 720
-
 TwpEngine *g_engine;
 
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
@@ -60,6 +57,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	g_engine = this;
 	sq_resetobject(&_defaultObj);
 	_screenScene.addChild(&_inputState);
+	_screenScene.addChild(&_dialog);
 }
 
 TwpEngine::~TwpEngine() {
@@ -233,18 +231,26 @@ Common::Error TwpEngine::run() {
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
+	// GGPackEntryReader reader;
+	// if (reader.open(_pack, "TronDialogs.byack")) {
+	// 	YackParser parser;
+	// 	YackTokenReader ytr;
+	// 	ytr.open(&reader);
+
+	// 	for (auto it=ytr.begin();it!=ytr.end();it++) {
+	// 		Common::String s = ytr.readText(*it);
+	// 		debug("%s [%d, %d] %s", it->toString().c_str(), it->start, it->end, s.c_str());
+	// 	}
+
+	// 	unique_ptr<YCompilationUnit> cu(parser.parse(&reader));
+	// 	YackDump dump;
+	// 	cu->accept(dump);
+	// }
+
 	HSQUIRRELVM v = _vm.get();
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
-	GGPackEntryReader reader;
-	if (reader.open(_pack, "Opening.byack")) {
-		YackParser parser;
-		unique_ptr<YCompilationUnit> cu(parser.parse(&reader));
-		YackDump dump;
-		cu->accept(dump);
-	}
-
 	// const SQChar *code = "cameraInRoom(StartScreen)";
 	const SQChar *code = "start(1)";
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index a9d169a3ed2..27455b75bdd 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -40,6 +40,10 @@
 #include "twp/prefs.h"
 #include "twp/tsv.h"
 #include "twp/scenegraph.h"
+#include "twp/dialog.h"
+
+#define SCREEN_WIDTH 1280
+#define SCREEN_HEIGHT 720
 
 namespace Twp {
 
@@ -51,6 +55,7 @@ class Scene;
 class Room;
 class InputState;
 class Object;
+class Dialog;
 struct TwpGameDescription;
 
 class TwpEngine : public Engine {
@@ -164,6 +169,7 @@ public:
 	InputState _inputState;
 	Camera _camera;
 	TextDb _textDb;
+	Dialog _dialog;
 	struct Cursor {
 		Math::Vector2d pos;
 		bool leftDown = false;
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 0ec95593aef..8e48afeb692 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -24,35 +24,35 @@
 
 namespace Twp {
 
-Common::String Token::toString() const {
+Common::String YackToken::toString() const {
 	switch (id) {
-	case TokenId::Assign:
+	case YackTokenId::Assign:
 		return "Assign";
-	case TokenId::NewLine:
+	case YackTokenId::NewLine:
 		return "NewLine";
-	case TokenId::Colon:
+	case YackTokenId::Colon:
 		return "Colon";
-	case TokenId::Code:
+	case YackTokenId::Code:
 		return "Code";
-	case TokenId::Comment:
+	case YackTokenId::Comment:
 		return "Comment";
-	case TokenId::End:
+	case YackTokenId::End:
 		return "End";
-	case TokenId::Goto:
+	case YackTokenId::Goto:
 		return "Goto";
-	case TokenId::Identifier:
+	case YackTokenId::Identifier:
 		return "Identifier";
-	case TokenId::None:
+	case YackTokenId::None:
 		return "None";
-	case TokenId::Int:
+	case YackTokenId::Int:
 		return "Integer";
-	case TokenId::Float:
+	case YackTokenId::Float:
 		return "Float";
-	case TokenId::Condition:
+	case YackTokenId::Condition:
 		return "Condition";
-	case TokenId::String:
+	case YackTokenId::String:
 		return "String";
-	case TokenId::Whitespace:
+	case YackTokenId::Whitespace:
 		return "Whitespace";
 	default:
 		return "?";
@@ -66,12 +66,12 @@ YackTokenReader::Iterator::Iterator(YackTokenReader &reader, int64 pos)
 }
 
 YackTokenReader::Iterator::Iterator(const Iterator &it)
-	: _reader(it._reader), _pos(it._pos), _token(it._token) {
+	: _reader(it._reader), _pos(it._pos), _YackToken(it._YackToken) {
 }
 
 YackTokenReader::Iterator &YackTokenReader::Iterator::operator++() {
 	_reader->_stream->seek(_pos);
-	_reader->readToken(_token);
+	_reader->readYackToken(_YackToken);
 	_pos = _reader->_stream->pos();
 	return *this;
 }
@@ -82,16 +82,16 @@ YackTokenReader::Iterator YackTokenReader::Iterator::operator++(int) {
 	return tmp;
 }
 
-Token &YackTokenReader::Iterator::operator*() {
-	return _token;
+YackToken &YackTokenReader::Iterator::operator*() {
+	return _YackToken;
 }
 
-const Token &YackTokenReader::Iterator::operator*() const {
-	return _token;
+const YackToken &YackTokenReader::Iterator::operator*() const {
+	return _YackToken;
 }
 
-Token *YackTokenReader::Iterator::operator->() {
-	return &_token;
+YackToken *YackTokenReader::Iterator::operator->() {
+	return &_YackToken;
 }
 
 void YackTokenReader::open(Common::SeekableReadStream *stream) {
@@ -113,37 +113,37 @@ void YackTokenReader::ignore(int64 n, int delim) {
 	}
 }
 
-TokenId YackTokenReader::readCode() {
+YackTokenId YackTokenReader::readCode() {
 	byte c;
 	byte previousChar = 0;
 	while ((c = peek()) != '\n' && c != '\0') {
 		ignore();
 		if (previousChar == ' ' && c == '[' && peek() != ' ') {
 			_stream->seek(-1, SEEK_CUR);
-			return TokenId::Code;
+			return YackTokenId::Code;
 		}
 		previousChar = c;
 	}
-	return TokenId::Code;
+	return YackTokenId::Code;
 }
 
-TokenId YackTokenReader::readDollar() {
+YackTokenId YackTokenReader::readDollar() {
 	char c;
 	while ((c = peek()) != '[' && c != ' ' && c != '\n' && c != '\0') {
 		ignore();
 	}
-	return TokenId::Dollar;
+	return YackTokenId::Dollar;
 }
 
-TokenId YackTokenReader::readCondition() {
+YackTokenId YackTokenReader::readCondition() {
 	while (peek() != ']') {
 		ignore();
 	}
 	ignore();
-	return TokenId::Condition;
+	return YackTokenId::Condition;
 }
 
-TokenId YackTokenReader::readNumber() {
+YackTokenId YackTokenReader::readNumber() {
 	bool isFloat = false;
 	while (Common::isDigit(peek())) {
 		ignore();
@@ -155,21 +155,21 @@ TokenId YackTokenReader::readNumber() {
 	while (Common::isDigit(peek())) {
 		ignore();
 	}
-	return isFloat ? TokenId::Float : TokenId::Int;
+	return isFloat ? YackTokenId::Float : YackTokenId::Int;
 }
 
-TokenId YackTokenReader::readComment() {
+YackTokenId YackTokenReader::readComment() {
 	ignore(INT_MAX, '\n');
 	_stream->seek(_stream->pos() - 1);
-	return TokenId::Comment;
+	return YackTokenId::Comment;
 }
 
-TokenId YackTokenReader::readString() {
+YackTokenId YackTokenReader::readString() {
 	ignore(INT_MAX, '\"');
-	return TokenId::String;
+	return YackTokenId::String;
 }
 
-TokenId YackTokenReader::readIdentifier(char c) {
+YackTokenId YackTokenReader::readIdentifier(char c) {
 	Common::String id;
 	id += c;
 	while (Common::isAlnum(peek()) || peek() == '_') {
@@ -178,39 +178,39 @@ TokenId YackTokenReader::readIdentifier(char c) {
 	}
 	if (id == "waitwhile") {
 		readCode();
-		return TokenId::WaitWhile;
+		return YackTokenId::WaitWhile;
 	}
-	return TokenId::Identifier;
+	return YackTokenId::Identifier;
 }
 
-TokenId YackTokenReader::readTokenId() {
+YackTokenId YackTokenReader::readYackTokenId() {
 	char c;
 	_stream->read(&c, 1);
 	if (_stream->eos()) {
-		return TokenId::End;
+		return YackTokenId::End;
 	}
 
 	switch (c) {
 	case '\0':
-		return TokenId::End;
+		return YackTokenId::End;
 	case '\n':
 		_line++;
-		return TokenId::NewLine;
+		return YackTokenId::NewLine;
 	case '\t':
 	case ' ':
 		while (Common::isSpace(peek()) && peek() != '\n')
 			ignore();
-		return TokenId::Whitespace;
+		return YackTokenId::Whitespace;
 	case '!':
 		return readCode();
 	case ':':
-		return TokenId::Colon;
+		return YackTokenId::Colon;
 	case '$':
 		return readDollar();
 	case '[':
 		return readCondition();
 	case '=':
-		return TokenId::Assign;
+		return YackTokenId::Assign;
 	case '\"':
 		return readString();
 	case '#':
@@ -219,32 +219,32 @@ TokenId YackTokenReader::readTokenId() {
 	default:
 		if (c == '-' && peek() == '>') {
 			ignore();
-			return TokenId::Goto;
+			return YackTokenId::Goto;
 		}
 		if (c == '-' || Common::isDigit(c)) {
 			return readNumber();
 		} else if (Common::isAlpha(c)) {
 			return readIdentifier(c);
 		}
-		error("unknown character: %c", c);
-		return TokenId::None;
+		debug("unknown character: %c", c);
+		return YackTokenId::None;
 	}
 }
 
-bool YackTokenReader::readToken(Token &token) {
+bool YackTokenReader::readYackToken(YackToken &YackToken) {
 	int64 start = _stream->pos();
 	int line = _line;
-	auto id = readTokenId();
-	while (id == TokenId::Whitespace || id == TokenId::Comment || id == TokenId::NewLine || id == TokenId::None) {
+	auto id = readYackTokenId();
+	while (id == YackTokenId::Whitespace || id == YackTokenId::Comment || id == YackTokenId::NewLine || id == YackTokenId::None) {
 		start = _stream->pos();
 		line = _line;
-		id = readTokenId();
+		id = readYackTokenId();
 	}
 	int64 end = _stream->pos();
-	token.id = id;
-	token.start = start;
-	token.end = end;
-	token.line = line;
+	YackToken.id = id;
+	YackToken.start = start;
+	YackToken.end = end;
+	YackToken.line = line;
 	return true;
 }
 
@@ -259,8 +259,8 @@ Common::String YackTokenReader::readText(int64 pos, int64 size) {
 	return out;
 }
 
-Common::String YackTokenReader::readText(const Token &token) {
-	return readText(token.start, token.end - token.start);
+Common::String YackTokenReader::readText(const YackToken &YackToken) {
+	return readText(YackToken.start, YackToken.end - YackToken.start);
 }
 
 YackTokenReader::iterator YackTokenReader::begin() {
@@ -272,7 +272,7 @@ YackTokenReader::iterator YackTokenReader::end() {
 	return Iterator(*this, pos);
 }
 
-bool YackParser::match(const std::initializer_list<TokenId> &ids) {
+bool YackParser::match(const std::initializer_list<YackTokenId> &ids) {
 	auto it = _it;
 	for (auto id : ids) {
 		if (it->id != id)
@@ -325,10 +325,13 @@ YLabel *YackParser::parseLabel() {
 	// label
 	pLabel.reset(new YLabel(_it->line));
 	pLabel->_name = _reader.readText(*_it++);
+	debug("label %s", pLabel->_name.c_str());
 	do {
-		if (match({TokenId::Colon}) || match({TokenId::End}))
+		if (match({YackTokenId::Colon}) || match({YackTokenId::End}))
 			break;
+		YackDump d;
 		YStatement *pStatement = parseStatement();
+		pStatement->accept(d);
 		pLabel->_stmts.push_back(pStatement);
 	} while (true);
 
@@ -341,7 +344,7 @@ YStatement *YackParser::parseStatement() {
 	// expression
 	pStatement->_exp.reset(parseExpression());
 	// conditions
-	while (match({TokenId::Condition})) {
+	while (match({YackTokenId::Condition})) {
 		pStatement->_conds.push_back(parseCondition());
 	}
 	return pStatement.release();
@@ -350,6 +353,7 @@ YCond *YackParser::parseCondition() {
 	auto text = _reader.readText(*_it);
 	auto conditionText = text.substr(1, text.size() - 2);
 	auto line = _it->line;
+	_it++;
 	if (conditionText == "once") {
 		return new YOnce(line);
 	} else if (conditionText == "showonce") {
@@ -364,17 +368,17 @@ YCond *YackParser::parseCondition() {
 	return pCondition;
 }
 YExp *YackParser::parseExpression() {
-	if (match({TokenId::Identifier, TokenId::Colon, TokenId::String}))
+	if (match({YackTokenId::Identifier, YackTokenId::Colon, YackTokenId::String}))
 		return parseSayExpression();
-	if (match({TokenId::WaitWhile}))
+	if (match({YackTokenId::WaitWhile}))
 		return parseWaitWhileExpression();
-	if (match({TokenId::Identifier}))
+	if (match({YackTokenId::Identifier}))
 		return parseInstructionExpression();
-	if (match({TokenId::Goto}))
+	if (match({YackTokenId::Goto}))
 		return parseGotoExpression();
-	if (match({TokenId::Int}))
+	if (match({YackTokenId::Int}))
 		return parseChoiceExpression();
-	if (match({TokenId::Code}))
+	if (match({YackTokenId::Code}))
 		return parseCodeExpression();
 	return nullptr;
 }
@@ -408,7 +412,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "waitfor") {
 		// waitfor [actor]
 		auto pExp = new YWaitFor();
-		if (_it->id == TokenId::Identifier) {
+		if (_it->id == YackTokenId::Identifier) {
 			auto actor = _reader.readText(*_it++);
 			pExp->_actor = actor;
 		}
@@ -416,7 +420,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "parrot") {
 		// parrot [active]
 		auto pExp = new YParrot();
-		if (_it->id == TokenId::Identifier) {
+		if (_it->id == YackTokenId::Identifier) {
 			auto active = _reader.readText(*_it++);
 			pExp->_active = active == "yes";
 		}
@@ -424,7 +428,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "dialog") {
 		// dialog [actor]
 		auto pExp = new YDialog();
-		if (_it->id == TokenId::Identifier) {
+		if (_it->id == YackTokenId::Identifier) {
 			auto actor = _reader.readText(*_it++);
 			pExp->_actor = actor;
 		}
@@ -432,7 +436,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "override") {
 		// override [node]
 		auto pExp = new YOverride();
-		if (_it->id == TokenId::Identifier) {
+		if (_it->id == YackTokenId::Identifier) {
 			auto node = _reader.readText(*_it++);
 			pExp->_node = node;
 		}
@@ -440,7 +444,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "allowobjects") {
 		// allowobjects [allow]
 		auto pExp = new YAllowObjects();
-		if (_it->id == TokenId::Identifier) {
+		if (_it->id == YackTokenId::Identifier) {
 			auto node = _reader.readText(*_it++);
 			pExp->_active = node == "YES";
 		}
@@ -448,7 +452,7 @@ YExp *YackParser::parseInstructionExpression() {
 	} else if (identifier == "limit") {
 		// limit [number]
 		auto pExp = new YLimit();
-		if (_it->id == TokenId::Int) {
+		if (_it->id == YackTokenId::Int) {
 			auto node = _reader.readText(*_it++);
 			pExp->_max = std::strtol(node.c_str(), nullptr, 10);
 		}
@@ -474,7 +478,7 @@ YChoice *YackParser::parseChoiceExpression() {
 	auto number = atol(_reader.readText(*_it).c_str());
 	_it++;
 	Common::String text;
-	if (_it->id == TokenId::Dollar) {
+	if (_it->id == YackTokenId::Dollar) {
 		text = _reader.readText(*_it);
 	} else {
 		text = _reader.readText(*_it);
@@ -494,7 +498,7 @@ YCompilationUnit *YackParser::parse(Common::SeekableReadStream *stream) {
 	_it = _reader.begin();
 	auto pCu = unique_ptr<YCompilationUnit>();
 	pCu.reset(new YCompilationUnit());
-	while (!match({TokenId::End})) {
+	while (!match({YackTokenId::End})) {
 		pCu->_labels.push_back(parseLabel());
 	}
 	return pCu.release();
diff --git a/engines/twp/yack.h b/engines/twp/yack.h
index 5bd4a1a2186..27a639f456a 100644
--- a/engines/twp/yack.h
+++ b/engines/twp/yack.h
@@ -32,7 +32,7 @@ namespace Twp {
 template<typename T, class DL = Common::DefaultDeleter<T> >
 using unique_ptr = Common::ScopedPtr<T, DL>;
 
-enum class TokenId {
+enum class YackTokenId {
 	None,
 	NewLine,
 	Identifier,
@@ -54,7 +54,7 @@ enum class TokenId {
 // enumeration that lists all errors that can occur
 enum YackError {
 	ERR_NONE,              // no error
-	ERR_INVALIDTOKEN,      // invalid token
+	ERR_INVALIDYackToken,      // invalid YackToken
 	ERR_STRINGEXPECTED,    // string expected
 	ERR_COLONEXPECTED,     // `:` expected
 	ERR_COMMAEXPECTED,     // `,` expected
@@ -310,8 +310,8 @@ public:
 	virtual void defaultVisit(const YackNode &node) {}
 };
 
-struct Token {
-	TokenId id;
+struct YackToken {
+	YackTokenId id;
 	int64 start;
 	int64 end;
 	int line;
@@ -323,15 +323,15 @@ class YackTokenReader {
 public:
 	class Iterator {
 	public:
-		using value_type = Token;
+		using value_type = YackToken;
 		using difference_type = ptrdiff_t;
-		using pointer = Token *;
-		using reference = Token &;
+		using pointer = YackToken *;
+		using reference = YackToken &;
 
 	private:
 		YackTokenReader *_reader = nullptr;
 		int64 _pos = 0;
-		Token _token;
+		YackToken _YackToken;
 
 	public:
 		Iterator() {}
@@ -342,15 +342,15 @@ public:
 
 		Iterator &operator=(const Iterator &rhs) {
 			_pos = rhs._pos;
-			_token = rhs._token;
+			_YackToken = rhs._YackToken;
 			_reader = rhs._reader;
 			return *this;
 		}
 		bool operator==(const Iterator &rhs) const { return _pos == rhs._pos; }
 		bool operator!=(const Iterator &rhs) const { return _pos != rhs._pos; }
-		Token &operator*();
-		const Token &operator*() const;
-		Token *operator->();
+		YackToken &operator*();
+		const YackToken &operator*() const;
+		YackToken *operator->();
 	};
 
 	using iterator = Iterator;
@@ -360,19 +360,19 @@ public:
 
 	iterator begin();
 	iterator end();
+	Common::String readText(const YackToken &YackToken);
 
 private:
-	bool readToken(Token &token);
-	Common::String readText(const Token &token);
+	bool readYackToken(YackToken &YackToken);
 	Common::String readText(int64 pos, int64 size);
-	TokenId readTokenId();
-	TokenId readCode();
-	TokenId readCondition();
-	TokenId readDollar();
-	TokenId readNumber();
-	TokenId readComment();
-	TokenId readString();
-	TokenId readIdentifier(char c);
+	YackTokenId readYackTokenId();
+	YackTokenId readCode();
+	YackTokenId readCondition();
+	YackTokenId readDollar();
+	YackTokenId readNumber();
+	YackTokenId readComment();
+	YackTokenId readString();
+	YackTokenId readIdentifier(char c);
 	byte peek();
 	void ignore(int64 n = 1, int delim = EOF);
 
@@ -389,7 +389,7 @@ public:
 	YCompilationUnit* parse(Common::SeekableReadStream *stream);
 
 private:
-	bool match(const std::initializer_list<TokenId> &ids);
+	bool match(const std::initializer_list<YackTokenId> &ids);
 	YLabel* parseLabel();
 	YStatement *parseStatement();
 	YCond *parseCondition();


Commit: 8fd8b0aac05995fa1051f523623a2c88cc6b9435
    https://github.com/scummvm/scummvm/commit/8fd8b0aac05995fa1051f523623a2c88cc6b9435
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add room fade and fix crash in threads

Changed paths:
  A engines/twp/shaders.cpp
  A engines/twp/shaders.h
    engines/twp/actorlib.cpp
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/yack.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 1c1086497af..a67074a7e45 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -275,14 +275,23 @@ static SQInteger actorRoom(HSQUIRRELVM v) {
 	return 1;
 }
 
-static SQInteger actorHideLayer(HSQUIRRELVM v) {
-	warning("TODO: actorHideLayer not implemented");
+static SQInteger actorShowHideLayer(HSQUIRRELVM v, bool visible) {
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Common::String layer;
+	if (SQ_FAILED(sqget(v, 3, layer)))
+		return sq_throwerror(v, "failed to get layer");
+	actor->showLayer(layer, visible);
 	return 0;
 }
 
+static SQInteger actorHideLayer(HSQUIRRELVM v) {
+	return actorShowHideLayer(v, false);
+}
+
 static SQInteger actorShowLayer(HSQUIRRELVM v) {
-	warning("TODO: actorShowLayer not implemented");
-	return 0;
+	return actorShowHideLayer(v, true);
 }
 
 static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
@@ -296,43 +305,110 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 }
 
 static SQInteger actorPosX(HSQUIRRELVM v) {
-	warning("TODO: actorPosX not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	sqpush(v, actor->_node->getPos().getX());
+	return 1;
 }
 
 static SQInteger actorPosY(HSQUIRRELVM v) {
-	warning("TODO: actorPosY not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	sqpush(v, actor->_node->getPos().getY());
+	return 1;
 }
 
+// Plays the specified animation from the player's costume JSON filename.
+// If YES loop the animation. Default is NO.
 static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
-	warning("TODO: actorPlayAnimation not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Common::String animation;
+	if (SQ_FAILED(sqget(v, 3, animation)))
+		return sq_throwerror(v, "failed to get animation");
+	int loop = 0;
+	if ((sq_gettop(v) >= 4) && (SQ_FAILED(sqget(v, 4, loop))))
+		return sq_throwerror(v, "failed to get loop");
+	debug("Play anim %s %s loop=%s", actor->_key.c_str(), animation.c_str(), loop ? "yes" : "no");
+	actor->play(animation, loop != 0);
 	return 0;
 }
 
+// Sets the rendering offset of the actor to x and y.
+//
+// A rendering offset of 0,0 would cause them to be rendered from the middle of their image.
+// Actor's are typically adjusted so they are rendered from the middle of the bottom of their feet.
+// To maintain sanity, it is best if all actors have the same image size and are all adjust the same, but this is not a requirement.
 static SQInteger actorRenderOffset(HSQUIRRELVM v) {
-	warning("TODO: actorRenderOffset not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int x, y;
+	if (SQ_FAILED(sqget(v, 3, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 4, y)))
+		return sq_throwerror(v, "failed to get y");
+	actor->_node->setRenderOffset(Math::Vector2d(x, y));
 	return 0;
 }
 
 static SQInteger actorStand(HSQUIRRELVM v) {
-	warning("TODO: actorStand not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	actor->stand();
 	return 0;
 }
 
+// Makes the specified actor stop moving immediately.
+//
+// . code-block:: Squirrel
+// actorStopWalking(currentActor)
+// actorStopWalking(postalworker)
 static SQInteger actorStopWalking(HSQUIRRELVM v) {
-	warning("TODO: actorStopWalking not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	actor->stopWalking();
+	actor->stand();
 	return 0;
 }
 
+// Set the text color of the specified actor's text that appears when they speak.
 static SQInteger actorTalkColors(HSQUIRRELVM v) {
-	warning("TODO: actorTalkColors not implemented");
+	Object *actor = sqobj(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int color;
+	if (SQ_FAILED(sqget(v, 3, color)))
+		return sq_throwerror(v, "failed to get talk color");
+	actor->_talkColor = Color::rgb(color);
 	return 0;
 }
 
+// If an actor is specified, returns true if that actor is currently talking.
+// If no actor is specified, returns true if the player's current actor is currently talking.
+//
+// . code-block:: Squirrel
+// actorTalking()
+// actorTalking(vo)
 static SQInteger actorTalking(HSQUIRRELVM v) {
-	warning("TODO: actorTalking not implemented");
-	return 0;
+	Object *actor = nullptr;
+	if (sq_gettop(v) == 2) {
+		actor = sqobj(v, 2);
+		if (!actor) {
+			sqpush(v, false);
+			return 1;
+		}
+	} else {
+		actor = g_engine->_actor;
+	}
+	bool isTalking = actor && actor->getTalking() && actor->getTalking()->isEnabled();
+	sqpush(v, isTalking);
+	return 1;
 }
 
 static SQInteger actorTurnTo(HSQUIRRELVM v) {
@@ -340,23 +416,64 @@ static SQInteger actorTurnTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Specifies the offset that will be applied to the actor's speech text that appears on screen.
 static SQInteger actorTalkOffset(HSQUIRRELVM v) {
-	warning("TODO: actorTalkOffset not implemented");
+	Object *actor = sqobj(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int x, y;
+	if (SQ_FAILED(sqget(v, 3, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 4, y)))
+		return sq_throwerror(v, "failed to get y");
+	actor->_talkOffset = Math::Vector2d(x, y);
 	return 0;
 }
 
 static SQInteger actorUsePos(HSQUIRRELVM v) {
-	warning("TODO: actorUsePos not implemented");
+	Math::Vector2d usePos;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Object *obj = sqobj(v, 3);
+	if (!obj)
+		usePos = Math::Vector2d();
+	else
+		usePos = obj->_usePos;
+	if (sq_gettop(v) == 4) {
+		int dir;
+		if (SQ_FAILED(sqget(v, 4, dir)))
+			return sq_throwerror(v, "failed to get direction");
+		else
+			actor->_useDir = (Direction)dir;
+	}
+	actor->_usePos = usePos;
 	return 0;
 }
 
+// Specifies whether the actor needs to abide by walkboxes or not.
+//
+// . code-block:: Squirrel
+// actorUseWalkboxes(coroner, NO)
 static SQInteger actorUseWalkboxes(HSQUIRRELVM v) {
-	warning("TODO: actorUseWalkboxes not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int useWalkboxes = 1;
+	if (SQ_FAILED(sqget(v, 3, useWalkboxes)))
+		return sq_throwerror(v, "failed to get useWalkboxes");
+	actor->_useWalkboxes = useWalkboxes != 0;
 	return 0;
 }
 
 static SQInteger actorVolume(HSQUIRRELVM v) {
-	warning("TODO: actorVolume not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	float volume = 0.0f;
+	if (SQ_FAILED(sqget(v, 3, volume)))
+		return sq_throwerror(v, "failed to get volume");
+	actor->_volume = volume;
 	return 0;
 }
 
@@ -418,11 +535,58 @@ static SQInteger flashSelectableActor(HSQUIRRELVM v) {
 	return 0;
 }
 
-static SQInteger sayLine(HSQUIRRELVM v) {
-	warning("TODO: sayLine not implemented");
+static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
+	Object *obj;
+	int index;
+	Common::StringArray texts;
+	if (sq_gettype(v, 2) == OT_TABLE) {
+		obj = sqobj(v, 2);
+		index = 3;
+	} else {
+		index = 2;
+		obj = g_engine->_actor;
+	}
+
+	if (sq_gettype(v, index) == OT_ARRAY) {
+		HSQOBJECT arr;
+		sq_getstackobj(v, index, &arr);
+		sqgetitems(arr, [&](HSQOBJECT item) { texts.push_back(sq_objtostring(&item)); });
+	} else {
+		int numIds = sq_gettop(v) - index + 1;
+		for (int i = 0; i < numIds; i++) {
+			if (sq_gettype(v, index + i) != OT_NULL) {
+				Common::String text;
+				if (SQ_FAILED(sqget(v, index + i, text)))
+					return sq_throwerror(v, "failed to get text");
+				texts.push_back(text);
+			}
+		}
+	}
+	debug("sayline: {obj.key}, {texts}");
+	obj->say(texts, obj->_talkColor);
 	return 0;
 }
 
+static void stopTalking() {
+	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
+		Layer *layer = *it;
+		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
+			(*it2)->stopTalking();
+		}
+	}
+}
+
+// Causes an actor to say a line of dialog and play the appropriate talking animations.
+// In the first example, the actor ray will say the line.
+// In the second, the selected actor will say the line.
+// In the third example, the first line is displayed, then the second one.
+// See also:
+// - `mumbleLine method`
+static SQInteger sayLine(HSQUIRRELVM v) {
+	stopTalking();
+	return sayOrMumbleLine(v);
+}
+
 static SQInteger sayLineAt(HSQUIRRELVM v) {
 	warning("TODO: sayLineAt not implemented");
 	return 0;
@@ -469,13 +633,32 @@ static SQInteger masterActorArray(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Makes actor say a line or multiple lines.
+// Unlike sayLine this line will not interrupt any other talking on the screen.
+// Cannot be interrupted by normal sayLines.
+// See also:
+// - `sayLine method`.
 static SQInteger mumbleLine(HSQUIRRELVM v) {
-	warning("TODO: mumbleLine not implemented");
-	return 0;
+	return sayOrMumbleLine(v);
 }
 
+// Stops all the current sayLines or mumbleLines that the actor is currently saying or are queued to be said.
+// Passing ALL will stop anyone who is talking to stop.
+// If no parameter is passed, it will stop the currentActor talking.
 static SQInteger stopTalking(HSQUIRRELVM v) {
-	warning("TODO: stopTalking not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 2) {
+		if (sq_gettype(v, 2) == OT_INTEGER) {
+			stopTalking();
+		} else {
+			Object *actor = sqobj(v, 2);
+			if (!actor)
+				return sq_throwerror(v, "failed to get actor/object");
+			actor->stopTalking();
+		}
+	} else if (nArgs == 1) {
+		g_engine->_actor->stopTalking();
+	}
 	return 0;
 }
 
@@ -488,9 +671,25 @@ static SQInteger selectActor(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns an array of all the actors that are currently within a specified trigger box.
+//
+// . code-block:: Squirrel
+// local stepsArray = triggerActors(AStreet.bookStoreLampTrigger)
+// if (stepsArray.len()) {    // someone's on the steps
+// }
 static SQInteger triggerActors(HSQUIRRELVM v) {
-	warning("TODO: triggerActors not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	sq_newarray(v, 0);
+	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
+		Object* actor = *it;
+		if (obj->contains(actor->_node->getPos())) {
+			sq_pushobject(v, actor->_table);
+			sq_arrayappend(v, -2);
+		}
+	}
+	return 1;
 }
 
 static SQInteger verbUIColors(HSQUIRRELVM v) {
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 03e859b84dd..d4550a07403 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twp/gfx.h"
+#include "twp/twp.h"
 #include "common/debug.h"
 #include "graphics/opengl/debug.h"
 #include "graphics/opengl/context.h"
@@ -68,14 +69,59 @@ void Texture::load(const Graphics::Surface &surface) {
 	GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, getFormat(surface.format.bytesPerPixel), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));
 }
 
-void Texture::bind(const Texture *pTexture) {
-	if (pTexture && pTexture->id) {
-		GL_CALL(glBindTexture(GL_TEXTURE_2D, pTexture->id));
+void Texture::bind(const Texture *texture) {
+	if (texture && texture->id) {
+		GL_CALL(glBindTexture(GL_TEXTURE_2D, texture->id));
 	} else {
 		GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
 	}
 }
 
+void Texture::capture(Graphics::Surface &surface) {
+	Common::Array<byte> pixels(width * height * 4);
+	GLint boundFrameBuffer;
+
+	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &boundFrameBuffer);
+	if (boundFrameBuffer != fbo) {
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+	}
+	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]);
+	if (boundFrameBuffer != fbo) {
+		glBindFramebuffer(GL_FRAMEBUFFER, boundFrameBuffer);
+	}
+	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
+	surface.init(width, height, 4 * width, &pixels[0], fmt);
+}
+
+RenderTexture::RenderTexture(Math::Vector2d size) {
+	// result = RenderTexture(width: size.x, height: size.y)
+	width = size.getX();
+	height = size.getY();
+
+	// first create the framebuffer
+	glGenFramebuffers(1, &fbo);
+	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+	// then create an empty texture
+	glGenTextures(1, &id);
+	glBindTexture(GL_TEXTURE_2D, id);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glBindTexture(GL_TEXTURE_2D, 0);
+
+	// then attach it to framebuffer object
+	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0);
+	assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+RenderTexture::~RenderTexture() {
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	glDeleteTextures(1, &id);
+	glDeleteFramebuffers(1, &fbo);
+}
+
 Shader::Shader() {
 }
 
@@ -180,6 +226,7 @@ void Gfx::init() {
 	GL_CALL(glEnableVertexAttribArray(_texCoordsLoc));
 
 	glBindBuffer(GL_ARRAY_BUFFER, 0);
+	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFbo);
 }
 
 void Gfx::clear(Color color) {
@@ -203,9 +250,9 @@ void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
 	drawPrimitives(GL_LINE_STRIP, vertices, count, trsf);
 }
 
-void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture* texture) {
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture *texture) {
 	if (v_size > 0) {
-		_texture = texture ? texture: &gEmptyTexture;
+		_texture = texture ? texture : &gEmptyTexture;
 		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
 
 		// set blending
@@ -245,10 +292,17 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 	}
 }
 
-void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture* texture) {
+void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture *texture) {
 	if (i_size > 0) {
-		_texture = texture ? texture: &gEmptyTexture;
-		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		int num = _shader->getNumTextures();
+		if (num == 0) {
+			_texture = texture ? texture : &gEmptyTexture;
+			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+		} else {
+			for (int i = 0; i < num; i++) {
+				GL_CALL(glBindTexture(GL_TEXTURE_2D, _shader->getTexture(i)));
+			}
+		}
 
 		// set blending
 		GL_CALL(glEnable(GL_BLEND));
@@ -271,9 +325,17 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 		GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
 		GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32) * i_size, indices, GL_STREAM_DRAW));
 
-		GL_CALL(glActiveTexture(GL_TEXTURE0));
-		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
-		GL_CALL(glUniform1i(0, 0));
+		if (num == 0) {
+			GL_CALL(glActiveTexture(GL_TEXTURE0));
+			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
+			GL_CALL(glUniform1i(0, 0));
+		} else {
+			for (int i = 0; i < num; i++) {
+				GL_CALL(glActiveTexture(GL_TEXTURE0 + i));
+				GL_CALL(glBindTexture(GL_TEXTURE_2D, _shader->getTexture(i)));
+				GL_CALL(glUniform1i(_shader->getTextureLoc(i), i));
+			}
+		}
 
 		_shader->setUniform("u_transform", getFinalTransform(trsf));
 		_shader->applyUniforms();
@@ -289,7 +351,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 	}
 }
 
-void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture* texture) {
+void Gfx::draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf, Texture *texture) {
 	drawPrimitives(GL_TRIANGLES, vertices, v_size, indices, i_size, trsf, texture);
 }
 
@@ -332,6 +394,10 @@ void Gfx::drawSprite(Common::Rect textRect, Texture &texture, Color color, Math:
 	draw(vertices, 4, quadIndices, 6, trsf, &texture);
 }
 
+void Gfx::drawSprite(Texture &texture, Color color, Math::Matrix4 trsf, bool flipX, bool flipY) {
+	drawSprite(Common::Rect(texture.width, texture.height), texture, color, trsf, flipX, flipY);
+}
+
 void Gfx::camera(Math::Vector2d size) {
 	_cameraSize = size;
 	_mvp = ortho(0.f, size.getX(), 0.f, size.getY(), -1.f, 1.f);
@@ -344,4 +410,17 @@ Math::Vector2d Gfx::camera() const {
 void Gfx::use(Shader *shader) {
 	_shader = shader ? shader : &_defaultShader;
 }
+
+void Gfx::setRenderTarget(RenderTexture *target) {
+	if (!target) {
+		glBindFramebuffer(GL_FRAMEBUFFER, _oldFbo);
+		int w = g_engine->_system->getWidth();
+		int h = g_engine->_system->getHeight();
+		glViewport(0, 0, w, h);
+	} else {
+		glBindFramebuffer(GL_FRAMEBUFFER, target->fbo);
+		glViewport(0, 0, target->width, target->height);
+	}
+}
+
 } // namespace Twp
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index fecae3454cf..a6f9bcdfe43 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -56,7 +56,7 @@ struct Color {
 	}
 
 	static Color rgb(int c) {
-  		return Color((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF));
+		return Color((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF));
 	}
 
 	static Color create(uint8 red, uint8 green, uint8 blue, uint8 alpha = 0xFF) {
@@ -74,13 +74,21 @@ public:
 
 class Texture {
 public:
-	void load(const Graphics::Surface& surface);
-	static void bind(const Texture *pTexture);
+	virtual ~Texture() {}
+	void load(const Graphics::Surface &surface);
+	static void bind(const Texture *texture);
+	void capture(Graphics::Surface &surface);
 
 public:
 	uint32 id;
-    int width, height;
-    uint32 fbo;
+	int width, height;
+	uint32 fbo;
+};
+
+class RenderTexture : public Texture {
+public:
+	RenderTexture(Math::Vector2d size);
+	virtual ~RenderTexture() override;
 };
 
 struct TextureSlot {
@@ -97,6 +105,9 @@ public:
 
 	void setUniform(const char *name, Math::Matrix4 value);
 	virtual void applyUniforms() {}
+	virtual int getNumTextures() { return 0;};
+	virtual int getTexture(int index) { return 0;};
+	virtual int getTextureLoc(int index) { return 0;};
 
 private:
 	uint32 loadShader(const char *code, uint32 shaderType);
@@ -123,15 +134,18 @@ public:
 	Math::Vector2d cameraPos() const { return _cameraPos; }
 	void cameraPos(Math::Vector2d pos) { _cameraPos = pos; }
 
-	void use(Shader* shader);
+	Shader *getShader() { return _shader; }
+	void use(Shader *shader);
+	void setRenderTarget(RenderTexture *target);
 
 	void clear(Color color);
-	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, Math::Matrix4 transf = Math::Matrix4(), Texture* texture = NULL);
-	void drawPrimitives(uint32 primitivesType, Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 transf = Math::Matrix4(), Texture* texture = NULL);
-	void drawLines(Vertex* vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
-	void draw(Vertex* vertices, int v_size, uint32* indices, int i_size, Math::Matrix4 trsf = Math::Matrix4(), Texture* texture = NULL);
+	void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
+	void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
+	void drawLines(Vertex *vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
+	void draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf = Math::Matrix4(), Texture *texture = NULL);
 	void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
-	void drawSprite(Common::Rect textRect, Texture& texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
+	void drawSprite(Common::Rect textRect, Texture &texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
+	void drawSprite(Texture &texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
 
 private:
 	Math::Matrix4 getFinalTransform(Math::Matrix4 trsf);
@@ -140,14 +154,15 @@ private:
 private:
 	uint32 _vbo = 0, _ebo = 0;
 	Shader _defaultShader;
-	Shader* _shader = nullptr;
+	Shader *_shader = nullptr;
 	Math::Matrix4 _mvp;
 	Math::Vector2d _cameraPos;
 	Math::Vector2d _cameraSize;
 	Textures _textures;
-	Texture* _texture = nullptr;
+	Texture *_texture = nullptr;
 	int32 _posLoc = 0, _colLoc = 0, _texCoordsLoc = 0, _texLoc = 0, _trsfLoc = 0;
+	int32 _oldFbo;
 };
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index c75d8da6568..98fcc91561a 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -54,6 +54,7 @@ MODULE_OBJS = \
 	motor.o \
 	yack.o \
 	dialog.o \
+	shaders.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 696e0151aee..ae91bf48adb 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -156,7 +156,7 @@ void Object::trig(const Common::String &name) {
 		if (_triggers.contains(trigNum)) {
 			_triggers[trigNum]->trig();
 		} else {
-			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+			//warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
 		}
 	} else {
 		error("todo: trig %s", name.c_str());
@@ -385,8 +385,8 @@ bool Object::isWalking() {
 }
 
 void Object::stopWalking() {
-	  if (_walkTo)
-	    _walkTo->disable();
+	if (_walkTo)
+		_walkTo->disable();
 }
 
 void Object::setAnimationNames(const Common::String &head, const Common::String &stand, const Common::String &walk, const Common::String &reach) {
@@ -505,4 +505,21 @@ void Object::pickupObject(Object *obj) {
 	}
 }
 
+void Object::stopTalking() {
+	if (_talking) {
+		_talking->disable();
+		setHeadIndex(1);
+	}
+}
+
+void Object::say(const Common::StringArray &texts, Color color) {
+	_talkingState._obj = this;
+	_talkingState._color = color;
+	_talkingState.say(texts, this);
+}
+
+void TalkingState::say(const Common::StringArray &texts, Object *obj) {
+	// TODO: obj->setTalking(new Talking(obj, texts, color));
+}
+
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index abe9e7027ab..e27810c40df 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -84,6 +84,13 @@ class Motor;
 class Node;
 class Layer;
 
+struct TalkingState {
+    Object* _obj;
+    Color _color;
+
+	void say(const Common::StringArray& texts, Object* obj);
+};
+
 class Object {
 public:
 	Object();
@@ -162,6 +169,10 @@ public:
 	void setShakeTo(Motor* shakeTo);
 	void setJiggleTo(Motor* jiggleTo);
 
+	Motor* getTalking() { return _talking; }
+	void stopTalking();
+	void say(const Common::StringArray& texts, Color color);
+
 	void pickupObject(Object *obj);
 
 private:
@@ -230,6 +241,7 @@ private:
 	Motor* _turnTo = nullptr;
 	Motor* _shakeTo = nullptr;
 	Motor* _jiggleTo = nullptr;
+	TalkingState _talkingState;
 };
 
 } // namespace Twp
diff --git a/engines/twp/room.h b/engines/twp/room.h
index e2dce81931e..0d35571dd26 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -31,6 +31,15 @@
 
 namespace Twp {
 
+enum class RoomEffect {
+	None = 0,
+	Sepia = 1,
+	Ega = 2,
+	Vhs = 3,
+	Ghost = 4,
+	BlackAndWhite = 5
+};
+
 class Node;
 class Object;
 
@@ -105,11 +114,11 @@ public:
 	Math::Vector2d getScreenSize();
 
 	Layer *layer(int zsort);
-	Object *getObj(const Common::String& key);
+	Object *getObj(const Common::String &key);
 
 	Light *createLight(Color color, Math::Vector2d pos);
 	float getScaling(float yPos);
-	void objectParallaxLayer(Object* obj, int zsort);
+	void objectParallaxLayer(Object *obj, int zsort);
 
 public:
 	Common::String _name;              // Name of the room
@@ -128,7 +137,8 @@ public:
 	Common::Array<Object *> _triggers; // Triggers currently enabled in the room
 	bool _pseudo = false;
 	Common::Array<Object *> _objects;
-	Scene* _scene = nullptr;
+	Scene *_scene = nullptr;
+	RoomEffect _effect;
 };
 
 } // namespace Twp
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 0fbdc194b6b..982d0743e74 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -244,8 +244,43 @@ static SQInteger roomEffect(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Fades in or out (FADE_IN, FADE_OUT, FADE_WOBBLE, FADE_WOBBLE_TO_SEPIA) of the current room over the specified duration.
+//
+// Used for dramatic effect when we want to teleport the player actor to somewhere new, or when starting/ending a cutscene that takes place in another room.
+//
+// .. code-block:: Squirrel
+// roomFade(FADE_OUT, 0.5)
+// breaktime(0.5)
+// actorAt(currentActor, Alleyway.newLocationSpot)
+// cameraFollow(currentActor)
+// roomFade(FADE_IN, 0.5)
 static SQInteger roomFade(HSQUIRRELVM v) {
-	warning("TODO: roomFade not implemented");
+	SQInteger fadeType;
+	float t;
+	if (SQ_FAILED(sqget(v, 2, fadeType)))
+		return sq_throwerror(v, "failed to get fadeType");
+	if (SQ_FAILED(sqget(v, 3, t)))
+		return sq_throwerror(v, "failed to get time");
+	FadeEffect effect = FadeEffect::In;
+	bool sepia = false;
+	switch (fadeType) {
+	case FADE_IN:
+		effect = FadeEffect::In;
+		break;
+	case FADE_OUT:
+		effect = FadeEffect::Out;
+		break;
+	case FADE_WOBBLE:
+		effect = FadeEffect::Wobble;
+		break;
+	case FADE_WOBBLE_TO_SEPIA:
+		effect = FadeEffect::Wobble;
+		sepia = true;
+		break;
+	default:
+		break;
+	}
+	g_engine->fadeTo(effect, t, sepia);
 	return 0;
 }
 
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
new file mode 100644
index 00000000000..8c2860fb026
--- /dev/null
+++ b/engines/twp/shaders.cpp
@@ -0,0 +1,135 @@
+/* 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 "twp/shaders.h"
+#include "twp/twp.h"
+#include "graphics/opengl/debug.h"
+#include "graphics/opengl/system_headers.h"
+
+namespace Twp {
+
+FadeShader::FadeShader() {
+	const char *vsrc = R"(#version 110
+		uniform mat4 u_transform;
+	attribute vec2 a_position;
+	attribute vec4 a_color;
+	attribute vec2 a_texCoords;
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	void main() {
+		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+		v_color = a_color;
+		v_texCoords = a_texCoords;
+	})";
+	const char *fadeShader = R"(#version 110
+#ifdef GL_ES
+precision highp float;
+#endif
+
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	uniform sampler2D u_texture;
+	uniform sampler2D u_texture2;
+
+	uniform float u_timer;
+	uniform float u_fade;
+	uniform int u_fadeToSep;
+	uniform float u_movement;
+
+	void main() {
+		const float RADIUS = 0.75;
+		const float SOFTNESS = 0.45;
+		const float ScratchValue = 0.3;
+		vec2 uv = v_texCoords;
+		float pi2 = (3.142 * 2.0);
+		float intervals = 4.0;
+		uv.x += sin((u_timer + uv.y) * (pi2 * intervals)) * u_movement;
+		vec4 texColor1 = v_color * texture2D(u_texture, uv);
+		vec4 texColor2 = v_color * texture2D(u_texture2, uv);
+		if (u_fadeToSep != 0) {
+			float gray = dot(texColor2.rgb, vec3(0.299, 0.587, 0.114));
+			vec2 dist = vec2(uv.x - 0.5, uv.y - 0.5);
+			vec3 sepiaColor = vec3(gray, gray, gray) * vec3(0.9, 0.8, 0.6);
+			float len = dot(dist, dist);
+			float vignette = smoothstep(RADIUS, RADIUS - SOFTNESS, len);
+			vec3 sep = mix(texColor2.rgb, sepiaColor, 0.80) * vignette;
+			gl_FragColor.rgb = (texColor1.rgb * (1.0 - u_fade)) + (sep * u_fade);
+		} else {
+			gl_FragColor.rgb = (texColor1.rgb * (1.0 - u_fade)) + (texColor2.rgb * u_fade);
+		}
+		gl_FragColor.a = 1.0;
+	})";
+	init(vsrc, fadeShader);
+
+	GL_CALL(_textureLoc[0] = glGetUniformLocation(program, "u_texture"));
+	GL_CALL(_textureLoc[1] = glGetUniformLocation(program, "u_texture2"));
+	GL_CALL(_timerLoc = glGetUniformLocation(program, "u_timer"));
+	GL_CALL(_fadeLoc = glGetUniformLocation(program, "u_fade"));
+	GL_CALL(_fadeToSepLoc = glGetUniformLocation(program, "u_fadeToSep"));
+	GL_CALL(_movementLoc = glGetUniformLocation(program, "u_movement"));
+}
+
+FadeShader::~FadeShader() {}
+
+void FadeShader::applyUniforms() {
+	float movement = (sin(M_PI * _fade) * _movement);
+	GL_CALL(glUniform1f(_timerLoc, _elapsed));
+	GL_CALL(glUniform1f(_fadeLoc, _fade));
+	GL_CALL(glUniform1i(_fadeToSepLoc, _fadeToSepia ? 1 : 0));
+	GL_CALL(glUniform1f(_movementLoc, movement));
+}
+
+int FadeShader::getNumTextures() { return 2; }
+
+int FadeShader::getTexture(int index) {
+	switch (index) {
+	case 0:
+		return _texture1->id;
+	case 1:
+		return _texture2->id;
+	}
+	return 0;
+}
+
+int FadeShader::getTextureLoc(int index) { return _textureLoc[index]; }
+
+void ShaderParams::updateShader() {
+	// TODO
+	//   if (effect == RoomEffect::Sepia) {
+	//     Shader* shader = g_engine->getGfx().getShader();
+	//     shader->setUniform("RandomValue", randomValue);
+	//     shader->setUniform("TimeLapse", timeLapse);
+	//   } else if (effect == RoomEffect::Vhs) {
+	//     Shader* shader = g_engine->getGfx().getShader();
+	//     shader->setUniform("iGlobalTime", iGlobalTime);
+	//     shader->setUniform("iNoiseThreshold", iNoiseThreshold);
+	//   } else if (effect == RoomEffect::Ghost) {
+	//     Shader* shader = g_engine->getGfx().getShader();
+	//     shader->setUniform("iGlobalTime", iGlobalTime);
+	//     shader->setUniform("iFade", iFade);
+	//     shader->setUniform("wobbleIntensity", wobbleIntensity);
+	//     shader->setUniform("shadows", shadows);
+	//     shader->setUniform("midtones", midtones);
+	//     shader->setUniform("highlights", highlights);
+	//   }
+}
+
+} // namespace Twp
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
new file mode 100644
index 00000000000..98d07b9ad5b
--- /dev/null
+++ b/engines/twp/shaders.h
@@ -0,0 +1,88 @@
+/* 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 TWP_SHADERS_H
+#define TWP_SHADERS_H
+
+#include "twp/room.h"
+#include "math/vector3d.h"
+
+namespace Twp {
+
+struct ShaderParams {
+    RoomEffect effect;
+    float sepiaFlicker;
+    float randomValue[5];
+    float timeLapse;
+    float iGlobalTime;
+    float iNoiseThreshold;
+    float iFade;
+    float wobbleIntensity;
+    Math::Vector3d shadows;
+    Math::Vector3d midtones;
+    Math::Vector3d highlights;
+
+	void updateShader();
+};
+
+
+enum class FadeEffect {
+	None,
+	In,
+	Out,
+	Wobble
+};
+
+class FadeShader: public Shader {
+public:
+	FadeShader();
+	virtual ~FadeShader() override;
+
+	virtual int getNumTextures() override;
+	virtual int getTexture(int index) override;
+	virtual int getTextureLoc(int index) override;
+
+private:
+	virtual void applyUniforms() final;
+
+public:
+	FadeEffect _effect = FadeEffect::None;
+	Room *_room = nullptr;
+	Texture *_texture1 = nullptr;
+	Texture *_texture2 = nullptr;
+	Math::Vector2d _cameraPos;
+	float _duration = 0.f;
+	float _elapsed = 0.f;
+	float _movement = 0.f;		// movement for wobble effect
+	float _fade = 0.f;			// fade value between [0.f,1.f]
+	bool _fadeToSepia = false;	// true to fade to sepia
+
+private:
+	int _textureLoc[2];
+	int _timerLoc;
+	int _fadeLoc;
+	int _fadeToSepLoc;
+	int _movementLoc;
+};
+
+}
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 385eeffc9fa..d76b67b5efe 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -108,9 +108,9 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 	// TODO: update this
 	if (_room) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
-		//Object *obj = objAt(roomPos);
+		// Object *obj = objAt(roomPos);
 
-		if (_cursor.leftDown) {
+		if (_cursor.isLeftClick()) {
 			// button left: execute selected verb
 			clickedAtHandled(roomPos);
 			// if (!handled && obj) {
@@ -146,10 +146,12 @@ void TwpEngine::update(float elapsed) {
 	// _sentence.pos = scrPos;
 	// _dlg.mousePos = scrPos;
 	if (_room) {
-		if (_cursor.leftDown)
+		if (_cursor.isLeftClick())
 			clickedAt(_cursor.pos);
 	}
 
+	_fadeShader->_elapsed += elapsed;
+
 	// update camera
 	_camera.update(_room, _followActor, elapsed);
 
@@ -164,10 +166,21 @@ void TwpEngine::update(float elapsed) {
 		it++;
 	}
 
-	// update threads
-	for (auto it = _threads.begin(); it != _threads.end();) {
+	// update threads: make a copy of the threads because during threads update, new threads can be added
+	Common::Array<ThreadBase*> threads(_threads);
+	Common::Array<ThreadBase*> threadsToRemove;
+
+	for (auto it = threads.begin(); it != threads.end(); it++) {
 		ThreadBase *thread = *it;
 		if (thread->update(elapsed)) {
+			threadsToRemove.push_back(thread);
+		}
+	}
+
+	// remove threads that are terminated
+	for (auto it = _threads.begin(); it != _threads.end();) {
+		ThreadBase *thread = *it;
+		if(find(threadsToRemove, thread)!=-1) {
 			it = _threads.erase(it);
 			delete thread;
 			continue;
@@ -187,19 +200,123 @@ void TwpEngine::update(float elapsed) {
 	}
 }
 
+void TwpEngine::setShaderEffect(RoomEffect effect) {
+	_shaderParams.effect = effect;
+	//   switch (effect) {
+	//   case RoomEffect::None:
+	//     _gfx.use(nullptr);
+	// 	break;
+	//   case RoomEffect::Sepia:
+	//     let shader = newShader(vertexShader, sepiaShader);
+	//     gfxShader(shader);
+	//     shader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
+	// 	break;
+	//   case RoomEffect::BlackAndWhite:
+	//     gfxShader(newShader(vertexShader, bwShader));
+	// 	break;
+	//   case RoomEffect::Ega:
+	//     gfxShader(newShader(vertexShader, egaShader));
+	// 	break;
+	//   case RoomEffect::Vhs:
+	//     gfxShader(newShader(vertexShader, vhsShader));
+	// 	break;
+	//   case RoomEffect::Ghost:
+	//     let shader = newShader(vertexShader, ghostShader);
+	//     gfxShader(shader);
+	// 	break;
+	//   }
+}
+
 void TwpEngine::draw() {
 	if (_room) {
 		Math::Vector2d screenSize = _room->getScreenSize();
 		_gfx.camera(screenSize);
 	}
+
+	RenderTexture renderTexture(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+	RenderTexture renderTexture2(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+	RenderTexture renderTexture3(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+
+	// draw scene into a texture
+	_gfx.setRenderTarget(&renderTexture);
 	_gfx.clear(Color(0, 0, 0));
-	_gfx.use(NULL);
+	_gfx.use(nullptr);
 	_scene.draw();
 
+	// then render this texture with room effect to another texture
+	_gfx.setRenderTarget(&renderTexture2);
+	// setShaderEffect(_room->_effect);
+	// _shaderParams.randomValue[0] = g_engine.rand.rand(0f..1f);
+	// _shaderParams.timeLapse = fmodf(_time, 1000.f);
+	// _shaderParams.iGlobalTime = _shaderParams.timeLapse;
+	// _shaderParams.updateShader();
+
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+	bool flipY = _fadeShader->_effect == FadeEffect::Wobble;
+	Math::Vector2d camPos = _gfx.camera();
+	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, flipY);
+
+	Texture *screenTexture = &renderTexture2;
+	if (_fadeShader->_effect != FadeEffect::None) {
+		// draw second room if any
+		_gfx.setRenderTarget(&renderTexture);
+		_gfx.use(nullptr);
+		_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+		_gfx.cameraPos(_fadeShader->_cameraPos);
+		_gfx.clear(Color(0, 0, 0));
+		if (_fadeShader->_effect == FadeEffect::Wobble) {
+			Math::Vector2d camSize = _fadeShader->_room->getScreenSize();
+			_gfx.camera(camSize);
+			_fadeShader->_room->_scene->draw();
+		}
+
+		_fadeShader->_fade = clamp(_fadeShader->_elapsed / _fadeShader->_duration, 0.f, 1.f);
+
+		// draw fade
+		Texture *texture1 = nullptr;
+		Texture *texture2 = nullptr;
+		switch (_fadeShader->_effect) {
+		case FadeEffect::Wobble:
+			texture1 = &renderTexture;
+			texture2 = &renderTexture2;
+			screenTexture = &renderTexture;
+			break;
+		case FadeEffect::In:
+			texture1 = &renderTexture,
+			texture2 = &renderTexture2;
+			screenTexture = &renderTexture3;
+			break;
+		case FadeEffect::Out:
+			texture1 = &renderTexture;
+			texture2 = &renderTexture2;
+			_fadeShader->_fade = 1.f - _fadeShader->_fade;
+			screenTexture = &renderTexture3;
+			break;
+		case FadeEffect::None:
+			break;
+		}
+
+		_gfx.setRenderTarget(&renderTexture3);
+		_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+		_fadeShader->_texture1 = texture1;
+		_fadeShader->_texture2 = texture2;
+		_gfx.use(_fadeShader.get());
+		_gfx.cameraPos(Math::Vector2d());
+		_gfx.drawSprite(*texture1);
+	}
+
+	// draw to screen
+	_gfx.setRenderTarget(nullptr);
+	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
+	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, flipY);
+
+	// draw UI
+	_gfx.use(nullptr);
 	_screenScene.draw();
 
 	g_system->updateScreen();
+
+	// _gfx.cameraPos(camPos);
 }
 
 Common::Error TwpEngine::run() {
@@ -210,6 +327,8 @@ Common::Error TwpEngine::run() {
 	setDebugger(new Console());
 
 	_gfx.init();
+	_fadeShader.reset(new FadeShader());
+
 	_lighting = new Lighting();
 
 	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
@@ -258,7 +377,7 @@ Common::Error TwpEngine::run() {
 
 	// Simple event handling loop
 	Common::Event e;
-	uint deltaTimeMs = 0;
+	uint time = _system->getMillis();
 	while (!shouldQuit()) {
 		const int dx = 4;
 		const int dy = 4;
@@ -304,13 +423,16 @@ Common::Error TwpEngine::run() {
 
 		_gfx.cameraPos(camPos);
 
-		update(deltaTimeMs / 1000.f);
+		uint32 newTime = _system->getMillis();
+		uint32 delta = newTime - time;
+		time = newTime;
+		update(delta / 1000.f);
 		draw();
+		_cursor.update();
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
-		deltaTimeMs = 10;
-		g_system->delayMillis(deltaTimeMs);
+		g_system->delayMillis(10);
 	}
 
 	return Common::kNoError;
@@ -489,7 +611,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 
 	// exit current room
 	exitRoom(_room);
-	// TODO: _fadeEffect.effect = None;
+	// TODO: _fadeShader->effect = None;
 
 	// sets the current room for scripts
 	sqsetf(sqrootTbl(v), "currentRoom", room->_table);
@@ -680,6 +802,16 @@ void TwpEngine::follow(Object *actor) {
 	}
 }
 
+void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
+	_fadeShader->_fadeToSepia = fadeToSep;
+	_fadeShader->_effect = effect;
+	_fadeShader->_room = _room;
+	_fadeShader->_cameraPos = cameraPos();
+	_fadeShader->_duration = duration;
+	_fadeShader->_movement = effect == FadeEffect::Wobble ? 0.005f : 0.f;
+	_fadeShader->_elapsed = 0.f;
+}
+
 template<typename TFunc>
 void objsAt(Math::Vector2d pos, TFunc func) {
 	// TODO
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 27455b75bdd..01f32a5d253 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -33,6 +33,7 @@
 #include "graphics/screen.h"
 #include "twp/detection.h"
 #include "twp/vm.h"
+#include "twp/shaders.h"
 #include "twp/resmanager.h"
 #include "twp/ggpack.h"
 #include "twp/squirrel/squirrel.h"
@@ -130,6 +131,7 @@ public:
 	// Returns the camera position: the position of the middle of the screen.
 	Math::Vector2d cameraPos();
 	void follow(Object *actor);
+	void fadeTo(FadeEffect effect, float duration, bool fadeToSep = false);
 
 	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
 	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
@@ -143,6 +145,7 @@ private:
 	void cancelSentence(Object *actor = nullptr);
 	void clickedAt(Math::Vector2d scrPos);
 	bool clickedAtHandled(Math::Vector2d roomPos);
+	void setShaderEffect(RoomEffect effect);
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -172,14 +175,24 @@ public:
 	Dialog _dialog;
 	struct Cursor {
 		Math::Vector2d pos;
+		bool oldLeftDown = false;
 		bool leftDown = false;
+		bool oldRightDown = false;
 		bool rightDown = false;
+
+		void update() {
+			oldLeftDown = leftDown;
+			oldRightDown = rightDown;
+		}
+		bool isLeftClick() { return oldLeftDown && !leftDown; }
 	} _cursor;
 
 private:
 	Gfx _gfx;
 	Vm _vm;
 	Preferences _prefs;
+	ShaderParams _shaderParams;
+	unique_ptr<FadeShader> _fadeShader;
 };
 
 extern TwpEngine *g_engine;
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 8e48afeb692..66f0d224cd6 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -329,9 +329,7 @@ YLabel *YackParser::parseLabel() {
 	do {
 		if (match({YackTokenId::Colon}) || match({YackTokenId::End}))
 			break;
-		YackDump d;
 		YStatement *pStatement = parseStatement();
-		pStatement->accept(d);
 		pLabel->_stmts.push_back(pStatement);
 	} while (true);
 


Commit: 0e6ef39297c1edbb52fea3c1ecda7b3081387221
    https://github.com/scummvm/scummvm/commit/0e6ef39297c1edbb52fea3c1ecda7b3081387221
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix cutscene resumed too early

Changed paths:
    engines/twp/genlib.cpp
    engines/twp/objlib.cpp
    engines/twp/syslib.cpp
    engines/twp/task.h
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index f06a5387564..ff7c4406c16 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -308,9 +308,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 }
 
 static SQInteger frameCounter(HSQUIRRELVM v) {
-	// TODO: frameCounter
-	warning("frameCounter not implemented");
-	return 0;
+	return sqpush(v, g_engine->_frameCounter);
 }
 
 static SQInteger getUserPref(HSQUIRRELVM v) {
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index c705b6819ac..77eff3d72e5 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -151,7 +151,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 // deleteObject(drip)
 static SQInteger deleteObject(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
-	if(obj) {
+	if (obj) {
 		obj->delObject();
 		delete obj;
 	}
@@ -373,7 +373,7 @@ static SQInteger objectHidden(HSQUIRRELVM v) {
 	if (obj) {
 		int hidden = 0;
 		sqget(v, 3, hidden);
-		debug("Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(),hidden == 0 ? "true" : "false");
+		debug("Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(), hidden == 0 ? "true" : "false");
 		obj->_node->setVisible(hidden == 0);
 	}
 	return 0;
@@ -743,15 +743,15 @@ static SQInteger objectState(HSQUIRRELVM v) {
 	if (nArgs == 2) {
 		sqpush(v, obj->_state);
 		return 1;
-	} else if (nArgs == 3) {
+	}
+	if (nArgs == 3) {
 		int state;
 		if (SQ_FAILED(sqget(v, 3, state)))
 			return sq_throwerror(v, "failed to get state");
 		obj->setState(state);
 		return 0;
-	} else {
-		return sq_throwerror(v, "invalid number of arguments");
 	}
+	return sq_throwerror(v, "invalid number of arguments");
 }
 
 // Gets or sets if an object is player touchable.
@@ -864,7 +864,7 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, verb)))
 		return sq_throwerror(v, "failed to get verb");
 
-	//int verbId = verb;
+	// int verbId = verb;
 	if (!g_engine->_actor) {
 		// TODO:
 		// for (vb in gEngine.hud.actorSlot(gEngine.actor).verbs) {
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index ed6c63d56f0..f3e28b7b7f8 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -190,7 +190,7 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	if (!curThread)
 		return sq_throwerror(v, "Current thread should be created with startthread");
 
-	debug("curThread.id: %d", curThread->getId());
+	debug("curThread.id: %d, %s", curThread->getId(), curThread->getName().c_str());
 	debug("add breakwhilecond name=%s pid=%d", name.c_str(), curThread->getId());
 	g_engine->_tasks.push_back(new BreakWhileCond<Predicate>(curThread->getId(), name, pred));
 	return -666;
@@ -201,9 +201,11 @@ static SQInteger breakwhileanimating(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Breaks while a camera is moving.
+// Once the thread finishes execution, the method will continue running.
+// It is an error to call breakwhilecamera in a function that was not started with startthread.
 static SQInteger breakwhilecamera(HSQUIRRELVM v) {
-	warning("TODO: breakwhilecamera: not implemented");
-	return 0;
+	return breakwhilecond(v, [] { return g_engine->_camera.isMoving();}, "breakwhilecamera()");
 }
 
 // Breaks while a cutscene is running.
@@ -215,8 +217,8 @@ static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
-	warning("TODO: breakwhiledialog: not implemented");
-	return 0;
+	return breakwhilecond(
+		v, [] { return g_engine->_dialog.getState() != DialogState::None;}, "breakwhiledialog()");
 }
 
 static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
@@ -326,9 +328,17 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns how long (in seconds) the game has been played for in total (not just this session).
+//// Saved when the game is saved.
+// Also used for testing.
+// The value is a float, so 1 = 1 second, 0.5 = half a second.
+//
+// . code-block:: Squirrel
+// if (gameTime() > (time+testerTronTimeOut)) { // Do something
+// }
 static SQInteger gameTime(HSQUIRRELVM v) {
-	warning("TODO: gameTime: not implemented");
-	return 0;
+	sqpush(v, g_engine->_time);
+	return 1;
 }
 
 static SQInteger sysInclude(HSQUIRRELVM v) {
@@ -395,9 +405,12 @@ static SQInteger logWarning(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns game time in milliseconds.
+// Based on when the machine is booted and runs all the time (not paused or saved).
+// See also gameTime, which is in seconds.
 static SQInteger microTime(HSQUIRRELVM v) {
-	warning("TODO: microTime: not implemented");
-	return 0;
+	sqpush(v, g_engine->_time * 1000.0f);
+	return 1;
 }
 
 static SQInteger moveCursorTo(HSQUIRRELVM v) {
diff --git a/engines/twp/task.h b/engines/twp/task.h
index 082a1039d78..3eb11684482 100644
--- a/engines/twp/task.h
+++ b/engines/twp/task.h
@@ -51,7 +51,7 @@ public:
 			return false;
 		ThreadBase *pt = sqthread(_parentId);
 		if (pt) {
-			debug("Resume task: %d", _parentId);
+			debug("Resume task: %d, %s", _parentId, pt->getName().c_str());
 			pt->resume();
 		}
 		return true;
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 15a0dd357fa..1ce64e918fe 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -50,10 +50,11 @@ void ThreadBase::resume() {
 
 Thread::Thread(int id) {
 	_id = id;
+	_pauseable = true;
 }
 
 Thread::~Thread() {
-	debug("delete thread %d, %s, global: %s", _id, _name.c_str(), _global?"yes":"no");
+	debug("delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
 	HSQUIRRELVM v = g_engine->getVm();
 	for (int i = 0; i < _args.size(); i++) {
 		sq_release(v, &_args[i]);
@@ -101,14 +102,13 @@ void Thread::stop() {
 }
 
 Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
-	: _name("cutscene"),
-	  _v(v),
+	: _v(v),
 	  _threadObj(threadObj),
 	  _closure(closure),
 	  _closureOverride(closureOverride),
 	  _envObj(envObj) {
 
-	_pauseable = false;
+	_name = "cutscene";
 	_id = newThreadId();
 	//_inputState = g_engine->inputState.getState();
 	_actor = g_engine->_followActor;
@@ -179,11 +179,12 @@ void Cutscene::checkEndCutsceneOverride() {
 }
 
 bool Cutscene::update(float elapsed) {
-	if (_waitTime > 0)
+	if (_waitTime > 0) {
 		_waitTime -= elapsed;
-	if (_waitTime <= 0) {
-		_waitTime = 0;
-		resume();
+		if (_waitTime <= 0) {
+			_waitTime = 0;
+			resume();
+		}
 	} else if (_numFrames > 0) {
 		_numFrames -= 1;
 		_numFrames = 0;
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index 164d847c504..11321ab2416 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -66,7 +66,7 @@ public:
 	float _waitTime = 0.f;
 	int _numFrames = 0;
 	bool _paused = false;
-	bool _pauseable = true;
+	bool _pauseable = false;
 
 protected:
 	int _id = 0;
@@ -135,7 +135,6 @@ private:
 	bool isStopped();
 
 private:
-	Common::String _name;
 	HSQUIRRELVM _v;
 	HSQOBJECT _threadObj, _closure, _closureOverride, _envObj;
 	CutsceneState _state;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d76b67b5efe..25e38e46750 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -137,6 +137,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 
 void TwpEngine::update(float elapsed) {
 	_time += elapsed;
+	_frameCounter++;
 
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
@@ -155,20 +156,17 @@ void TwpEngine::update(float elapsed) {
 	// update camera
 	_camera.update(_room, _followActor, elapsed);
 
-	// update tasks
-	for (auto it = _tasks.begin(); it != _tasks.end();) {
-		Task *task = *it;
-		if (task->update(elapsed)) {
-			it = _tasks.erase(it);
-			delete task;
-			continue;
+	// update cutscene
+	if (_cutscene) {
+		if (_cutscene->update(elapsed)) {
+			delete _cutscene;
+			_cutscene = nullptr;
 		}
-		it++;
 	}
 
 	// update threads: make a copy of the threads because during threads update, new threads can be added
-	Common::Array<ThreadBase*> threads(_threads);
-	Common::Array<ThreadBase*> threadsToRemove;
+	Common::Array<ThreadBase *> threads(_threads);
+	Common::Array<ThreadBase *> threadsToRemove;
 
 	for (auto it = threads.begin(); it != threads.end(); it++) {
 		ThreadBase *thread = *it;
@@ -180,7 +178,7 @@ void TwpEngine::update(float elapsed) {
 	// remove threads that are terminated
 	for (auto it = _threads.begin(); it != _threads.end();) {
 		ThreadBase *thread = *it;
-		if(find(threadsToRemove, thread)!=-1) {
+		if (find(threadsToRemove, thread) != -1) {
 			it = _threads.erase(it);
 			delete thread;
 			continue;
@@ -188,6 +186,17 @@ void TwpEngine::update(float elapsed) {
 		it++;
 	}
 
+	// update tasks
+	for (auto it = _tasks.begin(); it != _tasks.end();) {
+		Task *task = *it;
+		if (task->update(elapsed)) {
+			it = _tasks.erase(it);
+			delete task;
+			continue;
+		}
+		it++;
+	}
+
 	// update objects
 	if (_room) {
 		for (int j = 0; j < _room->_layers.size(); j++) {


Commit: 8f933b9e82045d7190fb3b231e2a7886610b9cd3
    https://github.com/scummvm/scummvm/commit/8f933b9e82045d7190fb3b231e2a7886610b9cd3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add actor functions

Changed paths:
  A engines/twp/hud.cpp
  A engines/twp/hud.h
    engines/twp/actorlib.cpp
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index a67074a7e45..a48a8d17eb0 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -194,13 +194,48 @@ static SQInteger actorCostume(HSQUIRRELVM v) {
 }
 
 static SQInteger actorDistanceTo(HSQUIRRELVM v) {
-	warning("TODO: actorDistanceTo not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Object *obj = nullptr;
+	if (sq_gettop(v) == 3)
+		obj = sqobj(v, 3);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	else
+		obj = g_engine->_actor;
+	sqpush(v, distance(actor->_node->getPos(), obj->getUsePos()));
+	return 1;
 }
 
 static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
-	warning("TODO: actorDistanceWithin not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 3) {
+		Object *actor1 = g_engine->_actor;
+		Object *actor2 = sqactor(v, 2);
+		if (!actor2)
+			return sq_throwerror(v, "failed to get actor");
+		Object *obj = sqobj(v, 3);
+		if (!obj)
+			return sq_throwerror(v, "failed to get spot");
+		// not sure about this, needs to be check one day ;)
+		sqpush(v, distance(actor1->_node->getAbsPos(), obj->getUsePos()) < distance(actor2->_node->getAbsPos(), obj->getUsePos()));
+		return 1;
+	} else if (nArgs == 4) {
+		Object *actor = sqactor(v, 2);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+		Object *obj = sqobj(v, 3);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object");
+		int dist;
+		if (SQ_FAILED(sqget(v, 4, dist)))
+			return sq_throwerror(v, "failed to get distance");
+		sqpush(v, distance(actor->_node->getAbsPos(), obj->getUsePos()) < dist);
+		return 1;
+	} else {
+		return sq_throwerror(v, "actorDistanceWithin not implemented");
+	}
 }
 
 // Makes the actor face a given direction.
@@ -252,9 +287,22 @@ static SQInteger actorHidden(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns an array of all the actors that are currently within a specified trigger box.
+//
+// . code-block:: Squirrel
+// local stepsArray = triggerActors(AStreet.bookStoreLampTrigger)
+// if (stepsArray.len()) {    // someone's on the steps
+// }
 static SQInteger actorInTrigger(HSQUIRRELVM v) {
-	warning("TODO: actorInTrigger not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Object *obj = sqobj(v, 3);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	bool inside = obj->contains(actor->_node->getAbsPos());
+	sqpush(v, inside);
+	return 1;
 }
 
 static SQInteger actorInWalkbox(HSQUIRRELVM v) {
@@ -299,8 +347,41 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 	return 0;
 }
 
+// If a direction is specified: makes the actor face a given direction, which cannot be changed no matter what the player does.
+// Directions are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT.
+// If "NO" is specified, it removes all locking and allows the actor to change its facing direction based on player input or otherwise.
 static SQInteger actorLockFacing(HSQUIRRELVM v) {
-	warning("TODO: actorLockFacing not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	switch (sq_gettype(v, 3)) {
+	case OT_INTEGER: {
+		int facing = 0;
+		if (SQ_FAILED(sqget(v, 3, facing)))
+			return sq_throwerror(v, "failed to get facing");
+		actor->lockFacing(facing);
+	} break;
+	case OT_TABLE: {
+		HSQOBJECT obj;
+		int back = FACE_BACK;
+		int front = FACE_FRONT;
+		int left = FACE_LEFT;
+		int right = FACE_RIGHT;
+		int reset = 0;
+		sq_getstackobj(v, 3, &obj);
+		sqgetf(v, obj, "back", back);
+		sqgetf(v, obj, "front", front);
+		sqgetf(v, obj, "left", left);
+		sqgetf(v, obj, "right", right);
+		sqgetf(v, obj, "reset", reset);
+		if (reset != 0)
+			actor->resetLockFacing();
+		else
+			actor->lockFacing((Facing)left, (Facing)right, (Facing)front, (Facing)back);
+	} break;
+	default:
+		return sq_throwerror(v, "unknown facing type");
+	}
 	return 0;
 }
 
@@ -683,7 +764,7 @@ static SQInteger triggerActors(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get object");
 	sq_newarray(v, 0);
 	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
-		Object* actor = *it;
+		Object *actor = *it;
 		if (obj->contains(actor->_node->getPos())) {
 			sq_pushobject(v, actor->_table);
 			sq_arrayappend(v, -2);
@@ -693,7 +774,45 @@ static SQInteger triggerActors(HSQUIRRELVM v) {
 }
 
 static SQInteger verbUIColors(HSQUIRRELVM v) {
-	warning("TODO: verbUIColors not implemented");
+	int actorSlot;
+	if (SQ_FAILED(sqget(v, 2, actorSlot)))
+		return sq_throwerror(v, "failed to get actorSlot");
+	HSQOBJECT table;
+	if (SQ_FAILED(sqget(v, 3, table)))
+		return sq_throwerror(v, "failed to get table");
+	if (!sq_istable(table))
+		return sq_throwerror(v, "failed to get verb definitionTable");
+
+	// get mandatory colors
+	int
+		sentence = 0,
+		verbNormal = 0,
+		verbNormalTint = 0,
+		verbHighlight = 0,
+		verbHighlightTint = 0,
+		inventoryFrame = 0,
+		inventoryBackground = 0;
+	sqgetf(table, "sentence", sentence);
+	sqgetf(table, "verbNormal", verbNormal);
+	sqgetf(table, "verbNormalTint", verbNormalTint);
+	sqgetf(table, "verbHighlight", verbHighlight);
+	sqgetf(table, "verbHighlightTint", verbHighlightTint);
+	sqgetf(table, "inventoryFrame", inventoryFrame);
+	sqgetf(table, "inventoryBackground", inventoryBackground);
+
+	// get optional colors
+	int
+		retroNormal = verbNormal;
+	int retroHighlight = verbNormalTint;
+	int dialogNormal = verbNormal;
+	int dialogHighlight = verbHighlight;
+	sqgetf(table, "retroNormal", retroNormal);
+	sqgetf(table, "retroHighlight", retroHighlight);
+	sqgetf(table, "dialogNormal", dialogNormal);
+	sqgetf(table, "dialogHighlight", dialogHighlight);
+
+	g_engine->_hud._actorSlots[actorSlot - 1].verbUiColors =
+		VerbUiColors{.sentence = Color::rgb(sentence), .verbNormal = Color::rgb(verbNormal), .verbNormalTint = Color::rgb(verbNormalTint), .verbHighlight = Color::rgb(verbHighlight), .verbHighlightTint = Color::rgb(verbHighlightTint), .inventoryFrame = Color::rgb(inventoryFrame), .inventoryBackground = Color::rgb(inventoryBackground), .retroNormal = Color::rgb(retroNormal), .retroHighlight = Color::rgb(retroHighlight), .dialogNormal = Color::rgb(dialogNormal), .dialogHighlight = Color::rgb(dialogHighlight)};
 	return 0;
 }
 
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index ff7c4406c16..a394d0a281e 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -252,7 +252,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
 	Math::Vector2d halfScreen(g_engine->_room->getScreenSize() / 2.f);
-	debug("cameraPanTo: {pos}, dur={duration}, method={interpolation}");
+	debug("cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
 	g_engine->follow(nullptr);
 	g_engine->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
 	return 0;
@@ -276,35 +276,72 @@ static SQInteger sqChr(HSQUIRRELVM v) {
 
 // Returns x coordinates of the mouse in screen coordinates.
 static SQInteger cursorPosX(HSQUIRRELVM v) {
-	// TODO: cursorPosX
-	warning("cursorPosX not implemented");
-	return 0;
-
-	//   let scrPos = winToScreen(mousePos())
-	//   push(v, scrPos.x)
-	//   return 1;
+	sqpush(v, g_engine->_cursor.pos.getX());
+	return 1;
 }
 
 // Returns y coordinates of the mouse in screen coordinates.
 static SQInteger cursorPosY(HSQUIRRELVM v) {
-	// TODO: cursorPosY
-	warning("cursorPosY not implemented");
-	return 0;
-	//   let scrPos = winToScreen(mousePos())
-	//   push(v, scrPos.y)
-	//   return 1;
+	sqpush(v, g_engine->_cursor.pos.getY());
+	return 1;
 }
 
 static SQInteger distance(HSQUIRRELVM v) {
-	// TODO: distance
-	warning("distance not implemented");
-	return 0;
+	if (sq_gettype(v, 2) == OT_INTEGER) {
+		int num1;
+		if (SQ_FAILED(sqget(v, 2, num1)))
+			return sq_throwerror(v, "failed to get num1");
+		int num2;
+		if (SQ_FAILED(sqget(v, 3, num2)))
+			return sq_throwerror(v, "failed to get num2");
+		float d = abs(num1 - num2);
+		sqpush(v, d);
+		return 1;
+	}
+
+	Object *obj1 = sqobj(v, 2);
+	if (!obj1)
+		return sq_throwerror(v, "failed to get object1 or actor1");
+	Object *obj2 = sqobj(v, 3);
+	if (!obj2)
+		return sq_throwerror(v, "failed to get object2 or actor2");
+	Math::Vector2d d = obj1->_node->getAbsPos() - obj2->_node->getAbsPos();
+	sqpush(v, sqrt(d.getX() * d.getX() + d.getY() * d.getY()));
+	return 1;
 }
 
 static SQInteger findScreenPosition(HSQUIRRELVM v) {
-	// TODO: findScreenPosition
-	warning("findScreenPosition not implemented");
-	return 0;
+	if (sq_gettype(v, 2) == OT_INTEGER) {
+		int verb;
+		if (SQ_FAILED(sqget(v, 2, verb)))
+			return sq_throwerror(v, "failed to get verb");
+		ActorSlot *actorSlot = g_engine->_hud.actorSlot(g_engine->_actor);
+		for (int i = 1; i < 22; i++) {
+			Verb vb = actorSlot->verbs[i];
+			if (vb.id.id == verb) {
+				SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
+				SpriteSheetFrame *verbFrame = &verbSheet->frameTable[Common::String::format("%s_en", vb.image.c_str())];
+				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
+				debug("findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
+				sqpush(v, pos);
+				return 1;
+			}
+		}
+		return sq_throwerror(v, "failed to find verb");
+	}
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object or actor");
+	if (obj->inInventory()) {
+		// TODO: sqpush(v, g_engine->_uiInv.getPos(obj));
+		return 1;
+	}
+
+	Math::Vector2d rPos = g_engine->roomToScreen(obj->_node->getAbsPos());
+	Math::Vector2d pos(rPos.getX() + obj->_node->getSize().getX() / 2.f, rPos.getY() + obj->_node->getSize().getY() / 2.f);
+	debug("findScreenPosition({obj.name}) => {pos}");
+	sqpush(v, pos);
+	return 1;
 }
 
 static SQInteger frameCounter(HSQUIRRELVM v) {
@@ -593,10 +630,18 @@ static SQInteger refreshUI(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Returns the x and y dimensions of the current screen/window.
+//
+// .. code-block:: Squirrel
+// function clickedAt(x,y) {
+//     local screenHeight = screenSize().y
+//     local exitButtonB = screenHeight - (exitButtonPadding + 16)
+//     if (y > exitButtonB) { ... }
+// }
 static SQInteger screenSize(HSQUIRRELVM v) {
-	// TODO: screenSize
-	warning("screenSize not implemented");
-	return 0;
+	Math::Vector2d screen = g_engine->_room->getScreenSize();
+	sqpush(v, screen);
+	return 1;
 }
 
 static SQInteger setDebugger(HSQUIRRELVM v) {
@@ -618,8 +663,37 @@ static SQInteger setUserPref(HSQUIRRELVM v) {
 }
 
 static SQInteger setVerb(HSQUIRRELVM v) {
-	// TODO: setVerb
-	warning("setVerb not implemented");
+	int actorSlot;
+	if (SQ_FAILED(sqget(v, 2, actorSlot)))
+		return sq_throwerror(v, "failed to get actor slot");
+	int verbSlot;
+	if (SQ_FAILED(sqget(v, 3, verbSlot)))
+		return sq_throwerror(v, "failed to get verb slot");
+	HSQOBJECT table;
+	if (SQ_FAILED(sqget(v, 4, table)))
+		return sq_throwerror(v, "failed to get verb definitionTable");
+	if (!sq_istable(table))
+		return sq_throwerror(v, "verb definitionTable is not a table");
+	int id;
+	Common::String image;
+	Common::String text;
+	Common::String fun;
+	Common::String key;
+	int flags = 0;
+	sqgetf(table, "verb", id);
+	sqgetf(table, "text", text);
+	if (sqrawexists(table, "image"))
+		sqgetf(table, "image", image);
+	if (sqrawexists(table, "func"))
+		sqgetf(table, "func", fun);
+	if (sqrawexists(table, "key"))
+		sqgetf(table, "key", key);
+	if (sqrawexists(table, "flags"))
+		sqgetf(table, "flags", flags);
+	debug("setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
+	VerbId verbId;
+	verbId.id = id;
+	g_engine->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb{.id = verbId, .image = image, .fun = fun, .text = text, .key = key, .flags = flags};
 	return 0;
 }
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
new file mode 100644
index 00000000000..4c532c0a468
--- /dev/null
+++ b/engines/twp/hud.cpp
@@ -0,0 +1,176 @@
+/* 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 "twp/hud.h"
+#include "twp/twp.h"
+#include "math/vector2d.h"
+#include "graphics/opengl/debug.h"
+#include "graphics/opengl/system_headers.h"
+
+namespace Twp {
+
+HudShader::HudShader() {
+	const char *verbVtxShader = R"(#version 110
+		precision mediump float;
+	attribute vec2 a_position;
+	attribute vec4 a_color;
+	attribute vec2 a_texCoords;
+
+	uniform vec4 u_shadowColor;
+	uniform vec4 u_normalColor;
+	uniform vec4 u_highlightColor;
+	uniform vec2 u_ranges;
+	uniform mat4 u_transform;
+
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	varying vec4 v_shadowColor;
+	varying vec4 v_normalColor;
+	varying vec4 v_highlightColor;
+	varying vec2 v_ranges;
+
+	void main(void) {
+		v_color = a_color;
+		v_texCoords = a_texCoords;
+		v_shadowColor = u_shadowColor;
+		v_normalColor = u_normalColor;
+		v_highlightColor = u_highlightColor;
+		v_ranges = u_ranges;
+		vec4 worldPosition = vec4(a_position, 0, 1);
+		vec4 normalizedPosition = u_transform * worldPosition;
+		gl_Position = normalizedPosition;
+	})";
+
+const char* verbFgtShader = R"(#version 110
+#ifdef GL_ES
+precision highp float;
+#endif
+
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	varying vec4 v_shadowColor;
+	varying vec4 v_normalColor;
+	varying vec4 v_highlightColor;
+	varying vec2 v_ranges;
+	uniform sampler2D u_texture;
+
+	void main(void) {
+		float shadows = v_ranges.x;
+		float highlights = v_ranges.y;
+		vec4 texColor = texture2D(u_texture, v_texCoords);
+		if (texColor.g <= shadows) {
+			texColor *= v_shadowColor;
+		} else if (texColor.g >= highlights) {
+			texColor *= v_highlightColor;
+		} else {
+			texColor *= v_normalColor;
+		}
+		texColor *= v_color;
+		gl_FragColor = texColor;
+	})";
+	init(verbVtxShader, verbFgtShader);
+
+	GL_CALL(_rangesLoc = glGetUniformLocation(program, "u_ranges"));
+	GL_CALL(_shadowColorLoc = glGetUniformLocation(program, "u_shadowColor"));
+	GL_CALL(_normalColorLoc = glGetUniformLocation(program, "u_normalColor"));
+	GL_CALL(_highlightColorLoc = glGetUniformLocation(program, "u_highlightColor"));
+}
+
+HudShader::~HudShader() {}
+
+void HudShader::applyUniforms() {
+	float value[]={0.8f, 0.8f};
+	GL_CALL(glUniform2fv(_rangesLoc, 1, value));
+	GL_CALL(glUniform4fv(_shadowColorLoc, 1, _shadowColor.v));
+	GL_CALL(glUniform4fv(_normalColorLoc, 1, _normalColor.v));
+	GL_CALL(glUniform4fv(_highlightColorLoc, 1, _highlightColor.v));
+}
+
+Hud::Hud() : Node("hud") {
+	_zOrder = 100;
+	for (int i = 0; i < NUMACTORS; i++) {
+		ActorSlot *slot = &_actorSlots[i];
+		slot->actor = nullptr;
+	}
+}
+
+ActorSlot *Hud::actorSlot(Object *actor) {
+	for (int i = 0; i < NUMACTORS; i++) {
+		ActorSlot *slot = &_actorSlots[i];
+		if (slot->actor == actor) {
+			return slot;
+		}
+	}
+	return nullptr;
+}
+
+void Hud::drawCore(Math::Matrix4 trsf) {
+	ActorSlot *slot = this->actorSlot(_actor);
+	if(!slot) return;
+
+	// draw HUD background
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	// TODO: let classic = prefs(ClassicSentence);
+	bool classic = true;
+	const SpriteSheetFrame &backingFrame = gameSheet->frameTable[classic ? "ui_backing_tall" : "ui_backing"];
+	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
+	float alpha = 1.0f; // prefs(UiBackingAlpha);
+	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha), trsf);
+
+	// TODO; let verbHlt = prefs(InvertVerbHighlight);
+	bool verbHlt = true;
+	Color verbHighlight = verbHlt ? Color() : slot->verbUiColors.verbHighlight;
+	Color verbColor = verbHlt ? slot->verbUiColors.verbHighlight : Color();
+
+	// draw actor's verbs
+	SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
+	Texture *verbTexture = g_engine->_resManager.texture(verbSheet->meta.image);
+	// let lang = prefs(Lang);
+	Common::String lang = "en";
+	bool retroVerbs = false; // prefs(RetroVerbs);
+	Common::String verbSuffix = retroVerbs ? "_retro" : "";
+
+	Shader *saveShader = g_engine->getGfx().getShader();
+	g_engine->getGfx().use(&_shader);
+	_shader._shadowColor = slot->verbUiColors.verbNormalTint;
+	_shader._normalColor = slot->verbUiColors.verbHighlight;
+	_shader._highlightColor = slot->verbUiColors.verbHighlightTint;
+
+	bool isOver = false;
+	for (int i = 1; i < 22; i++) {
+		const Verb &verb = slot->verbs[i];
+		if (verb.image.size() > 0) {
+			const SpriteSheetFrame &verbFrame = verbSheet->frameTable[Common::String::format("%s%s_%s", verb.image.c_str(), verbSuffix.c_str(), lang.c_str())];
+			bool over = verbFrame.spriteSourceSize.contains(_mousePos.getX(), _mousePos.getY());
+			if (over)
+				isOver = true;
+			Color color = (over || (verb.id.id == _defaultVerbId)) ? verbHighlight : verbColor;
+			if (_mouseClick && over) {
+				_verb = verb;
+			}
+			g_engine->getGfx().drawSprite(verbFrame.frame, *verbTexture, color, trsf);
+		}
+	}
+	g_engine->getGfx().use(saveShader);
+	_over = isOver;
+}
+
+} // namespace Twp
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
new file mode 100644
index 00000000000..1f33a5759fe
--- /dev/null
+++ b/engines/twp/hud.h
@@ -0,0 +1,112 @@
+/* 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 "twp/gfx.h"
+#include "twp/object.h"
+#include "twp/scenegraph.h"
+
+#define NUMVERBS 9
+#define NUMACTORS 6
+
+#ifndef TWP_HUD_H
+#define TWP_HUD_H
+
+namespace Twp {
+
+struct VerbUiColors {
+	Color sentence;
+	Color verbNormal;
+	Color verbNormalTint;
+	Color verbHighlight;
+	Color verbHighlightTint;
+	Color dialogNormal;
+	Color dialogHighlight;
+	Color inventoryFrame;
+	Color inventoryBackground;
+	Color retroNormal;
+	Color retroHighlight;
+};
+
+struct Verb {
+	VerbId id;
+	Common::String image;
+	Common::String fun;
+	Common::String text;
+	Common::String key;
+	int flags;
+};
+
+struct ActorSlot {
+	VerbUiColors verbUiColors;
+	Verb verbs[22];
+	bool selectable;
+	Object *actor;
+};
+
+class Hud;
+struct VerbRect {
+	Hud *hud;
+	int index;
+};
+
+class HudShader: public Shader {
+public:
+	HudShader();
+	virtual ~HudShader() override;
+
+private:
+	virtual void applyUniforms() final;
+
+public:
+	Color _shadowColor;
+	Color _normalColor;
+	Color _highlightColor;
+
+private:
+	int _rangesLoc;
+	int _shadowColorLoc;
+	int _normalColorLoc;
+	int _highlightColorLoc;
+};
+
+class Hud : public Node {
+public:
+	Hud();
+
+	ActorSlot* actorSlot(Object* actor);
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+
+public:
+	ActorSlot _actorSlots[NUMACTORS];
+	Object *_actor = nullptr;
+	VerbRect _verbRects[NUMVERBS];
+	Verb _verb;
+	HudShader _shader;
+	Math::Vector2d _mousePos;
+	bool _mouseClick = false;
+	bool _over = false;
+	int _defaultVerbId = 0;
+};
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 98fcc91561a..acc6cc82492 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -55,6 +55,7 @@ MODULE_OBJS = \
 	yack.o \
 	dialog.o \
 	shaders.o \
+	hud.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index ae91bf48adb..83b8adcd071 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -30,11 +30,6 @@
 
 namespace Twp {
 
-#define STAND_ANIMNAME "stand"
-#define HEAD_ANIMNAME "head"
-#define WALK_ANIMNAME "walk"
-#define REACH_ANIMNAME "reach"
-
 Object::Object()
 	: _talkOffset(0, 90) {
 	_node = new Node("newObj");
@@ -156,7 +151,7 @@ void Object::trig(const Common::String &name) {
 		if (_triggers.contains(trigNum)) {
 			_triggers[trigNum]->trig();
 		} else {
-			//warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+			// warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
 		}
 	} else {
 		error("todo: trig %s", name.c_str());
@@ -518,6 +513,21 @@ void Object::say(const Common::StringArray &texts, Color color) {
 	_talkingState.say(texts, this);
 }
 
+void Object::resetLockFacing() {
+	_facingMap.clear();
+}
+
+void Object::lockFacing(int facing) {
+  _facingLockValue = facing;
+}
+
+void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
+	_facingMap[FACE_LEFT] = left;
+	_facingMap[FACE_RIGHT] = right;
+	_facingMap[FACE_FRONT] = front;
+	_facingMap[FACE_BACK] = back;
+}
+
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
 	// TODO: obj->setTalking(new Talking(obj, texts, color));
 }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index e27810c40df..449e5df5ed8 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -30,6 +30,11 @@
 #include "twp/ids.h"
 #include "twp/gfx.h"
 
+#define STAND_ANIMNAME "stand"
+#define HEAD_ANIMNAME "head"
+#define WALK_ANIMNAME "walk"
+#define REACH_ANIMNAME "reach"
+
 namespace Twp {
 
 enum ObjectType {
@@ -124,6 +129,9 @@ public:
 
 	void showLayer(const Common::String &layer, bool visible);
 	Facing getFacing() const;
+	void lockFacing(int facing);
+	void lockFacing(Facing left, Facing right, Facing front, Facing back);
+	void resetLockFacing();
 	void trig(const Common::String &name);
 
 	void setPop(int count);
@@ -163,6 +171,7 @@ public:
 	void setRotateTo(Motor* rotateTo);
 	void setMoveTo(Motor* moveTo);
 	void setWalkTo(Motor* walkTo);
+	Motor* getWalkTo() const { return _walkTo; }
 	void setTalking(Motor* talking);
 	void setBlink(Motor* blink);
 	void setTurnTo(Motor* turnTo);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index ebaa04e3930..dee4e1713ea 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -163,7 +163,7 @@ void Node::setSize(Math::Vector2d size) {
 }
 
 static int cmpNodes(const Node *x, const Node *y) {
-	return y->getZSort() < x->getZSort();
+	return y->getZSort() <= x->getZSort();
 }
 
 void Node::draw(Math::Matrix4 parent) {
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 8883946d133..b65ed0ef8d8 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -97,6 +97,7 @@ public:
 	void setAnchor(Math::Vector2d anchor);
 	void setAnchorNorm(Math::Vector2d anchorNorm);
 	void setSize(Math::Vector2d size);
+	Math::Vector2d getSize() const { return _size; }
 	virtual Rectf getRect() const;
 
 	void draw(Math::Matrix4 parent = Math::Matrix4());
@@ -116,7 +117,7 @@ private:
 protected:
 	Common::String _name;
 	Math::Vector2d _pos;
-	float _zOrder = 0.f;
+	int _zOrder = 0;
 	Node *_parent = nullptr;
 	Common::Array<Node *> _children;
 	Math::Vector2d _offset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
@@ -168,10 +169,11 @@ public:
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
-
-private:
+public:
     const ObjectAnimation* _anim = nullptr;
     bool _disabled = false;
+
+private:
     Common::String _sheet;
     Common::Array<Common::String> _frames;
     int _frameIndex = 0;
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index cbfbb2a343b..5e0baf4d16c 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -316,7 +316,7 @@ int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	return nparams;
 }
 
-void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char* name) {
+void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char *name) {
 	sq_pushobject(v, o);
 	sq_pushstring(v, name, -1);
 	sq_get(v, -2);
@@ -337,6 +337,13 @@ void sqexec(HSQUIRRELVM v, const char *code, const char *filename) {
 	sq_settop(v, top);
 }
 
+ThreadBase *sqthread(HSQUIRRELVM v, int i) {
+	int id;
+	if (SQ_SUCCEEDED(sqget(v, i, id)))
+		return sqthread(id);
+	return nullptr;
+}
+
 ThreadBase *sqthread(int id) {
 	if (g_engine->_cutscene) {
 		if (g_engine->_cutscene->getId() == id) {
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 9b7d8ece921..6f7e77312fe 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -150,6 +150,7 @@ Object *sqobj(HSQUIRRELVM v, int i);
 Object *sqactor(HSQOBJECT table);
 Object *sqactor(HSQUIRRELVM v, int i);
 ThreadBase *sqthread(HSQUIRRELVM v);
+ThreadBase *sqthread(HSQUIRRELVM v, int id);
 ThreadBase *sqthread(int id);
 
 template<typename... T>
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index f3e28b7b7f8..6c511fb7748 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -25,6 +25,7 @@
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/task.h"
+#include "twp/scenegraph.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
 #include "twp/squirrel/sqobject.h"
@@ -196,16 +197,34 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	return -666;
 }
 
+static bool isAnimating(Object *obj) {
+	return obj->_nodeAnim->_anim && !obj->_nodeAnim->_disabled && obj->_animName != obj->getAnimName(STAND_ANIMNAME);
+}
+
+// When called in a function started with startthread, execution is suspended until animatingItem has completed its animation.
+// Note, animatingItem can be an actor or an object.
+// It is an error to call breakwhileanimating in a function that was not started with `startthread`.
+//
+// . code-block:: Squirrel
+// actorFace(ray, FACE_LEFT)
+// actorCostume(ray, "RayVomit")
+// actorPlayAnimation(ray, "vomit")
+// breakwhileanimating(ray)
+// actorCostume(ray, "RayAnimation")
 static SQInteger breakwhileanimating(HSQUIRRELVM v) {
-	warning("TODO: breakwhileanimating: not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	return breakwhilecond(
+		v, [obj]() { return isAnimating(obj); }, "breakwhileanimating(%s)", obj->_key.c_str());
 }
 
 // Breaks while a camera is moving.
 // Once the thread finishes execution, the method will continue running.
 // It is an error to call breakwhilecamera in a function that was not started with startthread.
 static SQInteger breakwhilecamera(HSQUIRRELVM v) {
-	return breakwhilecond(v, [] { return g_engine->_camera.isMoving();}, "breakwhilecamera()");
+	return breakwhilecond(
+		v, [] { return g_engine->_camera.isMoving(); }, "breakwhilecamera()");
 }
 
 // Breaks while a cutscene is running.
@@ -218,7 +237,7 @@ static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
 	return breakwhilecond(
-		v, [] { return g_engine->_dialog.getState() != DialogState::None;}, "breakwhiledialog()");
+		v, [] { return g_engine->_dialog.getState() != DialogState::None; }, "breakwhiledialog()");
 }
 
 static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
@@ -262,14 +281,71 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
 }
 
+// Returns true if at least 1 actor is talking.
+static bool isSomeoneTalking() {
+	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
+		Object *obj = *it;
+		if (obj->getTalking() && obj->getTalking()->isEnabled())
+			return true;
+	}
+	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
+		Layer *layer = *it;
+		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
+			Object *obj = *it2;
+			if (obj->getTalking() && obj->getTalking()->isEnabled())
+				return true;
+		}
+	}
+	return false;
+}
+
+// If an actor is specified, breaks until actor has finished talking.
+// If no actor is specified, breaks until ALL actors have finished talking.
+// Once talking finishes, the method will continue running.
+// It is an error to call breakwhiletalking in a function that was not started with startthread.
+//
+// . code-block:: Squirrel
+// while(closeToWillie()) {
+//     local line = randomfrom(lines)
+//     breakwhiletalking(willie)
+//     mumbleLine(willie, line)
+//     breakwhiletalking(willie)
+// }
 static SQInteger breakwhiletalking(HSQUIRRELVM v) {
-	warning("TODO: breakwhiletalking: not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	if (nArgs == 1) {
+		return breakwhilecond(
+			v, []() { return isSomeoneTalking(); }, "breakwhiletalking(all)");
+	}
+	if (nArgs == 2) {
+		Object *obj = sqobj(v, 2);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object");
+		return breakwhilecond(
+			v, [obj]() { return obj->getTalking() && obj->getTalking()->isEnabled(); }, "breakwhiletalking(%s)", obj->_name.c_str());
+	}
+
+	return sq_throwerror(v, "Invalid number of arguments for breakwhiletalking");
 }
+
+// If an actor is specified, breaks until actor has finished walking.
+// Once arrived at destination, the method will continue running.
+// It is an error to call breakwhilewalking in a function that was not started with `startthread`.
+//
+// . code-block:: Squirrel
+// startthread(@(){
+//    actorWalkTo(currentActor, Nickel.copyTron)
+//    breakwhilewalking(currentActor)
+//    pushSentence(VERB_USE, nickel, Nickel.copyTron)
+//})
 static SQInteger breakwhilewalking(HSQUIRRELVM v) {
-	warning("TODO: breakwhilewalking: not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	return breakwhilecond(
+		v, [obj]() { return obj->getWalkTo() && obj->getWalkTo()->isEnabled(); }, "breakwhilewalking(%s)", obj->_name.c_str());
 }
+
 static SQInteger breakwhilesound(HSQUIRRELVM v) {
 	warning("TODO: breakwhilesound: not implemented");
 	return 0;
@@ -314,7 +390,8 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger cutsceneOverride(HSQUIRRELVM v) {
-	warning("TODO: cutsceneOverride: not implemented");
+	debug("cutsceneOverride");
+	g_engine->_cutscene->cutsceneOverride();
 	return 0;
 }
 
@@ -391,17 +468,35 @@ static SQInteger isInputOn(HSQUIRRELVM v) {
 }
 
 static SQInteger logEvent(HSQUIRRELVM v) {
-	warning("TODO: logEvent: not implemented");
+	SQInteger numArgs = sq_gettop(v);
+	Common::String msg, event;
+	if (SQ_FAILED(sqget(v, 2, event)))
+		return sq_throwerror(v, "failed to get event");
+	if (numArgs == 3) {
+		if (SQ_FAILED(sqget(v, 3, event)))
+			return sq_throwerror(v, "failed to get message");
+		msg = event + ": " + msg;
+	}
+	debug("%s", msg.c_str());
 	return 0;
 }
 
+// Like a print statement, but gets sent to the output log file instead.
+// Useful for testing.
 static SQInteger logInfo(HSQUIRRELVM v) {
-	warning("TODO: logInfo: not implemented");
+	Common::String msg;
+	if (SQ_FAILED(sqget(v, 2, msg)))
+		return sq_throwerror(v, "failed to get message");
+	debug("%s", msg.c_str());
 	return 0;
 }
 
+// Sends a warning message to the output log file.
 static SQInteger logWarning(HSQUIRRELVM v) {
-	warning("TODO: logWarning: not implemented");
+	Common::String msg;
+	if (SQ_FAILED(sqget(v, 2, msg)))
+		return sq_throwerror(v, "failed to get message");
+	warning("%s", msg.c_str());
 	return 0;
 }
 
@@ -459,13 +554,43 @@ static SQInteger stopthread(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Returns the thread ID of the currently running script/thread.
+//
+// If no thread is running, it will return 0.
+//
+// . code-block:: Squirrel
+// Phone <-
+// {
+//     phoneRingingTID = 0
+//     script phoneRinging(number) {
+//         phoneRingingTID = threadid()
+//         ...
+//     }
+//     function clickedButton(...) {
+//         if (!phoneRingingTID) {
+//             ...
+//         }
+//     }
+// }
 static SQInteger threadid(HSQUIRRELVM v) {
-	warning("TODO: threadid: not implemented");
-	return 0;
+	ThreadBase *t = sqthread(v);
+	if (t)
+		sqpush(v, t->getId());
+	else
+		sqpush(v, 0);
+	return 1;
 }
 
+// Specify whether a thread should be pauseable or not.
+// If a thread is not pauseable, it won't be possible to pause this thread.
 static SQInteger threadpauseable(HSQUIRRELVM v) {
-	warning("TODO: threadpauseable: not implemented");
+	ThreadBase *t = sqthread(v, 2);
+	if (t)
+		return sq_throwerror(v, "failed to get thread");
+	int pauseable = 0;
+	if (SQ_FAILED(sqget(v, 3, pauseable)))
+		return sq_throwerror(v, "failed to get pauseable");
+	t->_pauseable = pauseable != 0;
 	return 0;
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 25e38e46750..c699e954621 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -435,7 +435,7 @@ Common::Error TwpEngine::run() {
 		uint32 newTime = _system->getMillis();
 		uint32 delta = newTime - time;
 		time = newTime;
-		update(delta / 1000.f);
+		update(8.f*delta / 1000.f);
 		draw();
 		_cursor.update();
 
@@ -848,4 +848,26 @@ Object *TwpEngine::objAt(Math::Vector2d pos) {
 	return result;
 }
 
+void TwpEngine::setActor(Object *actor, bool userSelected) {
+	_actor = actor;
+	_hud._actor = actor;
+	if (!_hud.getParent() && actor) {
+		_screenScene.addChild(&_hud);
+	} else if (_hud.getParent() && !actor) {
+		_screenScene.removeChild(&_hud);
+	}
+
+	// call onActorSelected callbacks
+	sqcall("onActorSelected", actor->_table, userSelected);
+	Room *room = !actor ? nullptr : actor->_room;
+	if (room) {
+		if (sqrawexists(room->_table, "onActorSelected")) {
+			sqcall(room->_table, "onActorSelected", actor->_table, userSelected);
+		}
+	}
+
+	if (actor)
+		follow(actor);
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 01f32a5d253..56a5e3f3e54 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -42,6 +42,7 @@
 #include "twp/tsv.h"
 #include "twp/scenegraph.h"
 #include "twp/dialog.h"
+#include "twp/hud.h"
 
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
@@ -120,7 +121,7 @@ public:
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
-	void setActor(Object *actor) { _actor = actor; }
+	void setActor(Object *actor, bool userSelected = false);
 	Object *objAt(Math::Vector2d pos);
 
 	Room *defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
@@ -186,6 +187,7 @@ public:
 		}
 		bool isLeftClick() { return oldLeftDown && !leftDown; }
 	} _cursor;
+	Hud _hud;
 
 private:
 	Gfx _gfx;
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 8f55291da3b..c7c211f4543 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -128,4 +128,14 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 	}
 }
 
+float distanceSquared(Math::Vector2d p1, Math::Vector2d p2) {
+	float dx = p1.getX() - p2.getX();
+	float dy = p1.getY() - p2.getY();
+	return dx * dx + dy * dy;
+}
+
+float distance(Math::Vector2d p1, Math::Vector2d p2) {
+	return sqrt(distanceSquared(p1, p2));
+}
+
 } // namespace Twp
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 932d2994fba..743a579b2e5 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -49,6 +49,8 @@ Math::Vector2d parseVec2(const Common::String &s);
 Common::Rect parseRect(const Common::String &s);
 void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
 
+float distance(Math::Vector2d p1, Math::Vector2d p2);
+
 template<typename T>
 int find(Common::Array<T>& array, const T& o) {
 	int index = -1;


Commit: 736129c6671c711201ccdabcd03fa67bd030edc6
    https://github.com/scummvm/scummvm/commit/736129c6671c711201ccdabcd03fa67bd030edc6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix objects duplicated

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/genlib.cpp
    engines/twp/gfx.h
    engines/twp/hud.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/yack.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index a48a8d17eb0..2e300e93092 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -89,7 +89,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 			Math::Vector2d pos = spot->_node->getPos() + spot->_usePos;
 			actor->setRoom(spot->_room);
 			actor->stopWalking();
-			debug("actorAt %s at %s, room '%s'", actor->_key.c_str(), spot->_key.c_str(), spot->_room->_name.c_str());
+			debug("actorAt %s at %s (%d, %d), room '%s'", actor->_key.c_str(), spot->_key.c_str(), (int)pos.getX(), (int)pos.getY(), spot->_room->_name.c_str());
 			actor->_node->setPos(pos);
 			actor->setFacing(getFacing(spot->_useDir, actor->getFacing()));
 		} else {
@@ -343,8 +343,58 @@ static SQInteger actorShowLayer(HSQUIRRELVM v) {
 }
 
 static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
-	warning("TODO: actorSlotSelectable not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	switch (nArgs) {
+	case 2: {
+		int selectable;
+		if (SQ_FAILED(sqget(v, 2, selectable)))
+			return sq_throwerror(v, "failed to get selectable");
+		// TODO:
+		// switch(selectable) {
+		// case 0:
+		//   g_engine->actorSwitcher.mode.excl asOn;
+		//   break;
+		// case 1:
+		//   g_engine->actorSwitcher.mode.incl asOn;
+		//   break;
+		// case 2:
+		//   g_engine->actorSwitcher.mode.incl asTemporaryUnselectable;
+		//   break;
+		// case 3:
+		//   g_engine->actorSwitcher.mode.excl asTemporaryUnselectable;
+		//   break;
+		// default:
+		//   return sq_throwerror(v, "invalid selectable value");
+		// }
+		return 0;
+	}
+	case 3: {
+		bool selectable;
+		if (SQ_FAILED(sqget(v, 3, selectable)))
+			return sq_throwerror(v, "failed to get selectable");
+		if (sq_gettype(v, 2) == OT_INTEGER) {
+			int slot;
+			if (SQ_FAILED(sqget(v, 2, slot)))
+				return sq_throwerror(v, "failed to get slot");
+			g_engine->_hud._actorSlots[slot - 1].selectable = selectable;
+		} else {
+			Object *actor = sqactor(v, 2);
+			if (!actor)
+				return sq_throwerror(v, "failed to get actor");
+			Common::String key;
+			sqgetf(actor->_table, "_key", key);
+			debug("actorSlotSelectable(%s, %s)", key.c_str(), selectable ? "yes" : "no");
+			ActorSlot *slot = g_engine->_hud.actorSlot(actor);
+			if (!slot)
+				warning("slot for actor %s not found", key.c_str());
+			else
+				slot->selectable = selectable;
+		}
+		return 0;
+	}
+	default:
+		return sq_throwerror(v, "invalid number of arguments");
+	}
 }
 
 // If a direction is specified: makes the actor face a given direction, which cannot be changed no matter what the player does.
@@ -568,8 +618,20 @@ static SQInteger actorWalking(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Sets the walk speed of an actor.
+//
+// The numbers are in pixel's per second.
+// The vertical movement is typically half (or more) than the horizontal movement to simulate depth in the 2D world.
 static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
-	warning("TODO: actorWalkSpeed not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int x, y;
+	if (SQ_FAILED(sqget(v, 3, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 4, y)))
+		return sq_throwerror(v, "failed to get y");
+	actor->_walkSpeed = Math::Vector2d(x, y);
 	return 0;
 }
 
@@ -579,7 +641,11 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 }
 
 static SQInteger addSelectableActor(HSQUIRRELVM v) {
-	warning("TODO: addSelectableActor not implemented");
+	int slot;
+	if (SQ_FAILED(sqget(v, 2, slot)))
+		return sq_throwerror(v, "failed to get slot");
+	Object *actor = sqactor(v, 3);
+	g_engine->_hud._actorSlots[slot - 1].actor = actor;
 	return 0;
 }
 
@@ -643,7 +709,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 			}
 		}
 	}
-	debug("sayline: {obj.key}, {texts}");
+	debug("sayline: %s, {texts}", obj->_key.c_str());
 	obj->say(texts, obj->_talkColor);
 	return 0;
 }
@@ -668,8 +734,43 @@ static SQInteger sayLine(HSQUIRRELVM v) {
 	return sayOrMumbleLine(v);
 }
 
+// Say a line of dialog and play the appropriate talking animations.
+// In the first example, the actor ray will say the line.
+// In the second, the selected actor will say the line.
+// In the third example, the first line is displayed, then the second one.
+// See also:
+// - `mumbleLine method`
 static SQInteger sayLineAt(HSQUIRRELVM v) {
-	warning("TODO: sayLineAt not implemented");
+	int x, y;
+	Common::String text;
+	float duration = -1.0f;
+	if (SQ_FAILED(sqget(v, 2, x)))
+		return sq_throwerror(v, "failed to get x");
+	if (SQ_FAILED(sqget(v, 3, y)))
+		return sq_throwerror(v, "failed to get y");
+	Color color;
+	if (sq_gettype(v, 4) == OT_INTEGER) {
+		int c;
+		if (SQ_FAILED(sqget(v, 4, c)))
+			return sq_throwerror(v, "failed to get color");
+		color = Color::rgb(c);
+		if (SQ_FAILED(sqget(v, 5, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		if (SQ_FAILED(sqget(v, 6, text)))
+			return sq_throwerror(v, "failed to get text");
+	} else {
+		Object *actor = sqactor(v, 4);
+		if (!actor)
+			return sq_throwerror(v, "failed to get actor");
+		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getAbsPos());
+		x = pos.getX();
+		y = pos.getY();
+		color = actor->_talkColor;
+		if (SQ_FAILED(sqget(v, 6, text)))
+			return sq_throwerror(v, "failed to get text");
+	}
+
+	warning("TODO: saylineAt: (%d,%d) text=%s color=%s duration=%f", x, y, text.c_str(), color.toStr().c_str(), duration);
 	return 0;
 }
 
@@ -691,8 +792,13 @@ static SQInteger isActorOnScreen(HSQUIRRELVM v) {
 }
 
 static SQInteger isActorSelectable(HSQUIRRELVM v) {
-	warning("TODO: isActorSelectable not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	ActorSlot *slot = g_engine->_hud.actorSlot(actor);
+	bool selectable = !slot ? false : slot->selectable;
+	sqpush(v, selectable);
+	return 1;
 }
 
 // If an actor is specified, returns true otherwise returns false.
@@ -801,8 +907,7 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	sqgetf(table, "inventoryBackground", inventoryBackground);
 
 	// get optional colors
-	int
-		retroNormal = verbNormal;
+	int retroNormal = verbNormal;
 	int retroHighlight = verbNormalTint;
 	int dialogNormal = verbNormal;
 	int dialogHighlight = verbHighlight;
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index a394d0a281e..1da69ca152e 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -316,6 +316,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 2, verb)))
 			return sq_throwerror(v, "failed to get verb");
 		ActorSlot *actorSlot = g_engine->_hud.actorSlot(g_engine->_actor);
+		if(!actorSlot) return 0;
 		for (int i = 1; i < 22; i++) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
@@ -339,7 +340,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 
 	Math::Vector2d rPos = g_engine->roomToScreen(obj->_node->getAbsPos());
 	Math::Vector2d pos(rPos.getX() + obj->_node->getSize().getX() / 2.f, rPos.getY() + obj->_node->getSize().getY() / 2.f);
-	debug("findScreenPosition({obj.name}) => {pos}");
+	debug("findScreenPosition(%s) => (%f,%f)", obj->_name.c_str(), pos.getX(), pos.getY());
 	sqpush(v, pos);
 	return 1;
 }
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index a6f9bcdfe43..b2bffa65af1 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -56,12 +56,28 @@ struct Color {
 	}
 
 	static Color rgb(int c) {
-		return Color((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF));
+		return create((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF), (uint8)((c >> 24) & 0xFF));
 	}
 
 	static Color create(uint8 red, uint8 green, uint8 blue, uint8 alpha = 0xFF) {
 		return Color(red / 255.f, green / 255.f, blue / 255.f, alpha / 255.f);
 	}
+
+	Common::String toStr() {
+		return Common::String::format("rgba(%f,%f,%f,%f)", rgba.r, rgba.g, rgba.b, rgba.a);
+	}
+
+	Color operator-(const Color& c) {
+		return Color(rgba.r - c.rgba.r, rgba.g - c.rgba.g, rgba.b - c.rgba.b, rgba.a - c.rgba.a);
+	}
+
+	Color operator+(const Color& c) {
+		return Color(rgba.r + c.rgba.r, rgba.g + c.rgba.g, rgba.b + c.rgba.b, rgba.a + c.rgba.a);
+	}
+
+	Color operator*(float f) {
+  		return Color(rgba.r * f, rgba.g * f, rgba.b * f, rgba.a * f);
+	}
 };
 
 // This is a point in 2D with a color and texture coordinates
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 4c532c0a468..9eafd453911 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -29,7 +29,6 @@ namespace Twp {
 
 HudShader::HudShader() {
 	const char *verbVtxShader = R"(#version 110
-		precision mediump float;
 	attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
@@ -60,10 +59,6 @@ HudShader::HudShader() {
 	})";
 
 const char* verbFgtShader = R"(#version 110
-#ifdef GL_ES
-precision highp float;
-#endif
-
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	varying vec4 v_shadowColor;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 996353a52d0..2e85dc1ac7c 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -20,6 +20,7 @@
  *
  */
 
+#include "twp/twp.h"
 #include "twp/motor.h"
 #include "twp/object.h"
 #include "twp/scenegraph.h"
@@ -112,4 +113,19 @@ void Shake::update(float elapsed) {
 	_node->setOffset(Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime)));
 }
 
+OverlayTo::OverlayTo(float duration, Room *room, Color to)
+	: _room(room),
+	  _to(to),
+	  _tween(g_engine->_room->getOverlay(), to, duration, InterpolationMethod()) {
+}
+
+OverlayTo::~OverlayTo() {}
+
+void OverlayTo::update(float elapsed) {
+	_tween.update(elapsed);
+	_room->setOverlay(_tween.current());
+	if (!_tween.running())
+		disable();
+}
+
 } // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index da7abd1d486..b06eee7eb80 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -56,7 +56,7 @@ public:
 			}
 			if (easing_f.func) {
 				f = easing_f.func(f);
-				value = frm + f * delta;
+				value = frm + delta * f;
 			}
 		} else {
 			value = to;
@@ -172,6 +172,19 @@ private:
 	float _elapsed = 0.f;
 };
 
+class OverlayTo : public Motor {
+public:
+	virtual ~OverlayTo();
+	OverlayTo(float duration, Room* room, Color to);
+
+	virtual void update(float elapsed) override;
+
+private:
+	Room *_room = nullptr;
+	Color _to;
+	Tween<Color> _tween;
+};
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 83b8adcd071..bf72f626eff 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -138,7 +138,7 @@ void Object::showLayer(const Common::String &layer, bool visible) {
 Facing Object::getFacing() const {
 	if (_facingLockValue != 0)
 		return (Facing)_facingLockValue;
-	else if (_facingMap.contains(_facing))
+	if (_facingMap.contains(_facing))
 		return _facingMap[_facing];
 	return _facing;
 }
@@ -451,9 +451,8 @@ void Object::setShakeTo(Motor *shakeTo) { SET_MOTOR(shakeTo); }
 void Object::setJiggleTo(Motor *jiggleTo) { SET_MOTOR(jiggleTo); }
 
 void Object::update(float elapsedSec) {
-	// TODO: update
-	//   if not self.dependentObj.isNil:
-	//     self.node.visible = self.dependentObj.getState() == self.dependentState
+	if (_dependentObj)
+		_node->setVisible(_dependentObj->getState() == _dependentState);
 	if (_alphaTo)
 		_alphaTo->update(elapsedSec);
 	if (_rotateTo)
@@ -476,6 +475,7 @@ void Object::update(float elapsedSec) {
 	if (_nodeAnim)
 		_nodeAnim->update(elapsedSec);
 
+	// TODO: update
 	//   if self.icons.len > 1 and self.iconFps > 0:
 	//     self.iconElapsed += elapsedSec
 	//     if self.iconElapsed > (1f / self.iconFps.float32):
@@ -518,7 +518,7 @@ void Object::resetLockFacing() {
 }
 
 void Object::lockFacing(int facing) {
-  _facingLockValue = facing;
+	_facingLockValue = facing;
 }
 
 void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
@@ -528,6 +528,43 @@ void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
 	_facingMap[FACE_BACK] = back;
 }
 
+int Object::flags() {
+	int result = 0;
+	if (sqrawexists(_table, "flags"))
+		sqgetf(_table, "flags", result);
+	return result;
+}
+
+UseFlag Object::useFlag() {
+	int flags = getFlags();
+	if (flags & USE_WITH)
+		return ufUseWith;
+	if (flags & USE_ON)
+		return ufUseOn;
+	if (flags & USE_IN)
+		return ufUseIn;
+	return ufNone;
+}
+
+float Object::getScale() {
+	if (getPop() > 0)
+		return 4.25f + popScale() * 0.25f;
+	return 4.f;
+}
+
+void Object::removeInventory(Object *obj) {
+	int i = find(_inventory, obj);
+	if (i >= 0) {
+		_inventory.remove_at(i);
+		obj->_owner = nullptr;
+	}
+}
+
+void Object::removeInventory() {
+	if (_owner)
+		_owner->removeInventory(this);
+}
+
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
 	// TODO: obj->setTalking(new Talking(obj, texts, color));
 }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 449e5df5ed8..528e46f9bdb 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -52,6 +52,14 @@ enum Direction {
 	dBack = 8
 };
 
+enum UseFlag {
+	ufNone,
+	ufUseWith,
+	ufUseOn,
+	ufUseIn,
+	ufGiveTo
+};
+
 struct ObjectAnimation {
 	Common::String name;
 	Common::String sheet;
@@ -77,9 +85,9 @@ struct VerbId {
 
 class Object;
 struct Sentence {
-    VerbId verb;
-    Object* noun1 = nullptr;
-	Object* noun2 = nullptr;
+	VerbId verb;
+	Object *noun1 = nullptr;
+	Object *noun2 = nullptr;
 	bool enabled = false;
 };
 
@@ -90,18 +98,18 @@ class Node;
 class Layer;
 
 struct TalkingState {
-    Object* _obj;
-    Color _color;
+	Object *_obj;
+	Color _color;
 
-	void say(const Common::StringArray& texts, Object* obj);
+	void say(const Common::StringArray &texts, Object *obj);
 };
 
 class Object {
 public:
 	Object();
-	Object(HSQOBJECT o, const Common::String& key);
+	Object(HSQOBJECT o, const Common::String &key);
 
-	static Object* createActor();
+	static Object *createActor();
 
 	int getId() const;
 
@@ -120,6 +128,8 @@ public:
 	// objectState(coin, HERE)
 	// objectTouchable(coin, YES)
 	void setState(int state, bool instant = false);
+	int getState() const { return _state; }
+
 	bool touchable();
 	void setTouchable(bool value);
 
@@ -137,6 +147,7 @@ public:
 	void setPop(int count);
 	int getPop() const { return _popCount; }
 	float popScale() const;
+	float getScale();
 
 	int defaultVerbId();
 	void setFacing(Facing facing);
@@ -144,21 +155,25 @@ public:
 	Math::Vector2d getUsePos();
 	Facing getDoorFacing();
 
-	void setIcon(int fps, const Common::StringArray& icons);
-	void setIcon(const Common::String& icon);
+	void setIcon(int fps, const Common::StringArray &icons);
+	void setIcon(const Common::String &icon);
 	Common::String getIcon();
 	bool inInventory();
+	void removeInventory(Object *obj);
+	void removeInventory();
 
 	int getFlags();
+	UseFlag useFlag();
+
 	bool contains(Math::Vector2d pos);
-	void setRoom(Room* room);
+	void setRoom(Room *room);
 	void delObject();
 	void stopObjectMotors();
-	void dependentOn(Object* dependentObj, int state);
+	void dependentOn(Object *dependentObj, int state);
 
 	Common::String getAnimName(const Common::String &key);
 	void setHeadIndex(int head);
-	void setAnimationNames(const Common::String& head, const Common::String& stand, const Common::String& walk, const Common::String& reach);
+	void setAnimationNames(const Common::String &head, const Common::String &stand, const Common::String &walk, const Common::String &reach);
 	bool isWalking();
 	void stopWalking();
 	void blinkRate(float min, float max);
@@ -167,20 +182,20 @@ public:
 
 	void update(float elapsedSec);
 
-	void setAlphaTo(Motor* alphaTo);
-	void setRotateTo(Motor* rotateTo);
-	void setMoveTo(Motor* moveTo);
-	void setWalkTo(Motor* walkTo);
-	Motor* getWalkTo() const { return _walkTo; }
-	void setTalking(Motor* talking);
-	void setBlink(Motor* blink);
-	void setTurnTo(Motor* turnTo);
-	void setShakeTo(Motor* shakeTo);
-	void setJiggleTo(Motor* jiggleTo);
-
-	Motor* getTalking() { return _talking; }
+	void setAlphaTo(Motor *alphaTo);
+	void setRotateTo(Motor *rotateTo);
+	void setMoveTo(Motor *moveTo);
+	void setWalkTo(Motor *walkTo);
+	Motor *getWalkTo() const { return _walkTo; }
+	void setTalking(Motor *talking);
+	void setBlink(Motor *blink);
+	void setTurnTo(Motor *turnTo);
+	void setShakeTo(Motor *shakeTo);
+	void setJiggleTo(Motor *jiggleTo);
+
+	Motor *getTalking() { return _talking; }
 	void stopTalking();
-	void say(const Common::StringArray& texts, Color color);
+	void say(const Common::StringArray &texts, Color color);
 
 	void pickupObject(Object *obj);
 
@@ -188,16 +203,17 @@ private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
 	bool playCore(const Common::String &state, bool loop = false, bool instant = false);
+	int flags();
 
 public:
 	HSQOBJECT _table;
 	Common::String _name;
 	Common::String _parent;
-	Common::String _sheet;								// Spritesheet to use when a sprite is displayed in the room: "raw" means raw texture, empty string means use room texture
-	Common::String _key;								// key used to identify this object by script
+	Common::String _sheet; // Spritesheet to use when a sprite is displayed in the room: "raw" means raw texture, empty string means use room texture
+	Common::String _key;   // key used to identify this object by script
 	Common::String _costumeName, _costumeSheet;
 	int _state = -1;
-	Math::Vector2d _usePos;								// use position
+	Math::Vector2d _usePos; // use position
 	Direction _useDir = dNone;
 	Common::Rect _hotspot;
 	ObjectType _objType = otNone;
@@ -226,30 +242,30 @@ public:
 	Color _talkColor;
 	Common::HashMap<Common::String, Common::String> _animNames;
 	bool _lit = false;
-	Object* _owner = nullptr;
-	Common::Array<Object*> _inventory;
+	Object *_owner = nullptr;
+	Common::Array<Object *> _inventory;
 	int _inventoryOffset = 0;
 	Common::StringArray _icons;
 	int _iconFps = 0;
 	int _iconIndex = 0;
-	float _iconElapsed  = 0.f;
+	float _iconElapsed = 0.f;
 	HSQOBJECT _enter, _leave;
 	int _dependentState = 0;
-    Object* _dependentObj;
-    float _popElapsed = 0.f;
-    int _popCount = 0;
+	Object *_dependentObj = nullptr;
+	float _popElapsed = 0.f;
+	int _popCount = 0;
 	Sentence _exec;
 
 private:
-	Motor* _alphaTo = nullptr;
-	Motor* _rotateTo = nullptr;
-	Motor* _moveTo = nullptr;
-	Motor* _walkTo = nullptr;
-	Motor* _talking = nullptr;
-	Motor* _blink = nullptr;
-	Motor* _turnTo = nullptr;
-	Motor* _shakeTo = nullptr;
-	Motor* _jiggleTo = nullptr;
+	Motor *_alphaTo = nullptr;
+	Motor *_rotateTo = nullptr;
+	Motor *_moveTo = nullptr;
+	Motor *_walkTo = nullptr;
+	Motor *_talking = nullptr;
+	Motor *_blink = nullptr;
+	Motor *_turnTo = nullptr;
+	Motor *_shakeTo = nullptr;
+	Motor *_jiggleTo = nullptr;
 	TalkingState _talkingState;
 };
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 77eff3d72e5..e555a59ce9f 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -184,9 +184,20 @@ static SQInteger findObjectAt(HSQUIRRELVM v) {
 }
 
 static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
-	// TODO: isInventoryOnScreen
-	warning("isInventoryOnScreen not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	if (!obj->_owner || (obj->_owner != g_engine->_actor)) {
+		debug("Is '%s(%s)' in inventory: no", obj->_name.c_str(), obj->_key.c_str());
+		sqpush(v, false);
+		return 1;
+	}
+	int offset = obj->_owner->_inventoryOffset;
+	int index = find(obj->_owner->_inventory, obj);
+	int res = index >= offset * 4 && index < (offset * 4 + 8);
+	debug("Is '%s(%s)' in inventory: {%d}", obj->_name.c_str(), obj->_key.c_str(), res);
+	sqpush(v, res);
+	return 1;
 }
 
 // Returns true if the object is actually an object and not something else.
@@ -706,12 +717,11 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 // Sets the object in the screen space.
 // It means that its position is relative to the screen, not to the room.
 static SQInteger objectScreenSpace(HSQUIRRELVM v) {
-	warning("TODO: objectShader not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	g_engine->_screenScene.addChild(obj->_node);
 	return 0;
-	// Object *obj = sqobj(v, 2);
-	// if (!obj)
-	// 	return sq_throwerror(v, "failed to get object");
-	// g_engine->_screen->addChild(obj->_node);
 }
 
 static SQInteger objectShader(HSQUIRRELVM v) {
@@ -866,15 +876,16 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 
 	// int verbId = verb;
 	if (!g_engine->_actor) {
-		// TODO:
-		// for (vb in gEngine.hud.actorSlot(gEngine.actor).verbs) {
-		//   if (vb.id == verbId) {
-		//     if (sqrawexists(obj.table, vb.fun)) {
-		//       sqpush(v, true);
-		//       return 1;
-		// 	}
-		//   }
-		// }
+		ActorSlot *slot = g_engine->_hud.actorSlot(g_engine->_actor);
+		for (int i = 0; i < 22; i++) {
+			Verb *vb = &slot->verbs[i];
+			if (vb->id.id == verb) {
+				if (sqrawexists(obj->_table, vb->fun)) {
+					sqpush(v, true);
+					return 1;
+				}
+			}
+		}
 	}
 	sqpush(v, false);
 	return 1;
@@ -948,9 +959,13 @@ static SQInteger popInventory(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Removes an object from the current actor's inventory.
+// If the object is not in the current actor's inventory, the command silently fails.
 static SQInteger removeInventory(HSQUIRRELVM v) {
-	// TODO: removeInventory
-	warning("removeInventory not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	obj->removeInventory();
 	return 0;
 }
 
@@ -1042,7 +1057,6 @@ void sqgame_register_objlib(HSQUIRRELVM v) {
 	regFunc(v, setDefaultObject, _SC("setDefaultObject"));
 	regFunc(v, shakeObject, _SC("shakeObject"));
 	regFunc(v, stopObjectMotors, _SC("stopObjectMotors"));
-	regFunc(v, objectAt, _SC("objectAt"));
 }
 
 } // namespace Twp
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index bf3f6375bfc..d5867102b20 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -98,6 +98,7 @@ Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 	setId(_table, newRoomId());
 	_name = name;
 	_scene = new Scene();
+  	_scene->addChild(&_overlayNode);
 }
 
 Room::~Room() {
@@ -124,6 +125,7 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	Common::String name = frames.size() > 0 ? frames[0] : "noname";
 	sqsetf(obj->_table, "name", name);
 	obj->_key = name;
+	obj->_node->setName(name);
 	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 
 	obj->_room = this;
@@ -165,6 +167,7 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 	setId(obj->_table, newObjId());
 	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
+	obj->_node->setName(obj->_key);
 	obj->_touchable = false;
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
@@ -372,11 +375,10 @@ void Room::objectParallaxLayer(Object *obj, int zsort) {
 	if (obj->_layer != l) {
 		// removes object from old layer
 		int id = obj->getId();
-		for (int i = 0; i < obj->_layer->_objects.size(); i++) {
-			if (obj->_layer->_objects[i]->getId() == id) {
-				obj->_layer->_objects.remove_at(i);
-				break;
-			}
+		if(obj->_layer) {
+			int i = find(obj->_layer->_objects, obj);
+			obj->_layer->_node->removeChild(obj->_node);
+			obj->_layer->_objects.remove_at(i);
 		}
 		// adds object to the new one
 		l->_objects.push_back(obj);
@@ -386,6 +388,14 @@ void Room::objectParallaxLayer(Object *obj, int zsort) {
 	}
 }
 
+void Room::setOverlay(Color color) {
+	_overlayNode.setOverlayColor(color);
+}
+
+Color Room::getOverlay() const {
+	return _overlayNode.getOverlayColor();
+}
+
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
 	_names.push_back(name);
 	_parallax = parallax;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 0d35571dd26..78856a798ec 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -28,6 +28,8 @@
 #include "math/vector2d.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/font.h"
+#include "twp/motor.h"
+#include "twp/scenegraph.h"
 
 namespace Twp {
 
@@ -119,6 +121,8 @@ public:
 	Light *createLight(Color color, Math::Vector2d pos);
 	float getScaling(float yPos);
 	void objectParallaxLayer(Object *obj, int zsort);
+	void setOverlay(Color color);
+	Color getOverlay() const;
 
 public:
 	Common::String _name;              // Name of the room
@@ -138,7 +142,9 @@ public:
 	bool _pseudo = false;
 	Common::Array<Object *> _objects;
 	Scene *_scene = nullptr;
+	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect;
+	Motor* _overlayTo = nullptr;
 };
 
 } // namespace Twp
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 982d0743e74..8fb29fe5b96 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -301,9 +301,45 @@ static SQInteger roomLayer(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Puts a color overlay on the top of the entire room.
+//
+// Transition from startColor to endColor over duration seconds.
+// The endColor remains on screen until changed.
+// Note that the actual colour is an 8 digit number, the first two digits (00-ff) represent the transparency, while the last 6 digits represent the actual colour.
+// If transparency is set to 00, the overlay is completely see through.
+// If startColor is not on the screen already, it will flash to that color before starting the transition.
+// If no endColor or duration are provided, it will change instantly to color and remain there.
+//
+// .. code-block:: Squirrel
+// // Make lights in QuickiePal flicker
+// roomOverlayColor(0x20dff2cd, 0x20dff2cd, 0.0)
+// breaktime(1/60)
+// roomOverlayColor(0x00000000, 0x00000000, 0.0)
+// breaktime(1/60)
+//
+// if (currentActor == franklin) {
+//     roomOverlayColor(0x800040AA)
+// }
 static SQInteger roomOverlayColor(HSQUIRRELVM v) {
-	warning("TODO: roomOverlayColor not implemented");
-	return 0;
+	int startColor;
+  SQInteger numArgs = sq_gettop(v);
+  if (SQ_FAILED(sqget(v, 2, startColor)))
+    return sq_throwerror(v, "failed to get startColor");
+  Room* room = g_engine->_room;
+  if (room->_overlayTo)
+      room->_overlayTo->disable();
+  room->setOverlay(Color::rgb(startColor));
+  if (numArgs == 4) {
+    int endColor;
+    if (SQ_FAILED(sqget(v, 3, endColor)))
+      return sq_throwerror(v, "failed to get endColor");
+    float duration;
+    if (SQ_FAILED(sqget(v, 4, duration)))
+      return sq_throwerror(v, "failed to get duration");
+    debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
+    g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::rgb(endColor));
+  }
+  return 0;
 }
 
 static SQInteger roomRotateTo(HSQUIRRELVM v) {
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index dee4e1713ea..d8672036feb 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -26,11 +26,22 @@
 #include "twp/twp.h"
 #include "twp/gfx.h"
 #include "twp/object.h"
+#include "twp/util.h"
 
 namespace Twp {
 
 #define DEFAULT_FPS 10.f
 
+#define NUMOBJECTS 8
+#define NUMOBJECTSBYROW 4
+#define MARGIN 8.f
+#define MARGINBOTTOM 10.f
+#define BACKOFFSET 7.f
+#define ARROWWIDTH 56.f
+#define ARROWHEIGHT 86.f
+#define BACKWIDTH 137.f
+#define BACKHEIGHT 75.f
+
 static float _getFps(float fps, float animFps) {
 	if (fps != 0.f)
 		return fps;
@@ -49,11 +60,13 @@ Node::~Node() {}
 void Node::addChild(Node *child) {
 	if (child->_parent) {
 		child->_pos -= getAbsPos();
-		for (int i = 0; i < _children.size(); i++) {
-			if (_children[i] == child) {
-				child->_parent->_children.erase(child->_parent->_children.begin() + i);
+		for (auto it = _children.begin(); it != _children.end();) {
+			Node *node = *it;
+			if (node == child) {
+				it = child->_parent->_children.erase(it);
 				break;
 			}
+			it++;
 		}
 	}
 	_children.push_back(child);
@@ -163,7 +176,7 @@ void Node::setSize(Math::Vector2d size) {
 }
 
 static int cmpNodes(const Node *x, const Node *y) {
-	return y->getZSort() <= x->getZSort();
+	return y->getZSort() < x->getZSort();
 }
 
 void Node::draw(Math::Matrix4 parent) {
@@ -171,10 +184,11 @@ void Node::draw(Math::Matrix4 parent) {
 		Math::Matrix4 trsf = getTrsf(parent);
 		Math::Matrix4 myTrsf(trsf);
 		myTrsf.translate(Math::Vector3d(-_anchor.getX(), _anchor.getY(), 0.f));
-		Common::sort(_children.begin(), _children.end(), cmpNodes);
+		Common::Array<Node *> children(_children);
+		Common::sort(children.begin(), children.end(), cmpNodes);
 		drawCore(myTrsf);
-		for (int i = 0; i < _children.size(); i++) {
-			Node *child = _children[i];
+		for (int i = 0; i < children.size(); i++) {
+			Node *child = children[i];
 			child->draw(trsf);
 		}
 	}
@@ -212,18 +226,6 @@ Rectf Node::getRect() const {
 	return Rectf::fromPosAndSize(getAbsPos(), Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm * _size);
 }
 
-OverlayNode::OverlayNode()
-	: Node("overlay"),
-	  _ovlColor(0, 0, 0, 0) {
-}
-
-OverlayNode::~OverlayNode() {}
-
-void OverlayNode::drawCore(Math::Matrix4 trsf) {
-	Gfx &gfx = g_engine->getGfx();
-	gfx.drawQuad(gfx.camera(), _ovlColor);
-}
-
 ParallaxNode::ParallaxNode(const Math::Vector2d &parallax, const Common::String &sheet, const Common::StringArray &frames)
 	: Node("parallax"),
 	  _parallax(parallax),
@@ -299,7 +301,7 @@ void Anim::trigSound() {
 
 void Anim::update(float elapsed) {
 	if (_anim)
-		setVisible(Common::find(_obj->_hiddenLayers.begin(), _obj->_hiddenLayers.end(), _anim->name) == NULL);
+		setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == -1);
 	if (_instant)
 		disable();
 	else if (_frames.size() != 0) {
@@ -409,7 +411,7 @@ Scene::Scene() : Node("Scene") {
 }
 Scene::~Scene() {}
 
-InputState::InputState(): Node("InputState") {}
+InputState::InputState() : Node("InputState") {}
 InputState::~InputState() {}
 
 void InputState::drawCore(Math::Matrix4 trsf) {
@@ -418,11 +420,149 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
 	//   if prefs(ClassicSentence) and self.hotspot:
 	//       cursorName = "hotspot_" & self.cursorName
-	const SpriteSheetFrame& sf = gameSheet->frameTable[ "cursor"];
-	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f,  - sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
+	const SpriteSheetFrame &sf = gameSheet->frameTable["cursor"];
+	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
 	scale(trsf, Math::Vector2d(2.f, 2.f));
 	g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
 }
 
+OverlayNode::OverlayNode() : Node("overlay") {
+	_ovlColor = Color(0.f, 0.f, 0.f, 0.f); // transparent
+	_zOrder = INT_MIN;
+}
+
+void OverlayNode::drawCore(Math::Matrix4 trsf) {
+	Math::Vector2d size = g_engine->getGfx().camera();
+	g_engine->getGfx().drawQuad(size, _ovlColor);
+}
+
+static bool hasUpArrow(Object *actor) {
+	return actor->_inventoryOffset != 0;
+}
+
+static bool hasDownArrow(Object *actor) {
+	return actor->_inventory.size() > (actor->_inventoryOffset * NUMOBJECTSBYROW + NUMOBJECTS);
+}
+
+Inventory::Inventory() : Node("Inventory") {}
+
+void Inventory::drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
+	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
+	trsf.translate(pos);
+	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+}
+
+void Inventory::drawArrows(Math::Matrix4 trsf) {
+	// TODO: bool isRetro = prefs(RetroVerbs);
+	bool isRetro = false;
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheetFrame *arrowUp = &gameSheet->frameTable[isRetro ? "scroll_up_retro" : "scroll_up"];
+	SpriteSheetFrame *arrowDn = &gameSheet->frameTable[isRetro ? "scroll_down_retro" : "scroll_down"];
+	float alphaUp = hasUpArrow(_actor) ? 1.f : 0.f;
+	float alphaDn = hasDownArrow(_actor) ? 1.f : 0.f;
+	Math::Matrix4 tUp(trsf);
+	tUp.translate(Math::Vector3d(SCREEN_WIDTH / 2.f + ARROWWIDTH / 2.f + MARGIN, 1.5f * ARROWHEIGHT + BACKOFFSET, 0.f));
+	Math::Matrix4 tDn(trsf);
+	tDn.translate(Math::Vector3d(SCREEN_WIDTH / 2.f + ARROWWIDTH / 2.f + MARGIN, 0.5f * ARROWHEIGHT, 0.f));
+
+	drawSprite(*arrowUp, texture, Color::withAlpha(_verbNormal, alphaUp), tUp);
+	drawSprite(*arrowDn, texture, Color::withAlpha(_verbNormal, alphaDn), tDn);
+}
+
+void Inventory::drawBack(Math::Matrix4 trsf) {
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheetFrame *back = &gameSheet->frameTable["inventory_background"];
+
+	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + back->sourceSize.getX() / 2.f;
+	float offsetX = startOffsetX;
+	float offsetY = 3.f * back->sourceSize.getY() / 2.f + MARGINBOTTOM + BACKOFFSET;
+
+	for (int i = 0; i < 4; i++) {
+		Math::Matrix4 t(trsf);
+		t.translate(Math::Vector3d(offsetX, offsetY, 0.f));
+		drawSprite(*back, texture, _backColor, t);
+		offsetX += back->sourceSize.getX() + BACKOFFSET;
+	}
+
+	offsetX = startOffsetX;
+	offsetY = back->sourceSize.getY() / 2.f + MARGINBOTTOM;
+	for (int i = 0; i < 4; i++) {
+		Math::Matrix4 t(trsf);
+		t.translate(Math::Vector3d(offsetX, offsetY, 0.f));
+		drawSprite(*back, texture, _backColor, t);
+		offsetX += back->sourceSize.getX() + BACKOFFSET;
+	}
+}
+
+void Inventory::drawItems(Math::Matrix4 trsf) {
+	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + BACKWIDTH / 2.f;
+	float startOffsetY = MARGINBOTTOM + 1.5f * BACKHEIGHT + BACKOFFSET;
+	SpriteSheet *itemsSheet = g_engine->_resManager.spriteSheet("InventoryItems");
+	Texture *texture = g_engine->_resManager.texture(itemsSheet->meta.image);
+	int count = MIN(NUMOBJECTS, (int)_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW);
+
+	for (int i = 0; i < count; i++) {
+		Object *obj = _actor->_inventory[_actor->_inventoryOffset * NUMOBJECTSBYROW + i];
+		Common::String icon = obj->getIcon();
+		if (itemsSheet->frameTable.contains(icon)) {
+			SpriteSheetFrame *itemFrame = &itemsSheet->frameTable[icon];
+			Math::Vector2d pos(startOffsetX + ((i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET)), startOffsetY - ((i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET)));
+			Math::Matrix4 t(trsf);
+			trsf.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+			float s = obj->getScale();
+			Twp::scale(t, Math::Vector2d(s, s));
+			drawSprite(*itemFrame, texture, Color(), t);
+		}
+	}
+}
+
+void Inventory::drawCore(Math::Matrix4 trsf) {
+	if (_actor) {
+		drawArrows(trsf);
+		drawBack(trsf);
+		drawItems(trsf);
+	}
+}
+
+void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
+	// udate colors
+	_actor = actor;
+	_backColor = backColor;
+	_verbNormal = verbNormal;
+
+	_obj = nullptr;
+	// TODO: Inventory::update
+	//   if (_actor) {
+	//     let scrPos = winToScreen(mousePos());
+
+	//     // update mouse click
+	//     let down = mbLeft in mouseBtns();
+	//     if not self.down and down:
+	//       self.down = true;
+	//       if gArrowUpRect.contains(scrPos):
+	//         self.actor.inventoryOffset -= 1;
+	//         if self.actor.inventoryOffset < 0:
+	//           self.actor.inventoryOffset = clamp(self.actor.inventoryOffset, 0, (self.actor.inventory.len - 5) div 4)
+	//       elif gArrowDnRect.contains(scrPos):
+	//         self.actor.inventoryOffset += 1;
+	//         self.actor.inventoryOffset = clamp(self.actor.inventoryOffset, 0, (self.actor.inventory.len - 5) div 4)
+	//     elif not down:
+	//       self.down = false;
+
+	//     for i in 0..<gItemRects.len:
+	//       let item = gItemRects[i];
+	//       if item.contains(scrPos):
+	//         let index = self.actor.inventoryOffset * NumObjectsByRow + i;
+	//         if index < self.actor.inventory.len:
+	//           self.obj = self.actor.inventory[index];
+	//         break
+
+	//     for obj in self.actor.inventory:
+	//       obj.update(elapsed);;
+	//   }
+}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index b65ed0ef8d8..263a811b946 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -29,7 +29,7 @@
 #include "common/str.h"
 #include "twp/gfx.h"
 #include "twp/rectf.h"
-#include "twp/room.h"
+#include "twp/font.h"
 #include "twp/spritesheet.h"
 
 namespace Twp {
@@ -37,11 +37,11 @@ namespace Twp {
 // Represents a node in a scene graph.
 class Node {
 public:
-	Node(const Common::String& name, Math::Vector2d scale = Math::Vector2d(1, 1), Color color = Color());
+	Node(const Common::String &name, Math::Vector2d scale = Math::Vector2d(1, 1), Color color = Color());
 	virtual ~Node();
 
-	void setName(const Common::String& name) { _name = name; }
-	const Common::String& getName() const { return _name; }
+	void setName(const Common::String &name) { _name = name; }
+	const Common::String &getName() const { return _name; }
 
 	void setVisible(bool visible) { _visible = visible; }
 	bool isVisible() const { return _visible; }
@@ -51,28 +51,28 @@ public:
 	// Arguments:
 	// - `child`: child node to add.
 	void addChild(Node *child);
-	void removeChild(Node* node);
+	void removeChild(Node *node);
 	void clear();
 	// Removes this node from its parent.
 	void remove();
-	const Common::Array<Node *>& getChildren() const { return _children; }
+	const Common::Array<Node *> &getChildren() const { return _children; }
 
-	Node* getParent() const { return _parent; }
-	const Node* getRoot() const;
+	Node *getParent() const { return _parent; }
+	const Node *getRoot() const;
 	// Finds a node in its children and returns its position.
-	int find(Node* other);
+	int find(Node *other);
 
 	// Gets the local position for this node (relative to its parent)
-	void setPos(const Math::Vector2d& pos) { _pos = pos; }
+	void setPos(const Math::Vector2d &pos) { _pos = pos; }
 	Math::Vector2d getPos() const { return _pos; }
 
-	void setOffset(const Math::Vector2d& offset) { _offset = offset; }
+	void setOffset(const Math::Vector2d &offset) { _offset = offset; }
 	Math::Vector2d getOffset() const { return _offset; }
 
-	void setRenderOffset(const Math::Vector2d& offset) { _renderOffset = offset; }
+	void setRenderOffset(const Math::Vector2d &offset) { _renderOffset = offset; }
 	Math::Vector2d getRenderOffset() const { return _renderOffset; }
 
-	void setScale(const Math::Vector2d& scale) { _scale = scale; }
+	void setScale(const Math::Vector2d &scale) { _scale = scale; }
 	virtual Math::Vector2d getScale() const { return _scale; }
 
 	// Gets the absolute position for this node.
@@ -127,21 +127,9 @@ protected:
 	float _rotationOffset = 0.f;
 };
 
-class OverlayNode final: public Node {
+class ParallaxNode final : public Node {
 public:
-	OverlayNode();
-	virtual ~OverlayNode();
-
-protected:
-	void drawCore(Math::Matrix4 trsf) override final;
-
-private:
-	Color _ovlColor;
-};
-
-class ParallaxNode final: public Node {
-public:
-	ParallaxNode(const Math::Vector2d& parallax, const Common::String& sheet, const Common::StringArray& frames);
+	ParallaxNode(const Math::Vector2d &parallax, const Common::String &sheet, const Common::StringArray &frames);
 	virtual ~ParallaxNode();
 
 	Math::Matrix4 getTrsf(Math::Matrix4 parentTrsf) override final;
@@ -157,45 +145,47 @@ private:
 
 struct ObjectAnimation;
 
+class Object;
 class Anim : public Node {
 public:
-	Anim(Object* obj);
+	Anim(Object *obj);
 
 	void clearFrames();
-	void setAnim(const ObjectAnimation* anim, float fps = 0.f, bool loop = false, bool instant = false);
+	void setAnim(const ObjectAnimation *anim, float fps = 0.f, bool loop = false, bool instant = false);
 	void update(float elapsed);
 	void disable() { _disabled = true; }
 	void trigSound();
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
+
 public:
-    const ObjectAnimation* _anim = nullptr;
-    bool _disabled = false;
+	const ObjectAnimation *_anim = nullptr;
+	bool _disabled = false;
 
 private:
-    Common::String _sheet;
-    Common::Array<Common::String> _frames;
-    int _frameIndex = 0;
-    float _elapsed = 0.f;
-    float _frameDuration = 0.f;
-    bool _loop = false;
+	Common::String _sheet;
+	Common::Array<Common::String> _frames;
+	int _frameIndex = 0;
+	float _elapsed = 0.f;
+	float _frameDuration = 0.f;
+	bool _loop = false;
 	bool _instant;
-    Object* _obj = nullptr;
+	Object *_obj = nullptr;
 };
 
-class ActorNode final: public Node {
+class ActorNode final : public Node {
 public:
-	ActorNode(Object* obj);
+	ActorNode(Object *obj);
 
 	int getZSort() const override final;
 	Math::Vector2d getScale() const override final;
 
 private:
-	Object* _object = nullptr;
+	Object *_object = nullptr;
 };
 
-class TextNode final: public Node {
+class TextNode final : public Node {
 public:
 	TextNode();
 	virtual ~TextNode() final;
@@ -212,13 +202,13 @@ private:
 	Text _text;
 };
 
-class Scene final: public Node {
+class Scene final : public Node {
 public:
 	Scene();
 	virtual ~Scene() final;
 };
 
-class InputState final: public Node {
+class InputState final : public Node {
 public:
 	InputState();
 	virtual ~InputState() final;
@@ -227,6 +217,39 @@ private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
 };
 
+class OverlayNode final : public Node {
+public:
+	OverlayNode();
+
+	void setOverlayColor(Color color) { _ovlColor = color; }
+	Color getOverlayColor() const { return _ovlColor; }
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Color _ovlColor;
+};
+
+class Inventory: public Node {
+public:
+	Inventory();
+	void update(float elapsed, Object* actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override final;
+	void drawArrows(Math::Matrix4 trsf);
+	void drawBack(Math::Matrix4 trsf);
+	void drawItems(Math::Matrix4 trsf);
+	void drawSprite(SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
+
+private:
+	Object* _actor = nullptr;
+    Color _backColor, _verbNormal;
+    bool _down = false;
+    Object* _obj = nullptr;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 6c511fb7748..b58eebd7c90 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -585,7 +585,7 @@ static SQInteger threadid(HSQUIRRELVM v) {
 // If a thread is not pauseable, it won't be possible to pause this thread.
 static SQInteger threadpauseable(HSQUIRRELVM v) {
 	ThreadBase *t = sqthread(v, 2);
-	if (t)
+	if (!t)
 		return sq_throwerror(v, "failed to get thread");
 	int pauseable = 0;
 	if (SQ_FAILED(sqget(v, 3, pauseable)))
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c699e954621..184885cf85f 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -58,6 +58,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	sq_resetobject(&_defaultObj);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_dialog);
+	_screenScene.addChild(&_uiInv);
 }
 
 TwpEngine::~TwpEngine() {
@@ -207,6 +208,15 @@ void TwpEngine::update(float elapsed) {
 			}
 		}
 	}
+
+	 // update inventory
+  if (!_actor) {
+    _uiInv.update(elapsed);
+  } else {
+    // TODO: _hud.update(scrPos, _noun1, _cursor.isLeftClick());
+    VerbUiColors* verbUI = &_hud.actorSlot(_actor)->verbUiColors;
+    _uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
+  }
 }
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
@@ -359,22 +369,6 @@ Common::Error TwpEngine::run() {
 	if (saveSlot != -1)
 		(void)loadGameState(saveSlot);
 
-	// GGPackEntryReader reader;
-	// if (reader.open(_pack, "TronDialogs.byack")) {
-	// 	YackParser parser;
-	// 	YackTokenReader ytr;
-	// 	ytr.open(&reader);
-
-	// 	for (auto it=ytr.begin();it!=ytr.end();it++) {
-	// 		Common::String s = ytr.readText(*it);
-	// 		debug("%s [%d, %d] %s", it->toString().c_str(), it->start, it->end, s.c_str());
-	// 	}
-
-	// 	unique_ptr<YCompilationUnit> cu(parser.parse(&reader));
-	// 	YackDump dump;
-	// 	cu->accept(dump);
-	// }
-
 	HSQUIRRELVM v = _vm.get();
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
@@ -435,7 +429,7 @@ Common::Error TwpEngine::run() {
 		uint32 newTime = _system->getMillis();
 		uint32 delta = newTime - time;
 		time = newTime;
-		update(8.f*delta / 1000.f);
+		update(8.f * delta / 1000.f);
 		draw();
 		_cursor.update();
 
@@ -630,7 +624,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	_room = room;
 	_scene.addChild(_room->_scene);
 	_room->_lights._numLights = 0;
-	// TODO:   _room->overlay = Transparent;
+	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
 	//   if (_actor)
 	//     _hud.verb = _hud.actorSlot(_actor).verbs[0];
@@ -870,4 +864,116 @@ void TwpEngine::setActor(Object *actor, bool userSelected) {
 		follow(actor);
 }
 
+bool TwpEngine::selectable(Object *actor) {
+	for (int i = 0; i < NUMACTORS; i++) {
+		ActorSlot *slot = &_hud._actorSlots[i];
+		if (slot->actor == actor)
+			return slot->selectable;
+	}
+	return false;
+}
+
+static void giveTo(Object *actor1, Object *actor2, Object *obj) {
+	obj->_owner = actor2;
+	actor2->_inventory.push_back(obj);
+	int index = find(actor1->_inventory, obj);
+	if (index != -1)
+		actor1->_inventory.remove_at(index);
+}
+
+void TwpEngine::resetVerb() {
+	debug("reset nouns");
+	_noun1 = nullptr;
+	_noun2 = nullptr;
+	_useFlag = ufNone;
+	_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+}
+
+bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+	sqcall("onObjectClick", noun1->_table);
+
+	// Called after the actor has walked to the object.
+	Common::String name = !actor ? "currentActor" : actor->_key;
+	Common::String noun1name = !noun1 ? "null" : noun1->_key;
+	Common::String noun2name = !noun2 ? "null" : noun2->_key;
+	Common::String verbFuncName = _hud.actorSlot(actor)->verbs[verbId.id].fun;
+	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
+
+	// test if object became untouchable
+	if (!noun1->inInventory() && !noun1->_touchable)
+		return false;
+	if (noun2 && !noun2->inInventory() && !noun2->_touchable)
+		return false;
+
+	// check if verb is use and object can be used with or in or on
+	if ((verbId.id == VERB_USE) && !noun2) {
+		_useFlag = noun1->useFlag();
+		if (_useFlag != ufNone) {
+			_noun1 = noun1;
+			return false;
+		}
+	}
+
+	if (verbId.id == VERB_GIVE) {
+		if (!noun2) {
+			debug("set use flag to ufGiveTo");
+			_useFlag = ufGiveTo;
+			_noun1 = noun1;
+		} else {
+			bool handled = false;
+			if (sqrawexists(noun2->_table, verbFuncName)) {
+				debug("call {verbFuncName} on {noun2.key}");
+				sqcallfunc(handled, noun2->_table, verbFuncName.c_str(), noun1->_table);
+			}
+			// verbGive is called on object only for non selectable actors
+			if (!handled && !selectable(noun2) && sqrawexists(noun1->_table, verbFuncName)) {
+				debug("call {verbFuncName} on {noun1.key}");
+				sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
+				handled = true;
+			}
+			if (!handled) {
+				debug("call objectGive");
+				sqcall("objectGive", noun1->_table, _actor->_table, noun2->_table);
+				giveTo(_actor, noun2, noun1);
+			}
+			resetVerb();
+		}
+		return false;
+	}
+
+	if (!noun2) {
+		if (sqrawexists(noun1->_table, verbFuncName)) {
+			int count = sqparamCount(getVm(), noun1->_table, verbFuncName);
+			debug("call {noun1.key}.{verbFuncName}");
+			if (count == 1) {
+				sqcall(noun1->_table, verbFuncName.c_str());
+			} else {
+				sqcall(noun1->_table, verbFuncName.c_str(), actor->_table);
+			}
+		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
+			sqcall(noun1->_table, VERBDEFAULT);
+		} else {
+			debug("call defaultObject.{verbFuncName}");
+			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, actor->_table);
+		}
+	} else {
+		if (sqrawexists(noun1->_table, verbFuncName)) {
+			debug("call {noun1.key}.{verbFuncName}");
+			sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
+		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
+			sqcall(noun1->_table, VERBDEFAULT);
+		} else {
+			debug("call defaultObject.{verbFuncName}");
+			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, noun2->_table);
+		}
+	}
+
+	if (verbId.id == VERB_PICKUP) {
+		sqcall("onPickup", noun1->_table, actor->_table);
+	}
+
+	resetVerb();
+	return false;
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 56a5e3f3e54..50dba11dc62 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -46,6 +46,7 @@
 
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
+#define VERBDEFAULT "verbDefault"
 
 namespace Twp {
 
@@ -147,6 +148,9 @@ private:
 	void clickedAt(Math::Vector2d scrPos);
 	bool clickedAtHandled(Math::Vector2d roomPos);
 	void setShaderEffect(RoomEffect effect);
+	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
+	bool selectable(Object *actor);
+	void resetVerb();
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -163,6 +167,7 @@ public:
 	float _time = 0.f; // time in seconds
 	Object *_noun1 = nullptr;
 	Object *_noun2 = nullptr;
+	UseFlag _useFlag;
 	HSQOBJECT _defaultObj;
 	bool _walkFastState = false;
 	int _frameCounter = 0;
@@ -195,6 +200,7 @@ private:
 	Preferences _prefs;
 	ShaderParams _shaderParams;
 	unique_ptr<FadeShader> _fadeShader;
+	Inventory _uiInv;
 };
 
 extern TwpEngine *g_engine;
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 66f0d224cd6..3a5a73fb5bf 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -325,7 +325,7 @@ YLabel *YackParser::parseLabel() {
 	// label
 	pLabel.reset(new YLabel(_it->line));
 	pLabel->_name = _reader.readText(*_it++);
-	debug("label %s", pLabel->_name.c_str());
+	// debug("label %s", pLabel->_name.c_str());
 	do {
 		if (match({YackTokenId::Colon}) || match({YackTokenId::End}))
 			break;


Commit: a055e4deaf0ac37a62063656560b7b8f6b8f33c8
    https://github.com/scummvm/scummvm/commit/a055e4deaf0ac37a62063656560b7b8f6b8f33c8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add walkto

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 2e300e93092..96fd82bbebd 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -635,8 +635,35 @@ static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Tells the specified actor to walk to an x/y position or to an actor position or to an object position.
 static SQInteger actorWalkTo(HSQUIRRELVM v) {
-	warning("TODO: actorWalkTo not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	if (nArgs == 3) {
+		Object *obj = sqobj(v, 3);
+		if (!obj)
+			return sq_throwerror(v, "failed to get actor or object");
+		else
+			actor->walk(obj);
+	} else if ((nArgs == 4) || (nArgs == 5)) {
+		int x, y;
+		if (SQ_FAILED(sqget(v, 3, x)))
+			return sq_throwerror(v, "failed to get x");
+		if (SQ_FAILED(sqget(v, 4, y)))
+			return sq_throwerror(v, "failed to get y");
+		Facing *facing = nullptr;
+		if (nArgs == 5) {
+			int dir;
+			if (SQ_FAILED(sqget(v, 5, dir)))
+				return sq_throwerror(v, "failed to get dir");
+			facing = (Facing*)&dir;
+		}
+		actor->walk(Math::Vector2d(x, y), facing);
+	} else {
+		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
+	}
 	return 0;
 }
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 2e85dc1ac7c..51816e10005 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -24,6 +24,7 @@
 #include "twp/motor.h"
 #include "twp/object.h"
 #include "twp/scenegraph.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
@@ -128,4 +129,140 @@ void OverlayTo::update(float elapsed) {
 		disable();
 }
 
+ReachAnim::ReachAnim(Object *actor, Object *obj)
+	: _actor(actor), _obj(obj) {
+}
+
+ReachAnim::~ReachAnim() {
+}
+
+void ReachAnim::playReachAnim() {
+	Common::String anim = _actor->getAnimName(REACH_ANIMNAME + _obj->getReachAnim());
+	_actor->play(anim);
+}
+
+void ReachAnim::update(float elapsed) {
+	switch (_state) {
+	case 0:
+		playReachAnim();
+		_state = 1;
+		break;
+	case 1:
+		_elapsed += elapsed;
+		if (_elapsed > 0.5)
+			_state = 2;
+		break;
+	case 2:
+		_actor->stand();
+		_actor->execVerb();
+		disable();
+		_state = 3;
+		break;
+	default:
+		break;
+		;
+	}
+}
+
+WalkTo::WalkTo(Object *obj, Math::Vector2d dest, int facing)
+	: _obj(obj), _facing(facing) {
+	if (obj->_useWalkboxes) {
+		_path = obj->_room->calculatePath(obj->_node->getAbsPos(), dest);
+	} else {
+		_path = {obj->_node->getAbsPos(), dest};
+	}
+	_wsd = sqrt(obj->_walkSpeed.getX() * obj->_walkSpeed.getX() + obj->_walkSpeed.getY() * obj->_walkSpeed.getY());
+	if (sqrawexists(obj->_table, "preWalking"))
+		sqcall(obj->_table, "preWalking");
+}
+
+void WalkTo::disable() {
+	Motor::disable();
+	if (_path.size() != 0) {
+		debug("actor walk cancelled");
+	}
+	_obj->play("stand");
+}
+
+static bool needsReachAnim(int verbId) {
+	return (verbId == VERB_PICKUP) || (verbId == VERB_OPEN) || (verbId == VERB_CLOSE) || (verbId == VERB_PUSH) || (verbId == VERB_PULL) || (verbId == VERB_USE);
+}
+
+void WalkTo::actorArrived() {
+	bool needsReach = _obj->_exec.enabled && needsReachAnim(_obj->_exec.verb.id);
+	if (!needsReach)
+		disable();
+
+	debug("actorArrived");
+	_obj->play("stand");
+	// the faces to the specified direction (if any)
+	if (_facing) {
+		debug("actor arrived with facing %d", _facing);
+		_obj->setFacing((Facing)_facing);
+	}
+
+	// call `actorArrived` callback
+	if (sqrawexists(_obj->_table, "actorArrived")) {
+		debug("call actorArrived callback");
+		sqcall(_obj->_table, "actorArrived");
+	}
+
+	// we need to execute a sentence when arrived ?
+	if (_obj->_exec.enabled) {
+		VerbId verb = _obj->_exec.verb;
+		Object *noun1 = _obj->_exec.noun1;
+		Object *noun2 = _obj->_exec.noun2;
+		// call `postWalk`callback
+		Common::String funcName = isActor(noun1->getId()) ? "actorPostWalk" : "objectPostWalk";
+		if (sqrawexists(_obj->_table, funcName)) {
+			debug("call %s callback", funcName.c_str());
+			HSQOBJECT n2Table;
+			if (noun2)
+				n2Table = noun2->_table;
+			else
+				sq_resetobject(&n2Table);
+			sqcall(_obj->_table, funcName.c_str(), verb.id, noun1->_table, n2Table);
+		}
+
+		if (needsReach)
+			_reach = new ReachAnim(_obj, noun1);
+		else
+			_obj->execVerb();
+	}
+}
+
+void WalkTo::update(float elapsed) {
+	if (_path.size() != 0) {
+		Math::Vector2d dest = _path[0];
+		float d = distance(dest, _obj->_node->getAbsPos());
+
+		// arrived at destination ?
+		if (d < 1.0) {
+			_obj->_node->setPos(_path[0]);
+			_path.remove_at(0);
+			if (_path.size() == 0) {
+				actorArrived();
+			}
+		} else {
+			Math::Vector2d delta = dest - _obj->_node->getAbsPos();
+			float duration = d / _wsd;
+			float factor = Twp::clamp(elapsed / duration, 0.f, 1.f);
+
+			Math::Vector2d dd = delta * factor;
+			_obj->_node->setPos(_obj->_node->getPos() + dd);
+			if (abs(delta.getX()) >= abs(delta.getY())) {
+				_obj->setFacing(delta.getX() >= 0 ? FACE_RIGHT : FACE_LEFT);
+			} else {
+				_obj->setFacing(delta.getY() > 0 ? FACE_BACK : FACE_FRONT);
+			}
+		}
+	}
+
+	if (_reach && _reach->isEnabled()) {
+		_reach->update(elapsed);
+		if (!_reach->isEnabled())
+			disable();
+	}
+}
+
 } // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index b06eee7eb80..d50e709c363 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -175,7 +175,7 @@ private:
 class OverlayTo : public Motor {
 public:
 	virtual ~OverlayTo();
-	OverlayTo(float duration, Room* room, Color to);
+	OverlayTo(float duration, Room *room, Color to);
 
 	virtual void update(float elapsed) override;
 
@@ -185,6 +185,40 @@ private:
 	Tween<Color> _tween;
 };
 
+class ReachAnim : public Motor {
+public:
+	virtual ~ReachAnim();
+	ReachAnim(Object* actor, Object* obj);
+
+	virtual void update(float elasped) override;
+
+private:
+	void playReachAnim();
+
+private:
+	Object* _actor = nullptr;
+    Object* _obj = nullptr;
+    int _state = 0;
+    float _elapsed = 0.f;
+};
+
+class WalkTo : public Motor {
+public:
+	WalkTo(Object* obj, Math::Vector2d dest, int facing = 0);
+	virtual void disable() override;
+
+private:
+	void actorArrived();
+	virtual void update(float elapsed) override;
+
+private:
+	Object* _obj = nullptr;
+    Common::Array<Math::Vector2d> _path;
+    int _facing = 0;
+    float _wsd;
+    ReachAnim* _reach = nullptr;
+};
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index bf72f626eff..7b31d2b0284 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -28,6 +28,9 @@
 #include "twp/ggpack.h"
 #include "twp/motor.h"
 
+#define MIN_TALK_DIST 60
+#define MIN_USE_DIST 15
+
 namespace Twp {
 
 Object::Object()
@@ -565,6 +568,106 @@ void Object::removeInventory() {
 		_owner->removeInventory(this);
 }
 
+Common::String Object::getReachAnim() {
+	int flags = getFlags();
+	if (flags & REACH_LOW)
+		return "_low";
+	if (flags & REACH_HIGH)
+		return "_high";
+	return "_med";
+}
+
+// true of you don't have to be close to the object
+static bool verbNotClose(VerbId id) {
+	return id.id == VERB_LOOKAT;
+}
+
+static void cantReach(Object *self, Object *noun2) {
+	// TODO: check if we need to use sqrawexists or sqexists
+	if (sqrawexists(self->_table, "verbCantReach")) {
+		int nParams = sqparamCount(g_engine->getVm(), self->_table, "verbCantReach");
+		debug("verbCantReach found in obj '{self.key}' with {nParams} params");
+		if (nParams == 1) {
+			sqcall(self->_table, "verbCantReach");
+		} else {
+			HSQOBJECT table;
+			sq_resetobject(&table);
+			if (noun2)
+				table = noun2->_table;
+			sqcall(self->_table, "verbCantReach", self->_table, table);
+		}
+	} else if (!noun2) {
+		cantReach(noun2, nullptr);
+	} else {
+		HSQOBJECT nilTbl;
+		sqcall(g_engine->_defaultObj, "verbCantReach", self->_table, !noun2 ? nilTbl : noun2->_table);
+	}
+}
+
+void Object::execVerb() {
+	if (_exec.enabled) {
+		VerbId verb = _exec.verb;
+		Object *noun1 = _exec.noun1;
+		Object *noun2 = _exec.noun2;
+
+		debug("actorArrived: exec sentence");
+		if (!noun1->inInventory()) {
+			// Object became untouchable as we were walking there
+			if (!noun1->_touchable) {
+				debug("actorArrived: noun1 untouchable");
+				_exec.enabled = false;
+				return;
+			}
+			// Did we get close enough?
+			float dist = distance(getUsePos(), noun1->getUsePos());
+			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
+			debug("actorArrived: noun1 min_dist: {dist} > {min_dist} (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?");
+			if (!verbNotClose(verb) && (dist > min_dist)) {
+				cantReach(this, noun1);
+				return;
+			}
+			if (noun1->_useDir != dNone) {
+				setFacing((Facing)noun1->_useDir);
+			}
+		}
+		if (noun2 && !noun2->inInventory()) {
+			if (!noun2->_touchable) {
+				// Object became untouchable as we were walking there.
+				debug("actorArrived: noun2 untouchable");
+				_exec.enabled = false;
+				return;
+			}
+			float dist = distance(getUsePos(), noun2->getUsePos());
+			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
+			debug("actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
+			if (dist > min_dist) {
+				cantReach(this, noun2);
+				return;
+			}
+		}
+
+		debug("actorArrived: callVerb");
+		_exec.enabled = false;
+		g_engine->callVerb(this, verb, noun1, noun2);
+	}
+}
+
+// Walks an actor to the `pos` or actor `obj` and then faces `dir`.
+void Object::walk(Math::Vector2d pos, Facing *facing) {
+	debug("walk to obj %s: %f,%f, %d", _key.c_str(), pos.getX(), pos.getY(), (int)*facing);
+	if (!_walkTo || (!_walkTo->isEnabled())) {
+		play(getAnimName(WALK_ANIMNAME), true);
+	}
+	_walkTo = new WalkTo(this, pos, facing ? *facing : 0);
+}
+
+// Walks an actor to the `obj` and then faces it.
+void Object::walk(Object *obj) {
+	debug("walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
+	Facing facing = (Facing)obj->_useDir;
+	walk(obj->getUsePos(), &facing);
+}
+
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
 	// TODO: obj->setTalking(new Talking(obj, texts, color));
 }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 528e46f9bdb..24f3dfa19b6 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -174,6 +174,8 @@ public:
 	Common::String getAnimName(const Common::String &key);
 	void setHeadIndex(int head);
 	void setAnimationNames(const Common::String &head, const Common::String &stand, const Common::String &walk, const Common::String &reach);
+	Common::String getReachAnim();
+
 	bool isWalking();
 	void stopWalking();
 	void blinkRate(float min, float max);
@@ -187,6 +189,9 @@ public:
 	void setMoveTo(Motor *moveTo);
 	void setWalkTo(Motor *walkTo);
 	Motor *getWalkTo() const { return _walkTo; }
+	void walk(Math::Vector2d pos, Facing* facing);
+	void walk(Object* obj);
+
 	void setTalking(Motor *talking);
 	void setBlink(Motor *blink);
 	void setTurnTo(Motor *turnTo);
@@ -199,6 +204,8 @@ public:
 
 	void pickupObject(Object *obj);
 
+	void execVerb();
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index d5867102b20..7621dffe466 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -374,7 +374,6 @@ void Room::objectParallaxLayer(Object *obj, int zsort) {
 	Layer *l = layer(zsort);
 	if (obj->_layer != l) {
 		// removes object from old layer
-		int id = obj->getId();
 		if(obj->_layer) {
 			int i = find(obj->_layer->_objects, obj);
 			obj->_layer->_node->removeChild(obj->_node);
@@ -396,6 +395,10 @@ Color Room::getOverlay() const {
 	return _overlayNode.getOverlayColor();
 }
 
+Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
+	return {frm, to};
+}
+
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
 	_names.push_back(name);
 	_parallax = parallax;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 78856a798ec..f842e44b456 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -124,6 +124,8 @@ public:
 	void setOverlay(Color color);
 	Color getOverlay() const;
 
+	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d frm, Math::Vector2d to);
+
 public:
 	Common::String _name;              // Name of the room
 	Common::String _sheet;             // Name of the spritesheet to use
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 50dba11dc62..1560187835c 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -137,6 +137,7 @@ public:
 
 	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
 	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
+	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 
 private:
 	void update(float elapsedMs);
@@ -148,7 +149,6 @@ private:
 	void clickedAt(Math::Vector2d scrPos);
 	bool clickedAtHandled(Math::Vector2d roomPos);
 	void setShaderEffect(RoomEffect effect);
-	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 	bool selectable(Object *actor);
 	void resetVerb();
 


Commit: e9ac13baf4e131a7c9fe61e0234d1c3e006c67cb
    https://github.com/scummvm/scummvm/commit/e9ac13baf4e131a7c9fe61e0234d1c3e006c67cb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix HUD draw

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/gfx.h
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/objlib.cpp
    engines/twp/roomlib.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 96fd82bbebd..0717006956e 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -166,10 +166,10 @@ static SQInteger actorColor(HSQUIRRELVM v) {
 	Object *actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	SQInteger c;
-	if (SQ_FAILED(sq_getinteger(v, 3, &c)))
+	int c;
+	if (SQ_FAILED(sqget(v, 3, c)))
 		return sq_throwerror(v, "failed to get color");
-	actor->_node->setColor(Color::rgb(c));
+	actor->_node->setColor(Color::fromRgba(c));
 	return 0;
 }
 
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index b2bffa65af1..f2365752e51 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -56,6 +56,10 @@ struct Color {
 	}
 
 	static Color rgb(int c) {
+		return create((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF), 0xFF);
+	}
+
+	static Color fromRgba(int c) {
 		return create((uint8)((c >> 16) & 0xFF), (uint8)((c >> 8) & 0xFF), (uint8)(c & 0xFF), (uint8)((c >> 24) & 0xFF));
 	}
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 9eafd453911..570cef123df 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -92,11 +92,10 @@ const char* verbFgtShader = R"(#version 110
 HudShader::~HudShader() {}
 
 void HudShader::applyUniforms() {
-	float value[]={0.8f, 0.8f};
-	GL_CALL(glUniform2fv(_rangesLoc, 1, value));
-	GL_CALL(glUniform4fv(_shadowColorLoc, 1, _shadowColor.v));
-	GL_CALL(glUniform4fv(_normalColorLoc, 1, _normalColor.v));
-	GL_CALL(glUniform4fv(_highlightColorLoc, 1, _highlightColor.v));
+	GL_CALL(glUniform2f(_rangesLoc, 0.8f, 0.8f));
+	GL_CALL(glUniform4f(_shadowColorLoc, _shadowColor.rgba.r, _shadowColor.rgba.g, _shadowColor.rgba.b, _shadowColor.rgba.a));
+	GL_CALL(glUniform4f(_normalColorLoc, _normalColor.rgba.r, _normalColor.rgba.g, _normalColor.rgba.b, _normalColor.rgba.a));
+	GL_CALL(glUniform4f(_highlightColorLoc, _highlightColor.rgba.r, _highlightColor.rgba.g, _highlightColor.rgba.b, _highlightColor.rgba.a));
 }
 
 Hud::Hud() : Node("hud") {
@@ -117,6 +116,12 @@ ActorSlot *Hud::actorSlot(Object *actor) {
 	return nullptr;
 }
 
+void Hud::drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf) {
+  Math::Vector3d pos(sf.spriteSourceSize.left,  - sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY(), 0.f);
+  trsf.translate(pos);
+  g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+}
+
 void Hud::drawCore(Math::Matrix4 trsf) {
 	ActorSlot *slot = this->actorSlot(_actor);
 	if(!slot) return;
@@ -144,10 +149,10 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
 
 	Shader *saveShader = g_engine->getGfx().getShader();
-	g_engine->getGfx().use(&_shader);
-	_shader._shadowColor = slot->verbUiColors.verbNormalTint;
-	_shader._normalColor = slot->verbUiColors.verbHighlight;
-	_shader._highlightColor = slot->verbUiColors.verbHighlightTint;
+	// g_engine->getGfx().use(&_shader);
+	// _shader._shadowColor = slot->verbUiColors.verbNormalTint;
+	// _shader._normalColor = slot->verbUiColors.verbHighlight;
+	// _shader._highlightColor = slot->verbUiColors.verbHighlightTint;
 
 	bool isOver = false;
 	for (int i = 1; i < 22; i++) {
@@ -161,10 +166,10 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 			if (_mouseClick && over) {
 				_verb = verb;
 			}
-			g_engine->getGfx().drawSprite(verbFrame.frame, *verbTexture, color, trsf);
+			drawSprite(verbFrame, verbTexture, color, trsf);
 		}
 	}
-	g_engine->getGfx().use(saveShader);
+	// g_engine->getGfx().use(saveShader);
 	_over = isOver;
 }
 
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 1f33a5759fe..5b6cda0463d 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -95,6 +95,7 @@ public:
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
+	void drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
 
 public:
 	ActorSlot _actorSlots[NUMACTORS];
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index e555a59ce9f..35bf1f4500b 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -341,7 +341,7 @@ static SQInteger objectColor(HSQUIRRELVM v) {
 		int color = 0;
 		if (SQ_FAILED(sqget(v, 3, color)))
 			return sq_throwerror(v, "failed to get color");
-		obj->_node->setColor(Color::rgb(color));
+		obj->_node->setColor(Color::fromRgba(color));
 	}
 	return 0;
 }
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 8fb29fe5b96..08c45bbc48b 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -328,7 +328,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
   Room* room = g_engine->_room;
   if (room->_overlayTo)
       room->_overlayTo->disable();
-  room->setOverlay(Color::rgb(startColor));
+  room->setOverlay(Color::fromRgba(startColor));
   if (numArgs == 4) {
     int endColor;
     if (SQ_FAILED(sqget(v, 3, endColor)))
@@ -337,7 +337,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
     if (SQ_FAILED(sqget(v, 4, duration)))
       return sq_throwerror(v, "failed to get duration");
     debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
-    g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::rgb(endColor));
+    g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
   }
   return 0;
 }


Commit: c48bfe68b7ad4db950a4cd918e23f3c38fc5b453
    https://github.com/scummvm/scummvm/commit/c48bfe68b7ad4db950a4cd918e23f3c38fc5b453
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add talking

Changed paths:
  A engines/twp/lip.cpp
  A engines/twp/lip.h
    engines/twp/actorlib.cpp
    engines/twp/ids.h
    engines/twp/module.mk
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.h
    engines/twp/tsv.cpp
    engines/twp/tsv.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 0717006956e..70b7c9a8a4e 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -660,7 +660,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get dir");
 			facing = (Facing*)&dir;
 		}
-		actor->walk(Math::Vector2d(x, y), facing);
+		actor->walk(Math::Vector2d(x, y), facing ? *facing : 0);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index f5633a221de..c3430521993 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -22,6 +22,8 @@
 #ifndef TWP_IDS_H
 #define TWP_IDS_H
 
+#include "common/hashmap.h"
+
 #define START_ACTORID 1000
 #define END_ACTORID 2000
 #define START_ROOMID 2000
@@ -237,6 +239,17 @@ enum Facing {
 	FACE_BACK = 8
 };
 
+}
+
+namespace Common {
+template<> struct Hash<Twp::Facing> : public UnaryFunction<Twp::Facing, uint> {
+	uint operator()(Twp::Facing val) const { return (uint)val; }
+};
+}
+
+
+namespace Twp {
+
 bool isThread(int id);
 bool isRoom(int id);
 bool isActor(int id);
diff --git a/engines/twp/lip.cpp b/engines/twp/lip.cpp
new file mode 100644
index 00000000000..029371d4408
--- /dev/null
+++ b/engines/twp/lip.cpp
@@ -0,0 +1,48 @@
+
+/* 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 "twp/lip.h"
+
+namespace Twp {
+
+void Lip::load(Common::SeekableReadStream *stream) {
+	_items.clear();
+	while (!stream->eos()) {
+		LipItem item;
+		Common::String line = stream->readLine();
+		sscanf(line.c_str(), "%f\t%c", &item.time, &item.letter);
+		_items.push_back(item);
+	}
+}
+
+char Lip::letter(float time) {
+	if (_items.size() == 0)
+		return 'A';
+	for (int i = 0; i < _items.size() - 1; i++) {
+		if (time < _items[i + 1].time) {
+			return _items[i].letter;
+		}
+	}
+	return _items[_items.size() - 1].letter;
+}
+
+} // namespace Twp
diff --git a/engines/twp/lip.h b/engines/twp/lip.h
new file mode 100644
index 00000000000..51955b5978f
--- /dev/null
+++ b/engines/twp/lip.h
@@ -0,0 +1,53 @@
+
+/* 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 TWP_LIP_H
+#define TWP_LIP_H
+
+#include "common/stream.h"
+#include "common/array.h"
+
+namespace Twp {
+
+struct LipItem {
+	float time;
+	char letter;
+};
+
+// This contains the lip animation for a specific text.
+//
+// A lip animation contains a list of moth shape at a specific time.
+// You can see https://github.com/DanielSWolf/rhubarb-lip-sync to
+// have additional information about the mouth shapes.
+class Lip {
+public:
+	void load(Common::SeekableReadStream* stream);
+	// Gets the letter corresponding to a mouth shape at a spcific time.
+	char letter(float time);
+
+private:
+	Common::Array<LipItem> _items;
+};
+
+}
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index acc6cc82492..5413a88e5a6 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -56,6 +56,7 @@ MODULE_OBJS = \
 	dialog.o \
 	shaders.o \
 	hud.o \
+	lip.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 51816e10005..ef6c33c2a52 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -265,4 +265,161 @@ void WalkTo::update(float elapsed) {
 	}
 }
 
+Talking::Talking(Object *obj, const Common::StringArray &texts, Color color) {
+	_obj = obj;
+	_color = color;
+	_texts.assign(texts.begin() + 1, texts.end());
+	say(texts[0]);
+}
+
+static int letterToIndex(char c) {
+	switch (c) {
+	case 'A':
+		return 1;
+	case 'B':
+		return 2;
+	case 'C':
+		return 3;
+	case 'D':
+		return 4;
+	case 'E':
+		return 5;
+	case 'F':
+		return 6;
+	case 'G':
+		return 1;
+	case 'H':
+		return 4;
+	case 'X':
+		return 1;
+	default:
+		error("unknown letter %c", c);
+	}
+}
+
+void Talking::update(float elapsed) {
+	if (isEnabled()) {
+		_elapsed += elapsed;
+		if (_elapsed < _duration) {
+			char letter = _lip.letter(_elapsed);
+			_obj->setHeadIndex(letterToIndex(letter));
+		} else {
+			if (_texts.size() > 0) {
+				say(_texts[0]);
+				_texts.remove_at(0);
+			} else {
+				disable();
+			}
+		}
+	}
+}
+
+void Talking::say(const Common::String &text) {
+	Common::String txt(text);
+	if (text[0] == '@') {
+		int id = atoi(text.c_str() + 1);
+		txt = g_engine->_textDb.getText(id);
+
+		id = onTalkieId(id);
+		Common::String key = talkieKey();
+		key.toUppercase();
+		Common::String name = Common::String::format("%s_%d", key.c_str(), id);
+		Common::String path = name + ".lip";
+
+		debug("Load lip %s", path.c_str());
+		if (g_engine->_pack.assetExists(path.c_str())) {
+			GGPackEntryReader entry;
+			entry.open(g_engine->_pack, path);
+			_lip.load(&entry);
+			debug("Lip %s loaded: {self.lip}", path.c_str());
+		}
+
+		// TODO: call sayingLine
+		// TODO: _soundId = self.loadActorSpeech(name)
+	} else if (text[0] == '^') {
+		txt = text.substr(1);
+	}
+
+	// remove text in parenthesis
+	if (txt[0] == '(') {
+		int i = txt.find(')');
+		if (i != -1)
+			txt = txt.substr(i + 1);
+	}
+
+	debug("sayLine '%s'", txt.c_str());
+
+	// modify state ?
+	Common::String state;
+	if (txt[0] == '{') {
+		int i = txt.find('}');
+		if (i != -1) {
+			state = txt.substr(1, txt.size() - 2);
+			debug("Set state from anim '%s'", state.c_str());
+			if (state != "notalk") {
+				_obj->play(state);
+			}
+			txt = txt.substr(i + 1);
+		}
+	}
+
+	setDuration(txt);
+
+	if(_obj->_sayNode) {
+		_obj->_sayNode->remove();
+	}
+	Text text2("sayline", txt, thCenter, tvCenter, SCREEN_WIDTH * 3.f / 4.f, _color);
+	_obj->_sayNode = new TextNode();
+	((TextNode *)_obj->_sayNode)->setText(text2);
+	_obj->_sayNode->setColor(_color);
+	_node = _obj->_sayNode;
+	Math::Vector2d pos = g_engine->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
+
+	// clamp position to keep it on screen
+	pos.setX(Twp::clamp(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
+	pos.setY(Twp::clamp(pos.getY(), 10.f + text2.getBounds().getY(), SCREEN_HEIGHT - text2.getBounds().getY()));
+
+	_obj->_sayNode->setPos(pos);
+	_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
+	g_engine->_screenScene.addChild(_obj->_sayNode);
+}
+
+void Talking::disable() {
+	Motor::disable();
+	_texts.clear();
+	_obj->setHeadIndex(1);
+	_node->remove();
+}
+
+int Talking::onTalkieId(int id) {
+	int result = 0;
+	sqcallfunc(result, "onTalkieID", _obj->_table, id);
+	if (result == 0)
+		result = id;
+	return result;
+}
+
+void Talking::setDuration(const Common::String &text) {
+	_elapsed = 0;
+	// let sayLineBaseTime = prefs(SayLineBaseTime);
+	float sayLineBaseTime = 1.5f;
+	//   let sayLineCharTime = prefs(SayLineCharTime);
+	float sayLineCharTime = 0.025f;
+	// let sayLineMinTime = prefs(SayLineMinTime);
+	float sayLineMinTime = 0.2f;
+	//   let sayLineSpeed = prefs(SayLineSpeed);
+	float sayLineSpeed = 0.5f;
+	float duration = (sayLineBaseTime + sayLineCharTime * text.size()) / (0.2f + sayLineSpeed);
+	_duration = MAX(duration, sayLineMinTime);
+}
+
+Common::String Talking::talkieKey() {
+	Common::String result;
+	if (sqrawexists(_obj->_table, "_talkieKey"))
+		sqgetf(_obj->_table, "_talkieKey", result);
+	else
+		sqgetf(_obj->_table, "_key", result);
+	return result;
+}
+
 } // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index d50e709c363..d93e3c0f178 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -26,6 +26,7 @@
 #include "math/vector2d.h"
 #include "twp/camera.h"
 #include "twp/util.h"
+#include "twp/lip.h"
 
 namespace Twp {
 
@@ -188,7 +189,7 @@ private:
 class ReachAnim : public Motor {
 public:
 	virtual ~ReachAnim();
-	ReachAnim(Object* actor, Object* obj);
+	ReachAnim(Object *actor, Object *obj);
 
 	virtual void update(float elasped) override;
 
@@ -196,15 +197,15 @@ private:
 	void playReachAnim();
 
 private:
-	Object* _actor = nullptr;
-    Object* _obj = nullptr;
-    int _state = 0;
-    float _elapsed = 0.f;
+	Object *_actor = nullptr;
+	Object *_obj = nullptr;
+	int _state = 0;
+	float _elapsed = 0.f;
 };
 
 class WalkTo : public Motor {
 public:
-	WalkTo(Object* obj, Math::Vector2d dest, int facing = 0);
+	WalkTo(Object *obj, Math::Vector2d dest, int facing = 0);
 	virtual void disable() override;
 
 private:
@@ -212,11 +213,35 @@ private:
 	virtual void update(float elapsed) override;
 
 private:
-	Object* _obj = nullptr;
-    Common::Array<Math::Vector2d> _path;
-    int _facing = 0;
-    float _wsd;
-    ReachAnim* _reach = nullptr;
+	Object *_obj = nullptr;
+	Common::Array<Math::Vector2d> _path;
+	int _facing = 0;
+	float _wsd;
+	ReachAnim *_reach = nullptr;
+};
+
+// Creates a talking animation for a specified object.
+class Talking : public Motor {
+public:
+	Talking(Object* obj, const Common::StringArray& texts, Color color);
+
+private:
+	virtual void update(float elapsed) override;
+	virtual void disable() override;
+	int onTalkieId(int id);
+	Common::String talkieKey();
+	void setDuration(const Common::String& text);
+	void say(const Common::String& text);
+
+private:
+	Object *_obj = nullptr;
+	Node *_node = nullptr;
+	Lip _lip;
+	float _elapsed = 0.f;
+	float _duration = 0.f;
+	//   SoundId soundId;
+	Color _color;
+	Common::StringArray _texts;
 };
 
 } // namespace Twp
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 7b31d2b0284..5946b49ec62 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -45,6 +45,11 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 	: _talkOffset(0, 90), _table(o), _key(key) {
 }
 
+Object::~Object() {
+	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
+	_node->getParent()->removeChild(_node);
+}
+
 Object *Object::createActor() {
 	Object *result = new Object();
 	result->_hotspot = Common::Rect(-18, 0, 37, 71);
@@ -141,8 +146,10 @@ void Object::showLayer(const Common::String &layer, bool visible) {
 Facing Object::getFacing() const {
 	if (_facingLockValue != 0)
 		return (Facing)_facingLockValue;
-	if (_facingMap.contains(_facing))
-		return _facingMap[_facing];
+	for (int i = 0; i < _facingMap.size(); i++) {
+		if (_facingMap[i].key == _facing)
+			return _facingMap[i].value;
+	}
 	return _facing;
 }
 
@@ -308,11 +315,6 @@ void Object::setRoom(Room *room) {
 	}
 }
 
-void Object::delObject() {
-	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
-	_node->getParent()->removeChild(_node);
-}
-
 static void disableMotor(Motor *motor) {
 	if (motor)
 		motor->disable();
@@ -478,18 +480,21 @@ void Object::update(float elapsedSec) {
 	if (_nodeAnim)
 		_nodeAnim->update(elapsedSec);
 
-	// TODO: update
-	//   if self.icons.len > 1 and self.iconFps > 0:
-	//     self.iconElapsed += elapsedSec
-	//     if self.iconElapsed > (1f / self.iconFps.float32):
-	//       self.iconElapsed = 0f
-	//       self.iconIndex = (self.iconIndex + 1) mod self.icons.len
+	if ((_icons.size() > 1) && (_iconFps > 0)) {
+		_iconElapsed += elapsedSec;
+		if (_iconElapsed > (1.f / _iconFps)) {
+			_iconElapsed = 0.f;
+			_iconIndex = (_iconIndex + 1) % _icons.size();
+		}
+	}
 
-	//   if self.popCount > 0:
-	//       self.popElapsed += elapsedSec
-	//       if self.popElapsed > 0.5f:
-	//         dec self.popCount
-	//         self.popElapsed -= 0.5f
+	if (_popCount > 0) {
+		_popElapsed += elapsedSec;
+		if (_popElapsed > 0.5f) {
+			_popCount--;
+			_popElapsed -= 0.5f;
+		}
+	}
 }
 
 void Object::pickupObject(Object *obj) {
@@ -525,10 +530,10 @@ void Object::lockFacing(int facing) {
 }
 
 void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
-	_facingMap[FACE_LEFT] = left;
-	_facingMap[FACE_RIGHT] = right;
-	_facingMap[FACE_FRONT] = front;
-	_facingMap[FACE_BACK] = back;
+	_facingMap.push_back({FACE_LEFT, left});
+	_facingMap.push_back({FACE_RIGHT, right});
+	_facingMap.push_back({FACE_FRONT, front});
+	_facingMap.push_back({FACE_BACK, back});
 }
 
 int Object::flags() {
@@ -653,23 +658,32 @@ void Object::execVerb() {
 }
 
 // Walks an actor to the `pos` or actor `obj` and then faces `dir`.
-void Object::walk(Math::Vector2d pos, Facing *facing) {
-	debug("walk to obj %s: %f,%f, %d", _key.c_str(), pos.getX(), pos.getY(), (int)*facing);
+void Object::walk(Math::Vector2d pos, int facing) {
+	debug("walk to obj %s: %f,%f, %d", _key.c_str(), pos.getX(), pos.getY(), facing);
 	if (!_walkTo || (!_walkTo->isEnabled())) {
 		play(getAnimName(WALK_ANIMNAME), true);
 	}
-	_walkTo = new WalkTo(this, pos, facing ? *facing : 0);
+	_walkTo = new WalkTo(this, pos, facing);
 }
 
 // Walks an actor to the `obj` and then faces it.
 void Object::walk(Object *obj) {
 	debug("walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
 	Facing facing = (Facing)obj->_useDir;
-	walk(obj->getUsePos(), &facing);
+	walk(obj->getUsePos(), facing);
+}
+
+void Object::turn(Facing facing) {
+	setFacing(facing);
+}
+
+void Object::turn(Object *obj) {
+	Facing facing = getFacingToFaceTo(this, obj);
+	setFacing(facing);
 }
 
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
-	// TODO: obj->setTalking(new Talking(obj, texts, color));
+	obj->setTalking(new Talking(obj, texts, _color));
 }
 
 } // namespace Twp
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 24f3dfa19b6..bba68323b51 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -104,10 +104,16 @@ struct TalkingState {
 	void say(const Common::StringArray &texts, Object *obj);
 };
 
+struct LockFacing {
+	Facing key;
+	Facing value;
+};
+
 class Object {
 public:
 	Object();
 	Object(HSQOBJECT o, const Common::String &key);
+	~Object();
 
 	static Object *createActor();
 
@@ -189,7 +195,7 @@ public:
 	void setMoveTo(Motor *moveTo);
 	void setWalkTo(Motor *walkTo);
 	Motor *getWalkTo() const { return _walkTo; }
-	void walk(Math::Vector2d pos, Facing* facing);
+	void walk(Math::Vector2d pos, int facing = 0);
 	void walk(Object* obj);
 
 	void setTalking(Motor *talking);
@@ -205,6 +211,8 @@ public:
 	void pickupObject(Object *obj);
 
 	void execVerb();
+	void turn(Facing facing);
+	void turn(Object* obj);
 
 private:
 	Common::String suffix() const;
@@ -236,7 +244,7 @@ public:
 	Common::String _animName;
 	int _animFlags = 0;
 	bool _animLoop = false;
-	Common::HashMap<Facing, Facing, Common::Hash<int> > _facingMap;
+	Common::Array<LockFacing> _facingMap;
 	Facing _facing = FACE_FRONT;
 	int _facingLockValue = 0;
 	float _fps = 0.f;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 35bf1f4500b..fb36c3bb80d 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -152,7 +152,6 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 static SQInteger deleteObject(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
 	if (obj) {
-		obj->delObject();
 		delete obj;
 	}
 	return 0;
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index d8672036feb..7f8fc2a6a31 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -565,4 +565,32 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 	//   }
 }
 
+SentenceNode::SentenceNode() : Node("Sentence") {
+	_zOrder = -100;
+}
+
+SentenceNode::~SentenceNode() {
+}
+
+void SentenceNode::setText(const Common::String &text) {
+	_text = text;
+}
+
+void SentenceNode::drawCore(Math::Matrix4 trsf) {
+	Text text("sayline", _text);
+	float x, y;
+	//   if prefs(ClassicSentence):
+	//     x = (ScreenWidth - text.bounds.x) / 2f;
+	//     y = 208f;
+	//   else:
+	x = MAX(_pos.getX() - text.getBounds().getX() / 2.f, MARGIN);
+	x = MIN(x, SCREEN_WIDTH - text.getBounds().getX() - MARGIN);
+	y = _pos.getY() + 2.f * 38.f;
+	if (y >= SCREEN_HEIGHT)
+		y = _pos.getY() - 38.f;
+	Math::Matrix4 t;
+	t.translate(Math::Vector3d(x, y, 0.f));
+	text.draw(g_engine->getGfx(), t);
+}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 263a811b946..1ffa42d0d1a 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -250,6 +250,20 @@ private:
     Object* _obj = nullptr;
 };
 
+class SentenceNode: public Node {
+public:
+	SentenceNode();
+	virtual ~SentenceNode();
+
+	void setText(const Common::String& text);
+
+private:
+	void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Common::String _text;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 6f7e77312fe..2e12e9c2703 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -138,6 +138,9 @@ void sqcall(HSQOBJECT o, const char *name, T... args);
 template<typename TResult, typename... T>
 static void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args);
 
+template<typename TResult, typename... T>
+static void sqcallfunc(TResult &result, const char *name, T... args);
+
 void sqexec(HSQUIRRELVM v, const char *code, const char *filename = nullptr);
 
 class Room;
@@ -224,6 +227,33 @@ void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
 	sq_settop(v, top);
 }
 
+template<typename TResult, typename... T>
+void sqcallfunc(TResult &result, const char *name, T... args) {
+	constexpr std::size_t n = sizeof...(T);
+	HSQUIRRELVM v = g_engine->getVm();
+	HSQOBJECT o = sqrootTbl(v);
+	SQInteger top = sq_gettop(v);
+	sqpush(v, o);
+	sq_pushstring(v, _SC(name), -1);
+	if (SQ_FAILED(sq_get(v, -2))) {
+		sq_settop(v, top);
+		error("can't find %s function", name);
+		return;
+	}
+	sq_remove(v, -2);
+
+	sqpush(v, o);
+	sqpush(v, std::forward<T>(args)...);
+	if (SQ_FAILED(sq_call(v, n + 1, SQTrue, SQTrue))) {
+		// sqstd_printcallstack(v);
+		sq_settop(v, top);
+		error("function %s call failed", name);
+		return;
+	}
+	sqget(v, -1, result);
+	sq_settop(v, top);
+}
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 199799c9e44..ad6f42a1e7c 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -27,13 +27,12 @@
 namespace Twp {
 
 void TextDb::parseTsv(Common::SeekableReadStream &stream) {
-	char s[128];
 	stream.readLine();
 	while(!stream.eos()) {
 		Common::String line = stream.readLine();
-		int id;
-		sscanf(line.c_str(), "%d\t%s", &id, s);
-		_texts[id] = s;
+		int pos = line.find(' ',0);
+		int id = atoi(line.c_str());
+		_texts[id] = line.substr(pos+1);
 	}
 }
 
diff --git a/engines/twp/tsv.h b/engines/twp/tsv.h
index 0f0cc97c9bf..12773f249c9 100644
--- a/engines/twp/tsv.h
+++ b/engines/twp/tsv.h
@@ -31,8 +31,6 @@ class TextDb {
 public:
 	void parseTsv(Common::SeekableReadStream& stream);
 	Common::String getText(const Common::String& text);
-
-private:
 	Common::String getText(int id);
 
 private:
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 184885cf85f..b58e487b626 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -57,6 +57,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	g_engine = this;
 	sq_resetobject(&_defaultObj);
 	_screenScene.addChild(&_inputState);
+	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
 	_screenScene.addChild(&_uiInv);
 }
@@ -105,37 +106,158 @@ bool TwpEngine::clickedAtHandled(Math::Vector2d roomPos) {
 	return result;
 }
 
+bool TwpEngine::preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+	bool result = false;
+	HSQOBJECT n2Table;
+	Common::String n2Name;
+	if (noun2) {
+		n2Table = noun2->_table;
+		n2Name = Common::String::format("%s(%s)", noun2->_name.c_str(), noun2->_key.c_str());
+	} else {
+		sq_resetobject(&n2Table);
+	}
+	if (sqrawexists(actor->_table, "actorPreWalk")) {
+		debug("actorPreWalk %d n1=%s(%s) n2=%s", verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str());
+		sqcallfunc(result, actor->_table, "actorPreWalk", verbId.id, noun1->_table, n2Table);
+	}
+	if (!result) {
+		Common::String funcName = isActor(noun1->getId()) ? "actorPreWalk" : "objectPreWalk";
+		if (sqrawexists(noun1->_table, funcName)) {
+			sqcallfunc(result, noun1->_table, funcName.c_str(), verbId.id, noun1->_table, n2Table);
+			debug("%s %d n1=%s(%s) n2=%s -> %s", funcName.c_str(), verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str(), result ? "yes" : "no");
+		}
+	}
+	return result;
+}
+
+static bool verbNoWalkTo(VerbId verbId, Object *noun1) {
+	if (verbId.id == VERB_LOOKAT)
+		return (noun1->getFlags() & FAR_LOOK) != 0;
+	return false;
+}
+
+// Called to execute a sentence and, if needed, start the actor walking.
+// If `actor` is `null` then the selectedActor is assumed.
+bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+	Common::String name = !actor ? "currentActor" : actor->_key;
+	Common::String noun1name = !noun1 ? "null" : noun1->_key;
+	Common::String noun2name = !noun2 ? "null" : noun2->_key;
+	debug("exec({name},{verbId.VerbId},{noun1name},{noun2name})");
+	actor = !actor ? g_engine->_actor : actor;
+	if ((verbId.id <= 0) || (verbId.id > 13) || (!noun1) || (!actor))
+		return false;
+	// TODO
+	// if (a?._verb_tid) stopthread(actor._verb_tid)
+
+	debug("noun1.inInventory: {noun1.inInventory} and noun1.touchable: {noun1.touchable} nowalk: {verbNoWalkTo(verbId, noun1)}");
+
+	// test if object became untouchable
+	if (!noun1->inInventory() && !noun1->_touchable)
+		return false;
+	if (noun2 && (!noun2->inInventory()) && (!noun2->_touchable))
+		return false;
+
+	if (noun1->inInventory()) {
+		if (!noun2 || noun2->inInventory()) {
+			callVerb(actor, verbId, noun1, noun2);
+			return true;
+		}
+	}
+
+	if (preWalk(actor, verbId, noun1, noun2))
+		return true;
+
+	if (verbNoWalkTo(verbId, noun1)) {
+		if (!noun1->inInventory()) { // TODO: test if verb.flags != VERB_INSTANT
+			actor->turn(noun1);
+			callVerb(actor, verbId, noun1, noun2);
+			return true;
+		}
+	}
+
+	actor->_exec.verb = verbId;
+	actor->_exec.noun1 = noun1;
+	actor->_exec.noun2 = noun2;
+	actor->_exec.enabled = true;
+	if (!noun1->inInventory())
+		actor->walk(noun1);
+	else
+		actor->walk(noun2);
+	return true;
+}
+
 void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 	// TODO: update this
 	if (_room) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
-		// Object *obj = objAt(roomPos);
+		Object *obj = objAt(roomPos);
 
 		if (_cursor.isLeftClick()) {
 			// button left: execute selected verb
-			clickedAtHandled(roomPos);
-			// if (!handled && obj) {
-			//     sqcall("onVerbClick");
-			//     handled = execSentence(nullptr, 1, _noun1, _noun2);
-			//   }
-			//   if not handled:
-			//     if (not self.actor.isNil and scrPos.y > 172) {
-			//       self.actor.walk(room_pos)
-			//       self.hud.verb = self.hud.actorSlot(self.actor).verbs[0]
-			// 	}
-			// Just clicking on the ground
+			bool handled = clickedAtHandled(roomPos);
+			if (!handled && obj) {
+				Verb vb = verb();
+				sqcall("onVerbClick");
+				handled = execSentence(nullptr, vb.id, _noun1, _noun2);
+			}
+			if (!handled) {
+				if (_actor && (scrPos.getY() > 172)) {
+					_actor->walk(roomPos);
+					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+				}
+			}
+			// TODO: Just clicking on the ground
 			//     cancelSentence(self.actor)
+		} else if (_cursor.isRightClick()) {
+			// button right: execute default verb
+			if (obj) {
+				VerbId verb;
+				verb.id = obj->defaultVerbId();
+				execSentence(nullptr, verb, _noun1, _noun2);
+			}
+		} else if (_walkFastState && _cursor.leftDown && _actor && (scrPos.getY() > 172)) {
+			_actor->walk(roomPos);
 		}
-		// else if _cursor.rightDown) {
-		//   // button right: execute default verb
-		//   if not obj.isNil:
-		//     discard self.execSentence(nil, obj.defaultVerbId, self.noun1, self.noun2)
-		// } else if (self.walkFastState and self.mouseState.pressed() and not self.actor.isNil and scrPos.y > 172) {
-		//   self.actor.walk(room_pos);
-		// }
 	}
 }
 
+Verb TwpEngine::verb() {
+	Verb result = _hud._verb;
+	if (result.id.id == VERB_WALKTO && _noun1 && _noun1->inInventory())
+		result = _hud.actorSlot(_actor)->verbs[_noun1->defaultVerbId()];
+	return result;
+}
+
+Common::String TwpEngine::cursorText() {
+	Common::String result;
+	if (_dialog.getState() == DialogState::None) {
+		if (_hud.isVisible() && _hud._over) {
+			return _hud._verb.id.id > 1 ? _textDb.getText(verb().text) : "";
+		}
+
+		// give can be used only on inventory and talkto to talkable objects (actors)
+		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->_name);
+
+		// add verb if not walk to or if noun1 is present
+		if ((_hud._verb.id.id > 1) || (result.size() > 0)) {
+			// if inventory, use default verb instead of walkto
+			Common::String verbText = verb().text;
+			result = result.size() > 0 ? Common::String::format("%s %s", _textDb.getText(verbText).c_str(), result.c_str()) : _textDb.getText(verbText);
+			if (_useFlag == ufUseWith)
+				result += " " + _textDb.getText(10000);
+			else if (_useFlag == ufUseOn)
+				result += " " + _textDb.getText(10001);
+			else if (_useFlag == ufUseIn)
+				result += " " + _textDb.getText(10002);
+			else if (_useFlag == ufGiveTo)
+				result += " " + _textDb.getText(10003);
+			if (_noun2)
+				result += " " + _textDb.getText(_noun2->_name);
+		}
+	}
+	return result;
+}
+
 void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
@@ -144,12 +266,14 @@ void TwpEngine::update(float elapsed) {
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
 	//_inputState.visible = _inputState.showCursor; // TODO: || _dlg.state == WaitingForChoice;
 	_inputState.setPos(scrPos);
+	_sentence.setPos(scrPos);
 	// TODO:
-	// _sentence.pos = scrPos;
 	// _dlg.mousePos = scrPos;
 	if (_room) {
-		if (_cursor.isLeftClick())
+		if (_cursor.isLeftClick() || _cursor.isRightClick())
 			clickedAt(_cursor.pos);
+
+		_sentence.setText(cursorText());
 	}
 
 	_fadeShader->_elapsed += elapsed;
@@ -209,14 +333,14 @@ void TwpEngine::update(float elapsed) {
 		}
 	}
 
-	 // update inventory
-  if (!_actor) {
-    _uiInv.update(elapsed);
-  } else {
-    // TODO: _hud.update(scrPos, _noun1, _cursor.isLeftClick());
-    VerbUiColors* verbUI = &_hud.actorSlot(_actor)->verbUiColors;
-    _uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
-  }
+	// update inventory
+	if (!_actor) {
+		_uiInv.update(elapsed);
+	} else {
+		// TODO: _hud.update(scrPos, _noun1, _cursor.isLeftClick());
+		VerbUiColors *verbUI = &_hud.actorSlot(_actor)->verbUiColors;
+		_uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
+	}
 }
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
@@ -272,7 +396,7 @@ void TwpEngine::draw() {
 
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	bool flipY = _fadeShader->_effect == FadeEffect::Wobble;
-	Math::Vector2d camPos = _gfx.camera();
+	Math::Vector2d camPos = _gfx.cameraPos();
 	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, flipY);
 
 	Texture *screenTexture = &renderTexture2;
@@ -335,7 +459,7 @@ void TwpEngine::draw() {
 
 	g_system->updateScreen();
 
-	// _gfx.cameraPos(camPos);
+	_gfx.cameraPos(camPos);
 }
 
 Common::Error TwpEngine::run() {
@@ -708,7 +832,6 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 			for (int j = 0; j < _room->_layers.size(); j++) {
 				Object *obj = layer->_objects[i];
 				if (obj->_temporary) {
-					obj->delObject();
 					delete obj;
 				} else if (isActor(obj->getId()) && _actor != obj) {
 					obj->stopObjectMotors();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 1560187835c..675a6f79d30 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -151,6 +151,10 @@ private:
 	void setShaderEffect(RoomEffect effect);
 	bool selectable(Object *actor);
 	void resetVerb();
+	Common::String cursorText();
+	Verb verb();
+	bool execSentence(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
+	bool preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2);
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -191,6 +195,7 @@ public:
 			oldRightDown = rightDown;
 		}
 		bool isLeftClick() { return oldLeftDown && !leftDown; }
+		bool isRightClick() { return oldRightDown && !rightDown; }
 	} _cursor;
 	Hud _hud;
 
@@ -201,6 +206,7 @@ private:
 	ShaderParams _shaderParams;
 	unique_ptr<FadeShader> _fadeShader;
 	Inventory _uiInv;
+	SentenceNode _sentence;
 };
 
 extern TwpEngine *g_engine;


Commit: 8acd373b0c742c21a4fbcc9f8119f0cae68b2abb
    https://github.com/scummvm/scummvm/commit/8acd373b0c742c21a4fbcc9f8119f0cae68b2abb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add callbacks

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/callback.cpp
    engines/twp/callback.h
    engines/twp/dialog.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 70b7c9a8a4e..61a01d1eda6 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -542,8 +542,22 @@ static SQInteger actorTalking(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Turn to the pos, dir, object or actor over 2 frames.
 static SQInteger actorTurnTo(HSQUIRRELVM v) {
-	warning("TODO: actorTurnTo not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	if (sq_gettype(v, 3) == OT_INTEGER) {
+		int facing = 0;
+		if (SQ_FAILED(sqget(v, 3, facing)))
+			return sq_throwerror(v, "failed to get facing");
+		actor->turn((Facing)facing);
+	} else {
+		Object *obj = sqobj(v, 3);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object to face to");
+		actor->turn(obj);
+	}
 	return 0;
 }
 
@@ -608,14 +622,65 @@ static SQInteger actorVolume(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Gets the specified actor to walk forward the distance specified.
+//
+// . code-block:: Squirrel
+// script sheriffOpening2() {
+//     cutscene(@() {
+//         actorAt(sheriff, CityHall.spot1)
+//         actorWalkForward(currentActor, 50)
+//         ...
+//     }
+// }
 static SQInteger actorWalkForward(HSQUIRRELVM v) {
-	warning("TODO: actorWalkForward not implemented");
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	int dist;
+	if (SQ_FAILED(sqget(v, 3, dist)))
+		return sq_throwerror(v, "failed to get dist");
+	Math::Vector2d dir;
+	switch (actor->getFacing()) {
+	case FACE_FRONT:
+		dir = Math::Vector2d(0, -dist);
+		break;
+	case FACE_BACK:
+		dir = Math::Vector2d(0, dist);
+		break;
+	case FACE_LEFT:
+		dir = Math::Vector2d(-dist, 0);
+		break;
+	case FACE_RIGHT:
+		dir = Math::Vector2d(dist, 0);
+		break;
+	}
+	actor->walk(actor->_node->getAbsPos() + dir);
 	return 0;
 }
 
+// Returns true if the specified actor is currently walking.
+// If no actor is specified, then returns true if the current player character is walking.
+//
+// . code-block:: Squirrel
+// script _startWriting() {
+//    if (!actorWalking(this)) {
+//        if (notebookOpen == NO) {
+//            actorPlayAnimation(reyes, "start_writing", NO)
+//            breaktime(0.30)
+//        }
+//        ...
+//    }
+//}
 static SQInteger actorWalking(HSQUIRRELVM v) {
-	warning("TODO: actorWalking not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	Object *actor = nullptr;
+	if (nArgs == 1) {
+		actor = g_engine->_actor;
+	} else if (nArgs == 2) {
+		actor = sqactor(v, 2);
+	}
+	sqpush(v, actor && actor->isWalking());
+	return 1;
 }
 
 // Sets the walk speed of an actor.
@@ -658,7 +723,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 			int dir;
 			if (SQ_FAILED(sqget(v, 5, dir)))
 				return sq_throwerror(v, "failed to get dir");
-			facing = (Facing*)&dir;
+			facing = (Facing *)&dir;
 		}
 		actor->walk(Math::Vector2d(x, y), facing ? *facing : 0);
 	} else {
diff --git a/engines/twp/callback.cpp b/engines/twp/callback.cpp
index 08e3dd08453..cd3a229189c 100644
--- a/engines/twp/callback.cpp
+++ b/engines/twp/callback.cpp
@@ -20,16 +20,21 @@
  */
 
 #include "twp/callback.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
+Callback::Callback(int id, float duration, const Common::String &name, const Common::Array<HSQOBJECT> &args)
+	: _id(id), _duration(duration), _name(name), _args(args) {
+}
+
 void Callback::call() {
-	sqcall(name, args);
+	sqcall(_name.c_str(), _args);
 }
 
 bool Callback::update(float elapsed) {
-	self.elapsed += elapsed;
-	bool result = self.elapsed > self.duration;
+	_elapsed += elapsed;
+	bool result = _elapsed > _duration;
 	if (result)
 		call();
 	return result;
diff --git a/engines/twp/callback.h b/engines/twp/callback.h
index 5cb65095224..8c339ccf5ba 100644
--- a/engines/twp/callback.h
+++ b/engines/twp/callback.h
@@ -30,11 +30,16 @@ namespace Twp {
 
 class Callback {
 public:
-	int id = 0;
-	Common::String name;
-	Common::Array<HSQOBJECT> args;
-	float duration = 0.f;
-	float elapsed = 0.f;
+	Callback(int id, float duration, const Common::String& name, const Common::Array<HSQOBJECT>& args);
+	bool update(float elapsed);
+	int getId() const { return _id; }
+
+private:
+	int _id = 0;
+	Common::String _name;
+	Common::Array<HSQOBJECT> _args;
+	float _duration = 0.f;
+	float _elapsed = 0.f;
 
 public:
 	void call();
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 7cb411e285c..36bf95a4ef2 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -168,6 +168,8 @@ public:
 
 	DialogState getState() const { return _state; }
 
+	void setMousePos(Math::Vector2d pos) { _mousePos = pos; }
+
 	void start(const Common::String &actor, const Common::String &name, const Common::String &node);
 	void selectLabel(int line, const Common::String &name);
 	bool isOnce(int line) const;
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 1da69ca152e..a604522488a 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -316,7 +316,8 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 2, verb)))
 			return sq_throwerror(v, "failed to get verb");
 		ActorSlot *actorSlot = g_engine->_hud.actorSlot(g_engine->_actor);
-		if(!actorSlot) return 0;
+		if (!actorSlot)
+			return 0;
 		for (int i = 1; i < 22; i++) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
@@ -540,9 +541,39 @@ static SQInteger ord(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Executes a verb sentence as though the player had inputted/constructed it themselves.
+// You can push several sentences one after the other.
+// They will execute in reverse order (it's a stack).
 static SQInteger pushSentence(HSQUIRRELVM v) {
-	// TODO: pushSentence
-	warning("pushSentence not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	int id;
+	if (SQ_FAILED(sqget(v, 2, id)))
+		return sq_throwerror(v, "Failed to get verb id");
+
+	if (id == VERB_DIALOG) {
+		int choice;
+		if (SQ_FAILED(sqget(v, 3, choice)))
+			return sq_throwerror(v, "Failed to get choice");
+		// TODO choose(choice)
+		warning("pushSentence with VERB_DIALOG not implemented");
+		return 0;
+	}
+
+	Object *obj1 = nullptr;
+	Object *obj2 = nullptr;
+	if (nArgs >= 3) {
+		obj1 = sqobj(v, 3);
+		if (!obj1)
+			return sq_throwerror(v, "Failed to get obj1");
+	}
+	if (nArgs == 4) {
+		obj2 = sqobj(v, 4);
+		if (!obj2)
+			return sq_throwerror(v, "Failed to get obj2");
+	}
+	VerbId verb;
+	verb.id = id;
+	g_engine->execSentence(nullptr, verb, obj1, obj2);
 	return 0;
 }
 
@@ -716,8 +747,26 @@ static SQInteger startDialog(HSQUIRRELVM v) {
 }
 
 static SQInteger stopSentence(HSQUIRRELVM v) {
-	// TODO: stopSentence
-	warning("stopSentence not implemented");
+	SQInteger nArgs = sq_gettop(v);
+	switch (nArgs) {
+	case 1: {
+		for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+			Layer *layer = g_engine->_room->_layers[i];
+			for (int j = 0; j < layer->_objects.size(); j++) {
+				Object *obj = layer->_objects[j];
+				obj->_exec.enabled = false;
+			}
+		}
+	case 2: {
+		Object *obj = sqobj(v, 2);
+		obj->_exec.enabled = false;
+		break;
+	}
+	default:
+		warning("stopSentence not implemented with %lld arguments", nArgs);
+		break;
+	}
+	}
 	return 0;
 }
 
@@ -787,7 +836,7 @@ static SQInteger translate(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, text)))
 		return sq_throwerror(v, "Failed to get text");
 	Common::String newText = g_engine->getTextDb().getText(text);
-	// debug("translate({text}): {newText}");
+	debug("translate(%s): %s", text, newText.c_str());
 	sqpush(v, newText);
 	return 1;
 }
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 5413a88e5a6..2bb57b22dba 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -57,6 +57,7 @@ MODULE_OBJS = \
 	shaders.o \
 	hud.o \
 	lip.o \
+	callback.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 7f8fc2a6a31..6ddd0aa25d2 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -412,6 +412,7 @@ Scene::Scene() : Node("Scene") {
 Scene::~Scene() {}
 
 InputState::InputState() : Node("InputState") {}
+
 InputState::~InputState() {}
 
 void InputState::drawCore(Math::Matrix4 trsf) {
@@ -427,6 +428,38 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
 }
 
+InputStateFlag InputState::getState() const {
+	int tmp = 0;
+	tmp |= (_inputActive ? UI_INPUT_ON : UI_INPUT_OFF);
+	tmp |= (_inputVerbsActive ? UI_VERBS_ON : UI_VERBS_OFF);
+	tmp |= (_showCursor ? UI_CURSOR_ON : UI_CURSOR_OFF);
+	tmp |= (_inputHUD ? UI_HUDOBJECTS_ON : UI_HUDOBJECTS_OFF);
+	return (InputStateFlag)tmp;
+}
+
+void InputState::setState(InputStateFlag state) {
+	if ((UI_INPUT_ON & state) == UI_INPUT_ON)
+		_inputActive = true;
+	if ((UI_INPUT_OFF & state) == UI_INPUT_OFF)
+		_inputActive = false;
+	if ((UI_VERBS_ON & state) == UI_VERBS_ON)
+		_inputVerbsActive = true;
+	if ((UI_VERBS_OFF & state) == UI_VERBS_OFF)
+		_inputVerbsActive = false;
+	if ((UI_CURSOR_ON & state) == UI_CURSOR_ON) {
+		_showCursor = true;
+		_visible = true;
+	}
+	if ((UI_CURSOR_OFF & state) == UI_CURSOR_OFF) {
+		_showCursor = false;
+		_visible = false;
+	}
+	if ((UI_HUDOBJECTS_ON & state) == UI_HUDOBJECTS_ON)
+		_inputHUD = true;
+	if ((UI_HUDOBJECTS_OFF & state) == UI_HUDOBJECTS_OFF)
+		_inputHUD = false;
+}
+
 OverlayNode::OverlayNode() : Node("overlay") {
 	_ovlColor = Color(0.f, 0.f, 0.f, 0.f); // transparent
 	_zOrder = INT_MIN;
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 1ffa42d0d1a..c8cb69d6c2d 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -208,13 +208,61 @@ public:
 	virtual ~Scene() final;
 };
 
+enum class CursorShape {
+    Normal,
+    Front,
+    Back,
+    Left,
+    Right,
+    Pause
+};
+
+
+enum InputStateFlag {
+	II_FLAGS_UI_INPUT_ON = 1,
+	II_FLAGS_UI_INPUT_OFF = 2,
+	II_FLAGS_UI_VERBS_ON = 4,
+	II_FLAGS_UI_VERBS_OFF = 8,
+	II_FLAGS_UI_HUDOBJECTS_ON = 0x10,
+	II_FLAGS_UI_HUDOBJECTS_OFF = 0x20,
+	II_FLAGS_UI_CURSOR_ON = 0x40,
+	II_FLAGS_UI_CURSOR_OFF = 0x80
+};
+
 class InputState final : public Node {
 public:
 	InputState();
 	virtual ~InputState() final;
 
+	InputStateFlag getState() const;
+	void setState(InputStateFlag state);
+
+	void setInputHUD(bool value) { _inputHUD = value; }
+	bool getInputHUD() const { return _inputHUD; }
+
+	void setInputActive(bool value) { _inputActive = value; }
+	bool getInputActive() const { return _inputActive; }
+
+	void setShowCursor(bool value) { _showCursor = value; }
+	bool getShowCursor() const { return _showCursor; }
+
+	void setInputVerbsActive(bool value) { _inputVerbsActive = value; }
+	bool getInputVerbsActive() const { return _inputVerbsActive; }
+
+	void setHotspot(bool value) { _hotspot = value; }
+	bool getHotspot() const { return _hotspot; }
+
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	bool _inputHUD = false;
+    bool _inputActive = false;
+    bool _showCursor = false;
+    bool _inputVerbsActive = false;
+    CursorShape _cursorShape = CursorShape::Normal;
+    Common::String _cursorName;
+    bool _hotspot = false;
 };
 
 class OverlayNode final : public Node {
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 5e0baf4d16c..9f6f28e4476 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -231,6 +231,20 @@ HSQOBJECT sqrootTbl(HSQUIRRELVM v) {
 	return result;
 }
 
+void sqcall(const char *name, const Common::Array<HSQOBJECT> &args) {
+	HSQUIRRELVM v = g_engine->getVm();
+	HSQOBJECT o = sqrootTbl(v);
+	SQInteger top = sq_gettop(v);
+	sqpushfunc(v, o, name);
+
+	sq_pushobject(v, o);
+	for (int i = 0; i < args.size(); i++) {
+		sq_pushobject(v, args[i]);
+	}
+	sq_call(v, 1 + args.size(), SQFalse, SQTrue);
+	sq_settop(v, top);
+}
+
 static int getId(HSQOBJECT table) {
 	SQInteger result = 0;
 	sqgetf(table, "_id", result);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 2e12e9c2703..aaaa118a796 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -128,6 +128,7 @@ HSQOBJECT sqrootTbl(HSQUIRRELVM v);
 
 void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char *name);
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name);
+void sqcall(const char *name, const Common::Array<HSQOBJECT> &args);
 
 template<typename... T>
 void sqcall(const char *name, T... args);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index b58eebd7c90..0d7b2c24f7c 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -25,6 +25,7 @@
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/task.h"
+#include "twp/callback.h"
 #include "twp/scenegraph.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
@@ -124,9 +125,50 @@ static SQInteger activeController(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Sets a timer of duration seconds.
+//
+// When the timer is up, method will be executed.
+// Use this method so that the callback will get saved.
+// That is, if you set a callback to call method in 30 minutes, play the game for 10 minutes, save and quit;
+// when you return to the game, it will remember that it needs to wait 20 minutes before calling method.
+// If the game is paused, all callback timers are paused.
+// Note, method cannot be code, it must be a defined script or function (otherwise, the game wouldn't be able to save what it needs to do when the timer is up).
+// .. code-block:: Squirrel
+// if (actorTalking()) {
+//   addCallback(30, doADance)    // Wait another 30 seconds
+//   return
+//}
 static SQInteger addCallback(HSQUIRRELVM v) {
-	warning("TODO: addCallback: not implemented");
-	return 0;
+	SQInteger count = sq_gettop(v);
+	float duration;
+	if (SQ_FAILED(sqget(v, 2, duration)))
+		return sq_throwerror(v, "failed to get duration");
+	HSQOBJECT meth;
+	sq_resetobject(&meth);
+	if (SQ_FAILED(sq_getstackobj(v, 3, &meth)) || !sq_isclosure(meth))
+		return sq_throwerror(v, "failed to get method");
+
+	Common::String methodName;
+	if (SQ_SUCCEEDED(sq_getclosurename(v, 3))) {
+		const SQChar *tmpMethodName;
+		sq_getstring(v, -1, &tmpMethodName);
+		methodName = tmpMethodName;
+	}
+
+	Common::Array<HSQOBJECT> args;
+	for (int i = 4; i <= count; i++) {
+		HSQOBJECT arg;
+		sq_resetobject(&arg);
+		if (SQ_FAILED(sq_getstackobj(v, i, &arg)))
+			return sq_throwerror(v, Common::String::format("failed to get argument %d", i).c_str());
+		args.push_back(arg);
+	}
+
+	Callback *callback = new Callback(newCallbackId(), duration, methodName, args);
+	g_engine->_callbacks.push_back(callback);
+
+	sqpush(v, callback->getId());
+	return 1;
 }
 
 // Registers a folder that assets can appear in.
@@ -235,14 +277,20 @@ static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 		v, [] { return g_engine->_cutscene != nullptr; }, "breakwhilecutscene()");
 }
 
+// Breaks while a dialog is running.
+// Once the thread finishes execution, the method will continue running.
+// It is an error to call breakwhiledialog in a function that was not started with startthread.
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
 	return breakwhilecond(
 		v, [] { return g_engine->_dialog.getState() != DialogState::None; }, "breakwhiledialog()");
 }
 
+// Breaks while input is not active.
+// Once the thread finishes execution, the method will continue running.
+// It is an error to call breakwhileinputoff in a function that was not started with startthread.
 static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
-	warning("TODO: breakwhileinputoff: not implemented");
-	return 0;
+	return breakwhilecond(
+		v, [] { return !g_engine->_inputState.getInputActive(); }, "breakwhileinputoff()");
 }
 
 // Breaks while the thread referenced by threadId is running.
@@ -433,38 +481,73 @@ static SQInteger inputController(HSQUIRRELVM v) {
 }
 
 static SQInteger inputHUD(HSQUIRRELVM v) {
-	warning("TODO: inputHUD: not implemented");
+	bool on;
+	if (SQ_FAILED(sqget(v, 2, on)))
+		return sq_throwerror(v, "failed to get on");
+	g_engine->_inputState.setInputHUD(on);
 	return 0;
 }
 
 static SQInteger inputOff(HSQUIRRELVM v) {
-	warning("TODO: inputOff: not implemented");
+	if (!g_engine->_cutscene || g_engine->_cutscene->isStopped()) {
+		g_engine->_inputState.setInputActive(false);
+		g_engine->_inputState.setShowCursor(false);
+	}
 	return 0;
 }
 
 static SQInteger inputOn(HSQUIRRELVM v) {
-	warning("TODO: inputOn: not implemented");
+	Cutscene *cutscene = g_engine->_cutscene;
+	if (!cutscene || cutscene->isStopped()) {
+		g_engine->_inputState.setInputActive(true);
+		g_engine->_inputState.setShowCursor(true);
+	} else {
+		int state = g_engine->_inputState.getState();
+		state |= UI_INPUT_ON;
+		state &= (~UI_INPUT_OFF);
+		state |= UI_CURSOR_ON;
+		state &= (~UI_CURSOR_OFF);
+		cutscene->setInputState((InputStateFlag)state);
+		cutscene->setShowCursor(true);
+	}
 	return 0;
 }
 
 static SQInteger inputSilentOff(HSQUIRRELVM v) {
-	warning("TODO: inputSilentOff: not implemented");
+	g_engine->_inputState.setInputActive(false);
 	return 0;
 }
 
 static SQInteger sysInputState(HSQUIRRELVM v) {
-	warning("TODO: sysInputState: not implemented");
-	return 0;
+	SQInteger numArgs = sq_gettop(v);
+	if (numArgs == 1) {
+		int state = (int)g_engine->_inputState.getState();
+		sqpush(v, state);
+		return 1;
+	}
+	if (numArgs == 2) {
+		int state;
+		if (SQ_FAILED(sqget(v, 2, state)))
+			return sq_throwerror(v, "failed to get state");
+		g_engine->_inputState.setState((InputStateFlag)state);
+		return 0;
+	}
+	return sq_throwerror(v, Common::String::format("inputState with %lld arguments not implemented", numArgs).c_str());
 }
 
 static SQInteger inputVerbs(HSQUIRRELVM v) {
-	warning("TODO: inputVerbs: not implemented");
-	return 0;
+	bool on;
+	if (SQ_FAILED(sqget(v, 2, on)))
+		return sq_throwerror(v, "failed to get isActive");
+	debug("inputVerbs: %s", on ? "yes" : "no");
+	g_engine->_inputState.setInputVerbsActive(on);
+	return 1;
 }
 
 static SQInteger isInputOn(HSQUIRRELVM v) {
-	warning("TODO: isInputOn: not implemented");
-	return 0;
+	bool isActive = g_engine->_inputState.getInputActive();
+	sqpush(v, isActive);
+	return 1;
 }
 
 static SQInteger logEvent(HSQUIRRELVM v) {
@@ -513,8 +596,19 @@ static SQInteger moveCursorTo(HSQUIRRELVM v) {
 	return 0;
 }
 
+// removeCallback(id: int) remove the given callback
 static SQInteger removeCallback(HSQUIRRELVM v) {
-	warning("TODO: removeCallback: not implemented");
+	int id = 0;
+	if (SQ_FAILED(sqget(v, 2, id)))
+		return sq_throwerror(v, "failed to get callback");
+	for (int i = 0; i < g_engine->_callbacks.size(); i++) {
+		Callback *cb = g_engine->_callbacks[i];
+		if (cb->getId() == id) {
+			g_engine->_callbacks.remove_at(i);
+			delete cb;
+			return 0;
+		}
+	}
 	return 0;
 }
 
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 1ce64e918fe..80780e5f1c8 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -110,14 +110,13 @@ Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJ
 
 	_name = "cutscene";
 	_id = newThreadId();
-	//_inputState = g_engine->inputState.getState();
+	_inputState = g_engine->_inputState.getState();
 	_actor = g_engine->_followActor;
-	//_showCursor = g_engine->inputState.showCursor;
+	_showCursor = g_engine->_inputState.getShowCursor();
 	_state = csStart;
 	debug("Create cutscene %d with input: 0x%X", _id, _inputState);
-	// TODO:
-	//   g_engine->_inputState.inputActive = false;
-	//   g_engine->_inputState.showCursor = false;
+	g_engine->_inputState.setInputActive(false);
+	g_engine->_inputState.setShowCursor(false);
 	for (int i = 0; i < g_engine->_threads.size(); i++) {
 		ThreadBase *thread = g_engine->_threads[i];
 		if (thread->isGlobal())
@@ -155,10 +154,10 @@ void Cutscene::start() {
 void Cutscene::stop() {
 	_state = csQuit;
 	debug("End cutscene");
-	// g_engine->inputState.setState(self.inputState);
-	// g_engine->inputState.showCursor = self.showCursor;
-	// if self.showCursor:
-	// 	g_engine->inputState.inputActive = true
+	g_engine->_inputState.setState(_inputState);
+	g_engine->_inputState.setShowCursor(_showCursor);
+	if (_showCursor)
+		g_engine->_inputState.setInputActive(true);
 	debug("Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
 	for (int i = 0; i < g_engine->_threads.size(); i++) {
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index 11321ab2416..fae4e5cce85 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -102,17 +102,6 @@ enum CutsceneState {
 	csQuit
 };
 
-enum InputStateFlag {
-	II_FLAGS_UI_INPUT_ON = 1,
-	II_FLAGS_UI_INPUT_OFF = 2,
-	II_FLAGS_UI_VERBS_ON = 4,
-	II_FLAGS_UI_VERBS_OFF = 8,
-	II_FLAGS_UI_HUDOBJECTS_ON = 0x10,
-	II_FLAGS_UI_HUDOBJECTS_OFF = 0x20,
-	II_FLAGS_UI_CURSOR_ON = 0x40,
-	II_FLAGS_UI_CURSOR_OFF = 0x80
-};
-
 class Object;
 class Cutscene final : public ThreadBase {
 public:
@@ -127,12 +116,15 @@ public:
 
 	bool hasOverride() const;
 	inline void cutsceneOverride() { _state = csOverride; }
+	bool isStopped();
+
+	void setInputState(InputStateFlag state) { _inputState = state; }
+	void setShowCursor(bool state) { _showCursor = state; }
 
 private:
 	void checkEndCutscene();
 	void checkEndCutsceneOverride();
 	void doCutsceneOverride();
-	bool isStopped();
 
 private:
 	HSQUIRRELVM _v;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index b58e487b626..49b86171895 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -56,6 +56,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	  _randomSource("Twp") {
 	g_engine = this;
 	sq_resetobject(&_defaultObj);
+	_screenScene.setName("Screen");
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
@@ -264,12 +265,13 @@ void TwpEngine::update(float elapsed) {
 
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
-	//_inputState.visible = _inputState.showCursor; // TODO: || _dlg.state == WaitingForChoice;
+	// _inputState.setVisible(_inputState.getShowCursor() || _dialog.getState() == WaitingForChoice);
 	_inputState.setPos(scrPos);
 	_sentence.setPos(scrPos);
-	// TODO:
-	// _dlg.mousePos = scrPos;
+	_dialog.setMousePos(scrPos);
+
 	if (_room) {
+		// update nouns and useFlag
 		if (_cursor.isLeftClick() || _cursor.isRightClick())
 			clickedAt(_cursor.pos);
 
@@ -311,6 +313,17 @@ void TwpEngine::update(float elapsed) {
 		it++;
 	}
 
+	// update callbacks
+	for (auto it = _callbacks.begin(); it != _callbacks.end();) {
+		Callback *cb = *it;
+		if (cb->update(elapsed)) {
+			it = _callbacks.erase(it);
+			delete cb;
+			continue;
+		}
+		it++;
+	}
+
 	// update tasks
 	for (auto it = _tasks.begin(); it != _tasks.end();) {
 		Task *task = *it;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 675a6f79d30..6ea6fac7bff 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -43,6 +43,7 @@
 #include "twp/scenegraph.h"
 #include "twp/dialog.h"
 #include "twp/hud.h"
+#include "twp/callback.h"
 
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
@@ -138,6 +139,7 @@ public:
 	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
 	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
 	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
+	bool execSentence(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 
 private:
 	void update(float elapsedMs);
@@ -153,7 +155,6 @@ private:
 	void resetVerb();
 	Common::String cursorText();
 	Verb verb();
-	bool execSentence(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 	bool preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2);
 
 public:
@@ -165,6 +166,7 @@ public:
 	Common::Array<Object *> _objects;
 	Common::Array<ThreadBase *> _threads;
 	Common::Array<Task *> _tasks;
+	Common::Array<Callback *> _callbacks;
 	Object *_actor = nullptr;
 	Object *_followActor = nullptr;
 	Room *_room = nullptr;


Commit: dcb5cd583403608833c780be8e936ddb1a76da70
    https://github.com/scummvm/scummvm/commit/dcb5cd583403608833c780be8e936ddb1a76da70
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix head anim

Changed paths:
    engines/twp/object.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 5946b49ec62..d066ab56272 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -376,7 +376,7 @@ Common::String Object::getAnimName(const Common::String &key) {
 
 void Object::setHeadIndex(int head) {
 	for (int i = 0; i <= 6; i++) {
-		showLayer(Common::String::format("%s%d", getAnimName(STAND_ANIMNAME).c_str(), i), i == head);
+		showLayer(Common::String::format("%s%d", getAnimName(HEAD_ANIMNAME).c_str(), i), i == head);
 	}
 }
 


Commit: 57075bd922aa53cd6c02820728f53e0566f64103
    https://github.com/scummvm/scummvm/commit/57075bd922aa53cd6c02820728f53e0566f64103
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add GGPackSet

Changed paths:
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 6187cd6dd49..63bbef029be 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twp/ggpack.h"
+#include "common/archive.h"
 #include "common/debug.h"
 
 namespace Twp {
@@ -639,6 +640,15 @@ bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
 	return true;
 }
 
+bool GGPackEntryReader::open(GGPackSet &packs, const Common::String &entry) {
+	for (int i = 0; i < packs._packs.size(); i++) {
+		GGPackDecoder *pack = &packs._packs[i];
+		if (open(*pack, entry))
+			return true;
+	}
+	return false;
+}
+
 uint32 GGPackEntryReader::read(void *dataPtr, uint32 dataSize) {
 	return _ms.read(dataPtr, dataSize);
 }
@@ -679,4 +689,31 @@ uint32 GGBnutReader::read(void *dataPtr, uint32 dataSize) {
 
 bool GGBnutReader::eos() const { return _s->eos(); }
 
+void GGPackSet::init() {
+	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+
+	Common::ArchiveMemberList fileList;
+	SearchMan.listMatchingMembers(fileList, "*.ggpack*");
+	for (auto it = fileList.begin(); it != fileList.end(); ++it) {
+		const Common::ArchiveMember &m = **it;
+		Common::SeekableReadStream *stream = m.createReadStream();
+		GGPackDecoder pack;
+		if (stream && pack.open(stream, key)) {
+			_packs.push_back(pack);
+		}
+	}
+}
+
+bool GGPackSet::assetExists(const char *asset) {
+	for (int i = 0; i < _packs.size(); i++) {
+		GGPackDecoder *pack = &_packs[i];
+		if (pack->assetExists(asset))
+			return true;
+	}
+	return false;
+}
+
 } // namespace Twp
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index 52d5a2c4afc..89f283b9f5e 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -130,6 +130,15 @@ private:
 	Common::SeekableReadStream *_s = nullptr;
 };
 
+class GGPackSet {
+public:
+	void init();
+	bool assetExists(const char* asset);
+
+public:
+	Common::Array<GGPackDecoder> _packs;
+};
+
 class GGBnutReader: public Common::ReadStream {
 public:
 	GGBnutReader();
@@ -149,6 +158,7 @@ public:
 	GGPackEntryReader();
 
 	bool open(GGPackDecoder& pack, const Common::String& entry);
+	bool open(GGPackSet& packs, const Common::String& entry);
 
 	uint32 read(void *dataPtr, uint32 dataSize) override;
 	bool eos() const override;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 49b86171895..bd7d39c3de0 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -487,14 +487,7 @@ Common::Error TwpEngine::run() {
 
 	_lighting = new Lighting();
 
-	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
-
-	Common::File f;
-	f.open("ThimbleweedPark.ggpack1");
-	_pack.open(&f, key);
+	_pack.init();
 
 	// TODO: load with selected lang
 	GGPackEntryReader entry;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 6ea6fac7bff..f27dd665a6a 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -159,7 +159,7 @@ private:
 
 public:
 	Graphics::Screen *_screen = nullptr;
-	GGPackDecoder _pack;
+	GGPackSet _pack;
 	ResManager _resManager;
 	Common::Array<Room *> _rooms;
 	Common::Array<Object *> _actors;


Commit: e88c12424ca3212517b95d372fe7e1b38a41a8fd
    https://github.com/scummvm/scummvm/commit/e88c12424ca3212517b95d372fe7e1b38a41a8fd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix inventory

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index e0224ad7396..08811653c32 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -264,7 +264,7 @@ YLabel *Dialog::label(int line, const Common::String &name) const {
 }
 
 void Dialog::selectLabel(int line, const Common::String &name) {
-	debug("select label %s", name.c_str());
+	//debug("select label %s", name.c_str());
 	_lbl = label(line, name);
 	_currentStatement = 0;
 	clearSlots();
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 36bf95a4ef2..204ba2a1c07 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -166,6 +166,7 @@ public:
 	Dialog();
 	virtual ~Dialog() override;
 
+	void update(float dt);
 	DialogState getState() const { return _state; }
 
 	void setMousePos(Math::Vector2d pos) { _mousePos = pos; }
@@ -192,7 +193,6 @@ private:
 	void clearSlots();
 
 	virtual void drawCore(Math::Matrix4 trsf) override final;
-	void update(float dt);
 
 public:
 	Common::Array<DialogConditionState> _states;
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 570cef123df..40702c1ee99 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -29,7 +29,7 @@ namespace Twp {
 
 HudShader::HudShader() {
 	const char *verbVtxShader = R"(#version 110
-	attribute vec2 a_position;
+		attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
 
@@ -46,14 +46,14 @@ HudShader::HudShader() {
 	varying vec4 v_highlightColor;
 	varying vec2 v_ranges;
 
-	void main(void) {
+	void main() {
 		v_color = a_color;
 		v_texCoords = a_texCoords;
 		v_shadowColor = u_shadowColor;
 		v_normalColor = u_normalColor;
 		v_highlightColor = u_highlightColor;
 		v_ranges = u_ranges;
-		vec4 worldPosition = vec4(a_position, 0, 1);
+		vec4 worldPosition = vec4(a_position, 0.0, 1.0);
 		vec4 normalizedPosition = u_transform * worldPosition;
 		gl_Position = normalizedPosition;
 	})";
@@ -67,7 +67,7 @@ const char* verbFgtShader = R"(#version 110
 	varying vec2 v_ranges;
 	uniform sampler2D u_texture;
 
-	void main(void) {
+	void main() {
 		float shadows = v_ranges.x;
 		float highlights = v_ranges.y;
 		vec4 texColor = texture2D(u_texture, v_texCoords);
@@ -116,15 +116,16 @@ ActorSlot *Hud::actorSlot(Object *actor) {
 	return nullptr;
 }
 
-void Hud::drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf) {
-  Math::Vector3d pos(sf.spriteSourceSize.left,  - sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY(), 0.f);
-  trsf.translate(pos);
-  g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+void Hud::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
+	Math::Vector3d pos(sf.spriteSourceSize.left, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY(), 0.f);
+	trsf.translate(pos);
+	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
 }
 
 void Hud::drawCore(Math::Matrix4 trsf) {
 	ActorSlot *slot = this->actorSlot(_actor);
-	if(!slot) return;
+	if (!slot)
+		return;
 
 	// draw HUD background
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
@@ -132,7 +133,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	bool classic = true;
 	const SpriteSheetFrame &backingFrame = gameSheet->frameTable[classic ? "ui_backing_tall" : "ui_backing"];
 	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
-	float alpha = 1.0f; // prefs(UiBackingAlpha);
+	float alpha = 0.33f; // prefs(UiBackingAlpha);
 	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha), trsf);
 
 	// TODO; let verbHlt = prefs(InvertVerbHighlight);
@@ -173,4 +174,10 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	_over = isOver;
 }
 
+void Hud::update(Math::Vector2d pos, Object *hotspot, bool mouseClick) {
+	_mousePos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
+	_defaultVerbId = !hotspot ? 0 : hotspot->defaultVerbId();
+	_mouseClick = mouseClick;
+}
+
 } // namespace Twp
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 5b6cda0463d..6efa177be7f 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -92,6 +92,8 @@ public:
 	Hud();
 
 	ActorSlot* actorSlot(Object* actor);
+	bool isOver() const { return _over; }
+	void update(Math::Vector2d pos, Object* hotspot, bool mouseClick);
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 7621dffe466..017cc35b0a5 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -98,7 +98,7 @@ Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 	setId(_table, newRoomId());
 	_name = name;
 	_scene = new Scene();
-  	_scene->addChild(&_overlayNode);
+	_scene->addChild(&_overlayNode);
 }
 
 Room::~Room() {
@@ -374,7 +374,7 @@ void Room::objectParallaxLayer(Object *obj, int zsort) {
 	Layer *l = layer(zsort);
 	if (obj->_layer != l) {
 		// removes object from old layer
-		if(obj->_layer) {
+		if (obj->_layer) {
 			int i = find(obj->_layer->_objects, obj);
 			obj->_layer->_node->removeChild(obj->_node);
 			obj->_layer->_objects.remove_at(i);
@@ -395,6 +395,20 @@ Color Room::getOverlay() const {
 	return _overlayNode.getOverlayColor();
 }
 
+void Room::update(float elapsed) {
+	if (_overlayTo)
+		_overlayTo->update(elapsed);
+	// if (_rotateTo)
+	// 	_rotateTo->update(elapsedSec);
+	for (int j = 0; j < _layers.size(); j++) {
+		Layer *layer = _layers[j];
+		for (int k = 0; k < layer->_objects.size(); k++) {
+			Object *obj = layer->_objects[k];
+			obj->update(elapsed);
+		}
+	}
+}
+
 Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
 	return {frm, to};
 }
diff --git a/engines/twp/room.h b/engines/twp/room.h
index f842e44b456..637575021d9 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -31,6 +31,9 @@
 #include "twp/motor.h"
 #include "twp/scenegraph.h"
 
+#define FULLSCREENCLOSEUP	1
+#define FULLSCREENROOM		2
+
 namespace Twp {
 
 enum class RoomEffect {
@@ -110,6 +113,8 @@ public:
 
 	void load(Common::SeekableReadStream &s);
 
+	void update(float elapsedSec);
+
 	Object *createObject(const Common::String &sheet, const Common::Array<Common::String> &frames);
 	Object *createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 6ddd0aa25d2..932a670d87d 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -32,7 +32,6 @@ namespace Twp {
 
 #define DEFAULT_FPS 10.f
 
-#define NUMOBJECTS 8
 #define NUMOBJECTSBYROW 4
 #define MARGIN 8.f
 #define MARGINBOTTOM 10.f
@@ -478,7 +477,13 @@ static bool hasDownArrow(Object *actor) {
 	return actor->_inventory.size() > (actor->_inventoryOffset * NUMOBJECTSBYROW + NUMOBJECTS);
 }
 
-Inventory::Inventory() : Node("Inventory") {}
+Inventory::Inventory() : Node("Inventory") {
+	for (int i = 0; i < NUMOBJECTS; i++) {
+		float x = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + ((i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET));
+		float y = MARGINBOTTOM + BACKHEIGHT + BACKOFFSET - ((i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET));
+		_itemRects[i] = Common::Rect(x, y, x + BACKWIDTH, y + BACKHEIGHT);
+	}
+}
 
 void Inventory::drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
@@ -542,9 +547,9 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 		Common::String icon = obj->getIcon();
 		if (itemsSheet->frameTable.contains(icon)) {
 			SpriteSheetFrame *itemFrame = &itemsSheet->frameTable[icon];
-			Math::Vector2d pos(startOffsetX + ((i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET)), startOffsetY - ((i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET)));
+			Math::Vector2d pos(startOffsetX + ((float)(i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET)), startOffsetY - ((float)(i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET)));
 			Math::Matrix4 t(trsf);
-			trsf.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 			float s = obj->getScale();
 			Twp::scale(t, Math::Vector2d(s, s));
 			drawSprite(*itemFrame, texture, Color(), t);
@@ -561,41 +566,49 @@ void Inventory::drawCore(Math::Matrix4 trsf) {
 }
 
 void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
+	static Common::Rect gArrowUpRect(SCREEN_WIDTH / 2.f, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET, SCREEN_WIDTH / 2.f + ARROWWIDTH, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET + ARROWHEIGHT);
+	static Common::Rect gArrowDnRect(SCREEN_WIDTH / 2.f, MARGINBOTTOM, SCREEN_WIDTH / 2.f + ARROWWIDTH, MARGINBOTTOM + ARROWHEIGHT);
+
 	// udate colors
 	_actor = actor;
 	_backColor = backColor;
 	_verbNormal = verbNormal;
 
 	_obj = nullptr;
-	// TODO: Inventory::update
-	//   if (_actor) {
-	//     let scrPos = winToScreen(mousePos());
-
-	//     // update mouse click
-	//     let down = mbLeft in mouseBtns();
-	//     if not self.down and down:
-	//       self.down = true;
-	//       if gArrowUpRect.contains(scrPos):
-	//         self.actor.inventoryOffset -= 1;
-	//         if self.actor.inventoryOffset < 0:
-	//           self.actor.inventoryOffset = clamp(self.actor.inventoryOffset, 0, (self.actor.inventory.len - 5) div 4)
-	//       elif gArrowDnRect.contains(scrPos):
-	//         self.actor.inventoryOffset += 1;
-	//         self.actor.inventoryOffset = clamp(self.actor.inventoryOffset, 0, (self.actor.inventory.len - 5) div 4)
-	//     elif not down:
-	//       self.down = false;
-
-	//     for i in 0..<gItemRects.len:
-	//       let item = gItemRects[i];
-	//       if item.contains(scrPos):
-	//         let index = self.actor.inventoryOffset * NumObjectsByRow + i;
-	//         if index < self.actor.inventory.len:
-	//           self.obj = self.actor.inventory[index];
-	//         break
-
-	//     for obj in self.actor.inventory:
-	//       obj.update(elapsed);;
-	//   }
+	if (_actor) {
+		Math::Vector2d scrPos = g_engine->_cursor.pos;
+
+		// update mouse click
+		bool down = g_engine->_cursor.leftDown;
+		if (_down && down) {
+			_down = true;
+			if (gArrowUpRect.contains(scrPos.getX(), scrPos.getY())) {
+				_actor->_inventoryOffset -= 1;
+				if (_actor->_inventoryOffset < 0)
+					_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
+			} else if (gArrowDnRect.contains(scrPos.getX(), scrPos.getY())) {
+				_actor->_inventoryOffset++;
+				_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
+			}
+		} else if (!down) {
+			_down = false;
+		}
+
+		for (int i = 0; i < NUMOBJECTS; i++) {
+			const Common::Rect &item = _itemRects[i];
+			if (item.contains(scrPos.getX(), scrPos.getY())) {
+				int index = _actor->_inventoryOffset * NUMOBJECTSBYROW + i;
+				if (index < _actor->_inventory.size())
+					_obj = _actor->_inventory[index];
+				break;
+			}
+		}
+
+		for (int i = 0; i < _actor->_inventory.size(); i++) {
+			Object *obj = _actor->_inventory[i];
+			obj->update(elapsed);
+		}
+	}
 }
 
 SentenceNode::SentenceNode() : Node("Sentence") {
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index c8cb69d6c2d..4d0563ba688 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -32,6 +32,8 @@
 #include "twp/font.h"
 #include "twp/spritesheet.h"
 
+#define NUMOBJECTS 8
+
 namespace Twp {
 
 // Represents a node in a scene graph.
@@ -284,6 +286,8 @@ public:
 	Inventory();
 	void update(float elapsed, Object* actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
 
+	Object* getObject() const { return _obj; }
+
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
 	void drawArrows(Math::Matrix4 trsf);
@@ -296,6 +300,7 @@ private:
     Color _backColor, _verbNormal;
     bool _down = false;
     Object* _obj = nullptr;
+	Common::Rect _itemRects[NUMOBJECTS];
 };
 
 class SentenceNode: public Node {
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index ad6f42a1e7c..741bcdc739d 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -28,58 +28,58 @@ namespace Twp {
 
 void TextDb::parseTsv(Common::SeekableReadStream &stream) {
 	stream.readLine();
-	while(!stream.eos()) {
+	while (!stream.eos()) {
 		Common::String line = stream.readLine();
-		int pos = line.find(' ',0);
+		int pos = line.find('\t', 0);
 		int id = atoi(line.c_str());
-		_texts[id] = line.substr(pos+1);
+		_texts[id] = line.substr(pos + 1);
 	}
 }
 
 Common::String TextDb::getText(int id) {
-  Common::String result;
-  if (_texts.contains(id)) {
-    result = _texts[id];
-    if (result.hasSuffix("#M") || result.hasSuffix("#F"))
-      result = result.substr(0, result.size()-3);
-    // TODO: replace \" by ";
-    // result = result.replace("\\\"", "\"");
-  } else {
-    result = Common::String::format("Text %d not found", id);
-    error("Text %d not found", id);
-  }
-  return result;
+	Common::String result;
+	if (_texts.contains(id)) {
+		result = _texts[id];
+		if (result.hasSuffix("#M") || result.hasSuffix("#F"))
+			result = result.substr(0, result.size() - 3);
+		// TODO: replace \" by ";
+		// result = result.replace("\\\"", "\"");
+	} else {
+		result = Common::String::format("Text %d not found", id);
+		error("Text %d not found", id);
+	}
+	return result;
 }
 
-Common::String TextDb::getText(const Common::String& text) {
-  HSQUIRRELVM v = g_engine->getVm();
-  if (text.size() > 0) {
-    if (text[0] == '@') {
-      int id = atoi(text.c_str()+1);
-      return getText(id);
-	} else if (text[0] == '^') {
-      return text.substr(1);
-	} else if (text[0] == '$') {
-      Common::String txt;
-      SQInteger top = sq_gettop(v);
-      sq_pushroottable(v);
-      Common::String code = Common::String::format("return %s", text.substr(1, text.size()-2).c_str());
-      if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "execCode", SQTrue))) {
-        error("Error executing code %s", code.c_str());
-	  } else {
-        sq_push(v, -2);
-        // call
-        if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
-          error("Error calling code %s", code.c_str());
-		} else {
-          sqget(v, -1, txt);
-          sq_settop(v, top);
-          return getText(txt);
+Common::String TextDb::getText(const Common::String &text) {
+	HSQUIRRELVM v = g_engine->getVm();
+	if (text.size() > 0) {
+		if (text[0] == '@') {
+			int id = atoi(text.c_str() + 1);
+			return getText(id);
+		} else if (text[0] == '^') {
+			return text.substr(1);
+		} else if (text[0] == '$') {
+			Common::String txt;
+			SQInteger top = sq_gettop(v);
+			sq_pushroottable(v);
+			Common::String code = Common::String::format("return %s", text.substr(1, text.size() - 2).c_str());
+			if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "execCode", SQTrue))) {
+				error("Error executing code %s", code.c_str());
+			} else {
+				sq_push(v, -2);
+				// call
+				if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
+					error("Error calling code %s", code.c_str());
+				} else {
+					sqget(v, -1, txt);
+					sq_settop(v, top);
+					return getText(txt);
+				}
+			}
 		}
-	  }
 	}
-  }
-  return text;
+	return text;
 }
 
 } // namespace Twp
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index bd7d39c3de0..96d08865ec1 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -193,7 +193,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		Object *obj = objAt(roomPos);
 
-		if (_cursor.isLeftClick()) {
+		if (_cursor.isLeftDown()) {
 			// button left: execute selected verb
 			bool handled = clickedAtHandled(roomPos);
 			if (!handled && obj) {
@@ -209,7 +209,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 			}
 			// TODO: Just clicking on the ground
 			//     cancelSentence(self.actor)
-		} else if (_cursor.isRightClick()) {
+		} else if (_cursor.isRightDown()) {
 			// button right: execute default verb
 			if (obj) {
 				VerbId verb;
@@ -259,25 +259,107 @@ Common::String TwpEngine::cursorText() {
 	return result;
 }
 
+template<typename TFunc>
+void objsAt(Math::Vector2d pos, TFunc func) {
+	if (g_engine->_uiInv.getObject() && g_engine->_room->_fullscreen == FULLSCREENROOM)
+		func(g_engine->_uiInv.getObject());
+	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+		Layer *layer = g_engine->_room->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[j];
+			if (obj != g_engine->_actor && (obj->_touchable || obj->inInventory()) && obj->_node->isVisible() && obj->_objType == otNone && obj->contains(pos))
+				if (func(obj))
+					return;
+		}
+	}
+}
+
+Object *inventoryAt(Math::Vector2d pos) {
+	Object *result = nullptr;
+	objsAt(pos, [&](Object *x) {
+		if (x->inInventory()) {
+			result = x;
+			return true;
+		}
+		return false;
+	});
+	return result;
+}
+
 void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
 
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
-	// _inputState.setVisible(_inputState.getShowCursor() || _dialog.getState() == WaitingForChoice);
+	_inputState.setVisible(_inputState.getShowCursor() || _dialog.getState() == WaitingForChoice);
 	_inputState.setPos(scrPos);
 	_sentence.setPos(scrPos);
 	_dialog.setMousePos(scrPos);
 
 	if (_room) {
 		// update nouns and useFlag
-		if (_cursor.isLeftClick() || _cursor.isRightClick())
-			clickedAt(_cursor.pos);
+		Math::Vector2d roomPos = screenToRoom(scrPos);
+		if (_room->_fullscreen == FULLSCREENROOM) {
+			if ((_hud._verb.id.id == VERB_USE) && (_useFlag != ufNone)) {
+				_noun2 = objAt(roomPos);
+			} else if (_hud._verb.id.id == VERB_GIVE) {
+				if (_useFlag != ufGiveTo) {
+					_noun1 = inventoryAt(roomPos);
+					_useFlag = ufNone;
+					_noun2 = nullptr;
+				} else {
+					objsAt(roomPos, [&](Object *x) {
+						if (x != _actor && x->getFlags() & GIVEABLE) {
+							_noun2 = x;
+							return true;
+						}
+						return false;
+					});
+					if (_noun2)
+						debug("Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
+				}
+			} else {
+				_noun1 = objAt(roomPos);
+				_useFlag = ufNone;
+				_noun2 = nullptr;
+			}
 
-		_sentence.setText(cursorText());
+			_inputState.setHotspot(_noun1!=nullptr);
+			_hud.setVisible(_inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None);
+			_sentence.setVisible(_hud.isVisible());
+			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
+			//_actorSwitcher.visible = _dialog.state == DialogState.None and self.cutscene.isNil;
+			_sentence.setText(cursorText());
+
+			// call clickedAt if any button down
+			if ((_dialog.getState() == DialogState::None) && !_hud.isOver()) {
+				// TODO:
+				// if (_cursor.leftDown) {
+				// 	if mouseDnDur > initDuration(milliseconds = 500):
+				// 		walkFast();
+				// } else {
+				// 	walkFast(false);
+				// }
+				if (_cursor.isLeftDown() || _cursor.isRightDown())
+					clickedAt(scrPos);
+			}
+		} else {
+			_hud.setVisible(false);
+			_uiInv.setVisible(false);
+			_noun1 = objAt(roomPos);
+			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->_name);
+			_sentence.setText(cText);
+			// TODO: _inputState.setCursorShape(CursorShape::Normal);
+			if (_cursor.isLeftDown())
+				clickedAt(scrPos);
+		}
+
+		if (_cursor.isLeftDown() || _cursor.isRightDown())
+			clickedAt(_cursor.pos);
 	}
 
+	_dialog.update(elapsed);
 	_fadeShader->_elapsed += elapsed;
 
 	// update camera
@@ -337,20 +419,14 @@ void TwpEngine::update(float elapsed) {
 
 	// update objects
 	if (_room) {
-		for (int j = 0; j < _room->_layers.size(); j++) {
-			Layer *layer = _room->_layers[j];
-			for (int k = 0; k < layer->_objects.size(); k++) {
-				Object *obj = layer->_objects[k];
-				obj->update(elapsed);
-			}
-		}
+		_room->update(elapsed);
 	}
 
 	// update inventory
 	if (!_actor) {
 		_uiInv.update(elapsed);
 	} else {
-		// TODO: _hud.update(scrPos, _noun1, _cursor.isLeftClick());
+		_hud.update(scrPos, _noun1, _cursor.isLeftDown());
 		VerbUiColors *verbUI = &_hud.actorSlot(_actor)->verbUiColors;
 		_uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
 	}
@@ -462,12 +538,12 @@ void TwpEngine::draw() {
 	}
 
 	// draw to screen
+	_gfx.use(nullptr);
 	_gfx.setRenderTarget(nullptr);
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
-	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, flipY);
+	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
 
 	// draw UI
-	_gfx.use(nullptr);
 	_screenScene.draw();
 
 	g_system->updateScreen();
@@ -539,6 +615,9 @@ Common::Error TwpEngine::run() {
 				break;
 			case Common::EVENT_LBUTTONDOWN:
 				_cursor.leftDown = true;
+				if (!_cursor.oldLeftDown) {
+					_cursor.mouseDownTime = g_engine->_time;
+				}
 				break;
 			case Common::EVENT_LBUTTONUP:
 				_cursor.leftDown = false;
@@ -744,7 +823,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 
 	// exit current room
 	exitRoom(_room);
-	// TODO: _fadeShader->effect = None;
+	_fadeShader->_effect = FadeEffect::None;
 
 	// sets the current room for scripts
 	sqsetf(sqrootTbl(v), "currentRoom", room->_table);
@@ -756,8 +835,8 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	_room->_lights._numLights = 0;
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
-	//   if (_actor)
-	//     _hud.verb = _hud.actorSlot(_actor).verbs[0];
+	if (_actor)
+		_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 
 	// move current actor to the new room
 	Math::Vector2d camPos;
@@ -944,21 +1023,6 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 	_fadeShader->_elapsed = 0.f;
 }
 
-template<typename TFunc>
-void objsAt(Math::Vector2d pos, TFunc func) {
-	// TODO
-	// if g_engine->_uiInv->_obj && g_engine->_room->fullscreen == FullscreenRoom)
-	// 	func(g_engine->_uiInv._obj);
-	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
-		Layer *layer = g_engine->_room->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
-			if (obj != g_engine->_actor && (obj->_touchable || obj->inInventory()) && obj->_node->isVisible() && obj->_objType == otNone && obj->contains(pos))
-				func(obj);
-		}
-	}
-}
-
 Object *TwpEngine::objAt(Math::Vector2d pos) {
 	int zOrder = INT_MAX;
 	Object *result = nullptr;
@@ -967,6 +1031,7 @@ Object *TwpEngine::objAt(Math::Vector2d pos) {
 			result = obj;
 			zOrder = obj->_node->getZSort();
 		}
+		return false;
 	});
 	return result;
 }
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f27dd665a6a..5ee63d0ba5b 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -191,15 +191,17 @@ public:
 		bool leftDown = false;
 		bool oldRightDown = false;
 		bool rightDown = false;
+		float mouseDownTime = 0.f;
 
 		void update() {
 			oldLeftDown = leftDown;
 			oldRightDown = rightDown;
 		}
-		bool isLeftClick() { return oldLeftDown && !leftDown; }
-		bool isRightClick() { return oldRightDown && !rightDown; }
+		bool isLeftDown() { return !oldLeftDown && leftDown; }
+		bool isRightDown() { return !oldRightDown && rightDown; }
 	} _cursor;
 	Hud _hud;
+	Inventory _uiInv;
 
 private:
 	Gfx _gfx;
@@ -207,7 +209,6 @@ private:
 	Preferences _prefs;
 	ShaderParams _shaderParams;
 	unique_ptr<FadeShader> _fadeShader;
-	Inventory _uiInv;
 	SentenceNode _sentence;
 };
 


Commit: 4e67d24e04cba135b8539e95052113802db85776
    https://github.com/scummvm/scummvm/commit/4e67d24e04cba135b8539e95052113802db85776
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add path finding

Changed paths:
  A engines/twp/clipper/CMakeLists.txt
  A engines/twp/clipper/clipper.cpp
  A engines/twp/clipper/clipper.hpp
  A engines/twp/graph.cpp
  A engines/twp/graph.h
  A engines/twp/walkboxnode.cpp
  A engines/twp/walkboxnode.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/module.mk
    engines/twp/motor.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/clipper/CMakeLists.txt b/engines/twp/clipper/CMakeLists.txt
new file mode 100644
index 00000000000..3250c9c48e3
--- /dev/null
+++ b/engines/twp/clipper/CMakeLists.txt
@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(clipper CXX)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+set(CLIPPER_SRC clipper.cpp)
+add_library(clipper STATIC ${CLIPPER_SRC})
diff --git a/engines/twp/clipper/clipper.cpp b/engines/twp/clipper/clipper.cpp
new file mode 100644
index 00000000000..89fbadba9f9
--- /dev/null
+++ b/engines/twp/clipper/clipper.cpp
@@ -0,0 +1,4311 @@
+/*******************************************************************************
+*                                                                              *
+* Author    :  Angus Johnson                                                   *
+* Version   :  6.4.2                                                           *
+* Date      :  27 February 2017                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2017                                         *
+*                                                                              *
+* License:                                                                     *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt                                         *
+*                                                                              *
+* Attributions:                                                                *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping"                                     *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
+* http://portal.acm.org/citation.cfm?id=129906                                 *
+*                                                                              *
+* Computer graphics and geometric modeling: implementation and algorithms      *
+* By Max K. Agoston                                                            *
+* Springer; 1 edition (January 4, 2005)                                        *
+* http://books.google.com/books?q=vatti+clipping+agoston                       *
+*                                                                              *
+* See also:                                                                    *
+* "Polygon Offsetting by Computing Winding Numbers"                            *
+* Paper no. DETC2005-85513 pp. 565-575                                         *
+* ASME 2005 International Design Engineering Technical Conferences             *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
+* September 24-28, 2005 , Long Beach, California, USA                          *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
+*                                                                              *
+*******************************************************************************/
+
+/*******************************************************************************
+*                                                                              *
+* This is a translation of the Delphi Clipper library and the naming style     *
+* used has retained a Delphi flavour.                                          *
+*                                                                              *
+*******************************************************************************/
+
+#include "clipper.hpp"
+#include <cmath>
+#include <vector>
+#include <algorithm>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+#include <functional>
+
+#include "common/debug.h"
+
+namespace ClipperLib {
+
+static double const pi = 3.141592653589793238;
+static double const two_pi = pi * 2;
+static double const def_arc_tolerance = 0.25;
+
+enum Direction { dRightToLeft, dLeftToRight };
+
+static int const Unassigned = -1;  //edge not currently 'owning' a solution
+static int const Skip = -2;        //edge that would otherwise close a path
+
+#define HORIZONTAL (-1.0E+40)
+#define TOLERANCE (1.0e-20)
+#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
+
+struct TEdge {
+  IntPoint Bot;
+  IntPoint Curr; //current (updated for every new scanbeam)
+  IntPoint Top;
+  double Dx;
+  PolyType PolyTyp;
+  EdgeSide Side; //side only refers to current side of solution poly
+  int WindDelta; //1 or -1 depending on winding direction
+  int WindCnt;
+  int WindCnt2; //winding count of the opposite polytype
+  int OutIdx;
+  TEdge *Next;
+  TEdge *Prev;
+  TEdge *NextInLML;
+  TEdge *NextInAEL;
+  TEdge *PrevInAEL;
+  TEdge *NextInSEL;
+  TEdge *PrevInSEL;
+};
+
+struct IntersectNode {
+  TEdge *Edge1;
+  TEdge *Edge2;
+  IntPoint Pt;
+};
+
+struct LocalMinimum {
+  cInt Y;
+  TEdge *LeftBound;
+  TEdge *RightBound;
+};
+
+struct OutPt;
+
+//OutRec: contains a path in the clipping solution. Edges in the AEL will
+//carry a pointer to an OutRec when they are part of the clipping solution.
+struct OutRec {
+  int Idx;
+  bool IsHole;
+  bool IsOpen;
+  OutRec *FirstLeft;  //see comments in clipper.pas
+  PolyNode *PolyNd;
+  OutPt *Pts;
+  OutPt *BottomPt;
+};
+
+struct OutPt {
+  int Idx;
+  IntPoint Pt;
+  OutPt *Next;
+  OutPt *Prev;
+};
+
+struct Join {
+  OutPt *OutPt1;
+  OutPt *OutPt2;
+  IntPoint OffPt;
+};
+
+struct LocMinSorter {
+  inline bool operator()(const LocalMinimum &locMin1, const LocalMinimum &locMin2) {
+    return locMin2.Y < locMin1.Y;
+  }
+};
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+inline cInt Round(double val) {
+  if ((val < 0))
+    return static_cast<cInt>(val - 0.5);
+  else
+    return static_cast<cInt>(val + 0.5);
+}
+//------------------------------------------------------------------------------
+
+inline cInt Abs(cInt val) {
+  return val < 0 ? -val : val;
+}
+
+//------------------------------------------------------------------------------
+// PolyTree methods ...
+//------------------------------------------------------------------------------
+
+void PolyTree::Clear() {
+  for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
+    delete AllNodes[i];
+  AllNodes.resize(0);
+  Childs.resize(0);
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyTree::GetFirst() const {
+  if (!Childs.empty())
+    return Childs[0];
+  else
+    return 0;
+}
+//------------------------------------------------------------------------------
+
+int PolyTree::Total() const {
+  int result = (int) AllNodes.size();
+  //with negative offsets, ignore the hidden outer polygon ...
+  if (result > 0 && Childs[0] != AllNodes[0])
+    result--;
+  return result;
+}
+
+//------------------------------------------------------------------------------
+// PolyNode methods ...
+//------------------------------------------------------------------------------
+
+PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {
+}
+//------------------------------------------------------------------------------
+
+int PolyNode::ChildCount() const {
+  return (int) Childs.size();
+}
+//------------------------------------------------------------------------------
+
+void PolyNode::AddChild(PolyNode &child) {
+  unsigned cnt = (unsigned) Childs.size();
+  Childs.push_back(&child);
+  child.Parent = this;
+  child.Index = cnt;
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyNode::GetNext() const {
+  if (!Childs.empty())
+    return Childs[0];
+  else
+    return GetNextSiblingUp();
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyNode::GetNextSiblingUp() const {
+  if (!Parent) //protects against PolyTree.GetNextSiblingUp()
+    return 0;
+  else if (Index == Parent->Childs.size() - 1)
+    return Parent->GetNextSiblingUp();
+  else
+    return Parent->Childs[Index + 1];
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsHole() const {
+  bool result = true;
+  PolyNode *node = Parent;
+  while (node) {
+    result = !result;
+    node = node->Parent;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsOpen() const {
+  return m_IsOpen;
+}
+//------------------------------------------------------------------------------
+
+#ifndef use_int32
+
+//------------------------------------------------------------------------------
+// Int128 class (enables safe math on signed 64bit integers)
+// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
+//    Int128 val2((long64)9223372036854775807);
+//    Int128 val3 = val1 * val2;
+//    val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
+//------------------------------------------------------------------------------
+
+class Int128 {
+public:
+  ulong64 lo;
+  long64 hi;
+
+  Int128(long64 _lo = 0) {
+    lo = (ulong64) _lo;
+    if (_lo < 0)
+      hi = -1;
+    else
+      hi = 0;
+  }
+
+  Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
+
+  Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
+
+  Int128 &operator=(const long64 &val) {
+    lo = (ulong64) val;
+    if (val < 0)
+      hi = -1;
+    else
+      hi = 0;
+    return *this;
+  }
+
+  bool operator==(const Int128 &val) const { return (hi == val.hi && lo == val.lo); }
+
+  bool operator!=(const Int128 &val) const { return !(*this == val); }
+
+  bool operator>(const Int128 &val) const {
+    if (hi != val.hi)
+      return hi > val.hi;
+    else
+      return lo > val.lo;
+  }
+
+  bool operator<(const Int128 &val) const {
+    if (hi != val.hi)
+      return hi < val.hi;
+    else
+      return lo < val.lo;
+  }
+
+  bool operator>=(const Int128 &val) const { return !(*this < val); }
+
+  bool operator<=(const Int128 &val) const { return !(*this > val); }
+
+  Int128 &operator+=(const Int128 &rhs) {
+    hi += rhs.hi;
+    lo += rhs.lo;
+    if (lo < rhs.lo)
+      hi++;
+    return *this;
+  }
+
+  Int128 operator+(const Int128 &rhs) const {
+    Int128 result(*this);
+    result += rhs;
+    return result;
+  }
+
+  Int128 &operator-=(const Int128 &rhs) {
+    *this += -rhs;
+    return *this;
+  }
+
+  Int128 operator-(const Int128 &rhs) const {
+    Int128 result(*this);
+    result -= rhs;
+    return result;
+  }
+
+  Int128 operator-() const //unary negation
+  {
+    if (lo == 0)
+      return Int128(-hi, 0);
+    else
+      return Int128(~hi, ~lo + 1);
+  }
+
+  operator double() const {
+    const double shift64 = 18446744073709551616.0; //2^64
+    if (hi < 0) {
+      if (lo == 0)
+        return (double) hi * shift64;
+      else
+        return -(double) (~lo + ~hi * shift64);
+    } else
+      return (double) (lo + hi * shift64);
+  }
+
+};
+//------------------------------------------------------------------------------
+
+Int128 Int128Mul(long64 lhs, long64 rhs) {
+  bool negate = (lhs < 0) != (rhs < 0);
+
+  if (lhs < 0)
+    lhs = -lhs;
+  ulong64 int1Hi = ulong64(lhs) >> 32;
+  ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
+
+  if (rhs < 0)
+    rhs = -rhs;
+  ulong64 int2Hi = ulong64(rhs) >> 32;
+  ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
+
+  //nb: see comments in clipper.pas
+  ulong64 a = int1Hi * int2Hi;
+  ulong64 b = int1Lo * int2Lo;
+  ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+  Int128 tmp;
+  tmp.hi = long64(a + (c >> 32));
+  tmp.lo = long64(c << 32);
+  tmp.lo += long64(b);
+  if (tmp.lo < b)
+    tmp.hi++;
+  if (negate)
+    tmp = -tmp;
+  return tmp;
+}
+#endif
+
+//------------------------------------------------------------------------------
+// Miscellaneous global functions
+//------------------------------------------------------------------------------
+
+bool Orientation(const Path &poly) {
+  return Area(poly) >= 0;
+}
+//------------------------------------------------------------------------------
+
+double Area(const Path &poly) {
+  int size = (int) poly.size();
+  if (size < 3)
+    return 0;
+
+  double a = 0;
+  for (int i = 0, j = size - 1; i < size; ++i) {
+    a += ((double) poly[j].X + poly[i].X) * ((double) poly[j].Y - poly[i].Y);
+    j = i;
+  }
+  return -a * 0.5;
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutPt *op) {
+  const OutPt *startOp = op;
+  if (!op)
+    return 0;
+  double a = 0;
+  do {
+    a += (double) (op->Prev->Pt.X + op->Pt.X) * (double) (op->Prev->Pt.Y - op->Pt.Y);
+    op = op->Next;
+  } while (op != startOp);
+  return a * 0.5;
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutRec &outRec) {
+  return Area(outRec.Pts);
+}
+//------------------------------------------------------------------------------
+
+bool PointIsVertex(const IntPoint &Pt, OutPt *pp) {
+  OutPt *pp2 = pp;
+  do {
+    if (pp2->Pt == Pt)
+      return true;
+    pp2 = pp2->Next;
+  } while (pp2 != pp);
+  return false;
+}
+//------------------------------------------------------------------------------
+
+//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+int PointInPolygon(const IntPoint &pt, const Path &path) {
+  //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+  int result = 0;
+  size_t cnt = path.size();
+  if (cnt < 3)
+    return 0;
+  IntPoint ip = path[0];
+  for (size_t i = 1; i <= cnt; ++i) {
+    IntPoint ipNext = (i == cnt ? path[0] : path[i]);
+    if (ipNext.Y == pt.Y) {
+      if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
+          ((ipNext.X > pt.X) == (ip.X < pt.X))))
+        return -1;
+    }
+    if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
+      if (ip.X >= pt.X) {
+        if (ipNext.X > pt.X)
+          result = 1 - result;
+        else {
+          double d = (double) (ip.X - pt.X) * (ipNext.Y - pt.Y) -
+              (double) (ipNext.X - pt.X) * (ip.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (ipNext.Y > ip.Y))
+            result = 1 - result;
+        }
+      } else {
+        if (ipNext.X > pt.X) {
+          double d = (double) (ip.X - pt.X) * (ipNext.Y - pt.Y) -
+              (double) (ipNext.X - pt.X) * (ip.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (ipNext.Y > ip.Y))
+            result = 1 - result;
+        }
+      }
+    }
+    ip = ipNext;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+int PointInPolygon(const IntPoint &pt, OutPt *op) {
+  //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+  int result = 0;
+  OutPt *startOp = op;
+  for (;;) {
+    if (op->Next->Pt.Y == pt.Y) {
+      if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y &&
+          ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
+        return -1;
+    }
+    if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
+      if (op->Pt.X >= pt.X) {
+        if (op->Next->Pt.X > pt.X)
+          result = 1 - result;
+        else {
+          double d = (double) (op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+              (double) (op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+            result = 1 - result;
+        }
+      } else {
+        if (op->Next->Pt.X > pt.X) {
+          double d = (double) (op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+              (double) (op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+            result = 1 - result;
+        }
+      }
+    }
+    op = op->Next;
+    if (startOp == op)
+      break;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) {
+  OutPt *op = OutPt1;
+  do {
+    //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+    int res = PointInPolygon(op->Pt, OutPt2);
+    if (res >= 0)
+      return res > 0;
+    op = op->Next;
+  } while (op != OutPt1);
+  return true;
+}
+//----------------------------------------------------------------------
+
+bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
+        Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
+  else
+#endif
+    return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
+        (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+                 const IntPoint pt3, bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
+  else
+#endif
+    return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) == (pt1.X - pt2.X) * (pt2.Y - pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+                 const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
+  else
+#endif
+    return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) == (pt1.X - pt2.X) * (pt3.Y - pt4.Y);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsHorizontal(TEdge &e) {
+  return e.Dx == HORIZONTAL;
+}
+//------------------------------------------------------------------------------
+
+inline double GetDx(const IntPoint pt1, const IntPoint pt2) {
+  return (pt1.Y == pt2.Y) ?
+         HORIZONTAL : (double) (pt2.X - pt1.X) / (pt2.Y - pt1.Y);
+}
+//---------------------------------------------------------------------------
+
+inline void SetDx(TEdge &e) {
+  cInt dy = (e.Top.Y - e.Bot.Y);
+  if (dy == 0)
+    e.Dx = HORIZONTAL;
+  else
+    e.Dx = (double) (e.Top.X - e.Bot.X) / dy;
+}
+//---------------------------------------------------------------------------
+
+inline void SwapSides(TEdge &Edge1, TEdge &Edge2) {
+  EdgeSide Side = Edge1.Side;
+  Edge1.Side = Edge2.Side;
+  Edge2.Side = Side;
+}
+//------------------------------------------------------------------------------
+
+inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) {
+  int OutIdx = Edge1.OutIdx;
+  Edge1.OutIdx = Edge2.OutIdx;
+  Edge2.OutIdx = OutIdx;
+}
+//------------------------------------------------------------------------------
+
+inline cInt TopX(TEdge &edge, const cInt currentY) {
+  return (currentY == edge.Top.Y) ?
+         edge.Top.X : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
+}
+//------------------------------------------------------------------------------
+
+void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) {
+#ifdef use_xyz
+  ip.Z = 0;
+#endif
+
+  double b1, b2;
+  if (Edge1.Dx == Edge2.Dx) {
+    ip.Y = Edge1.Curr.Y;
+    ip.X = TopX(Edge1, ip.Y);
+    return;
+  } else if (Edge1.Dx == 0) {
+    ip.X = Edge1.Bot.X;
+    if (IsHorizontal(Edge2))
+      ip.Y = Edge2.Bot.Y;
+    else {
+      b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
+      ip.Y = Round(ip.X / Edge2.Dx + b2);
+    }
+  } else if (Edge2.Dx == 0) {
+    ip.X = Edge2.Bot.X;
+    if (IsHorizontal(Edge1))
+      ip.Y = Edge1.Bot.Y;
+    else {
+      b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
+      ip.Y = Round(ip.X / Edge1.Dx + b1);
+    }
+  } else {
+    b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
+    b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
+    double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
+    ip.Y = Round(q);
+    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
+      ip.X = Round(Edge1.Dx * q + b1);
+    else
+      ip.X = Round(Edge2.Dx * q + b2);
+  }
+
+  if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
+    if (Edge1.Top.Y > Edge2.Top.Y)
+      ip.Y = Edge1.Top.Y;
+    else
+      ip.Y = Edge2.Top.Y;
+    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
+      ip.X = TopX(Edge1, ip.Y);
+    else
+      ip.X = TopX(Edge2, ip.Y);
+  }
+  //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+  if (ip.Y > Edge1.Curr.Y) {
+    ip.Y = Edge1.Curr.Y;
+    //use the more vertical edge to derive X ...
+    if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
+      ip.X = TopX(Edge2, ip.Y);
+    else
+      ip.X = TopX(Edge1, ip.Y);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ReversePolyPtLinks(OutPt *pp) {
+  if (!pp)
+    return;
+  OutPt *pp1, *pp2;
+  pp1 = pp;
+  do {
+    pp2 = pp1->Next;
+    pp1->Next = pp1->Prev;
+    pp1->Prev = pp2;
+    pp1 = pp2;
+  } while (pp1 != pp);
+}
+//------------------------------------------------------------------------------
+
+void DisposeOutPts(OutPt *&pp) {
+  if (pp == 0)
+    return;
+  pp->Prev->Next = 0;
+  while (pp) {
+    OutPt *tmpPp = pp;
+    pp = pp->Next;
+    delete tmpPp;
+  }
+}
+//------------------------------------------------------------------------------
+
+inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) {
+  std::memset(e, 0, sizeof(TEdge));
+  e->Next = eNext;
+  e->Prev = ePrev;
+  e->Curr = Pt;
+  e->OutIdx = Unassigned;
+}
+//------------------------------------------------------------------------------
+
+void InitEdge2(TEdge &e, PolyType Pt) {
+  if (e.Curr.Y >= e.Next->Curr.Y) {
+    e.Bot = e.Curr;
+    e.Top = e.Next->Curr;
+  } else {
+    e.Top = e.Curr;
+    e.Bot = e.Next->Curr;
+  }
+  SetDx(e);
+  e.PolyTyp = Pt;
+}
+//------------------------------------------------------------------------------
+
+TEdge *RemoveEdge(TEdge *e) {
+  //removes e from double_linked_list (but without removing from memory)
+  e->Prev->Next = e->Next;
+  e->Next->Prev = e->Prev;
+  TEdge *result = e->Next;
+  e->Prev = 0; //flag as removed (see ClipperBase.Clear)
+  return result;
+}
+//------------------------------------------------------------------------------
+
+inline void ReverseHorizontal(TEdge &e) {
+  //swap horizontal edges' Top and Bottom x's so they follow the natural
+  //progression of the bounds - ie so their xbots will align with the
+  //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
+  std::swap(e.Top.X, e.Bot.X);
+#ifdef use_xyz
+  std::swap(e.Top.Z, e.Bot.Z);
+#endif
+}
+//------------------------------------------------------------------------------
+
+void SwapPoints(IntPoint &pt1, IntPoint &pt2) {
+  IntPoint tmp = pt1;
+  pt1 = pt2;
+  pt2 = tmp;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
+                       IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
+  //precondition: segments are Collinear.
+  if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
+    if (pt1a.X > pt1b.X)
+      SwapPoints(pt1a, pt1b);
+    if (pt2a.X > pt2b.X)
+      SwapPoints(pt2a, pt2b);
+    if (pt1a.X > pt2a.X)
+      pt1 = pt1a;
+    else
+      pt1 = pt2a;
+    if (pt1b.X < pt2b.X)
+      pt2 = pt1b;
+    else
+      pt2 = pt2b;
+    return pt1.X < pt2.X;
+  } else {
+    if (pt1a.Y < pt1b.Y)
+      SwapPoints(pt1a, pt1b);
+    if (pt2a.Y < pt2b.Y)
+      SwapPoints(pt2a, pt2b);
+    if (pt1a.Y < pt2a.Y)
+      pt1 = pt1a;
+    else
+      pt1 = pt2a;
+    if (pt1b.Y > pt2b.Y)
+      pt2 = pt1b;
+    else
+      pt2 = pt2b;
+    return pt1.Y > pt2.Y;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) {
+  OutPt *p = btmPt1->Prev;
+  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+    p = p->Prev;
+  double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+  p = btmPt1->Next;
+  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+    p = p->Next;
+  double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+
+  p = btmPt2->Prev;
+  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+    p = p->Prev;
+  double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+  p = btmPt2->Next;
+  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+    p = p->Next;
+  double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+
+  if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
+      std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
+    return Area(btmPt1) > 0; //if otherwise identical use orientation
+  else
+    return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
+}
+//------------------------------------------------------------------------------
+
+OutPt *GetBottomPt(OutPt *pp) {
+  OutPt *dups = 0;
+  OutPt *p = pp->Next;
+  while (p != pp) {
+    if (p->Pt.Y > pp->Pt.Y) {
+      pp = p;
+      dups = 0;
+    } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
+      if (p->Pt.X < pp->Pt.X) {
+        dups = 0;
+        pp = p;
+      } else {
+        if (p->Next != pp && p->Prev != pp)
+          dups = p;
+      }
+    }
+    p = p->Next;
+  }
+  if (dups) {
+    //there appears to be at least 2 vertices at BottomPt so ...
+    while (dups != p) {
+      if (!FirstIsBottomPt(p, dups))
+        pp = dups;
+      dups = dups->Next;
+      while (dups->Pt != pp->Pt)
+        dups = dups->Next;
+    }
+  }
+  return pp;
+}
+//------------------------------------------------------------------------------
+
+bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
+                           const IntPoint pt2, const IntPoint pt3) {
+  if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
+    return false;
+  else if (pt1.X != pt3.X)
+    return (pt2.X > pt1.X) == (pt2.X < pt3.X);
+  else
+    return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) {
+  if (seg1a > seg1b)
+    std::swap(seg1a, seg1b);
+  if (seg2a > seg2b)
+    std::swap(seg2a, seg2b);
+  return (seg1a < seg2b) && (seg2a < seg1b);
+}
+
+//------------------------------------------------------------------------------
+// ClipperBase class methods ...
+//------------------------------------------------------------------------------
+
+ClipperBase::ClipperBase() //constructor
+{
+  m_CurrentLM = m_MinimaList.begin(); //begin() == end() here
+  m_UseFullRange = false;
+}
+//------------------------------------------------------------------------------
+
+ClipperBase::~ClipperBase() //destructor
+{
+  Clear();
+}
+//------------------------------------------------------------------------------
+
+void RangeTest(const IntPoint &Pt, bool &useFullRange) {
+  if (useFullRange) {
+    if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
+      error("Coordinate outside allowed range");
+  } else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) {
+    useFullRange = true;
+    RangeTest(Pt, useFullRange);
+  }
+}
+//------------------------------------------------------------------------------
+
+TEdge *FindNextLocMin(TEdge *E) {
+  for (;;) {
+    while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
+      E = E->Next;
+    if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
+      break;
+    while (IsHorizontal(*E->Prev))
+      E = E->Prev;
+    TEdge *E2 = E;
+    while (IsHorizontal(*E))
+      E = E->Next;
+    if (E->Top.Y == E->Prev->Bot.Y)
+      continue; //ie just an intermediate horz.
+    if (E2->Prev->Bot.X < E->Bot.X)
+      E = E2;
+    break;
+  }
+  return E;
+}
+//------------------------------------------------------------------------------
+
+TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) {
+  TEdge *Result = E;
+  TEdge *Horz = 0;
+
+  if (E->OutIdx == Skip) {
+    //if edges still remain in the current bound beyond the skip edge then
+    //create another LocMin and call ProcessBound once more
+    if (NextIsForward) {
+      while (E->Top.Y == E->Next->Bot.Y)
+        E = E->Next;
+      //don't include top horizontals when parsing a bound a second time,
+      //they will be contained in the opposite bound ...
+      while (E != Result && IsHorizontal(*E))
+        E = E->Prev;
+    } else {
+      while (E->Top.Y == E->Prev->Bot.Y)
+        E = E->Prev;
+      while (E != Result && IsHorizontal(*E))
+        E = E->Next;
+    }
+
+    if (E == Result) {
+      if (NextIsForward)
+        Result = E->Next;
+      else
+        Result = E->Prev;
+    } else {
+      //there are more edges in the bound beyond result starting with E
+      if (NextIsForward)
+        E = Result->Next;
+      else
+        E = Result->Prev;
+      MinimaList::value_type locMin;
+      locMin.Y = E->Bot.Y;
+      locMin.LeftBound = 0;
+      locMin.RightBound = E;
+      E->WindDelta = 0;
+      Result = ProcessBound(E, NextIsForward);
+      m_MinimaList.push_back(locMin);
+    }
+    return Result;
+  }
+
+  TEdge *EStart;
+
+  if (IsHorizontal(*E)) {
+    //We need to be careful with open paths because this may not be a
+    //true local minima (ie E may be following a skip edge).
+    //Also, consecutive horz. edges may start heading left before going right.
+    if (NextIsForward)
+      EStart = E->Prev;
+    else
+      EStart = E->Next;
+    if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
+    {
+      if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
+        ReverseHorizontal(*E);
+    } else if (EStart->Bot.X != E->Bot.X)
+      ReverseHorizontal(*E);
+  }
+
+  EStart = E;
+  if (NextIsForward) {
+    while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
+      Result = Result->Next;
+    if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
+      //nb: at the top of a bound, horizontals are added to the bound
+      //only when the preceding edge attaches to the horizontal's left vertex
+      //unless a Skip edge is encountered when that becomes the top divide
+      Horz = Result;
+      while (IsHorizontal(*Horz->Prev))
+        Horz = Horz->Prev;
+      if (Horz->Prev->Top.X > Result->Next->Top.X)
+        Result = Horz->Prev;
+    }
+    while (E != Result) {
+      E->NextInLML = E->Next;
+      if (IsHorizontal(*E) && E != EStart &&
+          E->Bot.X != E->Prev->Top.X)
+        ReverseHorizontal(*E);
+      E = E->Next;
+    }
+    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
+      ReverseHorizontal(*E);
+    Result = Result->Next; //move to the edge just beyond current bound
+  } else {
+    while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
+      Result = Result->Prev;
+    if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
+      Horz = Result;
+      while (IsHorizontal(*Horz->Next))
+        Horz = Horz->Next;
+      if (Horz->Next->Top.X == Result->Prev->Top.X ||
+          Horz->Next->Top.X > Result->Prev->Top.X)
+        Result = Horz->Next;
+    }
+
+    while (E != Result) {
+      E->NextInLML = E->Prev;
+      if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+        ReverseHorizontal(*E);
+      E = E->Prev;
+    }
+    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+      ReverseHorizontal(*E);
+    Result = Result->Prev; //move to the edge just beyond current bound
+  }
+
+  return Result;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) {
+#ifdef use_lines
+  if (!Closed && PolyTyp == ptClip)
+    error("AddPath: Open paths must be subject.");
+#else
+  if (!Closed)
+    error("AddPath: Open paths have been disabled.");
+#endif
+
+  int highI = (int) pg.size() - 1;
+  if (Closed)
+    while (highI > 0 && (pg[highI] == pg[0]))
+      --highI;
+  while (highI > 0 && (pg[highI] == pg[highI - 1]))
+    --highI;
+  if ((Closed && highI < 2) || (!Closed && highI < 1))
+    return false;
+
+  //create a new edge array ...
+  TEdge *edges = new TEdge[highI + 1];
+
+  bool IsFlat = true;
+  //1. Basic (first) edge initialization ...
+  edges[1].Curr = pg[1];
+  RangeTest(pg[0], m_UseFullRange);
+  RangeTest(pg[highI], m_UseFullRange);
+  InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
+  InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
+  for (int i = highI - 1; i >= 1; --i) {
+    RangeTest(pg[i], m_UseFullRange);
+    InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
+  }
+
+  TEdge *eStart = &edges[0];
+
+  //2. Remove duplicate vertices, and (when closed) collinear edges ...
+  TEdge *E = eStart, *eLoopStop = eStart;
+  for (;;) {
+    //nb: allows matching start and end points when not Closed ...
+    if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
+      if (E == E->Next)
+        break;
+      if (E == eStart)
+        eStart = E->Next;
+      E = RemoveEdge(E);
+      eLoopStop = E;
+      continue;
+    }
+    if (E->Prev == E->Next)
+      break; //only two vertices
+    else if (Closed &&
+        SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) &&
+        (!m_PreserveCollinear ||
+            !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
+      //Collinear edges are allowed for open paths but in closed paths
+      //the default is to merge adjacent collinear edges into a single edge.
+      //However, if the PreserveCollinear property is enabled, only overlapping
+      //collinear edges (ie spikes) will be removed from closed paths.
+      if (E == eStart)
+        eStart = E->Next;
+      E = RemoveEdge(E);
+      E = E->Prev;
+      eLoopStop = E;
+      continue;
+    }
+    E = E->Next;
+    if ((E == eLoopStop) || (!Closed && E->Next == eStart))
+      break;
+  }
+
+  if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
+    delete[] edges;
+    return false;
+  }
+
+  if (!Closed) {
+    m_HasOpenPaths = true;
+    eStart->Prev->OutIdx = Skip;
+  }
+
+  //3. Do second stage of edge initialization ...
+  E = eStart;
+  do {
+    InitEdge2(*E, PolyTyp);
+    E = E->Next;
+    if (IsFlat && E->Curr.Y != eStart->Curr.Y)
+      IsFlat = false;
+  } while (E != eStart);
+
+  //4. Finally, add edge bounds to LocalMinima list ...
+
+  //Totally flat paths must be handled differently when adding them
+  //to LocalMinima list to avoid endless loops etc ...
+  if (IsFlat) {
+    if (Closed) {
+      delete[] edges;
+      return false;
+    }
+    E->Prev->OutIdx = Skip;
+    MinimaList::value_type locMin;
+    locMin.Y = E->Bot.Y;
+    locMin.LeftBound = 0;
+    locMin.RightBound = E;
+    locMin.RightBound->Side = esRight;
+    locMin.RightBound->WindDelta = 0;
+    for (;;) {
+      if (E->Bot.X != E->Prev->Top.X)
+        ReverseHorizontal(*E);
+      if (E->Next->OutIdx == Skip)
+        break;
+      E->NextInLML = E->Next;
+      E = E->Next;
+    }
+    m_MinimaList.push_back(locMin);
+    m_edges.push_back(edges);
+    return true;
+  }
+
+  m_edges.push_back(edges);
+  bool leftBoundIsForward;
+  TEdge *EMin = 0;
+
+  //workaround to avoid an endless loop in the while loop below when
+  //open paths have matching start and end points ...
+  if (E->Prev->Bot == E->Prev->Top)
+    E = E->Next;
+
+  for (;;) {
+    E = FindNextLocMin(E);
+    if (E == EMin)
+      break;
+    else if (!EMin)
+      EMin = E;
+
+    //E and E.Prev now share a local minima (left aligned if horizontal).
+    //Compare their slopes to find which starts which bound ...
+    MinimaList::value_type locMin;
+    locMin.Y = E->Bot.Y;
+    if (E->Dx < E->Prev->Dx) {
+      locMin.LeftBound = E->Prev;
+      locMin.RightBound = E;
+      leftBoundIsForward = false; //Q.nextInLML = Q.prev
+    } else {
+      locMin.LeftBound = E;
+      locMin.RightBound = E->Prev;
+      leftBoundIsForward = true; //Q.nextInLML = Q.next
+    }
+
+    if (!Closed)
+      locMin.LeftBound->WindDelta = 0;
+    else if (locMin.LeftBound->Next == locMin.RightBound)
+      locMin.LeftBound->WindDelta = -1;
+    else
+      locMin.LeftBound->WindDelta = 1;
+    locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
+
+    E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
+    if (E->OutIdx == Skip)
+      E = ProcessBound(E, leftBoundIsForward);
+
+    TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
+    if (E2->OutIdx == Skip)
+      E2 = ProcessBound(E2, !leftBoundIsForward);
+
+    if (locMin.LeftBound->OutIdx == Skip)
+      locMin.LeftBound = 0;
+    else if (locMin.RightBound->OutIdx == Skip)
+      locMin.RightBound = 0;
+    m_MinimaList.push_back(locMin);
+    if (!leftBoundIsForward)
+      E = E2;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) {
+  bool result = false;
+  for (Paths::size_type i = 0; i < ppg.size(); ++i)
+    if (AddPath(ppg[i], PolyTyp, Closed))
+      result = true;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Clear() {
+  DisposeLocalMinimaList();
+  for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
+    TEdge *edges = m_edges[i];
+    delete[] edges;
+  }
+  m_edges.clear();
+  m_UseFullRange = false;
+  m_HasOpenPaths = false;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Reset() {
+  m_CurrentLM = m_MinimaList.begin();
+  if (m_CurrentLM == m_MinimaList.end())
+    return; //ie nothing to process
+  std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
+
+  m_Scanbeam = ScanbeamList(); //clears/resets priority_queue
+  //reset all edges ...
+  for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) {
+    InsertScanbeam(lm->Y);
+    TEdge *e = lm->LeftBound;
+    if (e) {
+      e->Curr = e->Bot;
+      e->Side = esLeft;
+      e->OutIdx = Unassigned;
+    }
+
+    e = lm->RightBound;
+    if (e) {
+      e->Curr = e->Bot;
+      e->Side = esRight;
+      e->OutIdx = Unassigned;
+    }
+  }
+  m_ActiveEdges = 0;
+  m_CurrentLM = m_MinimaList.begin();
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeLocalMinimaList() {
+  m_MinimaList.clear();
+  m_CurrentLM = m_MinimaList.begin();
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) {
+  if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
+    return false;
+  locMin = &(*m_CurrentLM);
+  ++m_CurrentLM;
+  return true;
+}
+//------------------------------------------------------------------------------
+
+IntRect ClipperBase::GetBounds() {
+  IntRect result;
+  MinimaList::iterator lm = m_MinimaList.begin();
+  if (lm == m_MinimaList.end()) {
+    result.left = result.top = result.right = result.bottom = 0;
+    return result;
+  }
+  result.left = lm->LeftBound->Bot.X;
+  result.top = lm->LeftBound->Bot.Y;
+  result.right = lm->LeftBound->Bot.X;
+  result.bottom = lm->LeftBound->Bot.Y;
+  while (lm != m_MinimaList.end()) {
+    //todo - needs fixing for open paths
+    result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
+    TEdge *e = lm->LeftBound;
+    for (;;) {
+      TEdge *bottomE = e;
+      while (e->NextInLML) {
+        if (e->Bot.X < result.left)
+          result.left = e->Bot.X;
+        if (e->Bot.X > result.right)
+          result.right = e->Bot.X;
+        e = e->NextInLML;
+      }
+      result.left = std::min(result.left, e->Bot.X);
+      result.right = std::max(result.right, e->Bot.X);
+      result.left = std::min(result.left, e->Top.X);
+      result.right = std::max(result.right, e->Top.X);
+      result.top = std::min(result.top, e->Top.Y);
+      if (bottomE == lm->LeftBound)
+        e = lm->RightBound;
+      else
+        break;
+    }
+    ++lm;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::InsertScanbeam(const cInt Y) {
+  m_Scanbeam.push(Y);
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::PopScanbeam(cInt &Y) {
+  if (m_Scanbeam.empty())
+    return false;
+  Y = m_Scanbeam.top();
+  m_Scanbeam.pop();
+  while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
+    m_Scanbeam.pop();
+  } // Pop duplicates.
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeAllOutRecs() {
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+    DisposeOutRec(i);
+  m_PolyOuts.clear();
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeOutRec(PolyOutList::size_type index) {
+  OutRec *outRec = m_PolyOuts[index];
+  if (outRec->Pts)
+    DisposeOutPts(outRec->Pts);
+  delete outRec;
+  m_PolyOuts[index] = 0;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DeleteFromAEL(TEdge *e) {
+  TEdge *AelPrev = e->PrevInAEL;
+  TEdge *AelNext = e->NextInAEL;
+  if (!AelPrev && !AelNext && (e != m_ActiveEdges))
+    return; //already deleted
+  if (AelPrev)
+    AelPrev->NextInAEL = AelNext;
+  else
+    m_ActiveEdges = AelNext;
+  if (AelNext)
+    AelNext->PrevInAEL = AelPrev;
+  e->NextInAEL = 0;
+  e->PrevInAEL = 0;
+}
+//------------------------------------------------------------------------------
+
+OutRec *ClipperBase::CreateOutRec() {
+  OutRec *result = new OutRec;
+  result->IsHole = false;
+  result->IsOpen = false;
+  result->FirstLeft = 0;
+  result->Pts = 0;
+  result->BottomPt = 0;
+  result->PolyNd = 0;
+  m_PolyOuts.push_back(result);
+  result->Idx = (int) m_PolyOuts.size() - 1;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) {
+  //check that one or other edge hasn't already been removed from AEL ...
+  if (Edge1->NextInAEL == Edge1->PrevInAEL ||
+      Edge2->NextInAEL == Edge2->PrevInAEL)
+    return;
+
+  if (Edge1->NextInAEL == Edge2) {
+    TEdge *Next = Edge2->NextInAEL;
+    if (Next)
+      Next->PrevInAEL = Edge1;
+    TEdge *Prev = Edge1->PrevInAEL;
+    if (Prev)
+      Prev->NextInAEL = Edge2;
+    Edge2->PrevInAEL = Prev;
+    Edge2->NextInAEL = Edge1;
+    Edge1->PrevInAEL = Edge2;
+    Edge1->NextInAEL = Next;
+  } else if (Edge2->NextInAEL == Edge1) {
+    TEdge *Next = Edge1->NextInAEL;
+    if (Next)
+      Next->PrevInAEL = Edge2;
+    TEdge *Prev = Edge2->PrevInAEL;
+    if (Prev)
+      Prev->NextInAEL = Edge1;
+    Edge1->PrevInAEL = Prev;
+    Edge1->NextInAEL = Edge2;
+    Edge2->PrevInAEL = Edge1;
+    Edge2->NextInAEL = Next;
+  } else {
+    TEdge *Next = Edge1->NextInAEL;
+    TEdge *Prev = Edge1->PrevInAEL;
+    Edge1->NextInAEL = Edge2->NextInAEL;
+    if (Edge1->NextInAEL)
+      Edge1->NextInAEL->PrevInAEL = Edge1;
+    Edge1->PrevInAEL = Edge2->PrevInAEL;
+    if (Edge1->PrevInAEL)
+      Edge1->PrevInAEL->NextInAEL = Edge1;
+    Edge2->NextInAEL = Next;
+    if (Edge2->NextInAEL)
+      Edge2->NextInAEL->PrevInAEL = Edge2;
+    Edge2->PrevInAEL = Prev;
+    if (Edge2->PrevInAEL)
+      Edge2->PrevInAEL->NextInAEL = Edge2;
+  }
+
+  if (!Edge1->PrevInAEL)
+    m_ActiveEdges = Edge1;
+  else if (!Edge2->PrevInAEL)
+    m_ActiveEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) {
+  if (!e->NextInLML)
+    error("UpdateEdgeIntoAEL: invalid call");
+
+  e->NextInLML->OutIdx = e->OutIdx;
+  TEdge *AelPrev = e->PrevInAEL;
+  TEdge *AelNext = e->NextInAEL;
+  if (AelPrev)
+    AelPrev->NextInAEL = e->NextInLML;
+  else
+    m_ActiveEdges = e->NextInLML;
+  if (AelNext)
+    AelNext->PrevInAEL = e->NextInLML;
+  e->NextInLML->Side = e->Side;
+  e->NextInLML->WindDelta = e->WindDelta;
+  e->NextInLML->WindCnt = e->WindCnt;
+  e->NextInLML->WindCnt2 = e->WindCnt2;
+  e = e->NextInLML;
+  e->Curr = e->Bot;
+  e->PrevInAEL = AelPrev;
+  e->NextInAEL = AelNext;
+  if (!IsHorizontal(*e))
+    InsertScanbeam(e->Top.Y);
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::LocalMinimaPending() {
+  return (m_CurrentLM != m_MinimaList.end());
+}
+
+//------------------------------------------------------------------------------
+// TClipper methods ...
+//------------------------------------------------------------------------------
+
+Clipper::Clipper(int initOptions) : ClipperBase() //constructor
+{
+  m_ExecuteLocked = false;
+  m_UseFullRange = false;
+  m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
+  m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
+  m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
+  m_HasOpenPaths = false;
+#ifdef use_xyz
+  m_ZFill = 0;
+#endif
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::ZFillFunction(ZFillCallback zFillFunc)
+{
+  m_ZFill = zFillFunc;
+}
+//------------------------------------------------------------------------------
+#endif
+
+bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) {
+  return Execute(clipType, solution, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) {
+  return Execute(clipType, polytree, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, Paths &solution,
+                      PolyFillType subjFillType, PolyFillType clipFillType) {
+  if (m_ExecuteLocked)
+    return false;
+  if (m_HasOpenPaths)
+    error("Error: PolyTree struct is needed for open path clipping.");
+  m_ExecuteLocked = true;
+  solution.resize(0);
+  m_SubjFillType = subjFillType;
+  m_ClipFillType = clipFillType;
+  m_ClipType = clipType;
+  m_UsingPolyTree = false;
+  bool succeeded = ExecuteInternal();
+  if (succeeded)
+    BuildResult(solution);
+  DisposeAllOutRecs();
+  m_ExecuteLocked = false;
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
+                      PolyFillType subjFillType, PolyFillType clipFillType) {
+  if (m_ExecuteLocked)
+    return false;
+  m_ExecuteLocked = true;
+  m_SubjFillType = subjFillType;
+  m_ClipFillType = clipFillType;
+  m_ClipType = clipType;
+  m_UsingPolyTree = true;
+  bool succeeded = ExecuteInternal();
+  if (succeeded)
+    BuildResult2(polytree);
+  DisposeAllOutRecs();
+  m_ExecuteLocked = false;
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixHoleLinkage(OutRec &outrec) {
+  //skip OutRecs that (a) contain outermost polygons or
+  //(b) already have the correct owner/child linkage ...
+  if (!outrec.FirstLeft ||
+      (outrec.IsHole != outrec.FirstLeft->IsHole &&
+          outrec.FirstLeft->Pts))
+    return;
+
+  OutRec *orfl = outrec.FirstLeft;
+  while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
+    orfl = orfl->FirstLeft;
+  outrec.FirstLeft = orfl;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ExecuteInternal() {
+  bool succeeded = true;
+  {
+    Reset();
+    m_Maxima = MaximaList();
+    m_SortedEdges = 0;
+
+    succeeded = true;
+    cInt botY, topY;
+    if (!PopScanbeam(botY))
+      return false;
+    InsertLocalMinimaIntoAEL(botY);
+    while (PopScanbeam(topY) || LocalMinimaPending()) {
+      ProcessHorizontals();
+      ClearGhostJoins();
+      if (!ProcessIntersections(topY)) {
+        succeeded = false;
+        break;
+      }
+      ProcessEdgesAtTopOfScanbeam(topY);
+      botY = topY;
+      InsertLocalMinimaIntoAEL(botY);
+    }
+  }
+
+  if (succeeded) {
+    //fix orientations ...
+    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+      OutRec *outRec = m_PolyOuts[i];
+      if (!outRec->Pts || outRec->IsOpen)
+        continue;
+      if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
+        ReversePolyPtLinks(outRec->Pts);
+    }
+
+    if (!m_Joins.empty())
+      JoinCommonEdges();
+
+    //unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
+    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+      OutRec *outRec = m_PolyOuts[i];
+      if (!outRec->Pts)
+        continue;
+      if (outRec->IsOpen)
+        FixupOutPolyline(*outRec);
+      else
+        FixupOutPolygon(*outRec);
+    }
+
+    if (m_StrictSimple)
+      DoSimplePolygons();
+  }
+
+  ClearJoins();
+  ClearGhostJoins();
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetWindingCount(TEdge &edge) {
+  TEdge *e = edge.PrevInAEL;
+  //find the edge of the same polytype that immediately preceeds 'edge' in AEL
+  while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
+    e = e->PrevInAEL;
+  if (!e) {
+    if (edge.WindDelta == 0) {
+      PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
+      edge.WindCnt = (pft == pftNegative ? -1 : 1);
+    } else
+      edge.WindCnt = edge.WindDelta;
+    edge.WindCnt2 = 0;
+    e = m_ActiveEdges; //ie get ready to calc WindCnt2
+  } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
+    edge.WindCnt = 1;
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; //ie get ready to calc WindCnt2
+  } else if (IsEvenOddFillType(edge)) {
+    //EvenOdd filling ...
+    if (edge.WindDelta == 0) {
+      //are we inside a subj polygon ...
+      bool Inside = true;
+      TEdge *e2 = e->PrevInAEL;
+      while (e2) {
+        if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
+          Inside = !Inside;
+        e2 = e2->PrevInAEL;
+      }
+      edge.WindCnt = (Inside ? 0 : 1);
+    } else {
+      edge.WindCnt = edge.WindDelta;
+    }
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; //ie get ready to calc WindCnt2
+  } else {
+    //nonZero, Positive or Negative filling ...
+    if (e->WindCnt * e->WindDelta < 0) {
+      //prev edge is 'decreasing' WindCount (WC) toward zero
+      //so we're outside the previous polygon ...
+      if (Abs(e->WindCnt) > 1) {
+        //outside prev poly but still inside another.
+        //when reversing direction of prev poly use the same WC
+        if (e->WindDelta * edge.WindDelta < 0)
+          edge.WindCnt = e->WindCnt;
+          //otherwise continue to 'decrease' WC ...
+        else
+          edge.WindCnt = e->WindCnt + edge.WindDelta;
+      } else
+        //now outside all polys of same polytype so set own WC ...
+        edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
+    } else {
+      //prev edge is 'increasing' WindCount (WC) away from zero
+      //so we're inside the previous polygon ...
+      if (edge.WindDelta == 0)
+        edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
+        //if wind direction is reversing prev then use same WC
+      else if (e->WindDelta * edge.WindDelta < 0)
+        edge.WindCnt = e->WindCnt;
+        //otherwise add to WC ...
+      else
+        edge.WindCnt = e->WindCnt + edge.WindDelta;
+    }
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; //ie get ready to calc WindCnt2
+  }
+
+  //update WindCnt2 ...
+  if (IsEvenOddAltFillType(edge)) {
+    //EvenOdd filling ...
+    while (e != &edge) {
+      if (e->WindDelta != 0)
+        edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
+      e = e->NextInAEL;
+    }
+  } else {
+    //nonZero, Positive or Negative filling ...
+    while (e != &edge) {
+      edge.WindCnt2 += e->WindDelta;
+      e = e->NextInAEL;
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddFillType(const TEdge &edge) const {
+  if (edge.PolyTyp == ptSubject)
+    return m_SubjFillType == pftEvenOdd;
+  else
+    return m_ClipFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const {
+  if (edge.PolyTyp == ptSubject)
+    return m_ClipFillType == pftEvenOdd;
+  else
+    return m_SubjFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsContributing(const TEdge &edge) const {
+  PolyFillType pft, pft2;
+  if (edge.PolyTyp == ptSubject) {
+    pft = m_SubjFillType;
+    pft2 = m_ClipFillType;
+  } else {
+    pft = m_ClipFillType;
+    pft2 = m_SubjFillType;
+  }
+
+  switch (pft) {
+  case pftEvenOdd:
+    //return false if a subj line has been flagged as inside a subj polygon
+    if (edge.WindDelta == 0 && edge.WindCnt != 1)
+      return false;
+    break;
+  case pftNonZero:
+    if (Abs(edge.WindCnt) != 1)
+      return false;
+    break;
+  case pftPositive:
+    if (edge.WindCnt != 1)
+      return false;
+    break;
+  default: //pftNegative
+    if (edge.WindCnt != -1)
+      return false;
+  }
+
+  switch (m_ClipType) {
+  case ctIntersection:
+    switch (pft2) {
+    case pftEvenOdd:
+    case pftNonZero:return (edge.WindCnt2 != 0);
+    case pftPositive:return (edge.WindCnt2 > 0);
+    default:return (edge.WindCnt2 < 0);
+    }
+    break;
+  case ctUnion:
+    switch (pft2) {
+    case pftEvenOdd:
+    case pftNonZero:return (edge.WindCnt2 == 0);
+    case pftPositive:return (edge.WindCnt2 <= 0);
+    default:return (edge.WindCnt2 >= 0);
+    }
+    break;
+  case ctDifference:
+    if (edge.PolyTyp == ptSubject)
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:return (edge.WindCnt2 == 0);
+      case pftPositive:return (edge.WindCnt2 <= 0);
+      default:return (edge.WindCnt2 >= 0);
+      }
+    else
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:return (edge.WindCnt2 != 0);
+      case pftPositive:return (edge.WindCnt2 > 0);
+      default:return (edge.WindCnt2 < 0);
+      }
+    break;
+  case ctXor:
+    if (edge.WindDelta == 0) //XOr always contributing unless open
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:return (edge.WindCnt2 == 0);
+      case pftPositive:return (edge.WindCnt2 <= 0);
+      default:return (edge.WindCnt2 >= 0);
+      }
+    else
+      return true;
+    break;
+  default:return true;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
+  OutPt *result;
+  TEdge *e, *prevE;
+  if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
+    result = AddOutPt(e1, Pt);
+    e2->OutIdx = e1->OutIdx;
+    e1->Side = esLeft;
+    e2->Side = esRight;
+    e = e1;
+    if (e->PrevInAEL == e2)
+      prevE = e2->PrevInAEL;
+    else
+      prevE = e->PrevInAEL;
+  } else {
+    result = AddOutPt(e2, Pt);
+    e1->OutIdx = e2->OutIdx;
+    e1->Side = esRight;
+    e2->Side = esLeft;
+    e = e2;
+    if (e->PrevInAEL == e1)
+      prevE = e1->PrevInAEL;
+    else
+      prevE = e->PrevInAEL;
+  }
+
+  if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
+    cInt xPrev = TopX(*prevE, Pt.Y);
+    cInt xE = TopX(*e, Pt.Y);
+    if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
+        SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) {
+      OutPt *outPt = AddOutPt(prevE, Pt);
+      AddJoin(result, outPt, e->Top);
+    }
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
+  AddOutPt(e1, Pt);
+  if (e2->WindDelta == 0)
+    AddOutPt(e2, Pt);
+  if (e1->OutIdx == e2->OutIdx) {
+    e1->OutIdx = Unassigned;
+    e2->OutIdx = Unassigned;
+  } else if (e1->OutIdx < e2->OutIdx)
+    AppendPolygon(e1, e2);
+  else
+    AppendPolygon(e2, e1);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddEdgeToSEL(TEdge *edge) {
+  //SEL pointers in PEdge are reused to build a list of horizontal edges.
+  //However, we don't need to worry about order with horizontal edge processing.
+  if (!m_SortedEdges) {
+    m_SortedEdges = edge;
+    edge->PrevInSEL = 0;
+    edge->NextInSEL = 0;
+  } else {
+    edge->NextInSEL = m_SortedEdges;
+    edge->PrevInSEL = 0;
+    m_SortedEdges->PrevInSEL = edge;
+    m_SortedEdges = edge;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::PopEdgeFromSEL(TEdge *&edge) {
+  if (!m_SortedEdges)
+    return false;
+  edge = m_SortedEdges;
+  DeleteFromSEL(m_SortedEdges);
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::CopyAELToSEL() {
+  TEdge *e = m_ActiveEdges;
+  m_SortedEdges = e;
+  while (e) {
+    e->PrevInSEL = e->PrevInAEL;
+    e->NextInSEL = e->NextInAEL;
+    e = e->NextInAEL;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) {
+  Join *j = new Join;
+  j->OutPt1 = op1;
+  j->OutPt2 = op2;
+  j->OffPt = OffPt;
+  m_Joins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearJoins() {
+  for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+    delete m_Joins[i];
+  m_Joins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearGhostJoins() {
+  for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
+    delete m_GhostJoins[i];
+  m_GhostJoins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) {
+  Join *j = new Join;
+  j->OutPt1 = op;
+  j->OutPt2 = 0;
+  j->OffPt = OffPt;
+  m_GhostJoins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) {
+  const LocalMinimum *lm;
+  while (PopLocalMinima(botY, lm)) {
+    TEdge *lb = lm->LeftBound;
+    TEdge *rb = lm->RightBound;
+
+    OutPt *Op1 = 0;
+    if (!lb) {
+      //nb: don't insert LB into either AEL or SEL
+      InsertEdgeIntoAEL(rb, 0);
+      SetWindingCount(*rb);
+      if (IsContributing(*rb))
+        Op1 = AddOutPt(rb, rb->Bot);
+    } else if (!rb) {
+      InsertEdgeIntoAEL(lb, 0);
+      SetWindingCount(*lb);
+      if (IsContributing(*lb))
+        Op1 = AddOutPt(lb, lb->Bot);
+      InsertScanbeam(lb->Top.Y);
+    } else {
+      InsertEdgeIntoAEL(lb, 0);
+      InsertEdgeIntoAEL(rb, lb);
+      SetWindingCount(*lb);
+      rb->WindCnt = lb->WindCnt;
+      rb->WindCnt2 = lb->WindCnt2;
+      if (IsContributing(*lb))
+        Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
+      InsertScanbeam(lb->Top.Y);
+    }
+
+    if (rb) {
+      if (IsHorizontal(*rb)) {
+        AddEdgeToSEL(rb);
+        if (rb->NextInLML)
+          InsertScanbeam(rb->NextInLML->Top.Y);
+      } else
+        InsertScanbeam(rb->Top.Y);
+    }
+
+    if (!lb || !rb)
+      continue;
+
+    //if any output polygons share an edge, they'll need joining later ...
+    if (Op1 && IsHorizontal(*rb) &&
+        m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) {
+      for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
+        Join *jr = m_GhostJoins[i];
+        //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+        //the 'ghost' join to a real join ready for later ...
+        if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X))
+          AddJoin(jr->OutPt1, Op1, jr->OffPt);
+      }
+    }
+
+    if (lb->OutIdx >= 0 && lb->PrevInAEL &&
+        lb->PrevInAEL->Curr.X == lb->Bot.X &&
+        lb->PrevInAEL->OutIdx >= 0 &&
+        SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) &&
+        (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
+      OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
+      AddJoin(Op1, Op2, lb->Top);
+    }
+
+    if (lb->NextInAEL != rb) {
+
+      if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
+          SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) &&
+          (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
+        OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
+        AddJoin(Op1, Op2, rb->Top);
+      }
+
+      TEdge *e = lb->NextInAEL;
+      if (e) {
+        while (e != rb) {
+          //nb: For calculating winding counts etc, IntersectEdges() assumes
+          //that param1 will be to the Right of param2 ABOVE the intersection ...
+          IntersectEdges(rb, e, lb->Curr); //order important here
+          e = e->NextInAEL;
+        }
+      }
+    }
+
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromSEL(TEdge *e) {
+  TEdge *SelPrev = e->PrevInSEL;
+  TEdge *SelNext = e->NextInSEL;
+  if (!SelPrev && !SelNext && (e != m_SortedEdges))
+    return; //already deleted
+  if (SelPrev)
+    SelPrev->NextInSEL = SelNext;
+  else
+    m_SortedEdges = SelNext;
+  if (SelNext)
+    SelNext->PrevInSEL = SelPrev;
+  e->NextInSEL = 0;
+  e->PrevInSEL = 0;
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
+{
+  if (pt.Z != 0 || !m_ZFill) return;
+  else if (pt == e1.Bot) pt.Z = e1.Bot.Z;
+  else if (pt == e1.Top) pt.Z = e1.Top.Z;
+  else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
+  else if (pt == e2.Top) pt.Z = e2.Top.Z;
+  else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
+}
+//------------------------------------------------------------------------------
+#endif
+
+void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) {
+  bool e1Contributing = (e1->OutIdx >= 0);
+  bool e2Contributing = (e2->OutIdx >= 0);
+
+#ifdef use_xyz
+  SetZ(Pt, *e1, *e2);
+#endif
+
+#ifdef use_lines
+  //if either edge is on an OPEN path ...
+  if (e1->WindDelta == 0 || e2->WindDelta == 0) {
+    //ignore subject-subject open path intersections UNLESS they
+    //are both open paths, AND they are both 'contributing maximas' ...
+    if (e1->WindDelta == 0 && e2->WindDelta == 0)
+      return;
+
+      //if intersecting a subj line with a subj poly ...
+    else if (e1->PolyTyp == e2->PolyTyp &&
+        e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) {
+      if (e1->WindDelta == 0) {
+        if (e2Contributing) {
+          AddOutPt(e1, Pt);
+          if (e1Contributing)
+            e1->OutIdx = Unassigned;
+        }
+      } else {
+        if (e1Contributing) {
+          AddOutPt(e2, Pt);
+          if (e2Contributing)
+            e2->OutIdx = Unassigned;
+        }
+      }
+    } else if (e1->PolyTyp != e2->PolyTyp) {
+      //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
+      if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
+          (m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
+        AddOutPt(e1, Pt);
+        if (e1Contributing)
+          e1->OutIdx = Unassigned;
+      } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
+          (m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
+        AddOutPt(e2, Pt);
+        if (e2Contributing)
+          e2->OutIdx = Unassigned;
+      }
+    }
+    return;
+  }
+#endif
+
+  //update winding counts...
+  //assumes that e1 will be to the Right of e2 ABOVE the intersection
+  if (e1->PolyTyp == e2->PolyTyp) {
+    if (IsEvenOddFillType(*e1)) {
+      int oldE1WindCnt = e1->WindCnt;
+      e1->WindCnt = e2->WindCnt;
+      e2->WindCnt = oldE1WindCnt;
+    } else {
+      if (e1->WindCnt + e2->WindDelta == 0)
+        e1->WindCnt = -e1->WindCnt;
+      else
+        e1->WindCnt += e2->WindDelta;
+      if (e2->WindCnt - e1->WindDelta == 0)
+        e2->WindCnt = -e2->WindCnt;
+      else
+        e2->WindCnt -= e1->WindDelta;
+    }
+  } else {
+    if (!IsEvenOddFillType(*e2))
+      e1->WindCnt2 += e2->WindDelta;
+    else
+      e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
+    if (!IsEvenOddFillType(*e1))
+      e2->WindCnt2 -= e1->WindDelta;
+    else
+      e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
+  }
+
+  PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
+  if (e1->PolyTyp == ptSubject) {
+    e1FillType = m_SubjFillType;
+    e1FillType2 = m_ClipFillType;
+  } else {
+    e1FillType = m_ClipFillType;
+    e1FillType2 = m_SubjFillType;
+  }
+  if (e2->PolyTyp == ptSubject) {
+    e2FillType = m_SubjFillType;
+    e2FillType2 = m_ClipFillType;
+  } else {
+    e2FillType = m_ClipFillType;
+    e2FillType2 = m_SubjFillType;
+  }
+
+  cInt e1Wc, e2Wc;
+  switch (e1FillType) {
+  case pftPositive: e1Wc = e1->WindCnt;
+    break;
+  case pftNegative: e1Wc = -e1->WindCnt;
+    break;
+  default: e1Wc = Abs(e1->WindCnt);
+  }
+  switch (e2FillType) {
+  case pftPositive: e2Wc = e2->WindCnt;
+    break;
+  case pftNegative: e2Wc = -e2->WindCnt;
+    break;
+  default: e2Wc = Abs(e2->WindCnt);
+  }
+
+  if (e1Contributing && e2Contributing) {
+    if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
+        (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
+      AddLocalMaxPoly(e1, e2, Pt);
+    } else {
+      AddOutPt(e1, Pt);
+      AddOutPt(e2, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if (e1Contributing) {
+    if (e2Wc == 0 || e2Wc == 1) {
+      AddOutPt(e1, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if (e2Contributing) {
+    if (e1Wc == 0 || e1Wc == 1) {
+      AddOutPt(e2, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
+    //neither edge is currently contributing ...
+
+    cInt e1Wc2, e2Wc2;
+    switch (e1FillType2) {
+    case pftPositive: e1Wc2 = e1->WindCnt2;
+      break;
+    case pftNegative : e1Wc2 = -e1->WindCnt2;
+      break;
+    default: e1Wc2 = Abs(e1->WindCnt2);
+    }
+    switch (e2FillType2) {
+    case pftPositive: e2Wc2 = e2->WindCnt2;
+      break;
+    case pftNegative: e2Wc2 = -e2->WindCnt2;
+      break;
+    default: e2Wc2 = Abs(e2->WindCnt2);
+    }
+
+    if (e1->PolyTyp != e2->PolyTyp) {
+      AddLocalMinPoly(e1, e2, Pt);
+    } else if (e1Wc == 1 && e2Wc == 1)
+      switch (m_ClipType) {
+      case ctIntersection:
+        if (e1Wc2 > 0 && e2Wc2 > 0)
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctUnion:
+        if (e1Wc2 <= 0 && e2Wc2 <= 0)
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctDifference:
+        if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+            ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctXor:AddLocalMinPoly(e1, e2, Pt);
+      }
+    else
+      SwapSides(*e1, *e2);
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetHoleState(TEdge *e, OutRec *outrec) {
+  TEdge *e2 = e->PrevInAEL;
+  TEdge *eTmp = 0;
+  while (e2) {
+    if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
+      if (!eTmp)
+        eTmp = e2;
+      else if (eTmp->OutIdx == e2->OutIdx)
+        eTmp = 0;
+    }
+    e2 = e2->PrevInAEL;
+  }
+  if (!eTmp) {
+    outrec->FirstLeft = 0;
+    outrec->IsHole = false;
+  } else {
+    outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
+    outrec->IsHole = !outrec->FirstLeft->IsHole;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) {
+  //work out which polygon fragment has the correct hole state ...
+  if (!outRec1->BottomPt)
+    outRec1->BottomPt = GetBottomPt(outRec1->Pts);
+  if (!outRec2->BottomPt)
+    outRec2->BottomPt = GetBottomPt(outRec2->Pts);
+  OutPt *OutPt1 = outRec1->BottomPt;
+  OutPt *OutPt2 = outRec2->BottomPt;
+  if (OutPt1->Pt.Y > OutPt2->Pt.Y)
+    return outRec1;
+  else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
+    return outRec2;
+  else if (OutPt1->Pt.X < OutPt2->Pt.X)
+    return outRec1;
+  else if (OutPt1->Pt.X > OutPt2->Pt.X)
+    return outRec2;
+  else if (OutPt1->Next == OutPt1)
+    return outRec2;
+  else if (OutPt2->Next == OutPt2)
+    return outRec1;
+  else if (FirstIsBottomPt(OutPt1, OutPt2))
+    return outRec1;
+  else
+    return outRec2;
+}
+//------------------------------------------------------------------------------
+
+bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) {
+  do {
+    outRec1 = outRec1->FirstLeft;
+    if (outRec1 == outRec2)
+      return true;
+  } while (outRec1);
+  return false;
+}
+//------------------------------------------------------------------------------
+
+OutRec *Clipper::GetOutRec(int Idx) {
+  OutRec *outrec = m_PolyOuts[Idx];
+  while (outrec != m_PolyOuts[outrec->Idx])
+    outrec = m_PolyOuts[outrec->Idx];
+  return outrec;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) {
+  //get the start and ends of both output polygons ...
+  OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
+  OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
+
+  OutRec *holeStateRec;
+  if (OutRec1RightOfOutRec2(outRec1, outRec2))
+    holeStateRec = outRec2;
+  else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+    holeStateRec = outRec1;
+  else
+    holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+  //get the start and ends of both output polygons and
+  //join e2 poly onto e1 poly and delete pointers to e2 ...
+
+  OutPt *p1_lft = outRec1->Pts;
+  OutPt *p1_rt = p1_lft->Prev;
+  OutPt *p2_lft = outRec2->Pts;
+  OutPt *p2_rt = p2_lft->Prev;
+
+  //join e2 poly onto e1 poly and delete pointers to e2 ...
+  if (e1->Side == esLeft) {
+    if (e2->Side == esLeft) {
+      //z y x a b c
+      ReversePolyPtLinks(p2_lft);
+      p2_lft->Next = p1_lft;
+      p1_lft->Prev = p2_lft;
+      p1_rt->Next = p2_rt;
+      p2_rt->Prev = p1_rt;
+      outRec1->Pts = p2_rt;
+    } else {
+      //x y z a b c
+      p2_rt->Next = p1_lft;
+      p1_lft->Prev = p2_rt;
+      p2_lft->Prev = p1_rt;
+      p1_rt->Next = p2_lft;
+      outRec1->Pts = p2_lft;
+    }
+  } else {
+    if (e2->Side == esRight) {
+      //a b c z y x
+      ReversePolyPtLinks(p2_lft);
+      p1_rt->Next = p2_rt;
+      p2_rt->Prev = p1_rt;
+      p2_lft->Next = p1_lft;
+      p1_lft->Prev = p2_lft;
+    } else {
+      //a b c x y z
+      p1_rt->Next = p2_lft;
+      p2_lft->Prev = p1_rt;
+      p1_lft->Prev = p2_rt;
+      p2_rt->Next = p1_lft;
+    }
+  }
+
+  outRec1->BottomPt = 0;
+  if (holeStateRec == outRec2) {
+    if (outRec2->FirstLeft != outRec1)
+      outRec1->FirstLeft = outRec2->FirstLeft;
+    outRec1->IsHole = outRec2->IsHole;
+  }
+  outRec2->Pts = 0;
+  outRec2->BottomPt = 0;
+  outRec2->FirstLeft = outRec1;
+
+  int OKIdx = e1->OutIdx;
+  int ObsoleteIdx = e2->OutIdx;
+
+  e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly
+  e2->OutIdx = Unassigned;
+
+  TEdge *e = m_ActiveEdges;
+  while (e) {
+    if (e->OutIdx == ObsoleteIdx) {
+      e->OutIdx = OKIdx;
+      e->Side = e1->Side;
+      break;
+    }
+    e = e->NextInAEL;
+  }
+
+  outRec2->Idx = outRec1->Idx;
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) {
+  if (e->OutIdx < 0) {
+    OutRec *outRec = CreateOutRec();
+    outRec->IsOpen = (e->WindDelta == 0);
+    OutPt *newOp = new OutPt;
+    outRec->Pts = newOp;
+    newOp->Idx = outRec->Idx;
+    newOp->Pt = pt;
+    newOp->Next = newOp;
+    newOp->Prev = newOp;
+    if (!outRec->IsOpen)
+      SetHoleState(e, outRec);
+    e->OutIdx = outRec->Idx;
+    return newOp;
+  } else {
+    OutRec *outRec = m_PolyOuts[e->OutIdx];
+    //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
+    OutPt *op = outRec->Pts;
+
+    bool ToFront = (e->Side == esLeft);
+    if (ToFront && (pt == op->Pt))
+      return op;
+    else if (!ToFront && (pt == op->Prev->Pt))
+      return op->Prev;
+
+    OutPt *newOp = new OutPt;
+    newOp->Idx = outRec->Idx;
+    newOp->Pt = pt;
+    newOp->Next = op;
+    newOp->Prev = op->Prev;
+    newOp->Prev->Next = newOp;
+    op->Prev = newOp;
+    if (ToFront)
+      outRec->Pts = newOp;
+    return newOp;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::GetLastOutPt(TEdge *e) {
+  OutRec *outRec = m_PolyOuts[e->OutIdx];
+  if (e->Side == esLeft)
+    return outRec->Pts;
+  else
+    return outRec->Pts->Prev;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessHorizontals() {
+  TEdge *horzEdge;
+  while (PopEdgeFromSEL(horzEdge))
+    ProcessHorizontal(horzEdge);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMinima(TEdge *e) {
+  return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMaxima(TEdge *e, const cInt Y) {
+  return e && e->Top.Y == Y && !e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+inline bool IsIntermediate(TEdge *e, const cInt Y) {
+  return e->Top.Y == Y && e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPair(TEdge *e) {
+  if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
+    return e->Next;
+  else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
+    return e->Prev;
+  else
+    return 0;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPairEx(TEdge *e) {
+  //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal)
+  TEdge *result = GetMaximaPair(e);
+  if (result && (result->OutIdx == Skip ||
+      (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
+    return 0;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) {
+  if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
+    return;
+  if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
+    return;
+
+  if (Edge1->NextInSEL == Edge2) {
+    TEdge *Next = Edge2->NextInSEL;
+    if (Next)
+      Next->PrevInSEL = Edge1;
+    TEdge *Prev = Edge1->PrevInSEL;
+    if (Prev)
+      Prev->NextInSEL = Edge2;
+    Edge2->PrevInSEL = Prev;
+    Edge2->NextInSEL = Edge1;
+    Edge1->PrevInSEL = Edge2;
+    Edge1->NextInSEL = Next;
+  } else if (Edge2->NextInSEL == Edge1) {
+    TEdge *Next = Edge1->NextInSEL;
+    if (Next)
+      Next->PrevInSEL = Edge2;
+    TEdge *Prev = Edge2->PrevInSEL;
+    if (Prev)
+      Prev->NextInSEL = Edge1;
+    Edge1->PrevInSEL = Prev;
+    Edge1->NextInSEL = Edge2;
+    Edge2->PrevInSEL = Edge1;
+    Edge2->NextInSEL = Next;
+  } else {
+    TEdge *Next = Edge1->NextInSEL;
+    TEdge *Prev = Edge1->PrevInSEL;
+    Edge1->NextInSEL = Edge2->NextInSEL;
+    if (Edge1->NextInSEL)
+      Edge1->NextInSEL->PrevInSEL = Edge1;
+    Edge1->PrevInSEL = Edge2->PrevInSEL;
+    if (Edge1->PrevInSEL)
+      Edge1->PrevInSEL->NextInSEL = Edge1;
+    Edge2->NextInSEL = Next;
+    if (Edge2->NextInSEL)
+      Edge2->NextInSEL->PrevInSEL = Edge2;
+    Edge2->PrevInSEL = Prev;
+    if (Edge2->PrevInSEL)
+      Edge2->PrevInSEL->NextInSEL = Edge2;
+  }
+
+  if (!Edge1->PrevInSEL)
+    m_SortedEdges = Edge1;
+  else if (!Edge2->PrevInSEL)
+    m_SortedEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetNextInAEL(TEdge *e, Direction dir) {
+  return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
+}
+//------------------------------------------------------------------------------
+
+void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left, cInt &Right) {
+  if (HorzEdge.Bot.X < HorzEdge.Top.X) {
+    Left = HorzEdge.Bot.X;
+    Right = HorzEdge.Top.X;
+    Dir = dLeftToRight;
+  } else {
+    Left = HorzEdge.Top.X;
+    Right = HorzEdge.Bot.X;
+    Dir = dRightToLeft;
+  }
+}
+//------------------------------------------------------------------------
+
+/*******************************************************************************
+* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or    *
+* Bottom of a scanbeam) are processed as if layered. The order in which HEs    *
+* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#]    *
+* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs),      *
+* and with other non-horizontal edges [*]. Once these intersections are        *
+* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into   *
+* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs.    *
+*******************************************************************************/
+
+void Clipper::ProcessHorizontal(TEdge *horzEdge) {
+  Direction dir;
+  cInt horzLeft, horzRight;
+  bool IsOpen = (horzEdge->WindDelta == 0);
+
+  GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+  TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
+  while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
+    eLastHorz = eLastHorz->NextInLML;
+  if (!eLastHorz->NextInLML)
+    eMaxPair = GetMaximaPair(eLastHorz);
+
+  MaximaList::const_iterator maxIt;
+  MaximaList::const_reverse_iterator maxRit;
+  if (m_Maxima.size() > 0) {
+    //get the first maxima in range (X) ...
+    if (dir == dLeftToRight) {
+      maxIt = m_Maxima.begin();
+      while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
+        maxIt++;
+      if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
+        maxIt = m_Maxima.end();
+    } else {
+      maxRit = m_Maxima.rbegin();
+      while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X)
+        maxRit++;
+      if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
+        maxRit = m_Maxima.rend();
+    }
+  }
+
+  OutPt *op1 = 0;
+
+  for (;;) //loop through consec. horizontal edges
+  {
+
+    bool IsLastHorz = (horzEdge == eLastHorz);
+    TEdge *e = GetNextInAEL(horzEdge, dir);
+    while (e) {
+
+      //this code block inserts extra coords into horizontal edges (in output
+      //polygons) whereever maxima touch these horizontal edges. This helps
+      //'simplifying' polygons (ie if the Simplify property is set).
+      if (m_Maxima.size() > 0) {
+        if (dir == dLeftToRight) {
+          while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
+            if (horzEdge->OutIdx >= 0 && !IsOpen)
+              AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
+            maxIt++;
+          }
+        } else {
+          while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) {
+            if (horzEdge->OutIdx >= 0 && !IsOpen)
+              AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
+            maxRit++;
+          }
+        }
+      }
+
+      if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
+          (dir == dRightToLeft && e->Curr.X < horzLeft))
+        break;
+
+      //Also break if we've got to the end of an intermediate horizontal edge ...
+      //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+      if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
+          e->Dx < horzEdge->NextInLML->Dx)
+        break;
+
+      if (horzEdge->OutIdx >= 0 && !IsOpen)  //note: may be done multiple times
+      {
+#ifdef use_xyz
+        if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e);
+        else SetZ(e->Curr, *e, *horzEdge);
+#endif
+        op1 = AddOutPt(horzEdge, e->Curr);
+        TEdge *eNextHorz = m_SortedEdges;
+        while (eNextHorz) {
+          if (eNextHorz->OutIdx >= 0 &&
+              HorzSegmentsOverlap(horzEdge->Bot.X,
+                                  horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
+            OutPt *op2 = GetLastOutPt(eNextHorz);
+            AddJoin(op2, op1, eNextHorz->Top);
+          }
+          eNextHorz = eNextHorz->NextInSEL;
+        }
+        AddGhostJoin(op1, horzEdge->Bot);
+      }
+
+      //OK, so far we're still in range of the horizontal Edge  but make sure
+      //we're at the last of consec. horizontals when matching with eMaxPair
+      if (e == eMaxPair && IsLastHorz) {
+        if (horzEdge->OutIdx >= 0)
+          AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
+        DeleteFromAEL(horzEdge);
+        DeleteFromAEL(eMaxPair);
+        return;
+      }
+
+      if (dir == dLeftToRight) {
+        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+        IntersectEdges(horzEdge, e, Pt);
+      } else {
+        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+        IntersectEdges(e, horzEdge, Pt);
+      }
+      TEdge *eNext = GetNextInAEL(e, dir);
+      SwapPositionsInAEL(horzEdge, e);
+      e = eNext;
+    } //end while(e)
+
+    //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+    if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
+      break;
+
+    UpdateEdgeIntoAEL(horzEdge);
+    if (horzEdge->OutIdx >= 0)
+      AddOutPt(horzEdge, horzEdge->Bot);
+    GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+  } //end for (;;)
+
+  if (horzEdge->OutIdx >= 0 && !op1) {
+    op1 = GetLastOutPt(horzEdge);
+    TEdge *eNextHorz = m_SortedEdges;
+    while (eNextHorz) {
+      if (eNextHorz->OutIdx >= 0 &&
+          HorzSegmentsOverlap(horzEdge->Bot.X,
+                              horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
+        OutPt *op2 = GetLastOutPt(eNextHorz);
+        AddJoin(op2, op1, eNextHorz->Top);
+      }
+      eNextHorz = eNextHorz->NextInSEL;
+    }
+    AddGhostJoin(op1, horzEdge->Top);
+  }
+
+  if (horzEdge->NextInLML) {
+    if (horzEdge->OutIdx >= 0) {
+      op1 = AddOutPt(horzEdge, horzEdge->Top);
+      UpdateEdgeIntoAEL(horzEdge);
+      if (horzEdge->WindDelta == 0)
+        return;
+      //nb: HorzEdge is no longer horizontal here
+      TEdge *ePrev = horzEdge->PrevInAEL;
+      TEdge *eNext = horzEdge->NextInAEL;
+      if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
+          ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
+          (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+              SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
+        OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
+        AddJoin(op1, op2, horzEdge->Top);
+      } else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
+          eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
+          eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
+          SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
+        OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
+        AddJoin(op1, op2, horzEdge->Top);
+      }
+    } else
+      UpdateEdgeIntoAEL(horzEdge);
+  } else {
+    if (horzEdge->OutIdx >= 0)
+      AddOutPt(horzEdge, horzEdge->Top);
+    DeleteFromAEL(horzEdge);
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ProcessIntersections(const cInt topY) {
+  if (!m_ActiveEdges)
+    return true;
+  {
+    BuildIntersectList(topY);
+    size_t IlSize = m_IntersectList.size();
+    if (IlSize == 0)
+      return true;
+    if (IlSize == 1 || FixupIntersectionOrder())
+      ProcessIntersectList();
+    else
+      return false;
+  }
+
+  m_SortedEdges = 0;
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeIntersectNodes() {
+  for (size_t i = 0; i < m_IntersectList.size(); ++i)
+    delete m_IntersectList[i];
+  m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildIntersectList(const cInt topY) {
+  if (!m_ActiveEdges)
+    return;
+
+  //prepare for sorting ...
+  TEdge *e = m_ActiveEdges;
+  m_SortedEdges = e;
+  while (e) {
+    e->PrevInSEL = e->PrevInAEL;
+    e->NextInSEL = e->NextInAEL;
+    e->Curr.X = TopX(*e, topY);
+    e = e->NextInAEL;
+  }
+
+  //bubblesort ...
+  bool isModified;
+  do {
+    isModified = false;
+    e = m_SortedEdges;
+    while (e->NextInSEL) {
+      TEdge *eNext = e->NextInSEL;
+      IntPoint Pt;
+      if (e->Curr.X > eNext->Curr.X) {
+        IntersectPoint(*e, *eNext, Pt);
+        if (Pt.Y < topY)
+          Pt = IntPoint(TopX(*e, topY), topY);
+        IntersectNode *newNode = new IntersectNode;
+        newNode->Edge1 = e;
+        newNode->Edge2 = eNext;
+        newNode->Pt = Pt;
+        m_IntersectList.push_back(newNode);
+
+        SwapPositionsInSEL(e, eNext);
+        isModified = true;
+      } else
+        e = eNext;
+    }
+    if (e->PrevInSEL)
+      e->PrevInSEL->NextInSEL = 0;
+    else
+      break;
+  } while (isModified);
+  m_SortedEdges = 0; //important
+}
+//------------------------------------------------------------------------------
+
+
+void Clipper::ProcessIntersectList() {
+  for (size_t i = 0; i < m_IntersectList.size(); ++i) {
+    IntersectNode *iNode = m_IntersectList[i];
+    {
+      IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
+      SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
+    }
+    delete iNode;
+  }
+  m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) {
+  return node2->Pt.Y < node1->Pt.Y;
+}
+//------------------------------------------------------------------------------
+
+inline bool EdgesAdjacent(const IntersectNode &inode) {
+  return (inode.Edge1->NextInSEL == inode.Edge2) ||
+      (inode.Edge1->PrevInSEL == inode.Edge2);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::FixupIntersectionOrder() {
+  //pre-condition: intersections are sorted Bottom-most first.
+  //Now it's crucial that intersections are made only between adjacent edges,
+  //so to ensure this the order of intersections may need adjusting ...
+  CopyAELToSEL();
+  std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
+  size_t cnt = m_IntersectList.size();
+  for (size_t i = 0; i < cnt; ++i) {
+    if (!EdgesAdjacent(*m_IntersectList[i])) {
+      size_t j = i + 1;
+      while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
+        j++;
+      if (j == cnt)
+        return false;
+      std::swap(m_IntersectList[i], m_IntersectList[j]);
+    }
+    SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DoMaxima(TEdge *e) {
+  TEdge *eMaxPair = GetMaximaPairEx(e);
+  if (!eMaxPair) {
+    if (e->OutIdx >= 0)
+      AddOutPt(e, e->Top);
+    DeleteFromAEL(e);
+    return;
+  }
+
+  TEdge *eNext = e->NextInAEL;
+  while (eNext && eNext != eMaxPair) {
+    IntersectEdges(e, eNext, e->Top);
+    SwapPositionsInAEL(e, eNext);
+    eNext = e->NextInAEL;
+  }
+
+  if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
+    DeleteFromAEL(e);
+    DeleteFromAEL(eMaxPair);
+  } else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
+    if (e->OutIdx >= 0)
+      AddLocalMaxPoly(e, eMaxPair, e->Top);
+    DeleteFromAEL(e);
+    DeleteFromAEL(eMaxPair);
+  }
+#ifdef use_lines
+  else if (e->WindDelta == 0) {
+    if (e->OutIdx >= 0) {
+      AddOutPt(e, e->Top);
+      e->OutIdx = Unassigned;
+    }
+    DeleteFromAEL(e);
+
+    if (eMaxPair->OutIdx >= 0) {
+      AddOutPt(eMaxPair, e->Top);
+      eMaxPair->OutIdx = Unassigned;
+    }
+    DeleteFromAEL(eMaxPair);
+  }
+#endif
+  else
+    error("DoMaxima error");
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) {
+  TEdge *e = m_ActiveEdges;
+  while (e) {
+    //1. process maxima, treating them as if they're 'bent' horizontal edges,
+    //   but exclude maxima with horizontal edges. nb: e can't be a horizontal.
+    bool IsMaximaEdge = IsMaxima(e, topY);
+
+    if (IsMaximaEdge) {
+      TEdge *eMaxPair = GetMaximaPairEx(e);
+      IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
+    }
+
+    if (IsMaximaEdge) {
+      if (m_StrictSimple)
+        m_Maxima.push_back(e->Top.X);
+      TEdge *ePrev = e->PrevInAEL;
+      DoMaxima(e);
+      if (!ePrev)
+        e = m_ActiveEdges;
+      else
+        e = ePrev->NextInAEL;
+    } else {
+      //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
+      if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
+        UpdateEdgeIntoAEL(e);
+        if (e->OutIdx >= 0)
+          AddOutPt(e, e->Bot);
+        AddEdgeToSEL(e);
+      } else {
+        e->Curr.X = TopX(*e, topY);
+        e->Curr.Y = topY;
+#ifdef use_xyz
+        e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
+#endif
+      }
+
+      //When StrictlySimple and 'e' is being touched by another edge, then
+      //make sure both edges have a vertex here ...
+      if (m_StrictSimple) {
+        TEdge *ePrev = e->PrevInAEL;
+        if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) &&
+            (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) {
+          IntPoint pt = e->Curr;
+#ifdef use_xyz
+          SetZ(pt, *ePrev, *e);
+#endif
+          OutPt *op = AddOutPt(ePrev, pt);
+          OutPt *op2 = AddOutPt(e, pt);
+          AddJoin(op, op2, pt); //StrictlySimple (type-3) join
+        }
+      }
+
+      e = e->NextInAEL;
+    }
+  }
+
+  //3. Process horizontals at the Top of the scanbeam ...
+  m_Maxima.sort();
+  ProcessHorizontals();
+  m_Maxima.clear();
+
+  //4. Promote intermediate vertices ...
+  e = m_ActiveEdges;
+  while (e) {
+    if (IsIntermediate(e, topY)) {
+      OutPt *op = 0;
+      if (e->OutIdx >= 0)
+        op = AddOutPt(e, e->Top);
+      UpdateEdgeIntoAEL(e);
+
+      //if output polygons share an edge, they'll need joining later ...
+      TEdge *ePrev = e->PrevInAEL;
+      TEdge *eNext = e->NextInAEL;
+      if (ePrev && ePrev->Curr.X == e->Bot.X &&
+          ePrev->Curr.Y == e->Bot.Y && op &&
+          ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+          SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) &&
+          (e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
+        OutPt *op2 = AddOutPt(ePrev, e->Bot);
+        AddJoin(op, op2, e->Top);
+      } else if (eNext && eNext->Curr.X == e->Bot.X &&
+          eNext->Curr.Y == e->Bot.Y && op &&
+          eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
+          SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) &&
+          (e->WindDelta != 0) && (eNext->WindDelta != 0)) {
+        OutPt *op2 = AddOutPt(eNext, e->Bot);
+        AddJoin(op, op2, e->Top);
+      }
+    }
+    e = e->NextInAEL;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolyline(OutRec &outrec) {
+  OutPt *pp = outrec.Pts;
+  OutPt *lastPP = pp->Prev;
+  while (pp != lastPP) {
+    pp = pp->Next;
+    if (pp->Pt == pp->Prev->Pt) {
+      if (pp == lastPP)
+        lastPP = pp->Prev;
+      OutPt *tmpPP = pp->Prev;
+      tmpPP->Next = pp->Next;
+      pp->Next->Prev = tmpPP;
+      delete pp;
+      pp = tmpPP;
+    }
+  }
+
+  if (pp == pp->Prev) {
+    DisposeOutPts(pp);
+    outrec.Pts = 0;
+    return;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolygon(OutRec &outrec) {
+  //FixupOutPolygon() - removes duplicate points and simplifies consecutive
+  //parallel edges by removing the middle vertex.
+  OutPt *lastOK = 0;
+  outrec.BottomPt = 0;
+  OutPt *pp = outrec.Pts;
+  bool preserveCol = m_PreserveCollinear || m_StrictSimple;
+
+  for (;;) {
+    if (pp->Prev == pp || pp->Prev == pp->Next) {
+      DisposeOutPts(pp);
+      outrec.Pts = 0;
+      return;
+    }
+
+    //test for duplicate points and collinear edges ...
+    if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
+        (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
+            (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
+      lastOK = 0;
+      OutPt *tmp = pp;
+      pp->Prev->Next = pp->Next;
+      pp->Next->Prev = pp->Prev;
+      pp = pp->Prev;
+      delete tmp;
+    } else if (pp == lastOK)
+      break;
+    else {
+      if (!lastOK)
+        lastOK = pp;
+      pp = pp->Next;
+    }
+  }
+  outrec.Pts = pp;
+}
+//------------------------------------------------------------------------------
+
+int PointCount(OutPt *Pts) {
+  if (!Pts)
+    return 0;
+  int result = 0;
+  OutPt *p = Pts;
+  do {
+    result++;
+    p = p->Next;
+  } while (p != Pts);
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult(Paths &polys) {
+  polys.reserve(m_PolyOuts.size());
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    if (!m_PolyOuts[i]->Pts)
+      continue;
+    Path pg;
+    OutPt *p = m_PolyOuts[i]->Pts->Prev;
+    int cnt = PointCount(p);
+    if (cnt < 2)
+      continue;
+    pg.reserve(cnt);
+    for (int i = 0; i < cnt; ++i) {
+      pg.push_back(p->Pt);
+      p = p->Prev;
+    }
+    polys.push_back(pg);
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult2(PolyTree &polytree) {
+  polytree.Clear();
+  polytree.AllNodes.reserve(m_PolyOuts.size());
+  //add each output polygon/contour to polytree ...
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+    OutRec *outRec = m_PolyOuts[i];
+    int cnt = PointCount(outRec->Pts);
+    if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
+      continue;
+    FixHoleLinkage(*outRec);
+    PolyNode *pn = new PolyNode();
+    //nb: polytree takes ownership of all the PolyNodes
+    polytree.AllNodes.push_back(pn);
+    outRec->PolyNd = pn;
+    pn->Parent = 0;
+    pn->Index = 0;
+    pn->Contour.reserve(cnt);
+    OutPt *op = outRec->Pts->Prev;
+    for (int j = 0; j < cnt; j++) {
+      pn->Contour.push_back(op->Pt);
+      op = op->Prev;
+    }
+  }
+
+  //fixup PolyNode links etc ...
+  polytree.Childs.reserve(m_PolyOuts.size());
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+    OutRec *outRec = m_PolyOuts[i];
+    if (!outRec->PolyNd)
+      continue;
+    if (outRec->IsOpen) {
+      outRec->PolyNd->m_IsOpen = true;
+      polytree.AddChild(*outRec->PolyNd);
+    } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
+      outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
+    else
+      polytree.AddChild(*outRec->PolyNd);
+  }
+}
+//------------------------------------------------------------------------------
+
+void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) {
+  //just swap the contents (because fIntersectNodes is a single-linked-list)
+  IntersectNode inode = int1; //gets a copy of Int1
+  int1.Edge1 = int2.Edge1;
+  int1.Edge2 = int2.Edge2;
+  int1.Pt = int2.Pt;
+  int2.Edge1 = inode.Edge1;
+  int2.Edge2 = inode.Edge2;
+  int2.Pt = inode.Pt;
+}
+//------------------------------------------------------------------------------
+
+inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) {
+  if (e2.Curr.X == e1.Curr.X) {
+    if (e2.Top.Y > e1.Top.Y)
+      return e2.Top.X < TopX(e1, e2.Top.Y);
+    else
+      return e1.Top.X > TopX(e2, e1.Top.Y);
+  } else
+    return e2.Curr.X < e1.Curr.X;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
+                cInt &Left, cInt &Right) {
+  if (a1 < a2) {
+    if (b1 < b2) {
+      Left = std::max(a1, b1);
+      Right = std::min(a2, b2);
+    } else {
+      Left = std::max(a1, b2);
+      Right = std::min(a2, b1);
+    }
+  } else {
+    if (b1 < b2) {
+      Left = std::max(a2, b1);
+      Right = std::min(a1, b2);
+    } else {
+      Left = std::max(a2, b2);
+      Right = std::min(a1, b1);
+    }
+  }
+  return Left < Right;
+}
+//------------------------------------------------------------------------------
+
+inline void UpdateOutPtIdxs(OutRec &outrec) {
+  OutPt *op = outrec.Pts;
+  do {
+    op->Idx = outrec.Idx;
+    op = op->Prev;
+  } while (op != outrec.Pts);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) {
+  if (!m_ActiveEdges) {
+    edge->PrevInAEL = 0;
+    edge->NextInAEL = 0;
+    m_ActiveEdges = edge;
+  } else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
+    edge->PrevInAEL = 0;
+    edge->NextInAEL = m_ActiveEdges;
+    m_ActiveEdges->PrevInAEL = edge;
+    m_ActiveEdges = edge;
+  } else {
+    if (!startEdge)
+      startEdge = m_ActiveEdges;
+    while (startEdge->NextInAEL &&
+        !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
+      startEdge = startEdge->NextInAEL;
+    edge->NextInAEL = startEdge->NextInAEL;
+    if (startEdge->NextInAEL)
+      startEdge->NextInAEL->PrevInAEL = edge;
+    edge->PrevInAEL = startEdge;
+    startEdge->NextInAEL = edge;
+  }
+}
+//----------------------------------------------------------------------
+
+OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) {
+  OutPt *result = new OutPt;
+  result->Pt = outPt->Pt;
+  result->Idx = outPt->Idx;
+  if (InsertAfter) {
+    result->Next = outPt->Next;
+    result->Prev = outPt;
+    outPt->Next->Prev = result;
+    outPt->Next = result;
+  } else {
+    result->Prev = outPt->Prev;
+    result->Next = outPt;
+    outPt->Prev->Next = result;
+    outPt->Prev = result;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b,
+              const IntPoint Pt, bool DiscardLeft) {
+  Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
+  Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
+  if (Dir1 == Dir2)
+    return false;
+
+  //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
+  //want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
+  //So, to facilitate this while inserting Op1b and Op2b ...
+  //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
+  //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
+  if (Dir1 == dLeftToRight) {
+    while (op1->Next->Pt.X <= Pt.X &&
+        op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
+      op1 = op1->Next;
+    if (DiscardLeft && (op1->Pt.X != Pt.X))
+      op1 = op1->Next;
+    op1b = DupOutPt(op1, !DiscardLeft);
+    if (op1b->Pt != Pt) {
+      op1 = op1b;
+      op1->Pt = Pt;
+      op1b = DupOutPt(op1, !DiscardLeft);
+    }
+  } else {
+    while (op1->Next->Pt.X >= Pt.X &&
+        op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
+      op1 = op1->Next;
+    if (!DiscardLeft && (op1->Pt.X != Pt.X))
+      op1 = op1->Next;
+    op1b = DupOutPt(op1, DiscardLeft);
+    if (op1b->Pt != Pt) {
+      op1 = op1b;
+      op1->Pt = Pt;
+      op1b = DupOutPt(op1, DiscardLeft);
+    }
+  }
+
+  if (Dir2 == dLeftToRight) {
+    while (op2->Next->Pt.X <= Pt.X &&
+        op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
+      op2 = op2->Next;
+    if (DiscardLeft && (op2->Pt.X != Pt.X))
+      op2 = op2->Next;
+    op2b = DupOutPt(op2, !DiscardLeft);
+    if (op2b->Pt != Pt) {
+      op2 = op2b;
+      op2->Pt = Pt;
+      op2b = DupOutPt(op2, !DiscardLeft);
+    }
+  } else {
+    while (op2->Next->Pt.X >= Pt.X &&
+        op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
+      op2 = op2->Next;
+    if (!DiscardLeft && (op2->Pt.X != Pt.X))
+      op2 = op2->Next;
+    op2b = DupOutPt(op2, DiscardLeft);
+    if (op2b->Pt != Pt) {
+      op2 = op2b;
+      op2->Pt = Pt;
+      op2b = DupOutPt(op2, DiscardLeft);
+    }
+  }
+
+  if ((Dir1 == dLeftToRight) == DiscardLeft) {
+    op1->Prev = op2;
+    op2->Next = op1;
+    op1b->Next = op2b;
+    op2b->Prev = op1b;
+  } else {
+    op1->Next = op2;
+    op2->Prev = op1;
+    op1b->Prev = op2b;
+    op2b->Next = op1b;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) {
+  OutPt *op1 = j->OutPt1, *op1b;
+  OutPt *op2 = j->OutPt2, *op2b;
+
+  //There are 3 kinds of joins for output polygons ...
+  //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
+  //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
+  //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
+  //location at the Bottom of the overlapping segment (& Join.OffPt is above).
+  //3. StrictSimple joins where edges touch but are not collinear and where
+  //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
+  bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
+
+  if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
+      (j->OffPt == j->OutPt2->Pt)) {
+    //Strictly Simple join ...
+    if (outRec1 != outRec2)
+      return false;
+    op1b = j->OutPt1->Next;
+    while (op1b != op1 && (op1b->Pt == j->OffPt))
+      op1b = op1b->Next;
+    bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
+    op2b = j->OutPt2->Next;
+    while (op2b != op2 && (op2b->Pt == j->OffPt))
+      op2b = op2b->Next;
+    bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
+    if (reverse1 == reverse2)
+      return false;
+    if (reverse1) {
+      op1b = DupOutPt(op1, false);
+      op2b = DupOutPt(op2, true);
+      op1->Prev = op2;
+      op2->Next = op1;
+      op1b->Next = op2b;
+      op2b->Prev = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    } else {
+      op1b = DupOutPt(op1, true);
+      op2b = DupOutPt(op2, false);
+      op1->Next = op2;
+      op2->Prev = op1;
+      op1b->Prev = op2b;
+      op2b->Next = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    }
+  } else if (isHorizontal) {
+    //treat horizontal joins differently to non-horizontal joins since with
+    //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
+    //may be anywhere along the horizontal edge.
+    op1b = op1;
+    while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2)
+      op1 = op1->Prev;
+    while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2)
+      op1b = op1b->Next;
+    if (op1b->Next == op1 || op1b->Next == op2)
+      return false; //a flat 'polygon'
+
+    op2b = op2;
+    while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b)
+      op2 = op2->Prev;
+    while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1)
+      op2b = op2b->Next;
+    if (op2b->Next == op2 || op2b->Next == op1)
+      return false; //a flat 'polygon'
+
+    cInt Left, Right;
+    //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
+    if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
+      return false;
+
+    //DiscardLeftSide: when overlapping edges are joined, a spike will created
+    //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
+    //on the discard Side as either may still be needed for other joins ...
+    IntPoint Pt;
+    bool DiscardLeftSide;
+    if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
+      Pt = op1->Pt;
+      DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
+    } else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
+      Pt = op2->Pt;
+      DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
+    } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
+      Pt = op1b->Pt;
+      DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
+    } else {
+      Pt = op2b->Pt;
+      DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
+    }
+    j->OutPt1 = op1;
+    j->OutPt2 = op2;
+    return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
+  } else {
+    //nb: For non-horizontal joins ...
+    //    1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
+    //    2. Jr.OutPt1.Pt > Jr.OffPt.Y
+
+    //make sure the polygons are correctly oriented ...
+    op1b = op1->Next;
+    while ((op1b->Pt == op1->Pt) && (op1b != op1))
+      op1b = op1b->Next;
+    bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
+        !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
+    if (Reverse1) {
+      op1b = op1->Prev;
+      while ((op1b->Pt == op1->Pt) && (op1b != op1))
+        op1b = op1b->Prev;
+      if ((op1b->Pt.Y > op1->Pt.Y) ||
+          !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
+        return false;
+    }
+    op2b = op2->Next;
+    while ((op2b->Pt == op2->Pt) && (op2b != op2))
+      op2b = op2b->Next;
+    bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
+        !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
+    if (Reverse2) {
+      op2b = op2->Prev;
+      while ((op2b->Pt == op2->Pt) && (op2b != op2))
+        op2b = op2b->Prev;
+      if ((op2b->Pt.Y > op2->Pt.Y) ||
+          !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
+        return false;
+    }
+
+    if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
+        ((outRec1 == outRec2) && (Reverse1 == Reverse2)))
+      return false;
+
+    if (Reverse1) {
+      op1b = DupOutPt(op1, false);
+      op2b = DupOutPt(op2, true);
+      op1->Prev = op2;
+      op2->Next = op1;
+      op1b->Next = op2b;
+      op2b->Prev = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    } else {
+      op1b = DupOutPt(op1, true);
+      op2b = DupOutPt(op2, false);
+      op1->Next = op2;
+      op2->Prev = op1;
+      op1b->Prev = op2b;
+      op2b->Next = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    }
+  }
+}
+//----------------------------------------------------------------------
+
+static OutRec *ParseFirstLeft(OutRec *FirstLeft) {
+  while (FirstLeft && !FirstLeft->Pts)
+    FirstLeft = FirstLeft->FirstLeft;
+  return FirstLeft;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) {
+  //tests if NewOutRec contains the polygon before reassigning FirstLeft
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (outRec->Pts && firstLeft == OldOutRec) {
+      if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
+        outRec->FirstLeft = NewOutRec;
+    }
+  }
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) {
+  //A polygon has split into two such that one is now the inner of the other.
+  //It's possible that these polygons now wrap around other polygons, so check
+  //every polygon that's also contained by OuterOutRec's FirstLeft container
+  //(including 0) to see if they've become inner to the new inner polygon ...
+  OutRec *orfl = OuterOutRec->FirstLeft;
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+
+    if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
+      continue;
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec)
+      continue;
+    if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
+      outRec->FirstLeft = InnerOutRec;
+    else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
+      outRec->FirstLeft = OuterOutRec;
+    else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec)
+      outRec->FirstLeft = orfl;
+  }
+}
+//----------------------------------------------------------------------
+void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) {
+  //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (outRec->Pts && firstLeft == OldOutRec)
+      outRec->FirstLeft = NewOutRec;
+  }
+}
+//----------------------------------------------------------------------
+
+void Clipper::JoinCommonEdges() {
+  for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
+    Join *join = m_Joins[i];
+
+    OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
+    OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
+
+    if (!outRec1->Pts || !outRec2->Pts)
+      continue;
+    if (outRec1->IsOpen || outRec2->IsOpen)
+      continue;
+
+    //get the polygon fragment with the correct hole state (FirstLeft)
+    //before calling JoinPoints() ...
+    OutRec *holeStateRec;
+    if (outRec1 == outRec2)
+      holeStateRec = outRec1;
+    else if (OutRec1RightOfOutRec2(outRec1, outRec2))
+      holeStateRec = outRec2;
+    else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+      holeStateRec = outRec1;
+    else
+      holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+    if (!JoinPoints(join, outRec1, outRec2))
+      continue;
+
+    if (outRec1 == outRec2) {
+      //instead of joining two polygons, we've just created a new one by
+      //splitting one polygon into two.
+      outRec1->Pts = join->OutPt1;
+      outRec1->BottomPt = 0;
+      outRec2 = CreateOutRec();
+      outRec2->Pts = join->OutPt2;
+
+      //update all OutRec2.Pts Idx's ...
+      UpdateOutPtIdxs(*outRec2);
+
+      if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
+        //outRec1 contains outRec2 ...
+        outRec2->IsHole = !outRec1->IsHole;
+        outRec2->FirstLeft = outRec1;
+
+        if (m_UsingPolyTree)
+          FixupFirstLefts2(outRec2, outRec1);
+
+        if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
+          ReversePolyPtLinks(outRec2->Pts);
+
+      } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
+        //outRec2 contains outRec1 ...
+        outRec2->IsHole = outRec1->IsHole;
+        outRec1->IsHole = !outRec2->IsHole;
+        outRec2->FirstLeft = outRec1->FirstLeft;
+        outRec1->FirstLeft = outRec2;
+
+        if (m_UsingPolyTree)
+          FixupFirstLefts2(outRec1, outRec2);
+
+        if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
+          ReversePolyPtLinks(outRec1->Pts);
+      } else {
+        //the 2 polygons are completely separate ...
+        outRec2->IsHole = outRec1->IsHole;
+        outRec2->FirstLeft = outRec1->FirstLeft;
+
+        //fixup FirstLeft pointers that may need reassigning to OutRec2
+        if (m_UsingPolyTree)
+          FixupFirstLefts1(outRec1, outRec2);
+      }
+
+    } else {
+      //joined 2 polygons together ...
+
+      outRec2->Pts = 0;
+      outRec2->BottomPt = 0;
+      outRec2->Idx = outRec1->Idx;
+
+      outRec1->IsHole = holeStateRec->IsHole;
+      if (holeStateRec == outRec2)
+        outRec1->FirstLeft = outRec2->FirstLeft;
+      outRec2->FirstLeft = outRec1;
+
+      if (m_UsingPolyTree)
+        FixupFirstLefts3(outRec2, outRec1);
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset support functions ...
+//------------------------------------------------------------------------------
+
+DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
+  if (pt2.X == pt1.X && pt2.Y == pt1.Y)
+    return DoublePoint(0, 0);
+
+  double Dx = (double) (pt2.X - pt1.X);
+  double dy = (double) (pt2.Y - pt1.Y);
+  double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy);
+  Dx *= f;
+  dy *= f;
+  return DoublePoint(dy, -Dx);
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset class
+//------------------------------------------------------------------------------
+
+ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
+  this->MiterLimit = miterLimit;
+  this->ArcTolerance = arcTolerance;
+  m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+ClipperOffset::~ClipperOffset() {
+  Clear();
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Clear() {
+  for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+    delete m_polyNodes.Childs[i];
+  m_polyNodes.Childs.clear();
+  m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPath(const Path &path, JoinType joinType, EndType endType) {
+  int highI = (int) path.size() - 1;
+  if (highI < 0)
+    return;
+  PolyNode *newNode = new PolyNode();
+  newNode->m_jointype = joinType;
+  newNode->m_endtype = endType;
+
+  //strip duplicate points from path and also get index to the lowest point ...
+  if (endType == etClosedLine || endType == etClosedPolygon)
+    while (highI > 0 && path[0] == path[highI])
+      highI--;
+  newNode->Contour.reserve(highI + 1);
+  newNode->Contour.push_back(path[0]);
+  int j = 0, k = 0;
+  for (int i = 1; i <= highI; i++)
+    if (newNode->Contour[j] != path[i]) {
+      j++;
+      newNode->Contour.push_back(path[i]);
+      if (path[i].Y > newNode->Contour[k].Y ||
+          (path[i].Y == newNode->Contour[k].Y &&
+              path[i].X < newNode->Contour[k].X))
+        k = j;
+    }
+  if (endType == etClosedPolygon && j < 2) {
+    delete newNode;
+    return;
+  }
+  m_polyNodes.AddChild(*newNode);
+
+  //if this path's lowest pt is lower than all the others then update m_lowest
+  if (endType != etClosedPolygon)
+    return;
+  if (m_lowest.X < 0)
+    m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+  else {
+    IntPoint ip = m_polyNodes.Childs[(int) m_lowest.X]->Contour[(int) m_lowest.Y];
+    if (newNode->Contour[k].Y > ip.Y ||
+        (newNode->Contour[k].Y == ip.Y &&
+            newNode->Contour[k].X < ip.X))
+      m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType, EndType endType) {
+  for (Paths::size_type i = 0; i < paths.size(); ++i)
+    AddPath(paths[i], joinType, endType);
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::FixOrientations() {
+  //fixup orientations of all closed paths if the orientation of the
+  //closed path with the lowermost vertex is wrong ...
+  if (m_lowest.X >= 0 &&
+      !Orientation(m_polyNodes.Childs[(int) m_lowest.X]->Contour)) {
+    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedPolygon ||
+          (node.m_endtype == etClosedLine && Orientation(node.Contour)))
+        ReversePath(node.Contour);
+    }
+  } else {
+    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
+        ReversePath(node.Contour);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(Paths &solution, double delta) {
+  solution.clear();
+  FixOrientations();
+  DoOffset(delta);
+
+  //now clean up 'corners' ...
+  Clipper clpr;
+  clpr.AddPaths(m_destPolys, ptSubject, true);
+  if (delta > 0) {
+    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+  } else {
+    IntRect r = clpr.GetBounds();
+    Path outer(4);
+    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+    outer[2] = IntPoint(r.right + 10, r.top - 10);
+    outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+    clpr.AddPath(outer, ptSubject, true);
+    clpr.ReverseSolution(true);
+    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+    if (solution.size() > 0)
+      solution.erase(solution.begin());
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(PolyTree &solution, double delta) {
+  solution.Clear();
+  FixOrientations();
+  DoOffset(delta);
+
+  //now clean up 'corners' ...
+  Clipper clpr;
+  clpr.AddPaths(m_destPolys, ptSubject, true);
+  if (delta > 0) {
+    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+  } else {
+    IntRect r = clpr.GetBounds();
+    Path outer(4);
+    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+    outer[2] = IntPoint(r.right + 10, r.top - 10);
+    outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+    clpr.AddPath(outer, ptSubject, true);
+    clpr.ReverseSolution(true);
+    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+    //remove the outer PolyNode rectangle ...
+    if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
+      PolyNode *outerNode = solution.Childs[0];
+      solution.Childs.reserve(outerNode->ChildCount());
+      solution.Childs[0] = outerNode->Childs[0];
+      solution.Childs[0]->Parent = outerNode->Parent;
+      for (int i = 1; i < outerNode->ChildCount(); ++i)
+        solution.AddChild(*outerNode->Childs[i]);
+    } else
+      solution.Clear();
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoOffset(double delta) {
+  m_destPolys.clear();
+  m_delta = delta;
+
+  //if Zero offset, just copy any CLOSED polygons to m_p and return ...
+  if (NEAR_ZERO(delta)) {
+    m_destPolys.reserve(m_polyNodes.ChildCount());
+    for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedPolygon)
+        m_destPolys.push_back(node.Contour);
+    }
+    return;
+  }
+
+  //see offset_triginometry3.svg in the documentation folder ...
+  if (MiterLimit > 2)
+    m_miterLim = 2 / (MiterLimit * MiterLimit);
+  else
+    m_miterLim = 0.5;
+
+  double y;
+  if (ArcTolerance <= 0.0)
+    y = def_arc_tolerance;
+  else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
+    y = std::fabs(delta) * def_arc_tolerance;
+  else
+    y = ArcTolerance;
+  //see offset_triginometry2.svg in the documentation folder ...
+  double steps = pi / std::acos(1 - y / std::fabs(delta));
+  if (steps > std::fabs(delta) * pi)
+    steps = std::fabs(delta) * pi;  //ie excessive precision check
+  m_sin = std::sin(two_pi / steps);
+  m_cos = std::cos(two_pi / steps);
+  m_StepsPerRad = steps / two_pi;
+  if (delta < 0.0)
+    m_sin = -m_sin;
+
+  m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
+  for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+    PolyNode &node = *m_polyNodes.Childs[i];
+    m_srcPoly = node.Contour;
+
+    int len = (int) m_srcPoly.size();
+    if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
+      continue;
+
+    m_destPoly.clear();
+    if (len == 1) {
+      if (node.m_jointype == jtRound) {
+        double X = 1.0, Y = 0.0;
+        for (cInt j = 1; j <= steps; j++) {
+          m_destPoly.push_back(IntPoint(
+              Round(m_srcPoly[0].X + X * delta),
+              Round(m_srcPoly[0].Y + Y * delta)));
+          double X2 = X;
+          X = X * m_cos - m_sin * Y;
+          Y = X2 * m_sin + Y * m_cos;
+        }
+      } else {
+        double X = -1.0, Y = -1.0;
+        for (int j = 0; j < 4; ++j) {
+          m_destPoly.push_back(IntPoint(
+              Round(m_srcPoly[0].X + X * delta),
+              Round(m_srcPoly[0].Y + Y * delta)));
+          if (X < 0)
+            X = 1;
+          else if (Y < 0)
+            Y = 1;
+          else
+            X = -1;
+        }
+      }
+      m_destPolys.push_back(m_destPoly);
+      continue;
+    }
+    //build m_normals ...
+    m_normals.clear();
+    m_normals.reserve(len);
+    for (int j = 0; j < len - 1; ++j)
+      m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
+    if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
+      m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
+    else
+      m_normals.push_back(DoublePoint(m_normals[len - 2]));
+
+    if (node.m_endtype == etClosedPolygon) {
+      int k = len - 1;
+      for (int j = 0; j < len; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+    } else if (node.m_endtype == etClosedLine) {
+      int k = len - 1;
+      for (int j = 0; j < len; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+      m_destPoly.clear();
+      //re-build m_normals ...
+      DoublePoint n = m_normals[len - 1];
+      for (int j = len - 1; j > 0; j--)
+        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+      m_normals[0] = DoublePoint(-n.X, -n.Y);
+      k = 0;
+      for (int j = len - 1; j >= 0; j--)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+    } else {
+      int k = 0;
+      for (int j = 1; j < len - 1; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+
+      IntPoint pt1;
+      if (node.m_endtype == etOpenButt) {
+        int j = len - 1;
+        pt1 = IntPoint((cInt) Round(m_srcPoly[j].X + m_normals[j].X *
+            delta), (cInt) Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
+        m_destPoly.push_back(pt1);
+        pt1 = IntPoint((cInt) Round(m_srcPoly[j].X - m_normals[j].X *
+            delta), (cInt) Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
+        m_destPoly.push_back(pt1);
+      } else {
+        int j = len - 1;
+        k = len - 2;
+        m_sinA = 0;
+        m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
+        if (node.m_endtype == etOpenSquare)
+          DoSquare(j, k);
+        else
+          DoRound(j, k);
+      }
+
+      //re-build m_normals ...
+      for (int j = len - 1; j > 0; j--)
+        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+      m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
+
+      k = len - 1;
+      for (int j = k - 1; j > 0; --j)
+        OffsetPoint(j, k, node.m_jointype);
+
+      if (node.m_endtype == etOpenButt) {
+        pt1 = IntPoint((cInt) Round(m_srcPoly[0].X - m_normals[0].X * delta),
+                       (cInt) Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
+        m_destPoly.push_back(pt1);
+        pt1 = IntPoint((cInt) Round(m_srcPoly[0].X + m_normals[0].X * delta),
+                       (cInt) Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
+        m_destPoly.push_back(pt1);
+      } else {
+        k = 1;
+        m_sinA = 0;
+        if (node.m_endtype == etOpenSquare)
+          DoSquare(0, 1);
+        else
+          DoRound(0, 1);
+      }
+      m_destPolys.push_back(m_destPoly);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) {
+  //cross product ...
+  m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
+  if (std::fabs(m_sinA * m_delta) < 1.0) {
+    //dot product ...
+    double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
+    if (cosA > 0) // angle => 0 degrees
+    {
+      m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+                                    Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+      return;
+    }
+    //else angle => 180 degrees
+  } else if (m_sinA > 1.0)
+    m_sinA = 1.0;
+  else if (m_sinA < -1.0)
+    m_sinA = -1.0;
+
+  if (m_sinA * m_delta < 0) {
+    m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+                                  Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+    m_destPoly.push_back(m_srcPoly[j]);
+    m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+                                  Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+  } else
+    switch (jointype) {
+    case jtMiter: {
+      double r = 1 + (m_normals[j].X * m_normals[k].X +
+          m_normals[j].Y * m_normals[k].Y);
+      if (r >= m_miterLim)
+        DoMiter(j, k, r);
+      else
+        DoSquare(j, k);
+      break;
+    }
+    case jtSquare: DoSquare(j, k);
+      break;
+    case jtRound: DoRound(j, k);
+      break;
+    }
+  k = j;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoSquare(int j, int k) {
+  double dx = std::tan(std::atan2(m_sinA,
+                                  m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4);
+  m_destPoly.push_back(IntPoint(
+      Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
+      Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
+  m_destPoly.push_back(IntPoint(
+      Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
+      Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoMiter(int j, int k, double r) {
+  double q = m_delta / r;
+  m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
+                                Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoRound(int j, int k) {
+  double a = std::atan2(m_sinA,
+                        m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y);
+  int steps = std::max((int) Round(m_StepsPerRad * std::fabs(a)), 1);
+
+  double X = m_normals[k].X, Y = m_normals[k].Y, X2;
+  for (int i = 0; i < steps; ++i) {
+    m_destPoly.push_back(IntPoint(
+        Round(m_srcPoly[j].X + X * m_delta),
+        Round(m_srcPoly[j].Y + Y * m_delta)));
+    X2 = X;
+    X = X * m_cos - m_sin * Y;
+    Y = X2 * m_sin + Y * m_cos;
+  }
+  m_destPoly.push_back(IntPoint(
+      Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+      Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+}
+
+//------------------------------------------------------------------------------
+// Miscellaneous public functions
+//------------------------------------------------------------------------------
+
+void Clipper::DoSimplePolygons() {
+  PolyOutList::size_type i = 0;
+  while (i < m_PolyOuts.size()) {
+    OutRec *outrec = m_PolyOuts[i++];
+    OutPt *op = outrec->Pts;
+    if (!op || outrec->IsOpen)
+      continue;
+    do //for each Pt in Polygon until duplicate found do ...
+    {
+      OutPt *op2 = op->Next;
+      while (op2 != outrec->Pts) {
+        if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
+          //split the polygon into two ...
+          OutPt *op3 = op->Prev;
+          OutPt *op4 = op2->Prev;
+          op->Prev = op4;
+          op4->Next = op;
+          op2->Prev = op3;
+          op3->Next = op2;
+
+          outrec->Pts = op;
+          OutRec *outrec2 = CreateOutRec();
+          outrec2->Pts = op2;
+          UpdateOutPtIdxs(*outrec2);
+          if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
+            //OutRec2 is contained by OutRec1 ...
+            outrec2->IsHole = !outrec->IsHole;
+            outrec2->FirstLeft = outrec;
+            if (m_UsingPolyTree)
+              FixupFirstLefts2(outrec2, outrec);
+          } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
+            //OutRec1 is contained by OutRec2 ...
+            outrec2->IsHole = outrec->IsHole;
+            outrec->IsHole = !outrec2->IsHole;
+            outrec2->FirstLeft = outrec->FirstLeft;
+            outrec->FirstLeft = outrec2;
+            if (m_UsingPolyTree)
+              FixupFirstLefts2(outrec, outrec2);
+          } else {
+            //the 2 polygons are separate ...
+            outrec2->IsHole = outrec->IsHole;
+            outrec2->FirstLeft = outrec->FirstLeft;
+            if (m_UsingPolyTree)
+              FixupFirstLefts1(outrec, outrec2);
+          }
+          op2 = op; //ie get ready for the Next iteration
+        }
+        op2 = op2->Next;
+      }
+      op = op->Next;
+    } while (op != outrec->Pts);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ReversePath(Path &p) {
+  std::reverse(p.begin(), p.end());
+}
+//------------------------------------------------------------------------------
+
+void ReversePaths(Paths &p) {
+  for (Paths::size_type i = 0; i < p.size(); ++i)
+    ReversePath(p[i]);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) {
+  Clipper c;
+  c.StrictlySimple(true);
+  c.AddPath(in_poly, ptSubject, true);
+  c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) {
+  Clipper c;
+  c.StrictlySimple(true);
+  c.AddPaths(in_polys, ptSubject, true);
+  c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(Paths &polys, PolyFillType fillType) {
+  SimplifyPolygons(polys, polys, fillType);
+}
+//------------------------------------------------------------------------------
+
+inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) {
+  double Dx = ((double) pt1.X - pt2.X);
+  double dy = ((double) pt1.Y - pt2.Y);
+  return (Dx * Dx + dy * dy);
+}
+//------------------------------------------------------------------------------
+
+double DistanceFromLineSqrd(
+    const IntPoint &pt, const IntPoint &ln1, const IntPoint &ln2) {
+  //The equation of a line in general form (Ax + By + C = 0)
+  //given 2 points (x�,y�) & (x�,y�) is ...
+  //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
+  //A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
+  //perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
+  //see http://en.wikipedia.org/wiki/Perpendicular_distance
+  double A = double(ln1.Y - ln2.Y);
+  double B = double(ln2.X - ln1.X);
+  double C = A * ln1.X + B * ln1.Y;
+  C = A * pt.X + B * pt.Y - C;
+  return (C * C) / (A * A + B * B);
+}
+//---------------------------------------------------------------------------
+
+bool SlopesNearCollinear(const IntPoint &pt1,
+                         const IntPoint &pt2, const IntPoint &pt3, double distSqrd) {
+  //this function is more accurate when the point that's geometrically
+  //between the other 2 points is the one that's tested for distance.
+  //ie makes it more likely to pick up 'spikes' ...
+  if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
+    if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
+      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+    else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
+      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+    else
+      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+  } else {
+    if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
+      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+    else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
+      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+    else
+      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) {
+  double Dx = (double) pt1.X - pt2.X;
+  double dy = (double) pt1.Y - pt2.Y;
+  return ((Dx * Dx) + (dy * dy) <= distSqrd);
+}
+//------------------------------------------------------------------------------
+
+OutPt *ExcludeOp(OutPt *op) {
+  OutPt *result = op->Prev;
+  result->Next = op->Next;
+  op->Next->Prev = result;
+  result->Idx = 0;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) {
+  //distance = proximity in units/pixels below which vertices
+  //will be stripped. Default ~= sqrt(2).
+
+  size_t size = in_poly.size();
+
+  if (size == 0) {
+    out_poly.clear();
+    return;
+  }
+
+  OutPt *outPts = new OutPt[size];
+  for (size_t i = 0; i < size; ++i) {
+    outPts[i].Pt = in_poly[i];
+    outPts[i].Next = &outPts[(i + 1) % size];
+    outPts[i].Next->Prev = &outPts[i];
+    outPts[i].Idx = 0;
+  }
+
+  double distSqrd = distance * distance;
+  OutPt *op = &outPts[0];
+  while (op->Idx == 0 && op->Next != op->Prev) {
+    if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
+      op = ExcludeOp(op);
+      size--;
+    } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
+      ExcludeOp(op->Next);
+      op = ExcludeOp(op);
+      size -= 2;
+    } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) {
+      op = ExcludeOp(op);
+      size--;
+    } else {
+      op->Idx = 1;
+      op = op->Next;
+    }
+  }
+
+  if (size < 3)
+    size = 0;
+  out_poly.resize(size);
+  for (size_t i = 0; i < size; ++i) {
+    out_poly[i] = op->Pt;
+    op = op->Next;
+  }
+  delete[] outPts;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(Path &poly, double distance) {
+  CleanPolygon(poly, poly, distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) {
+  out_polys.resize(in_polys.size());
+  for (Paths::size_type i = 0; i < in_polys.size(); ++i)
+    CleanPolygon(in_polys[i], out_polys[i], distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(Paths &polys, double distance) {
+  CleanPolygons(polys, polys, distance);
+}
+//------------------------------------------------------------------------------
+
+void Minkowski(const Path &poly, const Path &path,
+               Paths &solution, bool isSum, bool isClosed) {
+  int delta = (isClosed ? 1 : 0);
+  size_t polyCnt = poly.size();
+  size_t pathCnt = path.size();
+  Paths pp;
+  pp.reserve(pathCnt);
+  if (isSum)
+    for (size_t i = 0; i < pathCnt; ++i) {
+      Path p;
+      p.reserve(polyCnt);
+      for (size_t j = 0; j < poly.size(); ++j)
+        p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
+      pp.push_back(p);
+    }
+  else
+    for (size_t i = 0; i < pathCnt; ++i) {
+      Path p;
+      p.reserve(polyCnt);
+      for (size_t j = 0; j < poly.size(); ++j)
+        p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
+      pp.push_back(p);
+    }
+
+  solution.clear();
+  solution.reserve((pathCnt + delta) * (polyCnt + 1));
+  for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
+    for (size_t j = 0; j < polyCnt; ++j) {
+      Path quad;
+      quad.reserve(4);
+      quad.push_back(pp[i % pathCnt][j % polyCnt]);
+      quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
+      quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
+      quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
+      if (!Orientation(quad))
+        ReversePath(quad);
+      solution.push_back(quad);
+    }
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, bool pathIsClosed) {
+  Minkowski(pattern, path, solution, true, pathIsClosed);
+  Clipper c;
+  c.AddPaths(solution, ptSubject, true);
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void TranslatePath(const Path &input, Path &output, const IntPoint delta) {
+  //precondition: input != output
+  output.resize(input.size());
+  for (size_t i = 0; i < input.size(); ++i)
+    output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, bool pathIsClosed) {
+  Clipper c;
+  for (size_t i = 0; i < paths.size(); ++i) {
+    Paths tmp;
+    Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
+    c.AddPaths(tmp, ptSubject, true);
+    if (pathIsClosed) {
+      Path tmp2;
+      TranslatePath(paths[i], tmp2, pattern[0]);
+      c.AddPath(tmp2, ptClip, true);
+    }
+  }
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) {
+  Minkowski(poly1, poly2, solution, false, true);
+  Clipper c;
+  c.AddPaths(solution, ptSubject, true);
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+enum NodeType { ntAny, ntOpen, ntClosed };
+
+void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype, Paths &paths) {
+  bool match = true;
+  if (nodetype == ntClosed)
+    match = !polynode.IsOpen();
+  else if (nodetype == ntOpen)
+    return;
+
+  if (!polynode.Contour.empty() && match)
+    paths.push_back(polynode.Contour);
+  for (int i = 0; i < polynode.ChildCount(); ++i)
+    AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
+}
+//------------------------------------------------------------------------------
+
+void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  AddPolyNodeToPaths(polytree, ntAny, paths);
+}
+//------------------------------------------------------------------------------
+
+void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  AddPolyNodeToPaths(polytree, ntClosed, paths);
+}
+//------------------------------------------------------------------------------
+
+void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  //Open paths are top level only, so ...
+  for (int i = 0; i < polytree.ChildCount(); ++i)
+    if (polytree.Childs[i]->IsOpen())
+      paths.push_back(polytree.Childs[i]->Contour);
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const IntPoint &p) {
+  s << "(" << p.X << "," << p.Y << ")";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const Path &p) {
+  if (p.empty())
+    return s;
+  Path::size_type last = p.size() - 1;
+  for (Path::size_type i = 0; i < last; i++)
+    s << "(" << p[i].X << "," << p[i].Y << "), ";
+  s << "(" << p[last].X << "," << p[last].Y << ")\n";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const Paths &p) {
+  for (Paths::size_type i = 0; i < p.size(); i++)
+    s << p[i];
+  s << "\n";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
diff --git a/engines/twp/clipper/clipper.hpp b/engines/twp/clipper/clipper.hpp
new file mode 100644
index 00000000000..c036c130f9f
--- /dev/null
+++ b/engines/twp/clipper/clipper.hpp
@@ -0,0 +1,403 @@
+/*******************************************************************************
+*                                                                              *
+* Author    :  Angus Johnson                                                   *
+* Version   :  6.4.2                                                           *
+* Date      :  27 February 2017                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2017                                         *
+*                                                                              *
+* License:                                                                     *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt                                         *
+*                                                                              *
+* Attributions:                                                                *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping"                                     *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
+* http://portal.acm.org/citation.cfm?id=129906                                 *
+*                                                                              *
+* Computer graphics and geometric modeling: implementation and algorithms      *
+* By Max K. Agoston                                                            *
+* Springer; 1 edition (January 4, 2005)                                        *
+* http://books.google.com/books?q=vatti+clipping+agoston                       *
+*                                                                              *
+* See also:                                                                    *
+* "Polygon Offsetting by Computing Winding Numbers"                            *
+* Paper no. DETC2005-85513 pp. 565-575                                         *
+* ASME 2005 International Design Engineering Technical Conferences             *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
+* September 24-28, 2005 , Long Beach, California, USA                          *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
+*                                                                              *
+*******************************************************************************/
+
+#ifndef clipper_hpp
+#define clipper_hpp
+
+#define CLIPPER_VERSION "6.4.2"
+
+//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
+//improve performance but coordinate values are limited to the range +/- 46340
+//#define use_int32
+
+//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
+//#define use_xyz
+
+//use_lines: Enables line clipping. Adds a very minor cost to performance.
+#define use_lines
+
+//use_deprecated: Enables temporary support for the obsolete functions
+//#define use_deprecated  
+
+#include <vector>
+#include <list>
+#include <set>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+#include <functional>
+#include <queue>
+
+namespace ClipperLib {
+
+enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
+enum PolyType { ptSubject, ptClip };
+//By far the most widely used winding rules for polygon filling are
+//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
+//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
+//see http://glprogramming.com/red/chapter11.html
+enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
+
+#ifdef use_int32
+typedef int cInt;
+static cInt const loRange = 0x7FFF;
+static cInt const hiRange = 0x7FFF;
+#else
+typedef signed long long cInt;
+static cInt const loRange = 0x3FFFFFFF;
+static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
+typedef signed long long long64;     //used by Int128 class
+typedef unsigned long long ulong64;
+
+#endif
+
+struct IntPoint {
+  cInt X;
+  cInt Y;
+#ifdef use_xyz
+  cInt Z;
+  IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
+#else
+  IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y) {};
+#endif
+
+  friend inline bool operator==(const IntPoint &a, const IntPoint &b) {
+    return a.X == b.X && a.Y == b.Y;
+  }
+  friend inline bool operator!=(const IntPoint &a, const IntPoint &b) {
+    return a.X != b.X || a.Y != b.Y;
+  }
+};
+//------------------------------------------------------------------------------
+
+typedef std::vector<IntPoint> Path;
+typedef std::vector<Path> Paths;
+
+inline Path &operator<<(Path &poly, const IntPoint &p) {
+  poly.push_back(p);
+  return poly;
+}
+inline Paths &operator<<(Paths &polys, const Path &p) {
+  polys.push_back(p);
+  return polys;
+}
+
+std::ostream &operator<<(std::ostream &s, const IntPoint &p);
+std::ostream &operator<<(std::ostream &s, const Path &p);
+std::ostream &operator<<(std::ostream &s, const Paths &p);
+
+struct DoublePoint {
+  double X;
+  double Y;
+  DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
+  DoublePoint(IntPoint ip) : X((double) ip.X), Y((double) ip.Y) {}
+};
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
+#endif
+
+enum InitOptions { ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4 };
+enum JoinType { jtSquare, jtRound, jtMiter };
+enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound };
+
+class PolyNode;
+typedef std::vector<PolyNode *> PolyNodes;
+
+class PolyNode {
+public:
+  PolyNode();
+  virtual ~PolyNode() {};
+  Path Contour;
+  PolyNodes Childs;
+  PolyNode *Parent;
+  PolyNode *GetNext() const;
+  bool IsHole() const;
+  bool IsOpen() const;
+  int ChildCount() const;
+private:
+  //PolyNode& operator =(PolyNode& other);
+  unsigned Index; //node index in Parent.Childs
+  bool m_IsOpen;
+  JoinType m_jointype;
+  EndType m_endtype;
+  PolyNode *GetNextSiblingUp() const;
+  void AddChild(PolyNode &child);
+  friend class Clipper; //to access Index
+  friend class ClipperOffset;
+};
+
+class PolyTree : public PolyNode {
+public:
+  ~PolyTree() { Clear(); };
+  PolyNode *GetFirst() const;
+  void Clear();
+  int Total() const;
+private:
+  //PolyTree& operator =(PolyTree& other);
+  PolyNodes AllNodes;
+  friend class Clipper; //to access AllNodes
+};
+
+bool Orientation(const Path &poly);
+double Area(const Path &poly);
+int PointInPolygon(const IntPoint &pt, const Path &path);
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
+
+void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415);
+void CleanPolygon(Path &poly, double distance = 1.415);
+void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance = 1.415);
+void CleanPolygons(Paths &polys, double distance = 1.415);
+
+void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, bool pathIsClosed);
+void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, bool pathIsClosed);
+void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution);
+
+void PolyTreeToPaths(const PolyTree &polytree, Paths &paths);
+void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths);
+void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths);
+
+void ReversePath(Path &p);
+void ReversePaths(Paths &p);
+
+struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
+
+//enums that are used internally ...
+enum EdgeSide { esLeft = 1, esRight = 2 };
+
+//forward declarations (for stuff used internally) ...
+struct TEdge;
+struct IntersectNode;
+struct LocalMinimum;
+struct OutPt;
+struct OutRec;
+struct Join;
+
+typedef std::vector<OutRec *> PolyOutList;
+typedef std::vector<TEdge *> EdgeList;
+typedef std::vector<Join *> JoinList;
+typedef std::vector<IntersectNode *> IntersectList;
+
+//------------------------------------------------------------------------------
+
+//ClipperBase is the ancestor to the Clipper class. It should not be
+//instantiated directly. This class simply abstracts the conversion of sets of
+//polygon coordinates into edge objects that are stored in a LocalMinima list.
+class ClipperBase {
+public:
+  ClipperBase();
+  virtual ~ClipperBase();
+  virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
+  bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
+  virtual void Clear();
+  IntRect GetBounds();
+  bool PreserveCollinear() { return m_PreserveCollinear; };
+  void PreserveCollinear(bool value) { m_PreserveCollinear = value; };
+protected:
+  void DisposeLocalMinimaList();
+  TEdge *AddBoundsToLML(TEdge *e, bool IsClosed);
+  virtual void Reset();
+  TEdge *ProcessBound(TEdge *E, bool IsClockwise);
+  void InsertScanbeam(const cInt Y);
+  bool PopScanbeam(cInt &Y);
+  bool LocalMinimaPending();
+  bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
+  OutRec *CreateOutRec();
+  void DisposeAllOutRecs();
+  void DisposeOutRec(PolyOutList::size_type index);
+  void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
+  void DeleteFromAEL(TEdge *e);
+  void UpdateEdgeIntoAEL(TEdge *&e);
+
+  typedef std::vector<LocalMinimum> MinimaList;
+  MinimaList::iterator m_CurrentLM;
+  MinimaList m_MinimaList;
+
+  bool m_UseFullRange;
+  EdgeList m_edges;
+  bool m_PreserveCollinear;
+  bool m_HasOpenPaths;
+  PolyOutList m_PolyOuts;
+  TEdge *m_ActiveEdges;
+
+  typedef std::priority_queue<cInt> ScanbeamList;
+  ScanbeamList m_Scanbeam;
+};
+//------------------------------------------------------------------------------
+
+class Clipper : public virtual ClipperBase {
+public:
+  Clipper(int initOptions = 0);
+  bool Execute(ClipType clipType,
+               Paths &solution,
+               PolyFillType fillType = pftEvenOdd);
+  bool Execute(ClipType clipType,
+               Paths &solution,
+               PolyFillType subjFillType,
+               PolyFillType clipFillType);
+  bool Execute(ClipType clipType,
+               PolyTree &polytree,
+               PolyFillType fillType = pftEvenOdd);
+  bool Execute(ClipType clipType,
+               PolyTree &polytree,
+               PolyFillType subjFillType,
+               PolyFillType clipFillType);
+  bool ReverseSolution() { return m_ReverseOutput; };
+  void ReverseSolution(bool value) { m_ReverseOutput = value; };
+  bool StrictlySimple() { return m_StrictSimple; };
+  void StrictlySimple(bool value) { m_StrictSimple = value; };
+  //set the callback function for z value filling on intersections (otherwise Z is 0)
+#ifdef use_xyz
+  void ZFillFunction(ZFillCallback zFillFunc);
+#endif
+protected:
+  virtual bool ExecuteInternal();
+private:
+  JoinList m_Joins;
+  JoinList m_GhostJoins;
+  IntersectList m_IntersectList;
+  ClipType m_ClipType;
+  typedef std::list<cInt> MaximaList;
+  MaximaList m_Maxima;
+  TEdge *m_SortedEdges;
+  bool m_ExecuteLocked;
+  PolyFillType m_ClipFillType;
+  PolyFillType m_SubjFillType;
+  bool m_ReverseOutput;
+  bool m_UsingPolyTree;
+  bool m_StrictSimple;
+#ifdef use_xyz
+  ZFillCallback   m_ZFill; //custom callback 
+#endif
+  void SetWindingCount(TEdge &edge);
+  bool IsEvenOddFillType(const TEdge &edge) const;
+  bool IsEvenOddAltFillType(const TEdge &edge) const;
+  void InsertLocalMinimaIntoAEL(const cInt botY);
+  void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge);
+  void AddEdgeToSEL(TEdge *edge);
+  bool PopEdgeFromSEL(TEdge *&edge);
+  void CopyAELToSEL();
+  void DeleteFromSEL(TEdge *e);
+  void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
+  bool IsContributing(const TEdge &edge) const;
+  bool IsTopHorz(const cInt XPos);
+  void DoMaxima(TEdge *e);
+  void ProcessHorizontals();
+  void ProcessHorizontal(TEdge *horzEdge);
+  void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+  OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+  OutRec *GetOutRec(int idx);
+  void AppendPolygon(TEdge *e1, TEdge *e2);
+  void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
+  OutPt *AddOutPt(TEdge *e, const IntPoint &pt);
+  OutPt *GetLastOutPt(TEdge *e);
+  bool ProcessIntersections(const cInt topY);
+  void BuildIntersectList(const cInt topY);
+  void ProcessIntersectList();
+  void ProcessEdgesAtTopOfScanbeam(const cInt topY);
+  void BuildResult(Paths &polys);
+  void BuildResult2(PolyTree &polytree);
+  void SetHoleState(TEdge *e, OutRec *outrec);
+  void DisposeIntersectNodes();
+  bool FixupIntersectionOrder();
+  void FixupOutPolygon(OutRec &outrec);
+  void FixupOutPolyline(OutRec &outrec);
+  bool IsHole(TEdge *e);
+  bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
+  void FixHoleLinkage(OutRec &outrec);
+  void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
+  void ClearJoins();
+  void ClearGhostJoins();
+  void AddGhostJoin(OutPt *op, const IntPoint offPt);
+  bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2);
+  void JoinCommonEdges();
+  void DoSimplePolygons();
+  void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec);
+  void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec);
+  void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec);
+#ifdef use_xyz
+  void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
+#endif
+};
+//------------------------------------------------------------------------------
+
+class ClipperOffset {
+public:
+  ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
+  ~ClipperOffset();
+  void AddPath(const Path &path, JoinType joinType, EndType endType);
+  void AddPaths(const Paths &paths, JoinType joinType, EndType endType);
+  void Execute(Paths &solution, double delta);
+  void Execute(PolyTree &solution, double delta);
+  void Clear();
+  double MiterLimit;
+  double ArcTolerance;
+private:
+  Paths m_destPolys;
+  Path m_srcPoly;
+  Path m_destPoly;
+  std::vector<DoublePoint> m_normals;
+  double m_delta, m_sinA, m_sin, m_cos;
+  double m_miterLim, m_StepsPerRad;
+  IntPoint m_lowest;
+  PolyNode m_polyNodes;
+
+  void FixOrientations();
+  void DoOffset(double delta);
+  void OffsetPoint(int j, int &k, JoinType jointype);
+  void DoSquare(int j, int k);
+  void DoMiter(int j, int k, double r);
+  void DoRound(int j, int k);
+};
+//------------------------------------------------------------------------------
+
+class clipperException : public std::exception {
+public:
+  clipperException(const char *description) : m_descr(description) {}
+  virtual ~clipperException() throw() {}
+  virtual const char *what() const throw() { return m_descr.c_str(); }
+private:
+  std::string m_descr;
+};
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
+
+#endif //clipper_hpp
+
+
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index d4550a07403..d64a139de08 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -250,6 +250,11 @@ void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
 	drawPrimitives(GL_LINE_STRIP, vertices, count, trsf);
 }
 
+void Gfx::drawLinesLoop(Vertex *vertices, int count, Math::Matrix4 trsf) {
+	noTexture();
+	drawPrimitives(GL_LINE_LOOP, vertices, count, trsf);
+}
+
 void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture *texture) {
 	if (v_size > 0) {
 		_texture = texture ? texture : &gEmptyTexture;
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index f2365752e51..6c4c7e1d4fa 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -162,6 +162,7 @@ public:
 	void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
 	void drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 transf = Math::Matrix4(), Texture *texture = NULL);
 	void drawLines(Vertex *vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
+	void drawLinesLoop(Vertex *vertices, int count, Math::Matrix4 trsf = Math::Matrix4());
 	void draw(Vertex *vertices, int v_size, uint32 *indices, int i_size, Math::Matrix4 trsf = Math::Matrix4(), Texture *texture = NULL);
 	void drawQuad(Math::Vector2d size, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4());
 	void drawSprite(Common::Rect textRect, Texture &texture, Color color = Color(), Math::Matrix4 trsf = Math::Matrix4(), bool flipX = false, bool flipY = false);
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
new file mode 100644
index 00000000000..9a064a202f8
--- /dev/null
+++ b/engines/twp/graph.cpp
@@ -0,0 +1,504 @@
+/* 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 "twp/graph.h"
+#include "twp/util.h"
+
+#define EPSILON 1e-9
+
+namespace Twp {
+
+struct Segment {
+	Segment(Math::Vector2d s, Math::Vector2d t);
+	void normalize();
+	float distance(Math::Vector2d p);
+
+	Math::Vector2d start, to;
+	float left, right, top, bottom;
+	float a, b, c;
+};
+
+Segment::Segment(Math::Vector2d s, Math::Vector2d t) {
+	start = s;
+	to = t;
+	left = MIN(s.getX(), t.getX());
+	right = MAX(s.getX(), t.getX());
+	top = MIN(s.getY(), t.getY());
+	bottom = MAX(s.getY(), t.getY());
+	a = s.getY() - t.getY();
+	b = t.getX() - s.getX();
+	c = -a * s.getX() - b * s.getY();
+	normalize();
+}
+
+void Segment::normalize() {
+	float z = sqrt(a * a + b * b);
+	if (abs(z) > EPSILON) {
+		a /= z;
+		b /= z;
+		c /= z;
+	}
+}
+
+float Segment::distance(Math::Vector2d p) {
+	return a * p.getX() + b * p.getY() + c;
+}
+
+IndexedPriorityQueue::IndexedPriorityQueue(Common::Array<float> &keys)
+	: _keys(keys) {
+}
+
+void IndexedPriorityQueue::insert(int index) {
+	_data.push_back(index);
+	reorderUp();
+}
+
+int IndexedPriorityQueue::pop() {
+	int r = _data[0];
+	_data[0] = _data[_data.size() - 1];
+	_data.pop_back();
+	reorderDown();
+	return r;
+}
+
+void IndexedPriorityQueue::reorderUp() {
+	if (_data.empty())
+		return;
+	size_t a = _data.size() - 1;
+	while (a > 0) {
+		if (_keys[_data[a]] >= _keys[_data[a - 1]])
+			return;
+		int tmp = _data[a];
+		_data[a] = _data[a - 1];
+		_data[a - 1] = tmp;
+		a--;
+	}
+}
+
+void IndexedPriorityQueue::reorderDown() {
+	if (_data.empty())
+		return;
+	for (int a = 0; a < static_cast<int>(_data.size() - 1); a++) {
+		if (_keys[_data[a]] <= _keys[_data[a + 1]])
+			return;
+		int tmp = _data[a];
+		_data[a] = _data[a + 1];
+		_data[a + 1] = tmp;
+	}
+}
+
+bool IndexedPriorityQueue::isEmpty() {
+	return _data.empty();
+}
+
+Graph::Graph() {}
+
+Graph::Graph(const Graph &graph) {
+	_nodes = graph._nodes;
+	_concaveVertices = graph._concaveVertices;
+	for (int i = 0; i < graph._edges.size(); i++) {
+		const Common::Array<GraphEdge> &e = graph._edges[i];
+		Common::Array<GraphEdge> sEdges;
+		for (int j = 0; j < e.size(); j++) {
+			const GraphEdge &se = e[j];
+			sEdges.push_back(GraphEdge(se.start, se.to, se.cost));
+		}
+		_edges.push_back(sEdges);
+	}
+}
+
+GraphEdge::GraphEdge(int s, int t, float c)
+	: start(s), to(t), cost(c) {
+}
+
+void Graph::addNode(Math::Vector2d node) {
+	_nodes.push_back(node);
+	_edges.push_back(Common::Array<GraphEdge>());
+}
+
+AStar::AStar(Graph *graph)
+	: _fCost(graph->_nodes.size()), _gCost(graph->_nodes.size()), _spt(graph->_nodes.size()), _sf(graph->_nodes.size()) {
+	_graph = graph;
+}
+
+// TODO this really should have some simd optimization
+// matrix multiplication is based on this
+static float dot(Math::Vector2d u, Math::Vector2d v) {
+	float result = 0.f;
+	result += u.getX() * v.getX();
+	result += u.getY() * v.getY();
+	return result;
+}
+
+static float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
+
+void AStar::search(int source, int target) {
+	IndexedPriorityQueue pq(_fCost);
+	pq.insert(source);
+	while (!pq.isEmpty()) {
+		int NCN = pq.pop();
+		_spt[NCN] = _sf[NCN];
+		if (NCN != target) {
+			// for (edge in _graph->edges[NCN]) {
+			for (int i = 0; i < _graph->_edges[NCN].size(); i++) {
+				GraphEdge &edge = _graph->_edges[NCN][i];
+				float Hcost = length(_graph->_nodes[edge.to] - _graph->_nodes[target]);
+				float Gcost = _gCost[NCN] + edge.cost;
+				if (!_sf[edge.to]) {
+					_fCost[edge.to] = Gcost + Hcost;
+					_gCost[edge.to] = Gcost;
+					pq.insert(edge.to);
+					_sf[edge.to] = &edge;
+				} else if (Gcost < _gCost[edge.to] && !_spt[edge.to]) {
+					_fCost[edge.to] = Gcost + Hcost;
+					_gCost[edge.to] = Gcost;
+					pq.reorderUp();
+					_sf[edge.to] = &edge;
+				}
+			}
+		}
+	}
+}
+
+void Graph::addEdge(GraphEdge e) {
+	if (!edge(e.start, e.to)) {
+		_edges[e.start].push_back(e);
+	}
+	if (!edge(e.to, e.start)) {
+		GraphEdge e2(e.to, e.start, e.cost);
+		_edges[e.to].push_back(e);
+	}
+}
+
+GraphEdge *Graph::edge(int start, int to) {
+	Common::Array<GraphEdge> &edges = _edges[start];
+	for (int i = 0; i < edges.size(); i++) {
+		GraphEdge *e = &edges[i];
+		if (e->to == to)
+			return e;
+	}
+	return nullptr;
+}
+
+Common::Array<int> reverse(const Common::Array<int> &arr) {
+	Common::Array<int> result(arr.size());
+	for (int i = 0; i < arr.size(); i++) {
+		result[arr.size() - 1 - i] = arr[i];
+	}
+	return result;
+}
+
+Common::Array<int> Graph::getPath(int source, int target) {
+	Common::Array<int> result;
+	AStar astar(this);
+	if (target >= 0) {
+		astar.search(source, target);
+		int nd = target;
+		result.push_back(nd);
+		while ((nd != source) && (astar._spt[nd] != nullptr)) {
+			nd = astar._spt[nd]->start;
+			result.push_back(nd);
+		}
+		return reverse(result);
+	}
+	return result;
+}
+
+void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
+	_walkboxes = walkboxes;
+}
+
+// Indicates whether or not the specified position is inside this walkbox.
+static bool inside(const Walkbox &self, Math::Vector2d position, bool toleranceOnOutside = true) {
+	bool result = false;
+	Math::Vector2d point = position;
+	const float epsilon = 1.0f;
+
+	// Must have 3 or more edges
+	const Common::Array<Math::Vector2d> &polygon = self.getPoints();
+	if (polygon.size() < 3)
+		return false;
+
+	Math::Vector2d oldPoint(polygon[polygon.size() - 1]);
+	float oldSqDist = distanceSquared(oldPoint, point);
+
+	for (int i = 0; i < polygon.size(); i++) {
+		Math::Vector2d newPoint = polygon[i];
+		float newSqDist = distanceSquared(newPoint, point);
+
+		if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
+			return toleranceOnOutside;
+
+		Math::Vector2d left;
+		Math::Vector2d right;
+		if (newPoint.getX() > oldPoint.getX()) {
+			left = oldPoint;
+			right = newPoint;
+		} else {
+			left = newPoint;
+			right = oldPoint;
+		}
+
+		if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX()) < (right.getY() - left.getY()) * (point.getX() - left.getX())))
+			result = !result;
+
+		oldPoint = newPoint;
+		oldSqDist = newSqDist;
+	}
+	return result;
+}
+
+Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
+	int vi1 = -1;
+	int vi2 = -1;
+	float minDist = 100000.0f;
+
+	const Common::Array<Math::Vector2d> &polygon = getPoints();
+	for (int i = 0; i < polygon.size(); i++) {
+		float dist = distanceToSegment(p3, polygon[i], polygon[(i + 1) % polygon.size()]);
+		if (dist < minDist) {
+			minDist = dist;
+			vi1 = i;
+			vi2 = (i + 1) % polygon.size();
+		}
+	}
+
+	Math::Vector2d p1 = polygon[vi1];
+	Math::Vector2d p2 = polygon[vi2];
+
+	float x1 = p1.getX();
+	float y1 = p1.getY();
+	float x2 = p2.getX();
+	float y2 = p2.getY();
+	float x3 = p3.getX();
+	float y3 = p3.getY();
+
+	float u = (((x3 - x1) * (x2 - x1)) + ((y3 - y1) * (y2 - y1))) / (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
+
+	float xu = x1 + u * (x2 - x1);
+	float yu = y1 + u * (y2 - y1);
+
+	if (u < 0)
+		return Math::Vector2d(x1, y1);
+	if (u > 1)
+		return Math::Vector2d(x2, y2);
+	return Math::Vector2d(xu, yu);
+}
+
+static bool less(Math::Vector2d p1, Math::Vector2d p2) {
+	return ((p1.getX() < p2.getX() - EPSILON) || (abs(p1.getX() - p2.getX()) < EPSILON) && (p1.getY() < p2.getY() - EPSILON));
+}
+
+static float det(float a, float b, float c, float d) {
+	return a * d - b * c;
+}
+
+static bool betw(float l, float r, float x) {
+	return (MIN(l, r) <= x + EPSILON) && (x <= MAX(l, r) + EPSILON);
+}
+
+static bool intersect_1d(float a, float b, float c, float d) {
+	float a2 = a;
+	float b2 = b;
+	float c2 = c;
+	float d2 = d;
+	if (a2 > b2)
+		SWAP(a2, b2);
+	if (c2 > d2)
+		SWAP(c2, d2);
+	return MAX(a2, c2) <= MIN(b2, d2) + EPSILON;
+}
+
+static bool lineSegmentsCross(Math::Vector2d a1, Math::Vector2d b1, Math::Vector2d c1, Math::Vector2d d1) {
+	Math::Vector2d a = a1;
+	Math::Vector2d b = b1;
+	Math::Vector2d c = c1;
+	Math::Vector2d d = d1;
+	if ((!intersect_1d(a.getX(), b.getX(), c.getX(), d.getX())) || (!intersect_1d(a.getY(), b.getY(), c.getY(), d.getY())))
+		return false;
+
+	Segment m(a, b);
+	Segment n(c, d);
+	float zn = det(m.a, m.b, n.a, n.b);
+
+	if (abs(zn) < EPSILON) {
+		if ((abs(m.distance(c)) > EPSILON) || (abs(n.distance(a)) > EPSILON))
+			return false;
+
+		if (less(b, a))
+			SWAP(a, b);
+		if (less(d, c))
+			SWAP(c, d);
+		return true;
+	}
+
+	float lx = -det(m.c, m.b, n.c, n.b) / zn;
+	float ly = -det(m.a, m.c, n.a, n.c) / zn;
+	return betw(a.getX(), b.getX(), lx) && betw(a.getY(), b.getY(), ly) && betw(c.getX(), d.getX(), lx) && betw(c.getY(), d.getY(), ly);
+}
+
+bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
+	const float epsilon = 0.5f;
+
+	// Not in LOS if any of the ends is outside the polygon
+	if (!_walkboxes[0].contains(start) || !_walkboxes[0].contains(to))
+		return false;
+
+	// In LOS if it's the same start and end location
+	if (length(start - to) < epsilon)
+		return true;
+
+	// Not in LOS if any edge is intersected by the start-end line segment
+	for (int i = 0; i < _walkboxes.size(); i++) {
+		Walkbox &walkbox = _walkboxes[i];
+		const Common::Array<Math::Vector2d> &polygon = walkbox.getPoints();
+		int size = polygon.size();
+		for (int j = 0; j < size; j++) {
+			Math::Vector2d v1 = polygon[j];
+			Math::Vector2d v2 = polygon[(j + 1) % size];
+			if (!lineSegmentsCross(start, to, v1, v2))
+				continue;
+
+			// In some cases a 'snapped' endpoint is just a little over the line due to rounding errors. So a 0.5 margin is used to tackle those cases.
+			if ((distanceToSegment(start, v1, v2) > epsilon) && (distanceToSegment(to, v1, v2) > epsilon))
+				return false;
+		}
+	}
+
+	// Finally the middle point in the segment determines if in LOS or not
+	Math::Vector2d v2 = (start + to) / 2.0f;
+	bool result = _walkboxes[0].contains(v2);
+	for (int i = 1; i < _walkboxes.size(); i++) {
+		if (_walkboxes[i].contains(v2, false))
+			result = false;
+	}
+	return result;
+}
+
+static int minIndex(const Common::Array<float> values) {
+	float min = values[0];
+	int index = 0;
+	for (int i = 1; i < values.size(); i++) {
+		if (values[i] < min) {
+			index = i;
+			min = values[i];
+		}
+	}
+	return index;
+}
+
+Graph *PathFinder::createGraph() {
+	Graph *result = new Graph();
+	for (int i = 0; i < _walkboxes.size(); i++) {
+		Walkbox &walkbox = _walkboxes[i];
+		if (walkbox.getPoints().size() > 2) {
+			bool visible = walkbox.isVisible();
+			for (int j = 0; j < walkbox.getPoints().size(); j++) {
+				if (walkbox.concave(j) == visible) {
+					Math::Vector2d vertex = walkbox.getPoints()[j];
+					result->_concaveVertices.push_back(vertex);
+					result->addNode(vertex);
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < result->_concaveVertices.size(); i++) {
+		for (int j = 0; j < result->_concaveVertices.size(); j++) {
+			Math::Vector2d c1(result->_concaveVertices[i]);
+			Math::Vector2d c2(result->_concaveVertices[j]);
+			if (inLineOfSight(c1, c2)) {
+				float d = distance(c1, c2);
+				result->addEdge(GraphEdge(i, j, d));
+			}
+		}
+	}
+	return result;
+}
+
+Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Math::Vector2d to) {
+	Common::Array<Math::Vector2d> result;
+	if (_walkboxes.size() > 0) {
+		// find the walkbox where the actor is and put it first
+		for (int i = 0; i < _walkboxes.size(); i++) {
+			const Walkbox &wb = _walkboxes[i];
+			if (inside(wb, start) && (i != 0)) {
+				SWAP(_walkboxes[0], _walkboxes[i]);
+				break;
+			}
+		}
+
+		// if no walkbox has been found => find the nearest walkbox
+		if (!inside(_walkboxes[0], start)) {
+			Common::Array<float> dists(_walkboxes.size());
+			for (int i = 0; i < _walkboxes.size(); i++) {
+				Walkbox wb = _walkboxes[i];
+				dists[i] = distance(wb.getClosestPointOnEdge(start), start);
+			}
+
+			int index = minIndex(dists);
+			if (index != 0)
+				SWAP(_walkboxes[0], _walkboxes[index]);
+		}
+
+		if (!_graph)
+			_graph = createGraph();
+
+		// create new node on start position
+		Graph *walkgraph = new Graph(*_graph);
+		int startNodeIndex = walkgraph->_nodes.size();
+
+		// if destination is not inside current walkable area, then get the closest point
+		const Walkbox &wb = _walkboxes[0];
+		if (wb.isVisible() && !wb.contains(to))
+			to = wb.getClosestPointOnEdge(to);
+
+		walkgraph->addNode(start);
+
+		for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
+			Math::Vector2d c = walkgraph->_concaveVertices[i];
+			if (inLineOfSight(start, c))
+				walkgraph->addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
+		}
+
+		// create new node on end position
+		int endNodeIndex = walkgraph->_nodes.size();
+		walkgraph->addNode(to);
+
+		for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
+			Math::Vector2d c = walkgraph->_concaveVertices[i];
+			if (inLineOfSight(to, c))
+				walkgraph->addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
+		}
+
+		if (inLineOfSight(start, to))
+			walkgraph->addEdge(GraphEdge(startNodeIndex, endNodeIndex, distance(start, to)));
+
+		Common::Array<int> indices = walkgraph->getPath(startNodeIndex, endNodeIndex);
+		for (int i = 0; i < indices.size(); i++) {
+			int index = indices[i];
+			result.push_back(walkgraph->_nodes[index]);
+		}
+	}
+	return result;
+}
+
+} // namespace Twp
diff --git a/engines/twp/graph.h b/engines/twp/graph.h
new file mode 100644
index 00000000000..38f9d4a2783
--- /dev/null
+++ b/engines/twp/graph.h
@@ -0,0 +1,131 @@
+/* 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 TWP_GRAPH_H
+#define TWP_GRAPH_H
+
+#include "common/array.h"
+#include "math/vector2d.h"
+
+namespace Twp {
+
+class IndexedPriorityQueue {
+public:
+	explicit IndexedPriorityQueue(Common::Array<float> &keys);
+
+	void insert(int index);
+	int pop();
+
+	void reorderUp();
+	void reorderDown();
+
+	bool isEmpty();
+
+private:
+	Common::Array<float> &_keys;
+	Common::Array<int> _data;
+};
+
+// An edge is a part of a walkable area, it is used by a Graph.
+// See also:
+//  - PathFinder
+//  - Graph
+struct GraphEdge {
+	GraphEdge(int start, int to, float cost);
+
+	int start;  // Index of the node in the graph representing the start of the edge.
+	int to;     // Index of the node in the graph representing the end of the edge.
+	float cost; // Cost of the edge in the graph.
+};
+
+// A graph helps to find a path between two points.
+// This class has been ported from http://www.groebelsloot.com/2016/03/13/pathfinding-part-2/
+// and modified
+class Graph {
+public:
+	Graph();
+	Graph(const Graph &graph);
+	void addNode(Math::Vector2d node);
+	void addEdge(GraphEdge edge);
+	// Gets the edge from 'from' index to 'to' index.
+	GraphEdge *edge(int start, int to);
+	Common::Array<int> getPath(int source, int target);
+
+	Common::Array<Math::Vector2d> _nodes;
+	Common::Array<Common::Array<GraphEdge> > _edges;
+	Common::Array<Math::Vector2d> _concaveVertices;
+};
+
+class AStar {
+public:
+	AStar(Graph *graph);
+	void search(int source, int target);
+
+	Graph *_graph = nullptr;
+	Common::Array<GraphEdge *> _spt; // The Shortest Path Tree
+	Common::Array<float> _gCost;     // This array will store the G cost of each node
+	Common::Array<float> _fCost;     // This array will store the F cost of each node
+	Common::Array<GraphEdge*> _sf;    // The Search Frontier
+};
+
+// Represents an area where an actor can or cannot walk
+class Walkbox {
+public:
+	Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
+
+  	// Indicates whether or not the specified position is inside this walkbox.
+	bool contains(Math::Vector2d position, bool toleranceOnOutside = true) const;
+	bool concave(int vertex) const;
+	void setVisible(bool visible) { _visible = visible; }
+	bool isVisible() const { return _visible; }
+	const Common::Array<Math::Vector2d>& getPoints() const { return _polygon; }
+	Math::Vector2d getClosestPointOnEdge(Math::Vector2d p3) const;
+
+public:
+	Common::String _name;
+
+private:
+	Common::Array<Math::Vector2d> _polygon;
+	bool _visible;
+};
+
+// A PathFinder is used to find a walkable path within one or several walkboxes.
+class PathFinder {
+public:
+	void setWalkboxes(const Common::Array<Walkbox> &walkboxes);
+	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d start, Math::Vector2d to);
+	void setDirty(bool dirty) { _isDirty = dirty; }
+	bool isDirty() const { return _isDirty; }
+	const Graph* getGraph() const { return _graph; }
+
+private:
+	Graph *createGraph();
+	bool inLineOfSight(Math::Vector2d start, Math::Vector2d to);
+
+private:
+	Common::Array<Walkbox> _walkboxes;
+	Graph *_graph = nullptr;
+	bool _isDirty = true;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 2bb57b22dba..4d9f081e9e8 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -21,8 +21,11 @@ SQUIRREL_OBJS = \
 	squirrel/sqstdrex.o \
 	squirrel/sqstdaux.o
 
+CLIPPER_OBJS = clipper/clipper.o
+
 MODULE_OBJS = \
 	$(SQUIRREL_OBJS) \
+	$(CLIPPER_OBJS) \
 	twp.o \
 	console.o \
 	metaengine.o \
@@ -58,6 +61,8 @@ MODULE_OBJS = \
 	hud.o \
 	lip.o \
 	callback.o \
+	graph.o \
+	walkboxnode.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index d93e3c0f178..fa1448ed5d7 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -208,6 +208,8 @@ public:
 	WalkTo(Object *obj, Math::Vector2d dest, int facing = 0);
 	virtual void disable() override;
 
+	const Common::Array<Math::Vector2d>& getPath() const { return _path; }
+
 private:
 	void actorArrived();
 	virtual void update(float elapsed) override;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 017cc35b0a5..ede917c10f8 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
 #include "twp/twp.h"
 #include "twp/room.h"
 #include "twp/ggpack.h"
@@ -28,6 +30,7 @@
 #include "twp/ids.h"
 #include "twp/object.h"
 #include "twp/util.h"
+#include "twp/clipper/clipper.hpp"
 #include "common/algorithm.h"
 
 namespace Twp {
@@ -94,6 +97,50 @@ static Scaling parseScaling(const Common::JSONArray &jScalings) {
 	return result;
 }
 
+static ClipperLib::Path toPolygon(const Walkbox &walkbox) {
+	ClipperLib::Path path;
+	const Common::Array<Math::Vector2d> &points = walkbox.getPoints();
+	for (int i = 0; i < points.size(); i++) {
+		path.push_back(ClipperLib::IntPoint(points[i].getX(), points[i].getY()));
+	}
+	return path;
+}
+
+static Walkbox toWalkbox(const ClipperLib::Path &path) {
+	Common::Array<Math::Vector2d> pts;
+	for (int i = 0; i < path.size(); i++) {
+		const ClipperLib::IntPoint &pt = path[i];
+		pts.push_back(Math::Vector2d(pt.X, pt.Y));
+	}
+	return Walkbox(pts, ClipperLib::Orientation(path));
+}
+
+static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
+	Common::Array<Walkbox> result;
+	if (walkboxes.size() > 0) {
+		ClipperLib::Paths subjects, clips;
+		for (int i = 0; i < walkboxes.size(); i++) {
+			const Walkbox &wb = walkboxes[i];
+			if (wb.isVisible()) {
+				subjects.push_back(toPolygon(wb));
+			} else {
+				clips.push_back(toPolygon(wb));
+			}
+		}
+
+		ClipperLib::Paths solutions;
+		ClipperLib::Clipper c;
+		c.AddPaths(subjects, ClipperLib::ptSubject, true);
+		c.AddPaths(clips, ClipperLib::ptClip, true);
+		c.Execute(ClipperLib::ClipType::ctUnion, solutions, ClipperLib::pftEvenOdd);
+
+		for (int i = 0; i < solutions.size(); i++) {
+			result.push_back(toWalkbox(solutions[i]));
+		}
+	}
+	return result;
+}
+
 Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 	setId(_table, newRoomId());
 	_name = name;
@@ -321,6 +368,8 @@ void Room::load(Common::SeekableReadStream &s) {
 		_scaling = _scalings[0];
 	}
 
+	_mergedPolygon = merge(_walkboxes);
+
 	delete value;
 }
 
@@ -409,8 +458,28 @@ void Room::update(float elapsed) {
 	}
 }
 
+void Room::walkboxHidden(const Common::String &name, bool hidden) {
+	for (int i = 0; i < _walkboxes.size(); i++) {
+		Walkbox &wb = _walkboxes[i];
+		if (wb._name == name) {
+			wb.setVisible(!hidden);
+			// 1 walkbox has change so update merged polygon
+			_mergedPolygon = merge(_walkboxes);
+			_pathFinder.setDirty(true);
+			return;
+		}
+	}
+}
+
 Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
-	return {frm, to};
+	if (_mergedPolygon.size() > 0) {
+		if (_pathFinder.isDirty()) {
+			_pathFinder.setWalkboxes(_mergedPolygon);
+			_pathFinder.setDirty(false);
+		}
+		return _pathFinder.calculatePath(frm, to);
+	}
+	return {};
 }
 
 Layer::Layer(const Common::String &name, Math::Vector2d parallax, int zsort) {
@@ -429,6 +498,56 @@ Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
 
+bool Walkbox::concave(int vertex) const {
+	Math::Vector2d current = _polygon[vertex];
+	Math::Vector2d next = _polygon[(vertex + 1) % _polygon.size()];
+	Math::Vector2d previous = _polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
+
+	Math::Vector2d left(current.getX() - previous.getX(), current.getY() - previous.getY());
+	Math::Vector2d right(next.getX() - current.getX(), next.getY() - current.getY());
+
+	float cross = (left.getX() * right.getY()) - (left.getY() * right.getX());
+	return _visible ? cross < 0 : cross >= 0;
+}
+
+bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
+	Math::Vector2d point = position;
+	const float epsilon = 1.0f;
+	bool result = false;
+
+	// Must have 3 or more edges
+	if (_polygon.size() < 3)
+		return false;
+
+	Math::Vector2d oldPoint(_polygon[_polygon.size() - 1]);
+	float oldSqDist = distanceSquared(oldPoint, point);
+
+	for (int i = 0; i < _polygon.size(); i++) {
+		Math::Vector2d newPoint = _polygon[i];
+		float newSqDist = distanceSquared(newPoint, point);
+
+		if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
+			return toleranceOnOutside;
+
+		Math::Vector2d left;
+		Math::Vector2d right;
+		if (newPoint.getX() > oldPoint.getX()) {
+			left = oldPoint;
+			right = newPoint;
+		} else {
+			left = newPoint;
+			right = oldPoint;
+		}
+
+		if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX())) < ((right.getY() - left.getY()) * (point.getX() - left.getX())))
+			result = !result;
+
+		oldPoint = newPoint;
+		oldSqDist = newSqDist;
+	}
+	return result;
+}
+
 float Scaling::getScaling(float yPos) {
 	if (values.size() == 0)
 		return 1.0f;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 637575021d9..c350a41a579 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -30,6 +30,7 @@
 #include "twp/font.h"
 #include "twp/motor.h"
 #include "twp/scenegraph.h"
+#include "twp/graph.h"
 
 #define FULLSCREENCLOSEUP	1
 #define FULLSCREENROOM		2
@@ -61,19 +62,6 @@ public:
 	Node *_node = nullptr;
 };
 
-// Represents an area where an actor can or cannot walk
-class Walkbox {
-public:
-	Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
-
-public:
-	Common::String _name;
-
-private:
-	Common::Array<Math::Vector2d> _polygon;
-	bool _visible;
-};
-
 struct ScalingValue {
 	float scale;
 	int y;
@@ -105,6 +93,7 @@ struct Lights {
 	Color _ambientLight; // Ambient light color
 };
 
+class PathFinder;
 class Scene;
 class Room {
 public:
@@ -129,6 +118,7 @@ public:
 	void setOverlay(Color color);
 	Color getOverlay() const;
 
+	void walkboxHidden(const Common::String &name, bool hidden);
 	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d frm, Math::Vector2d to);
 
 public:
@@ -139,12 +129,12 @@ public:
 	int _height = 0;                   // Height of the room (what else ?)
 	Common::Array<Layer *> _layers;    // Parallax layers of a room
 	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
+	Common::Array<Walkbox> _mergedPolygon;
 	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
 	Scaling _scaling;                  // Defines the scaling of the actor in the room
 	HSQOBJECT _table;                  // Squirrel table representing this room
 	bool _entering = false;            // Indicates whether or not an actor is entering this room
 	Lights _lights;                    // Lights of the room
-	Common::Array<Walkbox> _mergedPolygon;
 	Common::Array<Object *> _triggers; // Triggers currently enabled in the room
 	bool _pseudo = false;
 	Common::Array<Object *> _objects;
@@ -152,6 +142,7 @@ public:
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect;
 	Motor* _overlayTo = nullptr;
+	PathFinder _pathFinder;
 };
 
 } // namespace Twp
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 08c45bbc48b..7f8eb815d76 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -322,24 +322,24 @@ static SQInteger roomLayer(HSQUIRRELVM v) {
 // }
 static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 	int startColor;
-  SQInteger numArgs = sq_gettop(v);
-  if (SQ_FAILED(sqget(v, 2, startColor)))
-    return sq_throwerror(v, "failed to get startColor");
-  Room* room = g_engine->_room;
-  if (room->_overlayTo)
-      room->_overlayTo->disable();
-  room->setOverlay(Color::fromRgba(startColor));
-  if (numArgs == 4) {
-    int endColor;
-    if (SQ_FAILED(sqget(v, 3, endColor)))
-      return sq_throwerror(v, "failed to get endColor");
-    float duration;
-    if (SQ_FAILED(sqget(v, 4, duration)))
-      return sq_throwerror(v, "failed to get duration");
-    debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
-    g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
-  }
-  return 0;
+	SQInteger numArgs = sq_gettop(v);
+	if (SQ_FAILED(sqget(v, 2, startColor)))
+		return sq_throwerror(v, "failed to get startColor");
+	Room *room = g_engine->_room;
+	if (room->_overlayTo)
+		room->_overlayTo->disable();
+	room->setOverlay(Color::fromRgba(startColor));
+	if (numArgs == 4) {
+		int endColor;
+		if (SQ_FAILED(sqget(v, 3, endColor)))
+			return sq_throwerror(v, "failed to get endColor");
+		float duration;
+		if (SQ_FAILED(sqget(v, 4, duration)))
+			return sq_throwerror(v, "failed to get duration");
+		debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
+		g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
+	}
+	return 0;
 }
 
 static SQInteger roomRotateTo(HSQUIRRELVM v) {
@@ -355,8 +355,17 @@ static SQInteger roomSize(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Sets walkbox to be hidden (YES) or not (NO).
+// If the walkbox is hidden, the actors cannot walk to any point within that area anymore, nor to any walkbox that's connected to it on the other side from the actor.
+// Often used on small walkboxes below a gate or door to keep the actor from crossing that boundary if the gate/door is closed.
 static SQInteger walkboxHidden(HSQUIRRELVM v) {
-	warning("TODO: walkboxHidden not implemented");
+	Common::String walkbox;
+	if (SQ_FAILED(sqget(v, 2, walkbox)))
+		return sq_throwerror(v, "failed to get object or walkbox");
+	int hidden = 0;
+	if (SQ_FAILED(sqget(v, 3, hidden)))
+		return sq_throwerror(v, "failed to get object or hidden");
+	g_engine->_room->walkboxHidden(walkbox, hidden != 0);
 	return 0;
 }
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 932a670d87d..b654ba5b0ae 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -410,7 +410,8 @@ Scene::Scene() : Node("Scene") {
 }
 Scene::~Scene() {}
 
-InputState::InputState() : Node("InputState") {}
+InputState::InputState() : Node("InputState") {
+}
 
 InputState::~InputState() {}
 
@@ -422,7 +423,7 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	//       cursorName = "hotspot_" & self.cursorName
 	const SpriteSheetFrame &sf = gameSheet->frameTable["cursor"];
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
-	trsf.translate(pos);
+	trsf.translate(pos * 2.f);
 	scale(trsf, Math::Vector2d(2.f, 2.f));
 	g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 96d08865ec1..97de6cbc463 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -57,6 +57,8 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	g_engine = this;
 	sq_resetobject(&_defaultObj);
 	_screenScene.setName("Screen");
+	_scene.addChild(&_walkboxNode);
+	_screenScene.addChild(&_pathNode);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
@@ -73,7 +75,6 @@ static Math::Vector2d winToScreen(Math::Vector2d pos) {
 
 Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
 	Math::Vector2d screenSize = _room->getScreenSize();
-	pos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
 	return Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) * (pos - _gfx.cameraPos()) / screenSize;
 }
 
@@ -341,7 +342,7 @@ void TwpEngine::update(float elapsed) {
 				// } else {
 				// 	walkFast(false);
 				// }
-				if (_cursor.isLeftDown() || _cursor.isRightDown())
+				if (_cursor.leftDown || _cursor.rightDown)
 					clickedAt(scrPos);
 			}
 		} else {
@@ -351,11 +352,11 @@ void TwpEngine::update(float elapsed) {
 			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->_name);
 			_sentence.setText(cText);
 			// TODO: _inputState.setCursorShape(CursorShape::Normal);
-			if (_cursor.isLeftDown())
+			if (_cursor.leftDown)
 				clickedAt(scrPos);
 		}
 
-		if (_cursor.isLeftDown() || _cursor.isRightDown())
+		if (_cursor.leftDown || _cursor.rightDown)
 			clickedAt(_cursor.pos);
 	}
 
@@ -544,11 +545,11 @@ void TwpEngine::draw() {
 	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
 
 	// draw UI
+	_gfx.cameraPos(camPos);
 	_screenScene.draw();
 
 	g_system->updateScreen();
 
-	_gfx.cameraPos(camPos);
 }
 
 Common::Error TwpEngine::run() {
@@ -1116,12 +1117,12 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 		} else {
 			bool handled = false;
 			if (sqrawexists(noun2->_table, verbFuncName)) {
-				debug("call {verbFuncName} on {noun2.key}");
+				debug("call %s on %s", verbFuncName.c_str(), noun2->_key.c_str());
 				sqcallfunc(handled, noun2->_table, verbFuncName.c_str(), noun1->_table);
 			}
 			// verbGive is called on object only for non selectable actors
 			if (!handled && !selectable(noun2) && sqrawexists(noun1->_table, verbFuncName)) {
-				debug("call {verbFuncName} on {noun1.key}");
+				debug("call %s on %s", verbFuncName.c_str(), noun1->_key.c_str());
 				sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
 				handled = true;
 			}
@@ -1138,7 +1139,7 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	if (!noun2) {
 		if (sqrawexists(noun1->_table, verbFuncName)) {
 			int count = sqparamCount(getVm(), noun1->_table, verbFuncName);
-			debug("call {noun1.key}.{verbFuncName}");
+			debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
 			if (count == 1) {
 				sqcall(noun1->_table, verbFuncName.c_str());
 			} else {
@@ -1147,17 +1148,17 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
 			sqcall(noun1->_table, VERBDEFAULT);
 		} else {
-			debug("call defaultObject.{verbFuncName}");
+			debug("call defaultObject.%s", verbFuncName.c_str());
 			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, actor->_table);
 		}
 	} else {
 		if (sqrawexists(noun1->_table, verbFuncName)) {
-			debug("call {noun1.key}.{verbFuncName}");
+			debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
 			sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
 		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
 			sqcall(noun1->_table, VERBDEFAULT);
 		} else {
-			debug("call defaultObject.{verbFuncName}");
+			debug("call defaultObject.%s", verbFuncName.c_str());
 			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, noun2->_table);
 		}
 	}
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 5ee63d0ba5b..d1575e29bc6 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -44,6 +44,7 @@
 #include "twp/dialog.h"
 #include "twp/hud.h"
 #include "twp/callback.h"
+#include "twp/walkboxnode.h"
 
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
@@ -210,6 +211,8 @@ private:
 	ShaderParams _shaderParams;
 	unique_ptr<FadeShader> _fadeShader;
 	SentenceNode _sentence;
+	WalkboxNode _walkboxNode;
+	PathNode _pathNode;
 };
 
 extern TwpEngine *g_engine;
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index c7c211f4543..ff43ba1670b 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -134,6 +134,22 @@ float distanceSquared(Math::Vector2d p1, Math::Vector2d p2) {
 	return dx * dx + dy * dy;
 }
 
+float distanceToSegmentSquared(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
+	float l2 = distanceSquared(v, w);
+	if (l2 == 0)
+		return distanceSquared(p, v);
+	float t = ((p.getX() - v.getX()) * (w.getX() - v.getX()) + (p.getY() - v.getY()) * (w.getY() - v.getY())) / l2;
+	if (t < 0)
+		return distanceSquared(p, v);
+	if (t > 1)
+		return distanceSquared(p, w);
+	return distanceSquared(p, Math::Vector2d(v.getX() + t * (w.getX() - v.getX()), v.getY() + t * (w.getY() - v.getY())));
+}
+
+float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
+	return sqrt(distanceToSegmentSquared(p, v, w));
+}
+
 float distance(Math::Vector2d p1, Math::Vector2d p2) {
 	return sqrt(distanceSquared(p1, p2));
 }
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 743a579b2e5..564aa0ef7b0 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -50,6 +50,8 @@ Common::Rect parseRect(const Common::String &s);
 void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
 
 float distance(Math::Vector2d p1, Math::Vector2d p2);
+float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
+float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
 
 template<typename T>
 int find(Common::Array<T>& array, const T& o) {
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
new file mode 100644
index 00000000000..0d11adbb0bc
--- /dev/null
+++ b/engines/twp/walkboxnode.cpp
@@ -0,0 +1,175 @@
+/* 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 "twp/twp.h"
+#include "twp/walkboxnode.h"
+
+namespace Twp {
+
+WalkboxNode::WalkboxNode() : Node("Walkbox") {
+	_zOrder = -1000;
+	_mode = WalkboxMode::Merged;
+}
+
+void WalkboxNode::drawCore(Math::Matrix4 trsf) {
+	if (g_engine->_room) {
+		switch (_mode) {
+		case WalkboxMode::All: {
+			Math::Matrix4 transf;
+			// cancel camera pos
+			Math::Vector2d pos = g_engine->getGfx().cameraPos();
+			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
+			for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
+				Walkbox &wb = g_engine->_room->_walkboxes[i];
+				if (wb.isVisible()) {
+					Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
+					Common::Array<Vertex> vertices;
+					for (int j = 0; j < wb.getPoints().size(); j++) {
+						Math::Vector2d p = wb.getPoints()[j];
+						Vertex v;
+						v.pos = p;
+						v.color = color;
+						vertices.push_back(v);
+					}
+					g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
+				}
+			}
+		} break;
+		case WalkboxMode::Merged: {
+			Math::Matrix4 transf;
+			Math::Vector2d pos = g_engine->getGfx().cameraPos();
+			// cancel camera pos
+			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
+			for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
+				Walkbox &wb = g_engine->_room->_mergedPolygon[i];
+				Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
+				Common::Array<Vertex> vertices;
+				for (int j = 0; j < wb.getPoints().size(); j++) {
+					Math::Vector2d p = wb.getPoints()[j];
+					Vertex v;
+					v.pos = p;
+					v.color = color;
+					vertices.push_back(v);
+				}
+				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
+			}
+		} break;
+		default:
+			break;
+		}
+	}
+}
+
+PathNode::PathNode() : Node("Path") {
+	_zOrder = -1000;
+}
+
+Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
+	for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
+		Walkbox &wb = g_engine->_room->_mergedPolygon[i];
+		if (!wb.isVisible() && wb.contains(pos)) {
+			return wb.getClosestPointOnEdge(pos);
+		}
+	}
+	//   for wb in gEngine.room.mergedPolygon:
+	//     if wb.visible and not wb.contains(pos):
+	//       return wb.getClosestPointOnEdge(pos)
+	return pos;
+}
+
+void PathNode::drawCore(Math::Matrix4 trsf) {
+	Color yellow(1.f, 1.f, 0.f);
+	Object *actor = g_engine->_actor;
+	// draw actor path
+	if (actor && actor->getWalkTo()) {
+		WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
+		const Common::Array<Math::Vector2d> &path = walkTo->getPath();
+		if (path.size() > 0) {
+			Common::Array<Vertex> vertices;
+			Vertex v;
+			v.pos = g_engine->roomToScreen(actor->_node->getPos());
+			v.color = yellow;
+			vertices.push_back(v);
+			for (int i = 0; i < path.size(); i++) {
+				Math::Vector2d p = g_engine->roomToScreen(path[i]);
+				v.pos = p;
+				v.color = yellow;
+				vertices.push_back(v);
+
+				Math::Matrix4 t;
+				p -= Math::Vector2d(2.f, 2.f);
+				t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
+				g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
+			}
+			g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+		}
+	}
+
+	// draw graph nodes
+	const Graph *graph = g_engine->_room->_pathFinder.getGraph();
+	if (graph) {
+		for (int i = 0; i < graph->_concaveVertices.size(); i++) {
+			Math::Vector2d v = graph->_concaveVertices[i];
+			Math::Matrix4 t;
+			Math::Vector2d p = g_engine->roomToScreen(v) - Math::Vector2d(2.f, 2.f);
+			t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
+			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow);
+		}
+
+		// for (int i = 0; i < graph->_edges.size(); i++) {
+		// 	const Common::Array<GraphEdge> &edges = graph->_edges[i];
+		// 	for (int j = 0; j < edges.size(); j++) {
+		// 		const GraphEdge &edge = edges[j];
+		// 		Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
+		// 		Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
+		// 		Vertex vertices[] = {Vertex{.pos = p1, .color = Color()}, Vertex{.pos = p2, .color = Color()}};
+		// 		g_engine->getGfx().drawLines(&vertices[0], 2);
+		// 	}
+		// }
+	}
+
+	// draw path from actor to mouse position
+	if (actor) {
+		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getPos()) - Math::Vector2d(2.f, 2.f);
+		Math::Matrix4 t;
+		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
+		Math::Vector2d scrPos = g_engine->_cursor.pos;
+		Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
+		Math::Vector2d p = fixPos(roomPos);
+		t = Math::Matrix4();
+		pos = g_engine->roomToScreen(p) - Math::Vector2d(4.f, 4.f);
+		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
+
+		Common::Array<Math::Vector2d> path = g_engine->_room->calculatePath(fixPos(actor->_node->getPos()), p);
+		Common::Array<Vertex> vertices;
+		for (int i = 0; i < path.size(); i++) {
+			Vertex v;
+			v.pos = g_engine->roomToScreen(path[i]);;
+			v.color = yellow;
+			vertices.push_back(v);
+		}
+		g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
new file mode 100644
index 00000000000..3e73bcf8686
--- /dev/null
+++ b/engines/twp/walkboxnode.h
@@ -0,0 +1,57 @@
+/* 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 TWP_WALKBOXMODE_H
+#define TWP_WALKBOXMODE_H
+
+#include "twp/scenegraph.h"
+
+namespace Twp {
+
+enum class WalkboxMode {
+    None,
+    Merged,
+    All
+};
+
+class WalkboxNode : public Node {
+public:
+	WalkboxNode();
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override;
+
+private:
+	WalkboxMode _mode;
+};
+
+class PathNode : public Node {
+public:
+	PathNode();
+
+private:
+	Math::Vector2d fixPos(Math::Vector2d pos);
+	virtual void drawCore(Math::Matrix4 trsf) override;
+};
+
+} // namespace Twp
+
+#endif


Commit: c045312fd2e14466d4548eb2259f4e84ab0599c6
    https://github.com/scummvm/scummvm/commit/c045312fd2e14466d4548eb2259f4e84ab0599c6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add triggers

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/object.cpp
    engines/twp/roomlib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 61a01d1eda6..6340eef22a4 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -305,9 +305,45 @@ static SQInteger actorInTrigger(HSQUIRRELVM v) {
 	return 1;
 }
 
+// Returns true if the specified actor is inside the specified walkbox from the wimpy file.
+//
+// . code-block:: Squirrel
+// sheriffsOfficeJailDoor =
+// {
+//     name = "jail door"
+//     actorInWalkbox(currentActor, "jail")
+//     verbOpen = function()
+//     {
+//         if (jail_door_state == OPEN) {
+//             sayLine("The door is already open.")
+//         } else {
+//             if (actorInWalkbox(currentActor, "jail")) {
+//                 sayLine("I can't open it from in here.")
+//                 return
+//             } else {
+//                startthread(openJailDoor)
+//             }
+//         }
+//     }
+// }
 static SQInteger actorInWalkbox(HSQUIRRELVM v) {
-	warning("TODO: actorInWalkbox not implemented");
-	return 0;
+	Object *actor = sqactor(v, 2);
+	if (!actor)
+		return sq_throwerror(v, "failed to get actor");
+	Common::String name;
+	if (SQ_FAILED(sqget(v, 3, name)))
+		return sq_throwerror(v, "failed to get name");
+	for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
+		const Walkbox &walkbox = g_engine->_room->_walkboxes[i];
+		if (walkbox._name == name) {
+			if (walkbox.contains(actor->_node->getAbsPos())) {
+				sqpush(v, true);
+				return 1;
+			}
+		}
+	}
+	sqpush(v, false);
+	return 1;
 }
 
 static SQInteger actorRoom(HSQUIRRELVM v) {
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index d066ab56272..55047382e6c 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -164,8 +164,14 @@ void Object::trig(const Common::String &name) {
 			// warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
 		}
 	} else {
-		error("todo: trig %s", name.c_str());
-		// TODO: gEventMgr.trig(name.substr(1));
+		int id = 0;
+		sqgetf(sqrootTbl(g_engine->getVm()), name, id);
+		debug("TODO: sound trigger");
+		// var sound = soundDef(id);
+		// if (!sound)
+		// 	warn fmt "Cannot trig sound '{name}', sound not found (id={id})";
+		// else
+		// 	gEngine.audio.play(sound, Sound);
 	}
 }
 
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 7f8eb815d76..36910c7b1b4 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -69,7 +69,19 @@ static SQInteger createLight(HSQUIRRELVM v) {
 }
 
 static SQInteger enableTrigger(HSQUIRRELVM v) {
-	warning("TODO: enableTrigger not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	bool enabled;
+	if (SQ_FAILED(sqget(v, 3, enabled)))
+		return sq_throwerror(v, "failed to get enabled");
+	if (enabled)
+		g_engine->_room->_triggers.push_back(obj);
+	else {
+		int index = find(g_engine->_room->_triggers, obj);
+		if (index != -1)
+			g_engine->_room->_triggers.remove_at(index);
+	}
 	return 0;
 }
 
@@ -211,7 +223,29 @@ static SQInteger masterRoomArray(HSQUIRRELVM v) {
 }
 
 static SQInteger removeTrigger(HSQUIRRELVM v) {
-	warning("TODO: removeTrigger not implemented");
+	if (sq_gettype(v, 2) == OT_CLOSURE) {
+		HSQOBJECT closure;
+		sq_resetobject(&closure);
+		if (SQ_FAILED(sqget(v, 3, closure)))
+			return sq_throwerror(v, "failed to get closure");
+		for (int i = 0; i < g_engine->_room->_triggers.size(); i++) {
+			Object *trigger = g_engine->_room->_triggers[i];
+			if ((trigger->_enter._unVal.pClosure == closure._unVal.pClosure) || (trigger->_leave._unVal.pClosure == closure._unVal.pClosure)) {
+				g_engine->_room->_triggers.remove_at(i);
+				return 0;
+			}
+		}
+	} else {
+		Object *obj = sqobj(v, 2);
+		if (!obj)
+			return sq_throwerror(v, "failed to get object");
+		int i = find(g_engine->_room->_triggers, obj);
+		if (i != -1) {
+			debug("Remove room trigger: {obj.name}({obj.key})");
+			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
+		}
+		return 0;
+	}
 	return 0;
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 97de6cbc463..4bdee1d42b9 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -326,7 +326,7 @@ void TwpEngine::update(float elapsed) {
 				_noun2 = nullptr;
 			}
 
-			_inputState.setHotspot(_noun1!=nullptr);
+			_inputState.setHotspot(_noun1 != nullptr);
 			_hud.setVisible(_inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None);
 			_sentence.setVisible(_hud.isVisible());
 			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
@@ -431,6 +431,8 @@ void TwpEngine::update(float elapsed) {
 		VerbUiColors *verbUI = &_hud.actorSlot(_actor)->verbUiColors;
 		_uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
 	}
+
+	updateTriggers();
 }
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
@@ -549,7 +551,6 @@ void TwpEngine::draw() {
 	_screenScene.draw();
 
 	g_system->updateScreen();
-
 }
 
 Common::Error TwpEngine::run() {
@@ -1171,4 +1172,61 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	return false;
 }
 
+void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
+	if (trigger._type != OT_NULL) {
+		HSQUIRRELVM v = getVm();
+		// create trigger thread
+		sq_newthread(v, 1024);
+		HSQOBJECT threadObj;
+		sq_resetobject(&threadObj);
+		if (SQ_FAILED(sq_getstackobj(v, -1, &threadObj))) {
+			error("Couldn't get coroutine thread from stack");
+			return;
+		}
+		sq_addref(v, &threadObj);
+		sq_pop(v, 1);
+
+		// create args
+		SQInteger nParams, nfreevars;
+		sq_pushobject(v, trigger);
+		sq_getclosureinfo(v, -1, &nParams, &nfreevars);
+		sq_pop(v, 1);
+
+		int id = newThreadId();
+		Thread *thread = new Thread(id);
+		thread->setName("Trigger");
+		thread->_global = false;
+		thread->_threadObj = threadObj;
+		thread->_envObj = obj->_table;
+		thread->_closureObj = trigger;
+		if(nParams == 2) {
+			thread->_args.push_back(_actor->_table);
+		}
+		debug("create triggerthread id: %d}", thread->getId());
+		g_engine->_threads.push_back(thread);
+
+		// call the closure in the thread
+		if (!thread->call()) {
+			error("trigger call failed");
+		}
+	}
+}
+
+void TwpEngine::updateTriggers() {
+	if (_actor) {
+		for (int i = 0; i < _room->_triggers.size(); i++) {
+			Object *trigger = _room->_triggers[i];
+			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
+				debug("call enter trigger %s", trigger->_name.c_str());
+				trigger->_triggerActive = true;
+				callTrigger(trigger, trigger->_enter);
+			} else if (trigger->_triggerActive && !trigger->contains(_actor->_node->getAbsPos())) {
+				debug("call leave trigger %s", trigger->_name.c_str());
+				trigger->_triggerActive = false;
+				callTrigger(trigger, trigger->_leave);
+			}
+		}
+	}
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index d1575e29bc6..b63fb8a4dc6 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -157,6 +157,8 @@ private:
 	Common::String cursorText();
 	Verb verb();
 	bool preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2);
+	void updateTriggers();
+	void callTrigger(Object *obj, HSQOBJECT trigger);
 
 public:
 	Graphics::Screen *_screen = nullptr;


Commit: 309a3b456c8eaddc41b4d07d3703234450acf261
    https://github.com/scummvm/scummvm/commit/309a3b456c8eaddc41b4d07d3703234450acf261
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add cursor shape

Changed paths:
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index b654ba5b0ae..0567afa262b 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -419,9 +419,9 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	// draw cursor
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	//   if prefs(ClassicSentence) and self.hotspot:
-	//       cursorName = "hotspot_" & self.cursorName
-	const SpriteSheetFrame &sf = gameSheet->frameTable["cursor"];
+	// TODO: if prefs(ClassicSentence) and self.hotspot:
+	Common::String cursorName = _hotspot ? Common::String::format("hotspot_%s", _cursorName.c_str()): _cursorName;
+	const SpriteSheetFrame &sf = gameSheet->frameTable[cursorName];
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos * 2.f);
 	scale(trsf, Math::Vector2d(2.f, 2.f));
@@ -460,6 +460,32 @@ void InputState::setState(InputStateFlag state) {
 		_inputHUD = false;
 }
 
+void InputState::setCursorShape(CursorShape shape) {
+	if (_cursorShape != shape) {
+		_cursorShape = shape;
+		switch (shape) {
+		case CursorShape::Normal:
+			_cursorName = "cursor";
+			break;
+		case CursorShape::Left:
+			_cursorName = "cursor_left";
+			break;
+		case CursorShape::Right:
+			_cursorName = "cursor_right";
+			break;
+		case CursorShape::Front:
+			_cursorName = "cursor_front";
+			break;
+		case CursorShape::Back:
+			_cursorName = "cursor_back";
+			break;
+		case CursorShape::Pause:
+			_cursorName = "cursor_pause";
+			break;
+		}
+	}
+}
+
 OverlayNode::OverlayNode() : Node("overlay") {
 	_ovlColor = Color(0.f, 0.f, 0.f, 0.f); // transparent
 	_zOrder = INT_MIN;
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 4d0563ba688..e8bf66073a5 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -254,6 +254,8 @@ public:
 	void setHotspot(bool value) { _hotspot = value; }
 	bool getHotspot() const { return _hotspot; }
 
+	void setCursorShape(CursorShape shape);
+
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
 
@@ -263,7 +265,7 @@ private:
     bool _showCursor = false;
     bool _inputVerbsActive = false;
     CursorShape _cursorShape = CursorShape::Normal;
-    Common::String _cursorName;
+    Common::String _cursorName = "cursor";
     bool _hotspot = false;
 };
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 4bdee1d42b9..f59f0f2cec4 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -326,6 +326,28 @@ void TwpEngine::update(float elapsed) {
 				_noun2 = nullptr;
 			}
 
+			Math::Vector2d screenSize = _room->getScreenSize();
+			if ((scrPos.getX() < SCREEN_MARGIN) && (cameraPos().getX() >= 1.f)) {
+				_inputState.setCursorShape(CursorShape::Left);
+			} else if ((scrPos.getX() > (SCREEN_WIDTH - SCREEN_MARGIN)) && cameraPos().getX() < (_room->_roomSize.getX() - screenSize.getX())) {
+				_inputState.setCursorShape(CursorShape::Right);
+			} else if (_room->_fullscreen == FULLSCREENROOM && _noun1) {
+				// if the object is a door, it has a flag indicating its direction: left, right, front, back
+				int flags = _noun1->getFlags();
+				if (flags & DOOR_LEFT)
+					_inputState.setCursorShape(CursorShape::Left);
+				else if (flags &DOOR_RIGHT)
+					_inputState.setCursorShape(CursorShape::Right);
+				else if (flags & DOOR_FRONT)
+					_inputState.setCursorShape(CursorShape::Front);
+				else if (flags & DOOR_BACK)
+					_inputState.setCursorShape(CursorShape::Back);
+				else
+					_inputState.setCursorShape(CursorShape::Normal);
+			} else {
+				_inputState.setCursorShape(CursorShape::Normal);
+			}
+
 			_inputState.setHotspot(_noun1 != nullptr);
 			_hud.setVisible(_inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None);
 			_sentence.setVisible(_hud.isVisible());
@@ -351,7 +373,7 @@ void TwpEngine::update(float elapsed) {
 			_noun1 = objAt(roomPos);
 			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->_name);
 			_sentence.setText(cText);
-			// TODO: _inputState.setCursorShape(CursorShape::Normal);
+			_inputState.setCursorShape(CursorShape::Normal);
 			if (_cursor.leftDown)
 				clickedAt(scrPos);
 		}
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index b63fb8a4dc6..3001d40ad4f 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -46,6 +46,7 @@
 #include "twp/callback.h"
 #include "twp/walkboxnode.h"
 
+#define SCREEN_MARGIN 100.f
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
 #define VERBDEFAULT "verbDefault"


Commit: 5bde2ec4c695a7a77dcf480c60de4c117beec8f3
    https://github.com/scummvm/scummvm/commit/5bde2ec4c695a7a77dcf480c60de4c117beec8f3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add missing string functions

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index a604522488a..c687e7718a7 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -37,6 +37,8 @@
 #include "twp/squirrel/sqstdaux.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
+#include "common/crc.h"
+#include "common/stream.h"
 
 namespace Twp {
 
@@ -65,11 +67,8 @@ static void shuffle(Common::Array<T> &array) {
 }
 
 static SQInteger activeVerb(HSQUIRRELVM v) {
-	// TODO: activeVerb
-	// push(v, gEngine.hud.verb.id);
-	// return 1;
-	warning("activeVerb not implemented");
-	return 0;
+	sqpush(v, g_engine->_hud._verb.id.id);
+	return 1;
 }
 
 static SQInteger adhocalytics(HSQUIRRELVM v) {
@@ -770,46 +769,124 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Counts the occurrences of a substring sub in the string `str`.
 static SQInteger strcount(HSQUIRRELVM v) {
-	// TODO: strcount
-	warning("strcount not implemented");
-	return 0;
+	const char* str;
+	const char* sub;
+	if (SQ_FAILED(sqget(v, 2, str)))
+		return sq_throwerror(v, "Failed to get str");
+	if (SQ_FAILED(sqget(v, 3, sub)))
+		return sq_throwerror(v, "Failed to get sub");
+	int count = 0;
+    while ((str = strstr(str, sub))) {
+      str += strlen(sub);
+      ++count;
+    }
+	sq_pushinteger(v, count);
+	return 1;
 }
 
 static SQInteger strcrc(HSQUIRRELVM v) {
-	// TODO: strcrc
-	warning("strcrc not implemented");
-	return 0;
+	Common::CRC32 crc;
+	const SQChar *str;
+    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+      return sq_throwerror(v, _SC("failed to get string"));
+    }
+    uint32 u = crc.crcFast((const byte*)str, strlen(str));
+    sq_pushinteger(v, (SQInteger)u);
+    return 1;
 }
 
 static SQInteger strfind(HSQUIRRELVM v) {
-	// TODO: strfind
-	warning("strfind not implemented");
-	return 0;
+	const SQChar *str1;
+    if (SQ_FAILED(sq_getstring(v, 2, &str1))) {
+      return sq_throwerror(v, _SC("failed to get string1"));
+    }
+    const SQChar *str2;
+    if (SQ_FAILED(sq_getstring(v, 3, &str2))) {
+      return sq_throwerror(v, _SC("failed to get string1"));
+    }
+    const char* p = strstr(str1, str2);
+    if (p == nullptr) {
+      sq_pushinteger(v, -1);
+    } else {
+      sq_pushinteger(v, p - str1);
+    }
+    return 1;
 }
 
 static SQInteger strfirst(HSQUIRRELVM v) {
-	// TODO: strfirst
-	warning("strfirst not implemented");
-	return 0;
+	const SQChar *str;
+    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+      return sq_throwerror(v, _SC("failed to get string"));
+    }
+    if (strlen(str) > 0) {
+      const SQChar s[2]{str[0], '\0'};
+      sq_pushstring(v, s, 1);
+      return 1;
+    }
+    sq_pushnull(v);
+    return 1;
 }
 
 static SQInteger strlast(HSQUIRRELVM v) {
-	// TODO: strlast
-	warning("strlast not implemented");
-	return 0;
+	const SQChar *str;
+    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+      return sq_throwerror(v, _SC("failed to get string"));
+    }
+    auto len = strlen(str);
+    if (len > 0) {
+      const SQChar s[2]{str[len - 1], '\0'};
+      sq_pushstring(v, s, 1);
+      return 1;
+    }
+    sq_pushnull(v);
+    return 1;
 }
 
 static SQInteger strlines(HSQUIRRELVM v) {
-	// TODO: strlines
-	warning("strlines not implemented");
-	return 0;
+	Common::String text;
+    if (SQ_FAILED(sqget(v, 2, text))) {
+      return sq_throwerror(v, _SC("failed to get text"));
+    }
+    Common::String line;
+	Common::MemoryReadStream ms((const byte*)text.c_str(), text.size());
+    sq_newarray(v, 0);
+    while (!ms.eos()) {
+	  line = ms.readLine();
+      sq_pushstring(v, line.c_str(), line.size());
+      sq_arrayappend(v, -2);
+    }
+    return 1;
+}
+
+static void replaceAll(Common::String &text, const Common::String &search, const Common::String &replace) {
+  auto pos = text.find(search);
+  while (pos != Common::String::npos) {
+    text.replace(pos, search.size(), replace);
+    pos = text.find(search, pos + replace.size());
+  }
 }
 
 static SQInteger strreplace(HSQUIRRELVM v) {
-	// TODO: strreplace
-	warning("strreplace not implemented");
-	return 0;
+	const SQChar *input;
+    const SQChar *search;
+    const SQChar *replace;
+    if (SQ_FAILED(sq_getstring(v, 2, &input))) {
+      return sq_throwerror(v, _SC("failed to get input"));
+    }
+    if (SQ_FAILED(sq_getstring(v, 3, &search))) {
+      return sq_throwerror(v, _SC("failed to get search"));
+    }
+    if (SQ_FAILED(sq_getstring(v, 4, &replace))) {
+      return sq_throwerror(v, _SC("failed to get replace"));
+    }
+    Common::String strInput(input);
+    Common::String strSearch(search);
+    Common::String strReplace(replace);
+    replaceAll(strInput, strSearch, strReplace);
+    sq_pushstring(v, strInput.c_str(), strInput.size());
+    return 1;
 }
 
 // Splits the string `str` into substrings using a string separator.


Commit: ac904603480d69bd32de0527108dee3c050b2b13
    https://github.com/scummvm/scummvm/commit/ac904603480d69bd32de0527108dee3c050b2b13
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash when calling leave trigger

Changed paths:
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 55047382e6c..c4618deed08 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -68,6 +68,15 @@ int Object::getId() const {
 	return (int)result;
 }
 
+Common::String Object::getname() const {
+	if ((_table._type == OT_TABLE) && (sqrawexists(_table, "name"))) {
+		Common::String result;
+		sqgetf(_table, "name", result);
+		return result;
+	}
+	return _name;
+}
+
 void Object::setState(int state, bool instant) {
 	play(state, false, instant);
 	_state = state;
diff --git a/engines/twp/object.h b/engines/twp/object.h
index bba68323b51..f72d7fcb7ba 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -117,6 +117,7 @@ public:
 
 	static Object *createActor();
 
+	Common::String getname() const;
 	int getId() const;
 
 	// Changes the `state` of an object, although this can just be a internal state,
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 0d7b2c24f7c..12f2085dfd8 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -46,51 +46,38 @@ namespace Twp {
 static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	HSQUIRRELVM vm = g_engine->getVm();
 	SQInteger size = sq_gettop(v);
-	int id = newThreadId();
+	HSQOBJECT envObj, threadObj, closureObj;
 
-	Thread *t = new Thread(id);
-	t->_global = global;
-
-	sq_newtable(v);
-	sq_pushstring(v, _SC("_id"), -1);
-	sq_pushinteger(v, id);
-	sq_newslot(v, -3, SQFalse);
-	sq_getstackobj(v, -1, &t->_obj);
-	sq_addref(vm, &t->_obj);
-	sq_pop(v, 1);
-
-	sq_resetobject(&t->_envObj);
-	if (SQ_FAILED(sq_getstackobj(v, 1, &t->_envObj)))
+	sq_resetobject(&envObj);
+	if (SQ_FAILED(sq_getstackobj(v, 1, &envObj)))
 		return sq_throwerror(v, "Couldn't get environment from stack");
-	sq_addref(vm, &t->_envObj);
 
 	// create thread and store it on the stack
 	sq_newthread(vm, 1024);
-	sq_resetobject(&t->_threadObj);
-	if (SQ_FAILED(sq_getstackobj(vm, -1, &t->_threadObj)))
+	sq_resetobject(&threadObj);
+	if (SQ_FAILED(sq_getstackobj(vm, -1, &threadObj)))
 		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(vm, &t->_threadObj);
 
+	Common::Array<HSQOBJECT> args;
 	for (int i = 0; i < size - 2; i++) {
 		HSQOBJECT arg;
 		sq_resetobject(&arg);
 		if (SQ_FAILED(sq_getstackobj(v, 3 + i, &arg)))
 			return sq_throwerror(v, "Couldn't get coroutine args from stack");
-		t->_args.push_back(arg);
-		sq_addref(vm, &arg);
+		args.push_back(arg);
 	}
 
 	// get the closure
-	sq_resetobject(&t->_closureObj);
-	if (SQ_FAILED(sq_getstackobj(v, 2, &t->_closureObj)))
+	sq_resetobject(&closureObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &closureObj)))
 		return sq_throwerror(v, "Couldn't get coroutine thread from stack");
-	sq_addref(vm, &t->_closureObj);
 
 	const SQChar *name = nullptr;
 	if (SQ_SUCCEEDED(sq_getclosurename(v, 2)))
 		sq_getstring(v, -1, &name);
 
-	t->setName(Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(t->_closureObj)->_function->_sourcename), _closure(t->_closureObj)->_function->_lineinfos->_line));
+	Common::String threadName = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(closureObj)->_function->_sourcename), _closure(closureObj)->_function->_lineinfos->_line);
+	Thread *t = new Thread(threadName, global, threadObj, envObj, closureObj, args);
 	sq_pop(vm, 1);
 	if (name)
 		sq_pop(v, 1); // pop name
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 80780e5f1c8..47ad22f94a6 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -48,9 +48,23 @@ void ThreadBase::resume() {
 	}
 }
 
-Thread::Thread(int id) {
-	_id = id;
+Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQOBJECT envObj, HSQOBJECT closureObj, const Common::Array<HSQOBJECT> args) {
+	_id = newThreadId();
+	_name = name;
+	_global = global;
+	_threadObj = threadObj;
+	_envObj = envObj;
+	_closureObj = closureObj;
+	_args = args;
 	_pauseable = true;
+
+	HSQUIRRELVM v = g_engine->getVm();
+	for (int i = 0; i < _args.size(); i++) {
+		sq_addref(v, &_args[i]);
+	}
+	sq_addref(v, &_threadObj);
+	sq_addref(v, &_envObj);
+	sq_addref(v, &_closureObj);
 }
 
 Thread::~Thread() {
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index fae4e5cce85..feed4ab7169 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -77,7 +77,7 @@ protected:
 
 class Thread final : public ThreadBase {
 public:
-	Thread(int id);
+	Thread(const Common::String& name, bool global, HSQOBJECT threadObj, HSQOBJECT envObj, HSQOBJECT closureObj, const Common::Array<HSQOBJECT> args);
 	virtual ~Thread() override final;
 
 	virtual bool isGlobal() override final { return _global; }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f59f0f2cec4..82e3df1ba73 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -238,7 +238,7 @@ Common::String TwpEngine::cursorText() {
 		}
 
 		// give can be used only on inventory and talkto to talkable objects (actors)
-		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->_name);
+		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->getname());
 
 		// add verb if not walk to or if noun1 is present
 		if ((_hud._verb.id.id > 1) || (result.size() > 0)) {
@@ -254,7 +254,7 @@ Common::String TwpEngine::cursorText() {
 			else if (_useFlag == ufGiveTo)
 				result += " " + _textDb.getText(10003);
 			if (_noun2)
-				result += " " + _textDb.getText(_noun2->_name);
+				result += " " + _textDb.getText(_noun2->getname());
 		}
 	}
 	return result;
@@ -326,6 +326,9 @@ void TwpEngine::update(float elapsed) {
 				_noun2 = nullptr;
 			}
 
+			// update cursor shape
+			// if cursor is in the margin of the screen and if camera can move again
+			// then show a left arrow or right arrow
 			Math::Vector2d screenSize = _room->getScreenSize();
 			if ((scrPos.getX() < SCREEN_MARGIN) && (cameraPos().getX() >= 1.f)) {
 				_inputState.setCursorShape(CursorShape::Left);
@@ -336,7 +339,7 @@ void TwpEngine::update(float elapsed) {
 				int flags = _noun1->getFlags();
 				if (flags & DOOR_LEFT)
 					_inputState.setCursorShape(CursorShape::Left);
-				else if (flags &DOOR_RIGHT)
+				else if (flags & DOOR_RIGHT)
 					_inputState.setCursorShape(CursorShape::Right);
 				else if (flags & DOOR_FRONT)
 					_inputState.setCursorShape(CursorShape::Front);
@@ -1214,16 +1217,13 @@ void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
 		sq_getclosureinfo(v, -1, &nParams, &nfreevars);
 		sq_pop(v, 1);
 
-		int id = newThreadId();
-		Thread *thread = new Thread(id);
-		thread->setName("Trigger");
-		thread->_global = false;
-		thread->_threadObj = threadObj;
-		thread->_envObj = obj->_table;
-		thread->_closureObj = trigger;
-		if(nParams == 2) {
-			thread->_args.push_back(_actor->_table);
+		Common::Array<HSQOBJECT> args;
+		if (nParams == 2) {
+			args.push_back(_actor->_table);
 		}
+
+		Thread *thread = new Thread("Trigger", false, threadObj, obj->_table, trigger, args);
+
 		debug("create triggerthread id: %d}", thread->getId());
 		g_engine->_threads.push_back(thread);
 


Commit: 987146d451c8c80dceae6650f37a09ab743dde1c
    https://github.com/scummvm/scummvm/commit/987146d451c8c80dceae6650f37a09ab743dde1c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix several bugs

Changed paths:
    engines/twp/hud.h
    engines/twp/object.cpp
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 6efa177be7f..4fc85a8c3df 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -59,6 +59,15 @@ struct ActorSlot {
 	Verb verbs[22];
 	bool selectable;
 	Object *actor;
+
+	Verb *getVerb(int id) {
+		for (int i = 0; i < 22; i++) {
+			if (verbs[i].id.id == id) {
+				return &verbs[i];
+			}
+		}
+		return nullptr;
+	}
 };
 
 class Hud;
@@ -67,7 +76,7 @@ struct VerbRect {
 	int index;
 };
 
-class HudShader: public Shader {
+class HudShader : public Shader {
 public:
 	HudShader();
 	virtual ~HudShader() override;
@@ -91,13 +100,13 @@ class Hud : public Node {
 public:
 	Hud();
 
-	ActorSlot* actorSlot(Object* actor);
+	ActorSlot *actorSlot(Object *actor);
 	bool isOver() const { return _over; }
-	void update(Math::Vector2d pos, Object* hotspot, bool mouseClick);
+	void update(Math::Vector2d pos, Object *hotspot, bool mouseClick);
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
-	void drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
+	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 
 public:
 	ActorSlot _actorSlots[NUMACTORS];
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index c4618deed08..cc5e5886ea9 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -633,7 +633,7 @@ void Object::execVerb() {
 		debug("actorArrived: exec sentence");
 		if (!noun1->inInventory()) {
 			// Object became untouchable as we were walking there
-			if (!noun1->_touchable) {
+			if (!noun1->touchable()) {
 				debug("actorArrived: noun1 untouchable");
 				_exec.enabled = false;
 				return;
@@ -651,7 +651,7 @@ void Object::execVerb() {
 			}
 		}
 		if (noun2 && !noun2->inInventory()) {
-			if (!noun2->_touchable) {
+			if (!noun2->touchable()) {
 				// Object became untouchable as we were walking there.
 				debug("actorArrived: noun2 untouchable");
 				_exec.enabled = false;
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 0567afa262b..ef14139bec6 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -603,7 +603,7 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 
 	_obj = nullptr;
 	if (_actor) {
-		Math::Vector2d scrPos = g_engine->_cursor.pos;
+		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
 
 		// update mouse click
 		bool down = g_engine->_cursor.leftDown;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 82e3df1ba73..74cf5ac8785 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -57,8 +57,8 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	g_engine = this;
 	sq_resetobject(&_defaultObj);
 	_screenScene.setName("Screen");
-	_scene.addChild(&_walkboxNode);
-	_screenScene.addChild(&_pathNode);
+	// _scene.addChild(&_walkboxNode);
+	// _screenScene.addChild(&_pathNode);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
@@ -69,7 +69,7 @@ TwpEngine::~TwpEngine() {
 	delete _screen;
 }
 
-static Math::Vector2d winToScreen(Math::Vector2d pos) {
+Math::Vector2d TwpEngine::winToScreen(Math::Vector2d pos) {
 	return Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
 }
 
@@ -80,7 +80,6 @@ Math::Vector2d TwpEngine::roomToScreen(Math::Vector2d pos) {
 
 Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
 	Math::Vector2d screenSize = _room->getScreenSize();
-	pos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
 	return (pos * screenSize) / Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT) + _gfx.cameraPos();
 }
 
@@ -154,9 +153,9 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	debug("noun1.inInventory: {noun1.inInventory} and noun1.touchable: {noun1.touchable} nowalk: {verbNoWalkTo(verbId, noun1)}");
 
 	// test if object became untouchable
-	if (!noun1->inInventory() && !noun1->_touchable)
+	if (!noun1->inInventory() && !noun1->touchable())
 		return false;
-	if (noun2 && (!noun2->inInventory()) && (!noun2->_touchable))
+	if (noun2 && (!noun2->inInventory()) && (!noun2->touchable()))
 		return false;
 
 	if (noun1->inInventory()) {
@@ -268,7 +267,7 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 		Layer *layer = g_engine->_room->_layers[i];
 		for (int j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
-			if (obj != g_engine->_actor && (obj->_touchable || obj->inInventory()) && obj->_node->isVisible() && obj->_objType == otNone && obj->contains(pos))
+			if ((obj != g_engine->_actor) && (obj->touchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
 				if (func(obj))
 					return;
 		}
@@ -356,6 +355,8 @@ void TwpEngine::update(float elapsed) {
 			_sentence.setVisible(_hud.isVisible());
 			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
 			//_actorSwitcher.visible = _dialog.state == DialogState.None and self.cutscene.isNil;
+			//Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
+			//_sentence.setText(cursortxt.c_str());
 			_sentence.setText(cursorText());
 
 			// call clickedAt if any button down
@@ -382,7 +383,7 @@ void TwpEngine::update(float elapsed) {
 		}
 
 		if (_cursor.leftDown || _cursor.rightDown)
-			clickedAt(_cursor.pos);
+			clickedAt(winToScreen(_cursor.pos));
 	}
 
 	_dialog.update(elapsed);
@@ -888,7 +889,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	for (int i = 0; i < room->_layers.size(); i++) {
 		Layer *layer = room->_layers[i];
 		for (int j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[i];
+			Object *obj = layer->_objects[j];
 			if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
 		}
@@ -941,8 +942,8 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 		// delete all temporary objects
 		for (int i = 0; i < _room->_layers.size(); i++) {
 			Layer *layer = _room->_layers[i];
-			for (int j = 0; j < _room->_layers.size(); j++) {
-				Object *obj = layer->_objects[i];
+			for (int j = 0; j < layer->_objects.size(); j++) {
+				Object *obj = layer->_objects[j];
 				if (obj->_temporary) {
 					delete obj;
 				} else if (isActor(obj->getId()) && _actor != obj) {
@@ -1117,13 +1118,13 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	Common::String verbFuncName = _hud.actorSlot(actor)->verbs[verbId.id].fun;
+	Common::String verbFuncName = _hud.actorSlot(actor)->getVerb(verbId.id)->fun;
 	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable
-	if (!noun1->inInventory() && !noun1->_touchable)
+	if (!noun1->inInventory() && !noun1->touchable())
 		return false;
-	if (noun2 && !noun2->inInventory() && !noun2->_touchable)
+	if (noun2 && !noun2->inInventory() && !noun2->touchable())
 		return false;
 
 	// check if verb is use and object can be used with or in or on
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 3001d40ad4f..bc9ab970759 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -122,6 +122,7 @@ public:
 		return syncGame(s);
 	}
 
+	Math::Vector2d winToScreen(Math::Vector2d pos);
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 0d11adbb0bc..dc99bd9f788 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -152,7 +152,8 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		Math::Matrix4 t;
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
-		Math::Vector2d scrPos = g_engine->_cursor.pos;
+
+		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
 		Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
 		Math::Vector2d p = fixPos(roomPos);
 		t = Math::Matrix4();
@@ -164,11 +165,13 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		Common::Array<Vertex> vertices;
 		for (int i = 0; i < path.size(); i++) {
 			Vertex v;
-			v.pos = g_engine->roomToScreen(path[i]);;
+			v.pos = g_engine->roomToScreen(path[i]);
 			v.color = yellow;
 			vertices.push_back(v);
 		}
-		g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+		if (vertices.size() > 0) {
+			g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+		}
 	}
 }
 


Commit: 65ac57b9911806494896841c673b621bd344f0e7
    https://github.com/scummvm/scummvm/commit/65ac57b9911806494896841c673b621bd344f0e7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Support UTF-8 text

Changed paths:
    engines/twp/font.cpp
    engines/twp/font.h


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index f9a7adc8214..c64720bf5a8 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -54,16 +54,16 @@ typedef struct Line {
 
 class TokenReader {
 public:
-	TokenReader(const Common::String &text);
-	Common::String substr(Token tok);
+	TokenReader(const Common::U32String &text);
+	Common::U32String substr(Token tok);
 	bool readToken(Token &token);
 
 private:
-	char readChar();
+	CodePoint readChar();
 	TokenId readTokenId();
 
 private:
-	Common::String _text;
+	Common::U32String _text;
 	int _off;
 };
 
@@ -93,17 +93,17 @@ static void addGlyphQuad(Texture *texture, Common::Array<Vertex> &vertices, Char
 
 // Skips all characters while one char from the set `token` is found.
 // Returns number of characters skipped.
-static int skipWhile(const char *s, const char *toSkip, int start = 0) {
+static int skipWhile(const Common::U32String& s, const char *toSkip, int start = 0) {
 	int result = 0;
-	int len = strlen(s);
+	int len = s.size();
 	while ((start + result < len) && strchr(toSkip, s[result + start]))
 		result++;
 	return result;
 }
 
-static int skipUntil(const char *s, const char *until, int start = 0) {
+static int skipUntil(const Common::U32String& s, const char *until, int start = 0) {
 	int result = 0;
-	int len = strlen(s);
+	int len = s.size();
 	while ((start + result < len) && !strchr(until, s[result + start]))
 		result++;
 	return result;
@@ -119,14 +119,14 @@ static float width(Text &text, TokenReader &reader, Token tok) {
 	return result;
 }
 
-TokenReader::TokenReader(const Common::String &text) : _text(text), _off(0) {
+TokenReader::TokenReader(const Common::U32String &text) : _text(text), _off(0) {
 }
 
-Common::String TokenReader::substr(Token tok) {
+Common::U32String TokenReader::substr(Token tok) {
 	return _text.substr(tok.startOff, tok.endOff - tok.startOff + 1);
 }
 
-char TokenReader::readChar() {
+CodePoint TokenReader::readChar() {
 	char result = _text[_off];
 	_off++;
 	return result;
@@ -142,13 +142,13 @@ TokenId TokenReader::readTokenId() {
 			return tiNewLine;
 		case '\t':
 		case ' ':
-			_off += skipWhile(_text.c_str(), Whitespace, _off);
+			_off += skipWhile(_text, Whitespace, _off);
 			return tiWhitespace;
 		case '#':
 			_off += 7;
 			return tiColor;
 		default:
-			_off += skipUntil(_text.c_str(), Whitespace2, _off);
+			_off += skipUntil(_text, Whitespace2, _off);
 			return tiString;
 		}
 	} else {
@@ -309,10 +309,11 @@ void Text::update() {
 				tok = line.tokens[j];
 				if (tok.id == tiColor) {
 					int iColor;
-					sscanf(reader.substr(tok).c_str() + 1, "%x", &iColor);
+					Common::String s = reader.substr(tok);
+					sscanf(s.c_str() + 1, "%x", &iColor);
 					color = Color::withAlpha(Color::rgb(iColor & 0x00FFFFFF), color.rgba.a);
 				} else {
-					Common::String s = reader.substr(tok);
+					Common::U32String s = reader.substr(tok);
 					for (int k = 0; k < s.size(); k++) {
 						CodePoint c = s[k];
 						Glyph glyph = _font->getGlyph(c);
diff --git a/engines/twp/font.h b/engines/twp/font.h
index b4864d20fe1..3cfb32ef0f3 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -28,6 +28,12 @@
 #include "common/hashmap.h"
 #include "common/str.h"
 
+namespace Common {
+template<> struct Hash<Common::u32char_type_t> : public UnaryFunction<Common::u32char_type_t, uint> {
+	uint operator()(Common::u32char_type_t val) const { return (uint)val; }
+};
+}
+
 namespace Twp {
 
 struct KerningKey {
@@ -48,7 +54,7 @@ struct Hash<Twp::KerningKey> : public Common::UnaryFunction<Twp::KerningKey, uin
 } // namespace Common
 
 namespace Twp {
-typedef int32 CodePoint;
+typedef Common::u32char_type_t CodePoint;
 
 // represents a glyph: a part of an image for a specific font character
 struct Glyph {


Commit: 09f6f404c52e4befbadc097851266b4a7b1db847
    https://github.com/scummvm/scummvm/commit/09f6f404c52e4befbadc097851266b4a7b1db847
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add actorswitcher and enginedialogtarget

Changed paths:
  A engines/twp/actorswitcher.cpp
  A engines/twp/actorswitcher.h
  A engines/twp/enginedialogtarget.cpp
  A engines/twp/enginedialogtarget.h
    engines/twp/actorlib.cpp
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 6340eef22a4..28cc4498a7c 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -385,23 +385,22 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 		int selectable;
 		if (SQ_FAILED(sqget(v, 2, selectable)))
 			return sq_throwerror(v, "failed to get selectable");
-		// TODO:
-		// switch(selectable) {
-		// case 0:
-		//   g_engine->actorSwitcher.mode.excl asOn;
-		//   break;
-		// case 1:
-		//   g_engine->actorSwitcher.mode.incl asOn;
-		//   break;
-		// case 2:
-		//   g_engine->actorSwitcher.mode.incl asTemporaryUnselectable;
-		//   break;
-		// case 3:
-		//   g_engine->actorSwitcher.mode.excl asTemporaryUnselectable;
-		//   break;
-		// default:
-		//   return sq_throwerror(v, "invalid selectable value");
-		// }
+		switch(selectable) {
+		case 0:
+		  g_engine->_actorSwitcher._mode &= (~asOn);
+		  break;
+		case 1:
+		  g_engine->_actorSwitcher._mode |= asOn;
+		  break;
+		case 2:
+		  g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
+		  break;
+		case 3:
+		  g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
+		  break;
+		default:
+		  return sq_throwerror(v, "invalid selectable value");
+		}
 		return 0;
 	}
 	case 3: {
@@ -806,7 +805,10 @@ static SQInteger createActor(HSQUIRRELVM v) {
 }
 
 static SQInteger flashSelectableActor(HSQUIRRELVM v) {
-	warning("TODO: flashSelectableActor not implemented");
+	int time = 0;
+	if (SQ_FAILED(sqget(v, 2, time)))
+		return sq_throwerror(v, "failed to get time");
+	g_engine->flashSelectableActor(time);
 	return 0;
 }
 
@@ -837,20 +839,11 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 			}
 		}
 	}
-	debug("sayline: %s, {texts}", obj->_key.c_str());
+	debug("sayline: %s, %s", obj->_key.c_str(), join(texts, "|").c_str());
 	obj->say(texts, obj->_talkColor);
 	return 0;
 }
 
-static void stopTalking() {
-	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
-		Layer *layer = *it;
-		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
-			(*it2)->stopTalking();
-		}
-	}
-}
-
 // Causes an actor to say a line of dialog and play the appropriate talking animations.
 // In the first example, the actor ray will say the line.
 // In the second, the selected actor will say the line.
@@ -858,7 +851,7 @@ static void stopTalking() {
 // See also:
 // - `mumbleLine method`
 static SQInteger sayLine(HSQUIRRELVM v) {
-	stopTalking();
+	g_engine->stopTalking();
 	return sayOrMumbleLine(v);
 }
 
@@ -964,7 +957,7 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 2) {
 		if (sq_gettype(v, 2) == OT_INTEGER) {
-			stopTalking();
+			g_engine->stopTalking();
 		} else {
 			Object *actor = sqobj(v, 2);
 			if (!actor)
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
new file mode 100644
index 00000000000..cf32715f8fc
--- /dev/null
+++ b/engines/twp/actorswitcher.cpp
@@ -0,0 +1,166 @@
+/* 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 "twp/actorswitcher.h"
+#include "twp/twp.h"
+#include "twp/util.h"
+
+#define DISABLE_ALPHA 0.f
+#define ENABLE_ALPHA 1.f
+#define INACTIVE_ALPHA 0.5f
+#define ACTOR_SEP 60.f
+#define MARGIN 30.f
+#define ANIM_DURATION 0.120f
+
+namespace Twp {
+
+ActorSwitcherSlot::ActorSwitcherSlot(const Common::String &icon, Color back, Color frame, SelectFunc *selectFunc, int id) {
+	this->icon = icon;
+	this->back = back;
+	this->frame = frame;
+	this->selectFunc = selectFunc;
+	this->id = id;
+}
+
+void ActorSwitcherSlot::select() {
+	if(selectFunc) {
+		selectFunc(id);
+	}
+}
+
+ActorSwitcher::ActorSwitcher() : Node("ActorSwitcher") {
+	_alpha = ENABLE_ALPHA;
+}
+
+Math::Matrix4 ActorSwitcher::transform(Math::Matrix4 trsf, int index) {
+	float animPos = _mouseOver ? _animPos : 1.f;
+	Math::Vector3d pos(SCREEN_WIDTH - MARGIN, SCREEN_HEIGHT - MARGIN - animPos * ACTOR_SEP * index, 0.f);
+	Math::Vector2d s(2.f, 2.f);
+	trsf.translate(pos);
+	Twp::scale(trsf, s);
+	return trsf;
+}
+
+float ActorSwitcher::getAlpha(int index) const {
+	if (index == (_slots.size() - 1))
+		return ENABLE_ALPHA;
+	if (_mode & asTemporaryUnselectable)
+		return INACTIVE_ALPHA;
+	if (_mode & asOn)
+		return _mouseOver ? ENABLE_ALPHA : _alpha;
+	return _mouseOver ? INACTIVE_ALPHA : DISABLE_ALPHA;
+}
+
+void ActorSwitcher::drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index) {
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	const SpriteSheetFrame &iconBackFrame = gameSheet->frameTable["icon_background"];
+	const SpriteSheetFrame &iconActorFrame = gameSheet->frameTable[icon];
+	const SpriteSheetFrame &iconFrame = gameSheet->frameTable["icon_frame"];
+	Math::Matrix4 t = transform(trsf, index);
+	float alpha = getAlpha(index);
+
+	drawSprite(iconBackFrame, texture, Color::withAlpha(backColor, alpha), t);
+	drawSprite(iconActorFrame, texture, Color::withAlpha(Color(), alpha), t);
+	drawSprite(iconFrame, texture, Color::withAlpha(frameColor, alpha), t);
+}
+
+void ActorSwitcher::drawCore(Math::Matrix4 trsf) {
+	if (_mouseOver) {
+		for (int i = 0; i < _slots.size(); i++) {
+			ActorSwitcherSlot &slot = _slots[i];
+			drawIcon(slot.icon, slot.back, slot.frame, trsf, i);
+		}
+	} else if (_slots.size() > 0) {
+		ActorSwitcherSlot &slot = _slots[0];
+		drawIcon(slot.icon, slot.back, slot.frame, trsf, 0);
+	}
+}
+
+void ActorSwitcher::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
+	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
+	trsf.translate(pos);
+	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+}
+
+float ActorSwitcher::height() const {
+	float n = _mouseOver ? _slots.size() : 1;
+	return n * ACTOR_SEP;
+}
+
+int ActorSwitcher::iconIndex(Math::Vector2d pos) const {
+	float y = SCREEN_HEIGHT - pos.getY();
+	return _slots.size() - 1 - ((height() - y) / ACTOR_SEP);
+}
+
+Common::Rect ActorSwitcher::rect() const {
+	float h = height();
+	return Common::Rect(Common::Point(SCREEN_WIDTH - ACTOR_SEP, SCREEN_HEIGHT - h), ACTOR_SEP, h);
+}
+
+void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float elapsed) {
+	if (_visible) {
+		_slots = slots;
+
+		// update flash icon
+		if ((_flash != 0) && ((_flash == -1) || (_flashElapsed < _flash))) {
+			_flashElapsed = _flashElapsed + elapsed;
+			_alpha = 0.6f + 0.4f * sin(M_PI * 2.f * _flashElapsed);
+		} else {
+			_flash = 0;
+			_flashElapsed = 0.f;
+			_alpha = INACTIVE_ALPHA;
+		}
+
+		// check if mouse is over actor icons or gear icon
+		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
+		bool oldMouseOver = _mouseOver;
+		_mouseOver = !_down && rect().contains(scrPos.getX(), scrPos.getY());
+
+		// update anim
+		_animElapsed = _animElapsed + elapsed;
+
+		// stop anim or flash if necessary
+		if (oldMouseOver != _mouseOver) {
+			_animElapsed = 0.f;
+			if (_mouseOver)
+				_flash = 0;
+		}
+
+		// update anim pos
+		_animPos = MIN(1.f, _animElapsed / ANIM_DURATION);
+
+		// check if we select an actor or gear icon
+		if (_mouseOver && (g_engine->_cursor.leftDown) && !_down) {
+			_down = true;
+			// check if we allow to select an actor
+			int iconIdx = iconIndex(scrPos);
+			if ((_mode & asOn) || (iconIdx == (_slots.size() - 1))) {
+				if (_slots[iconIdx].selectFunc != nullptr)
+					_slots[iconIdx].select();
+			}
+		}
+		if (!g_engine->_cursor.leftDown)
+			_down = false;
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/actorswitcher.h b/engines/twp/actorswitcher.h
new file mode 100644
index 00000000000..fe1a5a3d673
--- /dev/null
+++ b/engines/twp/actorswitcher.h
@@ -0,0 +1,82 @@
+/* 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 TWP_ACTORSWITCHER_H
+#define TWP_ACTORSWITCHER_H
+
+#include "twp/scenegraph.h"
+#include "common/func.h"
+
+namespace Twp {
+
+#define asNone 0
+#define asOn 0x1
+#define asTemporaryUnselectable 0x2
+
+typedef void SelectFunc(int id);
+
+// This is where all the information about the actor icon stands
+struct ActorSwitcherSlot {
+	ActorSwitcherSlot(const Common::String& icon, Color back, Color frame, SelectFunc* selectFunc, int id = 0);
+
+	void select();
+
+	Common::String icon;
+	Color back, frame;
+	SelectFunc* selectFunc;
+	int id;
+};
+
+// This allows to change the selected actors or to display the options (gear icon)
+class ActorSwitcher : public Node {
+public:
+	ActorSwitcher();
+
+	void update(const Common::Array<ActorSwitcherSlot>& slots, float elapsed);
+	bool isMouseOver() const { return _mouseOver; }
+	void setFlash(int flash) { _flash = flash; }
+
+protected:
+	void drawCore(Math::Matrix4 trsf) override final;
+	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
+	void drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index);
+	Math::Matrix4 transform(Math::Matrix4 trsf, int index);
+	float getAlpha(int index) const;
+	float height() const;
+	int iconIndex(Math::Vector2d pos) const;
+	Common::Rect rect() const;
+
+public:
+	int _mode = asNone;        // current mode
+
+private:
+	bool _mouseOver = false;                  // true when mouse is over the icons
+	bool _down = false;                       // true when mouse button is down
+	float _alpha = 0.f;                       // alpha value for the icon when flash is ON (flash != 0)
+	int _flash = 0;                           // flash = 0: disable, flash = -1: enable, other values: time to flash
+	float _flashElapsed = 0.f;                // flash time elapsed in seconds
+	float _animElapsed = 0.f, _animPos = 0.f; // animation time elapsed in seconds and current position in the animation [0f-1f]
+	Common::Array<ActorSwitcherSlot> _slots;  // list of slots containing icon, colors and select function
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 08811653c32..def80b7f0cc 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -58,12 +58,12 @@ ExpVisitor::ExpVisitor(Dialog *dialog) : _dialog(dialog) {}
 ExpVisitor::~ExpVisitor() {}
 
 void ExpVisitor::visit(const YCodeExp &node) {
-	debug("execute code {node.code}");
+	debug("execute code %s", node._code.c_str());
 	sqexec(g_engine->getVm(), node._code.c_str(), "dialog");
 }
 
 void ExpVisitor::visit(const YGoto &node) {
-	debug("execute goto {node.name}");
+	debug("execute goto %s", node._name.c_str());
 	_dialog->selectLabel(node._line, node._name);
 }
 
@@ -74,7 +74,7 @@ void ExpVisitor::visit(const YShutup &node) {
 
 void ExpVisitor::visit(const YPause &node) {
 	debug("pause %d", node._time);
-	_dialog->_action.reset(_dialog->_tgt->pause(node._time));
+	_dialog->_action = _dialog->_tgt->pause(node._time);
 }
 
 void ExpVisitor::visit(const YWaitFor &node) {
@@ -99,7 +99,7 @@ void ExpVisitor::visit(const YAllowObjects &node) {
 
 void ExpVisitor::visit(const YWaitWhile &node) {
 	debug("wait while");
-	_dialog->_action.reset(_dialog->_tgt->waitWhile(node._cond));
+	_dialog->_action = _dialog->_tgt->waitWhile(node._cond);
 }
 
 void ExpVisitor::visit(const YLimit &node) {
@@ -108,7 +108,7 @@ void ExpVisitor::visit(const YLimit &node) {
 }
 
 void ExpVisitor::visit(const YSay &node) {
-	_dialog->_action.reset(_dialog->_tgt->say(node._actor, node._text));
+	_dialog->_action = _dialog->_tgt->say(node._actor, node._text);
 }
 
 CondVisitor::CondVisitor(Dialog *dialog) : _dialog(dialog) {}
@@ -264,11 +264,11 @@ YLabel *Dialog::label(int line, const Common::String &name) const {
 }
 
 void Dialog::selectLabel(int line, const Common::String &name) {
-	//debug("select label %s", name.c_str());
+	debug("select label %s", name.c_str());
 	_lbl = label(line, name);
 	_currentStatement = 0;
 	clearSlots();
-	_state = _lbl ? None : Active;
+	_state = _lbl ? Active: None;
 }
 
 void Dialog::gotoNextLabel() {
@@ -320,7 +320,42 @@ bool Dialog::acceptConditions(YStatement *stmt) {
 	}
 	return true;
 }
-void Dialog::running(float dt) {}
+void Dialog::running(float dt) {
+  if (_action && _action->isEnabled())
+    _action->update(dt);
+  else if (!_lbl)
+    _state = None;
+  else if (_currentStatement == _lbl->_stmts.size())
+    gotoNextLabel();
+  else {
+    _state = Active;
+    while (_lbl && (_currentStatement < _lbl->_stmts.size()) && (_state == Active)) {
+      YStatement* statmt = _lbl->_stmts[_currentStatement];
+	  IsChoice isChoice;
+	  statmt->_exp->accept(isChoice);
+      if (!acceptConditions(statmt))
+        _currentStatement ++;
+      else if (isChoice._isChoice) {
+        addSlot(statmt);
+        _currentStatement++;
+	  }
+      else if (choicesReady())
+        updateChoiceStates();
+      else if (_action && _action->isEnabled()) {
+        _action->update(dt);
+        return;
+	  } else {
+        run(statmt);
+        if (_lbl && (_currentStatement == _lbl->_stmts.size()))
+          gotoNextLabel();
+	  }
+	}
+    if (choicesReady())
+        updateChoiceStates();
+    else if (!_action || !_action->isEnabled())
+      _state = None;
+  }
+}
 
 static Common::String remove(const Common::String &txt, char startC, char endC) {
 	if (txt[0] == startC) {
@@ -343,6 +378,7 @@ void Dialog::addSlot(YStatement *stmt) {
 	YChoice *choice = (YChoice *)stmt->_exp.get();
 	if ((!_slots[choice->_number - 1]._isValid) && (numSlots() < _context.limit)) {
 		DialogSlot *slot = &_slots[choice->_number - 1];
+		slot->_text.setFont("sayline");
 		slot->_text.setText(Common::String::format("● %s", text(choice->_text).c_str()));
 		slot->_stmt = stmt;
 		slot->_dlg = this;
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 204ba2a1c07..109b114046f 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -119,6 +119,15 @@ public:
 	bool _isGoto = false;
 };
 
+class IsChoice : public YackVisitor {
+public:
+	virtual ~IsChoice() override {}
+	void visit(const YChoice &node) override { _isChoice = true; }
+
+public:
+	bool _isChoice = false;
+};
+
 class ExpVisitor : public YackVisitor {
 public:
 	ExpVisitor(Dialog *dialog);
@@ -198,7 +207,7 @@ public:
 	Common::Array<DialogConditionState> _states;
 	DialogContext _context;
 	unique_ptr<DialogTarget> _tgt;
-	unique_ptr<Motor> _action;
+	Motor* _action = nullptr;
 
 private:
 	DialogState _state = DialogState::None;
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
new file mode 100644
index 00000000000..61db0a336cc
--- /dev/null
+++ b/engines/twp/enginedialogtarget.cpp
@@ -0,0 +1,134 @@
+/* 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 "twp/enginedialogtarget.h"
+#include "twp/twp.h"
+
+namespace Twp {
+
+class Pause : public Motor {
+public:
+	Pause(float time) : _time(time) {}
+
+	virtual void update(float elapsed) override {
+		_time -= elapsed;
+		if (_time <= 0)
+			disable();
+	}
+
+private:
+	float _time = 0.f;
+};
+
+class WaitWhile : public Motor {
+public:
+	WaitWhile(EngineDialogTarget* target, const Common::String& cond) : _target(target), _cond(cond) {}
+
+	virtual void update(float elapsed) override {
+		if (!_target->execCond(_cond))
+    		disable();
+	}
+
+private:
+	EngineDialogTarget* _target = nullptr;
+	Common::String _cond;
+};
+
+static Object *actor(const Common::String &name) {
+	// for (actor in gEngine.actors) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *actor = g_engine->_actors[i];
+		if (actor->_key == name)
+			return actor;
+	}
+	return nullptr;
+}
+
+static Object *actorOrCurrent(const Common::String &name) {
+	Object *result = actor(name);
+	if (!result)
+		result = g_engine->_actor;
+	return result;
+}
+
+Color EngineDialogTarget::actorColor(const Common::String &actor) {
+	Object *act = actorOrCurrent(actor);
+	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogNormal;
+}
+
+Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
+	Object *act = actorOrCurrent(actor);
+	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
+}
+
+Motor *EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
+	debug("say %s: %s", actor.c_str(), text.c_str());
+	Object *act = actorOrCurrent(actor);
+	act->say({text}, act->_talkColor);
+	return act->getTalking();
+}
+
+Motor *EngineDialogTarget::waitWhile(const Common::String &cond) {
+	return new WaitWhile(this, cond);
+}
+
+void EngineDialogTarget::shutup() {
+	g_engine->stopTalking();
+}
+
+Motor *EngineDialogTarget::pause(float time) {
+	return new Pause(time);
+}
+
+bool EngineDialogTarget::execCond(const Common::String &cond) {
+	// check if the condition corresponds to an actor name
+	Object *act = actor(cond);
+	if (act) {
+		// yes, so we check if the current actor is the given actor name
+		Object *curActor = g_engine->_actor;
+		return curActor && curActor->_key == act->_key;
+	}
+
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	// compile
+	sq_pushroottable(v);
+	Common::String code = Common::String::format("return %s", cond.c_str());
+	if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "condition", SQTrue))) {
+		debug("Error executing code %s", code.c_str());
+		return false;
+	}
+
+	sq_push(v, -2);
+	// call
+	if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
+		debug("Error calling code {code}");
+		return false;
+	}
+
+	SQInteger condResult;
+	sq_getinteger(v, -1, &condResult);
+	bool result = condResult != 0;
+	sq_settop(v, top);
+	return result;
+}
+
+} // namespace Twp
diff --git a/engines/twp/enginedialogtarget.h b/engines/twp/enginedialogtarget.h
new file mode 100644
index 00000000000..4a04f23773e
--- /dev/null
+++ b/engines/twp/enginedialogtarget.h
@@ -0,0 +1,42 @@
+/* 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 TWP_DIALOG_TARGET_H
+#define TWP_DIALOG_TARGET_H
+
+#include "twp/dialog.h"
+
+namespace Twp {
+
+class EngineDialogTarget : public DialogTarget {
+public:
+	virtual Color actorColor(const Common::String &actor) override;
+	virtual Color actorColorHover(const Common::String &actor) override;
+	virtual Motor *say(const Common::String &actor, const Common::String &text) override;
+	virtual Motor *waitWhile(const Common::String &cond) override;
+	virtual void shutup() override;
+	virtual Motor *pause(float time) override;
+	virtual bool execCond(const Common::String &cond) override;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index c687e7718a7..cb5e089d617 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -756,6 +756,7 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 				obj->_exec.enabled = false;
 			}
 		}
+		break;
 	case 2: {
 		Object *obj = sqobj(v, 2);
 		obj->_exec.enabled = false;
@@ -954,6 +955,7 @@ void sqgame_register_genlib(HSQUIRRELVM v) {
 	regFunc(v, pushSentence, _SC("pushSentence"));
 	regFunc(v, randomFrom, _SC("randomfrom"));
 	regFunc(v, randomOdds, _SC("randomOdds"));
+	regFunc(v, randomOdds, _SC("randomodds"));
 	regFunc(v, randomseed, _SC("randomseed"));
 	regFunc(v, refreshUI, _SC("refreshUI"));
 	regFunc(v, screenSize, _SC("screenSize"));
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 4d9f081e9e8..eea983dd66c 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -63,6 +63,8 @@ MODULE_OBJS = \
 	callback.o \
 	graph.o \
 	walkboxnode.o \
+	actorswitcher.o \
+	enginedialogtarget.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index ef6c33c2a52..559e3fa260d 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -331,7 +331,7 @@ void Talking::say(const Common::String &text) {
 			GGPackEntryReader entry;
 			entry.open(g_engine->_pack, path);
 			_lip.load(&entry);
-			debug("Lip %s loaded: {self.lip}", path.c_str());
+			debug("Lip %s loaded", path.c_str());
 		}
 
 		// TODO: call sayingLine
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index fa1448ed5d7..56d773319e9 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -226,6 +226,7 @@ private:
 class Talking : public Motor {
 public:
 	Talking(Object* obj, const Common::StringArray& texts, Color color);
+	virtual ~Talking() {}
 
 private:
 	virtual void update(float elapsed) override;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index cc5e5886ea9..c59046e2675 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -221,7 +221,7 @@ Math::Vector2d Object::getUsePos() {
 	return isActor(getId()) ? _node->getPos() + _node->getOffset() : _node->getPos() + _node->getOffset() + _usePos;
 }
 
-bool Object::touchable() {
+bool Object::isTouchable() {
 	if (_objType == otNone) {
 		if (_state == GONE) {
 			return false;
@@ -304,10 +304,12 @@ int Object::getFlags() {
 }
 
 void Object::setRoom(Room *room) {
-	if (_room != room) {
-		stopObjectMotors();
+	if ((_room != room) || !_node->getParent()) {
+		if(_room != room) {
+			stopObjectMotors();
+		}
 		Room *oldRoom = _room;
-		if (oldRoom) {
+		if (oldRoom && _node->getParent()) {
 			debug("Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
 			Layer *layer = oldRoom->layer(0);
 			if (layer) {
@@ -456,7 +458,6 @@ void Object::stand() {
 #define SET_MOTOR(motorTo)     \
 	if (_##motorTo) {          \
 		_##motorTo->disable(); \
-		delete _##motorTo;     \
 	}                          \
 	_##motorTo = motorTo;
 
@@ -633,7 +634,7 @@ void Object::execVerb() {
 		debug("actorArrived: exec sentence");
 		if (!noun1->inInventory()) {
 			// Object became untouchable as we were walking there
-			if (!noun1->touchable()) {
+			if (!noun1->isTouchable()) {
 				debug("actorArrived: noun1 untouchable");
 				_exec.enabled = false;
 				return;
@@ -641,7 +642,7 @@ void Object::execVerb() {
 			// Did we get close enough?
 			float dist = distance(getUsePos(), noun1->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
-			debug("actorArrived: noun1 min_dist: {dist} > {min_dist} (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?");
+			debug("actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
 				cantReach(this, noun1);
 				return;
@@ -651,7 +652,7 @@ void Object::execVerb() {
 			}
 		}
 		if (noun2 && !noun2->inInventory()) {
-			if (!noun2->touchable()) {
+			if (!noun2->isTouchable()) {
 				// Object became untouchable as we were walking there.
 				debug("actorArrived: noun2 untouchable");
 				_exec.enabled = false;
diff --git a/engines/twp/object.h b/engines/twp/object.h
index f72d7fcb7ba..22871bb314e 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -137,7 +137,7 @@ public:
 	void setState(int state, bool instant = false);
 	int getState() const { return _state; }
 
-	bool touchable();
+	bool isTouchable();
 	void setTouchable(bool value);
 
 	void play(int state, bool loop = false, bool instant = false);
@@ -236,7 +236,6 @@ public:
 	Room *_room = nullptr;
 	Common::Array<ObjectAnimation> _anims;
 	bool _temporary = false;
-	bool _touchable = false;
 	Node *_node = nullptr;
 	Node *_sayNode = nullptr;
 	Anim *_nodeAnim = nullptr;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index fb36c3bb80d..4c92812fb99 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -770,14 +770,14 @@ static SQInteger objectTouchable(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get object");
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 2) {
-		sqpush(v, obj->_touchable);
+		sqpush(v, obj->isTouchable());
 		return 1;
 	}
 	if (nArgs == 3) {
 		bool touchable;
 		if (SQ_FAILED(sqget(v, 3, touchable)))
 			return sq_throwerror(v, "failed to get touchable");
-		obj->_touchable = touchable;
+		obj->setTouchable(touchable);
 		return 0;
 	}
 	return sq_throwerror(v, "objectTouchable: invalid argument");
@@ -917,8 +917,26 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 }
 
 static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
-	// TODO: pickupReplacementObject
-	warning("pickupReplacementObject not implemented");
+	Object *obj1 = sqobj(v, 2);
+	if (!obj1)
+		return sq_throwerror(v, "failed to get object 1");
+	Object *obj2 = sqobj(v, 3);
+	if (!obj2)
+		return sq_throwerror(v, "failed to get object 2");
+
+	// remove obj1 from inventory's owner
+	if (obj1->_owner) {
+		int index = find(obj1->_owner->_inventory, obj1);
+		obj1->_owner->_inventory.remove_at(index);
+		obj1->_owner = nullptr;
+	}
+
+	// replace obj2 by obj1
+	Object *owner = obj2->_owner;
+	int index = find(obj2->_owner->_inventory, obj2);
+	owner->_inventory[index] = obj1;
+	obj1->_owner = owner;
+	obj2->_owner = nullptr;
 	return 0;
 }
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index ede917c10f8..16ef0c6649a 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -177,7 +177,6 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 
 	obj->_room = this;
 	obj->_sheet = sheet;
-	obj->_touchable = false;
 
 	// create anim if any
 	if (frames.size() > 0) {
@@ -215,7 +214,6 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
 	obj->_node->setName(obj->_key);
-	obj->_touchable = false;
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 36910c7b1b4..f08775a6135 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -48,8 +48,46 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 }
 
 static SQInteger clampInWalkbox(HSQUIRRELVM v) {
-	warning("TODO: clampInWalkbox not implemented");
-	return 0;
+	SQInteger numArgs = sq_gettop(v);
+	Math::Vector2d pos1, pos2;
+	if (numArgs == 3) {
+		int x = 0;
+		if (SQ_FAILED(sqget(v, 2, x)))
+			return sq_throwerror(v, "failed to get x");
+		int y = 0;
+		if (SQ_FAILED(sqget(v, 3, y)))
+			return sq_throwerror(v, "failed to get y");
+		pos1 = Math::Vector2d(x, y);
+		pos2 = pos1;
+	} else if (numArgs == 5) {
+		int x1 = 0;
+		if (SQ_FAILED(sqget(v, 2, x1)))
+			return sq_throwerror(v, "failed to get x1");
+		int y1 = 0;
+		if (SQ_FAILED(sqget(v, 3, y1)))
+			return sq_throwerror(v, "failed to get y1");
+		pos1 = Math::Vector2d(x1, y1);
+		int x2 = 0;
+		if (SQ_FAILED(sqget(v, 4, x2)))
+			return sq_throwerror(v, "failed to get x2");
+		int y2 = 0;
+		if (SQ_FAILED(sqget(v, 5, y1)))
+			return sq_throwerror(v, "failed to get y2");
+		pos2 = Math::Vector2d(x2, y2);
+	} else {
+		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
+	}
+	const Common::Array<Walkbox> &walkboxes = g_engine->_room->_walkboxes;
+	for (int i = 0; i < walkboxes.size(); i++) {
+		const Walkbox &walkbox = walkboxes[i];
+		if (walkbox.contains(pos1)) {
+			sqpush(v, pos1);
+			return 1;
+		}
+	}
+	Math::Vector2d pos = walkboxes[0].getClosestPointOnEdge(pos2);
+	sqpush(v, pos);
+	return 1;
 }
 
 static SQInteger createLight(HSQUIRRELVM v) {
@@ -241,7 +279,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get object");
 		int i = find(g_engine->_room->_triggers, obj);
 		if (i != -1) {
-			debug("Remove room trigger: {obj.name}({obj.key})");
+			debug("Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
 			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
 		}
 		return 0;
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index ef14139bec6..54ed430dbd8 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -201,11 +201,6 @@ Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
 	return parentTrsf * getLocalTrsf();
 }
 
-static void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
-	m(0, 0) *= v.getX();
-	m(1, 1) *= v.getY();
-}
-
 Math::Matrix4 Node::getLocalTrsf() {
 	Math::Vector2d p = _pos + _offset;
 	Math::Matrix4 m1;
@@ -411,6 +406,7 @@ Scene::Scene() : Node("Scene") {
 Scene::~Scene() {}
 
 InputState::InputState() : Node("InputState") {
+	_zOrder = -100;
 }
 
 InputState::~InputState() {}
@@ -420,7 +416,7 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
 	// TODO: if prefs(ClassicSentence) and self.hotspot:
-	Common::String cursorName = _hotspot ? Common::String::format("hotspot_%s", _cursorName.c_str()): _cursorName;
+	Common::String cursorName = _hotspot ? Common::String::format("hotspot_%s", _cursorName.c_str()) : _cursorName;
 	const SpriteSheetFrame &sf = gameSheet->frameTable[cursorName];
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos * 2.f);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 74cf5ac8785..218d9066599 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -45,6 +45,7 @@
 #include "twp/task.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/yack.h"
+#include "twp/enginedialogtarget.h"
 
 namespace Twp {
 
@@ -55,6 +56,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	  _gameDescription(gameDesc),
 	  _randomSource("Twp") {
 	g_engine = this;
+	_dialog._tgt.reset(new EngineDialogTarget());
 	sq_resetobject(&_defaultObj);
 	_screenScene.setName("Screen");
 	// _scene.addChild(&_walkboxNode);
@@ -63,6 +65,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
 	_screenScene.addChild(&_uiInv);
+	_screenScene.addChild(&_actorSwitcher);
 }
 
 TwpEngine::~TwpEngine() {
@@ -143,19 +146,19 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	debug("exec({name},{verbId.VerbId},{noun1name},{noun2name})");
+	debug("exec(%s,%d,%s,%s)", name.c_str(), verbId.id, noun1name.c_str(), noun2name.c_str());
 	actor = !actor ? g_engine->_actor : actor;
 	if ((verbId.id <= 0) || (verbId.id > 13) || (!noun1) || (!actor))
 		return false;
 	// TODO
 	// if (a?._verb_tid) stopthread(actor._verb_tid)
 
-	debug("noun1.inInventory: {noun1.inInventory} and noun1.touchable: {noun1.touchable} nowalk: {verbNoWalkTo(verbId, noun1)}");
+	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory()?"YES":"NO", noun1->isTouchable()?"YES":"NO", verbNoWalkTo(verbId, noun1)?"YES":"NO");
 
 	// test if object became untouchable
-	if (!noun1->inInventory() && !noun1->touchable())
+	if (!noun1->inInventory() && !noun1->isTouchable())
 		return false;
-	if (noun2 && (!noun2->inInventory()) && (!noun2->touchable()))
+	if (noun2 && (!noun2->inInventory()) && (!noun2->isTouchable()))
 		return false;
 
 	if (noun1->inInventory()) {
@@ -187,9 +190,12 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	return true;
 }
 
+void TwpEngine::flashSelectableActor(int flash) {
+	_actorSwitcher.setFlash(flash);
+}
+
 void TwpEngine::clickedAt(Math::Vector2d scrPos) {
-	// TODO: update this
-	if (_room) {
+	if (_room && _inputState.getInputActive() && !_actorSwitcher.isMouseOver()) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		Object *obj = objAt(roomPos);
 
@@ -267,7 +273,7 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 		Layer *layer = g_engine->_room->_layers[i];
 		for (int j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
-			if ((obj != g_engine->_actor) && (obj->touchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
+			if ((obj != g_engine->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
 				if (func(obj))
 					return;
 		}
@@ -286,6 +292,44 @@ Object *inventoryAt(Math::Vector2d pos) {
 	return result;
 }
 
+static void selectSlotActor(int id) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		if (g_engine->_actors[i]->getId() == id) {
+			g_engine->setActor(g_engine->_actors[i]);
+			break;
+		}
+	}
+}
+
+ActorSwitcherSlot TwpEngine::actorSwitcherSlot(ActorSlot *slot) {
+	return ActorSwitcherSlot(slot->actor->getIcon(),
+							 slot->verbUiColors.inventoryBackground,
+							 slot->verbUiColors.inventoryFrame, selectSlotActor, slot->actor->getId());
+}
+
+Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
+	Common::Array<ActorSwitcherSlot> result;
+	if (_actor) {
+		// add current actor first
+		{
+			ActorSlot *slot = _hud.actorSlot(_actor);
+			result.push_back(actorSwitcherSlot(slot));
+		}
+
+		// then other selectable actors
+		for (int i = 0; i < NUMACTORS; i++) {
+			ActorSlot *slot = &_hud._actorSlots[i];
+			if (slot->selectable && slot->actor && (slot->actor != _actor) && (slot->actor->_room->_name != "Void"))
+				result.push_back(actorSwitcherSlot(slot));
+		}
+
+		// add gear icon
+		// TODO: result.push_back(ActorSwitcherSlot("icon_gear", Black, Gray, showOptions));
+		result.push_back(ActorSwitcherSlot("icon_gear", Color(0.f, 0.f, 0.f), Color(0.8f, 0.8f, 0.8f), selectSlotActor));
+	}
+	return result;
+}
+
 void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
@@ -355,7 +399,7 @@ void TwpEngine::update(float elapsed) {
 			_sentence.setVisible(_hud.isVisible());
 			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
 			//_actorSwitcher.visible = _dialog.state == DialogState.None and self.cutscene.isNil;
-			//Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
+			// Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
 			//_sentence.setText(cursortxt.c_str());
 			_sentence.setText(cursorText());
 
@@ -392,6 +436,9 @@ void TwpEngine::update(float elapsed) {
 	// update camera
 	_camera.update(_room, _followActor, elapsed);
 
+	// update actorswitcher
+	_actorSwitcher.update(actorSwitcherSlots(), elapsed);
+
 	// update cutscene
 	if (_cutscene) {
 		if (_cutscene->update(elapsed)) {
@@ -612,28 +659,22 @@ Common::Error TwpEngine::run() {
 
 	_vm.exec(code);
 
+	static int speed = 1;
+
 	// Simple event handling loop
 	Common::Event e;
 	uint time = _system->getMillis();
 	while (!shouldQuit()) {
-		const int dx = 4;
-		const int dy = 4;
 		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
 			switch (e.type) {
 			case Common::EVENT_KEYDOWN:
 				switch (e.kbd.keycode) {
 				case Common::KEYCODE_LEFT:
-					camPos.setX(camPos.getX() - dx);
+					speed = MAX(speed - 1, 1);
 					break;
 				case Common::KEYCODE_RIGHT:
-					camPos.setX(camPos.getX() + dx);
-					break;
-				case Common::KEYCODE_UP:
-					camPos.setY(camPos.getY() + dy);
-					break;
-				case Common::KEYCODE_DOWN:
-					camPos.setY(camPos.getY() - dy);
+					speed = MIN(speed + 1, 8);
 					break;
 				default:
 					break;
@@ -666,7 +707,7 @@ Common::Error TwpEngine::run() {
 		uint32 newTime = _system->getMillis();
 		uint32 delta = newTime - time;
 		time = newTime;
-		update(8.f * delta / 1000.f);
+		update(speed * delta / 1000.f);
 		draw();
 		_cursor.update();
 
@@ -738,9 +779,9 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 					obj->setState(0, true);
 
 					if (obj->_objType == otNone)
-						obj->_touchable = false;
+						obj->setTouchable(false);
 				} else if (obj->_objType == otNone) {
-					obj->_touchable = true;
+					obj->setTouchable(true);
 				}
 
 				layerNode->addChild(obj->_node);
@@ -872,7 +913,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 		cancelSentence();
 		if (door) {
 			Facing facing = getOppositeFacing(door->getDoorFacing());
-			_actor->_room = room;
+			_actor->setRoom(room);
 			if (door) {
 				_actor->setFacing(facing);
 				_actor->_node->setPos(door->getUsePos());
@@ -1122,9 +1163,9 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable
-	if (!noun1->inInventory() && !noun1->touchable())
+	if (!noun1->inInventory() && !noun1->isTouchable())
 		return false;
-	if (noun2 && !noun2->inInventory() && !noun2->touchable())
+	if (noun2 && !noun2->inInventory() && !noun2->isTouchable())
 		return false;
 
 	// check if verb is use and object can be used with or in or on
@@ -1252,4 +1293,13 @@ void TwpEngine::updateTriggers() {
 	}
 }
 
+void TwpEngine::stopTalking() {
+	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
+		Layer *layer = *it;
+		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
+			(*it2)->stopTalking();
+		}
+	}
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index bc9ab970759..5f316dccd9d 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -45,6 +45,7 @@
 #include "twp/hud.h"
 #include "twp/callback.h"
 #include "twp/walkboxnode.h"
+#include "twp/actorswitcher.h"
 
 #define SCREEN_MARGIN 100.f
 #define SCREEN_WIDTH 1280
@@ -128,6 +129,8 @@ public:
 
 	void setActor(Object *actor, bool userSelected = false);
 	Object *objAt(Math::Vector2d pos);
+	void flashSelectableActor(int flash);
+	void stopTalking();
 
 	Room *defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
@@ -161,6 +164,8 @@ private:
 	bool preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2);
 	void updateTriggers();
 	void callTrigger(Object *obj, HSQOBJECT trigger);
+	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
+	ActorSwitcherSlot actorSwitcherSlot(ActorSlot* slot);
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -207,6 +212,7 @@ public:
 	} _cursor;
 	Hud _hud;
 	Inventory _uiInv;
+	ActorSwitcher _actorSwitcher;
 
 private:
 	Gfx _gfx;
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index ff43ba1670b..a3ae47ae644 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -154,4 +154,20 @@ float distance(Math::Vector2d p1, Math::Vector2d p2) {
 	return sqrt(distanceSquared(p1, p2));
 }
 
+Common::String join(const Common::Array<Common::String> &array, const Common::String &sep) {
+	Common::String result;
+	if (array.size() > 0) {
+		result += array[0];
+		for (int i = 1; i < array.size(); i++) {
+			result += (sep + array[i]);
+		}
+	}
+	return result;
+}
+
+void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
+	m(0, 0) *= v.getX();
+	m(1, 1) *= v.getY();
+}
+
 } // namespace Twp
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 564aa0ef7b0..0d8249b78a0 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -54,7 +54,7 @@ float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
 float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
 
 template<typename T>
-int find(Common::Array<T>& array, const T& o) {
+int find(const Common::Array<T>& array, const T& o) {
 	int index = -1;
 	for (int i = 0; i < array.size(); i++) {
 		if (array[i] == o) {
@@ -65,6 +65,10 @@ int find(Common::Array<T>& array, const T& o) {
 	return index;
 }
 
+Common::String join(const Common::Array<Common::String>& array, const Common::String& sep);
+
+void scale(Math::Matrix4 &m, const Math::Vector2d &v);
+
 } // namespace Twp
 
 #endif


Commit: 1b7f4ef586a5fd1b20b18a0329946e28a05952a2
    https://github.com/scummvm/scummvm/commit/1b7f4ef586a5fd1b20b18a0329946e28a05952a2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix actorswitcher select

Changed paths:
    engines/twp/actorswitcher.cpp


diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index cf32715f8fc..815f70a2c28 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -108,7 +108,7 @@ float ActorSwitcher::height() const {
 
 int ActorSwitcher::iconIndex(Math::Vector2d pos) const {
 	float y = SCREEN_HEIGHT - pos.getY();
-	return _slots.size() - 1 - ((height() - y) / ACTOR_SEP);
+	return _slots.size() - 1 - (int)((height() - y) / ACTOR_SEP);
 }
 
 Common::Rect ActorSwitcher::rect() const {


Commit: 0f784c64add74c26477c8a651cbb65965213dea9
    https://github.com/scummvm/scummvm/commit/0f784c64add74c26477c8a651cbb65965213dea9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix break light animation

Changed paths:
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 559e3fa260d..d751177125f 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -160,7 +160,6 @@ void ReachAnim::update(float elapsed) {
 		break;
 	default:
 		break;
-		;
 	}
 }
 
@@ -181,7 +180,8 @@ void WalkTo::disable() {
 	if (_path.size() != 0) {
 		debug("actor walk cancelled");
 	}
-	_obj->play("stand");
+	if(_obj->isWalking())
+		_obj->play("stand");
 }
 
 static bool needsReachAnim(int verbId) {
@@ -225,7 +225,7 @@ void WalkTo::actorArrived() {
 		}
 
 		if (needsReach)
-			_reach = new ReachAnim(_obj, noun1);
+			_obj->setReach(new ReachAnim(_obj, noun1));
 		else
 			_obj->execVerb();
 	}
@@ -258,9 +258,10 @@ void WalkTo::update(float elapsed) {
 		}
 	}
 
-	if (_reach && _reach->isEnabled()) {
-		_reach->update(elapsed);
-		if (!_reach->isEnabled())
+	Motor* reach = _obj->getReach();
+	if (reach && reach->isEnabled()) {
+		reach->update(elapsed);
+		if (!reach->isEnabled())
 			disable();
 	}
 }
@@ -365,7 +366,7 @@ void Talking::say(const Common::String &text) {
 
 	setDuration(txt);
 
-	if(_obj->_sayNode) {
+	if (_obj->_sayNode) {
 		_obj->_sayNode->remove();
 	}
 	Text text2("sayline", txt, thCenter, tvCenter, SCREEN_WIDTH * 3.f / 4.f, _color);
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 56d773319e9..62b245314b8 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -219,7 +219,6 @@ private:
 	Common::Array<Math::Vector2d> _path;
 	int _facing = 0;
 	float _wsd;
-	ReachAnim *_reach = nullptr;
 };
 
 // Creates a talking animation for a specified object.
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index c59046e2675..1f3ba8b34ef 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -465,6 +465,7 @@ void Object::setAlphaTo(Motor *alphaTo) { SET_MOTOR(alphaTo); }
 void Object::setRotateTo(Motor *rotateTo) { SET_MOTOR(rotateTo); }
 void Object::setMoveTo(Motor *moveTo) { SET_MOTOR(moveTo); }
 void Object::setWalkTo(Motor *walkTo) { SET_MOTOR(walkTo); }
+void Object::setReach(Motor *reach) { SET_MOTOR(reach); }
 void Object::setTalking(Motor *talking) { SET_MOTOR(talking); }
 void Object::setBlink(Motor *blink) { SET_MOTOR(blink); }
 void Object::setTurnTo(Motor *turnTo) { SET_MOTOR(turnTo); }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 22871bb314e..e9f9960314d 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -195,7 +195,9 @@ public:
 	void setRotateTo(Motor *rotateTo);
 	void setMoveTo(Motor *moveTo);
 	void setWalkTo(Motor *walkTo);
+	void setReach(Motor *reach);
 	Motor *getWalkTo() const { return _walkTo; }
+	Motor *getReach() const { return _reach; }
 	void walk(Math::Vector2d pos, int facing = 0);
 	void walk(Object* obj);
 
@@ -276,6 +278,7 @@ private:
 	Motor *_rotateTo = nullptr;
 	Motor *_moveTo = nullptr;
 	Motor *_walkTo = nullptr;
+	Motor *_reach = nullptr;
 	Motor *_talking = nullptr;
 	Motor *_blink = nullptr;
 	Motor *_turnTo = nullptr;


Commit: 8f43063247fdef3d1f32d53f65e85013a965444b
    https://github.com/scummvm/scummvm/commit/8f43063247fdef3d1f32d53f65e85013a965444b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Hide actorswitcher when necessary

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 218d9066599..b08cb5ac143 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -398,7 +398,7 @@ void TwpEngine::update(float elapsed) {
 			_hud.setVisible(_inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None);
 			_sentence.setVisible(_hud.isVisible());
 			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
-			//_actorSwitcher.visible = _dialog.state == DialogState.None and self.cutscene.isNil;
+			_actorSwitcher.setVisible((_dialog.getState() == DialogState::None) && !_cutscene);
 			// Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
 			//_sentence.setText(cursortxt.c_str());
 			_sentence.setText(cursorText());


Commit: cc8e1478dedf45cb92d527ce2ed58f06a5b316c2
    https://github.com/scummvm/scummvm/commit/cc8e1478dedf45cb92d527ce2ed58f06a5b316c2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix objectMoveTo

Changed paths:
    engines/twp/objlib.cpp


diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 4c92812fb99..37357dd7153 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -491,7 +491,7 @@ static SQInteger objectLit(HSQUIRRELVM v) {
 // - `objectOffsetTo method <#objectOffsetTo.e>`_
 static SQInteger objectMoveTo(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
-	if (!obj) {
+	if (obj) {
 		int x = 0;
 		int y = 0;
 		if (SQ_FAILED(sqget(v, 3, x)))


Commit: 7af87bb96a175f40eae01dd8ca12980d05f4a958
    https://github.com/scummvm/scummvm/commit/7af87bb96a175f40eae01dd8ca12980d05f4a958
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add dialog choose

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/object.cpp


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index def80b7f0cc..866c953f64b 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -27,6 +27,45 @@
 
 namespace Twp {
 
+class SerialMotors : public Motor {
+public:
+	SerialMotors(const std::initializer_list<Motor *> &motors) : _motors(motors) {}
+	SerialMotors(const Common::Array<Motor *> &motors) : _motors(motors) {}
+
+	virtual void update(float elapsed) override {
+		if (_motors.size() > 0) {
+			_motors[0]->update(elapsed);
+			if (!_motors[0]->isEnabled()) {
+				debug("SerialMotors next");
+				_motors.remove_at(0);
+			}
+		} else {
+			debug("SerialMotors is over");
+			disable();
+		}
+	}
+
+private:
+	Common::Array<Motor *> _motors;
+};
+
+class SelectLabelMotor : public Motor {
+public:
+	SelectLabelMotor(Dialog *dlg, int line, const Common::String &name)
+		: _dlg(dlg), _line(line), _name(name) {
+	}
+
+	virtual void update(float elapsed) override {
+		_dlg->selectLabel(_line, _name);
+		disable();
+	}
+
+private:
+	Dialog *_dlg;
+	int _line;
+	Common::String _name;
+};
+
 CondStateVisitor::CondStateVisitor(Dialog *dlg, DialogSelMode mode) : _dlg(dlg), _mode(mode) {
 }
 
@@ -139,6 +178,37 @@ DialogSlot::DialogSlot() : Node("DialogSlot") {}
 Dialog::Dialog() : Node("Dialog") {}
 Dialog::~Dialog() {}
 
+static YChoice *getChoice(DialogSlot *slot) {
+	return (YChoice *)(slot->_stmt->_exp.get());
+}
+
+void Dialog::choose(int choice) {
+	if (_state == WaitingForChoice) {
+		choose(&_slots[choice]);
+	}
+}
+
+void Dialog::choose(DialogSlot *slot) {
+	if (slot) {
+		sqcall("onChoiceClick");
+		for (int i = 0; i < slot->_stmt->_conds.size(); i++) {
+			YCond *cond = slot->_stmt->_conds[i];
+			CondStateVisitor v(slot->_dlg, DialogSelMode::Choose);
+			cond->accept(v);
+		}
+		YChoice *choice = getChoice(slot);
+		if (slot->_dlg->_context.parrot) {
+			slot->_dlg->_state = DialogState::Active;
+			slot->_dlg->_action = new SerialMotors(
+				{slot->_dlg->_tgt->say(slot->_dlg->_context.actor, choice->_text),
+				 new SelectLabelMotor(slot->_dlg, choice->_goto->_line, choice->_goto->_name)});
+			slot->_dlg->clearSlots();
+		} else {
+			slot->_dlg->selectLabel(choice->_goto->_line, choice->_goto->_name);
+		}
+	}
+}
+
 void Dialog::start(const Common::String &actor, const Common::String &name, const Common::String &node) {
 	_context = DialogContext{.actor = actor, .dialogName = name, .parrot = true, .limit = MAXCHOICES};
 	// keepIf(self.states, proc(x: DialogConditionState): bool = x.mode != TempOnce);
@@ -162,9 +232,9 @@ void Dialog::update(float dt) {
 	case DialogState::WaitingForChoice: {
 		Color color = _tgt->actorColor(_context.actor);
 		Color colorHover = _tgt->actorColorHover(_context.actor);
-		for (size_t j = 0; j < MAXDIALOGSLOTS; j++) {
-			DialogSlot *slot = &_slots[j];
-			if (slot) {
+		for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
+			DialogSlot *slot = &_slots[i];
+			if (slot->_isValid) {
 				Rectf rect = Rectf::fromPosAndSize(slot->getPos() - Math::Vector2d(0.f, slot->_text.getBounds().getY()), slot->_text.getBounds());
 				bool over = slot && rect.contains(_mousePos);
 				if (rect.r.w > (SCREEN_WIDTH - SLOTMARGIN)) {
@@ -183,12 +253,11 @@ void Dialog::update(float dt) {
 					}
 				}
 				slot->_text.setColor(over ? colorHover : color);
-				// if (over && mbLeft in mouseBtns())
-				//   choose(i);
+				if (over && g_engine->_cursor.isLeftDown())
+					choose(i);
 			}
-			break;
 		}
-	}
+	} break;
 	}
 }
 
@@ -268,7 +337,7 @@ void Dialog::selectLabel(int line, const Common::String &name) {
 	_lbl = label(line, name);
 	_currentStatement = 0;
 	clearSlots();
-	_state = _lbl ? Active: None;
+	_state = _lbl ? Active : None;
 }
 
 void Dialog::gotoNextLabel() {
@@ -289,7 +358,7 @@ void Dialog::updateChoiceStates() {
 		DialogSlot *slot = &_slots[i];
 		if (slot->_isValid) {
 			for (size_t j = 0; j < slot->_stmt->_conds.size(); j++) {
-				YCond *cond = slot->_stmt->_conds[i];
+				YCond *cond = slot->_stmt->_conds[j];
 				CondStateVisitor v(this, DialogSelMode::Show);
 				cond->accept(v);
 			}
@@ -321,40 +390,39 @@ bool Dialog::acceptConditions(YStatement *stmt) {
 	return true;
 }
 void Dialog::running(float dt) {
-  if (_action && _action->isEnabled())
-    _action->update(dt);
-  else if (!_lbl)
-    _state = None;
-  else if (_currentStatement == _lbl->_stmts.size())
-    gotoNextLabel();
-  else {
-    _state = Active;
-    while (_lbl && (_currentStatement < _lbl->_stmts.size()) && (_state == Active)) {
-      YStatement* statmt = _lbl->_stmts[_currentStatement];
-	  IsChoice isChoice;
-	  statmt->_exp->accept(isChoice);
-      if (!acceptConditions(statmt))
-        _currentStatement ++;
-      else if (isChoice._isChoice) {
-        addSlot(statmt);
-        _currentStatement++;
-	  }
-      else if (choicesReady())
-        updateChoiceStates();
-      else if (_action && _action->isEnabled()) {
-        _action->update(dt);
-        return;
-	  } else {
-        run(statmt);
-        if (_lbl && (_currentStatement == _lbl->_stmts.size()))
-          gotoNextLabel();
-	  }
+	if (_action && _action->isEnabled())
+		_action->update(dt);
+	else if (!_lbl)
+		_state = None;
+	else if (_currentStatement == _lbl->_stmts.size())
+		gotoNextLabel();
+	else {
+		_state = Active;
+		while (_lbl && (_currentStatement < _lbl->_stmts.size()) && (_state == Active)) {
+			YStatement *statmt = _lbl->_stmts[_currentStatement];
+			IsChoice isChoice;
+			statmt->_exp->accept(isChoice);
+			if (!acceptConditions(statmt))
+				_currentStatement++;
+			else if (isChoice._isChoice) {
+				addSlot(statmt);
+				_currentStatement++;
+			} else if (choicesReady())
+				updateChoiceStates();
+			else if (_action && _action->isEnabled()) {
+				_action->update(dt);
+				return;
+			} else {
+				run(statmt);
+				if (_lbl && (_currentStatement == _lbl->_stmts.size()))
+					gotoNextLabel();
+			}
+		}
+		if (choicesReady())
+			updateChoiceStates();
+		else if (!_action || !_action->isEnabled())
+			_state = None;
 	}
-    if (choicesReady())
-        updateChoiceStates();
-    else if (!_action || !_action->isEnabled())
-      _state = None;
-  }
 }
 
 static Common::String remove(const Common::String &txt, char startC, char endC) {
@@ -383,6 +451,7 @@ void Dialog::addSlot(YStatement *stmt) {
 		slot->_stmt = stmt;
 		slot->_dlg = this;
 		slot->setPos(Math::Vector2d(SLOTMARGIN, SLOTMARGIN + slot->_text.getBounds().getY() * (MAXCHOICES - numSlots())));
+		slot->_isValid = true;
 	}
 }
 
@@ -402,6 +471,14 @@ void Dialog::clearSlots() {
 }
 
 void Dialog::drawCore(Math::Matrix4 trsf) {
+	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
+		DialogSlot *slot = &_slots[i];
+		if (slot->_isValid) {
+			Math::Matrix4 t(trsf);
+			t.translate(Math::Vector3d(slot->getPos().getX(), slot->getPos().getY(), 0.f));
+			slot->_text.draw(g_engine->getGfx(), t);
+		}
+	}
 }
 
 } // namespace Twp
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 109b114046f..7a787d77046 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -175,6 +175,7 @@ public:
 	Dialog();
 	virtual ~Dialog() override;
 
+	void choose(int choice);
 	void update(float dt);
 	DialogState getState() const { return _state; }
 
@@ -189,6 +190,7 @@ public:
 	bool isCond(const Common::String &cond) const;
 
 private:
+	void choose(DialogSlot* slot);
 	YLabel *label(int line, const Common::String &name) const;
 	void gotoNextLabel();
 	bool choicesReady() const { return numSlots() > 0; }
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 1f3ba8b34ef..630cae2d27d 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -305,7 +305,7 @@ int Object::getFlags() {
 
 void Object::setRoom(Room *room) {
 	if ((_room != room) || !_node->getParent()) {
-		if(_room != room) {
+		if (_room != room) {
 			stopObjectMotors();
 		}
 		Room *oldRoom = _room;
@@ -533,6 +533,8 @@ void Object::stopTalking() {
 }
 
 void Object::say(const Common::StringArray &texts, Color color) {
+	if (texts.size() == 0)
+		return;
 	_talkingState._obj = this;
 	_talkingState._color = color;
 	_talkingState.say(texts, this);


Commit: e75c4d891b31aef24bc03caec9ef3e2c6b55f684
    https://github.com/scummvm/scummvm/commit/e75c4d891b31aef24bc03caec9ef3e2c6b55f684
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add blink, fix room size, actor scaling and \"

Changed paths:
  A engines/twp/objectanimation.h
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/scenegraph.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.cpp
    engines/twp/util.h
    engines/twp/yack.h


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index cb5e089d617..e89f3319676 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -460,10 +460,6 @@ static SQInteger is_table(HSQUIRRELVM v) {
 	return is_oftype(v, [](SQObjectType type) { return type == OT_TABLE; });
 }
 
-static float randf() {
-	return g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
-}
-
 // Returns a random number from from to to inclusively.
 // The number is a pseudo-random number and the game will produce the same sequence of numbers unless primed using randomSeed(seed).
 //
@@ -476,8 +472,7 @@ static SQInteger sqrandom(HSQUIRRELVM v) {
 		sq_getfloat(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		float scale = randf();
-		SQFloat value = min + scale * (max - min);
+		SQFloat value = g_engine->getRandom(min, max);
 		sq_pushfloat(v, value);
 	} else {
 		SQInteger min, max;
@@ -625,7 +620,7 @@ static SQInteger randomOdds(HSQUIRRELVM v) {
 	float value = 0.0f;
 	if (SQ_FAILED(sqget(v, 2, value)))
 		return sq_throwerror(v, "failed to get value");
-	float rnd = randf();
+	float rnd = g_engine->getRandom();
 	bool res = rnd <= value;
 	sq_pushbool(v, res);
 	return 1;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 630cae2d27d..771c0e995ad 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -33,6 +33,49 @@
 
 namespace Twp {
 
+enum class BlinkState {
+	Closed,
+	Open
+};
+
+class Blink : public Motor {
+public:
+	Blink(Object *obj, float min, float max) : _obj(obj), _min(min), _max(max) {
+		_obj->showLayer("blink", false);
+		_state = BlinkState::Closed;
+		_duration = g_engine->getRandom(min, max);
+	}
+
+	virtual void update(float elapsed) override {
+		if (_state == BlinkState::Closed) {
+			// wait to blink
+			_elapsed += elapsed;
+			if (_elapsed > _duration) {
+				_state = BlinkState::Open;
+				_obj->showLayer("blink", true);
+				_elapsed = 0;
+			}
+		} else if (_state == BlinkState::Open) {
+			// wait time the eyes are closed
+			_elapsed += elapsed;
+			if (_elapsed > 0.25) {
+				_obj->showLayer("blink", false);
+				_duration = g_engine->getRandom(_min, _max);
+				_elapsed = 0;
+				_state = BlinkState::Closed;
+			}
+		}
+	}
+
+private:
+	Object *_obj = nullptr;
+	BlinkState _state = BlinkState::Closed;
+	float _min = 0.f;
+	float _max = 0.f;
+	float _elapsed = 0.f;
+	float _duration = 0.f;
+};
+
 Object::Object()
 	: _talkOffset(0, 90) {
 	_node = new Node("newObj");
@@ -47,7 +90,8 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 
 Object::~Object() {
 	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
-	_node->getParent()->removeChild(_node);
+	_nodeAnim->remove();
+	_node->remove();
 }
 
 Object *Object::createActor() {
@@ -343,7 +387,7 @@ void Object::stopObjectMotors() {
 	disableMotor(_moveTo);
 	disableMotor(_walkTo);
 	disableMotor(_talking);
-	disableMotor(_blink);
+	disableMotor(_blink.get());
 	disableMotor(_turnTo);
 	disableMotor(_shakeTo);
 	disableMotor(_jiggleTo);
@@ -423,11 +467,11 @@ void Object::setAnimationNames(const Common::String &head, const Common::String
 }
 
 void Object::blinkRate(float min, float max) {
-	// TODO:
-	//   if (min == 0.0 && max == 0.0)
-	//     _blink = nil;
-	//   else:
-	//     _blink = new Blink(this, min, max);
+	  if (min == 0.0 && max == 0.0) {
+	    _blink.reset();
+	  } else {
+	    _blink.reset(new Blink(this, min, max));
+	  }
 }
 
 void Object::setCostume(const Common::String &name, const Common::String &sheet) {
@@ -467,7 +511,6 @@ void Object::setMoveTo(Motor *moveTo) { SET_MOTOR(moveTo); }
 void Object::setWalkTo(Motor *walkTo) { SET_MOTOR(walkTo); }
 void Object::setReach(Motor *reach) { SET_MOTOR(reach); }
 void Object::setTalking(Motor *talking) { SET_MOTOR(talking); }
-void Object::setBlink(Motor *blink) { SET_MOTOR(blink); }
 void Object::setTurnTo(Motor *turnTo) { SET_MOTOR(turnTo); }
 void Object::setShakeTo(Motor *shakeTo) { SET_MOTOR(shakeTo); }
 void Object::setJiggleTo(Motor *jiggleTo) { SET_MOTOR(jiggleTo); }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index e9f9960314d..27dfe546536 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -29,6 +29,8 @@
 #include "twp/squirrel/squirrel.h"
 #include "twp/ids.h"
 #include "twp/gfx.h"
+#include "twp/util.h"
+#include "twp/objectanimation.h"
 
 #define STAND_ANIMNAME "stand"
 #define HEAD_ANIMNAME "head"
@@ -60,19 +62,6 @@ enum UseFlag {
 	ufGiveTo
 };
 
-struct ObjectAnimation {
-	Common::String name;
-	Common::String sheet;
-	Common::StringArray frames;
-	Common::Array<ObjectAnimation> layers;
-	Common::StringArray triggers;
-	Common::Array<Math::Vector2d> offsets;
-	bool loop;
-	float fps;
-	int flags;
-	int frameIndex;
-};
-
 class Trigger {
 public:
 	virtual ~Trigger() {}
@@ -280,7 +269,7 @@ private:
 	Motor *_walkTo = nullptr;
 	Motor *_reach = nullptr;
 	Motor *_talking = nullptr;
-	Motor *_blink = nullptr;
+	unique_ptr<Motor> _blink;
 	Motor *_turnTo = nullptr;
 	Motor *_shakeTo = nullptr;
 	Motor *_jiggleTo = nullptr;
diff --git a/engines/twp/objectanimation.h b/engines/twp/objectanimation.h
new file mode 100644
index 00000000000..b759370b3f7
--- /dev/null
+++ b/engines/twp/objectanimation.h
@@ -0,0 +1,46 @@
+/* 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 TWP_OBJECT_ANIM_H
+#define TWP_OBJECT_ANIM_H
+
+#include "common/str.h"
+#include "common/str-array.h"
+#include "common/array.h"
+#include "math/vector2d.h"
+
+namespace Twp {
+struct ObjectAnimation {
+	Common::String name;
+	Common::String sheet;
+	Common::StringArray frames;
+	Common::Array<ObjectAnimation> layers;
+	Common::StringArray triggers;
+	Common::Array<Math::Vector2d> offsets;
+	bool loop;
+	float fps;
+	int flags;
+	int frameIndex;
+};
+} // namespace Twp
+
+#endif
+
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 16ef0c6649a..04ddad35c26 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -368,6 +368,14 @@ void Room::load(Common::SeekableReadStream &s) {
 
 	_mergedPolygon = merge(_walkboxes);
 
+	// Fix room size (why ?)
+	int width = 0;
+	for (int i = 0; i < backNames.size(); i++) {
+		Common::String name = backNames[i];
+		width += g_engine->_resManager.spriteSheet(_sheet)->frameTable[name].sourceSize.getX();
+	}
+	_roomSize.setX(width);
+
 	delete value;
 }
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 54ed430dbd8..2cff54d0156 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -209,14 +209,14 @@ Math::Matrix4 Node::getLocalTrsf() {
 	mRot.buildAroundZ(Math::Angle(-_rotation + _rotationOffset));
 	Math::Matrix4 m2;
 	m2.setRotation(mRot);
-	scale(m2, _scale);
+	scale(m2, getScale());
 	Math::Matrix4 m3;
 	m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
 	return m1 * m2 * m3;
 }
 
 Rectf Node::getRect() const {
-	Math::Vector2d size = _size * _scale;
+	Math::Vector2d size = _size * getScale();
 	return Rectf::fromPosAndSize(getAbsPos(), Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm * _size);
 }
 
@@ -388,7 +388,7 @@ void TextNode::updateBounds() {
 }
 
 Rectf TextNode::getRect() const {
-	Math::Vector2d size = _size * _scale;
+	Math::Vector2d size = _size * getScale();
 	return Rectf::fromPosAndSize(getAbsPos() + Math::Vector2d(0, -size.getY()) + Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm, size);
 }
 
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 741bcdc739d..56b1b70ad9e 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -42,8 +42,8 @@ Common::String TextDb::getText(int id) {
 		result = _texts[id];
 		if (result.hasSuffix("#M") || result.hasSuffix("#F"))
 			result = result.substr(0, result.size() - 3);
-		// TODO: replace \" by ";
-		// result = result.replace("\\\"", "\"");
+		// replace \" by ";
+		result = Twp::replace(result, "\\\"", "\"");
 	} else {
 		result = Common::String::format("Text %d not found", id);
 		error("Text %d not found", id);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index b08cb5ac143..d3958065360 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -425,9 +425,6 @@ void TwpEngine::update(float elapsed) {
 			if (_cursor.leftDown)
 				clickedAt(scrPos);
 		}
-
-		if (_cursor.leftDown || _cursor.rightDown)
-			clickedAt(winToScreen(_cursor.pos));
 	}
 
 	_dialog.update(elapsed);
@@ -1302,4 +1299,13 @@ void TwpEngine::stopTalking() {
 	}
 }
 
+float TwpEngine::getRandom() const {
+	return g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+}
+
+float TwpEngine::getRandom(float min, float max) const {
+	float scale = getRandom();
+	return min + scale * (max - min);
+}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 5f316dccd9d..cb0faf74870 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -147,6 +147,9 @@ public:
 	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 	bool execSentence(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
 
+	float getRandom() const;
+	float getRandom(float min, float max) const;
+
 private:
 	void update(float elapsedMs);
 	void draw();
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index a3ae47ae644..cd7e797c107 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "twp/object.h"
 #include "twp/util.h"
 #include "twp/scenegraph.h"
 
@@ -165,6 +166,21 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 	return result;
 }
 
+Common::String replace(const Common::String& s, const Common::String& what, const Common::String& by) {
+	Common::String result;
+	uint i = 0;
+	size_t whatSize = what.size();
+	while (true) {
+      uint j = s.find(what, i);
+      if (j == Common::String::npos) break;
+      result += s.substr(i, j - i);
+      result += by;
+      i = j + whatSize;
+	}
+    result += s.substr(i);
+	return result;
+}
+
 void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
 	m(0, 0) *= v.getX();
 	m(1, 1) *= v.getY();
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 0d8249b78a0..9cbcff6ab14 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -23,12 +23,20 @@
 #define TWP_UTIL_H
 
 #include "twp/ids.h"
-#include "twp/object.h"
+#include "twp/objectanimation.h"
 #include "math/vector2d.h"
+#include "math/matrix4.h"
 #include "common/formats/json.h"
+#include "common/rect.h"
 
 namespace Twp {
 
+class Object;
+
+// general util
+template<typename T, class DL = Common::DefaultDeleter<T> >
+using unique_ptr = Common::ScopedPtr<T, DL>;
+
 template<typename T>
 T clamp(T x, T a, T b) {
 	if (x < a)
@@ -38,21 +46,18 @@ T clamp(T x, T a, T b) {
 	return x;
 }
 
-Math::Vector2d operator*(Math::Vector2d v, float f);
-
+// game util
 Facing getFacing(int dir, Facing facing);
 Facing flip(Facing facing);
 Facing getFacingToFaceTo(Object *actor, Object *obj);
 
+// parsing util
 bool toBool(const Common::JSONObject &jNode, const Common::String &key);
 Math::Vector2d parseVec2(const Common::String &s);
 Common::Rect parseRect(const Common::String &s);
 void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims);
 
-float distance(Math::Vector2d p1, Math::Vector2d p2);
-float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
-float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
-
+// array util
 template<typename T>
 int find(const Common::Array<T>& array, const T& o) {
 	int index = -1;
@@ -65,9 +70,16 @@ int find(const Common::Array<T>& array, const T& o) {
 	return index;
 }
 
+// string util
 Common::String join(const Common::Array<Common::String>& array, const Common::String& sep);
+Common::String replace(const Common::String& s, const Common::String& what, const Common::String& by);
 
+// math util
 void scale(Math::Matrix4 &m, const Math::Vector2d &v);
+Math::Vector2d operator*(Math::Vector2d v, float f);
+float distance(Math::Vector2d p1, Math::Vector2d p2);
+float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
+float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
 
 } // namespace Twp
 
diff --git a/engines/twp/yack.h b/engines/twp/yack.h
index 27a639f456a..d1e47c2003e 100644
--- a/engines/twp/yack.h
+++ b/engines/twp/yack.h
@@ -26,12 +26,10 @@
 #include "common/str.h"
 #include "common/stream.h"
 #include "common/debug.h"
+#include "twp/util.h"
 
 namespace Twp {
 
-template<typename T, class DL = Common::DefaultDeleter<T> >
-using unique_ptr = Common::ScopedPtr<T, DL>;
-
 enum class YackTokenId {
 	None,
 	NewLine,


Commit: 27365286eeaf6bec27791977cf1908eaa2dbd513
    https://github.com/scummvm/scummvm/commit/27365286eeaf6bec27791977cf1908eaa2dbd513
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix camera limit and ) in dialog

Changed paths:
    engines/twp/camera.cpp
    engines/twp/dialog.cpp
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 0b65a8cc212..d5131fdc2f1 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -35,8 +35,7 @@ void Camera::clamp(Math::Vector2d at) {
 
 		_pos.setX(Twp::clamp(at.getX(), screenSize.getX() / 2.f + _bounds.left(), screenSize.getX() / 2 + _bounds.right()));
 		_pos.setY(Twp::clamp(at.getY(), _bounds.bottom(), _bounds.top() - screenSize.getY() / 2));
-		//_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(roomSize.getX() - screenSize.getX() / 2.f, 0.f)));
-		_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(10000.f, 0.f)));
+		_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(roomSize.getX() - screenSize.getX() / 2.f, 0.f)));
 		_pos.setY(Twp::clamp(_pos.getY(), screenSize.getY() / 2, MAX(roomSize.getY() - screenSize.getY() / 2, 0.f)));
 	}
 }
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 866c953f64b..79bfb145e02 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -425,16 +425,6 @@ void Dialog::running(float dt) {
 	}
 }
 
-static Common::String remove(const Common::String &txt, char startC, char endC) {
-	if (txt[0] == startC) {
-		uint32 i = txt.find(endC);
-		if (i != Common::String::npos) {
-			return txt.substr(i);
-		}
-	}
-	return txt;
-}
-
 static Common::String text(const Common::String &txt) {
 	Common::String result = g_engine->getTextDb().getText(txt);
 	result = remove(result, '(', ')');
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index cd7e797c107..00ecf12fd27 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -181,6 +181,16 @@ Common::String replace(const Common::String& s, const Common::String& what, cons
 	return result;
 }
 
+Common::String remove(const Common::String &txt, char startC, char endC) {
+	if (txt[0] == startC) {
+		uint32 i = txt.find(endC);
+		if (i != Common::String::npos) {
+			return txt.substr(i+1);
+		}
+	}
+	return txt;
+}
+
 void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
 	m(0, 0) *= v.getX();
 	m(1, 1) *= v.getY();
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 9cbcff6ab14..17b9609a4a3 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -73,6 +73,7 @@ int find(const Common::Array<T>& array, const T& o) {
 // string util
 Common::String join(const Common::Array<Common::String>& array, const Common::String& sep);
 Common::String replace(const Common::String& s, const Common::String& what, const Common::String& by);
+Common::String remove(const Common::String &txt, char startC, char endC);
 
 // math util
 void scale(Math::Matrix4 &m, const Math::Vector2d &v);


Commit: e1cc3bb7f022be2decde51f04b9eda043c3c1cf7
    https://github.com/scummvm/scummvm/commit/e1cc3bb7f022be2decde51f04b9eda043c3c1cf7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add audio

Changed paths:
  A engines/twp/audio.cpp
  A engines/twp/audio.h
    engines/twp/genlib.cpp
    engines/twp/hud.cpp
    engines/twp/module.mk
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.h
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
new file mode 100644
index 00000000000..9a8cf4a5bbd
--- /dev/null
+++ b/engines/twp/audio.cpp
@@ -0,0 +1,232 @@
+/* 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 "common/debug.h"
+#include "audio/mixer.h"
+#include "audio/mixer_intern.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/vorbis.h"
+#include "audio/decoders/wave.h"
+#include "twp/audio.h"
+#include "twp/twp.h"
+#include "twp/ids.h"
+
+namespace Twp {
+
+void SoundStream::open(SoundDefinition *sndDef) {
+	sndDef->load();
+	_stream.open(&sndDef->_buffer[0], sndDef->_buffer.size());
+}
+
+uint32 SoundStream::read(void *dataPtr, uint32 dataSize) {
+	return _stream.read(dataPtr, dataSize);
+}
+
+bool SoundStream::eos() const {
+	return _stream.eos();
+}
+
+int64 SoundStream::pos() const {
+	return _stream.pos();
+}
+
+int64 SoundStream::size() const {
+	return _stream.size();
+}
+
+bool SoundStream::seek(int64 offset, int whence) {
+	return _stream.seek(offset, whence);
+}
+
+SoundDefinition::SoundDefinition(const Common::String &name) : _name(name), _id(newSoundDefId()) {
+}
+
+void SoundDefinition::load() {
+	if (!_loaded) {
+		GGPackEntryReader entry;
+		entry.open(g_engine->_pack, _name);
+		_buffer.resize(entry.size());
+		entry.read(&_buffer[0], entry.size());
+	}
+}
+
+bool AudioSystem::playing(int id) const {
+	// channel ID ?
+	if (id >= 1 && id <= 32) {
+		if (!_slots[id].busy)
+			return false;
+		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+	}
+	// sound definition ID ?
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && _slots[i].sndDef->getId() == id) {
+			return g_engine->_mixer->isSoundHandleActive(_slots[i].handle);
+		}
+	}
+	// sound ID ?
+	return g_engine->_mixer->isSoundIDActive(id);
+}
+
+bool AudioSystem::playing(SoundDefinition *soundDef) const {
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && _slots[i].sndDef == soundDef) {
+			return g_engine->_mixer->isSoundHandleActive(_slots[i].handle);
+		}
+	}
+	return false;
+}
+
+void AudioSystem::fadeOut(int id, float fadeTime) {
+	if (fadeTime < 0.01f) {
+		stop(id);
+	} else {
+		for (int i = 0; i < 32; i++) {
+			if (_slots[i].busy && _slots[i].id == id) {
+				_slots[i].fadeOutTimeMs = fadeTime;
+			}
+		}
+	}
+}
+
+void AudioSystem::stop(int id) {
+	// channel ID ?
+	if (id >= 1 && id <= 32) {
+		if (!_slots[id].busy)
+			return;
+		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+	}
+	// sound definition ID ?
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && _slots[i].sndDef->getId() == id) {
+			g_engine->_mixer->stopHandle(_slots[i].handle);
+		}
+	}
+	// sound ID ?
+	g_engine->_mixer->stopID(id);
+}
+
+void AudioSystem::setMasterVolume(float vol) {
+	_masterVolume = Twp::clamp(vol, 0.f, 1.f);
+
+	// update sounds
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && g_engine->_mixer->isSoundHandleActive(_slots[i].handle)) {
+			g_engine->_mixer->setChannelVolume(_slots[i].handle, _slots[i].volume * _masterVolume);
+		}
+	}
+}
+
+float AudioSystem::getMasterVolume() const {
+	return _masterVolume;
+}
+
+void AudioSystem::updateVolume(AudioSlot *slot) {
+	float vol = _masterVolume * slot->volume;
+	if (slot->fadeInTimeMs) {
+		vol *= (g_engine->_mixer->getElapsedTime(slot->handle).msecs() / slot->total);
+	}
+	if (slot->fadeOutTimeMs) {
+		float startFade = slot->total - slot->fadeOutTimeMs;
+		float progress = (g_engine->_mixer->getElapsedTime(slot->handle).msecs() - startFade) / slot->fadeOutTimeMs;
+		if ((progress >= 0) && (progress <= 1.f)) {
+			vol *= (1.f - progress);
+		}
+		if (progress > 1.0f) {
+			g_engine->_mixer->stopHandle(slot->handle);
+			return;
+		}
+	}
+	g_engine->_mixer->setChannelVolume(slot->handle, vol * Audio::Mixer::kMaxChannelVolume);
+}
+
+void AudioSystem::setVolume(int id, float vol) {
+	// channel ID ?
+	if (id >= 1 && id <= 32) {
+		if (!_slots[id].busy)
+			return;
+		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+	}
+	// sound definition ID or sound ID ?
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && ((_slots[i].sndDef->getId() == id) || (g_engine->_mixer->getSoundID(_slots[i].handle) == id))) {
+			_slots[i].volume = vol;
+			updateVolume(&_slots[i]);
+		}
+	}
+}
+
+void AudioSystem::update(float elapsed) {
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy && !g_engine->_mixer->isSoundHandleActive(_slots[i].handle)) {
+			_slots[i].busy = false;
+		}
+	}
+	// sound definition ID or sound ID ?
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].busy) {
+			updateVolume(&_slots[i]);
+		}
+	}
+}
+
+AudioSlot *AudioSystem::getFreeSlot() {
+	for (int i = 0; i < 32; i++) {
+		AudioSlot *slot = &_slots[i];
+		if (!slot->busy || !g_engine->_mixer->isSoundHandleActive(slot->handle)) {
+			slot->busy = false;
+			return slot;
+		}
+	}
+	return nullptr;
+}
+
+int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int loopTimes, float fadeInTimeMs, float volume, int objId) {
+	AudioSlot *slot = getFreeSlot();
+	if (!slot)
+		return 0;
+
+	const Common::String &name = sndDef->getName();
+	Audio::SeekableAudioStream *audioStream;
+	if (name.hasSuffixIgnoreCase(".ogg")) {
+		slot->stream.open(sndDef);
+		audioStream = Audio::makeVorbisStream(&slot->stream, DisposeAfterUse::NO);
+	} else if (name.hasSuffixIgnoreCase(".wav")) {
+		slot->stream.open(sndDef);
+		audioStream = Audio::makeWAVStream(&slot->stream, DisposeAfterUse::NO);
+	} else {
+		error("Unexpedted audio format: %s", name.c_str());
+	}
+	byte vol = (byte)(volume * 255);
+	int id = newSoundId();
+	if (fadeInTimeMs > 0.f) {
+		vol = 0;
+	}
+	g_engine->_mixer->playStream(cat, &slot->handle, audioStream, id, vol * _masterVolume);
+	slot->id = id;
+	slot->sndDef = sndDef;
+	slot->busy = true;
+	slot->volume = volume;
+	slot->fadeInTimeMs = fadeInTimeMs;
+	slot->total = audioStream->getLength().msecs();
+	return id;
+}
+
+} // namespace Twp
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
new file mode 100644
index 00000000000..fe7988cedbf
--- /dev/null
+++ b/engines/twp/audio.h
@@ -0,0 +1,120 @@
+/* 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 TWP_AUDIO_H
+#define TWP_AUDIO_H
+
+#include "common/str.h"
+#include "common/array.h"
+#include "common/stream.h"
+#include "audio/mixer.h"
+#include "twp/ggpack.h"
+
+namespace Twp {
+
+class AudioChannel;
+class SoundDefinition;
+
+struct SoundId {
+    int id;
+    int objId;
+    SoundDefinition* sndDef;
+    Audio::Mixer::SoundType cat;
+    AudioChannel* chan;
+    float pan;
+};
+
+class SoundDefinition;
+class SoundStream: public Common::SeekableReadStream {
+public:
+	void open(SoundDefinition* sndDef);
+
+	virtual uint32 read(void *dataPtr, uint32 dataSize) override;
+	virtual bool eos() const override;
+
+	virtual int64 pos() const override;
+	virtual int64 size() const override;
+	virtual bool seek(int64 offset, int whence = SEEK_SET) override;
+
+private:
+	MemStream _stream;
+};
+
+class SoundDefinition {
+public:
+	friend class SoundStream;
+
+public:
+	SoundDefinition(const Common::String& name);
+
+	void load();
+	int getId() const { return _id; }
+	Common::String getName() const { return _name; }
+
+private:
+    int _id;						// identifier for this sound
+    Common::String _name;		    // name of the sound to load
+    Common::Array<byte> _buffer;	// buffer containing the sound data
+    bool _loaded;				    // indicates whether or not the sound buffer has been loaded
+};
+
+struct AudioSlot {
+	Audio::SoundHandle handle;
+	SoundDefinition *sndDef = nullptr;
+	SoundStream stream;
+	bool busy = false;
+	float volume = 1.f;
+	float fadeInTimeMs = 0.f;
+	float fadeOutTimeMs = 0.f;
+	int total = 0;
+	int id = 0;
+};
+
+class AudioSystem {
+public:
+	int play(SoundDefinition* sndDef, Audio::Mixer::SoundType cat, int loopTimes = 0, float fadeInTimeMs = 0.f, float volume = 1.f, int objId = 0);
+
+	bool playing(int id) const;
+	bool playing(SoundDefinition* soundDef) const;
+
+	void fadeOut(int id, float fadeTime);
+	void stop(int id);
+
+	void setMasterVolume(float vol);
+	float getMasterVolume() const;
+	void setVolume(int id, float vol);
+
+	void update(float elapsed);
+
+	Common::Array<SoundDefinition*> _soundDefs;
+
+private:
+	void updateVolume(AudioSlot* slot);
+	AudioSlot* getFreeSlot();
+
+private:
+	AudioSlot _slots[32];
+	float _masterVolume = 1.f;
+};
+
+} // End of namespace Twp
+
+#endif // TWP_H
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index e89f3319676..18096a9597f 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -585,7 +585,7 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		HSQOBJECT obj;
 		sq_resetobject(&obj);
 		SQInteger size = sq_getsize(v, 2);
-		int index = g_engine->getRandomSource().getRandomNumber(size);
+		int index = g_engine->getRandomSource().getRandomNumber(size - 1);
 		int i = 0;
 		sq_push(v, 2);  // array
 		sq_pushnull(v); // null iterator
@@ -603,7 +603,7 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		sq_pushobject(v, obj);
 	} else {
 		SQInteger size = sq_gettop(v);
-		int index = g_engine->getRandomSource().getRandomNumber(size - 1);
+		int index = g_engine->getRandomSource().getRandomNumber(size - 3);
 		sq_push(v, 2 + index);
 	}
 	return 1;
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 40702c1ee99..cfe2400d5fe 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -29,7 +29,7 @@ namespace Twp {
 
 HudShader::HudShader() {
 	const char *verbVtxShader = R"(#version 110
-		attribute vec2 a_position;
+	attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
 
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index eea983dd66c..045f75e6b0d 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -65,6 +65,7 @@ MODULE_OBJS = \
 	walkboxnode.o \
 	actorswitcher.o \
 	enginedialogtarget.o \
+	audio.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index d751177125f..97881da0cb5 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -180,7 +180,7 @@ void WalkTo::disable() {
 	if (_path.size() != 0) {
 		debug("actor walk cancelled");
 	}
-	if(_obj->isWalking())
+	if (_obj->isWalking())
 		_obj->play("stand");
 }
 
@@ -258,7 +258,7 @@ void WalkTo::update(float elapsed) {
 		}
 	}
 
-	Motor* reach = _obj->getReach();
+	Motor *reach = _obj->getReach();
 	if (reach && reach->isEnabled()) {
 		reach->update(elapsed);
 		if (!reach->isEnabled())
@@ -315,6 +315,23 @@ void Talking::update(float elapsed) {
 	}
 }
 
+int Talking::loadActorSpeech(const Common::String &name) {
+	debug("loadActorSpeech %s.ogg", name.c_str());
+	Common::String filename(name);
+	filename.toUppercase();
+	filename += ".ogg";
+	if (g_engine->_pack.assetExists(filename.c_str())) {
+		SoundDefinition *soundDefinition = new SoundDefinition(filename);
+		if (!soundDefinition) {
+			debug("File %s.ogg not found", name.c_str());
+		} else {
+			g_engine->_audio._soundDefs.push_back(soundDefinition);
+			return g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f, _obj->getId());
+		}
+	}
+	return 0;
+}
+
 void Talking::say(const Common::String &text) {
 	Common::String txt(text);
 	if (text[0] == '@') {
@@ -336,7 +353,10 @@ void Talking::say(const Common::String &text) {
 		}
 
 		// TODO: call sayingLine
-		// TODO: _soundId = self.loadActorSpeech(name)
+		if(_obj->_sound) {
+			g_engine->_audio.stop(_obj->_sound);
+		}
+		_obj->_sound = loadActorSpeech(name);
 	} else if (text[0] == '^') {
 		txt = text.substr(1);
 	}
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 62b245314b8..e747b43a1d9 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -234,6 +234,7 @@ private:
 	Common::String talkieKey();
 	void setDuration(const Common::String& text);
 	void say(const Common::String& text);
+	int loadActorSpeech(const Common::String& name);
 
 private:
 	Object *_obj = nullptr;
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 27dfe546536..5620503ee09 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -261,6 +261,7 @@ public:
 	float _popElapsed = 0.f;
 	int _popCount = 0;
 	Sentence _exec;
+	int _sound = 0;
 
 private:
 	Motor *_alphaTo = nullptr;
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 3f7d2a49224..356082e12d7 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -24,130 +24,380 @@
 #include "twp/room.h"
 #include "twp/object.h"
 #include "twp/squtil.h"
+#include "twp/audio.h"
 #include "twp/squirrel/squirrel.h"
+#include "audio/mixer.h"
 
 namespace Twp {
 
+class SoundTrigger : public Trigger {
+public:
+	SoundTrigger(const Common::Array<SoundDefinition *> sounds) : _sounds(sounds) {}
+	virtual ~SoundTrigger() {}
+
+	virtual void trig() override {
+		int i = g_engine->getRandomSource().getRandomNumber(_sounds.size() - 1);
+		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType);
+	}
+
+private:
+	const Common::Array<SoundDefinition *> _sounds;
+};
+
+// Plays a sound at the specified actor's location.
+// If no sound is given, then it will turn off the trigger.
+// If a list of multiple sounds or an array are given, will randomly choose between the sound files.
+// The triggerNumber says which trigger in the animation JSON file should be used as a trigger to play the sound.
 static SQInteger actorSound(HSQUIRRELVM v) {
-	warning("TODO: actorSound not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get actor or object");
+	int trigNum = 0;
+	if (SQ_FAILED(sqget(v, 3, trigNum)))
+		return sq_throwerror(v, "failed to get trigger number");
+	SQInteger numSounds = sq_gettop(v) - 3;
+	if (numSounds != 0) {
+		int tmp = 0;
+		if ((numSounds == 1) && (SQ_SUCCEEDED(sqget(v, 4, tmp))) && (tmp == 0)) {
+			obj->_triggers.erase(trigNum);
+		} else {
+			Common::Array<SoundDefinition *> sounds;
+			if (sq_gettype(v, 4) == OT_ARRAY) {
+				if (SQ_FAILED(sqgetarray(v, 4, sounds)))
+					return sq_throwerror(v, "failed to get sounds");
+			} else {
+				sounds.resize(numSounds);
+				for (int i = 0; i < numSounds; i++) {
+					sounds[i] = sqsounddef(v, 4 + i);
+				}
+			}
+
+			Trigger *trigger = new SoundTrigger(sounds);
+			obj->_triggers[trigNum] = trigger;
+		}
+	}
 	return 0;
 }
 
+// Defines a sound and binds it to an id.
+// The defineSound(file) calls should be done at boot and do not load the file.
+// Its main use is to keep strings from being created and referenced during game play and providing a way to globally change a sound.
+// .. code-block:: Squirrel
+// clock_tick <- defineSound("clockTick.wav")
 static SQInteger defineSound(HSQUIRRELVM v) {
-	warning("TODO: defineSound not implemented");
-	return 0;
+	Common::String filename;
+	if (SQ_FAILED(sqget(v, 2, filename)))
+		return sq_throwerror(v, "failed to get filename");
+	SoundDefinition *sound = new SoundDefinition(filename);
+	g_engine->_audio._soundDefs.push_back(sound);
+	debug("defineSound(%s)-> %d", filename.c_str(), sound->getId());
+	sqpush(v, sound->getId());
+	return 1;
 }
 
+// Fades a sound out over a specified fade out duration (in seconds).
+// .. code-block:: Squirrel
+// fadeOutSound(soundElevatorMusic, 0.5)
 static SQInteger fadeOutSound(HSQUIRRELVM v) {
-	warning("TODO: fadeOutSound not implemented");
+	int sound = 0;
+	if (SQ_FAILED(sqget(v, 2, sound)))
+		return sq_throwerror(v, "failed to get sound");
+	float t;
+	if (SQ_FAILED(sqget(v, 3, t)))
+		return sq_throwerror(v, "failed to get fadeOut time");
+	g_engine->_audio.fadeOut(sound, t);
 	return 0;
 }
 
+// Returns `TRUE` if sound is currently playing.
+// Where sound can be a channel (an integer from 1-32), a sound id (as obtained when sound was created with playSound), an actual sound (ie one that has been defined using defineSound).
+// .. code-block:: Squirrel
+// if (isSoundPlaying(soundElevatorMusic)) { ...}
 static SQInteger isSoundPlaying(HSQUIRRELVM v) {
-	warning("TODO: isSoundPlaying not implemented");
-	return 0;
+	int soundId;
+	if (SQ_FAILED(sqget(v, 2, soundId)))
+		return sq_throwerror(v, "failed to get sound");
+	sqpush(v, g_engine->_audio.playing(soundId));
+	return 1;
 }
 
 static SQInteger playObjectSound(HSQUIRRELVM v) {
-	warning("TODO: playObjectSound not implemented");
-	return 0;
+	SQInteger nArgs = sq_gettop(v);
+	SoundDefinition *soundDef = sqsounddef(v, 2);
+	if (!soundDef)
+		return sq_throwerror(v, "failed to get sound");
+
+	Object *obj = sqobj(v, 3);
+	if (!obj)
+		return sq_throwerror(v, "failed to get actor or object");
+	int loopTimes = 1;
+	float fadeInTime = 0.0f;
+	if (nArgs >= 4) {
+		sqget(v, 4, loopTimes);
+		sqget(v, 5, fadeInTime);
+	}
+
+	if (obj->_sound) {
+		g_engine->_audio.stop(obj->_sound);
+	}
+
+	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, obj->getId());
+	obj->_sound = soundId;
+	sqpush(v, soundId);
+	return 1;
 }
 
+// Plays a sound that has been loaded with defineSound(file).
+// Classifies the audio as "sound" (not "music").
+// Returns a sound ID that can be used to reference the sound later on.
+// .. code-block:: Squirrel
+// playSound(clock_tick)
+// objectState(quickiePalFlickerLight, ON)
+// _flourescentSoundID = playSound(soundFlourescentOn)
 static SQInteger playSound(HSQUIRRELVM v) {
-	warning("TODO: playSound not implemented");
-	return 0;
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound) {
+		int soundId = 0;
+		sqget(v, 2, soundId);
+		return sq_throwerror(v, Common::String::format("failed to get sound: %d", soundId).c_str());
+	}
+	int soundId = g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	sqpush(v, soundId);
+	return 1;
 }
 
+// Starts playing sound at the specified volume, where volume is a float between 0 and 1.
+// Not for use in adjusting the volume of a sound that is already playing.
+// Returns a sound ID which can be used when turning off the sound or otherwise manipulating it.
+// .. code-block:: Squirrel
+// script runAway(bunnyActor) {
+//     local soundVolume = 1.0
+//     for (local soundVolume = 1.0; x > 0; x -= 0.25) {
+//         playSoundVolume(soundHop, soundVolume)
+//         objectOffsetTo(bunnyActor, -10, 0, 0.5)
+//         breaktime(1.0)
+//     }
+// }
 static SQInteger playSoundVolume(HSQUIRRELVM v) {
-	warning("TODO: playSoundVolume not implemented");
-	return 0;
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound)
+		return sq_throwerror(v, "failed to get sound");
+	int soundId = g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	sqpush(v, soundId);
+	return 1;
 }
 
 static SQInteger loadSound(HSQUIRRELVM v) {
-	warning("TODO: loadSound not implemented");
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound)
+		return sq_throwerror(v, "failed to get sound");
+	sound->load();
 	return 0;
 }
 
+// Loops music.
+// If loopTimes is not defined or is -1, will loop infinitely.
+// For the first loop, it will fade the sound in for fadeInTime seconds, if specified.
+// See also loopSound, which classifies the audio as being "sound" not "music".
+// This is important if we allow separate volume control adjustment.
+// .. code-block:: Squirrel
+// enter = function()
+// {
+//     print("Enter StartScreen")
+//     exCommand(EX_BUTTON_HOVER_SOUND, soundClockTick)
+//     _music = loopMusic(musicTempA)
+// }
 static SQInteger loopMusic(HSQUIRRELVM v) {
-	warning("TODO: loopMusic not implemented");
-	return 0;
+	int loopTimes = -1;
+	float fadeInTime = 0.f;
+	SQInteger numArgs = sq_gettop(v);
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound)
+		return sq_throwerror(v, "failed to get music");
+	if (numArgs == 3) {
+		sqget(v, 3, loopTimes);
+	}
+	if (numArgs == 4) {
+		sqget(v, 4, fadeInTime);
+	}
+	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kMusicSoundType, loopTimes, fadeInTime);
+	sqpush(v, soundId);
+	return 1;
 }
 
 static SQInteger loopObjectSound(HSQUIRRELVM v) {
-	warning("TODO: loopObjectSound not implemented");
-	return 0;
+	int loopTimes = -1;
+	float fadeInTime = 0.f;
+	SQInteger numArgs = sq_gettop(v);
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound)
+		return sq_throwerror(v, "failed to get music");
+	Object *obj = sqobj(v, 3);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	if (numArgs == 4) {
+		if (SQ_FAILED(sqget(v, 4, loopTimes))) {
+			return sq_throwerror(v, "failed to get loopTimes");
+		}
+	}
+	if (numArgs == 5) {
+		if (SQ_FAILED(sqget(v, 5, fadeInTime))) {
+			return sq_throwerror(v, "failed to get fadeInTime");
+		}
+	}
+	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
+	sqpush(v, soundId);
+	return 1;
 }
 
+// Loops a sound a specified number of times (loopTimes).
+// If loopTimes = -1 or not set, then it loops the sound forever.
+// You can fade in the sound for the first loop by setting the fadeInTime duration (in seconds).
+// If fadeInTime is 0 or not set, it will immediately be at full volume.
+// Returns a sound ID which can be used when turning off the sound or otherwise manipulating it.
+// See also loopMusic.
+// .. code-block:: Squirrel
+// local _muzac = loopSound(soundElevatorMusic, -1, 1.0)
+//
+// script daveCooking() {
+//     loopSound(soundSizzleLoop)
+//     ...
+// }
+//
+// if (Bank.bankTelephone.inUse) {
+//     breaktime(0.5)
+//     loopSound(soundPhoneBusy, 3)
+// }
 static SQInteger loopSound(HSQUIRRELVM v) {
-	warning("TODO: loopSound not implemented");
-	return 0;
+	int loopTimes = -1;
+	float fadeInTime = 0.f;
+	SQInteger numArgs = sq_gettop(v);
+	SoundDefinition *sound = sqsounddef(v, 2);
+	if (!sound)
+		return sq_throwerror(v, "failed to get music");
+	if (numArgs == 3) {
+		if (SQ_FAILED(sqget(v, 3, loopTimes))) {
+			return sq_throwerror(v, "failed to get loopTimes");
+		}
+	}
+	if (numArgs == 4) {
+		if (SQ_FAILED(sqget(v, 4, fadeInTime))) {
+			return sq_throwerror(v, "failed to get fadeInTime");
+		}
+	}
+	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
+	sqpush(v, soundId);
+	return 1;
+}
+
+static SQInteger soundVolume(HSQUIRRELVM v, Audio::Mixer::SoundType soundType) {
+	float volume = 0.f;
+	if (sq_gettop(v) == 2) {
+		if (SQ_FAILED(sqget(v, 2, volume))) {
+			return sq_throwerror(v, "failed to get volume");
+		}
+		int vol = volume * Audio::Mixer::kMaxMixerVolume;
+		g_engine->_mixer->setVolumeForSoundType(soundType, vol);
+		return 0;
+	}
+	volume = (float)g_engine->_mixer->getVolumeForSoundType(soundType) / Audio::Mixer::kMaxMixerVolume;
+	sqpush(v, volume);
+	return 1;
 }
 
 static SQInteger masterSoundVolume(HSQUIRRELVM v) {
-	warning("TODO: masterSoundVolume not implemented");
-	return 0;
+	float volume = 0.f;
+	if (sq_gettop(v) == 2) {
+		if (SQ_FAILED(sqget(v, 2, volume))) {
+			return sq_throwerror(v, "failed to get volume");
+		}
+		g_engine->_audio.setMasterVolume(volume);
+		return 0;
+	}
+	volume = g_engine->_audio.getMasterVolume();
+	sqpush(v, volume);
+	return 1;
 }
 
 static SQInteger musicMixVolume(HSQUIRRELVM v) {
-	warning("TODO: musicMixVolume not implemented");
-	return 0;
+	return soundVolume(v, Audio::Mixer::SoundType::kMusicSoundType);
 }
 
 static SQInteger playMusic(HSQUIRRELVM v) {
-	warning("TODO: playMusic not implemented");
-	return 0;
+	SoundDefinition *soundDef = sqsounddef(v, 2);
+	if (!soundDef)
+		return sq_throwerror(v, "failed to get music");
+	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
+	sqpush(v, soundId);
+	return 1;
 }
 
 static SQInteger soundMixVolume(HSQUIRRELVM v) {
-	warning("TODO: soundMixVolume not implemented");
-	return 0;
+	return soundVolume(v, Audio::Mixer::SoundType::kPlainSoundType);
 }
 
+// Sets the volume (float from 0 to 1) of an already playing sound.
+// Can be used for a channel (integer 1-32), soundId (as obtained when starting the sound playing) or an actual sound (defined by defineSound).
+// If _sound is not yet playing, then nothing will happen (if sound is subsequently set to play it will be at full volume).
+// .. code-block:: Squirrel
+// local _tronSoundTID = loopObjectSound(soundTronRattle_Loop, quickieToilet, -1, 0.25)
+// soundVolume(_tronSoundTID, 0.2)
+// shakeObject(quickieToilet, 0.25)
+// jiggleObject(quickieToilet, 0.25)
+// breaktime(0.2)
 static SQInteger soundVolume(HSQUIRRELVM v) {
-	warning("TODO: soundVolume not implemented");
+	int soundId;
+	if (SQ_FAILED(sqget(v, 2, soundId)))
+		return sq_throwerror(v, "failed to get sound");
+	float volume = 1.0f;
+	if (SQ_FAILED(sqget(v, 3, volume)))
+		return sq_throwerror(v, "failed to get volume");
+	g_engine->_audio.setVolume(soundId, volume);
 	return 0;
 }
 
 static SQInteger stopAllSounds(HSQUIRRELVM v) {
-	warning("TODO: stopAllSounds not implemented");
-	return 0;
-}
-
-static SQInteger stopMusic(HSQUIRRELVM v) {
-	warning("TODO: stopMusic not implemented");
+	g_engine->_mixer->stopAll();
 	return 0;
 }
 
+// Immediately stops the indicated sound.
+// Abruptly. Silently. No fades. It's dead.
+// Can be used for a channel (integer 1-32), _soundId (as obtained when starting the sound playing) or an actual sound (defined by defineSound).
+// If using a defined sound, will stop any sound that is named that, eg all cricket sounds (soundCrickets, soundCrickets).
+// .. code-block:: Squirrel
+// stopSound(soundElevatorMusic)
 static SQInteger stopSound(HSQUIRRELVM v) {
-	warning("TODO: stopSound not implemented");
+	int soundId;
+	if (SQ_FAILED(sqget(v, 2, soundId)))
+		return sq_throwerror(v, "failed to get sound");
+	g_engine->_audio.stop(soundId);
 	return 0;
 }
 
 static SQInteger talkieMixVolume(HSQUIRRELVM v) {
-	warning("TODO: talkieMixVolume not implemented");
-	return 0;
+	return soundVolume(v, Audio::Mixer::SoundType::kSpeechSoundType);
 }
 
 void sqgame_register_soundlib(HSQUIRRELVM v) {
-  regFunc(v, actorSound, "actorSound");
-  regFunc(v, defineSound, "defineSound");
-  regFunc(v, fadeOutSound, "fadeOutSound");
-  regFunc(v, isSoundPlaying, "isSoundPlaying");
-  regFunc(v, loadSound, "loadSound");
-  regFunc(v, loopMusic, "loopMusic");
-  regFunc(v, loopObjectSound, "loopObjectSound");
-  regFunc(v, loopSound, "loopSound");
-  regFunc(v, masterSoundVolume, "masterSoundVolume");
-  regFunc(v, musicMixVolume, "musicMixVolume");
-  regFunc(v, playMusic, "playMusic");
-  regFunc(v, playObjectSound, "playObjectSound");
-  regFunc(v, playSound, "playSound");
-  regFunc(v, playSoundVolume, "playSoundVolume");
-  regFunc(v, soundMixVolume, "soundMixVolume");
-  regFunc(v, soundVolume, "soundVolume");
-  regFunc(v, stopAllSounds, "stopAllSounds");
-  regFunc(v, stopMusic, "stopMusic");
-  regFunc(v, stopSound, "stopSound");
-  regFunc(v, talkieMixVolume, "talkieMixVolume");
+	regFunc(v, actorSound, "actorSound");
+	regFunc(v, defineSound, "defineSound");
+	regFunc(v, fadeOutSound, "fadeOutSound");
+	regFunc(v, isSoundPlaying, "isSoundPlaying");
+	regFunc(v, loadSound, "loadSound");
+	regFunc(v, loopMusic, "loopMusic");
+	regFunc(v, loopObjectSound, "loopObjectSound");
+	regFunc(v, loopSound, "loopSound");
+	regFunc(v, masterSoundVolume, "masterSoundVolume");
+	regFunc(v, musicMixVolume, "musicMixVolume");
+	regFunc(v, playMusic, "playMusic");
+	regFunc(v, playObjectSound, "playObjectSound");
+	regFunc(v, playSound, "playSound");
+	regFunc(v, playSoundVolume, "playSoundVolume");
+	regFunc(v, soundMixVolume, "soundMixVolume");
+	regFunc(v, soundVolume, "soundVolume");
+	regFunc(v, stopAllSounds, "stopAllSounds");
+	regFunc(v, stopSound, "stopSound");
+	regFunc(v, talkieMixVolume, "talkieMixVolume");
 }
 } // namespace Twp
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 9f6f28e4476..b5f043864af 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -314,6 +314,38 @@ Object *sqactor(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
+static SoundDefinition *sqsounddef(int id) {
+	for (int i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
+		SoundDefinition *sound = g_engine->_audio._soundDefs[i];
+		if (sound->getId() == id)
+			return sound;
+	}
+	return nullptr;
+}
+
+SoundDefinition *sqsounddef(HSQUIRRELVM v, int i) {
+	int id;
+	if (SQ_SUCCEEDED(sqget(v, i, id)))
+		return sqsounddef(id);
+	return nullptr;
+}
+
+// Audio::SoundHandle sqsound(int id) {
+//   for (int i=0;i<sound in g_engine->_mixer->_channels.size;i++) {
+// 	chan = g_engine->_mixer->_channels[i];
+//     if not sound.isNil and sound.id == id:
+//       return sound;
+//   }
+//   return {};
+// }
+
+SoundDefinition *sqsound(HSQUIRRELVM v, int i) {
+	// int id;
+	// if (SQ_SUCCEEDED(sqget(v, i, id)))
+	// 	return sqsound(id);
+	return nullptr;
+}
+
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	SQInteger top = sq_gettop(v);
 	sqpush(v, obj);
@@ -386,4 +418,21 @@ ThreadBase *sqthread(HSQUIRRELVM v) {
 	});
 }
 
+static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<SoundDefinition *> &arr) {
+	sq_pushobject(v, o);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		arr.push_back(sqsounddef(v, -1));
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 1);
+}
+
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<SoundDefinition *> &arr) {
+	HSQOBJECT obj;
+	SQRESULT result = sq_getstackobj(v, i, &obj);
+	sqgetarray(v, obj, arr);
+	return result;
+}
+
 } // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index aaaa118a796..f9b7ac9526a 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -80,6 +80,7 @@ void setId(HSQOBJECT &o, int id);
 
 void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
 SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr);
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<SoundDefinition*>&  arr);
 
 template<typename TFunc>
 void sqgetitems(HSQOBJECT o, TFunc func) {
@@ -153,6 +154,7 @@ Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
 Object *sqactor(HSQOBJECT table);
 Object *sqactor(HSQUIRRELVM v, int i);
+SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);
 ThreadBase *sqthread(HSQUIRRELVM v);
 ThreadBase *sqthread(HSQUIRRELVM v, int id);
 ThreadBase *sqthread(int id);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 12f2085dfd8..6377d823650 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -303,14 +303,11 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 
 	ThreadBase *t = sqthread(id);
 	if (!t) {
-		// TODO: sound
-		// let sound = sound(id);
-		// if (!sound) {
-		// 	warning("thread and sound not found: %d", id);
-		// 	return 0;
-		// }
-		// return breakwhilecond(v, [&] { return sound(id); }, "breakwhilerunning(%d)", id);
-		return 0;
+		if(!isSound(id)) {
+			warning("thread and sound not found: %d", id);
+			return 0;
+		}
+		return breakwhilecond(v, [id] { return g_engine->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
 	}
 	return breakwhilecond(
 		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
@@ -381,9 +378,13 @@ static SQInteger breakwhilewalking(HSQUIRRELVM v) {
 		v, [obj]() { return obj->getWalkTo() && obj->getWalkTo()->isEnabled(); }, "breakwhilewalking(%s)", obj->_name.c_str());
 }
 
+// Breaks until specified sound has finished playing.
+// Once sound finishes, the method will continue running.
 static SQInteger breakwhilesound(HSQUIRRELVM v) {
-	warning("TODO: breakwhilesound: not implemented");
-	return 0;
+  int soundId = 0;
+  if (SQ_FAILED(sqget(v, 2, soundId)))
+		return sq_throwerror(v, "failed to get sound");
+  return breakwhilecond(v, [soundId](){ return g_engine->_audio.playing(soundId); }, "breakwhilesound(%d)", soundId);
 }
 
 static SQInteger cutscene(HSQUIRRELVM v) {
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 47ad22f94a6..ba057170999 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -174,8 +174,9 @@ void Cutscene::stop() {
 		g_engine->_inputState.setInputActive(true);
 	debug("Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
-	for (int i = 0; i < g_engine->_threads.size(); i++) {
-		ThreadBase *thread = g_engine->_threads[i];
+	Common::Array<ThreadBase *> threads(g_engine->_threads);
+	for (int i = 0; i < threads.size(); i++) {
+		ThreadBase *thread = threads[i];
 		if (thread->isGlobal())
 			thread->unpause();
 	}
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d3958065360..7367095b6b6 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -334,6 +334,8 @@ void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
 
+	_audio.update(elapsed);
+
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
 	_inputState.setVisible(_inputState.getShowCursor() || _dialog.getState() == WaitingForChoice);
@@ -963,7 +965,7 @@ void TwpEngine::actorEnter() {
 
 void TwpEngine::exitRoom(Room *nextRoom) {
 	HSQUIRRELVM v = getVm();
-	// TODO: _audio.stopAll()
+	_mixer->stopAll();
 	if (_room) {
 		_room->_triggers.clear();
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index cb0faf74870..f87713df9c1 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -45,6 +45,7 @@
 #include "twp/hud.h"
 #include "twp/callback.h"
 #include "twp/walkboxnode.h"
+#include "twp/audio.h"
 #include "twp/actorswitcher.h"
 
 #define SCREEN_MARGIN 100.f
@@ -216,6 +217,7 @@ public:
 	Hud _hud;
 	Inventory _uiInv;
 	ActorSwitcher _actorSwitcher;
+	AudioSystem _audio;
 
 private:
 	Gfx _gfx;


Commit: bcb1c4d1cdef26fa6df36ca6952bc88d72d5c6fd
    https://github.com/scummvm/scummvm/commit/bcb1c4d1cdef26fa6df36ca6952bc88d72d5c6fd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add trigger sound

Changed paths:
    engines/twp/object.cpp
    engines/twp/scenegraph.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 771c0e995ad..330d5fe7f9c 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -210,21 +210,20 @@ void Object::trig(const Common::String &name) {
 	// debug fmt"Trigger object #{self.id} ({self.name}) sound '{name}'"
 	int trigNum;
 	sscanf(name.c_str(), "@%d", &trigNum);
-	if (trigNum != 0) {
+	if ((name.size() > 1) && Common::isDigit(name[1])) {
 		if (_triggers.contains(trigNum)) {
 			_triggers[trigNum]->trig();
 		} else {
-			// warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
 		}
 	} else {
 		int id = 0;
-		sqgetf(sqrootTbl(g_engine->getVm()), name, id);
-		debug("TODO: sound trigger");
-		// var sound = soundDef(id);
-		// if (!sound)
-		// 	warn fmt "Cannot trig sound '{name}', sound not found (id={id})";
-		// else
-		// 	gEngine.audio.play(sound, Sound);
+		sqgetf(sqrootTbl(g_engine->getVm()), name.substr(1), id);
+		SoundDefinition *sound = sqsounddef(id);
+		if (!sound)
+			warning("Cannot trig sound '%s', sound not found (id=%d)", name.c_str(), id);
+		else
+			g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	}
 }
 
@@ -467,11 +466,11 @@ void Object::setAnimationNames(const Common::String &head, const Common::String
 }
 
 void Object::blinkRate(float min, float max) {
-	  if (min == 0.0 && max == 0.0) {
-	    _blink.reset();
-	  } else {
-	    _blink.reset(new Blink(this, min, max));
-	  }
+	if (min == 0.0 && max == 0.0) {
+		_blink.reset();
+	} else {
+		_blink.reset(new Blink(this, min, max));
+	}
 }
 
 void Object::setCostume(const Common::String &name, const Common::String &sheet) {
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 2cff54d0156..295e08ca181 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -287,7 +287,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 void Anim::trigSound() {
 	if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
 		const Common::String &trigger = _anim->triggers[_frameIndex];
-		if (trigger.size() > 0) {
+		if ((trigger.size() > 0) && trigger!= "null") {
 			_obj->trig(trigger);
 		}
 	}
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index b5f043864af..9286c1025ef 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -314,7 +314,7 @@ Object *sqactor(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
-static SoundDefinition *sqsounddef(int id) {
+SoundDefinition *sqsounddef(int id) {
 	for (int i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
 		SoundDefinition *sound = g_engine->_audio._soundDefs[i];
 		if (sound->getId() == id)
@@ -330,22 +330,6 @@ SoundDefinition *sqsounddef(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
-// Audio::SoundHandle sqsound(int id) {
-//   for (int i=0;i<sound in g_engine->_mixer->_channels.size;i++) {
-// 	chan = g_engine->_mixer->_channels[i];
-//     if not sound.isNil and sound.id == id:
-//       return sound;
-//   }
-//   return {};
-// }
-
-SoundDefinition *sqsound(HSQUIRRELVM v, int i) {
-	// int id;
-	// if (SQ_SUCCEEDED(sqget(v, i, id)))
-	// 	return sqsound(id);
-	return nullptr;
-}
-
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	SQInteger top = sq_gettop(v);
 	sqpush(v, obj);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index f9b7ac9526a..9978a4b30f6 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -155,6 +155,7 @@ Object *sqobj(HSQUIRRELVM v, int i);
 Object *sqactor(HSQOBJECT table);
 Object *sqactor(HSQUIRRELVM v, int i);
 SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);
+SoundDefinition *sqsounddef(int id);
 ThreadBase *sqthread(HSQUIRRELVM v);
 ThreadBase *sqthread(HSQUIRRELVM v, int id);
 ThreadBase *sqthread(int id);


Commit: 52a0000fa000da04f6862c52de06488f0a90a6d8
    https://github.com/scummvm/scummvm/commit/52a0000fa000da04f6862c52de06488f0a90a6d8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix animation frame drawing

Changed paths:
    engines/twp/scenegraph.cpp


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 295e08ca181..5fda2eac87e 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -353,8 +353,8 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 			const SpriteSheetFrame &sf = sheet->frameTable[frame];
 			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
-			float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (-1.f + sf.sourceSize.getX()) - sf.spriteSourceSize.left;
-			float y = 0.5f * (sf.sourceSize.getY() + 1.f) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
+			float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (sf.sourceSize.getX()) - sf.spriteSourceSize.left;
+			float y = 0.5f * (sf.sourceSize.getY()) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
 			Math::Vector3d pos(int(-x), int(y), 0.f);
 			trsf.translate(pos);
 			g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);


Commit: 6c55dce29f856629ad013092e89d3dccda47ecf3
    https://github.com/scummvm/scummvm/commit/6c55dce29f856629ad013092e89d3dccda47ecf3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: misc fixes

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 79bfb145e02..5edf56d63a1 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -117,7 +117,7 @@ void ExpVisitor::visit(const YPause &node) {
 }
 
 void ExpVisitor::visit(const YWaitFor &node) {
-	debug("TODO: waitFor {node.actor}");
+	debug("TODO: waitFor {%s}", node._actor.c_str());
 }
 
 void ExpVisitor::visit(const YParrot &node) {
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 18096a9597f..0441c6b429a 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -334,7 +334,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	if (obj->inInventory()) {
-		// TODO: sqpush(v, g_engine->_uiInv.getPos(obj));
+		sqpush(v, g_engine->_uiInv.getPos(obj));
 		return 1;
 	}
 
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 330d5fe7f9c..0184f572103 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -214,14 +214,14 @@ void Object::trig(const Common::String &name) {
 		if (_triggers.contains(trigNum)) {
 			_triggers[trigNum]->trig();
 		} else {
-			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _name.c_str());
+			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _key.c_str());
 		}
 	} else {
 		int id = 0;
 		sqgetf(sqrootTbl(g_engine->getVm()), name.substr(1), id);
 		SoundDefinition *sound = sqsounddef(id);
 		if (!sound)
-			warning("Cannot trig sound '%s', sound not found (id=%d)", name.c_str(), id);
+			warning("Cannot trig sound '%s', sound not found (id=%d, %s)", name.c_str(), id, _key.c_str());
 		else
 			g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	}
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 5fda2eac87e..82ec911c8ec 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -287,7 +287,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 void Anim::trigSound() {
 	if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
 		const Common::String &trigger = _anim->triggers[_frameIndex];
-		if ((trigger.size() > 0) && trigger!= "null") {
+		if ((trigger.size() > 0) && trigger != "null") {
 			_obj->trig(trigger);
 		}
 	}
@@ -508,6 +508,14 @@ Inventory::Inventory() : Node("Inventory") {
 	}
 }
 
+Math::Vector2d Inventory::getPos(Object *inv) const {
+	if (_actor) {
+		int i = Twp::find(_actor->_inventory, inv) - _actor->_inventoryOffset * NUMOBJECTSBYROW;
+		return Math::Vector2d(_itemRects[i].left + _itemRects[i].width() / 2.f, _itemRects[i].bottom + _itemRects[i].height() / 2.f);
+	}
+	return {};
+}
+
 void Inventory::drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index e8bf66073a5..c3c60eb5606 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -289,6 +289,7 @@ public:
 	void update(float elapsed, Object* actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
 
 	Object* getObject() const { return _obj; }
+	Math::Vector2d getPos(Object* inv) const;
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;


Commit: fe0c2723fcedaba74a56e35b38b77ef69cb4817e
    https://github.com/scummvm/scummvm/commit/fe0c2723fcedaba74a56e35b38b77ef69cb4817e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Load savegame

Changed paths:
  A engines/twp/btea.cpp
  A engines/twp/btea.h
  A engines/twp/savegame.cpp
  A engines/twp/savegame.h
  A engines/twp/time.cpp
  A engines/twp/time.h
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/module.mk
    engines/twp/roomlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/btea.cpp b/engines/twp/btea.cpp
new file mode 100644
index 00000000000..619fcc71d31
--- /dev/null
+++ b/engines/twp/btea.cpp
@@ -0,0 +1,72 @@
+/* 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 "twp/btea.h"
+
+namespace Twp {
+
+#define DELTA 0x9e3779b9
+#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
+
+void BTEACrypto::encrypt(uint32_t *v, int n, const uint32_t *key) {
+	btea(v, n, key);
+}
+
+void BTEACrypto::decrypt(uint32_t *v, int n, const uint32_t *key) {
+	btea(v, -n, key);
+}
+
+// This method comes from https://en.wikipedia.org/wiki/XXTEA
+void BTEACrypto::btea(uint32_t *v, int n, const uint32_t *key) {
+	uint32_t y, z, sum;
+	unsigned p, rounds, e;
+	if (n > 1) { /* Coding Part */
+		rounds = 6 + 52 / n;
+		sum = 0;
+		z = v[n - 1];
+		do {
+			sum += DELTA;
+			e = (sum >> 2) & 3;
+			for (p = 0; p < static_cast<uint32_t>(n - 1); p++) {
+				y = v[p + 1];
+				z = v[p] += MX;
+			}
+			y = v[0];
+			z = v[n - 1] += MX;
+		} while (--rounds);
+	} else if (n < -1) { /* Decoding Part */
+		n = -n;
+		rounds = 6 + 52 / n;
+		sum = rounds * DELTA;
+		y = v[0];
+		do {
+			e = (sum >> 2) & 3;
+			for (p = n - 1; p > 0; p--) {
+				z = v[p - 1];
+				y = v[p] -= MX;
+			}
+			z = v[n - 1];
+			y = v[0] -= MX;
+			sum -= DELTA;
+		} while (--rounds);
+	}
+}
+} // namespace Twp
diff --git a/engines/twp/btea.h b/engines/twp/btea.h
new file mode 100644
index 00000000000..e41b7a01870
--- /dev/null
+++ b/engines/twp/btea.h
@@ -0,0 +1,40 @@
+/* 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 TWP_BTEA_H
+#define TWP_BTEA_H
+
+#include "common/system.h"
+
+namespace Twp {
+
+class BTEACrypto {
+public:
+  static void encrypt(uint32_t *v, int n, const uint32_t *k);
+  static void decrypt(uint32_t *v, int n, const uint32_t *k);
+
+private:
+  static void btea(uint32_t *v, int n, const uint32_t *k);
+};
+
+} // End of namespace Twp
+
+#endif // TWP_H
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 775ebbc3f4e..f28f5bb97c1 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -20,27 +20,28 @@
  */
 
 #include "common/translation.h"
-
+#include "common/savefile.h"
+#include "graphics/scaler.h"
+#include "image/png.h"
 #include "twp/metaengine.h"
 #include "twp/detection.h"
 #include "twp/twp.h"
+#include "twp/savegame.h"
+#include "twp/time.h"
+
+#define MAX_SAVES 99
 
 namespace Twp {
 
 static const ADExtraGuiOptionsMap optionsList[] = {
-	{
-		GAMEOPTION_ORIGINAL_SAVELOAD,
-		{
-			_s("Use original save/load screens"),
-			_s("Use the original save/load screens instead of the ScummVM ones"),
-			"original_menus",
-			false,
-			0,
-			0
-		}
-	},
-	AD_EXTRA_GUI_OPTIONS_TERMINATOR
-};
+	{GAMEOPTION_ORIGINAL_SAVELOAD,
+	 {_s("Use original save/load screens"),
+	  _s("Use the original save/load screens instead of the ScummVM ones"),
+	  "original_menus",
+	  false,
+	  0,
+	  0}},
+	AD_EXTRA_GUI_OPTIONS_TERMINATOR};
 
 } // End of namespace Twp
 
@@ -59,7 +60,77 @@ Common::Error TwpMetaEngine::createInstance(OSystem *syst, Engine **engine, cons
 
 bool TwpMetaEngine::hasFeature(MetaEngineFeature f) const {
 	return checkExtendedSaves(f) ||
-		(f == kSupportsLoadingDuringStartup);
+		   (f == kSupportsLoadingDuringStartup);
+}
+
+int TwpMetaEngine::getMaximumSaveSlot() const {
+	return MAX_SAVES;
+}
+
+SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+	Common::String filename = Common::String::format("%s%02d.save", target, slot);
+	Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
+
+	if (f) {
+
+		Common::InSaveFile *thumbnailFile = g_system->getSavefileManager()->openForLoading(Common::String::format("%s%02d.png", target, slot));
+
+		// Create the return descriptor
+		SaveStateDescriptor desc(this, slot, "?");
+
+		desc.setAutosave(slot == 1);
+		if (thumbnailFile) {
+			Image::PNGDecoder png;
+			if (png.loadStream(*thumbnailFile)) {
+				Graphics::ManagedSurface *thumbnail = new Graphics::ManagedSurface();
+				thumbnail->copyFrom(*png.getSurface());
+				Graphics::Surface *thumbnailSmall = new Graphics::Surface();
+				createThumbnail(thumbnailSmall, thumbnail);
+				desc.setThumbnail(thumbnailSmall);
+			}
+		}
+		Twp::SaveGame savegame;
+		Twp::SaveGameManager::getSaveGame(f, savegame);
+		Common::String time = Twp::formatTime(savegame.time, "%b %d at %H:%M");
+		if (savegame.easyMode)
+			time += " (casual)";
+		desc.setDescription(time);
+		desc.setPlayTime(savegame.gameTime * 1000);
+		Twp::DateTime dt = Twp::toDateTime(savegame.time);
+		desc.setSaveDate(dt.year, dt.month, dt.day);
+		desc.setSaveTime(dt.hour, dt.min);
+
+		return desc;
+	}
+
+	return SaveStateDescriptor();
+}
+
+SaveStateList TwpMetaEngine::listSaves(const char *target) const {
+	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+	Common::StringArray filenames;
+	Common::String saveDesc;
+	Common::String pattern = Common::String::format("%s##.save", target);
+
+	filenames = saveFileMan->listSavefiles(pattern);
+
+	SaveStateList saveList;
+	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+		const char *ext = strrchr(file->c_str(), '.');
+		int slot = ext ? atoi(ext - 2) : -1;
+
+		if (slot >= 0 && slot <= MAX_SAVES) {
+			Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
+
+			if (in) {
+				saveList.push_back(SaveStateDescriptor(this, slot, "?"));
+			}
+		}
+	}
+
+	// Sort saves based on slot number.
+	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
+	return saveList;
 }
 
 #if PLUGIN_ENABLED_DYNAMIC(TWP)
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 4938effecff..bf6541178a5 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -38,6 +38,11 @@ public:
 	bool hasFeature(MetaEngineFeature f) const override;
 
 	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+
+	SaveStateList listSaves(const char *target) const override;
+	int getMaximumSaveSlot() const override;
+
+	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
 };
 
 #endif // TWP_METAENGINE_H
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 045f75e6b0d..829a94385c0 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -66,6 +66,9 @@ MODULE_OBJS = \
 	actorswitcher.o \
 	enginedialogtarget.o \
 	audio.o \
+	savegame.o \
+	btea.o \
+	time.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index f08775a6135..1bb042254d8 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -261,6 +261,7 @@ static SQInteger masterRoomArray(HSQUIRRELVM v) {
 }
 
 static SQInteger removeTrigger(HSQUIRRELVM v) {
+	if(!g_engine->_room) return 0;
 	if (sq_gettype(v, 2) == OT_CLOSURE) {
 		HSQOBJECT closure;
 		sq_resetobject(&closure);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
new file mode 100644
index 00000000000..8c4fc9487c1
--- /dev/null
+++ b/engines/twp/savegame.cpp
@@ -0,0 +1,558 @@
+/* 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 "twp/ggpack.h"
+#include "twp/savegame.h"
+#include "twp/squtil.h"
+#include "twp/btea.h"
+
+namespace Twp {
+
+static Object *actor(const Common::String &key) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *a = g_engine->_actors[i];
+		if (a->_key == key)
+			return a;
+	}
+	return nullptr;
+}
+
+static DialogConditionMode parseMode(char mode) {
+	switch (mode) {
+	case '?':
+		return Once;
+	case '#':
+		return ShowOnce;
+	case '&':
+		return OnceEver;
+	case '$':
+		return ShowOnceEver;
+	case '^':
+		return TempOnce;
+	default:
+		warning("Invalid dialog condition mode: %c", mode);
+		return TempOnce;
+	}
+}
+
+static DialogConditionState parseState(Common::String &dialog) {
+	Common::String dialogName;
+	int i = 1;
+	while (i < dialog.size() && !Common::isDigit(dialog[i])) {
+		dialogName += dialog[i];
+		i++;
+	}
+
+	while (!g_engine->_pack.assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
+		dialogName += dialog[i];
+		i++;
+	}
+
+	Common::String num;
+	while ((i < dialog.size()) && Common::isDigit(dialog[i])) {
+		num += dialog[i];
+		i++;
+	}
+
+	DialogConditionState result;
+	result.mode = parseMode(dialog[0]);
+	result.dialog = dialogName;
+	result.line = atol(num.c_str());
+	result.actorKey = dialog.substr(i);
+	return result;
+}
+
+static Room *room(const Common::String &name) {
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		if (room->_name == name) {
+			return room;
+		}
+	}
+	return nullptr;
+}
+
+static Object *object(Room *room, const Common::String &key) {
+	for (int i = 0; i < room->_layers.size(); i++) {
+		Layer *layer = room->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *o = layer->_objects[j];
+			if (o->_key == key)
+				return o;
+		}
+	}
+	return nullptr;
+}
+
+static Object *object(const Common::String &key) {
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		for (int j = 0; j < room->_layers.size(); j++) {
+			Layer *layer = room->_layers[j];
+			for (int k = 0; k < layer->_objects.size(); k++) {
+				Object *o = layer->_objects[k];
+				if (o->_key == key)
+					return o;
+			}
+		}
+	}
+	return nullptr;
+}
+
+static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
+	HSQUIRRELVM v = g_engine->getVm();
+	SQInteger top = sq_gettop(v);
+	sq_resetobject(&obj);
+	if (json->isString()) {
+		sqpush(v, json->asString());
+		sqget(v, -1, obj);
+	} else if (json->isIntegerNumber()) {
+		sqpush(v, json->asIntegerNumber());
+		sqget(v, -1, obj);
+	} else if (json->isBool()) {
+		sqpush(v, json->asBool());
+		sqget(v, -1, obj);
+	} else if (json->isNumber()) {
+		sqpush(v, json->asNumber());
+		sqget(v, -1, obj);
+	} else if (json->isNull()) {
+	} else if (json->isArray()) {
+		sq_newarray(v, 0);
+		const Common::JSONArray &jArr = json->asArray();
+		for (int i = 0; i < jArr.size(); i++) {
+			HSQOBJECT tmp;
+			toSquirrel(jArr[i], tmp);
+			sqpush(v, tmp);
+			sq_arrayappend(v, -2);
+		}
+		sqget(v, -1, obj);
+	} else if (json->isObject()) {
+		const Common::JSONObject &jObject = json->asObject();
+		// check if it's a reference to an actor
+		if (jObject.contains("_actorKey")) {
+			obj = actor(jObject["_actorKey"]->asString())->_table;
+		} else if (jObject.contains("_roomKey")) {
+			Common::String roomName = jObject["_roomKey"]->asString();
+			if (jObject.contains("_objectKey")) {
+				Common::String objName = jObject["_objectKey"]->asString();
+				Room *r = room(roomName);
+				if (!r)
+					warning("room with key=%s not found", roomName.c_str());
+				else {
+					Object *o = object(r, objName);
+					if (!o)
+						warning("room object with key=%s/%s not found", roomName.c_str(), objName.c_str());
+					else
+						obj = o->_table;
+				}
+			} else {
+				Room *r = room(roomName);
+				if (!r) {
+					warning("room with key=%s not found", roomName.c_str());
+				} else {
+					obj = r->_table;
+				}
+			}
+		} else if (jObject.contains("_objectKey")) {
+			Common::String objName = jObject["_objectKey"]->asString();
+			Object *o = object(objName);
+			if (!o) {
+				warning("object with key=%s not found", objName.c_str());
+			} else {
+				obj = o->_table;
+			}
+		} else {
+			sq_newtable(v);
+			for (auto it = jObject.begin(); it != jObject.end(); it++) {
+				sqpush(v, it->_key);
+				HSQOBJECT tmp;
+				toSquirrel(it->_value, tmp);
+				sqpush(v, tmp);
+				sq_newslot(v, -3, SQFalse);
+			}
+			sqget(v, -1, obj);
+		}
+	}
+	sq_addref(v, &obj);
+	sq_settop(v, top);
+}
+
+static Common::String sub(const Common::String &s, size_t pos, size_t end) {
+	return s.substr(pos, s.size() - end);
+}
+
+static void setAnimations(Object *actor, const Common::JSONArray &anims) {
+	Common::String headAnim = anims[0]->asString();
+	Common::String standAnim = sub(anims[9]->asString(), 0, 7);
+	Common::String walkAnim = sub(anims[11]->asString(), 0, 7);
+	Common::String reachAnim = sub(anims[15]->asString(), 0, 11);
+	actor->setAnimationNames(headAnim, standAnim, walkAnim, reachAnim);
+}
+
+static void loadActor(Object *actor, const Common::JSONObject &json) {
+	bool touchable = true;
+	if (json.contains("_untouchable"))
+		touchable = json["_untouchable"]->asIntegerNumber() == 0;
+	actor->setTouchable(touchable);
+	bool hidden = false;
+	if (json.contains("_hidden"))
+		hidden = json["_hidden"]->asIntegerNumber() == 1;
+	actor->_node->setVisible(!hidden);
+	for (auto it = json.begin(); it != json.end(); it++) {
+		if (it->_key == "_animations") {
+			setAnimations(actor, it->_value->asArray());
+		} else if (it->_key == "_pos") {
+			actor->_node->setPos(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_costume") {
+			Common::String sheet;
+			if (json.contains("_costumeSheet"))
+				sheet = json["_costumeSheet"]->asString();
+			actor->setCostume(it->_value->asString(), sheet);
+		} else if (it->_key == "_costumeSheet") {
+		} else if (it->_key == "_color") {
+			actor->_node->setColor(Color::rgb(it->_value->asIntegerNumber()));
+			actor->_node->setAlpha(Color::fromRgba(it->_value->asIntegerNumber()).rgba.a);
+		} else if (it->_key == "_dir") {
+			actor->setFacing((Facing)it->_value->asIntegerNumber());
+		} else if (it->_key == "_lockFacing") {
+			actor->lockFacing(it->_value->asIntegerNumber());
+		} else if (it->_key == "_useDir") {
+			actor->_useDir = (Direction)it->_value->asIntegerNumber();
+		} else if (it->_key == "_usePos") {
+			actor->_usePos = parseVec2(it->_value->asString());
+		} else if (it->_key == "_offset") {
+			actor->_node->setOffset(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_renderOffset") {
+			actor->_node->setRenderOffset(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_roomKey") {
+			actor->setRoom(room(it->_value->asString()));
+		} else if ((it->_key == "_hidden") || (it->_key == "_untouchable")) {
+		} else if (it->_key == "_volume") {
+			actor->_volume = it->_value->asNumber();
+		} else {
+			if (!it->_key.hasPrefix("_")) {
+				HSQOBJECT tmp;
+				toSquirrel(it->_value, tmp);
+				if (sqrawexists(actor->_table, it->_key))
+					sqsetf(actor->_table, it->_key, tmp);
+				else
+					sqnewf(actor->_table, it->_key, tmp);
+			} else {
+				warning("load actor: key '%s' is unknown", it->_key.c_str());
+			}
+		}
+	}
+
+	if (sqrawexists(actor->_table, "postLoad"))
+		sqcall(actor->_table, "postLoad");
+}
+
+static void loadObject(Object *obj, const Common::JSONObject &json) {
+	int state = 0;
+	if (json.contains("_state"))
+		state = json["_state"]->asIntegerNumber();
+	if (obj->_node)
+		obj->setState(state, true);
+	else
+		warning("obj '{obj.key}' has no node !");
+	bool touchable = true;
+	if (json.contains("_touchable"))
+		touchable = json["_touchable"]->asIntegerNumber() == 1;
+	obj->setTouchable(touchable);
+	bool hidden = false;
+	if (json.contains("_hidden"))
+		hidden = json["_hidden"]->asIntegerNumber() == 1;
+	obj->_node->setVisible(!hidden);
+
+	for (auto it = json.begin(); it != json.end(); it++) {
+		if ((it->_key == "_state") || (it->_key == "_state") || (it->_key == "_state")) {
+			// discard
+		} else if (it->_key == "_pos") {
+			obj->_node->setPos(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_rotation") {
+			obj->_node->setRotation(it->_value->asNumber());
+		} else if (it->_key == "_dir") {
+			obj->setFacing((Facing)it->_value->asIntegerNumber());
+		} else if (it->_key == "_useDir") {
+			obj->_useDir = (Direction)it->_value->asIntegerNumber();
+		} else if (it->_key == "_usePos") {
+			obj->_usePos = parseVec2(it->_value->asString());
+		} else if (it->_key == "_offset") {
+			obj->_node->setOffset(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_renderOffset") {
+			obj->_node->setRenderOffset(parseVec2(it->_value->asString()));
+		} else if (it->_key == "_roomKey") {
+			obj->setRoom(room(it->_value->asString()));
+		} else if (!it->_key.hasPrefix("_")) {
+			HSQOBJECT tmp;
+			toSquirrel(it->_value, tmp);
+			if (sqrawexists(obj->_table, it->_key))
+				sqsetf(obj->_table, it->_key, tmp);
+			else
+				sqnewf(obj->_table, it->_key, tmp);
+		} else {
+			warning("load object: key '{%s}' is unknown", it->_key.c_str());
+		}
+	}
+
+	if (sqrawexists(obj->_table, "postLoad"))
+		sqcall(obj->_table, "postLoad");
+}
+
+static void loadPseudoObjects(Room *room, const Common::JSONObject &json) {
+	for (auto it = json.begin(); it != json.end(); it++) {
+		Object *o = object(room, it->_key);
+		if (!o)
+			warning("load: room '%s' object '%s' not loaded because it has not been found", room->_name.c_str(), it->_key.c_str());
+		else
+			loadObject(o, it->_value->asObject());
+	}
+}
+
+static void loadRoom(Room *room, const Common::JSONObject &json) {
+	for (auto it = json.begin(); it != json.end(); it++) {
+		if (it->_key == "_pseudoObjects") {
+			loadPseudoObjects(room, it->_value->asObject());
+		} else {
+			if (!it->_key.hasPrefix("_")) {
+				Object *o = object(room, it->_key);
+				if (!o) {
+					HSQOBJECT tmp;
+					toSquirrel(it->_value, tmp);
+					if (sqrawexists(room->_table, it->_key))
+						sqsetf(room->_table, it->_key, tmp);
+					else {
+						sqnewf(room->_table, it->_key, tmp);
+					}
+				} else {
+					loadObject(o, it->_value->asObject());
+				}
+			} else {
+				warning("Load room: key '{%s}' is unknown", it->_key.c_str());
+			}
+		}
+	}
+
+	if (sqrawexists(room->_table, "postLoad"))
+		sqcall(room->_table, "postLoad");
+}
+
+static void setActor(const Common::String &key) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *a = g_engine->_actors[i];
+		if (a->_key == key) {
+			g_engine->setActor(a, false);
+			return;
+		}
+	}
+}
+
+static int32_t computeHash(byte *data, size_t n) {
+	int32_t result = 0x6583463;
+	for (size_t i = 0; i < n; i++) {
+		result += (int32_t)data[i];
+	}
+	return result;
+}
+
+bool SaveGameManager::loadGame(const SaveGame& savegame) {
+	const Common::JSONObject& json = savegame.jSavegame->asObject();
+	long long int version = json["version"]->asIntegerNumber();
+	if (version != 2) {
+		error("Cannot load savegame version %lld", version);
+		delete savegame.jSavegame;
+		return false;
+	}
+
+	sqcall("preLoad");
+	loadGameScene(json["gameScene"]->asObject());
+	loadDialog(json["gameScene"]->asObject());
+	loadCallbacks(json["callbacks"]->asObject());
+	loadGlobals(json["globals"]->asObject());
+	loadActors(json["actors"]->asObject());
+	loadInventory(json["inventory"]);
+	loadRooms(json["rooms"]->asObject());
+	g_engine->_time = json["gameTime"]->asNumber();
+	g_engine->setTotalPlayTime(savegame.gameTime * 1000);
+	g_engine->_inputState.setState((InputStateFlag)json["inputState"]->asIntegerNumber());
+	loadObjects(json["objects"]->asObject());
+	setActor(json["selectedActor"]->asString());
+	g_engine->setRoom(room(json["currentRoom"]->asString()));
+
+	HSQUIRRELVM v = g_engine->getVm();
+	sqsetf(sqrootTbl(v), "SAVEBUILD", json["savebuild"]->asIntegerNumber());
+
+	sqcall("postLoad");
+
+	delete savegame.jSavegame;
+	return true;
+}
+
+bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame& savegame) {
+	static uint32_t savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
+	Common::Array<byte> data(stream->size());
+	stream->read(&data[0], data.size());
+	BTEACrypto::decrypt((uint32_t *)&data[0], data.size() / 4, savegameKey);
+	savegame.hashData = *(int32_t *)(&data[data.size() - 16]);
+	savegame.time = *(int32_t *)&data[data.size() - 12];
+	int32_t hashCheck = computeHash(&data[0], data.size() - 16);
+	if (savegame.hashData != hashCheck)
+		return false;
+
+	MemStream ms;
+	ms.open(&data[0], data.size() - 16);
+
+	GGHashMapDecoder decoder;
+	savegame.jSavegame = decoder.open(&ms);
+	const Common::JSONObject& jSavegame = savegame.jSavegame->asObject();
+	savegame.gameTime = jSavegame["gameTime"]->asNumber();
+	savegame.easyMode = jSavegame["easy_mode"]->asIntegerNumber() != 0;
+
+	return true;
+}
+
+void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
+	int mode = 0;
+	if (json["actorsSelectable"]->asIntegerNumber() != 0)
+		mode |= asOn;
+	if (json["actorsTempUnselectable"]->asIntegerNumber())
+		mode |= asTemporaryUnselectable;
+	g_engine->_actorSwitcher._mode = mode;
+	// TODO: tmpPrefs().forceTalkieText = json["forceTalkieText"].getInt() != 0;
+	const Common::JSONArray &jSelectableActors = json["selectableActors"]->asArray();
+	for (int i = 0; i < jSelectableActors.size(); i++) {
+		const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
+		Object *act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
+		g_engine->_hud._actorSlots[i].actor = act;
+		g_engine->_hud._actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
+	}
+}
+
+void SaveGameManager::loadDialog(const Common::JSONObject &json) {
+	debug("loadDialog");
+	g_engine->_dialog._states.clear();
+	for (auto it = json.begin(); it != json.end(); it++) {
+		Common::String dialog(it->_key);
+		// dialog format: mode dialog number actor
+		// example: #ChetAgentStreetDialog14reyes
+		// mode:
+		// ?: once
+		// #: showonce
+		// &: onceever
+		// $: showonceever
+		// ^: temponce
+		DialogConditionState state = parseState(dialog);
+		g_engine->_dialog._states.push_back(state);
+		// TODO: what to do with this dialog value ?
+		// let value = property.second.getInt()
+	}
+}
+
+void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
+	debug("loadCallbacks");
+	g_engine->_callbacks.clear();
+	if (!json["callbacks"]->isNull()) {
+		const Common::JSONArray &jCallbacks = json["callbacks"]->asArray();
+		for (int i = 0; i < jCallbacks.size(); i++) {
+			const Common::JSONObject &jCallBackHash = jCallbacks[i]->asObject();
+			int id = jCallBackHash["guid"]->asIntegerNumber();
+			float time = ((float)jCallBackHash["time"]->asIntegerNumber()) / 1000.f;
+			Common::String name = jCallBackHash["function"]->asString();
+			Common::Array<HSQOBJECT> args;
+			if (jCallBackHash.contains("param")) {
+				HSQOBJECT arg;
+				toSquirrel(jCallBackHash["param"], arg);
+				sqgetitems(arg, [&args](HSQOBJECT &o) { args.push_back(o); });
+			}
+			g_engine->_callbacks.push_back(new Callback(id, time, name, args));
+		}
+	}
+	setCallbackId(json["nextGuid"]->asIntegerNumber());
+}
+
+void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
+	debug("loadGlobals");
+	HSQUIRRELVM v = g_engine->getVm();
+	HSQOBJECT g;
+	sqgetf("g", g);
+	for (auto it = json.begin(); it != json.end(); it++) {
+		HSQOBJECT tmp;
+		toSquirrel(it->_value, tmp);
+		sq_addref(v, &tmp);
+		sqsetf(g, it->_key, tmp);
+	}
+}
+
+void SaveGameManager::loadActors(const Common::JSONObject &json) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *a = g_engine->_actors[i];
+		if (a->_key.size() > 0) {
+			loadActor(a, json[a->_key]->asObject());
+		}
+	}
+}
+
+void SaveGameManager::loadInventory(const Common::JSONValue *json) {
+	if (json->isObject()) {
+		const Common::JSONObject &jInventory = json->asObject();
+		const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
+		for (int i = 0; i < NUMACTORS; i++) {
+			Object *actor = g_engine->_hud._actorSlots[i].actor;
+			if (actor) {
+				actor->_inventory.clear();
+				const Common::JSONObject &jSlot = jSlots[i]->asObject();
+				if (jSlot.contains("objects")) {
+					if (jSlot["objects"]->isArray()) {
+						const Common::JSONArray &jSlotObjects = jSlot["objects"]->asArray();
+						for (int j = 0; j < jSlotObjects.size(); j++) {
+							const Common::JSONValue *jObj = jSlotObjects[j];
+							Object *obj = object(jObj->asString());
+							if (!obj)
+								warning("inventory obj '%s' not found", jObj->asString().c_str());
+							else
+								actor->pickupObject(obj);
+						}
+					}
+					// TODO: "jiggle"
+				}
+				actor->_inventoryOffset = jSlot["scroll"]->asIntegerNumber();
+			}
+		}
+	}
+}
+
+void SaveGameManager::loadRooms(const Common::JSONObject &json) {
+	for (auto it = json.begin(); it != json.end(); it++) {
+		loadRoom(room(it->_key), it->_value->asObject());
+	}
+}
+
+void SaveGameManager::loadObjects(const Common::JSONObject &json) {
+	for (auto it = json.begin(); it != json.end(); it++) {
+		Object *o = object(it->_key);
+		if (o)
+			loadObject(o, it->_value->asObject());
+		else
+			warning("object '%s' not found", it->_key.c_str());
+	}
+}
+
+} // namespace Twp
diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
new file mode 100644
index 00000000000..da3d29fb122
--- /dev/null
+++ b/engines/twp/savegame.h
@@ -0,0 +1,59 @@
+/* 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 TWP_SAVEGAME_H
+#define TWP_SAVEGAME_H
+
+#include "common/stream.h"
+#include "common/formats/json.h"
+
+namespace Twp {
+
+struct SaveGame {
+	int32_t hashData = 0;
+	int64_t time = 0;
+	int64_t gameTime = 0;
+	bool easyMode = false;
+	Common::JSONValue *jSavegame = nullptr;
+};
+
+class SaveGameManager {
+public:
+	static bool getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame);
+	bool loadGame(const SaveGame &savegame);
+
+private:
+	void loadGameScene(const Common::JSONObject &json);
+	void loadDialog(const Common::JSONObject &json);
+	void loadCallbacks(const Common::JSONObject &json);
+	void loadGlobals(const Common::JSONObject &json);
+	void loadActors(const Common::JSONObject &json);
+	void loadInventory(const Common::JSONValue *json);
+	void loadRooms(const Common::JSONObject &json);
+	void loadObjects(const Common::JSONObject &json);
+
+public:
+	bool _allowSaveGame = true;
+};
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 9286c1025ef..e1a1933c501 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -44,12 +44,24 @@ SQInteger sqpush(HSQUIRRELVM v, int value) {
 	return 1;
 }
 
+template<>
+SQInteger sqpush(HSQUIRRELVM v, long long value) {
+	sq_pushinteger(v, (SQInteger)value);
+	return 1;
+}
+
 template<>
 SQInteger sqpush(HSQUIRRELVM v, float value) {
 	sq_pushfloat(v, value);
 	return 1;
 }
 
+template<>
+SQInteger sqpush(HSQUIRRELVM v, double value) {
+	sq_pushfloat(v, (SQFloat)value);
+	return 1;
+}
+
 template<>
 SQInteger sqpush(HSQUIRRELVM v, bool value) {
 	sq_pushinteger(v, value ? 1 : 0);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 9978a4b30f6..422ae36bba2 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -29,6 +29,8 @@
 
 namespace Twp {
 
+HSQOBJECT sqrootTbl(HSQUIRRELVM v);
+
 template<typename T>
 HSQOBJECT sqtoobj(HSQUIRRELVM v, T value);
 
@@ -76,6 +78,12 @@ void sqgetf(HSQOBJECT o, const Common::String &name, T &value) {
 	sqgetf(v, o, name, value);
 }
 
+template<typename T>
+void sqgetf(const Common::String &name, T &value) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sqgetf(v, sqrootTbl(v), name, value);
+}
+
 void setId(HSQOBJECT &o, int id);
 
 void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
@@ -125,7 +133,6 @@ void sqnewf(HSQOBJECT o, const Common::String &key, T obj) {
 
 bool sqrawexists(HSQOBJECT obj, const Common::String &name);
 void sqsetdelegate(HSQOBJECT obj, HSQOBJECT del);
-HSQOBJECT sqrootTbl(HSQUIRRELVM v);
 
 void sqpushfunc(HSQUIRRELVM v, HSQOBJECT o, const char *name);
 int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 6377d823650..f4b5f6f46e2 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -303,11 +303,12 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 
 	ThreadBase *t = sqthread(id);
 	if (!t) {
-		if(!isSound(id)) {
+		if (!isSound(id)) {
 			warning("thread and sound not found: %d", id);
 			return 0;
 		}
-		return breakwhilecond(v, [id] { return g_engine->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
+		return breakwhilecond(
+			v, [id] { return g_engine->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
 	}
 	return breakwhilecond(
 		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
@@ -381,10 +382,11 @@ static SQInteger breakwhilewalking(HSQUIRRELVM v) {
 // Breaks until specified sound has finished playing.
 // Once sound finishes, the method will continue running.
 static SQInteger breakwhilesound(HSQUIRRELVM v) {
-  int soundId = 0;
-  if (SQ_FAILED(sqget(v, 2, soundId)))
+	int soundId = 0;
+	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-  return breakwhilecond(v, [soundId](){ return g_engine->_audio.playing(soundId); }, "breakwhilesound(%d)", soundId);
+	return breakwhilecond(
+		v, [soundId]() { return g_engine->_audio.playing(soundId); }, "breakwhilesound(%d)", soundId);
 }
 
 static SQInteger cutscene(HSQUIRRELVM v) {
@@ -437,7 +439,39 @@ static SQInteger dumpvar(HSQUIRRELVM v) {
 }
 
 static SQInteger exCommand(HSQUIRRELVM v) {
-	warning("TODO: exCommand: not implemented");
+	int cmd;
+	if (SQ_FAILED(sqget(v, 2, cmd)))
+		return sq_throwerror(v, "Failed to get command");
+	switch (cmd) {
+	case EX_ALLOW_SAVEGAMES: {
+		int enabled;
+		if (SQ_FAILED(sqget(v, 3, enabled)))
+			return sq_throwerror(v, "Failed to get enabled");
+		g_engine->_saveGameManager._allowSaveGame = enabled != 0;
+	} break;
+	case EX_POP_CHARACTER_SELECTION:
+		// seems not to be used
+		warning("TODO: exCommand EX_POP_CHARACTER_SELECTION: not implemented");
+		break;
+	case EX_CAMERA_TRACKING:
+		warning("TODO: exCommand EX_CAMERA_TRACKING: not implemented");
+		break;
+	case EX_RESTART:
+		warning("TODO: exCommand EX_RESTART: not implemented");
+		break;
+	case EX_IDLE_TIME:
+		warning("TODO: exCommand EX_IDLE_TIME: not implemented");
+		break;
+	case EX_DISABLE_SAVESYSTEM:
+		warning("TODO: exCommand EX_DISABLE_SAVESYSTEM: not implemented");
+		break;
+	case EX_OPTIONS_MUSIC:
+		warning("TODO: exCommand EX_OPTIONS_MUSIC: not implemented");
+		break;
+	default:
+		warning("exCommand(%dd) not implemented", cmd);
+		break;
+	}
 	return 0;
 }
 
diff --git a/engines/twp/time.cpp b/engines/twp/time.cpp
new file mode 100644
index 00000000000..c19f12c7105
--- /dev/null
+++ b/engines/twp/time.cpp
@@ -0,0 +1,48 @@
+/* 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/>.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#include <time.h>
+#include "twp/time.h"
+
+namespace Twp {
+
+Common::String formatTime(int64_t t, const char *format) {
+	time_t time = (time_t)t;
+	struct tm *tm = localtime(&time);
+	char buf[64];
+	strftime(buf, 64, format, tm);
+	return Common::String(buf);
+}
+
+DateTime toDateTime(int64_t t) {
+	time_t time = (time_t)t;
+	struct tm *tm = localtime(&time);
+	DateTime dateTime;
+	dateTime.year = 1900 + tm->tm_year;
+	dateTime.month = 1 + tm->tm_mon;
+	dateTime.day = tm->tm_mday;
+	dateTime.hour = tm->tm_hour;
+	dateTime.min = tm->tm_min;
+	return dateTime;
+}
+
+} // namespace Twp
diff --git a/engines/twp/time.h b/engines/twp/time.h
new file mode 100644
index 00000000000..9c13b279296
--- /dev/null
+++ b/engines/twp/time.h
@@ -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/>.
+ *
+ */
+
+#ifndef TWP_TIME_H
+#define TWP_TIME_H
+
+#include "common/str.h"
+
+namespace Twp {
+
+struct DateTime {
+	int year, month, day;
+	int hour, min;
+};
+
+Common::String formatTime(int64_t time, const char *format);
+DateTime toDateTime(int64_t time);
+
+} // namespace Twp
+
+#endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7367095b6b6..c9d90e4c650 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -26,6 +26,7 @@
 #include "common/file.h"
 #include "common/config-manager.h"
 #include "common/events.h"
+#include "common/savefile.h"
 #include "engines/util.h"
 #include "graphics/palette.h"
 #include "graphics/opengl/system_headers.h"
@@ -153,7 +154,7 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	// TODO
 	// if (a?._verb_tid) stopthread(actor._verb_tid)
 
-	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory()?"YES":"NO", noun1->isTouchable()?"YES":"NO", verbNoWalkTo(verbId, noun1)?"YES":"NO");
+	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory() ? "YES" : "NO", noun1->isTouchable() ? "YES" : "NO", verbNoWalkTo(verbId, noun1) ? "YES" : "NO");
 
 	// test if object became untouchable
 	if (!noun1->inInventory() && !noun1->isTouchable())
@@ -644,19 +645,19 @@ Common::Error TwpEngine::run() {
 	entry.open(_pack, "ThimbleweedText_en.tsv");
 	_textDb.parseTsv(entry);
 
-	// If a savegame was selected from the launcher, load it
-	int saveSlot = ConfMan.getInt("save_slot");
-	if (saveSlot != -1)
-		(void)loadGameState(saveSlot);
-
 	HSQUIRRELVM v = _vm.get();
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
-	// const SQChar *code = "cameraInRoom(StartScreen)";
-	const SQChar *code = "start(1)";
-
-	_vm.exec(code);
+	// If a savegame was selected from the launcher, load it
+	int saveSlot = ConfMan.getInt("save_slot");
+	if (saveSlot != -1)
+		(void)loadGameState(saveSlot);
+	else {
+		// const SQChar *code = "cameraInRoom(StartScreen)";
+		const SQChar *code = "start(1)";
+		_vm.exec(code);
+	}
 
 	static int speed = 1;
 
@@ -718,14 +719,27 @@ Common::Error TwpEngine::run() {
 	return Common::kNoError;
 }
 
-Common::Error TwpEngine::syncGame(Common::Serializer &s) {
-	// The Serializer has methods isLoading() and isSaving()
-	// if you need to specific steps; for example setting
-	// an array size after reading it's length, whereas
-	// for saving it would write the existing array's length
-	int dummy = 0;
-	s.syncAsUint32LE(dummy);
+Common::Error TwpEngine::loadGameState(int slot) {
+	Common::InSaveFile *file = getSaveFileManager()->openRawFile(getSaveStateName(slot));
+	if (file) {
+		return loadGameStream(file);
+	}
+	return Common::kPathDoesNotExist;
+}
+
+Common::Error TwpEngine::loadGameStream(Common::SeekableReadStream *stream) {
+	SaveGame savegame;
+	if (_saveGameManager.getSaveGame(stream, savegame)) {
+		_saveGameManager.loadGame(savegame);
+	}
+	return Common::kNoError;
+}
+
+Common::String TwpEngine::getSaveStateName(int slot) const {
+	return Common::String::format("twp%02d.save", slot);
+}
 
+Common::Error TwpEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
 	return Common::kNoError;
 }
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f87713df9c1..1814f952555 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -47,6 +47,7 @@
 #include "twp/walkboxnode.h"
 #include "twp/audio.h"
 #include "twp/actorswitcher.h"
+#include "twp/savegame.h"
 
 #define SCREEN_MARGIN 100.f
 #define SCREEN_WIDTH 1280
@@ -109,20 +110,11 @@ public:
 		return true;
 	}
 
-	/**
-	 * Uses a serializer to allow implementing savegame
-	 * loading and saving using a single method
-	 */
-	Common::Error syncGame(Common::Serializer &s);
+	virtual Common::String getSaveStateName(int slot) const override;
+	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
 
-	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
-		Common::Serializer s(nullptr, stream);
-		return syncGame(s);
-	}
-	Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
-		Common::Serializer s(stream, nullptr);
-		return syncGame(s);
-	}
+	Common::Error loadGameState(int slot) override;
+	Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
 
 	Math::Vector2d winToScreen(Math::Vector2d pos);
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
@@ -145,8 +137,8 @@ public:
 
 	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
 	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
-	bool callVerb(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
-	bool execSentence(Object* actor, VerbId verbId, Object* noun1, Object* noun2 = nullptr);
+	bool callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2 = nullptr);
+	bool execSentence(Object *actor, VerbId verbId, Object *noun1, Object *noun2 = nullptr);
 
 	float getRandom() const;
 	float getRandom(float min, float max) const;
@@ -169,7 +161,7 @@ private:
 	void updateTriggers();
 	void callTrigger(Object *obj, HSQOBJECT trigger);
 	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
-	ActorSwitcherSlot actorSwitcherSlot(ActorSlot* slot);
+	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -218,6 +210,7 @@ public:
 	Inventory _uiInv;
 	ActorSwitcher _actorSwitcher;
 	AudioSystem _audio;
+	SaveGameManager _saveGameManager;
 
 private:
 	Gfx _gfx;


Commit: 45c6656ffc9b4cff9518287a2a998f421d20876b
    https://github.com/scummvm/scummvm/commit/45c6656ffc9b4cff9518287a2a998f421d20876b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash after cutscene where the parent thread is dead

Changed paths:
    engines/twp/thread.cpp


diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index ba057170999..f02e000439f 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -181,7 +181,10 @@ void Cutscene::stop() {
 			thread->unpause();
 	}
 	sqcall("onCutsceneEnded");
-	sq_wakeupvm(_v, SQFalse, SQFalse, SQTrue, SQFalse);
+
+	ThreadBase *t = sqthread(_v);
+	if (t)
+		t->resume();
 	sq_suspendvm(getThread());
 }
 


Commit: e9f648497c753622b3e21b802e86593f61bdd591
    https://github.com/scummvm/scummvm/commit/e9f648497c753622b3e21b802e86593f61bdd591
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix van not visible in the Highway

Changed paths:
    engines/twp/motor.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 97881da0cb5..0809990cec7 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -110,8 +110,7 @@ Shake::Shake(Node *node, float amount)
 void Shake::update(float elapsed) {
 	_shakeTime += 40.f * elapsed;
 	_elapsed += elapsed;
-	// TODO: check if it's necessary to create a _shakeOffset in a node
-	_node->setOffset(Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime)));
+	_node->setShakeOffset(Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime)));
 }
 
 OverlayTo::OverlayTo(float duration, Room *room, Color to)
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 82ec911c8ec..523c1e6ab82 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -202,7 +202,7 @@ Math::Matrix4 Node::getTrsf(Math::Matrix4 parentTrsf) {
 }
 
 Math::Matrix4 Node::getLocalTrsf() {
-	Math::Vector2d p = _pos + _offset;
+	Math::Vector2d p = _pos + _offset + _shakeOffset;
 	Math::Matrix4 m1;
 	m1.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 	Math::Matrix3 mRot;
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index c3c60eb5606..3c1b40e6494 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -71,6 +71,9 @@ public:
 	void setOffset(const Math::Vector2d &offset) { _offset = offset; }
 	Math::Vector2d getOffset() const { return _offset; }
 
+	void setShakeOffset(const Math::Vector2d &offset) { _shakeOffset = offset; }
+	Math::Vector2d getShakeOffset() const { return _shakeOffset; }
+
 	void setRenderOffset(const Math::Vector2d &offset) { _renderOffset = offset; }
 	Math::Vector2d getRenderOffset() const { return _renderOffset; }
 
@@ -122,7 +125,7 @@ protected:
 	int _zOrder = 0;
 	Node *_parent = nullptr;
 	Common::Array<Node *> _children;
-	Math::Vector2d _offset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
+	Math::Vector2d _offset, _shakeOffset, _renderOffset, _anchor, _anchorNorm, _scale, _size;
 	Color _color, _computedColor;
 	bool _visible = true;
 	float _rotation = 0.f;


Commit: 2dbcc17db4f5ccc34ffc3be07c3d29770fdbe269
    https://github.com/scummvm/scummvm/commit/2dbcc17db4f5ccc34ffc3be07c3d29770fdbe269
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add exec command in console

Changed paths:
    engines/twp/console.cpp
    engines/twp/console.h


diff --git a/engines/twp/console.cpp b/engines/twp/console.cpp
index 88d8044583c..b7ec67f8e74 100644
--- a/engines/twp/console.cpp
+++ b/engines/twp/console.cpp
@@ -20,18 +20,28 @@
  */
 
 #include "twp/console.h"
+#include "twp/twp.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
 Console::Console() : GUI::Debugger() {
-	registerCmd("test",   WRAP_METHOD(Console, Cmd_test));
+	registerCmd("!",   WRAP_METHOD(Console, Cmd_exec));
 }
 
 Console::~Console() {
 }
 
-bool Console::Cmd_test(int argc, const char **argv) {
-	debugPrintf("Test\n");
+bool Console::Cmd_exec(int argc, const char **argv) {
+	Common::String s;
+	if (argc > 0) {
+		s += argv[0];
+		for (int i = 1; i < argc; i++) {
+			s += ' ';
+			s += argv[i];
+		}
+	}
+	sqexec(g_engine->getVm(), s.c_str(), "console");
 	return true;
 }
 
diff --git a/engines/twp/console.h b/engines/twp/console.h
index 00bcea2c181..9d28696f0a5 100644
--- a/engines/twp/console.h
+++ b/engines/twp/console.h
@@ -29,7 +29,7 @@ namespace Twp {
 
 class Console : public GUI::Debugger {
 private:
-	bool Cmd_test(int argc, const char **argv);
+	bool Cmd_exec(int argc, const char **argv);
 public:
 	Console();
 	~Console() override;


Commit: 9404fe941d3215c5f1c6d01d8645c9bbd59b4b6d
    https://github.com/scummvm/scummvm/commit/9404fe941d3215c5f1c6d01d8645c9bbd59b4b6d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix scenegraph issues

Changed paths:
    engines/twp/object.cpp
    engines/twp/room.cpp
    engines/twp/scenegraph.cpp
    engines/twp/util.h


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 0184f572103..895a6fba40c 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -367,7 +367,9 @@ void Object::setRoom(Room *room) {
 			debug("Add %s in room %s", _key.c_str(), room->_name.c_str());
 			Layer *layer = room->layer(0);
 			if (layer) {
-				layer->_objects.push_back(this);
+				int index = find(layer->_objects, this);
+				if (index == -1)
+					layer->_objects.push_back(this);
 				layer->_node->addChild(_node);
 			}
 		}
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 04ddad35c26..3bbbfd202f4 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -322,14 +322,14 @@ void Room::load(Common::SeekableReadStream &s) {
 		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
 			const Common::JSONObject &jObject = (*it)->asObject();
 			Object *obj = new Object();
-			obj->_state = -1;
+			Twp::setId(obj->_table, newObjId());
+			obj->_key = jObject["name"]->asString();
 			Node *objNode = new Node(obj->_key);
 			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_node = objNode;
 			obj->_nodeAnim = new Anim(obj);
 			obj->_node->addChild(obj->_nodeAnim);
-			obj->_key = jObject["name"]->asString();
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
 				obj->_useDir = parseUseDir(jObject["usedir"]->asString());
@@ -339,7 +339,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			obj->_hotspot = parseRect(jObject["hotspot"]->asString());
 			obj->_objType = toObjectType(jObject);
 			if (jObject.contains("parent"))
-				jObject["parent"]->asString();
+				obj->_parent = jObject["parent"]->asString();
 			obj->_room = this;
 			if (jObject.contains("animations")) {
 				parseObjectAnimations(jObject["animations"]->asArray(), obj->_anims);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 523c1e6ab82..815c665e653 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -57,16 +57,10 @@ Node::Node(const Common::String &name, Math::Vector2d scale, Color color)
 Node::~Node() {}
 
 void Node::addChild(Node *child) {
+	if(child->_parent == this) return;
 	if (child->_parent) {
 		child->_pos -= getAbsPos();
-		for (auto it = _children.begin(); it != _children.end();) {
-			Node *node = *it;
-			if (node == child) {
-				it = child->_parent->_children.erase(it);
-				break;
-			}
-			it++;
-		}
+		child->remove();
 	}
 	_children.push_back(child);
 	child->_parent = this;
@@ -91,14 +85,19 @@ int Node::find(Node *other) {
 	return -1;
 }
 
-void Node::removeChild(Node *node) {
-	int i = find(node);
-	if (i != -1) {
-		_children.remove_at(i);
-	}
+void Node::removeChild(Node *child) {
+	int i = find(child);
+	_children.remove_at(i);
+	child->_parent = nullptr;
 }
 
 void Node::clear() {
+	if (_children.size() > 0) {
+		Common::Array<Node *> children(_children);
+		for (int i = 0; i < children.size(); i++) {
+			children[i]->remove();
+		}
+	}
 	_children.clear();
 }
 
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 17b9609a4a3..1d5b99f8031 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -60,14 +60,12 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 // array util
 template<typename T>
 int find(const Common::Array<T>& array, const T& o) {
-	int index = -1;
 	for (int i = 0; i < array.size(); i++) {
 		if (array[i] == o) {
-			index = i;
-			break;
+			return i;
 		}
 	}
-	return index;
+	return -1;
 }
 
 // string util


Commit: 10b1e8c8121fe32e6969bc2d790e53326ef32762
    https://github.com/scummvm/scummvm/commit/10b1e8c8121fe32e6969bc2d790e53326ef32762
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix thread crash

Changed paths:
    engines/twp/thread.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index f02e000439f..ab625265d83 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -167,7 +167,7 @@ void Cutscene::start() {
 
 void Cutscene::stop() {
 	_state = csQuit;
-	debug("End cutscene");
+	debug("End cutscene: %d", getId());
 	g_engine->_inputState.setState(_inputState);
 	g_engine->_inputState.setShowCursor(_showCursor);
 	if (_showCursor)
@@ -183,8 +183,8 @@ void Cutscene::stop() {
 	sqcall("onCutsceneEnded");
 
 	ThreadBase *t = sqthread(_v);
-	if (t)
-		t->resume();
+	if (t && t->getId())
+		t->unpause();
 	sq_suspendvm(getThread());
 }
 
@@ -225,7 +225,6 @@ bool Cutscene::update(float elapsed) {
 		checkEndCutsceneOverride();
 		return false;
 	case csEnd:
-		debug("endCutscene");
 		stop();
 		return false;
 	case csQuit:
@@ -253,7 +252,6 @@ void Cutscene::doCutsceneOverride() {
 void Cutscene::checkEndCutscene() {
 	if (isStopped()) {
 		_state = csEnd;
-		debug("end cutscene: %d", getId());
 	}
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c9d90e4c650..9bd6d4feda6 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -459,14 +459,13 @@ void TwpEngine::update(float elapsed) {
 	}
 
 	// remove threads that are terminated
-	for (auto it = _threads.begin(); it != _threads.end();) {
+	for (auto it = threadsToRemove.begin(); it != threadsToRemove.end(); it++) {
 		ThreadBase *thread = *it;
-		if (find(threadsToRemove, thread) != -1) {
-			it = _threads.erase(it);
+		int i = find(_threads, *it);
+		if (i != -1) {
+			_threads.remove_at(i);
 			delete thread;
-			continue;
 		}
-		it++;
 	}
 
 	// update callbacks


Commit: 29e84e18c69b963232469537d4d1877165ea2ad8
    https://github.com/scummvm/scummvm/commit/29e84e18c69b963232469537d4d1877165ea2ad8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix loadObject in savegame

Changed paths:
    engines/twp/savegame.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 8c4fc9487c1..43d9179ed12 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -272,7 +272,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 	if (obj->_node)
 		obj->setState(state, true);
 	else
-		warning("obj '{obj.key}' has no node !");
+		warning("obj '%s' has no node !", obj->_key.c_str());
 	bool touchable = true;
 	if (json.contains("_touchable"))
 		touchable = json["_touchable"]->asIntegerNumber() == 1;
@@ -283,7 +283,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 	obj->_node->setVisible(!hidden);
 
 	for (auto it = json.begin(); it != json.end(); it++) {
-		if ((it->_key == "_state") || (it->_key == "_state") || (it->_key == "_state")) {
+		if ((it->_key == "_state") || (it->_key == "_touchable") || (it->_key == "_hidden")) {
 			// discard
 		} else if (it->_key == "_pos") {
 			obj->_node->setPos(parseVec2(it->_value->asString()));
@@ -309,7 +309,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 			else
 				sqnewf(obj->_table, it->_key, tmp);
 		} else {
-			warning("load object: key '{%s}' is unknown", it->_key.c_str());
+			warning("load object (%s): key '%s' is unknown", obj->_key.c_str(), it->_key.c_str());
 		}
 	}
 


Commit: a650a66013ce76d5d86e43b2637afc9f9799e208
    https://github.com/scummvm/scummvm/commit/a650a66013ce76d5d86e43b2637afc9f9799e208
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix inventory offset

Changed paths:
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 815c665e653..86a11933bd4 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -505,6 +505,8 @@ Inventory::Inventory() : Node("Inventory") {
 		float y = MARGINBOTTOM + BACKHEIGHT + BACKOFFSET - ((i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET));
 		_itemRects[i] = Common::Rect(x, y, x + BACKWIDTH, y + BACKHEIGHT);
 	}
+	_arrowUpRect = Common::Rect(SCREEN_WIDTH / 2.f, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET, SCREEN_WIDTH / 2.f + ARROWWIDTH, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET + ARROWHEIGHT);
+	_arrowDnRect = Common::Rect(SCREEN_WIDTH / 2.f, MARGINBOTTOM, SCREEN_WIDTH / 2.f + ARROWWIDTH, MARGINBOTTOM + ARROWHEIGHT);
 }
 
 Math::Vector2d Inventory::getPos(Object *inv) const {
@@ -596,9 +598,6 @@ void Inventory::drawCore(Math::Matrix4 trsf) {
 }
 
 void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
-	static Common::Rect gArrowUpRect(SCREEN_WIDTH / 2.f, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET, SCREEN_WIDTH / 2.f + ARROWWIDTH, ARROWHEIGHT + MARGINBOTTOM + BACKOFFSET + ARROWHEIGHT);
-	static Common::Rect gArrowDnRect(SCREEN_WIDTH / 2.f, MARGINBOTTOM, SCREEN_WIDTH / 2.f + ARROWWIDTH, MARGINBOTTOM + ARROWHEIGHT);
-
 	// udate colors
 	_actor = actor;
 	_backColor = backColor;
@@ -610,13 +609,13 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 
 		// update mouse click
 		bool down = g_engine->_cursor.leftDown;
-		if (_down && down) {
+		if (!_down && down) {
 			_down = true;
-			if (gArrowUpRect.contains(scrPos.getX(), scrPos.getY())) {
+			if (_arrowUpRect.contains(scrPos.getX(), scrPos.getY())) {
 				_actor->_inventoryOffset -= 1;
 				if (_actor->_inventoryOffset < 0)
 					_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
-			} else if (gArrowDnRect.contains(scrPos.getX(), scrPos.getY())) {
+			} else if (_arrowDnRect.contains(scrPos.getX(), scrPos.getY())) {
 				_actor->_inventoryOffset++;
 				_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
 			}
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 3c1b40e6494..874203c69e8 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -307,6 +307,8 @@ private:
     bool _down = false;
     Object* _obj = nullptr;
 	Common::Rect _itemRects[NUMOBJECTS];
+	Common::Rect _arrowUpRect;
+	Common::Rect _arrowDnRect;
 };
 
 class SentenceNode: public Node {


Commit: 75e2632053dfefd954812e63cafacf22463b7cf9
    https://github.com/scummvm/scummvm/commit/75e2632053dfefd954812e63cafacf22463b7cf9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix camera y position

Changed paths:
    engines/twp/camera.cpp


diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index d5131fdc2f1..737d45d883a 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -106,7 +106,7 @@ void Camera::update(Room *room, Object *follow, float elapsed) {
 		else if (sameActor && (pos.getY() < (cameraPos.getY() - margin.getY())))
 			y = pos.getY() + margin.getY();
 		else
-			y = cameraPos.getY() + d.getY() > 0 ? MIN(delta.getY(), d.getY()) : MAX(delta.getY(), d.getY());
+			y = cameraPos.getY() + (d.getY() > 0 ? MIN(delta.getY(), d.getY()) : MAX(delta.getY(), d.getY()));
 		setAtCore(Math::Vector2d(x, y));
 		if (!sameActor && (fabs(pos.getX() - x) < 1.f) && (fabs(pos.getY() - y) < 1.f))
 			_follow = follow;


Commit: a2a2397d8ac44e6271950cd8110df1541d8963d0
    https://github.com/scummvm/scummvm/commit/a2a2397d8ac44e6271950cd8110df1541d8963d0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add scaling triggers

Changed paths:
    engines/twp/room.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/room.h b/engines/twp/room.h
index c350a41a579..3ab6666732b 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -93,6 +93,13 @@ struct Lights {
 	Color _ambientLight; // Ambient light color
 };
 
+struct ScalingTrigger {
+	ScalingTrigger(Object * obj, Scaling* scaling);
+
+	Object * _obj = nullptr;
+	Scaling* _scaling = nullptr;
+};
+
 class PathFinder;
 class Scene;
 class Room {
@@ -136,6 +143,7 @@ public:
 	bool _entering = false;            // Indicates whether or not an actor is entering this room
 	Lights _lights;                    // Lights of the room
 	Common::Array<Object *> _triggers; // Triggers currently enabled in the room
+	Common::Array<ScalingTrigger> _scalingTriggers; // Scaling Triggers of the room
 	bool _pseudo = false;
 	Common::Array<Object *> _objects;
 	Scene *_scene = nullptr;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 9bd6d4feda6..ce8ab2eb0ad 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -943,6 +943,13 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 		Layer *layer = room->_layers[i];
 		for (int j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
+			// add all scaling triggers
+			if (obj->_objType == ObjectType::otTrigger) {
+				Scaling *scaling = getScaling(obj->_key);
+				if (scaling) {
+					_room->_scalingTriggers.push_back(ScalingTrigger(obj, scaling));
+				}
+			}
 			if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
 		}
@@ -981,6 +988,7 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 	_mixer->stopAll();
 	if (_room) {
 		_room->_triggers.clear();
+		_room->_scalingTriggers.clear();
 
 		actorExit();
 
@@ -1290,6 +1298,7 @@ void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
 
 void TwpEngine::updateTriggers() {
 	if (_actor) {
+		// check if actor enters or leaves an object trigger
 		for (int i = 0; i < _room->_triggers.size(); i++) {
 			Object *trigger = _room->_triggers[i];
 			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
@@ -1302,6 +1311,24 @@ void TwpEngine::updateTriggers() {
 				callTrigger(trigger, trigger->_leave);
 			}
 		}
+
+		// check if actor enters or leaves a scaling trigger
+		for (int i = 0; i < _room->_scalingTriggers.size(); i++) {
+			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
+			if (trigger->_obj->_triggerActive && !trigger->_obj->contains(_actor->_node->getAbsPos())) {
+				debug("leave scaling trigger %s", trigger->_obj->_key.c_str());
+				trigger->_obj->_triggerActive = false;
+				_room->_scaling = _room->_scalings[0];
+			}
+		}
+		for (int i = 0; i < _room->_scalingTriggers.size(); i++) {
+			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
+			if (!trigger->_obj->_triggerActive && trigger->_obj->contains(_actor->_node->getAbsPos())) {
+				debug("enter scaling trigger %s", trigger->_obj->_key.c_str());
+				trigger->_obj->_triggerActive = true;
+				_room->_scaling = *trigger->_scaling;
+			}
+		}
 	}
 }
 
@@ -1323,4 +1350,16 @@ float TwpEngine::getRandom(float min, float max) const {
 	return min + scale * (max - min);
 }
 
+Scaling *TwpEngine::getScaling(const Common::String &name) {
+	for (int i = 0; i < _room->_scalings.size(); i++) {
+		Scaling *scaling = &_room->_scalings[i];
+		if (scaling->trigger == name) {
+			return scaling;
+		}
+	}
+	return nullptr;
+}
+
+ScalingTrigger::ScalingTrigger(Object *obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
+
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 1814f952555..bd67cd65390 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -162,6 +162,7 @@ private:
 	void callTrigger(Object *obj, HSQOBJECT trigger);
 	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
 	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
+	Scaling* getScaling(const Common::String& name);
 
 public:
 	Graphics::Screen *_screen = nullptr;


Commit: 17d924dee97b4cb8e315c7ff614b437de8022a16
    https://github.com/scummvm/scummvm/commit/17d924dee97b4cb8e315c7ff614b437de8022a16
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add skip cutscene

Changed paths:
  A engines/twp/actions.h
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actions.h b/engines/twp/actions.h
new file mode 100644
index 00000000000..81a29947a35
--- /dev/null
+++ b/engines/twp/actions.h
@@ -0,0 +1,33 @@
+/* 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 TWP_ACTIONS_H
+#define TWP_ACTIONS_H
+
+namespace Twp {
+
+enum TwpAction {
+	kSkipCutscene,
+};
+
+}
+
+#endif
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index f28f5bb97c1..b8419b4a57d 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -21,6 +21,8 @@
 
 #include "common/translation.h"
 #include "common/savefile.h"
+#include "backends/keymapper/keymapper.h"
+#include "backends/keymapper/action.h"
 #include "graphics/scaler.h"
 #include "image/png.h"
 #include "twp/metaengine.h"
@@ -28,6 +30,7 @@
 #include "twp/twp.h"
 #include "twp/savegame.h"
 #include "twp/time.h"
+#include "twp/actions.h"
 
 #define MAX_SAVES 99
 
@@ -133,6 +136,19 @@ SaveStateList TwpMetaEngine::listSaves(const char *target) const {
 	return saveList;
 }
 
+Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) const {
+	Common::Keymap *engineKeyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, target, "Thimbleweed Park keymap");
+
+	Common::Action *act;
+
+	act = new Common::Action("SKIPCUTSCENE", _("Skip cutscene"));
+	act->setCustomEngineActionEvent(Twp::kSkipCutscene);
+	act->addDefaultInputMapping("ESCAPE");
+	engineKeyMap->addAction(act);
+
+	return Common::Keymap::arrayOf(engineKeyMap);
+}
+
 #if PLUGIN_ENABLED_DYNAMIC(TWP)
 REGISTER_PLUGIN_DYNAMIC(TWP, PLUGIN_TYPE_ENGINE, TwpMetaEngine);
 #else
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index bf6541178a5..7cb05ffd9e6 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -43,6 +43,8 @@ public:
 	int getMaximumSaveSlot() const override;
 
 	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
+
+	virtual Common::Array<Common::Keymap *> initKeymaps(const char *target) const override;
 };
 
 #endif // TWP_METAENGINE_H
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 86a11933bd4..faca0b229c4 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -57,7 +57,8 @@ Node::Node(const Common::String &name, Math::Vector2d scale, Color color)
 Node::~Node() {}
 
 void Node::addChild(Node *child) {
-	if(child->_parent == this) return;
+	if (child->_parent == this)
+		return;
 	if (child->_parent) {
 		child->_pos -= getAbsPos();
 		child->remove();
@@ -668,4 +669,56 @@ void SentenceNode::drawCore(Math::Matrix4 trsf) {
 	text.draw(g_engine->getGfx(), t);
 }
 
+SpriteNode::SpriteNode() : Node("Sprite") {}
+SpriteNode::~SpriteNode() {}
+
+void SpriteNode::setSprite(const Common::String &sheet, const Common::String &frame) {
+	_sheet = sheet;
+	_frame = frame;
+}
+
+void SpriteNode::drawCore(Math::Matrix4 trsf) {
+	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+	SpriteSheetFrame *frame = &sheet->frameTable[_frame];
+
+	Common::Rect rect = frame->frame;
+	setSize(Math::Vector2d(frame->frame.width(), frame->frame.height()));
+	float x = frame->sourceSize.getX() / 2.f - frame->spriteSourceSize.left;
+	float y = (frame->sourceSize.getY() + 1.f) / 2.f - frame->spriteSourceSize.height() - frame->spriteSourceSize.top;
+	Math::Vector2d anchor((int)(x), (int)(y));
+	setAnchor(anchor);
+
+	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+	g_engine->getGfx().drawSprite(rect, *texture, this->getComputedColor(), trsf);
+}
+
+NoOverrideNode::NoOverrideNode() : Node("NoOverride") {
+	_zOrder = -1000;
+	_elapsed = 42.f;
+
+	_icon.setSprite("GameSheet", "icon_no");
+	_icon.setScale(Math::Vector2d(2.f, 2.f));
+	_icon.setPos(Math::Vector2d(32.f, SCREEN_HEIGHT - 32.f));
+	addChild(&_icon);
+}
+
+NoOverrideNode::~NoOverrideNode() {
+}
+
+void NoOverrideNode::reset() {
+	_elapsed = 0.f;
+	setVisible(true);
+}
+
+bool NoOverrideNode::update(float elapsed) {
+	if (_elapsed > 2.f) {
+		setVisible(false);
+		return false;
+	}
+	_elapsed += elapsed;
+	setAlpha(clamp((2.f - _elapsed) / 2.f, 0.f, 1.f));
+	debug("no override: %.2f, %.2f", _elapsed, getAlpha());
+	return true;
+}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 874203c69e8..c572c0369ce 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -325,6 +325,34 @@ private:
 	Common::String _text;
 };
 
+class SpriteNode: public Node {
+public:
+	SpriteNode();
+	virtual ~SpriteNode();
+
+	void setSprite(const Common::String& sheet, const Common::String& frame);
+
+private:
+	void drawCore(Math::Matrix4 trsf) override final;
+
+private:
+	Common::String _sheet;
+	Common::String _frame;
+};
+
+class NoOverrideNode: public Node {
+public:
+	NoOverrideNode();
+	virtual ~NoOverrideNode();
+
+	void reset();
+	bool update(float elapsed);
+
+private:
+	SpriteNode _icon;
+	float _elapsed = 0.f;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index ce8ab2eb0ad..2541ee1646e 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -47,6 +47,7 @@
 #include "twp/squirrel/squirrel.h"
 #include "twp/yack.h"
 #include "twp/enginedialogtarget.h"
+#include "twp/actions.h"
 
 namespace Twp {
 
@@ -67,6 +68,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_screenScene.addChild(&_dialog);
 	_screenScene.addChild(&_uiInv);
 	_screenScene.addChild(&_actorSwitcher);
+	_screenScene.addChild(&_noOverride);
 }
 
 TwpEngine::~TwpEngine() {
@@ -214,8 +216,8 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 				}
 			}
-			// TODO: Just clicking on the ground
-			//     cancelSentence(self.actor)
+			// Just clicking on the ground
+			cancelSentence(_actor);
 		} else if (_cursor.isRightDown()) {
 			// button right: execute default verb
 			if (obj) {
@@ -324,8 +326,8 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 				result.push_back(actorSwitcherSlot(slot));
 		}
 
+		// TODO: showOptions
 		// add gear icon
-		// TODO: result.push_back(ActorSwitcherSlot("icon_gear", Black, Gray, showOptions));
 		result.push_back(ActorSwitcherSlot("icon_gear", Color(0.f, 0.f, 0.f), Color(0.8f, 0.8f, 0.8f), selectSlotActor));
 	}
 	return result;
@@ -336,6 +338,7 @@ void TwpEngine::update(float elapsed) {
 	_frameCounter++;
 
 	_audio.update(elapsed);
+	_noOverride.update(elapsed);
 
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
@@ -667,6 +670,14 @@ Common::Error TwpEngine::run() {
 		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
 			switch (e.type) {
+			case Common::EVENT_CUSTOM_ENGINE_ACTION_START: {
+				switch ((TwpAction)e.customType) {
+				case TwpAction::kSkipCutscene:
+					skipCutscene();
+					break;
+				}
+				break;
+			} break;
 			case Common::EVENT_KEYDOWN:
 				switch (e.kbd.keycode) {
 				case Common::KEYCODE_LEFT:
@@ -1350,6 +1361,16 @@ float TwpEngine::getRandom(float min, float max) const {
 	return min + scale * (max - min);
 }
 
+void TwpEngine::skipCutscene() {
+	if (!_cutscene)
+		return;
+	if (_cutscene->hasOverride()) {
+		_cutscene->cutsceneOverride();
+		return;
+	}
+	_noOverride.reset();
+}
+
 Scaling *TwpEngine::getScaling(const Common::String &name) {
 	for (int i = 0; i < _room->_scalings.size(); i++) {
 		Scaling *scaling = &_room->_scalings[i];
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index bd67cd65390..f928f1e4c29 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -163,6 +163,7 @@ private:
 	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
 	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
 	Scaling* getScaling(const Common::String& name);
+	void skipCutscene();
 
 public:
 	Graphics::Screen *_screen = nullptr;
@@ -188,6 +189,7 @@ public:
 	Cutscene *_cutscene = nullptr;
 	Scene _scene;
 	Scene _screenScene;
+	NoOverrideNode _noOverride;
 	InputState _inputState;
 	Camera _camera;
 	TextDb _textDb;


Commit: 1b9732110a99f5b0dcb908a5f4e99dada7d257ca
    https://github.com/scummvm/scummvm/commit/1b9732110a99f5b0dcb908a5f4e99dada7d257ca
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add options dialog

Changed paths:
  A engines/twp/dialogs.cpp
  A engines/twp/dialogs.h
    engines/twp/detection_tables.h
    engines/twp/hud.cpp
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/module.mk
    engines/twp/motor.cpp
    engines/twp/resmanager.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index 8f3e3cdf041..f0e603c56a7 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -22,7 +22,7 @@
 namespace Twp {
 
 const PlainGameDescriptor twpGames[] = {
-	{ "twp", "Twp" },
+	{ "twp", "Thimbleweed Park" },
 	{ 0, 0 }
 };
 
diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
new file mode 100644
index 00000000000..702d0d88e4d
--- /dev/null
+++ b/engines/twp/dialogs.cpp
@@ -0,0 +1,104 @@
+/* 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 "common/translation.h"
+
+#include "gui/gui-manager.h"
+#include "gui/widget.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/popup.h"
+#include "gui/ThemeEval.h"
+
+#include "twp/dialogs.h"
+
+namespace Twp {
+
+TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) : OptionsContainerWidget(boss, name, "TwpGameOptionsDialog", false, domain) {
+	GUI::StaticTextWidget *text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoLabel", _("Video:"));
+	text->setAlign(Graphics::TextAlign::kTextAlignStart);
+
+	_enableToiletPaperOverGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck1",
+																_("Toilet paper over"),
+																_("The toilet paper in some toilets will be shown “over”.\nIt’s a joke option that has no effects on the gameplay.."));
+	_enableAnnoyingInJokesGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck2",
+																_("Annoying in-jokes"),
+																_("The game will include in-jokes and references to past adventure games, in the form of both dialogues and objects.\nThere is a game achievement that can be obtained only if the in-jokes option is switched on."));
+
+	text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.ConrolsLabel", _("Conrols:"));
+	text->setAlign(Graphics::TextAlign::kTextAlignStart);
+
+	_enableInvertVerbColorsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck1", _("Invert verb colors"), _(""));
+	_enableRetroFontsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck2", _("Retro Fonts"), _(""));
+	_enableRetroVerbsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck3", _("Retro Verbs"), _(""));
+	_enableClassicSentenceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck4", _("Classic Sentence"), _(""));
+
+	text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.TextAndSpeechLabel", _("Text and Speech:"));
+	text->setAlign(Graphics::TextAlign::kTextAlignStart);
+
+	_enableDisplayTextGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Display Text"), _(""));
+	_enableHearVoiceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck2", _("Hear Vocie"), _(""));
+}
+
+void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
+	layouts.addDialog(layoutName, overlayedLayout);
+	layouts.addLayout(GUI::ThemeLayout::kLayoutVertical).addPadding(0, 0, 8, 8);
+
+	layouts.addPadding(0, 0, 8, 8)
+		.addSpace(10)
+		.addWidget("VideoLabel", "OptionsLabel")
+		.addWidget("VideoCheck1", "Checkbox")
+		.addWidget("VideoCheck2", "Checkbox")
+		.addWidget("ConrolsLabel", "OptionsLabel")
+		.addWidget("ControlsCheck1", "Checkbox")
+		.addWidget("ControlsCheck2", "Checkbox")
+		.addWidget("ControlsCheck3", "Checkbox")
+		.addWidget("ControlsCheck4", "Checkbox")
+		.addWidget("TextAndSpeechLabel", "OptionsLabel")
+		.addWidget("TextCheck1", "Checkbox")
+		.addWidget("TextCheck2", "Checkbox");
+
+	layouts.closeLayout().closeDialog();
+}
+
+void TwpOptionsContainerWidget::load() {
+	_enableToiletPaperOverGUICheckbox->setState(ConfMan.getBool("toiletPaperOver", _domain));
+	_enableAnnoyingInJokesGUICheckbox->setState(ConfMan.getBool("annoyingInJokes", _domain));
+	_enableInvertVerbColorsGUICheckbox->setState(ConfMan.getBool("invertVerbHighlight", _domain));
+	_enableRetroFontsGUICheckbox->setState(ConfMan.getBool("retroFonts", _domain));
+	_enableRetroVerbsGUICheckbox->setState(ConfMan.getBool("retroVerbs", _domain));
+	_enableClassicSentenceGUICheckbox->setState(ConfMan.getBool("hudSentence", _domain));
+	_enableDisplayTextGUICheckbox->setState(ConfMan.getBool("talkiesShowText", _domain));
+	_enableHearVoiceGUICheckbox->setState(ConfMan.getBool("talkiesHearVoice", _domain));
+}
+
+bool TwpOptionsContainerWidget::save() {
+	ConfMan.setBool("toiletPaperOver", _enableToiletPaperOverGUICheckbox->getState(), _domain);
+	ConfMan.setBool("annoyingInJokes", _enableAnnoyingInJokesGUICheckbox->getState(), _domain);
+	ConfMan.setBool("invertVerbHighlight", _enableInvertVerbColorsGUICheckbox->getState(), _domain);
+	ConfMan.setBool("retroFonts", _enableRetroFontsGUICheckbox->getState(), _domain);
+	ConfMan.setBool("retroVerbs", _enableRetroVerbsGUICheckbox->getState(), _domain);
+	ConfMan.setBool("hudSentence", _enableClassicSentenceGUICheckbox->getState(), _domain);
+	ConfMan.setBool("talkiesShowText", _enableDisplayTextGUICheckbox->getState(), _domain);
+	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
+	return true;
+}
+
+} // namespace Twp
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
new file mode 100644
index 00000000000..f7f88701bbd
--- /dev/null
+++ b/engines/twp/dialogs.h
@@ -0,0 +1,54 @@
+/* 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 TWP_DIALOGS_H
+#define TWP_DIALOGS_H
+
+#include "gui/dialog.h"
+#include "engines/dialogs.h"
+
+namespace Twp {
+
+class TwpOptionsContainerWidget : public GUI::OptionsContainerWidget {
+public:
+	TwpOptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &domain);
+	~TwpOptionsContainerWidget() override {}
+
+	virtual void load() override;
+	virtual bool save() override;
+
+private:
+	void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
+
+private:
+	GUI::CheckboxWidget *_enableToiletPaperOverGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableAnnoyingInJokesGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableInvertVerbColorsGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableRetroFontsGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableRetroVerbsGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableClassicSentenceGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableDisplayTextGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableHearVoiceGUICheckbox = nullptr;
+};
+
+}
+
+#endif
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index cfe2400d5fe..b973514ae0c 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -24,6 +24,7 @@
 #include "math/vector2d.h"
 #include "graphics/opengl/debug.h"
 #include "graphics/opengl/system_headers.h"
+#include "common/config-manager.h"
 
 namespace Twp {
 
@@ -129,15 +130,13 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 
 	// draw HUD background
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	// TODO: let classic = prefs(ClassicSentence);
-	bool classic = true;
+	bool classic = ConfMan.getBool("hudSentence");
 	const SpriteSheetFrame &backingFrame = gameSheet->frameTable[classic ? "ui_backing_tall" : "ui_backing"];
 	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
 	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha), trsf);
 
-	// TODO; let verbHlt = prefs(InvertVerbHighlight);
-	bool verbHlt = true;
+	bool verbHlt = ConfMan.getBool("invertVerbHighlight");
 	Color verbHighlight = verbHlt ? Color() : slot->verbUiColors.verbHighlight;
 	Color verbColor = verbHlt ? slot->verbUiColors.verbHighlight : Color();
 
@@ -146,7 +145,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	Texture *verbTexture = g_engine->_resManager.texture(verbSheet->meta.image);
 	// let lang = prefs(Lang);
 	Common::String lang = "en";
-	bool retroVerbs = false; // prefs(RetroVerbs);
+	bool retroVerbs = ConfMan.getBool("retroVerbs");
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
 
 	Shader *saveShader = g_engine->getGfx().getShader();
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index b8419b4a57d..e6103c65041 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -19,6 +19,13 @@
  *
  */
 
+
+#include "gui/gui-manager.h"
+#include "gui/widget.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/popup.h"
+#include "gui/ThemeEval.h"
+
 #include "common/translation.h"
 #include "common/savefile.h"
 #include "backends/keymapper/keymapper.h"
@@ -31,6 +38,7 @@
 #include "twp/savegame.h"
 #include "twp/time.h"
 #include "twp/actions.h"
+#include "twp/dialogs.h"
 
 #define MAX_SAVES 99
 
@@ -70,6 +78,17 @@ int TwpMetaEngine::getMaximumSaveSlot() const {
 	return MAX_SAVES;
 }
 
+void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
+	ConfMan.registerDefault("toiletPaperOver", false);
+	ConfMan.registerDefault("annoyingInJokes", false);
+	ConfMan.registerDefault("invertVerbHighlight", false);
+	ConfMan.registerDefault("retroFonts", false);
+	ConfMan.registerDefault("retroVerbs", false);
+	ConfMan.registerDefault("hudSentence", false);
+	ConfMan.registerDefault("talkiesShowText", true);
+	ConfMan.registerDefault("talkiesHearVoice", true);
+}
+
 SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
 	Common::String filename = Common::String::format("%s%02d.save", target, slot);
 	Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
@@ -136,6 +155,11 @@ SaveStateList TwpMetaEngine::listSaves(const char *target) const {
 	return saveList;
 }
 
+GUI::OptionsContainerWidget *TwpMetaEngine::buildEngineOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
+	GUI::OptionsContainerWidget *widget = new Twp::TwpOptionsContainerWidget(boss, name, target);
+	return widget;
+}
+
 Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) const {
 	Common::Keymap *engineKeyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, target, "Thimbleweed Park keymap");
 
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 7cb05ffd9e6..159940b0934 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -43,6 +43,9 @@ public:
 	int getMaximumSaveSlot() const override;
 
 	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
+	void registerDefaultSettings(const Common::String &) const override;
+
+	GUI::OptionsContainerWidget *buildEngineOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const override;
 
 	virtual Common::Array<Common::Keymap *> initKeymaps(const char *target) const override;
 };
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 829a94385c0..b13ee3a1f24 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -69,6 +69,7 @@ MODULE_OBJS = \
 	savegame.o \
 	btea.o \
 	time.o \
+	dialogs.o \
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 0809990cec7..c4e9b7792ac 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -20,6 +20,8 @@
  *
  */
 
+#include "common/config-manager.h"
+
 #include "twp/twp.h"
 #include "twp/motor.h"
 #include "twp/object.h"
@@ -315,6 +317,8 @@ void Talking::update(float elapsed) {
 }
 
 int Talking::loadActorSpeech(const Common::String &name) {
+	if(!ConfMan.getBool("talkiesHearVoice")) return 0;
+
 	debug("loadActorSpeech %s.ogg", name.c_str());
 	Common::String filename(name);
 	filename.toUppercase();
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 43ba318c7f9..f833b0fbe92 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/config-manager.h"
 #include "common/str.h"
 #include "image/png.h"
 #include "twp/resmanager.h"
@@ -78,8 +79,8 @@ void ResManager::loadSpriteSheet(const Common::String &name) {
 void ResManager::loadFont(const Common::String &name) {
 	if (name == "sayline") {
 		debug("Load font %s", name.c_str());
-		// TODO: Common::String resName = prefs(RetroFonts)? "FontRetroSheet.json": "FontModernSheet.json";
-		_fontModernSheet.load("FontModernSheet");
+		Common::String resName = ConfMan.getBool("retroFonts") ? "FontRetroSheet": "FontModernSheet";
+		_fontModernSheet.load(resName);
 		_fonts[name] = &_fontModernSheet;
 	} else if (name == "C64Font") {
 		debug("Load font %s", name.c_str());
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index faca0b229c4..7d371067b5e 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -22,6 +22,7 @@
 #include "math/matrix3.h"
 #include "math/vector3d.h"
 #include "common/algorithm.h"
+#include "common/config-manager.h"
 #include "twp/scenegraph.h"
 #include "twp/twp.h"
 #include "twp/gfx.h"
@@ -411,12 +412,30 @@ InputState::InputState() : Node("InputState") {
 
 InputState::~InputState() {}
 
+Common::String InputState::getCursorName() const {
+	switch (_cursorShape) {
+	case CursorShape::Left:
+		return "cursor_left";
+	case CursorShape::Right:
+		return "cursor_right";
+	case CursorShape::Front:
+		return "cursor_front";
+	case CursorShape::Back:
+		return "cursor_back";
+	case CursorShape::Pause:
+		return "cursor_pause";
+	}
+	return "cursor";
+}
+
 void InputState::drawCore(Math::Matrix4 trsf) {
+	Common::String cursorName = getCursorName();
 	// draw cursor
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	// TODO: if prefs(ClassicSentence) and self.hotspot:
-	Common::String cursorName = _hotspot ? Common::String::format("hotspot_%s", _cursorName.c_str()) : _cursorName;
+	if (ConfMan.getBool("hudSentence") && _hotspot) {
+		cursorName = "hotspot_" + cursorName;
+	}
 	const SpriteSheetFrame &sf = gameSheet->frameTable[cursorName];
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos * 2.f);
@@ -457,29 +476,7 @@ void InputState::setState(InputStateFlag state) {
 }
 
 void InputState::setCursorShape(CursorShape shape) {
-	if (_cursorShape != shape) {
-		_cursorShape = shape;
-		switch (shape) {
-		case CursorShape::Normal:
-			_cursorName = "cursor";
-			break;
-		case CursorShape::Left:
-			_cursorName = "cursor_left";
-			break;
-		case CursorShape::Right:
-			_cursorName = "cursor_right";
-			break;
-		case CursorShape::Front:
-			_cursorName = "cursor_front";
-			break;
-		case CursorShape::Back:
-			_cursorName = "cursor_back";
-			break;
-		case CursorShape::Pause:
-			_cursorName = "cursor_pause";
-			break;
-		}
-	}
+	_cursorShape = shape;
 }
 
 OverlayNode::OverlayNode() : Node("overlay") {
@@ -525,8 +522,7 @@ void Inventory::drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color,
 }
 
 void Inventory::drawArrows(Math::Matrix4 trsf) {
-	// TODO: bool isRetro = prefs(RetroVerbs);
-	bool isRetro = false;
+	bool isRetro = ConfMan.getBool("retroVerbs");
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
 	SpriteSheetFrame *arrowUp = &gameSheet->frameTable[isRetro ? "scroll_up_retro" : "scroll_up"];
@@ -655,15 +651,16 @@ void SentenceNode::setText(const Common::String &text) {
 void SentenceNode::drawCore(Math::Matrix4 trsf) {
 	Text text("sayline", _text);
 	float x, y;
-	//   if prefs(ClassicSentence):
-	//     x = (ScreenWidth - text.bounds.x) / 2f;
-	//     y = 208f;
-	//   else:
-	x = MAX(_pos.getX() - text.getBounds().getX() / 2.f, MARGIN);
-	x = MIN(x, SCREEN_WIDTH - text.getBounds().getX() - MARGIN);
-	y = _pos.getY() + 2.f * 38.f;
-	if (y >= SCREEN_HEIGHT)
-		y = _pos.getY() - 38.f;
+	if (ConfMan.getBool("hudSentence")) {
+		x = (SCREEN_WIDTH - text.getBounds().getX()) / 2.f;
+		y = 208.f;
+	} else {
+		x = MAX(_pos.getX() - text.getBounds().getX() / 2.f, MARGIN);
+		x = MIN(x, SCREEN_WIDTH - text.getBounds().getX() - MARGIN);
+		y = _pos.getY() + 2.f * 38.f;
+		if (y >= SCREEN_HEIGHT)
+			y = _pos.getY() - 38.f;
+	}
 	Math::Matrix4 t;
 	t.translate(Math::Vector3d(x, y, 0.f));
 	text.draw(g_engine->getGfx(), t);
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index c572c0369ce..f614f22cca3 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -261,6 +261,7 @@ public:
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
+	Common::String getCursorName() const;
 
 private:
 	bool _inputHUD = false;
@@ -268,7 +269,6 @@ private:
     bool _showCursor = false;
     bool _inputVerbsActive = false;
     CursorShape _cursorShape = CursorShape::Normal;
-    Common::String _cursorName = "cursor";
     bool _hotspot = false;
 };
 
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index e1a1933c501..6320497906b 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -74,6 +74,12 @@ SQInteger sqpush(HSQUIRRELVM v, Common::String value) {
 	return 1;
 }
 
+template<>
+SQInteger sqpush(HSQUIRRELVM v, const char* value) {
+	sq_pushstring(v, value, -1);
+	return 1;
+}
+
 template<>
 SQInteger sqpush(HSQUIRRELVM v, HSQOBJECT value) {
 	sq_pushobject(v, value);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 2541ee1646e..46c2843fdd1 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -216,8 +216,8 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 				}
 			}
-			// Just clicking on the ground
-			cancelSentence(_actor);
+			// TODO: Just clicking on the ground
+			//     cancelSentence(self.actor)
 		} else if (_cursor.isRightDown()) {
 			// button right: execute default verb
 			if (obj) {
@@ -661,6 +661,9 @@ Common::Error TwpEngine::run() {
 		_vm.exec(code);
 	}
 
+	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
+	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
+
 	static int speed = 1;
 
 	// Simple event handling loop


Commit: 8adea5d125490ff1897f14ae1efe13dde250fe12
    https://github.com/scummvm/scummvm/commit/8adea5d125490ff1897f14ae1efe13dde250fe12
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix default verb

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 46c2843fdd1..7e5bdea2113 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -213,7 +213,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 			if (!handled) {
 				if (_actor && (scrPos.getY() > 172)) {
 					_actor->walk(roomPos);
-					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+					_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
 				}
 			}
 			// TODO: Just clicking on the ground
@@ -234,7 +234,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 Verb TwpEngine::verb() {
 	Verb result = _hud._verb;
 	if (result.id.id == VERB_WALKTO && _noun1 && _noun1->inInventory())
-		result = _hud.actorSlot(_actor)->verbs[_noun1->defaultVerbId()];
+		result = *_hud.actorSlot(_actor)->getVerb(_noun1->defaultVerbId());
 	return result;
 }
 
@@ -931,7 +931,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
 	if (_actor)
-		_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+		_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
 
 	// move current actor to the new room
 	Math::Vector2d camPos;
@@ -1183,7 +1183,7 @@ void TwpEngine::resetVerb() {
 	_noun1 = nullptr;
 	_noun2 = nullptr;
 	_useFlag = ufNone;
-	_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+	_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
 }
 
 bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {


Commit: 098c53cd5d1d7813fb5439f2d2a1dede950bd973
    https://github.com/scummvm/scummvm/commit/098c53cd5d1d7813fb5439f2d2a1dede950bd973
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix cursor shape

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7e5bdea2113..7ac4706cf8a 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -386,13 +386,13 @@ void TwpEngine::update(float elapsed) {
 			} else if (_room->_fullscreen == FULLSCREENROOM && _noun1) {
 				// if the object is a door, it has a flag indicating its direction: left, right, front, back
 				int flags = _noun1->getFlags();
-				if (flags & DOOR_LEFT)
+				if ((flags & DOOR_LEFT)==DOOR_LEFT)
 					_inputState.setCursorShape(CursorShape::Left);
-				else if (flags & DOOR_RIGHT)
+				else if ((flags & DOOR_RIGHT)==DOOR_RIGHT)
 					_inputState.setCursorShape(CursorShape::Right);
-				else if (flags & DOOR_FRONT)
+				else if ((flags & DOOR_FRONT)==DOOR_FRONT)
 					_inputState.setCursorShape(CursorShape::Front);
-				else if (flags & DOOR_BACK)
+				else if ((flags & DOOR_BACK)==DOOR_BACK)
 					_inputState.setCursorShape(CursorShape::Back);
 				else
 					_inputState.setCursorShape(CursorShape::Normal);


Commit: f2c7730757b408e8c0012cd28adb8b6da3cc3376
    https://github.com/scummvm/scummvm/commit/f2c7730757b408e8c0012cd28adb8b6da3cc3376
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix HUD shader

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index d64a139de08..da8a812565d 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -216,8 +216,8 @@ void Gfx::init() {
 	GL_CALL(_posLoc = glGetAttribLocation(_defaultShader.program, "a_position"));
 	GL_CALL(_colLoc = glGetAttribLocation(_defaultShader.program, "a_color"));
 	GL_CALL(_texCoordsLoc = glGetAttribLocation(_defaultShader.program, "a_texCoords"));
-	GL_CALL(_texLoc = glGetUniformLocation(_defaultShader.program, "u_texture"));
-	GL_CALL(_trsfLoc = glGetUniformLocation(_defaultShader.program, "u_transform"));
+	GL_CALL(glGetUniformLocation(_defaultShader.program, "u_texture"));
+	GL_CALL(glGetUniformLocation(_defaultShader.program, "u_transform"));
 	GL_CALL(glVertexAttribPointer(_posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
 	GL_CALL(glEnableVertexAttribArray(_posLoc));
 	GL_CALL(glVertexAttribPointer(_colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
@@ -267,13 +267,16 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 
 		GL_CALL(glUseProgram(_shader->program));
 
+		int posLoc = glGetAttribLocation(_defaultShader.program, "a_position");
+		int colLoc = glGetAttribLocation(_defaultShader.program, "a_color");
+		int texCoordsLoc = glGetAttribLocation(_defaultShader.program, "a_texCoords");
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-		GL_CALL(glEnableVertexAttribArray(_posLoc));
-		GL_CALL(glVertexAttribPointer(_posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-		GL_CALL(glEnableVertexAttribArray(_colLoc));
-		GL_CALL(glVertexAttribPointer(_colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-		GL_CALL(glEnableVertexAttribArray(_texCoordsLoc));
-		GL_CALL(glVertexAttribPointer(_texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+		GL_CALL(glEnableVertexAttribArray(posLoc));
+		GL_CALL(glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
+		GL_CALL(glEnableVertexAttribArray(colLoc));
+		GL_CALL(glVertexAttribPointer(colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
+		GL_CALL(glEnableVertexAttribArray(texCoordsLoc));
+		GL_CALL(glVertexAttribPointer(texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
 
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
@@ -333,7 +336,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 		if (num == 0) {
 			GL_CALL(glActiveTexture(GL_TEXTURE0));
 			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
-			GL_CALL(glUniform1i(0, 0));
+			GL_CALL(glUniform1i(_shader->getUniformLocation("u_texture"), 0));
 		} else {
 			for (int i = 0; i < num; i++) {
 				GL_CALL(glActiveTexture(GL_TEXTURE0 + i));
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 6c4c7e1d4fa..de941670140 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -129,10 +129,11 @@ public:
 	virtual int getTexture(int index) { return 0;};
 	virtual int getTextureLoc(int index) { return 0;};
 
+	int getUniformLocation(const char *name);
+
 private:
 	uint32 loadShader(const char *code, uint32 shaderType);
 	void statusShader(uint32 shader);
-	int getUniformLocation(const char *name);
 
 public:
 	uint32 program;
@@ -181,8 +182,8 @@ private:
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture *_texture = nullptr;
-	int32 _posLoc = 0, _colLoc = 0, _texCoordsLoc = 0, _texLoc = 0, _trsfLoc = 0;
-	int32 _oldFbo;
+	int32 _posLoc = 0, _colLoc = 0, _texCoordsLoc = 0;
+	int32 _oldFbo = 0;
 };
 } // namespace Twp
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index b973514ae0c..a9dff811f41 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -29,7 +29,10 @@
 namespace Twp {
 
 HudShader::HudShader() {
-	const char *verbVtxShader = R"(#version 110
+}
+
+void HudShader::init() {
+	const char *vsrc = R"(#version 110
 	attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
@@ -48,18 +51,17 @@ HudShader::HudShader() {
 	varying vec2 v_ranges;
 
 	void main() {
+		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+
 		v_color = a_color;
 		v_texCoords = a_texCoords;
 		v_shadowColor = u_shadowColor;
 		v_normalColor = u_normalColor;
 		v_highlightColor = u_highlightColor;
 		v_ranges = u_ranges;
-		vec4 worldPosition = vec4(a_position, 0.0, 1.0);
-		vec4 normalizedPosition = u_transform * worldPosition;
-		gl_Position = normalizedPosition;
 	})";
 
-const char* verbFgtShader = R"(#version 110
+	const char* fsrc = R"(#version 110
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	varying vec4 v_shadowColor;
@@ -82,7 +84,7 @@ const char* verbFgtShader = R"(#version 110
 		texColor *= v_color;
 		gl_FragColor = texColor;
 	})";
-	init(verbVtxShader, verbFgtShader);
+	Shader::init(vsrc, fsrc);
 
 	GL_CALL(_rangesLoc = glGetUniformLocation(program, "u_ranges"));
 	GL_CALL(_shadowColorLoc = glGetUniformLocation(program, "u_shadowColor"));
@@ -107,6 +109,10 @@ Hud::Hud() : Node("hud") {
 	}
 }
 
+void Hud::init() {
+	_shader.init();
+}
+
 ActorSlot *Hud::actorSlot(Object *actor) {
 	for (int i = 0; i < NUMACTORS; i++) {
 		ActorSlot *slot = &_actorSlots[i];
@@ -149,10 +155,10 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
 
 	Shader *saveShader = g_engine->getGfx().getShader();
-	// g_engine->getGfx().use(&_shader);
-	// _shader._shadowColor = slot->verbUiColors.verbNormalTint;
-	// _shader._normalColor = slot->verbUiColors.verbHighlight;
-	// _shader._highlightColor = slot->verbUiColors.verbHighlightTint;
+	g_engine->getGfx().use(&_shader);
+	_shader._shadowColor = slot->verbUiColors.verbNormalTint;
+	_shader._normalColor = slot->verbUiColors.verbHighlight;
+	_shader._highlightColor = slot->verbUiColors.verbHighlightTint;
 
 	bool isOver = false;
 	for (int i = 1; i < 22; i++) {
@@ -169,7 +175,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 			drawSprite(verbFrame, verbTexture, color, trsf);
 		}
 	}
-	// g_engine->getGfx().use(saveShader);
+	g_engine->getGfx().use(saveShader);
 	_over = isOver;
 }
 
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 4fc85a8c3df..a715027feec 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -81,6 +81,8 @@ public:
 	HudShader();
 	virtual ~HudShader() override;
 
+	void init();
+
 private:
 	virtual void applyUniforms() final;
 
@@ -100,6 +102,7 @@ class Hud : public Node {
 public:
 	Hud();
 
+	void init();
 	ActorSlot *actorSlot(Object *actor);
 	bool isOver() const { return _over; }
 	void update(Math::Vector2d pos, Object *hotspot, bool mouseClick);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7ac4706cf8a..fc6ce630983 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -636,6 +636,7 @@ Common::Error TwpEngine::run() {
 	setDebugger(new Console());
 
 	_gfx.init();
+	_hud.init();
 	_fadeShader.reset(new FadeShader());
 
 	_lighting = new Lighting();


Commit: dca1cfea21e7b8bbcebf58cf744a45fcdd8453d4
    https://github.com/scummvm/scummvm/commit/dca1cfea21e7b8bbcebf58cf744a45fcdd8453d4
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add black & white and ghost shaders

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/roomlib.cpp
    engines/twp/shaders.cpp
    engines/twp/shaders.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index da8a812565d..a41ea38363e 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -176,6 +176,33 @@ void Shader::setUniform(const char *name, Math::Matrix4 value) {
 	glUseProgram(prev);
 }
 
+void Shader::setUniform(const char * name, float value) {
+  	GLint prev;
+	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
+	glUseProgram(program);
+    int loc = getUniformLocation(name);
+    GL_CALL(glUniform1f(loc, value));
+	glUseProgram(prev);
+}
+
+void Shader::setUniform(const char * name, Math::Vector3d value) {
+    GLint prev;
+	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
+	glUseProgram(program);
+    int loc = getUniformLocation(name);
+    GL_CALL(glUniform3fv(loc, 1, value.getData()));
+	glUseProgram(prev);
+}
+
+void Shader::setUniform(const char * name, Color value) {
+    GLint prev;
+	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
+	glUseProgram(program);
+    int loc = getUniformLocation(name);
+    GL_CALL(glUniform3fv(loc, 1, value.v));
+	glUseProgram(prev);
+}
+
 void Gfx::init() {
 	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
 	byte pixels[] = {0xFF, 0xFF, 0xFF, 0xFF};
@@ -185,19 +212,8 @@ void Gfx::init() {
 	empty.format = fmt;
 	empty.setPixels(pixels);
 	gEmptyTexture.load(empty);
-	const char *vsrc = R"(#version 110
-		uniform mat4 u_transform;
-	attribute vec2 a_position;
-	attribute vec4 a_color;
-	attribute vec2 a_texCoords;
-	varying vec4 v_color;
-	varying vec2 v_texCoords;
-	void main() {
-		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-		v_color = a_color;
-		v_texCoords = a_texCoords;
-	})";
-	const char* fsrc = R"(#version 110
+
+	const char* fragmentSrc = R"(#version 110
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	uniform sampler2D u_texture;
@@ -205,7 +221,7 @@ void Gfx::init() {
 		vec4 tex_color = texture2D(u_texture, v_texCoords);
 		gl_FragColor = v_color * tex_color;
 	})";
-	_defaultShader.init(vsrc, fsrc);
+	_defaultShader.init(vsrc, fragmentSrc);
 	_shader = &_defaultShader;
 	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
 
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index de941670140..f29fd5911ad 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -124,6 +124,10 @@ public:
 	void init(const char *vertex, const char *fragment);
 
 	void setUniform(const char *name, Math::Matrix4 value);
+	void setUniform(const char * name, float value);
+	void setUniform(const char * name, Math::Vector3d value);
+	void setUniform(const char * name, Color value);
+
 	virtual void applyUniforms() {}
 	virtual int getNumTextures() { return 0;};
 	virtual int getTexture(int index) { return 0;};
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 1bb042254d8..05e5df03dd6 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -261,7 +261,8 @@ static SQInteger masterRoomArray(HSQUIRRELVM v) {
 }
 
 static SQInteger removeTrigger(HSQUIRRELVM v) {
-	if(!g_engine->_room) return 0;
+	if (!g_engine->_room)
+		return 0;
 	if (sq_gettype(v, 2) == OT_CLOSURE) {
 		HSQOBJECT closure;
 		sq_resetobject(&closure);
@@ -313,7 +314,33 @@ static SQInteger roomActors(HSQUIRRELVM v) {
 }
 
 static SQInteger roomEffect(HSQUIRRELVM v) {
-	warning("TODO: roomEffect not implemented");
+	int effect = 0;
+	if (SQ_FAILED(sqget(v, 2, effect)))
+		return sq_throwerror(v, "failed to get effect");
+	RoomEffect roomEffect = (RoomEffect)effect;
+	SQInteger nArgs = sq_gettop(v);
+	if (roomEffect == RoomEffect::Ghost) {
+		if (nArgs == 14) {
+			sqget(v, 3, g_engine->_shaderParams.iFade);
+			sqget(v, 4, g_engine->_shaderParams.wobbleIntensity);
+			sqget(v, 6, g_engine->_shaderParams.shadows.rgba.r);
+			sqget(v, 7, g_engine->_shaderParams.shadows.rgba.g);
+			sqget(v, 8, g_engine->_shaderParams.shadows.rgba.b);
+			sqget(v, 9, g_engine->_shaderParams.midtones.rgba.r);
+			sqget(v, 10, g_engine->_shaderParams.midtones.rgba.g);
+			sqget(v, 11, g_engine->_shaderParams.midtones.rgba.b);
+			sqget(v, 12, g_engine->_shaderParams.highlights.rgba.r);
+			sqget(v, 13, g_engine->_shaderParams.highlights.rgba.g);
+			sqget(v, 14, g_engine->_shaderParams.highlights.rgba.b);
+		} else {
+			g_engine->_shaderParams.iFade = 1.f;
+			g_engine->_shaderParams.wobbleIntensity = 1.f;
+			g_engine->_shaderParams.shadows = Color(-0.3f, 0.f, 0.f);
+			g_engine->_shaderParams.midtones = Color(-0.2f, 0.f, 0.1f);
+			g_engine->_shaderParams.highlights = Color(0.f, 0.f, 0.2f);
+		}
+	}
+	g_engine->_room->_effect = (RoomEffect)effect;
 	return 0;
 }
 
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 8c2860fb026..6840bf3b4fe 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -26,22 +26,219 @@
 
 namespace Twp {
 
+const char *vsrc = R"(#version 110
+	uniform mat4 u_transform;
+attribute vec2 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texCoords;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+void main() {
+	gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+	v_color = a_color;
+	v_texCoords = a_texCoords;
+})";
+
+	const char* bwShader = R"(#version 110
+varying vec2 v_texCoords;
+varying vec4 v_color;
+uniform sampler2D u_texture;
+void main() {
+	vec4 texColor = texture2D(u_texture, v_texCoords);
+	vec4 col = v_color * texColor;
+	float gray = dot(col.xyz, vec3(0.299, 0.587, 0.114));
+	gl_FragColor = vec4(gray, gray, gray, col.a);
+})";
+
+const char* ghostShader = R"(#version 110
+// Work in progress ghost shader.. Too over the top at the moment, it'll make you sick.
+
+varying vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform float iGlobalTime;
+uniform float iFade;
+uniform float wobbleIntensity;
+uniform vec3 shadows;
+uniform vec3 midtones;
+uniform vec3 highlights;
+
+const float speed = 0.1;
+const float emboss = 0.70;
+const float intensity = 0.6;
+const int steps = 4;
+const float frequency = 9.0;
+
+float colour(vec2 coord) {
+	float col = 0.0;
+
+	float timeSpeed = iGlobalTime * speed;
+	vec2 adjc = coord;
+	adjc.x += timeSpeed; // adjc0.x += fcos*timeSpeed;
+	float sum0 = cos(adjc.x * frequency) * intensity;
+	col += sum0;
+
+	adjc = coord;
+	float fcos = 0.623489797;
+	float fsin = 0.781831503;
+	adjc.x += fcos * timeSpeed;
+	adjc.y -= fsin * timeSpeed;
+	float sum1 = cos((adjc.x * fcos - adjc.y * fsin) * frequency) * intensity;
+	col += sum1;
+
+	adjc = coord;
+	fcos = -0.900968909;
+	fsin = 0.433883607;
+	adjc.x += fcos * timeSpeed;
+	adjc.y -= fsin * timeSpeed;
+	col += cos((adjc.x * fcos - adjc.y * fsin) * frequency) * intensity;
+
+	// do same in reverse.
+	col += sum1;
+	col += sum0;
+
+	return cos(col);
+}
+
+vec3 rgb2hsv(vec3 c) {
+	vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+	vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+	vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+	float d = q.x - min(q.w, q.y);
+	float e = 1.0e-10;
+	return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+vec3 hsv2rgb(vec3 c) {
+	vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+	vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+	return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+float rand(vec2 Input) {
+	float dt = dot(Input, vec2(12.9898, 78.233));
+	float sn = mod(dt, 3.14);
+	return fract(sin(sn) * 43758.5453123);
+}
+
+float color_balance(float col, float l, vec3 change) {
+	// NOTE: change = (shadow, midtones, highlights)
+
+	float sup = 83.0;  // shadow upper bounds
+	float mup = 166.0; // midtones upper bounds
+
+	float value = col * 255.0;
+	l = l * 100.0;
+
+	if (l < sup) {
+		// shadow
+		float f = (sup - l + 1.0) / (sup + 1.0);
+		value += change.x * f;
+	} else if (l < mup) {
+		// midtone
+		float mrange = (mup - sup) / 2.0;
+		float mid = mrange + sup;
+		float diff = mid - l;
+		diff = -diff;
+		if (diff < 0.0) {
+			float f = 1.0 - (diff + 1.0) / (mrange + 1.0);
+			value += change.y * f;
+		}
+	} else {
+		// highlight
+		float f = (l - mup + 1.0) / (255.0 - mup + 1.0);
+		value += change.z * f;
+	}
+	value = min(255.0, max(0.0, value));
+	return value / 255.0;
+}
+
+vec2 rgb2cv(vec3 RGB) {
+	vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0 / 3.0) : vec4(RGB.gb, 0.0, -1.0 / 3.0);
+	vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
+	float C = Q.x - min(Q.w, Q.y);
+	return vec2(C, Q.x);
+}
+
+float rgbToLuminance(vec3 RGB) {
+	float cMax = max(max(RGB.x, RGB.y), RGB.z);
+	float cMin = min(min(RGB.x, RGB.y), RGB.z);
+
+	return (cMax + cMin) * 0.5;
+}
+
+void main(void) {
+	vec2 c1 = v_texCoords;
+	float cc1 = colour(c1);
+	vec2 offset;
+
+	c1.x += (0.001 * wobbleIntensity); // appx 12 pixels horizontal
+	offset.x = emboss * (cc1 - colour(c1));
+
+	c1.x = v_texCoords.x;
+	c1.y += (0.002 * wobbleIntensity); // appx 12 pixels verticle
+	offset.y = emboss * (cc1 - colour(c1));
+
+	// TODO: The effect should be centered around Franklyns position in the room, not the center
+	// if ( emitFromCenter == 1)
+	{
+		vec2 center = vec2(0.5, 0.5);
+		float distToCenter = distance(center, v_texCoords);
+		offset *= distToCenter * 2.0;
+	}
+
+	c1 = v_texCoords;
+	c1 += (offset * iFade);
+
+	vec3 col = vec3(0, 0, 0);
+	if (c1.x >= 0.0 && c1.x < (1.0 - 0.003125)) {
+		col = texture2D(u_texture, c1).rgb;
+		float intensity = rgbToLuminance(col); //(col.r + col.g + col.b) * 0.333333333;
+
+		// Exponential Shadows
+		float shadowsBleed = 1.0 - intensity;
+		shadowsBleed *= shadowsBleed;
+		shadowsBleed *= shadowsBleed;
+
+		// Exponential midtones
+		float midtonesBleed = 1.0 - abs(-1.0 + intensity * 2.0);
+		midtonesBleed *= midtonesBleed;
+		midtonesBleed *= midtonesBleed;
+
+		// Exponential Hilights
+		float hilightsBleed = intensity;
+		hilightsBleed *= hilightsBleed;
+		hilightsBleed *= hilightsBleed;
+
+		vec3 colorization = col.rgb + shadows * shadowsBleed +
+							midtones * midtonesBleed +
+							highlights * hilightsBleed;
+
+		colorization = mix(col, colorization, iFade);
+
+		// col = lerp(col, colorization, _Amount);
+		col = min(vec3(1.0), max(vec3(0.0), colorization));
+	}
+	gl_FragColor = v_color * vec4(col, texture2D(u_texture, c1).a);
+})";
+
 FadeShader::FadeShader() {
-	const char *vsrc = R"(#version 110
-		uniform mat4 u_transform;
-	attribute vec2 a_position;
-	attribute vec4 a_color;
-	attribute vec2 a_texCoords;
-	varying vec4 v_color;
-	varying vec2 v_texCoords;
-	void main() {
-		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-		v_color = a_color;
-		v_texCoords = a_texCoords;
-	})";
-	const char *fadeShader = R"(#version 110
+	// const char *vsrc = R"(#version 110
+	// 	uniform mat4 u_transform;
+	// attribute vec2 a_position;
+	// attribute vec4 a_color;
+	// attribute vec2 a_texCoords;
+	// varying vec4 v_color;
+	// varying vec2 v_texCoords;
+	// void main() {
+	// 	gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+	// 	v_color = a_color;
+	// 	v_texCoords = a_texCoords;
+	// })";
+	const char *fadeShader = R "(#version 110
 #ifdef GL_ES
-precision highp float;
+		precision highp float;
 #endif
 
 	varying vec4 v_color;
@@ -121,15 +318,16 @@ void ShaderParams::updateShader() {
 	//     Shader* shader = g_engine->getGfx().getShader();
 	//     shader->setUniform("iGlobalTime", iGlobalTime);
 	//     shader->setUniform("iNoiseThreshold", iNoiseThreshold);
-	//   } else if (effect == RoomEffect::Ghost) {
-	//     Shader* shader = g_engine->getGfx().getShader();
-	//     shader->setUniform("iGlobalTime", iGlobalTime);
-	//     shader->setUniform("iFade", iFade);
-	//     shader->setUniform("wobbleIntensity", wobbleIntensity);
-	//     shader->setUniform("shadows", shadows);
-	//     shader->setUniform("midtones", midtones);
-	//     shader->setUniform("highlights", highlights);
-	//   }
+	//   } else
+	if (effect == RoomEffect::Ghost) {
+		Shader *shader = g_engine->getGfx().getShader();
+		shader->setUniform("iGlobalTime", iGlobalTime);
+		shader->setUniform("iFade", iFade);
+		shader->setUniform("wobbleIntensity", wobbleIntensity);
+		shader->setUniform("shadows", shadows);
+		shader->setUniform("midtones", midtones);
+		shader->setUniform("highlights", highlights);
+	}
 }
 
 } // namespace Twp
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 98d07b9ad5b..2af471500da 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -27,18 +27,22 @@
 
 namespace Twp {
 
+extern const char* vsrc;
+extern const char* bwShader;
+extern const char* ghostShader;
+
 struct ShaderParams {
     RoomEffect effect;
-    float sepiaFlicker;
+    float sepiaFlicker = 1.f;
     float randomValue[5];
     float timeLapse;
     float iGlobalTime;
-    float iNoiseThreshold;
-    float iFade;
-    float wobbleIntensity;
-    Math::Vector3d shadows;
-    Math::Vector3d midtones;
-    Math::Vector3d highlights;
+    float iNoiseThreshold = 1.f;
+    float iFade = 1.f;
+    float wobbleIntensity = 1.f;
+    Color shadows = Color(-0.3f, 0.f, 0.f);
+    Color midtones = Color(-0.2f, 0.f, 0.1f);
+    Color highlights = Color(0.f, 0.f, 0.2f);
 
 	void updateShader();
 };
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index fc6ce630983..c1bf515d542 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -213,7 +213,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 			if (!handled) {
 				if (_actor && (scrPos.getY() > 172)) {
 					_actor->walk(roomPos);
-					_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
+					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 				}
 			}
 			// TODO: Just clicking on the ground
@@ -512,29 +512,28 @@ void TwpEngine::update(float elapsed) {
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
 	_shaderParams.effect = effect;
-	//   switch (effect) {
-	//   case RoomEffect::None:
-	//     _gfx.use(nullptr);
-	// 	break;
+	  switch (effect) {
+	  case RoomEffect::None:
+	    _gfx.use(nullptr);
+		break;
 	//   case RoomEffect::Sepia:
 	//     let shader = newShader(vertexShader, sepiaShader);
 	//     gfxShader(shader);
 	//     shader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
 	// 	break;
-	//   case RoomEffect::BlackAndWhite:
-	//     gfxShader(newShader(vertexShader, bwShader));
-	// 	break;
+	  case RoomEffect::BlackAndWhite:
+	  	_gfx.use(&_bwShader);
+		break;
 	//   case RoomEffect::Ega:
 	//     gfxShader(newShader(vertexShader, egaShader));
 	// 	break;
 	//   case RoomEffect::Vhs:
 	//     gfxShader(newShader(vertexShader, vhsShader));
 	// 	break;
-	//   case RoomEffect::Ghost:
-	//     let shader = newShader(vertexShader, ghostShader);
-	//     gfxShader(shader);
-	// 	break;
-	//   }
+	  case RoomEffect::Ghost:
+	    _gfx.use(&_ghostShader);
+		break;
+	  }
 }
 
 void TwpEngine::draw() {
@@ -555,11 +554,11 @@ void TwpEngine::draw() {
 
 	// then render this texture with room effect to another texture
 	_gfx.setRenderTarget(&renderTexture2);
-	// setShaderEffect(_room->_effect);
-	// _shaderParams.randomValue[0] = g_engine.rand.rand(0f..1f);
-	// _shaderParams.timeLapse = fmodf(_time, 1000.f);
-	// _shaderParams.iGlobalTime = _shaderParams.timeLapse;
-	// _shaderParams.updateShader();
+	setShaderEffect(_room->_effect);
+	_shaderParams.randomValue[0] = g_engine->getRandom();
+	_shaderParams.timeLapse = fmodf(_time, 1000.f);
+	_shaderParams.iGlobalTime = _shaderParams.timeLapse;
+	_shaderParams.updateShader();
 
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	bool flipY = _fadeShader->_effect == FadeEffect::Wobble;
@@ -637,6 +636,8 @@ Common::Error TwpEngine::run() {
 
 	_gfx.init();
 	_hud.init();
+	_bwShader.init(vsrc, bwShader);
+	_ghostShader.init(vsrc, ghostShader);
 	_fadeShader.reset(new FadeShader());
 
 	_lighting = new Lighting();
@@ -932,7 +933,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
 	if (_actor)
-		_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
+		_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 
 	// move current actor to the new room
 	Math::Vector2d camPos;
@@ -1184,7 +1185,7 @@ void TwpEngine::resetVerb() {
 	_noun1 = nullptr;
 	_noun2 = nullptr;
 	_useFlag = ufNone;
-	_hud._verb = *_hud.actorSlot(_actor)->getVerb(VERB_WALKTO);
+	_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 }
 
 bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
@@ -1194,7 +1195,9 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	Common::String verbFuncName = _hud.actorSlot(actor)->getVerb(verbId.id)->fun;
+	ActorSlot* slot = _hud.actorSlot(actor);
+	Verb* verb = slot->getVerb(verbId.id);
+	Common::String verbFuncName = verb ? verb->fun: slot->getVerb(0)->fun;
 	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f928f1e4c29..4a20fe13e43 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -214,16 +214,18 @@ public:
 	ActorSwitcher _actorSwitcher;
 	AudioSystem _audio;
 	SaveGameManager _saveGameManager;
+	ShaderParams _shaderParams;
 
 private:
 	Gfx _gfx;
 	Vm _vm;
 	Preferences _prefs;
-	ShaderParams _shaderParams;
 	unique_ptr<FadeShader> _fadeShader;
 	SentenceNode _sentence;
 	WalkboxNode _walkboxNode;
 	PathNode _pathNode;
+	Shader _bwShader;
+	Shader _ghostShader;
 };
 
 extern TwpEngine *g_engine;


Commit: 6972a169810f527bff011161c664c650139db026
    https://github.com/scummvm/scummvm/commit/6972a169810f527bff011161c664c650139db026
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add sepia shader

Changed paths:
    engines/twp/shaders.cpp
    engines/twp/shaders.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 6840bf3b4fe..5c4ed4e8163 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -223,20 +223,101 @@ void main(void) {
 	gl_FragColor = v_color * vec4(col, texture2D(u_texture, c1).a);
 })";
 
+const char* sepiaShader = R"(#version 110
+
+varying vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform float sepiaFlicker;
+uniform float RandomValue[5];
+uniform float TimeLapse;
+
+vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec3 permute(vec3 x) { return mod289(((x * 34.0) + 1.0) * x); }
+float snoise(vec2 v) {
+	const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0
+						0.366025403784439,  // 0.5*(sqrt(3.0)-1.0)
+						-0.577350269189626, // -1.0 + 2.0 * C.x
+						0.024390243902439); // 1.0 / 41.0
+
+	// First corner
+	vec2 i = floor(v + dot(v, C.yy));
+	vec2 x0 = v - i + dot(i, C.xx);
+
+	// Other corners
+	vec2 i1;
+	i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+	vec4 x12 = x0.xyxy + C.xxzz;
+	x12.xy -= i1;
+
+	// Permutations
+	i = mod289(i); // Avoid truncation effects in permutation
+	vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
+
+	vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
+	m = m * m;
+	m = m * m;
+
+	// Gradients: 41 points uniformly over a line, mapped onto a diamond.
+	// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
+
+	vec3 x = 2.0 * fract(p * C.www) - 1.0;
+	vec3 h = abs(x) - 0.5;
+	vec3 ox = floor(x + 0.5);
+	vec3 a0 = x - ox;
+
+	// Normalise gradients implicitly by scaling m
+	// Approximation of: m *= inversesqrt( a0*a0 + h*h );
+	m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
+
+	// Compute final noise value at P
+	vec3 g;
+	g.x = a0.x * x0.x + h.x * x0.y;
+	g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+	return 130.0 * dot(m, g);
+}
+
+void main(void) {
+	const float RADIUS = 0.75;
+	const float SOFTNESS = 0.45;
+	const float ScratchValue = 0.3;
+
+	vec4 texColor = texture2D(u_texture, v_texCoords);
+	vec4 col = v_color * texColor;
+	float gray = dot(col.rgb, vec3(0.299, 0.587, 0.114));
+	vec2 dist = vec2(v_texCoords.x - 0.5, v_texCoords.y - 0.5);
+	vec3 sepiaColor = vec3(gray) * vec3(0.9, 0.8, 0.6); // vec3(1.2, 1.0, 0.8);
+	float len = dot(dist, dist);
+	float vignette = smoothstep(RADIUS, RADIUS - SOFTNESS, len);
+	//   float vignette = (1.0 - len);
+	col.rgb = mix(col.rgb, sepiaColor, 0.80) * vignette * sepiaFlicker; // Want to keep SOME of the original color, so only use 80% sepia
+	//   col.rgb = vec3( vignette ) * sepiaFlicker;
+
+	for (int i = 0; i < 1; i++) {
+		if (RandomValue[i] < ScratchValue) {
+			// Pick a random spot to show scratches
+			float dist = 1.0 / ScratchValue;
+			float d = distance(v_texCoords, vec2(RandomValue[i] * dist, RandomValue[i] * dist));
+			if (d < 0.4) {
+				// Generate the scratch
+				float xPeriod = 8.0;
+				float yPeriod = 1.0;
+				float pi = 3.141592;
+				float phase = TimeLapse;
+				float turbulence = snoise(v_texCoords * 2.5);
+				float vScratch = 0.5 + (sin(((v_texCoords.x * xPeriod + v_texCoords.y * yPeriod + turbulence)) * pi + phase) * 0.5);
+				vScratch = clamp((vScratch * 10000.0) + 0.35, 0.0, 1.0);
+
+				col.rgb *= vScratch;
+			}
+		}
+	}
+	gl_FragColor = col;
+})";
+
 FadeShader::FadeShader() {
-	// const char *vsrc = R"(#version 110
-	// 	uniform mat4 u_transform;
-	// attribute vec2 a_position;
-	// attribute vec4 a_color;
-	// attribute vec2 a_texCoords;
-	// varying vec4 v_color;
-	// varying vec2 v_texCoords;
-	// void main() {
-	// 	gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-	// 	v_color = a_color;
-	// 	v_texCoords = a_texCoords;
-	// })";
-	const char *fadeShader = R "(#version 110
+	const char *fadeShader = R"(#version 110
 #ifdef GL_ES
 		precision highp float;
 #endif
@@ -309,17 +390,17 @@ int FadeShader::getTexture(int index) {
 int FadeShader::getTextureLoc(int index) { return _textureLoc[index]; }
 
 void ShaderParams::updateShader() {
-	// TODO
-	//   if (effect == RoomEffect::Sepia) {
-	//     Shader* shader = g_engine->getGfx().getShader();
-	//     shader->setUniform("RandomValue", randomValue);
-	//     shader->setUniform("TimeLapse", timeLapse);
+	if (effect == RoomEffect::Sepia) {
+		Shader *shader = g_engine->getGfx().getShader();
+		shader->setUniform("RandomValue", randomValue);
+		shader->setUniform("TimeLapse", timeLapse);
+	}
 	//   } else if (effect == RoomEffect::Vhs) {
 	//     Shader* shader = g_engine->getGfx().getShader();
 	//     shader->setUniform("iGlobalTime", iGlobalTime);
 	//     shader->setUniform("iNoiseThreshold", iNoiseThreshold);
 	//   } else
-	if (effect == RoomEffect::Ghost) {
+	else if (effect == RoomEffect::Ghost) {
 		Shader *shader = g_engine->getGfx().getShader();
 		shader->setUniform("iGlobalTime", iGlobalTime);
 		shader->setUniform("iFade", iFade);
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 2af471500da..8b748e61c83 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -30,6 +30,7 @@ namespace Twp {
 extern const char* vsrc;
 extern const char* bwShader;
 extern const char* ghostShader;
+extern const char* sepiaShader;
 
 struct ShaderParams {
     RoomEffect effect;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c1bf515d542..39054f87367 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -516,11 +516,10 @@ void TwpEngine::setShaderEffect(RoomEffect effect) {
 	  case RoomEffect::None:
 	    _gfx.use(nullptr);
 		break;
-	//   case RoomEffect::Sepia:
-	//     let shader = newShader(vertexShader, sepiaShader);
-	//     gfxShader(shader);
-	//     shader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
-	// 	break;
+	  case RoomEffect::Sepia: {
+	    _gfx.use(&_sepiaShader);
+	    _sepiaShader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
+	  } break;
 	  case RoomEffect::BlackAndWhite:
 	  	_gfx.use(&_bwShader);
 		break;
@@ -638,6 +637,7 @@ Common::Error TwpEngine::run() {
 	_hud.init();
 	_bwShader.init(vsrc, bwShader);
 	_ghostShader.init(vsrc, ghostShader);
+	_sepiaShader.init(vsrc, sepiaShader);
 	_fadeShader.reset(new FadeShader());
 
 	_lighting = new Lighting();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 4a20fe13e43..e90f9b4d137 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -226,6 +226,7 @@ private:
 	PathNode _pathNode;
 	Shader _bwShader;
 	Shader _ghostShader;
+	Shader _sepiaShader;
 };
 
 extern TwpEngine *g_engine;


Commit: 022168cd3200fd406c2f058746596ff528057c4a
    https://github.com/scummvm/scummvm/commit/022168cd3200fd406c2f058746596ff528057c4a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add language options

Changed paths:
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h
    engines/twp/hud.cpp
    engines/twp/metaengine.cpp
    engines/twp/prefs.cpp
    engines/twp/resmanager.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index 702d0d88e4d..90e8ddfc263 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -31,6 +31,8 @@
 
 namespace Twp {
 
+static const char *lang_items[] = {"en", "fr", "it", "de", "es"};
+
 TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) : OptionsContainerWidget(boss, name, "TwpGameOptionsDialog", false, domain) {
 	GUI::StaticTextWidget *text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoLabel", _("Video:"));
 	text->setAlign(Graphics::TextAlign::kTextAlignStart);
@@ -54,7 +56,14 @@ TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Comm
 	text->setAlign(Graphics::TextAlign::kTextAlignStart);
 
 	_enableDisplayTextGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Display Text"), _(""));
-	_enableHearVoiceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck2", _("Hear Vocie"), _(""));
+	_enableHearVoiceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck2", _("Hear Voice"), _(""));
+
+	_langGUIDropdown = new GUI::PopUpWidget(widgetsBoss(), "TwpGameOptionsDialog.LangDropDown");
+	_langGUIDropdown->appendEntry(_("English"));
+	_langGUIDropdown->appendEntry(_("French"));
+	_langGUIDropdown->appendEntry(_("Italian"));
+	_langGUIDropdown->appendEntry(_("German"));
+	_langGUIDropdown->appendEntry(_("Spanish"));
 }
 
 void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
@@ -72,6 +81,7 @@ void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Comm
 		.addWidget("ControlsCheck3", "Checkbox")
 		.addWidget("ControlsCheck4", "Checkbox")
 		.addWidget("TextAndSpeechLabel", "OptionsLabel")
+		.addWidget("LangDropDown", "PopUp")
 		.addWidget("TextCheck1", "Checkbox")
 		.addWidget("TextCheck2", "Checkbox");
 
@@ -87,6 +97,15 @@ void TwpOptionsContainerWidget::load() {
 	_enableClassicSentenceGUICheckbox->setState(ConfMan.getBool("hudSentence", _domain));
 	_enableDisplayTextGUICheckbox->setState(ConfMan.getBool("talkiesShowText", _domain));
 	_enableHearVoiceGUICheckbox->setState(ConfMan.getBool("talkiesHearVoice", _domain));
+	Common::String lang = ConfMan.get("language", _domain);
+	int index = 0;
+	for (int i = 0; i < ARRAYSIZE(lang_items); i++) {
+		if (lang == lang_items[i]) {
+			index = i;
+			break;
+		}
+	}
+	_langGUIDropdown->setSelected(index);
 }
 
 bool TwpOptionsContainerWidget::save() {
@@ -98,6 +117,8 @@ bool TwpOptionsContainerWidget::save() {
 	ConfMan.setBool("hudSentence", _enableClassicSentenceGUICheckbox->getState(), _domain);
 	ConfMan.setBool("talkiesShowText", _enableDisplayTextGUICheckbox->getState(), _domain);
 	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
+	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
+	ConfMan.set("language", lang_items[_langGUIDropdown->getSelected()], _domain);
 	return true;
 }
 
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index f7f88701bbd..8ad8922848b 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -47,6 +47,7 @@ private:
 	GUI::CheckboxWidget *_enableClassicSentenceGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableDisplayTextGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableHearVoiceGUICheckbox = nullptr;
+	GUI::PopUpWidget *_langGUIDropdown = nullptr;
 };
 
 }
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index a9dff811f41..34c7cf409f9 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -149,8 +149,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	// draw actor's verbs
 	SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
 	Texture *verbTexture = g_engine->_resManager.texture(verbSheet->meta.image);
-	// let lang = prefs(Lang);
-	Common::String lang = "en";
+	Common::String lang = ConfMan.get("language");
 	bool retroVerbs = ConfMan.getBool("retroVerbs");
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
 
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index e6103c65041..cc7a7bed7ab 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -87,6 +87,7 @@ void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
 	ConfMan.registerDefault("hudSentence", false);
 	ConfMan.registerDefault("talkiesShowText", true);
 	ConfMan.registerDefault("talkiesHearVoice", true);
+	ConfMan.registerDefault("language", "en");
 }
 
 SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
diff --git a/engines/twp/prefs.cpp b/engines/twp/prefs.cpp
index 8f0c830e482..22590161236 100644
--- a/engines/twp/prefs.cpp
+++ b/engines/twp/prefs.cpp
@@ -20,6 +20,8 @@
  */
 
 #include "twp/prefs.h"
+#include "twp/util.h"
+#include "common/config-manager.h"
 
 namespace Twp {
 
@@ -79,28 +81,21 @@ void Preferences::setPrefs(const Common::String &name, bool value) {
 	savePrefs();
 }
 
-bool Preferences::hasPrefs(const Common::String& name) {
-  return _node->asObject().contains(name);
+bool Preferences::hasPrefs(const Common::String &name) {
+	return _node->asObject().contains(name);
 }
 
-Common::JSONValue* Preferences::prefsAsJson(const Common::String& name) {
-  return _node->asObject()[name];
+Common::JSONValue *Preferences::prefsAsJson(const Common::String &name) {
+	return _node->asObject()[name];
 }
 
 void Preferences::savePrefs() {
 	// TODO: savePrefs()
 }
 
-Common::String Preferences::getKey(const Common::String& path){
-  size_t i = path.findLastOf(".");
-  Common::String name = path.substr(0, i);
-  Common::String ext = path.substr(i+1);
-  if (name.hasSuffix("_en")) {
-    // TODO: const Common::String& lang = prefs(Lang);
-	Common::String lang = "en";
-    return Common::String::format("%s_%s%s", name.substr(0, name.size()-4).c_str(), lang.c_str(), ext.c_str());
-  }
-  return path;
+Common::String Preferences::getKey(const Common::String &path) {
+	Common::String t = Twp::replace(path, "_en", "_" + ConfMan.get("language"));
+	return t;
 }
 
 } // namespace Twp
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index f833b0fbe92..9a24808c939 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -35,9 +35,7 @@ Common::String getKey(const char *path) {
 	size_t i = p.findLastOf(".");
 	p = p.substr(0, i);
 	if ((len > 4) && scumm_strnicmp(p.c_str() + 4, "_en", 3) == 0) {
-		// TODO
-		// Common::String lang = prefs(Lang);
-		Common::String lang = "en";
+		Common::String lang = ConfMan.get("language");
 		Common::String filename(path, len - 3);
 		const char *ext = path + i;
 		return Common::String::format("%s_%s%s", filename.c_str(), lang.c_str(), ext);
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 56b1b70ad9e..8f1d7bb2a56 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -32,7 +32,9 @@ void TextDb::parseTsv(Common::SeekableReadStream &stream) {
 		Common::String line = stream.readLine();
 		int pos = line.find('\t', 0);
 		int id = atoi(line.c_str());
-		_texts[id] = line.substr(pos + 1);
+		Common::String s = line.substr(pos + 1);
+		_texts[id] = s;
+		debug("%d: %s", id, _texts[id].c_str());
 	}
 }
 
@@ -41,7 +43,7 @@ Common::String TextDb::getText(int id) {
 	if (_texts.contains(id)) {
 		result = _texts[id];
 		if (result.hasSuffix("#M") || result.hasSuffix("#F"))
-			result = result.substr(0, result.size() - 3);
+			result = result.substr(0, result.size() - 2);
 		// replace \" by ";
 		result = Twp::replace(result, "\\\"", "\"");
 	} else {
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 39054f87367..058f12a3aa9 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -644,9 +644,9 @@ Common::Error TwpEngine::run() {
 
 	_pack.init();
 
-	// TODO: load with selected lang
+	Common::String lang = Common::String::format("ThimbleweedText_%s.tsv", ConfMan.get("language").c_str());
 	GGPackEntryReader entry;
-	entry.open(_pack, "ThimbleweedText_en.tsv");
+	entry.open(_pack, lang);
 	_textDb.parseTsv(entry);
 
 	HSQUIRRELVM v = _vm.get();


Commit: 16ef14aaeb7e128f71285c4e814c37b6e02a41a1
    https://github.com/scummvm/scummvm/commit/16ef14aaeb7e128f71285c4e814c37b6e02a41a1
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add input actions

Changed paths:
    engines/twp/actions.h
    engines/twp/hud.h
    engines/twp/metaengine.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/actions.h b/engines/twp/actions.h
index 81a29947a35..a1a5a9541fd 100644
--- a/engines/twp/actions.h
+++ b/engines/twp/actions.h
@@ -26,6 +26,21 @@ namespace Twp {
 
 enum TwpAction {
 	kSkipCutscene,
+	kSelectActor1,
+	kSelectActor2,
+	kSelectActor3,
+	kSelectActor4,
+	kSelectActor5,
+	kSelectActor6,
+	kSelectChoice1,
+	kSelectChoice2,
+	kSelectChoice3,
+	kSelectChoice4,
+	kSelectChoice5,
+	kSelectChoice6,
+	kSelectPreviousActor,
+	kSelectNextActor,
+	kSkipText
 };
 
 }
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index a715027feec..88c4b9496ef 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -57,8 +57,8 @@ struct Verb {
 struct ActorSlot {
 	VerbUiColors verbUiColors;
 	Verb verbs[22];
-	bool selectable;
-	Object *actor;
+	bool selectable = false;
+	Object *actor = nullptr;
 
 	Verb *getVerb(int id) {
 		for (int i = 0; i < 22; i++) {
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index cc7a7bed7ab..5f41ee1002b 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -19,7 +19,6 @@
  *
  */
 
-
 #include "gui/gui-manager.h"
 #include "gui/widget.h"
 #include "gui/widgets/edittext.h"
@@ -166,10 +165,36 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 
 	Common::Action *act;
 
-	act = new Common::Action("SKIPCUTSCENE", _("Skip cutscene"));
-	act->setCustomEngineActionEvent(Twp::kSkipCutscene);
-	act->addDefaultInputMapping("ESCAPE");
-	engineKeyMap->addAction(act);
+	struct {
+		Common::String name;
+		const Common::U32String desc;
+		Twp::TwpAction action;
+		Common::String input;
+	} actions[] = {
+		{"SKIPCUTSCENE", _("Skip cutscene"), Twp::kSkipCutscene, "ESCAPE"},
+		{"SELECTACTOR1", _("Select Actor 1"), Twp::kSelectActor1, "1"},
+		{"SELECTACTOR2", _("Select Actor 2"), Twp::kSelectActor2, "2"},
+		{"SELECTACTOR3", _("Select Actor 3"), Twp::kSelectActor3, "3"},
+		{"SELECTACTOR4", _("Select Actor 4"), Twp::kSelectActor4, "4"},
+		{"SELECTACTOR5", _("Select Actor 5"), Twp::kSelectActor5, "5"},
+		{"SELECTACTOR6", _("Select Actor 6"), Twp::kSelectActor6, "6"},
+		{"SELECTCHOICE1", _("Select Choice 1"), Twp::kSelectChoice1, "1"},
+		{"SELECTCHOICE2", _("Select Choice 2"), Twp::kSelectChoice2, "2"},
+		{"SELECTCHOICE3", _("Select Choice 3"), Twp::kSelectChoice3, "3"},
+		{"SELECTCHOICE4", _("Select Choice 4"), Twp::kSelectChoice4, "4"},
+		{"SELECTCHOICE5", _("Select Choice 5"), Twp::kSelectChoice5, "5"},
+		{"SELECTCHOICE6", _("Select Choice 6"), Twp::kSelectChoice6, "6"},
+		{"SELECTNEXTACTOR", _("Select Next Actor"), Twp::kSelectNextActor, "0"},
+		{"SELECTPREVACTOR", _("Select Previous Actor"), Twp::kSelectPreviousActor, "9"},
+		{"SKIPTEXT", _("Skip Text"), Twp::kSkipText, "."},
+	};
+
+	for (int i = 0; i < ARRAYSIZE(actions); i++) {
+		act = new Common::Action(actions[i].name.c_str(), actions[i].desc);
+		act->setCustomEngineActionEvent(actions[i].action);
+		act->addDefaultInputMapping(actions[i].input);
+		engineKeyMap->addAction(act);
+	}
 
 	return Common::Keymap::arrayOf(engineKeyMap);
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 058f12a3aa9..b08ade69920 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -386,13 +386,13 @@ void TwpEngine::update(float elapsed) {
 			} else if (_room->_fullscreen == FULLSCREENROOM && _noun1) {
 				// if the object is a door, it has a flag indicating its direction: left, right, front, back
 				int flags = _noun1->getFlags();
-				if ((flags & DOOR_LEFT)==DOOR_LEFT)
+				if ((flags & DOOR_LEFT) == DOOR_LEFT)
 					_inputState.setCursorShape(CursorShape::Left);
-				else if ((flags & DOOR_RIGHT)==DOOR_RIGHT)
+				else if ((flags & DOOR_RIGHT) == DOOR_RIGHT)
 					_inputState.setCursorShape(CursorShape::Right);
-				else if ((flags & DOOR_FRONT)==DOOR_FRONT)
+				else if ((flags & DOOR_FRONT) == DOOR_FRONT)
 					_inputState.setCursorShape(CursorShape::Front);
-				else if ((flags & DOOR_BACK)==DOOR_BACK)
+				else if ((flags & DOOR_BACK) == DOOR_BACK)
 					_inputState.setCursorShape(CursorShape::Back);
 				else
 					_inputState.setCursorShape(CursorShape::Normal);
@@ -512,27 +512,27 @@ void TwpEngine::update(float elapsed) {
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
 	_shaderParams.effect = effect;
-	  switch (effect) {
-	  case RoomEffect::None:
-	    _gfx.use(nullptr);
+	switch (effect) {
+	case RoomEffect::None:
+		_gfx.use(nullptr);
 		break;
-	  case RoomEffect::Sepia: {
-	    _gfx.use(&_sepiaShader);
-	    _sepiaShader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
-	  } break;
-	  case RoomEffect::BlackAndWhite:
-	  	_gfx.use(&_bwShader);
+	case RoomEffect::Sepia: {
+		_gfx.use(&_sepiaShader);
+		_sepiaShader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
+	} break;
+	case RoomEffect::BlackAndWhite:
+		_gfx.use(&_bwShader);
 		break;
-	//   case RoomEffect::Ega:
-	//     gfxShader(newShader(vertexShader, egaShader));
-	// 	break;
-	//   case RoomEffect::Vhs:
-	//     gfxShader(newShader(vertexShader, vhsShader));
-	// 	break;
-	  case RoomEffect::Ghost:
-	    _gfx.use(&_ghostShader);
+		//   case RoomEffect::Ega:
+		//     gfxShader(newShader(vertexShader, egaShader));
+		// 	break;
+		//   case RoomEffect::Vhs:
+		//     gfxShader(newShader(vertexShader, vhsShader));
+		// 	break;
+	case RoomEffect::Ghost:
+		_gfx.use(&_ghostShader);
 		break;
-	  }
+	}
 }
 
 void TwpEngine::draw() {
@@ -680,6 +680,64 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSkipCutscene:
 					skipCutscene();
 					break;
+				case TwpAction::kSelectActor1:
+				case TwpAction::kSelectActor2:
+				case TwpAction::kSelectActor3:
+				case TwpAction::kSelectActor4:
+				case TwpAction::kSelectActor5:
+				case TwpAction::kSelectActor6:
+					if (_dialog.getState() == DialogState::None) {
+						int index = (TwpAction)e.customType - kSelectActor1;
+						ActorSlot *slot = &_hud._actorSlots[index];
+						if (slot->selectable && slot->actor && (slot->actor->_room->_name != "Void")) {
+							setActor(slot->actor, true);
+						}
+					}
+					break;
+				case TwpAction::kSelectPreviousActor:
+					if (_actor) {
+						Common::Array<Object *> actors;
+						for (int i = 0; i < NUMACTORS; i++) {
+							ActorSlot *slot = &_hud._actorSlots[i];
+							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
+								actors.push_back(slot->actor);
+							}
+						}
+						int index = find(actors, _actor) - 1;
+						if (index < 0)
+							index += actors.size();
+						setActor(actors[index], true);
+					}
+					break;
+				case TwpAction::kSelectNextActor:
+					if (_actor) {
+						Common::Array<Object *> actors;
+						for (int i = 0; i < NUMACTORS; i++) {
+							ActorSlot *slot = &_hud._actorSlots[i];
+							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
+								actors.push_back(slot->actor);
+							}
+						}
+						int index = find(actors, _actor) + 1;
+						if (index >= actors.size())
+							index -= actors.size();
+						setActor(actors[index], true);
+					}
+					break;
+				case TwpAction::kSkipText:
+					stopTalking();
+					break;
+				case TwpAction::kSelectChoice1:
+				case TwpAction::kSelectChoice2:
+				case TwpAction::kSelectChoice3:
+				case TwpAction::kSelectChoice4:
+				case TwpAction::kSelectChoice5:
+				case TwpAction::kSelectChoice6:
+					if (_dialog.getState() == DialogState::None) {
+						int index = (TwpAction)e.customType - kSelectChoice1;
+						g_engine->_dialog.choose(index);
+					}
+					break;
 				}
 				break;
 			} break;
@@ -1195,9 +1253,9 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	ActorSlot* slot = _hud.actorSlot(actor);
-	Verb* verb = slot->getVerb(verbId.id);
-	Common::String verbFuncName = verb ? verb->fun: slot->getVerb(0)->fun;
+	ActorSlot *slot = _hud.actorSlot(actor);
+	Verb *verb = slot->getVerb(verbId.id);
+	Common::String verbFuncName = verb ? verb->fun : slot->getVerb(0)->fun;
 	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable


Commit: 4f88b8e0265ae3ac8919451b8ab28079997a6761
    https://github.com/scummvm/scummvm/commit/4f88b8e0265ae3ac8919451b8ab28079997a6761
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add hotspot marker

Changed paths:
    engines/twp/actions.h
    engines/twp/metaengine.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actions.h b/engines/twp/actions.h
index a1a5a9541fd..58a92e8e5f6 100644
--- a/engines/twp/actions.h
+++ b/engines/twp/actions.h
@@ -40,7 +40,8 @@ enum TwpAction {
 	kSelectChoice6,
 	kSelectPreviousActor,
 	kSelectNextActor,
-	kSkipText
+	kSkipText,
+	kShowHotspots
 };
 
 }
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 5f41ee1002b..8a09dbed78f 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -187,6 +187,7 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 		{"SELECTNEXTACTOR", _("Select Next Actor"), Twp::kSelectNextActor, "0"},
 		{"SELECTPREVACTOR", _("Select Previous Actor"), Twp::kSelectPreviousActor, "9"},
 		{"SKIPTEXT", _("Skip Text"), Twp::kSkipText, "."},
+		{"SHOWHOTSPOTS", _("Show hotspots"), Twp::kShowHotspots, "TAB"},
 	};
 
 	for (int i = 0; i < ARRAYSIZE(actions); i++) {
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 7d371067b5e..e944a2ec20e 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -718,4 +718,36 @@ bool NoOverrideNode::update(float elapsed) {
 	return true;
 }
 
+HotspotMarkerNode::HotspotMarkerNode() : Node("HotspotMarker") {
+	_zOrder = -1000;
+	_visible = false;
+}
+
+HotspotMarkerNode::~HotspotMarkerNode() {}
+
+void HotspotMarkerNode::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
+	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
+	trsf.translate(pos);
+	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+}
+
+void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
+	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheetFrame *frame = &gameSheet->frameTable["hotspot_marker"];
+	Color color = Color::create(255, 165, 0);
+	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+		Layer *layer = g_engine->_room->_layers[i];
+		for (int j = 0; j < layer->_objects.size(); j++) {
+			Object *obj = layer->_objects[j];
+			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
+				Math::Vector2d pos = g_engine->roomToScreen(obj->_node->getAbsPos());
+				Math::Matrix4 t;
+				t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+				drawSprite(*frame, texture, color, t);
+			}
+		}
+	}
+}
+
 } // namespace Twp
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index f614f22cca3..f5d5023032a 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -353,6 +353,16 @@ private:
 	float _elapsed = 0.f;
 };
 
+class HotspotMarkerNode: public Node {
+public:
+	HotspotMarkerNode();
+	virtual ~HotspotMarkerNode();
+
+private:
+	void drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
+	void drawCore(Math::Matrix4 trsf) override final;
+};
+
 } // End of namespace Twp
 
 #endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index b08ade69920..3b89bca2b6a 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -63,6 +63,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_screenScene.setName("Screen");
 	// _scene.addChild(&_walkboxNode);
 	// _screenScene.addChild(&_pathNode);
+	_screenScene.addChild(&_hotspotMarker);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
 	_screenScene.addChild(&_dialog);
@@ -735,9 +736,12 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectChoice6:
 					if (_dialog.getState() == DialogState::None) {
 						int index = (TwpAction)e.customType - kSelectChoice1;
-						g_engine->_dialog.choose(index);
+						_dialog.choose(index);
 					}
 					break;
+				case TwpAction::kShowHotspots:
+					_hotspotMarker.setVisible(!_hotspotMarker.isVisible());
+					break;
 				}
 				break;
 			} break;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index e90f9b4d137..3fbf34ba123 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -215,6 +215,7 @@ public:
 	AudioSystem _audio;
 	SaveGameManager _saveGameManager;
 	ShaderParams _shaderParams;
+	HotspotMarkerNode _hotspotMarker;
 
 private:
 	Gfx _gfx;


Commit: a5d90e24e01c5cb2a0951942cb446efc63c95be1
    https://github.com/scummvm/scummvm/commit/a5d90e24e01c5cb2a0951942cb446efc63c95be1
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add jiggleObject

Changed paths:
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index c4e9b7792ac..31608ec71b6 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -317,7 +317,8 @@ void Talking::update(float elapsed) {
 }
 
 int Talking::loadActorSpeech(const Common::String &name) {
-	if(!ConfMan.getBool("talkiesHearVoice")) return 0;
+	if (!ConfMan.getBool("talkiesHearVoice"))
+		return 0;
 
 	debug("loadActorSpeech %s.ogg", name.c_str());
 	Common::String filename(name);
@@ -356,7 +357,7 @@ void Talking::say(const Common::String &text) {
 		}
 
 		// TODO: call sayingLine
-		if(_obj->_sound) {
+		if (_obj->_sound) {
 			g_engine->_audio.stop(_obj->_sound);
 		}
 		_obj->_sound = loadActorSpeech(name);
@@ -446,4 +447,14 @@ Common::String Talking::talkieKey() {
 	return result;
 }
 
+Jiggle::Jiggle(Node *node, float amount) : _amount(amount), _node(node) {
+}
+
+Jiggle::~Jiggle() {}
+
+void Jiggle::update(float elapsed) {
+	_jiggleTime += 20.f * elapsed;
+	_node->setRotationOffset(_amount * sin(_jiggleTime));
+}
+
 } // namespace Twp
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index e747b43a1d9..ef874bd225c 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -208,7 +208,7 @@ public:
 	WalkTo(Object *obj, Math::Vector2d dest, int facing = 0);
 	virtual void disable() override;
 
-	const Common::Array<Math::Vector2d>& getPath() const { return _path; }
+	const Common::Array<Math::Vector2d> &getPath() const { return _path; }
 
 private:
 	void actorArrived();
@@ -224,7 +224,7 @@ private:
 // Creates a talking animation for a specified object.
 class Talking : public Motor {
 public:
-	Talking(Object* obj, const Common::StringArray& texts, Color color);
+	Talking(Object *obj, const Common::StringArray &texts, Color color);
 	virtual ~Talking() {}
 
 private:
@@ -232,9 +232,9 @@ private:
 	virtual void disable() override;
 	int onTalkieId(int id);
 	Common::String talkieKey();
-	void setDuration(const Common::String& text);
-	void say(const Common::String& text);
-	int loadActorSpeech(const Common::String& name);
+	void setDuration(const Common::String &text);
+	void say(const Common::String &text);
+	int loadActorSpeech(const Common::String &name);
 
 private:
 	Object *_obj = nullptr;
@@ -242,11 +242,24 @@ private:
 	Lip _lip;
 	float _elapsed = 0.f;
 	float _duration = 0.f;
-	//   SoundId soundId;
 	Color _color;
 	Common::StringArray _texts;
 };
 
+class Jiggle : public Motor {
+public:
+	Jiggle(Node *node, float amount);
+	virtual ~Jiggle();
+
+private:
+	virtual void update(float elapsed) override;
+
+private:
+	Node *_node = nullptr;
+	float _amount = 0.f;
+	float _jiggleTime = 0.f;
+};
+
 } // namespace Twp
 
 #endif
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 895a6fba40c..9a4bdaa7982 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -514,7 +514,6 @@ void Object::setReach(Motor *reach) { SET_MOTOR(reach); }
 void Object::setTalking(Motor *talking) { SET_MOTOR(talking); }
 void Object::setTurnTo(Motor *turnTo) { SET_MOTOR(turnTo); }
 void Object::setShakeTo(Motor *shakeTo) { SET_MOTOR(shakeTo); }
-void Object::setJiggleTo(Motor *jiggleTo) { SET_MOTOR(jiggleTo); }
 
 void Object::update(float elapsedSec) {
 	if (_dependentObj)
@@ -745,6 +744,10 @@ void Object::turn(Object *obj) {
 	setFacing(facing);
 }
 
+void Object::jiggle(float amount) {
+  _jiggleTo = new Jiggle(_node, amount);
+}
+
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
 	obj->setTalking(new Talking(obj, texts, _color));
 }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 5620503ee09..3f00e261de2 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -194,7 +194,6 @@ public:
 	void setBlink(Motor *blink);
 	void setTurnTo(Motor *turnTo);
 	void setShakeTo(Motor *shakeTo);
-	void setJiggleTo(Motor *jiggleTo);
 
 	Motor *getTalking() { return _talking; }
 	void stopTalking();
@@ -205,6 +204,7 @@ public:
 	void execVerb();
 	void turn(Facing facing);
 	void turn(Object* obj);
+	void jiggle(float amount);
 
 private:
 	Common::String suffix() const;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 37357dd7153..43a59fdde95 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -215,9 +215,21 @@ static SQInteger jiggleInventory(HSQUIRRELVM v) {
 	return 0;
 }
 
+// Rotate the object around its origin back and forth by the specified amount of pixels.
+// See also:
+// - `shakeObject`
+// - `stopObjectMotors`
+//
+// .. code-block:: Squirrel
+// jiggleObject(pigeonVan, 0.25)
 static SQInteger jiggleObject(HSQUIRRELVM v) {
-	// TODO: jiggleObject
-	warning("jiggleObject not implemented");
+	Object *obj = sqobj(v, 2);
+	if (!obj)
+		return sq_throwerror(v, "failed to get object");
+	float amount;
+	if (SQ_FAILED(sqget(v, 3, amount)))
+		return sq_throwerror(v, "failed to get amount");
+	obj->jiggle(amount);
 	return 0;
 }
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index f5d5023032a..175484a60ae 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -97,7 +97,10 @@ public:
 	virtual int getZSort() const { return _zOrder; }
 
 	void setRotation(float rotation) { _rotation = rotation; }
-	virtual float getRotation() const { return _rotation; }
+	float getRotation() const { return _rotation; }
+
+	void setRotationOffset(float rotationOffset) { _rotationOffset = rotationOffset; }
+	float getRotationOffset() const { return _rotationOffset; }
 
 	void setAnchor(Math::Vector2d anchor);
 	void setAnchorNorm(Math::Vector2d anchorNorm);


Commit: d32400501e0dcbecdf5a2359c1ee782a3b044d49
    https://github.com/scummvm/scummvm/commit/d32400501e0dcbecdf5a2359c1ee782a3b044d49
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix not compiling on Linux

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/audio.cpp
    engines/twp/btea.cpp
    engines/twp/btea.h
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/motor.h
    engines/twp/resmanager.cpp
    engines/twp/savegame.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 28cc4498a7c..e8ad3014849 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -1038,7 +1038,18 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	sqgetf(table, "dialogHighlight", dialogHighlight);
 
 	g_engine->_hud._actorSlots[actorSlot - 1].verbUiColors =
-		VerbUiColors{.sentence = Color::rgb(sentence), .verbNormal = Color::rgb(verbNormal), .verbNormalTint = Color::rgb(verbNormalTint), .verbHighlight = Color::rgb(verbHighlight), .verbHighlightTint = Color::rgb(verbHighlightTint), .inventoryFrame = Color::rgb(inventoryFrame), .inventoryBackground = Color::rgb(inventoryBackground), .retroNormal = Color::rgb(retroNormal), .retroHighlight = Color::rgb(retroHighlight), .dialogNormal = Color::rgb(dialogNormal), .dialogHighlight = Color::rgb(dialogHighlight)};
+		VerbUiColors(
+			Color::rgb(sentence),
+			Color::rgb(verbNormal), 
+			Color::rgb(verbNormalTint), 
+			Color::rgb(verbHighlight), 
+			Color::rgb(verbHighlightTint),
+			Color::rgb(dialogNormal),
+			Color::rgb(dialogHighlight),
+			Color::rgb(inventoryFrame), 
+			Color::rgb(inventoryBackground), 
+			Color::rgb(retroNormal), 
+			Color::rgb(retroHighlight));
 	return 0;
 }
 
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 9a8cf4a5bbd..ae30948ac1b 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -206,8 +206,12 @@ int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int
 	const Common::String &name = sndDef->getName();
 	Audio::SeekableAudioStream *audioStream;
 	if (name.hasSuffixIgnoreCase(".ogg")) {
+#ifdef USE_VORBIS
 		slot->stream.open(sndDef);
 		audioStream = Audio::makeVorbisStream(&slot->stream, DisposeAfterUse::NO);
+#else
+		audioStream = nullptr;
+#endif
 	} else if (name.hasSuffixIgnoreCase(".wav")) {
 		slot->stream.open(sndDef);
 		audioStream = Audio::makeWAVStream(&slot->stream, DisposeAfterUse::NO);
diff --git a/engines/twp/btea.cpp b/engines/twp/btea.cpp
index 619fcc71d31..3d3937c315d 100644
--- a/engines/twp/btea.cpp
+++ b/engines/twp/btea.cpp
@@ -26,17 +26,17 @@ namespace Twp {
 #define DELTA 0x9e3779b9
 #define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
 
-void BTEACrypto::encrypt(uint32_t *v, int n, const uint32_t *key) {
+void BTEACrypto::encrypt(uint32 *v, int n, const uint32 *key) {
 	btea(v, n, key);
 }
 
-void BTEACrypto::decrypt(uint32_t *v, int n, const uint32_t *key) {
+void BTEACrypto::decrypt(uint32 *v, int n, const uint32 *key) {
 	btea(v, -n, key);
 }
 
 // This method comes from https://en.wikipedia.org/wiki/XXTEA
-void BTEACrypto::btea(uint32_t *v, int n, const uint32_t *key) {
-	uint32_t y, z, sum;
+void BTEACrypto::btea(uint32 *v, int n, const uint32 *key) {
+	uint32 y, z, sum;
 	unsigned p, rounds, e;
 	if (n > 1) { /* Coding Part */
 		rounds = 6 + 52 / n;
@@ -45,7 +45,7 @@ void BTEACrypto::btea(uint32_t *v, int n, const uint32_t *key) {
 		do {
 			sum += DELTA;
 			e = (sum >> 2) & 3;
-			for (p = 0; p < static_cast<uint32_t>(n - 1); p++) {
+			for (p = 0; p < static_cast<uint32>(n - 1); p++) {
 				y = v[p + 1];
 				z = v[p] += MX;
 			}
diff --git a/engines/twp/btea.h b/engines/twp/btea.h
index e41b7a01870..ef9d3018b1f 100644
--- a/engines/twp/btea.h
+++ b/engines/twp/btea.h
@@ -28,13 +28,13 @@ namespace Twp {
 
 class BTEACrypto {
 public:
-  static void encrypt(uint32_t *v, int n, const uint32_t *k);
-  static void decrypt(uint32_t *v, int n, const uint32_t *k);
+  static void encrypt(uint32 *v, int n, const uint32 *k);
+  static void decrypt(uint32 *v, int n, const uint32 *k);
 
 private:
-  static void btea(uint32_t *v, int n, const uint32_t *k);
+  static void btea(uint32 *v, int n, const uint32 *k);
 };
 
 } // End of namespace Twp
 
-#endif // TWP_H
+#endif // TWP_BTEA_H
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 5edf56d63a1..aa5ea22fb8b 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -70,7 +70,13 @@ CondStateVisitor::CondStateVisitor(Dialog *dlg, DialogSelMode mode) : _dlg(dlg),
 }
 
 DialogConditionState CondStateVisitor::createState(int line, DialogConditionMode mode) {
-	return DialogConditionState{.mode = mode, .line = line, .dialog = _dlg->_context.dialogName, .actorKey = _dlg->_context.actor};
+	return DialogConditionState(mode, _dlg->_context.actor, _dlg->_context.dialogName, line);
+}
+
+DialogConditionState::DialogConditionState() {}
+
+DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String& k, const Common::String& dlg, int ln) 
+: mode(m), actorKey(k), dialog(dlg), line(ln) {
 }
 
 void CondStateVisitor::visit(const YOnce &node) {
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 7a787d77046..40c1ae832da 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -79,6 +79,9 @@ struct DialogConditionState {
 	DialogConditionMode mode;
 	Common::String actorKey, dialog;
 	int line;
+
+    DialogConditionState();
+	DialogConditionState(DialogConditionMode mode, const Common::String& actorKey, const Common::String& dialog, int line);
 };
 
 class DialogTarget {
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 0441c6b429a..349ac075384 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -719,7 +719,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 	debug("setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
 	VerbId verbId;
 	verbId.id = id;
-	g_engine->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb{.id = verbId, .image = image, .fun = fun, .text = text, .key = key, .flags = flags};
+	g_engine->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
 	return 0;
 }
 
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index a41ea38363e..1b8282ee141 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -385,10 +385,10 @@ void Gfx::drawQuad(Math::Vector2d size, Color color, Math::Matrix4 trsf) {
 	float x = 0;
 	float y = 0;
 	Vertex vertices[] = {
-		Vertex{.pos = {x + w, y + h}, .texCoords = {1, 0}, .color = color},
-		Vertex{.pos = {x + w, y}, .texCoords = {1, 1}, .color = color},
-		Vertex{.pos = {x, y}, .texCoords = {0, 1}, .color = color},
-		Vertex{.pos = {x, y + h}, .texCoords = {0, 0}, .color = color}};
+		Vertex{{x + w, y + h}, color, {1, 0}},
+		Vertex{{x + w, y}, color, {1, 1}},
+		Vertex{{x, y}, color, {0, 1}},
+		Vertex{{x, y + h}, color, {0, 0}}};
 	noTexture();
 	uint32 quadIndices[] = {
 		0, 1, 3,
@@ -408,10 +408,10 @@ void Gfx::drawSprite(Common::Rect textRect, Texture &texture, Color color, Math:
 
 	Math::Vector2d pos;
 	Vertex vertices[] = {
-		{.pos = {pos.getX() + textRect.width(), pos.getY() + textRect.height()}, .texCoords = {r, t}, .color = color},
-		{.pos = {pos.getX() + textRect.width(), pos.getY()}, .texCoords = {r, b}, .color = color},
-		{.pos = {pos.getX(), pos.getY()}, .texCoords = {l, b}, .color = color},
-		{.pos = {pos.getX(), pos.getY() + textRect.height()}, .texCoords = {l, t}, .color = color}};
+		{{pos.getX() + textRect.width(), pos.getY() + textRect.height()}, color, {r, t}},
+		{{pos.getX() + textRect.width(), pos.getY()}, color, {r, b}},
+		{{pos.getX(), pos.getY()}, color, {l, b}},
+		{{pos.getX(), pos.getY() + textRect.height()}, color, {l, t}}};
 	uint32 quadIndices[] = {
 		0, 1, 3,
 		1, 2, 3};
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 34c7cf409f9..28b10cc48ec 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -28,6 +28,20 @@
 
 namespace Twp {
 
+Verb::Verb() {}
+
+Verb::Verb(VerbId verbId, const Common::String& img, const Common::String& f, const Common::String& t, const Common::String& k, int fl)
+: id(verbId), image(img), fun(f), text(t), key(k), flags(fl) {
+}
+
+VerbUiColors::VerbUiColors() {}
+
+VerbUiColors::VerbUiColors(Color s, Color vbNormal, Color vbNormalTint, Color vbHiglight, Color vbHiglightTint, Color dlgNormal, Color dlgHighlt, Color invFrame, Color inventoryBack, Color retroNml, Color retroHighlt)
+: sentence(s), verbNormal(vbNormal), verbNormalTint(vbNormalTint), verbHighlight(vbHiglight), verbHighlightTint(vbHiglightTint), dialogNormal(dlgNormal), dialogHighlight(dlgHighlt), inventoryFrame(invFrame), inventoryBackground(inventoryBack), retroNormal(retroNml), retroHighlight(retroHighlt) {
+}
+
+ActorSlot::ActorSlot() {}
+
 HudShader::HudShader() {
 }
 
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 88c4b9496ef..2810fe03cef 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -43,6 +43,9 @@ struct VerbUiColors {
 	Color inventoryBackground;
 	Color retroNormal;
 	Color retroHighlight;
+
+	VerbUiColors();
+	VerbUiColors(Color s, Color vbNormal, Color vbNormalTint, Color vbHiglight, Color vbHiglightTint, Color dlgNormal, Color dlgHighlt, Color invFrame, Color inventoryBack, Color retroNml, Color retroHighlt);
 };
 
 struct Verb {
@@ -52,14 +55,21 @@ struct Verb {
 	Common::String text;
 	Common::String key;
 	int flags;
+
+	Verb();
+	Verb(VerbId id, const Common::String& image, const Common::String& fun, const Common::String& text, const Common::String& key, int flags = 0);
 };
 
 struct ActorSlot {
+public:
 	VerbUiColors verbUiColors;
 	Verb verbs[22];
 	bool selectable = false;
 	Object *actor = nullptr;
 
+public:
+	ActorSlot();
+
 	Verb *getVerb(int id) {
 		for (int i = 0; i < 22; i++) {
 			if (verbs[i].id.id == id) {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index ef874bd225c..f3c6b30597d 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -33,8 +33,8 @@ namespace Twp {
 template<typename T>
 struct Tween {
 public:
-	Tween(T f, T t, float duration, InterpolationMethod im)
-		: frm(f), to(t), delta(t - f), duration(duration), value(f), easing_f(easing(im.kind)), swing(im.swing), loop(im.loop) {
+	Tween(T f, T t, float d, InterpolationMethod im)
+		: frm(f), to(t), delta(t - f), duration(d), value(f), easing_f(easing(im.kind)), swing(im.swing), loop(im.loop) {
 	}
 
 	bool running() {
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 9a24808c939..1d6b2b757b4 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -46,10 +46,15 @@ Common::String getKey(const char *path) {
 void ResManager::loadTexture(const Common::String &name) {
 	debug("Load texture %s", name.c_str());
 	GGPackEntryReader r;
-	r.open(g_engine->_pack, name);
+	if(!r.open(g_engine->_pack, name)) {
+		error("Texture %s not found", name.c_str());
+	}
 	Image::PNGDecoder d;
 	d.loadStream(r);
 	const Graphics::Surface *surface = d.getSurface();
+	if(!surface) {
+		error("PNG %s not loaded, please check USE_PNG flag", name.c_str());
+	}
 
 	_textures[name].load(*surface);
 }
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 43d9179ed12..47f50ca0f10 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -407,10 +407,10 @@ bool SaveGameManager::loadGame(const SaveGame& savegame) {
 }
 
 bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame& savegame) {
-	static uint32_t savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
+	static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
 	Common::Array<byte> data(stream->size());
 	stream->read(&data[0], data.size());
-	BTEACrypto::decrypt((uint32_t *)&data[0], data.size() / 4, savegameKey);
+	BTEACrypto::decrypt((uint32 *)&data[0], data.size() / 4, savegameKey);
 	savegame.hashData = *(int32_t *)(&data[data.size() - 16]);
 	savegame.time = *(int32_t *)&data[data.size() - 12];
 	int32_t hashCheck = computeHash(&data[0], data.size() - 16);


Commit: dc52874c073c4f1bd97cfcab63c2356f46136ab0
    https://github.com/scummvm/scummvm/commit/dc52874c073c4f1bd97cfcab63c2356f46136ab0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Try to autodetect TWP games

Changed paths:
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/detection_tables.h
    engines/twp/ggpack.cpp


diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 453fb84b4f1..9fe09c930e3 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -30,16 +30,24 @@
 #include "twp/detection_tables.h"
 
 const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
-	{ Twp::kDebugGraphics, "Graphics", "Graphics debug level" },
-	{ Twp::kDebugPath, "Path", "Pathfinding debug level" },
-	{ Twp::kDebugFilePath, "FilePath", "File path debug level" },
-	{ Twp::kDebugScan, "Scan", "Scan for unrecognised games" },
-	{ Twp::kDebugScript, "Script", "Enable debug script dump" },
-	DEBUG_CHANNEL_END
-};
+	{Twp::kDebugGraphics, "Graphics", "Graphics debug level"},
+	{Twp::kDebugPath, "Path", "Pathfinding debug level"},
+	{Twp::kDebugFilePath, "FilePath", "File path debug level"},
+	{Twp::kDebugScan, "Scan", "Scan for unrecognised games"},
+	{Twp::kDebugScript, "Script", "Enable debug script dump"},
+	DEBUG_CHANNEL_END};
 
 TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(Twp::gameDescriptions,
-	sizeof(ADGameDescription), Twp::twpGames) {
+																			   sizeof(ADGameDescription), Twp::twpGames) {
+}
+
+ADDetectedGame TwpMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
+	for (auto it = allFiles.begin(); it != allFiles.end(); it++) {
+		if (it->_key.hasSuffix(".ggpack1")) {
+			return ADDetectedGame(Twp::gameDescriptions);
+		}
+	}
+	return ADDetectedGame();
 }
 
 REGISTER_PLUGIN_STATIC(TWP_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TwpMetaEngineDetection);
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index 9c07520127e..af490ef77e2 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -64,6 +64,9 @@ public:
 	const DebugChannelDef *getDebugChannels() const override {
 		return debugFlagList;
 	}
+
+private:
+	virtual ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra = nullptr) const override;
 };
 
 #endif // TWP_DETECTION_H
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index f0e603c56a7..1d4578e0d05 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -30,7 +30,7 @@ const ADGameDescription gameDescriptions[] = {
 	{
 		"twp",
 		nullptr,
-		AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
+		AD_ENTRY1("ThimbleweedPark.ggpack1", nullptr),
 		Common::UNK_LANG,
 		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 63bbef029be..99bc0d8cf41 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -453,13 +453,12 @@ GGHashMapDecoder::GGHashMapDecoder()
 Common::JSONValue *GGHashMapDecoder::open(Common::SeekableReadStream *s) {
 	uint32 signature = s->readUint32LE();
 	if (signature != 0x04030201) {
-		error("This version of package is not supported (yet?)");
-		return new Common::JSONValue();
+		return nullptr;
 	}
 	_stream = s;
-	uint32 numEntries = s->readUint32LE();
+	(void)s->readUint32LE();
 	if (!_readPlo(s, _offsets))
-		error("This version of package is not supported (yet?)");
+		return nullptr;
 	return readHash();
 }
 
@@ -605,6 +604,8 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 	ms.open(buffer.data(), entriesSize);
 	GGHashMapDecoder tblDecoder;
 	Common::JSONValue *value = tblDecoder.open(&ms);
+	if(!value) return false;
+
 	const Common::JSONObject &obj = value->asObject();
 	const Common::JSONArray &files = obj["files"]->asArray();
 	for (size_t i = 0; i < files.size(); i++) {
@@ -690,21 +691,37 @@ uint32 GGBnutReader::read(void *dataPtr, uint32 dataSize) {
 bool GGBnutReader::eos() const { return _s->eos(); }
 
 void GGPackSet::init() {
-	XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	// XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+	// try to auto-detect which XOR key to use to decrypt the resources of the game
+	const XorKey key1{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+	const XorKey key2{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+	const XorKey key3{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+	const XorKey key4{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+
+	const XorKey keys[]{key1, key2, key3, key4};
+	const char *key_names[]{"56ad", "566d", "5b6d", "5bad"};
 
 	Common::ArchiveMemberList fileList;
 	SearchMan.listMatchingMembers(fileList, "*.ggpack*");
-	for (auto it = fileList.begin(); it != fileList.end(); ++it) {
-		const Common::ArchiveMember &m = **it;
-		Common::SeekableReadStream *stream = m.createReadStream();
-		GGPackDecoder pack;
-		if (stream && pack.open(stream, key)) {
-			_packs.push_back(pack);
+
+	for (int i = 0; i < ARRAYSIZE(keys); i++) {
+		const XorKey *key = &keys[i];
+		for (auto it = fileList.begin(); it != fileList.end(); ++it) {
+			const Common::ArchiveMember &m = **it;
+			Common::SeekableReadStream *stream = m.createReadStream();
+			GGPackDecoder pack;
+			if (stream && pack.open(stream, *key)) {
+				_packs.push_back(pack);
+			}
+		}
+
+		if (_packs.size() > 0) {
+			// the game has been detected because we have at least 1 ggpack file.
+			debug("Thimbleweed Park detected with key %s", key_names[i]);
+			return;
 		}
 	}
+
+	error("This version of the game is invalid or not supported (yet?)");
 }
 
 bool GGPackSet::assetExists(const char *asset) {


Commit: e729524698677b4827e41275b9173f29417bc833
    https://github.com/scummvm/scummvm/commit/e729524698677b4827e41275b9173f29417bc833
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add sound panning

Changed paths:
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/motor.cpp
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index ae30948ac1b..ce741e4d0e0 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -28,6 +28,7 @@
 #include "twp/audio.h"
 #include "twp/twp.h"
 #include "twp/ids.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
@@ -154,6 +155,28 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 			return;
 		}
 	}
+	if (slot->objId) {
+		Object *obj = sqobj(slot->objId);
+		if (obj) {
+			float volObj = 0.f;
+			if (obj->_room == g_engine->_room) {
+				float width = g_engine->_room->getScreenSize().getX();
+				float x = g_engine->cameraPos().getX();
+				float diff = abs(x - obj->_node->getAbsPos().getX());
+				if (diff > (1.5f * width)) {
+					volObj = 0.f;
+				} else if (diff < (0.25f * width)) {
+					volObj = 1.f;
+				} else {
+					volObj = (width - (diff - (0.25f * width))) / width;
+				}
+
+				float pan = clamp((obj->_node->getAbsPos().getX() - x) / (width / 2), -1.0f, 1.0f);
+				g_engine->_mixer->setChannelBalance(slot->handle, (int8)(pan * 127));
+			}
+			vol *= volObj;
+		}
+	}
 	g_engine->_mixer->setChannelVolume(slot->handle, vol * Audio::Mixer::kMaxChannelVolume);
 }
 
@@ -225,6 +248,7 @@ int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int
 	}
 	g_engine->_mixer->playStream(cat, &slot->handle, audioStream, id, vol * _masterVolume);
 	slot->id = id;
+	slot->objId = objId;
 	slot->sndDef = sndDef;
 	slot->busy = true;
 	slot->volume = volume;
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index fe7988cedbf..7fdb7e7c7b4 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -77,15 +77,16 @@ private:
 };
 
 struct AudioSlot {
-	Audio::SoundHandle handle;
-	SoundDefinition *sndDef = nullptr;
-	SoundStream stream;
-	bool busy = false;
-	float volume = 1.f;
-	float fadeInTimeMs = 0.f;
-	float fadeOutTimeMs = 0.f;
+	Audio::SoundHandle handle;			// handle returned when this sound has been played
+	SoundDefinition *sndDef = nullptr;	// sound definition associated to this slot
+	SoundStream stream;					// audio stream
+	bool busy = false;					// is sound active
+	float volume = 1.f;					// actual volume for this slot
+	float fadeInTimeMs = 0.f;			// fade-in time in milliseconds
+	float fadeOutTimeMs = 0.f;			// fade-out time in milliseconds
 	int total = 0;
-	int id = 0;
+	int id = 0;							// unique sound ID
+	int objId = 0;						// object ID or 0 if none
 };
 
 class AudioSystem {
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 31608ec71b6..b8de94564ed 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -330,7 +330,7 @@ int Talking::loadActorSpeech(const Common::String &name) {
 			debug("File %s.ogg not found", name.c_str());
 		} else {
 			g_engine->_audio._soundDefs.push_back(soundDefinition);
-			return g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f, _obj->getId());
+			return g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
 		}
 	}
 	return 0;
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 356082e12d7..b140c388355 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -32,16 +32,17 @@ namespace Twp {
 
 class SoundTrigger : public Trigger {
 public:
-	SoundTrigger(const Common::Array<SoundDefinition *> sounds) : _sounds(sounds) {}
+	SoundTrigger(const Common::Array<SoundDefinition *> sounds, int objId) : _sounds(sounds), _objId(objId) {}
 	virtual ~SoundTrigger() {}
 
 	virtual void trig() override {
 		int i = g_engine->getRandomSource().getRandomNumber(_sounds.size() - 1);
-		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType);
+		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 0.f, _objId);
 	}
 
 private:
 	const Common::Array<SoundDefinition *> _sounds;
+	int _objId;
 };
 
 // Plays a sound at the specified actor's location.
@@ -72,7 +73,7 @@ static SQInteger actorSound(HSQUIRRELVM v) {
 				}
 			}
 
-			Trigger *trigger = new SoundTrigger(sounds);
+			Trigger *trigger = new SoundTrigger(sounds, obj->getId());
 			obj->_triggers[trigNum] = trigger;
 		}
 	}
@@ -141,7 +142,7 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 		g_engine->_audio.stop(obj->_sound);
 	}
 
-	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, obj->getId());
+	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
 	obj->_sound = soundId;
 	sqpush(v, soundId);
 	return 1;
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 6320497906b..df5a5844a4a 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -287,8 +287,7 @@ Room *sqroom(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
-Object *sqobj(HSQOBJECT table) {
-	int id = getId(table);
+Object *sqobj(int id) {
 	for (int i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (getId(actor->_table) == id)
@@ -309,6 +308,11 @@ Object *sqobj(HSQOBJECT table) {
 	return nullptr;
 }
 
+Object *sqobj(HSQOBJECT table) {
+	int id = getId(table);
+	return sqobj(id);
+}
+
 Object *sqobj(HSQUIRRELVM v, int i) {
 	HSQOBJECT table;
 	sq_getstackobj(v, i, &table);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 422ae36bba2..ec19f95f449 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -159,6 +159,7 @@ Room *sqroom(HSQOBJECT table);
 Room *sqroom(HSQUIRRELVM v, int i);
 Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
+Object *sqobj(int i);
 Object *sqactor(HSQOBJECT table);
 Object *sqactor(HSQUIRRELVM v, int i);
 SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);


Commit: 75281a865524611702e49a6d4a74a76c987817aa
    https://github.com/scummvm/scummvm/commit/75281a865524611702e49a6d4a74a76c987817aa
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add Boost Software License

Changed paths:
  A LICENSES/COPYING.BSL


diff --git a/LICENSES/COPYING.BSL b/LICENSES/COPYING.BSL
new file mode 100644
index 00000000000..ae3da63563c
--- /dev/null
+++ b/LICENSES/COPYING.BSL
@@ -0,0 +1,21 @@
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.


Commit: d52b83b83fe8eb3613a04d7a3248526e6eefd3a2
    https://github.com/scummvm/scummvm/commit/d52b83b83fe8eb3613a04d7a3248526e6eefd3a2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add inventory scroll

Changed paths:
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 9a4bdaa7982..daab0a04804 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -748,6 +748,17 @@ void Object::jiggle(float amount) {
   _jiggleTo = new Jiggle(_node, amount);
 }
 
+void Object::inventoryScrollUp() {
+	_inventoryOffset -= 1;
+	if (_inventoryOffset < 0)
+		_inventoryOffset = clamp(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
+}
+
+void Object::inventoryScrollDown() {
+	_inventoryOffset++;
+	_inventoryOffset = clamp(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
+}
+
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
 	obj->setTalking(new Talking(obj, texts, _color));
 }
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 3f00e261de2..91a6ad67513 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -206,6 +206,9 @@ public:
 	void turn(Object* obj);
 	void jiggle(float amount);
 
+	void inventoryScrollUp();
+	void inventoryScrollDown();
+
 private:
 	Common::String suffix() const;
 	// Plays an animation specified by the state
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index e944a2ec20e..9270725d03f 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -609,12 +609,9 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 		if (!_down && down) {
 			_down = true;
 			if (_arrowUpRect.contains(scrPos.getX(), scrPos.getY())) {
-				_actor->_inventoryOffset -= 1;
-				if (_actor->_inventoryOffset < 0)
-					_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
+				_actor->inventoryScrollUp();
 			} else if (_arrowDnRect.contains(scrPos.getX(), scrPos.getY())) {
-				_actor->_inventoryOffset++;
-				_actor->_inventoryOffset = clamp(_actor->_inventoryOffset, 0, ((int)_actor->_inventory.size() - 5) / 4);
+				_actor->inventoryScrollDown();
 			}
 		} else if (!down) {
 			_down = false;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 3b89bca2b6a..d24768142bc 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -774,6 +774,16 @@ Common::Error TwpEngine::run() {
 			case Common::EVENT_RBUTTONUP:
 				_cursor.rightDown = false;
 				break;
+			case Common::EVENT_WHEELDOWN:
+				if (_actor) {
+					_actor->inventoryScrollDown();
+				}
+				break;
+			case Common::EVENT_WHEELUP:
+				if (_actor) {
+					_actor->inventoryScrollUp();
+				}
+				break;
 			default:
 				break;
 			}


Commit: 66a163f7026a9f63a2737fb4933110cadc052449
    https://github.com/scummvm/scummvm/commit/66a163f7026a9f63a2737fb4933110cadc052449
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Actor should walk while mouse is down

Changed paths:
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d24768142bc..32bd1abca81 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -198,12 +198,27 @@ void TwpEngine::flashSelectableActor(int flash) {
 	_actorSwitcher.setFlash(flash);
 }
 
+void TwpEngine::walkFast(bool state) {
+	if (_walkFastState != state) {
+		debug("walk fast: %s", state ? "yes" : "no");
+		_walkFastState = state;
+		if (_actor)
+			sqcall(_actor->_table, "run", state);
+	}
+}
+
 void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 	if (_room && _inputState.getInputActive() && !_actorSwitcher.isMouseOver()) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		Object *obj = objAt(roomPos);
 
-		if (_cursor.isLeftDown()) {
+		if (_cursor.doubleClick) {
+			walkFast(true);
+			_holdToMove = true;
+			return;
+		}
+
+		if (_cursor.leftDown) {
 			// button left: execute selected verb
 			bool handled = clickedAtHandled(roomPos);
 			if (!handled && obj) {
@@ -213,21 +228,22 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 			}
 			if (!handled) {
 				if (_actor && (scrPos.getY() > 172)) {
-					_actor->walk(roomPos);
+					// Just clicking on the ground
+					cancelSentence(_actor);
+					if (_actor->_room == _room)
+						_actor->walk(roomPos);
 					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+					_holdToMove = true;
 				}
 			}
-			// TODO: Just clicking on the ground
-			//     cancelSentence(self.actor)
-		} else if (_cursor.isRightDown()) {
+
+		} else if (_cursor.rightDown) {
 			// button right: execute default verb
 			if (obj) {
 				VerbId verb;
 				verb.id = obj->defaultVerbId();
 				execSentence(nullptr, verb, _noun1, _noun2);
 			}
-		} else if (_walkFastState && _cursor.leftDown && _actor && (scrPos.getY() > 172)) {
-			_actor->walk(roomPos);
 		}
 	}
 }
@@ -412,15 +428,21 @@ void TwpEngine::update(float elapsed) {
 
 			// call clickedAt if any button down
 			if ((_dialog.getState() == DialogState::None) && !_hud.isOver()) {
-				// TODO:
-				// if (_cursor.leftDown) {
-				// 	if mouseDnDur > initDuration(milliseconds = 500):
-				// 		walkFast();
-				// } else {
-				// 	walkFast(false);
-				// }
-				if (_cursor.leftDown || _cursor.rightDown)
+				if (_cursor.isLeftDown() || _cursor.isRightDown()) {
 					clickedAt(scrPos);
+				} else if (_cursor.leftDown || _cursor.rightDown) {
+					if (_holdToMove && (_time > _nextHoldToMoveTime)) {
+						walkFast();
+						cancelSentence(_actor);
+						if (_actor->_room == _room && (distance(_actor->_node->getAbsPos(), roomPos) > 5)) {
+							_actor->walk(roomPos);
+						}
+						_nextHoldToMoveTime = _time + 0.250f;
+					}
+				} else if(!_cursor.doubleClick) {
+					_holdToMove = false;
+					walkFast(false);
+				}
 			}
 		} else {
 			_hud.setVisible(false);
@@ -524,12 +546,12 @@ void TwpEngine::setShaderEffect(RoomEffect effect) {
 	case RoomEffect::BlackAndWhite:
 		_gfx.use(&_bwShader);
 		break;
-		//   case RoomEffect::Ega:
-		//     gfxShader(newShader(vertexShader, egaShader));
-		// 	break;
-		//   case RoomEffect::Vhs:
-		//     gfxShader(newShader(vertexShader, vhsShader));
-		// 	break;
+	case RoomEffect::Ega:
+		// TODO: _gfx.use(&_egaShader);
+		break;
+	case RoomEffect::Vhs:
+		// TODO:_gfx.use(&_vhsShader);
+		break;
 	case RoomEffect::Ghost:
 		_gfx.use(&_ghostShader);
 		break;
@@ -761,14 +783,18 @@ Common::Error TwpEngine::run() {
 				break;
 			case Common::EVENT_LBUTTONDOWN:
 				_cursor.leftDown = true;
-				if (!_cursor.oldLeftDown) {
-					_cursor.mouseDownTime = g_engine->_time;
+				if ((_time - _cursor.leftDownTime) < 1.0f) {
+					_cursor.doubleClick = true;
+				} else {
+					_cursor.doubleClick = false;
 				}
 				break;
 			case Common::EVENT_LBUTTONUP:
 				_cursor.leftDown = false;
+				_cursor.leftDownTime = _time;
 				break;
 			case Common::EVENT_RBUTTONDOWN:
+				_cursor.doubleClick = false;
 				_cursor.rightDown = true;
 				break;
 			case Common::EVENT_RBUTTONUP:
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 3fbf34ba123..a303e3b26a4 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -124,6 +124,7 @@ public:
 	Object *objAt(Math::Vector2d pos);
 	void flashSelectableActor(int flash);
 	void stopTalking();
+	void walkFast(bool state = true);
 
 	Room *defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Room *room);
@@ -178,12 +179,14 @@ public:
 	Object *_actor = nullptr;
 	Object *_followActor = nullptr;
 	Room *_room = nullptr;
-	float _time = 0.f; // time in seconds
+	float _time = 0.f;
 	Object *_noun1 = nullptr;
 	Object *_noun2 = nullptr;
 	UseFlag _useFlag;
 	HSQOBJECT _defaultObj;
 	bool _walkFastState = false;
+	bool _holdToMove = false;
+	float _nextHoldToMoveTime = 0.f;
 	int _frameCounter = 0;
 	Lighting *_lighting = nullptr;
 	Cutscene *_cutscene = nullptr;
@@ -198,9 +201,10 @@ public:
 		Math::Vector2d pos;
 		bool oldLeftDown = false;
 		bool leftDown = false;
+		int leftDownTime = 0;
 		bool oldRightDown = false;
 		bool rightDown = false;
-		float mouseDownTime = 0.f;
+		int doubleClick = false;
 
 		void update() {
 			oldLeftDown = leftDown;


Commit: ef8a563251f3f348c74b7f51447ea945799b013a
    https://github.com/scummvm/scummvm/commit/ef8a563251f3f348c74b7f51447ea945799b013a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Gear icon should show options

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 32bd1abca81..0d51988fcfd 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -321,6 +321,11 @@ static void selectSlotActor(int id) {
 	}
 }
 
+static void showOptions(int id) {
+	if (g_engine && !g_engine->isPaused())
+		g_engine->openMainMenuDialog();
+}
+
 ActorSwitcherSlot TwpEngine::actorSwitcherSlot(ActorSlot *slot) {
 	return ActorSwitcherSlot(slot->actor->getIcon(),
 							 slot->verbUiColors.inventoryBackground,
@@ -345,7 +350,7 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 
 		// TODO: showOptions
 		// add gear icon
-		result.push_back(ActorSwitcherSlot("icon_gear", Color(0.f, 0.f, 0.f), Color(0.8f, 0.8f, 0.8f), selectSlotActor));
+		result.push_back(ActorSwitcherSlot("icon_gear", Color(0.f, 0.f, 0.f), Color(0.8f, 0.8f, 0.8f), showOptions));
 	}
 	return result;
 }
@@ -439,7 +444,7 @@ void TwpEngine::update(float elapsed) {
 						}
 						_nextHoldToMoveTime = _time + 0.250f;
 					}
-				} else if(!_cursor.doubleClick) {
+				} else if (!_cursor.doubleClick) {
 					_holdToMove = false;
 					walkFast(false);
 				}


Commit: c6569b3e9ea746d99fe3bff54d9080217776fbd8
    https://github.com/scummvm/scummvm/commit/c6569b3e9ea746d99fe3bff54d9080217776fbd8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix costume not set in map

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/object.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index e8ad3014849..985b82aa3bc 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -385,21 +385,21 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 		int selectable;
 		if (SQ_FAILED(sqget(v, 2, selectable)))
 			return sq_throwerror(v, "failed to get selectable");
-		switch(selectable) {
+		switch (selectable) {
 		case 0:
-		  g_engine->_actorSwitcher._mode &= (~asOn);
-		  break;
+			g_engine->_actorSwitcher._mode &= (~asOn);
+			break;
 		case 1:
-		  g_engine->_actorSwitcher._mode |= asOn;
-		  break;
+			g_engine->_actorSwitcher._mode |= asOn;
+			break;
 		case 2:
-		  g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
-		  break;
+			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
+			break;
 		case 3:
-		  g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
-		  break;
+			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
+			break;
 		default:
-		  return sq_throwerror(v, "invalid selectable value");
+			return sq_throwerror(v, "invalid selectable value");
 		}
 		return 0;
 	}
@@ -1040,15 +1040,15 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	g_engine->_hud._actorSlots[actorSlot - 1].verbUiColors =
 		VerbUiColors(
 			Color::rgb(sentence),
-			Color::rgb(verbNormal), 
-			Color::rgb(verbNormalTint), 
-			Color::rgb(verbHighlight), 
+			Color::rgb(verbNormal),
+			Color::rgb(verbNormalTint),
+			Color::rgb(verbHighlight),
 			Color::rgb(verbHighlightTint),
 			Color::rgb(dialogNormal),
 			Color::rgb(dialogHighlight),
-			Color::rgb(inventoryFrame), 
-			Color::rgb(inventoryBackground), 
-			Color::rgb(retroNormal), 
+			Color::rgb(inventoryFrame),
+			Color::rgb(inventoryBackground),
+			Color::rgb(retroNormal),
 			Color::rgb(retroHighlight));
 	return 0;
 }
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index daab0a04804..e69271161ea 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -481,6 +481,10 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 
 	GGHashMapDecoder dec;
 	Common::JSONValue *json = dec.open(&entry);
+	if(!json) {
+		warning("Costume %s(%s) for actor %s not found", name.c_str(), sheet.c_str(), _key.c_str());
+		return;
+	}
 	const Common::JSONObject &jCostume = json->asObject();
 
 	parseObjectAnimations(jCostume["animations"]->asArray(), _anims);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0d51988fcfd..a19cf5f56fc 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1093,11 +1093,12 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 }
 
 void TwpEngine::actorEnter() {
-	if (_actor)
+	if (_actor) {
 		sqcall(_actor->_table, "actorEnter");
-	if (_room) {
-		if (sqrawexists(_room->_table, "actorEnter")) {
-			sqcall(_room->_table, "actorEnter", _actor->_table);
+		if (_room) {
+			if (sqrawexists(_room->_table, "actorEnter")) {
+				sqcall(_room->_table, "actorEnter", _actor->_table);
+			}
 		}
 	}
 }
@@ -1154,7 +1155,7 @@ void TwpEngine::setRoom(Room *room) {
 }
 
 void TwpEngine::actorExit() {
-	if (!_actor && _room) {
+	if (_actor && _room) {
 		if (sqrawexists(_room->_table, "actorExit")) {
 			sqcall(_room->_table, "actorExit", _actor->_table);
 		}
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 00ecf12fd27..c15474f66d1 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -124,6 +124,7 @@ Common::Rect parseRect(const Common::String &s) {
 }
 
 void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<ObjectAnimation> &anims) {
+	anims.clear();
 	for (auto it = jAnims.begin(); it != jAnims.end(); it++) {
 		anims.push_back(parseObjectAnimation((*it)->asObject()));
 	}


Commit: a691564fab788ec10fbc385b987a4a50300eb5ae
    https://github.com/scummvm/scummvm/commit/a691564fab788ec10fbc385b987a4a50300eb5ae
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix costume bug

Changed paths:
    engines/twp/scenegraph.cpp


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 9270725d03f..f030f228a0a 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -275,6 +275,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
 	_loop = loop || anim->loop;
 	_instant = instant;
+	if(_obj) setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == -1);
 
 	clear();
 	for (int i = 0; i < _anim->layers.size(); i++) {


Commit: cf2e05e86a32a35015232a3312d5c410d56a3584
    https://github.com/scummvm/scummvm/commit/cf2e05e86a32a35015232a3312d5c410d56a3584
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix polygon merging + walkbox debug

Changed paths:
    engines/twp/room.cpp
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/walkboxnode.cpp
    engines/twp/walkboxnode.h


diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 3bbbfd202f4..4271e887f35 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -132,7 +132,7 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 		ClipperLib::Clipper c;
 		c.AddPaths(subjects, ClipperLib::ptSubject, true);
 		c.AddPaths(clips, ClipperLib::ptClip, true);
-		c.Execute(ClipperLib::ClipType::ctUnion, solutions, ClipperLib::pftEvenOdd);
+		c.Execute(ClipperLib::ClipType::ctDifference, solutions, ClipperLib::pftEvenOdd);
 
 		for (int i = 0; i < solutions.size(); i++) {
 			result.push_back(toWalkbox(solutions[i]));
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index f030f228a0a..7008efaf820 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -425,6 +425,8 @@ Common::String InputState::getCursorName() const {
 		return "cursor_back";
 	case CursorShape::Pause:
 		return "cursor_pause";
+	case CursorShape::Normal:
+		return "cursor";
 	}
 	return "cursor";
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index a19cf5f56fc..00cc2517605 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -61,8 +61,8 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_dialog._tgt.reset(new EngineDialogTarget());
 	sq_resetobject(&_defaultObj);
 	_screenScene.setName("Screen");
-	// _scene.addChild(&_walkboxNode);
-	// _screenScene.addChild(&_pathNode);
+	_scene.addChild(&_walkboxNode);
+	_screenScene.addChild(&_pathNode);
 	_screenScene.addChild(&_hotspotMarker);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
@@ -695,6 +695,7 @@ Common::Error TwpEngine::run() {
 	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
 
 	static int speed = 1;
+	static bool control = false;
 
 	// Simple event handling loop
 	Common::Event e;
@@ -780,9 +781,38 @@ Common::Error TwpEngine::run() {
 				case Common::KEYCODE_RIGHT:
 					speed = MIN(speed + 1, 8);
 					break;
+				case Common::KEYCODE_LCTRL:
+					control = true;
+					break;
+				default:
+					break;
+				}
+				break;
+			case Common::EVENT_KEYUP:
+				switch (e.kbd.keycode) {
+				case Common::KEYCODE_LCTRL:
+					control = false;
+					break;
+				case Common::KEYCODE_w:
+					if (control) {
+						WalkboxMode mode = (WalkboxMode)(((int)_walkboxNode.getMode() + 1) % 3);
+						debug("set walkbox mode to: %s", (mode == WalkboxMode::Merged ? "merged" : mode == WalkboxMode::All ? "all"
+																															: "none"));
+						_walkboxNode.setMode(mode);
+					}
+					break;
+				case Common::KEYCODE_g:
+					if (control) {
+						PathMode mode = (PathMode)(((int)_pathNode.getMode() + 1) % 3);
+						debug("set path mode to: %s", (mode == PathMode::GraphMode ? "graph" : mode == PathMode::All ? "all"
+																													 : "none"));
+						_pathNode.setMode(mode);
+					}
+					break;
 				default:
 					break;
 				}
+				break;
 			case Common::EVENT_MOUSEMOVE:
 				_cursor.pos = Math::Vector2d(e.mouse.x, e.mouse.y);
 				break;
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index dc99bd9f788..37ba983713c 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -26,7 +26,7 @@ namespace Twp {
 
 WalkboxNode::WalkboxNode() : Node("Walkbox") {
 	_zOrder = -1000;
-	_mode = WalkboxMode::Merged;
+	_mode = WalkboxMode::None;
 }
 
 void WalkboxNode::drawCore(Math::Matrix4 trsf) {
@@ -39,18 +39,16 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
 			for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
 				Walkbox &wb = g_engine->_room->_walkboxes[i];
-				if (wb.isVisible()) {
-					Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
-					Common::Array<Vertex> vertices;
-					for (int j = 0; j < wb.getPoints().size(); j++) {
-						Math::Vector2d p = wb.getPoints()[j];
-						Vertex v;
-						v.pos = p;
-						v.color = color;
-						vertices.push_back(v);
-					}
-					g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
+				Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
+				Common::Array<Vertex> vertices;
+				for (int j = 0; j < wb.getPoints().size(); j++) {
+					Math::Vector2d p = wb.getPoints()[j];
+					Vertex v;
+					v.pos = p;
+					v.color = color;
+					vertices.push_back(v);
 				}
+				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
 			}
 		} break;
 		case WalkboxMode::Merged: {
@@ -99,7 +97,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	Color yellow(1.f, 1.f, 0.f);
 	Object *actor = g_engine->_actor;
 	// draw actor path
-	if (actor && actor->getWalkTo()) {
+	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
 		WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
 		const Common::Array<Math::Vector2d> &path = walkTo->getPath();
 		if (path.size() > 0) {
@@ -124,8 +122,8 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	}
 
 	// draw graph nodes
-	const Graph *graph = g_engine->_room->_pathFinder.getGraph();
-	if (graph) {
+	const Twp::Graph *graph = g_engine->_room->_pathFinder.getGraph();
+	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && graph) {
 		for (int i = 0; i < graph->_concaveVertices.size(); i++) {
 			Math::Vector2d v = graph->_concaveVertices[i];
 			Math::Matrix4 t;
@@ -134,20 +132,22 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow);
 		}
 
-		// for (int i = 0; i < graph->_edges.size(); i++) {
-		// 	const Common::Array<GraphEdge> &edges = graph->_edges[i];
-		// 	for (int j = 0; j < edges.size(); j++) {
-		// 		const GraphEdge &edge = edges[j];
-		// 		Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
-		// 		Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
-		// 		Vertex vertices[] = {Vertex{.pos = p1, .color = Color()}, Vertex{.pos = p2, .color = Color()}};
-		// 		g_engine->getGfx().drawLines(&vertices[0], 2);
-		// 	}
-		// }
+		if ((_mode == PathMode::All)) {
+			for (int i = 0; i < graph->_edges.size(); i++) {
+				const Common::Array<GraphEdge> &edges = graph->_edges[i];
+				for (int j = 0; j < edges.size(); j++) {
+					const GraphEdge &edge = edges[j];
+					Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
+					Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
+					Vertex vertices[] = {Vertex{.pos = p1, .color = Color()}, Vertex{.pos = p2, .color = Color()}};
+					g_engine->getGfx().drawLines(&vertices[0], 2);
+				}
+			}
+		}
 	}
 
 	// draw path from actor to mouse position
-	if (actor) {
+	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor) {
 		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getPos()) - Math::Vector2d(2.f, 2.f);
 		Math::Matrix4 t;
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index 3e73bcf8686..99fe2ab3630 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -27,15 +27,18 @@
 namespace Twp {
 
 enum class WalkboxMode {
-    None,
-    Merged,
-    All
+	None,
+	Merged,
+	All
 };
 
 class WalkboxNode : public Node {
 public:
 	WalkboxNode();
 
+	void setMode(WalkboxMode mode) { _mode = mode; }
+	WalkboxMode getMode() const { return _mode; }
+
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override;
 
@@ -43,13 +46,25 @@ private:
 	WalkboxMode _mode;
 };
 
+enum class PathMode {
+	None,
+	GraphMode,
+	All
+};
+
 class PathNode : public Node {
 public:
 	PathNode();
 
+	void setMode(PathMode mode) { _mode = mode; }
+	PathMode getMode() const { return _mode; }
+
 private:
 	Math::Vector2d fixPos(Math::Vector2d pos);
 	virtual void drawCore(Math::Matrix4 trsf) override;
+
+private:
+	PathMode _mode;
 };
 
 } // namespace Twp


Commit: 4b6ac21caf4280329cf1d26c4ba7e283845ea05b
    https://github.com/scummvm/scummvm/commit/4b6ac21caf4280329cf1d26c4ba7e283845ea05b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Display use pos in debug

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/graph.cpp
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 1b8282ee141..073d16b34b6 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -30,6 +30,12 @@ namespace Twp {
 
 static Texture gEmptyTexture;
 
+Vertex::Vertex() {}
+
+Vertex::Vertex(Math::Vector2d p, Color c, Math::Vector2d t)
+	: pos(p), color(c), texCoords(t) {
+}
+
 Math::Matrix4 ortho(float left, float right, float bottom, float top, float zNear, float zFar) {
 	Math::Matrix4 result;
 	float *m = result.getData();
@@ -176,30 +182,30 @@ void Shader::setUniform(const char *name, Math::Matrix4 value) {
 	glUseProgram(prev);
 }
 
-void Shader::setUniform(const char * name, float value) {
-  	GLint prev;
+void Shader::setUniform(const char *name, float value) {
+	GLint prev;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
 	glUseProgram(program);
-    int loc = getUniformLocation(name);
-    GL_CALL(glUniform1f(loc, value));
+	int loc = getUniformLocation(name);
+	GL_CALL(glUniform1f(loc, value));
 	glUseProgram(prev);
 }
 
-void Shader::setUniform(const char * name, Math::Vector3d value) {
-    GLint prev;
+void Shader::setUniform(const char *name, Math::Vector3d value) {
+	GLint prev;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
 	glUseProgram(program);
-    int loc = getUniformLocation(name);
-    GL_CALL(glUniform3fv(loc, 1, value.getData()));
+	int loc = getUniformLocation(name);
+	GL_CALL(glUniform3fv(loc, 1, value.getData()));
 	glUseProgram(prev);
 }
 
-void Shader::setUniform(const char * name, Color value) {
-    GLint prev;
+void Shader::setUniform(const char *name, Color value) {
+	GLint prev;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
 	glUseProgram(program);
-    int loc = getUniformLocation(name);
-    GL_CALL(glUniform3fv(loc, 1, value.v));
+	int loc = getUniformLocation(name);
+	GL_CALL(glUniform3fv(loc, 1, value.v));
 	glUseProgram(prev);
 }
 
@@ -213,8 +219,8 @@ void Gfx::init() {
 	empty.setPixels(pixels);
 	gEmptyTexture.load(empty);
 
-	const char* fragmentSrc = R"(#version 110
-	varying vec4 v_color;
+	const char *fragmentSrc = R"(#version 110
+		varying vec4 v_color;
 	varying vec2 v_texCoords;
 	uniform sampler2D u_texture;
 	void main() {
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index f29fd5911ad..60e07b50342 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -90,10 +90,14 @@ public:
 	Math::Vector2d pos;
 	Color color;
 	Math::Vector2d texCoords;
+
+	Vertex();
+	Vertex(Math::Vector2d p, Color c = Color(), Math::Vector2d t = Math::Vector2d());
 };
 
 class Texture {
 public:
+	Texture() {}
 	virtual ~Texture() {}
 	void load(const Graphics::Surface &surface);
 	static void bind(const Texture *texture);
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 9a064a202f8..d7e5eb07693 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -224,6 +224,7 @@ Common::Array<int> Graph::getPath(int source, int target) {
 
 void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
 	_walkboxes = walkboxes;
+	_graph = nullptr;
 }
 
 // Indicates whether or not the specified position is inside this walkbox.
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 37ba983713c..db6bca7de6f 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -31,22 +31,29 @@ WalkboxNode::WalkboxNode() : Node("Walkbox") {
 
 void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 	if (g_engine->_room) {
+		Color white;
+		Color red(1.f, 0.f, 0.f);
+		Color green(0.f, 1.f, 0.f);
+		Color yellow(1.f, 1.f, 0.f);
 		switch (_mode) {
 		case WalkboxMode::All: {
 			Math::Matrix4 transf;
 			// cancel camera pos
 			Math::Vector2d pos = g_engine->getGfx().cameraPos();
 			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
-			for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
+			for (uint i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
 				Walkbox &wb = g_engine->_room->_walkboxes[i];
-				Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
+				Color color = wb.isVisible() ? green : red;
 				Common::Array<Vertex> vertices;
-				for (int j = 0; j < wb.getPoints().size(); j++) {
+				for (uint j = 0; j < wb.getPoints().size(); j++) {
 					Math::Vector2d p = wb.getPoints()[j];
-					Vertex v;
-					v.pos = p;
-					v.color = color;
-					vertices.push_back(v);
+					vertices.push_back(Vertex(p, color));
+
+					Color vertexColor = wb.concave(j) ? white : yellow;
+					Math::Matrix4 t(transf);
+					p -= Math::Vector2d(2.f, 2.f);
+					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
+					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
 				}
 				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
 			}
@@ -56,16 +63,19 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 			Math::Vector2d pos = g_engine->getGfx().cameraPos();
 			// cancel camera pos
 			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
-			for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
+			for (uint i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
 				Walkbox &wb = g_engine->_room->_mergedPolygon[i];
-				Color color = wb.isVisible() ? Color(0.f, 1.f, 0.f) : Color(1.f, 0.f, 0.f);
+				Color color = wb.isVisible() ? green : red;
 				Common::Array<Vertex> vertices;
-				for (int j = 0; j < wb.getPoints().size(); j++) {
+				for (uint j = 0; j < wb.getPoints().size(); j++) {
 					Math::Vector2d p = wb.getPoints()[j];
-					Vertex v;
-					v.pos = p;
-					v.color = color;
-					vertices.push_back(v);
+					vertices.push_back(Vertex(p, color));
+
+					Color vertexColor = wb.concave(j) ? white : yellow;
+					Math::Matrix4 t(transf);
+					p -= Math::Vector2d(2.f, 2.f);
+					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
+					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
 				}
 				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
 			}
@@ -94,7 +104,9 @@ Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
 }
 
 void PathNode::drawCore(Math::Matrix4 trsf) {
+	Color red(1.f, 0.f, 0.f);
 	Color yellow(1.f, 1.f, 0.f);
+	Color blue(0.f, 0.f, 1.f);
 	Object *actor = g_engine->_actor;
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
@@ -102,15 +114,10 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		const Common::Array<Math::Vector2d> &path = walkTo->getPath();
 		if (path.size() > 0) {
 			Common::Array<Vertex> vertices;
-			Vertex v;
-			v.pos = g_engine->roomToScreen(actor->_node->getPos());
-			v.color = yellow;
-			vertices.push_back(v);
-			for (int i = 0; i < path.size(); i++) {
+			vertices.push_back(Vertex(g_engine->roomToScreen(actor->_node->getPos()), yellow));
+			for (uint i = 0; i < path.size(); i++) {
 				Math::Vector2d p = g_engine->roomToScreen(path[i]);
-				v.pos = p;
-				v.color = yellow;
-				vertices.push_back(v);
+				vertices.push_back(Vertex(p, yellow));
 
 				Math::Matrix4 t;
 				p -= Math::Vector2d(2.f, 2.f);
@@ -124,7 +131,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	// draw graph nodes
 	const Twp::Graph *graph = g_engine->_room->_pathFinder.getGraph();
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && graph) {
-		for (int i = 0; i < graph->_concaveVertices.size(); i++) {
+		for (uint i = 0; i < graph->_concaveVertices.size(); i++) {
 			Math::Vector2d v = graph->_concaveVertices[i];
 			Math::Matrix4 t;
 			Math::Vector2d p = g_engine->roomToScreen(v) - Math::Vector2d(2.f, 2.f);
@@ -132,14 +139,14 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow);
 		}
 
-		if ((_mode == PathMode::All)) {
-			for (int i = 0; i < graph->_edges.size(); i++) {
+		if (_mode == PathMode::All) {
+			for (uint i = 0; i < graph->_edges.size(); i++) {
 				const Common::Array<GraphEdge> &edges = graph->_edges[i];
-				for (int j = 0; j < edges.size(); j++) {
+				for (uint j = 0; j < edges.size(); j++) {
 					const GraphEdge &edge = edges[j];
 					Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
 					Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
-					Vertex vertices[] = {Vertex{.pos = p1, .color = Color()}, Vertex{.pos = p2, .color = Color()}};
+					Vertex vertices[] = {Vertex(p1), Vertex(p2)};
 					g_engine->getGfx().drawLines(&vertices[0], 2);
 				}
 			}
@@ -161,13 +168,18 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
 
+		Object* obj = g_engine->objAt(roomPos);
+		if(obj) {
+			t = Math::Matrix4();
+			pos = g_engine->roomToScreen(obj->getUsePos()) - Math::Vector2d(4.f, 4.f);
+			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+			g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), red, t);
+		}
+
 		Common::Array<Math::Vector2d> path = g_engine->_room->calculatePath(fixPos(actor->_node->getPos()), p);
 		Common::Array<Vertex> vertices;
-		for (int i = 0; i < path.size(); i++) {
-			Vertex v;
-			v.pos = g_engine->roomToScreen(path[i]);
-			v.color = yellow;
-			vertices.push_back(v);
+		for (uint i = 0; i < path.size(); i++) {
+			vertices.push_back(Vertex(g_engine->roomToScreen(path[i]), yellow));
 		}
 		if (vertices.size() > 0) {
 			g_engine->getGfx().drawLines(&vertices[0], vertices.size());


Commit: 803cc32afadba6022cd99b31fa717d67ad43ee1d
    https://github.com/scummvm/scummvm/commit/803cc32afadba6022cd99b31fa717d67ad43ee1d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix TesterTron 3000

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/genlib.cpp
    engines/twp/hud.h
    engines/twp/objlib.cpp
    engines/twp/syslib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index aa5ea22fb8b..08f58e6ab9b 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -75,7 +75,7 @@ DialogConditionState CondStateVisitor::createState(int line, DialogConditionMode
 
 DialogConditionState::DialogConditionState() {}
 
-DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String& k, const Common::String& dlg, int ln) 
+DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String& k, const Common::String& dlg, int ln)
 : mode(m), actorKey(k), dialog(dlg), line(ln) {
 }
 
@@ -195,7 +195,7 @@ void Dialog::choose(int choice) {
 }
 
 void Dialog::choose(DialogSlot *slot) {
-	if (slot) {
+	if (slot && slot->_isValid) {
 		sqcall("onChoiceClick");
 		for (int i = 0; i < slot->_stmt->_conds.size(); i++) {
 			YCond *cond = slot->_stmt->_conds[i];
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 349ac075384..498ddb30996 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -317,7 +317,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		ActorSlot *actorSlot = g_engine->_hud.actorSlot(g_engine->_actor);
 		if (!actorSlot)
 			return 0;
-		for (int i = 1; i < 22; i++) {
+		for (int i = 1; i < MAX_VERBS; i++) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
 				SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
@@ -391,7 +391,7 @@ static SQInteger incutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger indialog(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_dialog.getState() != DialogState::None);
+	sqpush(v, (int)g_engine->_dialog.getState());
 	return 1;
 }
 
@@ -548,8 +548,8 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 		int choice;
 		if (SQ_FAILED(sqget(v, 3, choice)))
 			return sq_throwerror(v, "Failed to get choice");
-		// TODO choose(choice)
-		warning("pushSentence with VERB_DIALOG not implemented");
+		// use pushSentence with VERB_DIALOG
+		g_engine->_dialog.choose(choice);
 		return 0;
 	}
 
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 2810fe03cef..c8b6b6fee50 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -24,6 +24,7 @@
 #include "twp/scenegraph.h"
 
 #define NUMVERBS 9
+#define MAX_VERBS 22
 #define NUMACTORS 6
 
 #ifndef TWP_HUD_H
@@ -63,7 +64,7 @@ struct Verb {
 struct ActorSlot {
 public:
 	VerbUiColors verbUiColors;
-	Verb verbs[22];
+	Verb verbs[MAX_VERBS];
 	bool selectable = false;
 	Object *actor = nullptr;
 
@@ -71,7 +72,7 @@ public:
 	ActorSlot();
 
 	Verb *getVerb(int id) {
-		for (int i = 0; i < 22; i++) {
+		for (int i = 0; i < MAX_VERBS; i++) {
 			if (verbs[i].id.id == id) {
 				return &verbs[i];
 			}
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 43a59fdde95..edffdbfaf69 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -885,10 +885,9 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, verb)))
 		return sq_throwerror(v, "failed to get verb");
 
-	// int verbId = verb;
-	if (!g_engine->_actor) {
+	if (g_engine->_actor) {
 		ActorSlot *slot = g_engine->_hud.actorSlot(g_engine->_actor);
-		for (int i = 0; i < 22; i++) {
+		for (int i = 0; i < MAX_VERBS; i++) {
 			Verb *vb = &slot->verbs[i];
 			if (vb->id.id == verb) {
 				if (sqrawexists(obj->_table, vb->fun)) {
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index f4b5f6f46e2..8860216a2ce 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -614,7 +614,17 @@ static SQInteger microTime(HSQUIRRELVM v) {
 }
 
 static SQInteger moveCursorTo(HSQUIRRELVM v) {
-	warning("TODO: moveCursorTo: not implemented");
+	int x, y;
+	if (SQ_FAILED(sqget(v, 2, x)))
+		return sq_throwerror(v, "Failed to get x");
+	if (SQ_FAILED(sqget(v, 3, y)))
+		return sq_throwerror(v, "Failed to get y");
+	float t;
+	if (SQ_FAILED(sqget(v, 4, t)))
+		return sq_throwerror(v, "Failed to get time");
+
+	g_engine->_cursor.pos = Math::Vector2d(x, y);
+	// TODO: use time
 	return 0;
 }
 
@@ -952,6 +962,7 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "BUTTON_BACK", BUTTON_BACK);
 	regConst(v, "BUTTON_MOUSE_LEFT", BUTTON_MOUSE_LEFT);
 	regConst(v, "BUTTON_MOUSE_RIGHT", BUTTON_MOUSE_RIGHT);
+	regConst(v, "WAITING_FOR_CHOICE", 2);
 	regConst(v, "PLATFORM", 1); // TODO: choose the right platform
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 00cc2517605..0182725b9ca 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -154,8 +154,6 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	actor = !actor ? g_engine->_actor : actor;
 	if ((verbId.id <= 0) || (verbId.id > 13) || (!noun1) || (!actor))
 		return false;
-	// TODO
-	// if (a?._verb_tid) stopthread(actor._verb_tid)
 
 	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory() ? "YES" : "NO", noun1->isTouchable() ? "YES" : "NO", verbNoWalkTo(verbId, noun1) ? "YES" : "NO");
 
@@ -348,7 +346,6 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 				result.push_back(actorSwitcherSlot(slot));
 		}
 
-		// TODO: showOptions
 		// add gear icon
 		result.push_back(ActorSwitcherSlot("icon_gear", Color(0.f, 0.f, 0.f), Color(0.8f, 0.8f, 0.8f), showOptions));
 	}


Commit: 68541132c34a4aa7e6149286f0ef909f3d3cfb54
    https://github.com/scummvm/scummvm/commit/68541132c34a4aa7e6149286f0ef909f3d3cfb54
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix Franklin can't execute verbs

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0182725b9ca..97e2ab4be2f 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -152,7 +152,7 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
 	debug("exec(%s,%d,%s,%s)", name.c_str(), verbId.id, noun1name.c_str(), noun2name.c_str());
 	actor = !actor ? g_engine->_actor : actor;
-	if ((verbId.id <= 0) || (verbId.id > 13) || (!noun1) || (!actor))
+	if ((verbId.id <= 0) || (verbId.id > MAX_VERBS) || (!noun1) || (!actor))
 		return false;
 
 	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory() ? "YES" : "NO", noun1->isTouchable() ? "YES" : "NO", verbNoWalkTo(verbId, noun1) ? "YES" : "NO");


Commit: 0a51deccef432c4a51d9497af84ede38d5506ccc
    https://github.com/scummvm/scummvm/commit/0a51deccef432c4a51d9497af84ede38d5506ccc
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add description in savegame list

Changed paths:
    engines/twp/metaengine.cpp


diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 8a09dbed78f..94b2502e010 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -89,6 +89,13 @@ void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
 	ConfMan.registerDefault("language", "en");
 }
 
+static Common::String getDesc(const Twp::SaveGame& savegame) {
+	Common::String desc = Twp::formatTime(savegame.time, "%b %d at %H:%M");
+	if (savegame.easyMode)
+		desc += " (casual)";
+	return desc;
+}
+
 SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
 	Common::String filename = Common::String::format("%s%02d.save", target, slot);
 	Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
@@ -100,7 +107,6 @@ SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int sl
 		// Create the return descriptor
 		SaveStateDescriptor desc(this, slot, "?");
 
-		desc.setAutosave(slot == 1);
 		if (thumbnailFile) {
 			Image::PNGDecoder png;
 			if (png.loadStream(*thumbnailFile)) {
@@ -113,9 +119,8 @@ SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int sl
 		}
 		Twp::SaveGame savegame;
 		Twp::SaveGameManager::getSaveGame(f, savegame);
-		Common::String time = Twp::formatTime(savegame.time, "%b %d at %H:%M");
-		if (savegame.easyMode)
-			time += " (casual)";
+		Common::String time = getDesc(savegame);
+
 		desc.setDescription(time);
 		desc.setPlayTime(savegame.gameTime * 1000);
 		Twp::DateTime dt = Twp::toDateTime(savegame.time);
@@ -145,7 +150,10 @@ SaveStateList TwpMetaEngine::listSaves(const char *target) const {
 			Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
 
 			if (in) {
-				saveList.push_back(SaveStateDescriptor(this, slot, "?"));
+				Twp::SaveGame savegame;
+				Twp::SaveGameManager::getSaveGame(in, savegame);
+				Common::String time = getDesc(savegame);
+				saveList.push_back(SaveStateDescriptor(this, slot, time));
 			}
 		}
 	}


Commit: 4cde5db7293186721951cb9ce1f1fea9c22b2a72
    https://github.com/scummvm/scummvm/commit/4cde5db7293186721951cb9ce1f1fea9c22b2a72
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add savegame

Changed paths:
    engines/twp/callback.h
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/savegame.cpp
    engines/twp/savegame.h
    engines/twp/squirrel/sqvm.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/time.cpp
    engines/twp/time.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/callback.h b/engines/twp/callback.h
index 8c339ccf5ba..682fc820fdd 100644
--- a/engines/twp/callback.h
+++ b/engines/twp/callback.h
@@ -32,7 +32,12 @@ class Callback {
 public:
 	Callback(int id, float duration, const Common::String& name, const Common::Array<HSQOBJECT>& args);
 	bool update(float elapsed);
+
+	Common::String getName() const { return _name; }
 	int getId() const { return _id; }
+	float getDuration() const { return _duration; }
+	float getElapsed() const { return _elapsed; }
+	const Common::Array<HSQOBJECT>& getArgs() const { return _args; }
 
 private:
 	int _id = 0;
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 073d16b34b6..a20294f798f 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -30,6 +30,14 @@ namespace Twp {
 
 static Texture gEmptyTexture;
 
+int Color::toInt() const {
+	int r = (rgba.r * 255.f);
+	int g = (rgba.g * 255.f);
+	int b = (rgba.b * 255.f);
+	int a = (rgba.a * 255.f);
+	return (r << 16) | (g << 8) | b | (a << 24);
+}
+
 Vertex::Vertex() {}
 
 Vertex::Vertex(Math::Vector2d p, Color c, Math::Vector2d t)
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 60e07b50342..ba373d88f8a 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -82,6 +82,8 @@ struct Color {
 	Color operator*(float f) {
   		return Color(rgba.r * f, rgba.g * f, rgba.b * f, rgba.a * f);
 	}
+
+	int toInt() const;
 };
 
 // This is a point in 2D with a color and texture coordinates
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 99bc0d8cf41..1cc7745b3be 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -25,6 +25,7 @@
 
 namespace Twp {
 
+#define GGP_SIGNATURE 0x04030201
 #define GGP_NULL 1
 #define GGP_DICTIONARY 2
 #define GGP_ARRAY 3
@@ -504,6 +505,44 @@ bool MemStream::seek(int64 offset, int whence) {
 	return true;
 }
 
+OutMemStream::OutMemStream() : _buf(nullptr), _bufSize(0), _pos(0) {
+}
+
+bool OutMemStream::open(byte *buf, int64 bufSize) {
+	_buf = buf;
+	_bufSize = bufSize;
+	_pos = 0;
+	return true;
+}
+
+uint32 OutMemStream::write(const void *dataPtr, uint32 dataSize) {
+	int64 size = MIN((int64)dataSize, (int64)_bufSize - _pos);
+	memcpy(_buf + _pos, dataPtr, size);
+	_pos += size;
+	return size;
+}
+
+int64 OutMemStream::pos() const {
+	return _pos;
+}
+
+int64 OutMemStream::size() const {
+	return _bufSize;
+}
+
+bool OutMemStream::seek(int64 offset, int whence) {
+	if (whence == SEEK_SET) {
+		_pos = offset;
+		return true;
+	}
+	if (whence == SEEK_CUR) {
+		_pos += offset;
+		return true;
+	}
+	_pos = _bufSize + offset;
+	return true;
+}
+
 RangeStream::RangeStream() : _s(nullptr), _size(0) {
 }
 
@@ -594,17 +633,18 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 	s->seek(entriesOffset);
 
 	// decode entries
-	XorStream xor ;
-	xor.open(s, entriesSize, key);
+	XorStream xs;
+	xs.open(s, entriesSize, key);
 	Common::Array<byte> buffer(entriesSize);
-	xor.read(buffer.data(), entriesSize);
+	xs.read(buffer.data(), entriesSize);
 
 	// read entries as hash
 	MemStream ms;
 	ms.open(buffer.data(), entriesSize);
 	GGHashMapDecoder tblDecoder;
 	Common::JSONValue *value = tblDecoder.open(&ms);
-	if(!value) return false;
+	if (!value)
+		return false;
 
 	const Common::JSONObject &obj = value->asObject();
 	const Common::JSONArray &files = obj["files"]->asArray();
@@ -733,4 +773,126 @@ bool GGPackSet::assetExists(const char *asset) {
 	return false;
 }
 
+GGHashMapEncoder::GGHashMapEncoder() {
+}
+
+void GGHashMapEncoder::open(Common::SeekableWriteStream *stream) {
+	_s = stream;
+}
+
+void GGHashMapEncoder::write(const Common::JSONObject &obj) {
+	_s->writeUint32LE(GGP_SIGNATURE);
+	_s->writeUint32LE(obj.size());
+	_s->writeUint32LE(0);
+	writeMap(obj);
+	writeKeys();
+}
+
+void GGHashMapEncoder::writeKey(const Common::String &key) {
+	for (auto it = key.begin(); it != key.end(); it++) {
+		_s->writeByte(*it);
+	}
+	_s->writeByte(0);
+}
+
+void GGHashMapEncoder::writeKeys() {
+	int64 plo = _s->pos();
+	_s->seek(8, SEEK_SET);
+	_s->writeUint32LE(plo);
+	_s->seek(plo, SEEK_SET);
+
+	// write offsets
+	Common::StringArray strings(_strings.size());
+	for (auto it = _strings.begin(); it != _strings.end(); it++) {
+		strings[it->second] = it->first;
+	}
+	writeMarker(GGP_OFFSETS);
+	int64 offset = _s->pos() + 4 * strings.size() + 5;
+	for (auto it = strings.begin(); it != strings.end(); it++) {
+		_s->writeUint32LE(offset);
+		offset += it->size() + 1;
+	}
+	_s->writeUint32LE(GGP_ENDOFFSETS);
+
+	// write keys
+	writeMarker(GGP_KEYS);
+	for (auto it = strings.begin(); it != strings.end(); it++) {
+		writeKey(*it);
+	}
+}
+
+void GGHashMapEncoder::writeMarker(byte marker) {
+	_s->writeByte(marker);
+}
+
+void GGHashMapEncoder::writeRawString(const Common::String &s) {
+	if (_strings.find(s) != _strings.end()) {
+		_s->writeUint32LE(_strings[s]);
+	} else {
+		uint offset = _strings.size();
+		_strings.insert(Common::Pair<Common::String, uint32>(s, offset));
+		_s->writeUint32LE(offset);
+	}
+}
+
+void GGHashMapEncoder::writeString(const Common::String &s) {
+	writeMarker(GGP_STRING);
+	writeRawString(s);
+}
+
+void GGHashMapEncoder::writeInt(int value) {
+	writeMarker(GGP_INTEGER);
+	Common::String s = Common::String::format("%d", value);
+	writeRawString(s);
+}
+
+void GGHashMapEncoder::writeFloat(float value) {
+	writeMarker(GGP_DOUBLE);
+	Common::String s = Common::String::format("%f", value);
+	writeRawString(s);
+}
+
+void GGHashMapEncoder::writeNull() {
+	writeMarker(GGP_NULL);
+}
+
+void GGHashMapEncoder::writeValue(const Common::JSONValue *obj) {
+	if (obj->isIntegerNumber()) {
+		writeInt(obj->asIntegerNumber());
+	} else if (obj->isNumber()) {
+		writeFloat(obj->asNumber());
+	} else if (obj->isBool()) {
+		writeInt(obj->asBool() ? 1 : 0);
+	} else if (obj->isNull()) {
+		writeNull();
+	} else if (obj->isString()) {
+		writeString(obj->asString());
+	} else if (obj->isArray()) {
+		writeArray(obj->asArray());
+	} else if (obj->isObject()) {
+		writeMap(obj->asObject());
+	} else {
+		error("JSON value not managed");
+	}
+}
+
+void GGHashMapEncoder::writeArray(const Common::JSONArray &arr) {
+	writeMarker(GGP_ARRAY);
+	_s->writeUint32LE(arr.size());
+	for (auto it = arr.begin(); it != arr.end(); it++) {
+		writeValue(*it);
+	}
+	writeMarker(GGP_ARRAY);
+}
+
+void GGHashMapEncoder::writeMap(const Common::JSONObject &obj) {
+	writeMarker(GGP_DICTIONARY);
+	_s->writeUint32LE(obj.size());
+	for (auto it = obj.begin(); it != obj.end(); it++) {
+		writeRawString(it->_key);
+		writeValue(it->_value);
+	}
+	writeMarker(GGP_DICTIONARY);
+}
+
 } // namespace Twp
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index 89f283b9f5e..48c71249214 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -26,6 +26,7 @@
 #include "common/stream.h"
 #include "common/list.h"
 #include "common/path.h"
+#include "common/stablemap.h"
 #include "common/formats/json.h"
 
 namespace Twp {
@@ -53,6 +54,23 @@ private:
 	int64 _pos = 0;
 };
 
+class OutMemStream: public Common::SeekableWriteStream {
+public:
+	OutMemStream();
+
+	bool open(byte* buf, int64 bufSize);
+	uint32 write(const void *dataPtr, uint32 dataSize) override;
+
+	int64 pos() const  override;
+	int64 size() const  override;
+	bool seek(int64 offset, int whence = SEEK_SET)  override;
+
+private:
+    byte* _buf = nullptr;
+	int64 _bufSize = 0;
+	int64 _pos = 0;
+};
+
 class XorStream: public Common::SeekableReadStream {
 public:
 	XorStream();
@@ -108,6 +126,31 @@ private:
 	Common::Array<int> _offsets;
 };
 
+class GGHashMapEncoder {
+public:
+	GGHashMapEncoder();
+
+	void open(Common::SeekableWriteStream* stream);
+	void write(const Common::JSONObject& obj);
+
+private:
+	void writeMarker(byte marker);
+	void writeString(const Common::String& s);
+	void writeRawString(const Common::String& s);
+	void writeNull();
+	void writeInt(int value);
+	void writeFloat(float value);
+	void writeArray(const Common::JSONArray& arr);
+	void writeValue(const Common::JSONValue* obj);
+	void writeMap(const Common::JSONObject &obj);
+	void writeKeys();
+	void writeKey(const Common::String& key);
+
+private:
+	Common::SeekableWriteStream* _s = nullptr;
+	Common::StableMap<Common::String, uint32> _strings;
+};
+
 struct GGPackEntry {
     int offset, size;
 };
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 47f50ca0f10..f27be80d5f1 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -23,9 +23,14 @@
 #include "twp/savegame.h"
 #include "twp/squtil.h"
 #include "twp/btea.h"
+#include "twp/time.h"
+#include "common/file.h"
+#include "common/savefile.h"
 
 namespace Twp {
 
+const static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
+
 static Object *actor(const Common::String &key) {
 	for (int i = 0; i < g_engine->_actors.size(); i++) {
 		Object *a = g_engine->_actors[i];
@@ -35,6 +40,15 @@ static Object *actor(const Common::String &key) {
 	return nullptr;
 }
 
+static Object *actor(int id) {
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *a = g_engine->_actors[i];
+		if (a->getId() == id)
+			return a;
+	}
+	return nullptr;
+}
+
 static DialogConditionMode parseMode(char mode) {
 	switch (mode) {
 	case '?':
@@ -373,8 +387,8 @@ static int32_t computeHash(byte *data, size_t n) {
 	return result;
 }
 
-bool SaveGameManager::loadGame(const SaveGame& savegame) {
-	const Common::JSONObject& json = savegame.jSavegame->asObject();
+bool SaveGameManager::loadGame(const SaveGame &savegame) {
+	const Common::JSONObject &json = savegame.jSavegame->asObject();
 	long long int version = json["version"]->asIntegerNumber();
 	if (version != 2) {
 		error("Cannot load savegame version %lld", version);
@@ -406,8 +420,7 @@ bool SaveGameManager::loadGame(const SaveGame& savegame) {
 	return true;
 }
 
-bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame& savegame) {
-	static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
+bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame) {
 	Common::Array<byte> data(stream->size());
 	stream->read(&data[0], data.size());
 	BTEACrypto::decrypt((uint32 *)&data[0], data.size() / 4, savegameKey);
@@ -422,7 +435,10 @@ bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame&
 
 	GGHashMapDecoder decoder;
 	savegame.jSavegame = decoder.open(&ms);
-	const Common::JSONObject& jSavegame = savegame.jSavegame->asObject();
+	if(!savegame.jSavegame)
+		return false;
+
+	const Common::JSONObject &jSavegame = savegame.jSavegame->asObject();
 	savegame.gameTime = jSavegame["gameTime"]->asNumber();
 	savegame.easyMode = jSavegame["easy_mode"]->asIntegerNumber() != 0;
 
@@ -555,4 +571,398 @@ void SaveGameManager::loadObjects(const Common::JSONObject &json) {
 	}
 }
 
+static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false) {
+	switch (obj._type) {
+	case OT_INTEGER:
+		return new Common::JSONValue(sq_objtointeger(&obj));
+	case OT_FLOAT:
+		return new Common::JSONValue(sq_objtofloat(&obj));
+	case OT_STRING:
+		return new Common::JSONValue(sq_objtostring(&obj));
+	case OT_NULL:
+		return new Common::JSONValue();
+	case OT_ARRAY: {
+		Common::JSONArray arr;
+		sqgetitems(obj, [&arr](HSQOBJECT &item) { arr.push_back(tojson(item, true)); });
+		return new Common::JSONValue(arr);
+	}
+	case OT_TABLE: {
+		Common::JSONObject jObj;
+		if (checkId) {
+			int id;
+			sqgetf(obj, "_id", id);
+			if (isActor(id)) {
+				Object *a = actor(id);
+				jObj["_actorKey"] = new Common::JSONValue(a->_key);
+				return new Common::JSONValue(jObj);
+			} else if (isObject(id)) {
+				Object *o = sqobj(id);
+				if (!o)
+					return new Common::JSONValue();
+				jObj["_objectKey"] = new Common::JSONValue(o->_key);
+				if (o->_room && o->_room->_pseudo)
+					jObj["_roomKey"] = new Common::JSONValue(o->_room->_name);
+				return new Common::JSONValue(jObj);
+			} else if (isRoom(id)) {
+				Room *r = getRoom(id);
+				jObj["_roomKey"] = new Common::JSONValue(r->_name);
+				return new Common::JSONValue(jObj);
+			}
+		}
+
+		HSQUIRRELVM v = g_engine->getVm();
+		HSQOBJECT rootTbl = sqrootTbl(v);
+		sqgetpairs(obj, [&](const Common::String &k, HSQOBJECT &oTable) {
+			if ((k.size() > 0) && (!k.hasPrefix("_"))) {
+				if (!(skipObj && isObject(getId(oTable)) && (pseudo || sqrawexists(rootTbl, k)))) {
+					Common::JSONValue *json = tojson(oTable, true);
+					if (json) {
+						jObj[k] = json;
+					}
+				}
+			}
+		});
+		return new Common::JSONValue(jObj);
+	}
+	default:
+		return nullptr;
+	}
+}
+
+static Common::String removeFileExt(const Common::String &s) {
+	size_t i = s.findLastOf('.');
+	if (i != Common::String::npos) {
+		return s.substr(0, i);
+	}
+	return s;
+}
+
+Common::String toString(const Math::Vector2d &pos) {
+	return Common::String::format("{%d,%d}", (int)pos.getX(), (int)pos.getY());
+}
+
+static Common::JSONValue *createJActor(Object *actor) {
+	Common::JSONValue *jActorValue = tojson(actor->_table, false);
+	Common::JSONObject jActor(jActorValue->asObject());
+	int color = actor->_node->getComputedColor().toInt();
+	if (color != Color().toInt())
+		jActor["_color"] = new Common::JSONValue((long long int)color);
+	//   if (actor->hasCustomAnim())
+	//     jActor["_animations"] = actor->getCustomAnims();
+	jActor["_costume"] = new Common::JSONValue(removeFileExt(actor->_costumeName));
+	jActor["_dir"] = new Common::JSONValue((long long int)actor->_facing);
+	jActor["_lockFacing"] = new Common::JSONValue((long long int)actor->_facingLockValue);
+	jActor["_pos"] = new Common::JSONValue(toString(actor->_node->getPos()));
+	if (actor->_useDir != dNone)
+		jActor["_useDir"] = new Common::JSONValue((long long int)actor->_useDir);
+	if (actor->_usePos != Math::Vector2d(0.f, 0.f))
+		jActor["_usePos"] = new Common::JSONValue(toString(actor->_usePos));
+	if (actor->_node->getRenderOffset() != Math::Vector2d(0.f, 45.f))
+		jActor["_renderOffset"] = new Common::JSONValue(toString(actor->_node->getRenderOffset()));
+	if (actor->_costumeSheet.size() > 0)
+		jActor["_costumeSheet"] = new Common::JSONValue(actor->_costumeSheet);
+	if (actor->_room)
+		jActor["_roomKey"] = new Common::JSONValue(actor->_room->_name);
+	if (!actor->isTouchable() && actor->_node->isVisible())
+		jActor["_untouchable"] = new Common::JSONValue(1LL);
+	if (!actor->_node->isVisible())
+		jActor["_hidden"] = new Common::JSONValue(1LL);
+	if (actor->_volume != 0.f)
+		jActor["_volume"] = new Common::JSONValue(actor->_volume);
+	//   result.fields.sort(cmpKey);
+	return new Common::JSONValue(jActor);
+}
+
+static Common::JSONValue *createJActors() {
+	Common::JSONObject jActors;
+	for (int i = 0; i < g_engine->_actors.size(); i++) {
+		Object *actor = g_engine->_actors[i];
+		if (actor->_key.size() > 0) {
+			jActors[actor->_key] = createJActor(actor);
+		}
+	}
+	// result.fields.sort(cmpKey);
+	return new Common::JSONValue(jActors);
+}
+
+static Common::JSONValue *createJCallback(const Callback &callback) {
+	Common::JSONObject result;
+	result["function"] = new Common::JSONValue(callback.getName());
+	result["guid"] = new Common::JSONValue((long long int)callback.getId());
+	result["time"] = new Common::JSONValue(MAX(0.0, (double)(callback.getDuration() - callback.getElapsed())));
+	Common::JSONArray jArgs;
+	const Common::Array<HSQOBJECT> &args = callback.getArgs();
+	for (int i = 0; i < args.size(); i++) {
+		jArgs.push_back(tojson(args[i], false));
+	}
+	if (jArgs.size() > 0)
+		result["param"] = new Common::JSONValue(jArgs);
+	return new Common::JSONValue(result);
+}
+
+static Common::JSONValue *createJCallbackArray() {
+	Common::JSONArray result;
+	for (int i = 0; i < g_engine->_callbacks.size(); i++) {
+		result.push_back(createJCallback(*g_engine->_callbacks[i]));
+	}
+	return new Common::JSONValue(result);
+}
+
+static Common::JSONValue *createJCallbacks() {
+	Common::JSONObject json;
+	json["callbacks"] = createJCallbackArray();
+	json["nextGuid"] = new Common::JSONValue((long long int)getCallbackId());
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJRoomKey(Room *room) {
+	return new Common::JSONValue(room ? room->_name : "Void");
+}
+
+Common::String createJDlgStateKey(const DialogConditionState &state) {
+	Common::String s;
+	switch (state.mode) {
+	case OnceEver:
+		s = "&";
+		break;
+	case ShowOnce:
+		s = "#";
+		break;
+	case Once:
+		s = "?";
+		break;
+	case ShowOnceEver:
+		s = "$";
+		break;
+	default:
+		break;
+	}
+	return Common::String::format("%s%s%d%s", s.c_str(), state.dialog.c_str(), state.line, state.actorKey.c_str());
+}
+
+static Common::JSONValue *createJDialog() {
+	Common::JSONObject json;
+	for (int i = 0; i < g_engine->_dialog._states.size(); i++) {
+		const DialogConditionState &state = g_engine->_dialog._states[i];
+		if (state.mode != TempOnce) {
+			// TODO: value should be 1 or another value ?
+			json[createJDlgStateKey(state)] = new Common::JSONValue(state.mode == ShowOnce ? 2LL : 1LL);
+		}
+	}
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJEasyMode() {
+	HSQOBJECT g;
+	sqgetf("g", g);
+	int easyMode;
+	sqgetf(g, "easy_mode", easyMode);
+	return new Common::JSONValue((long long int)easyMode);
+}
+
+static Common::JSONValue *createJBool(bool value) {
+	return new Common::JSONValue(value ? 1LL : 0LL);
+}
+
+static Common::JSONValue *createJSelectableActor(const ActorSlot &slot) {
+	Common::JSONObject json;
+	if (slot.actor) {
+		json["_actorKey"] = new Common::JSONValue(slot.actor->_key);
+	}
+	json["selectable"] = createJBool(slot.selectable);
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJSelectableActors() {
+	Common::JSONArray json;
+	for (int i = 0; i < NUMACTORS; i++) {
+		const ActorSlot &slot = g_engine->_hud._actorSlots[i];
+		json.push_back(createJSelectableActor(slot));
+	}
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJGameScene() {
+	bool actorsSelectable = asOn & g_engine->_actorSwitcher._mode;
+	bool actorsTempUnselectable = asTemporaryUnselectable & g_engine->_actorSwitcher._mode;
+	Common::JSONObject json;
+	json["actorsSelectable"] = createJBool(actorsSelectable);
+	json["actorsTempUnselectable"] = createJBool(actorsTempUnselectable);
+	// TODO: json["forceTalkieText"] = createJBool(tmpPrefs().forceTalkieText);
+	json["selectableActors"] = createJSelectableActors();
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJGlobals() {
+	HSQOBJECT g;
+	sqgetf("g", g);
+	//   result.fields.sort(cmpKey);
+	return tojson(g, false);
+}
+
+static Common::JSONValue *createJInventory(const ActorSlot &slot) {
+	Common::JSONObject json;
+	if (!slot.actor) {
+		json["scroll"] = new Common::JSONValue(0LL);
+	} else {
+		Common::JSONArray objKeys;
+		Common::JSONArray jiggleArray;
+		bool anyJiggle = false;
+		//  for (obj in slot.actor.inventory) {
+		for (int i = 0; i < slot.actor->_inventory.size(); i++) {
+			Object *obj = slot.actor->_inventory[i];
+			// TODO: jiggle
+			// let jiggle = obj.getJiggle()
+			bool jiggle = false;
+			// if (jiggle)
+			// 	anyJiggle = true;
+			jiggleArray.push_back(createJBool(jiggle));
+			objKeys.push_back(new Common::JSONValue(obj->_key));
+		}
+
+		if (objKeys.size() > 0) {
+			json["objects"] = new Common::JSONValue(objKeys);
+		}
+		json["scroll"] = new Common::JSONValue((long long int)slot.actor->_inventoryOffset);
+		if (anyJiggle)
+			json["jiggle"] = new Common::JSONValue(jiggleArray);
+	}
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJInventory() {
+	Common::JSONArray slots;
+	for (int i = 0; i < NUMACTORS; i++) {
+		const ActorSlot &slot = g_engine->_hud._actorSlots[i];
+		slots.push_back(createJInventory(slot));
+	}
+	Common::JSONObject json;
+	json["slots"] = new Common::JSONValue(slots);
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJObject(HSQOBJECT &table, Object *obj) {
+	Common::JSONObject json(tojson(table, false)->asObject());
+	if (obj) {
+		if (!obj->_node->isVisible())
+			json["_hidden"] = new Common::JSONValue(1LL);
+		if (obj->_state != 0)
+			json["_state"] = new Common::JSONValue((long long int)obj->_state);
+		if (obj->_node->isVisible() && !obj->isTouchable())
+			json["_touchable"] = new Common::JSONValue(0LL);
+		if (obj->_node->getOffset() != Math::Vector2d())
+			json["_offset"] = new Common::JSONValue(toString(obj->_node->getOffset()));
+	}
+	//   result.fields.sort(cmpKey)
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJObjects() {
+	Common::JSONObject json;
+	sqgetpairs(sqrootTbl(g_engine->getVm()), [&](const Common::String &k, HSQOBJECT &v) {
+		if (isObject(getId(v))) {
+			Object *obj = sqobj(v);
+			if (!obj || (obj->_objType == otNone)) {
+				// info fmt"obj: createJObject({k})"
+				json[k] = createJObject(v, obj);
+			}
+		}
+	});
+	//   result.fields.sort(cmpKey)
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJPseudoObjects(Room *room) {
+	Common::JSONObject json;
+	sqgetpairs(room->_table, [&](const Common::String &k, HSQOBJECT &v) {
+		if (isObject(getId(v))) {
+			Object *obj = sqobj(v);
+			// info fmt"pseudoObj: createJObject({k})"
+			json[k] = createJObject(v, obj);
+		}
+	});
+	//   result.fields.sort(cmpKey)
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJRoom(Room *room) {
+	Common::JSONObject json(tojson(room->_table, false, true, room->_pseudo)->asObject());
+	if (room->_pseudo) {
+		json["_pseudoObjects"] = createJPseudoObjects(room);
+	}
+	//   result.fields.sort(cmpKey)
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createJRooms() {
+	Common::JSONObject json;
+	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+		Room *room = g_engine->_rooms[i];
+		if (room)
+			json[room->_name] = createJRoom(room);
+	}
+	//   result.fields.sort(cmpKey);
+	return new Common::JSONValue(json);
+}
+
+static Common::JSONValue *createSaveGame() {
+	Common::JSONObject json;
+	json["actors"] = createJActors();
+	json["callbacks"] = createJCallbacks();
+	json["currentRoom"] = createJRoomKey(g_engine->_room);
+	json["dialog"] = createJDialog();
+	json["easy_mode"] = createJEasyMode();
+	json["gameGUID"] = new Common::JSONValue("");
+	json["gameScene"] = createJGameScene();
+	json["gameTime"] = new Common::JSONValue(g_engine->_time);
+	json["globals"] = createJGlobals();
+	json["inputState"] = new Common::JSONValue((long long int)g_engine->_inputState.getState());
+	json["inventory"] = createJInventory();
+	json["objects"] = createJObjects();
+	json["rooms"] = createJRooms();
+	json["savebuild"] = new Common::JSONValue(958LL);
+	json["savetime"] = new Common::JSONValue(getTime());
+	json["selectedActor"] = new Common::JSONValue(g_engine->_actor ? g_engine->_actor->_key : "");
+	json["version"] = new Common::JSONValue((long long int)2);
+	return new Common::JSONValue(json);
+}
+
+void SaveGameManager::saveGame(Common::WriteStream *ws) {
+	sqcall("preSave");
+	Common::JSONValue *data = createSaveGame();
+
+	// dump savegame as json
+	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("save.json", false);
+	// Common::String s = data->stringify(true);
+	// saveFile->write(s.c_str(),s.size());
+	// saveFile->finalize();
+
+	const uint32 fullSize = 500000;
+	Common::Array<byte> buffer(fullSize + 16);
+	OutMemStream stream;
+	stream.open(buffer.data(), buffer.size());
+
+	GGHashMapEncoder encoder;
+	encoder.open(&stream);
+	encoder.write(data->asObject());
+
+	int32 savetime = data->asObject()["savetime"]->asIntegerNumber();
+	int32 hash = computeHash(buffer.data(), fullSize);
+	byte marker = (8 - ((fullSize + 9) % 8));
+
+	// write at the end 16 bytes: hashdata (4 bytes) + savetime (4 bytes) + marker (8 bytes)
+	int32 *p = (int32 *)(buffer.data() + fullSize);
+	p[0] = hash;
+	p[1] = savetime;
+	memset(&p[2], marker, 8);
+
+	// then encode data
+	BTEACrypto::encrypt((uint32 *)buffer.data(), buffer.size() / 4, savegameKey);
+
+	// and write data
+	ws->write(buffer.data(), buffer.size());
+
+	sqcall("postSave");
+}
+
 } // namespace Twp
diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
index da3d29fb122..816c5014dbb 100644
--- a/engines/twp/savegame.h
+++ b/engines/twp/savegame.h
@@ -39,6 +39,7 @@ class SaveGameManager {
 public:
 	static bool getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame);
 	bool loadGame(const SaveGame &savegame);
+	void saveGame(Common::WriteStream *stream);
 
 private:
 	void loadGameScene(const Common::JSONObject &json);
diff --git a/engines/twp/squirrel/sqvm.h b/engines/twp/squirrel/sqvm.h
index bc657fb1985..41d556a3832 100755
--- a/engines/twp/squirrel/sqvm.h
+++ b/engines/twp/squirrel/sqvm.h
@@ -20,7 +20,6 @@ void sq_base_register(HSQUIRRELVM v);
 struct SQExceptionTrap{
     SQExceptionTrap() {}
     SQExceptionTrap(SQInteger ss, SQInteger stackbase,SQInstruction *ip, SQInteger ex_target){ _stacksize = ss; _stackbase = stackbase; _ip = ip; _extarget = ex_target;}
-    SQExceptionTrap(const SQExceptionTrap &et) { (*this) = et;  }
     SQInteger _stackbase;
     SQInteger _stacksize;
     SQInstruction *_ip;
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index df5a5844a4a..74fc2fb3f4f 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -263,7 +263,7 @@ void sqcall(const char *name, const Common::Array<HSQOBJECT> &args) {
 	sq_settop(v, top);
 }
 
-static int getId(HSQOBJECT table) {
+int getId(HSQOBJECT table) {
 	SQInteger result = 0;
 	sqgetf(table, "_id", result);
 	return (int)result;
@@ -271,6 +271,10 @@ static int getId(HSQOBJECT table) {
 
 Room *sqroom(HSQOBJECT table) {
 	int id = getId(table);
+	return getRoom(id);
+}
+
+Room *getRoom(int id) {
 	for (int i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		if (getId(room->_table) == id)
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index ec19f95f449..d79b89adb08 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -155,8 +155,10 @@ void sqexec(HSQUIRRELVM v, const char *code, const char *filename = nullptr);
 class Room;
 class Object;
 
+int getId(HSQOBJECT table);
 Room *sqroom(HSQOBJECT table);
 Room *sqroom(HSQUIRRELVM v, int i);
+Room *getRoom(int id);
 Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
 Object *sqobj(int i);
diff --git a/engines/twp/time.cpp b/engines/twp/time.cpp
index c19f12c7105..c5a6df72104 100644
--- a/engines/twp/time.cpp
+++ b/engines/twp/time.cpp
@@ -45,4 +45,8 @@ DateTime toDateTime(int64_t t) {
 	return dateTime;
 }
 
+int64_t getTime() {
+	return (int64_t)time(NULL);
+}
+
 } // namespace Twp
diff --git a/engines/twp/time.h b/engines/twp/time.h
index 9c13b279296..d81c57c9d3d 100644
--- a/engines/twp/time.h
+++ b/engines/twp/time.h
@@ -33,6 +33,7 @@ struct DateTime {
 
 Common::String formatTime(int64_t time, const char *format);
 DateTime toDateTime(int64_t time);
+int64_t getTime();
 
 } // namespace Twp
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 97e2ab4be2f..7d5c0c5a1bc 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -27,6 +27,7 @@
 #include "common/config-manager.h"
 #include "common/events.h"
 #include "common/savefile.h"
+#include "image/png.h"
 #include "engines/util.h"
 #include "graphics/palette.h"
 #include "graphics/opengl/system_headers.h"
@@ -560,7 +561,7 @@ void TwpEngine::setShaderEffect(RoomEffect effect) {
 	}
 }
 
-void TwpEngine::draw() {
+void TwpEngine::draw(RenderTexture* outTexture) {
 	if (_room) {
 		Math::Vector2d screenSize = _room->getScreenSize();
 		_gfx.camera(screenSize);
@@ -578,7 +579,9 @@ void TwpEngine::draw() {
 
 	// then render this texture with room effect to another texture
 	_gfx.setRenderTarget(&renderTexture2);
-	setShaderEffect(_room->_effect);
+	if (_room) {
+		setShaderEffect(_room->_effect);
+	}
 	_shaderParams.randomValue[0] = g_engine->getRandom();
 	_shaderParams.timeLapse = fmodf(_time, 1000.f);
 	_shaderParams.iGlobalTime = _shaderParams.timeLapse;
@@ -597,7 +600,7 @@ void TwpEngine::draw() {
 		_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 		_gfx.cameraPos(_fadeShader->_cameraPos);
 		_gfx.clear(Color(0, 0, 0));
-		if (_fadeShader->_effect == FadeEffect::Wobble) {
+		if (_fadeShader->_room && _fadeShader->_effect == FadeEffect::Wobble) {
 			Math::Vector2d camSize = _fadeShader->_room->getScreenSize();
 			_gfx.camera(camSize);
 			_fadeShader->_room->_scene->draw();
@@ -640,7 +643,7 @@ void TwpEngine::draw() {
 
 	// draw to screen
 	_gfx.use(nullptr);
-	_gfx.setRenderTarget(nullptr);
+	_gfx.setRenderTarget(outTexture);
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
 
@@ -884,7 +887,36 @@ Common::String TwpEngine::getSaveStateName(int slot) const {
 	return Common::String::format("twp%02d.save", slot);
 }
 
+static Common::String changeFileExt(const Common::String &s, const Common::String &ext) {
+	size_t i = s.findLastOf('.');
+	if (i != Common::String::npos) {
+		return s.substr(0, i) + ext;
+	}
+	return s + ext;
+}
+
+Common::Error TwpEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
+	Common::String name = getSaveStateName(slot);
+	Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(name, false);
+	if (!saveFile)
+		return Common::kWritingFailed;
+
+	Common::Error result = saveGameStream(saveFile, isAutosave);
+	if (result.getCode() == Common::kNoError) {
+		name = changeFileExt(name, ".png");
+		Common::OutSaveFile *thumbnail = _saveFileMan->openForSaving(name, false);
+		g_engine->capture(*thumbnail, Math::Vector2d(320, 180));
+		thumbnail->finalize();
+
+		saveFile->finalize();
+	}
+
+	delete saveFile;
+	return result;
+}
+
 Common::Error TwpEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
+	_saveGameManager.saveGame(stream);
 	return Common::kNoError;
 }
 
@@ -1519,6 +1551,17 @@ Scaling *TwpEngine::getScaling(const Common::String &name) {
 	return nullptr;
 }
 
+void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
+	RenderTexture rt(size);
+	draw(&rt);
+
+	Graphics::Surface s;
+	rt.capture(s);
+	s.flipVertical(Common::Rect(size.getX(), size.getY()));
+
+	Image::writePNG(stream, s);
+}
+
 ScalingTrigger::ScalingTrigger(Object *obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index a303e3b26a4..2d9ff32f01b 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -115,6 +115,8 @@ public:
 
 	Common::Error loadGameState(int slot) override;
 	Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
+	Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
+	void capture(Common::WriteStream &stream, Math::Vector2d size);
 
 	Math::Vector2d winToScreen(Math::Vector2d pos);
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
@@ -146,7 +148,7 @@ public:
 
 private:
 	void update(float elapsedMs);
-	void draw();
+	void draw(RenderTexture* texture = nullptr);
 	void exitRoom(Room *nextRoom);
 	void actorEnter();
 	void actorExit();


Commit: fee1d7b1215c727a55b2dfc0204e9c46ee63a0f3
    https://github.com/scummvm/scummvm/commit/fee1d7b1215c727a55b2dfc0204e9c46ee63a0f3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix tons of warnings

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/actorswitcher.h
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/ggpack.cpp
    engines/twp/graph.cpp
    engines/twp/lip.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squirrel/sqvm.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp
    engines/twp/util.h
    engines/twp/walkboxnode.cpp
    engines/twp/yack.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 985b82aa3bc..25b7c11dba2 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -333,7 +333,7 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 3, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (int i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
+	for (size_t i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
 		const Walkbox &walkbox = g_engine->_room->_walkboxes[i];
 		if (walkbox._name == name) {
 			if (walkbox.contains(actor->_node->getAbsPos())) {
@@ -933,7 +933,7 @@ static SQInteger is_actor(HSQUIRRELVM v) {
 // See also masterRoomArray.
 static SQInteger masterActorArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		sqpush(v, actor->_table);
 		sq_arrayappend(v, -2);
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index 815f70a2c28..b6f701b3a03 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -59,7 +59,7 @@ Math::Matrix4 ActorSwitcher::transform(Math::Matrix4 trsf, int index) {
 	return trsf;
 }
 
-float ActorSwitcher::getAlpha(int index) const {
+float ActorSwitcher::getAlpha(size_t index) const {
 	if (index == (_slots.size() - 1))
 		return ENABLE_ALPHA;
 	if (_mode & asTemporaryUnselectable)
@@ -85,7 +85,7 @@ void ActorSwitcher::drawIcon(const Common::String &icon, Color backColor, Color
 
 void ActorSwitcher::drawCore(Math::Matrix4 trsf) {
 	if (_mouseOver) {
-		for (int i = 0; i < _slots.size(); i++) {
+		for (size_t i = 0; i < _slots.size(); i++) {
 			ActorSwitcherSlot &slot = _slots[i];
 			drawIcon(slot.icon, slot.back, slot.frame, trsf, i);
 		}
@@ -152,7 +152,7 @@ void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float
 		if (_mouseOver && (g_engine->_cursor.leftDown) && !_down) {
 			_down = true;
 			// check if we allow to select an actor
-			int iconIdx = iconIndex(scrPos);
+            size_t iconIdx = iconIndex(scrPos);
 			if ((_mode & asOn) || (iconIdx == (_slots.size() - 1))) {
 				if (_slots[iconIdx].selectFunc != nullptr)
 					_slots[iconIdx].select();
diff --git a/engines/twp/actorswitcher.h b/engines/twp/actorswitcher.h
index fe1a5a3d673..4b56bd4e9f6 100644
--- a/engines/twp/actorswitcher.h
+++ b/engines/twp/actorswitcher.h
@@ -59,7 +59,7 @@ protected:
 	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 	void drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index);
 	Math::Matrix4 transform(Math::Matrix4 trsf, int index);
-	float getAlpha(int index) const;
+	float getAlpha(size_t index) const;
 	float height() const;
 	int iconIndex(Math::Vector2d pos) const;
 	Common::Rect rect() const;
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 08f58e6ab9b..db37bae0017 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -197,7 +197,7 @@ void Dialog::choose(int choice) {
 void Dialog::choose(DialogSlot *slot) {
 	if (slot && slot->_isValid) {
 		sqcall("onChoiceClick");
-		for (int i = 0; i < slot->_stmt->_conds.size(); i++) {
+		for (size_t i = 0; i < slot->_stmt->_conds.size(); i++) {
 			YCond *cond = slot->_stmt->_conds[i];
 			CondStateVisitor v(slot->_dlg, DialogSelMode::Choose);
 			cond->accept(v);
@@ -348,8 +348,8 @@ void Dialog::selectLabel(int line, const Common::String &name) {
 
 void Dialog::gotoNextLabel() {
 	if (_lbl) {
-		int i = Twp::find(_cu->_labels, _lbl);
-		if ((i != -1) && (i != _cu->_labels.size() - 1)) {
+        size_t i = Twp::find(_cu->_labels, _lbl);
+		if ((i != (size_t)-1) && (i != _cu->_labels.size() - 1)) {
 			YLabel *label = _cu->_labels[i + 1];
 			selectLabel(label->_line, label->_name);
 		} else {
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 40c1ae832da..22e7264b2e0 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -216,7 +216,7 @@ public:
 
 private:
 	DialogState _state = DialogState::None;
-	int _currentStatement = 0;
+    size_t _currentStatement = 0;
 	unique_ptr<YCompilationUnit> _cu;
 	YLabel *_lbl = nullptr;
 	DialogSlot _slots[MAXDIALOGSLOTS];
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 61db0a336cc..7193d2e2634 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -54,7 +54,7 @@ private:
 
 static Object *actor(const Common::String &name) {
 	// for (actor in gEngine.actors) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (actor->_key == name)
 			return actor;
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index c64720bf5a8..aeb199487d3 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -64,7 +64,7 @@ private:
 
 private:
 	Common::U32String _text;
-	int _off;
+    size_t _off;
 };
 
 static Math::Vector2d normalize(Texture *texture, Math::Vector2d v) {
@@ -112,7 +112,7 @@ static int skipUntil(const Common::U32String& s, const char *until, int start =
 static float width(Text &text, TokenReader &reader, Token tok) {
 	float result = 0;
 	Common::String s = reader.substr(tok);
-	for (int i = 0; i < s.size(); i++) {
+	for (size_t i = 0; i < s.size(); i++) {
 		char c = s[i];
 		result += text.getFont()->getGlyph(c).advance;
 	}
@@ -301,11 +301,11 @@ void Text::update() {
 		float maxW;
 		float lineHeight = _font->getLineHeight();
 		float y = -lineHeight;
-		for (int i = 0; i < lines.size(); i++) {
+		for (size_t i = 0; i < lines.size(); i++) {
 			Line &line = lines[i];
 			CodePoint prevChar;
 			x = 0;
-			for (int j = 0; j < line.tokens.size(); j++) {
+			for (size_t j = 0; j < line.tokens.size(); j++) {
 				tok = line.tokens[j];
 				if (tok.id == tiColor) {
 					int iColor;
@@ -314,7 +314,7 @@ void Text::update() {
 					color = Color::withAlpha(Color::rgb(iColor & 0x00FFFFFF), color.rgba.a);
 				} else {
 					Common::U32String s = reader.substr(tok);
-					for (int k = 0; k < s.size(); k++) {
+					for (size_t k = 0; k < s.size(); k++) {
 						CodePoint c = s[k];
 						Glyph glyph = _font->getGlyph(c);
 						float kern = _font->getKerning(prevChar, c);
@@ -332,17 +332,17 @@ void Text::update() {
 
 		// Align text
 		if (_hAlign == thRight) {
-			for (int i = 0; i < lines.size(); i++) {
+			for (size_t i = 0; i < lines.size(); i++) {
 				float w = maxW - _quads[i].width();
-				for (int j = 0; j < lines[i].charInfos.size(); j++) {
+				for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
 					CharInfo &info = lines[i].charInfos[j];
 					info.pos.setX(info.pos.getX() + w);
 				}
 			}
 		} else if (_hAlign == thCenter) {
-			for (int i = 0; i < lines.size(); i++) {
+			for (size_t i = 0; i < lines.size(); i++) {
 				float w = maxW - _quads[i].width();
-				for (int j = 0; j < lines[i].charInfos.size(); j++) {
+				for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
 					CharInfo &info = lines[i].charInfos[j];
 					info.pos.setX(info.pos.getX() + w / 2.f);
 				}
@@ -350,8 +350,8 @@ void Text::update() {
 		}
 
 		// Add the glyphs to the vertices
-		for (int i = 0; i < lines.size(); i++) {
-			for (int j = 0; j < lines[i].charInfos.size(); j++) {
+		for (size_t i = 0; i < lines.size(); i++) {
+			for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
 				const CharInfo &info = lines[i].charInfos[j];
 				addGlyphQuad(_texture, _vertices, info);
 			}
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 498ddb30996..bebcf4d4a41 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -744,9 +744,9 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	switch (nArgs) {
 	case 1: {
-		for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+		for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 			Layer *layer = g_engine->_room->_layers[i];
-			for (int j = 0; j < layer->_objects.size(); j++) {
+			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Object *obj = layer->_objects[j];
 				obj->_exec.enabled = false;
 			}
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index a20294f798f..6e3d9fd5e1b 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -96,11 +96,11 @@ void Texture::capture(Graphics::Surface &surface) {
 	GLint boundFrameBuffer;
 
 	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &boundFrameBuffer);
-	if (boundFrameBuffer != fbo) {
+	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
 	}
 	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]);
-	if (boundFrameBuffer != fbo) {
+	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, boundFrameBuffer);
 	}
 	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index ba373d88f8a..1c922b86e41 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -108,7 +108,7 @@ public:
 public:
 	uint32 id;
 	int width, height;
-	uint32 fbo;
+	uint fbo;
 };
 
 class RenderTexture : public Texture {
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 1cc7745b3be..cb1113e615e 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -419,7 +419,7 @@ Common::JSONValue *GGHashMapDecoder::readArray() {
 		error("trying to parse a non-array");
 	Common::JSONArray arr;
 	uint32 length = _stream->readUint32LE();
-	for (int i = 0; i < length; i++) {
+	for (size_t i = 0; i < length; i++) {
 		Common::JSONValue *item = readValue();
 		arr.push_back(item);
 	}
@@ -436,7 +436,7 @@ Common::JSONValue *GGHashMapDecoder::readHash() {
 		error("trying to parse a non-hash: %d", c);
 	}
 	uint32 nPairs = _stream->readUint32LE();
-	for (int i = 0; i < nPairs; i++) {
+	for (size_t i = 0; i < nPairs; i++) {
 		Common::String key = readString(_stream->readUint32LE());
 		obj[key] = readValue();
 	}
@@ -592,7 +592,7 @@ uint32 XorStream::read(void *dataPtr, uint32 dataSize) {
 	int p = (int)pos();
 	uint32 result = _s->read(dataPtr, dataSize);
 	char *buf = (char *)dataPtr;
-	for (int i = 0; i < dataSize; i++) {
+	for (size_t i = 0; i < dataSize; i++) {
 		int x = buf[i] ^ _key.magicBytes[p & 0x0F] ^ (i * _key.multiplier);
 		buf[i] = (char)(x ^ _previous);
 		_previous = x;
@@ -682,7 +682,7 @@ bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
 }
 
 bool GGPackEntryReader::open(GGPackSet &packs, const Common::String &entry) {
-	for (int i = 0; i < packs._packs.size(); i++) {
+	for (size_t i = 0; i < packs._packs.size(); i++) {
 		GGPackDecoder *pack = &packs._packs[i];
 		if (open(*pack, entry))
 			return true;
@@ -765,7 +765,7 @@ void GGPackSet::init() {
 }
 
 bool GGPackSet::assetExists(const char *asset) {
-	for (int i = 0; i < _packs.size(); i++) {
+	for (size_t i = 0; i < _packs.size(); i++) {
 		GGPackDecoder *pack = &_packs[i];
 		if (pack->assetExists(asset))
 			return true;
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index d7e5eb07693..130849c31dc 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -114,10 +114,10 @@ Graph::Graph() {}
 Graph::Graph(const Graph &graph) {
 	_nodes = graph._nodes;
 	_concaveVertices = graph._concaveVertices;
-	for (int i = 0; i < graph._edges.size(); i++) {
+	for (size_t i = 0; i < graph._edges.size(); i++) {
 		const Common::Array<GraphEdge> &e = graph._edges[i];
 		Common::Array<GraphEdge> sEdges;
-		for (int j = 0; j < e.size(); j++) {
+		for (size_t j = 0; j < e.size(); j++) {
 			const GraphEdge &se = e[j];
 			sEdges.push_back(GraphEdge(se.start, se.to, se.cost));
 		}
@@ -158,7 +158,7 @@ void AStar::search(int source, int target) {
 		_spt[NCN] = _sf[NCN];
 		if (NCN != target) {
 			// for (edge in _graph->edges[NCN]) {
-			for (int i = 0; i < _graph->_edges[NCN].size(); i++) {
+			for (size_t i = 0; i < _graph->_edges[NCN].size(); i++) {
 				GraphEdge &edge = _graph->_edges[NCN][i];
 				float Hcost = length(_graph->_nodes[edge.to] - _graph->_nodes[target]);
 				float Gcost = _gCost[NCN] + edge.cost;
@@ -190,7 +190,7 @@ void Graph::addEdge(GraphEdge e) {
 
 GraphEdge *Graph::edge(int start, int to) {
 	Common::Array<GraphEdge> &edges = _edges[start];
-	for (int i = 0; i < edges.size(); i++) {
+	for (size_t i = 0; i < edges.size(); i++) {
 		GraphEdge *e = &edges[i];
 		if (e->to == to)
 			return e;
@@ -200,7 +200,7 @@ GraphEdge *Graph::edge(int start, int to) {
 
 Common::Array<int> reverse(const Common::Array<int> &arr) {
 	Common::Array<int> result(arr.size());
-	for (int i = 0; i < arr.size(); i++) {
+	for (size_t i = 0; i < arr.size(); i++) {
 		result[arr.size() - 1 - i] = arr[i];
 	}
 	return result;
@@ -241,7 +241,7 @@ static bool inside(const Walkbox &self, Math::Vector2d position, bool toleranceO
 	Math::Vector2d oldPoint(polygon[polygon.size() - 1]);
 	float oldSqDist = distanceSquared(oldPoint, point);
 
-	for (int i = 0; i < polygon.size(); i++) {
+	for (size_t i = 0; i < polygon.size(); i++) {
 		Math::Vector2d newPoint = polygon[i];
 		float newSqDist = distanceSquared(newPoint, point);
 
@@ -273,7 +273,7 @@ Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
 	float minDist = 100000.0f;
 
 	const Common::Array<Math::Vector2d> &polygon = getPoints();
-	for (int i = 0; i < polygon.size(); i++) {
+	for (size_t i = 0; i < polygon.size(); i++) {
 		float dist = distanceToSegment(p3, polygon[i], polygon[(i + 1) % polygon.size()]);
 		if (dist < minDist) {
 			minDist = dist;
@@ -305,7 +305,9 @@ Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
 }
 
 static bool less(Math::Vector2d p1, Math::Vector2d p2) {
-	return ((p1.getX() < p2.getX() - EPSILON) || (abs(p1.getX() - p2.getX()) < EPSILON) && (p1.getY() < p2.getY() - EPSILON));
+	return (((p1.getX() < p2.getX() - EPSILON) ||
+			 (abs(p1.getX() - p2.getX()) < EPSILON)) &&
+			(p1.getY() < p2.getY() - EPSILON));
 }
 
 static float det(float a, float b, float c, float d) {
@@ -368,7 +370,7 @@ bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 		return true;
 
 	// Not in LOS if any edge is intersected by the start-end line segment
-	for (int i = 0; i < _walkboxes.size(); i++) {
+	for (size_t i = 0; i < _walkboxes.size(); i++) {
 		Walkbox &walkbox = _walkboxes[i];
 		const Common::Array<Math::Vector2d> &polygon = walkbox.getPoints();
 		int size = polygon.size();
@@ -387,7 +389,7 @@ bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 	// Finally the middle point in the segment determines if in LOS or not
 	Math::Vector2d v2 = (start + to) / 2.0f;
 	bool result = _walkboxes[0].contains(v2);
-	for (int i = 1; i < _walkboxes.size(); i++) {
+	for (size_t i = 1; i < _walkboxes.size(); i++) {
 		if (_walkboxes[i].contains(v2, false))
 			result = false;
 	}
@@ -397,7 +399,7 @@ bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 static int minIndex(const Common::Array<float> values) {
 	float min = values[0];
 	int index = 0;
-	for (int i = 1; i < values.size(); i++) {
+	for (size_t i = 1; i < values.size(); i++) {
 		if (values[i] < min) {
 			index = i;
 			min = values[i];
@@ -408,11 +410,11 @@ static int minIndex(const Common::Array<float> values) {
 
 Graph *PathFinder::createGraph() {
 	Graph *result = new Graph();
-	for (int i = 0; i < _walkboxes.size(); i++) {
+	for (size_t i = 0; i < _walkboxes.size(); i++) {
 		Walkbox &walkbox = _walkboxes[i];
 		if (walkbox.getPoints().size() > 2) {
 			bool visible = walkbox.isVisible();
-			for (int j = 0; j < walkbox.getPoints().size(); j++) {
+			for (size_t j = 0; j < walkbox.getPoints().size(); j++) {
 				if (walkbox.concave(j) == visible) {
 					Math::Vector2d vertex = walkbox.getPoints()[j];
 					result->_concaveVertices.push_back(vertex);
@@ -422,8 +424,8 @@ Graph *PathFinder::createGraph() {
 		}
 	}
 
-	for (int i = 0; i < result->_concaveVertices.size(); i++) {
-		for (int j = 0; j < result->_concaveVertices.size(); j++) {
+	for (size_t i = 0; i < result->_concaveVertices.size(); i++) {
+		for (size_t j = 0; j < result->_concaveVertices.size(); j++) {
 			Math::Vector2d c1(result->_concaveVertices[i]);
 			Math::Vector2d c2(result->_concaveVertices[j]);
 			if (inLineOfSight(c1, c2)) {
@@ -439,7 +441,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 	Common::Array<Math::Vector2d> result;
 	if (_walkboxes.size() > 0) {
 		// find the walkbox where the actor is and put it first
-		for (int i = 0; i < _walkboxes.size(); i++) {
+		for (size_t i = 0; i < _walkboxes.size(); i++) {
 			const Walkbox &wb = _walkboxes[i];
 			if (inside(wb, start) && (i != 0)) {
 				SWAP(_walkboxes[0], _walkboxes[i]);
@@ -450,7 +452,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 		// if no walkbox has been found => find the nearest walkbox
 		if (!inside(_walkboxes[0], start)) {
 			Common::Array<float> dists(_walkboxes.size());
-			for (int i = 0; i < _walkboxes.size(); i++) {
+			for (size_t i = 0; i < _walkboxes.size(); i++) {
 				Walkbox wb = _walkboxes[i];
 				dists[i] = distance(wb.getClosestPointOnEdge(start), start);
 			}
@@ -474,7 +476,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 
 		walkgraph->addNode(start);
 
-		for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
+		for (size_t i = 0; i < walkgraph->_concaveVertices.size(); i++) {
 			Math::Vector2d c = walkgraph->_concaveVertices[i];
 			if (inLineOfSight(start, c))
 				walkgraph->addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
@@ -484,7 +486,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 		int endNodeIndex = walkgraph->_nodes.size();
 		walkgraph->addNode(to);
 
-		for (int i = 0; i < walkgraph->_concaveVertices.size(); i++) {
+		for (size_t i = 0; i < walkgraph->_concaveVertices.size(); i++) {
 			Math::Vector2d c = walkgraph->_concaveVertices[i];
 			if (inLineOfSight(to, c))
 				walkgraph->addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
@@ -494,7 +496,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 			walkgraph->addEdge(GraphEdge(startNodeIndex, endNodeIndex, distance(start, to)));
 
 		Common::Array<int> indices = walkgraph->getPath(startNodeIndex, endNodeIndex);
-		for (int i = 0; i < indices.size(); i++) {
+		for (size_t i = 0; i < indices.size(); i++) {
 			int index = indices[i];
 			result.push_back(walkgraph->_nodes[index]);
 		}
diff --git a/engines/twp/lip.cpp b/engines/twp/lip.cpp
index 029371d4408..a2dd2c0184f 100644
--- a/engines/twp/lip.cpp
+++ b/engines/twp/lip.cpp
@@ -37,7 +37,7 @@ void Lip::load(Common::SeekableReadStream *stream) {
 char Lip::letter(float time) {
 	if (_items.size() == 0)
 		return 'A';
-	for (int i = 0; i < _items.size() - 1; i++) {
+	for (size_t i = 0; i < _items.size() - 1; i++) {
 		if (time < _items[i + 1].time) {
 			return _items[i].letter;
 		}
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index e69271161ea..7123c78cdb2 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -153,7 +153,7 @@ void Object::play(const Common::String &state, bool loop, bool instant) {
 }
 
 bool Object::playCore(const Common::String &state, bool loop, bool instant) {
-	for (int i = 0; i < _anims.size(); i++) {
+	for (size_t i = 0; i < _anims.size(); i++) {
 		ObjectAnimation &anim = _anims[i];
 		if (anim.name == state) {
 			_animFlags = anim.flags;
@@ -172,7 +172,7 @@ bool Object::playCore(const Common::String &state, bool loop, bool instant) {
 
 void Object::showLayer(const Common::String &layer, bool visible) {
 	int index = -1;
-	for (int i = 0; i < _hiddenLayers.size(); i++) {
+	for (size_t i = 0; i < _hiddenLayers.size(); i++) {
 		if (_hiddenLayers[i] == layer) {
 			index = i;
 			break;
@@ -187,7 +187,7 @@ void Object::showLayer(const Common::String &layer, bool visible) {
 			_hiddenLayers.push_back(layer);
 	}
 	if (_node != NULL) {
-		for (int i = 0; i < _node->getChildren().size(); i++) {
+		for (size_t i = 0; i < _node->getChildren().size(); i++) {
 			Node *node = _node->getChildren()[i];
 			if (node->getName() == layer) {
 				node->setVisible(visible);
@@ -199,7 +199,7 @@ void Object::showLayer(const Common::String &layer, bool visible) {
 Facing Object::getFacing() const {
 	if (_facingLockValue != 0)
 		return (Facing)_facingLockValue;
-	for (int i = 0; i < _facingMap.size(); i++) {
+	for (size_t i = 0; i < _facingMap.size(); i++) {
 		if (_facingMap[i].key == _facing)
 			return _facingMap[i].value;
 	}
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 91a6ad67513..5c9b5454a95 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -253,7 +253,7 @@ public:
 	bool _lit = false;
 	Object *_owner = nullptr;
 	Common::Array<Object *> _inventory;
-	int _inventoryOffset = 0;
+    int _inventoryOffset = 0;
 	Common::StringArray _icons;
 	int _iconFps = 0;
 	int _iconIndex = 0;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 4271e887f35..04f3c414d9e 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -72,13 +72,13 @@ static Math::Vector2d parseParallax(const Common::JSONValue &v) {
 
 static Walkbox parseWalkbox(const Common::String &text) {
 	Common::Array<Math::Vector2d> points;
-	int i = 1;
-	int endPos;
+    size_t i = 1;
+    size_t endPos;
 	do {
 		uint32 commaPos = text.find(',', i);
 		long x = strtol(text.substr(i, commaPos - i).c_str(), nullptr, 10);
 		endPos = text.find('}', commaPos + 1);
-		long y = std::strtol(text.substr(commaPos + 1, endPos - commaPos - 1).c_str(), nullptr, 10);
+		long y = strtol(text.substr(commaPos + 1, endPos - commaPos - 1).c_str(), nullptr, 10);
 		i = endPos + 3;
 		points.push_back({(float)x, (float)y});
 	} while ((text.size() - 1) != endPos);
@@ -100,7 +100,7 @@ static Scaling parseScaling(const Common::JSONArray &jScalings) {
 static ClipperLib::Path toPolygon(const Walkbox &walkbox) {
 	ClipperLib::Path path;
 	const Common::Array<Math::Vector2d> &points = walkbox.getPoints();
-	for (int i = 0; i < points.size(); i++) {
+	for (size_t i = 0; i < points.size(); i++) {
 		path.push_back(ClipperLib::IntPoint(points[i].getX(), points[i].getY()));
 	}
 	return path;
@@ -108,7 +108,7 @@ static ClipperLib::Path toPolygon(const Walkbox &walkbox) {
 
 static Walkbox toWalkbox(const ClipperLib::Path &path) {
 	Common::Array<Math::Vector2d> pts;
-	for (int i = 0; i < path.size(); i++) {
+	for (size_t i = 0; i < path.size(); i++) {
 		const ClipperLib::IntPoint &pt = path[i];
 		pts.push_back(Math::Vector2d(pt.X, pt.Y));
 	}
@@ -119,7 +119,7 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 	Common::Array<Walkbox> result;
 	if (walkboxes.size() > 0) {
 		ClipperLib::Paths subjects, clips;
-		for (int i = 0; i < walkboxes.size(); i++) {
+		for (size_t i = 0; i < walkboxes.size(); i++) {
 			const Walkbox &wb = walkboxes[i];
 			if (wb.isVisible()) {
 				subjects.push_back(toPolygon(wb));
@@ -134,7 +134,7 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 		c.AddPaths(clips, ClipperLib::ptClip, true);
 		c.Execute(ClipperLib::ClipType::ctDifference, solutions, ClipperLib::pftEvenOdd);
 
-		for (int i = 0; i < solutions.size(); i++) {
+		for (size_t i = 0; i < solutions.size(); i++) {
 			result.push_back(toWalkbox(solutions[i]));
 		}
 	}
@@ -149,7 +149,7 @@ Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 }
 
 Room::~Room() {
-	for (int i = 0; i < _layers.size(); i++) {
+	for (size_t i = 0; i < _layers.size(); i++) {
 		delete _layers[i];
 	}
 	delete _scene;
@@ -271,7 +271,7 @@ void Room::load(Common::SeekableReadStream &s) {
 		backNames.push_back(jRoom["background"]->asString());
 	} else {
 		const Common::JSONArray &jBacks = jRoom["background"]->asArray();
-		for (int i = 0; i < jBacks.size(); i++) {
+		for (size_t i = 0; i < jBacks.size(); i++) {
 			backNames.push_back(jBacks[i]->asString());
 		}
 	}
@@ -284,12 +284,12 @@ void Room::load(Common::SeekableReadStream &s) {
 	// layers
 	if (jRoom.contains("layers")) {
 		const Common::JSONArray &jLayers = jRoom["layers"]->asArray();
-		for (int i = 0; i < jLayers.size(); i++) {
+		for (size_t i = 0; i < jLayers.size(); i++) {
 			Common::StringArray names;
 			const Common::JSONObject &jLayer = jLayers[i]->asObject();
 			if (jLayer["name"]->isArray()) {
 				const Common::JSONArray &jNames = jLayer["name"]->asArray();
-				for (int j = 0; j < jNames.size(); j++) {
+				for (size_t j = 0; j < jNames.size(); j++) {
 					names.push_back(jNames[j]->asString());
 				}
 			} else if (jLayer["name"]->isString()) {
@@ -370,7 +370,7 @@ void Room::load(Common::SeekableReadStream &s) {
 
 	// Fix room size (why ?)
 	int width = 0;
-	for (int i = 0; i < backNames.size(); i++) {
+	for (size_t i = 0; i < backNames.size(); i++) {
 		Common::String name = backNames[i];
 		width += g_engine->_resManager.spriteSheet(_sheet)->frameTable[name].sourceSize.getX();
 	}
@@ -380,7 +380,7 @@ void Room::load(Common::SeekableReadStream &s) {
 }
 
 Layer *Room::layer(int zsort) {
-	for (int i = 0; i < _layers.size(); i++) {
+	for (size_t i = 0; i < _layers.size(); i++) {
 		Layer *l = _layers[i];
 		if (l->_zsort == zsort)
 			return l;
@@ -402,9 +402,9 @@ Math::Vector2d Room::getScreenSize() {
 }
 
 Object *Room::getObj(const Common::String &key) {
-	for (int i = 0; i < _layers.size(); i++) {
+	for (size_t i = 0; i < _layers.size(); i++) {
 		Layer *layer = _layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (obj->_key == key)
 				return obj;
@@ -455,9 +455,9 @@ void Room::update(float elapsed) {
 		_overlayTo->update(elapsed);
 	// if (_rotateTo)
 	// 	_rotateTo->update(elapsedSec);
-	for (int j = 0; j < _layers.size(); j++) {
+	for (size_t j = 0; j < _layers.size(); j++) {
 		Layer *layer = _layers[j];
-		for (int k = 0; k < layer->_objects.size(); k++) {
+		for (size_t k = 0; k < layer->_objects.size(); k++) {
 			Object *obj = layer->_objects[k];
 			obj->update(elapsed);
 		}
@@ -465,7 +465,7 @@ void Room::update(float elapsed) {
 }
 
 void Room::walkboxHidden(const Common::String &name, bool hidden) {
-	for (int i = 0; i < _walkboxes.size(); i++) {
+	for (size_t i = 0; i < _walkboxes.size(); i++) {
 		Walkbox &wb = _walkboxes[i];
 		if (wb._name == name) {
 			wb.setVisible(!hidden);
@@ -528,7 +528,7 @@ bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
 	Math::Vector2d oldPoint(_polygon[_polygon.size() - 1]);
 	float oldSqDist = distanceSquared(oldPoint, point);
 
-	for (int i = 0; i < _polygon.size(); i++) {
+	for (size_t i = 0; i < _polygon.size(); i++) {
 		Math::Vector2d newPoint = _polygon[i];
 		float newSqDist = distanceSquared(newPoint, point);
 
@@ -557,7 +557,7 @@ bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
 float Scaling::getScaling(float yPos) {
 	if (values.size() == 0)
 		return 1.0f;
-	for (int i = 0; i < values.size(); i++) {
+	for (size_t i = 0; i < values.size(); i++) {
 		ScalingValue scaling = values[i];
 		if (yPos < scaling.y) {
 			if (i == 0)
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 05e5df03dd6..13ad1d1d39e 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -78,7 +78,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
 	}
 	const Common::Array<Walkbox> &walkboxes = g_engine->_room->_walkboxes;
-	for (int i = 0; i < walkboxes.size(); i++) {
+	for (size_t i = 0; i < walkboxes.size(); i++) {
 		const Walkbox &walkbox = walkboxes[i];
 		if (walkbox.contains(pos1)) {
 			sqpush(v, pos1);
@@ -228,7 +228,7 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 2, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		if (room->_name == name) {
 			sqpush(v, room->_table);
@@ -252,7 +252,7 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 // }
 static SQInteger masterRoomArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		sq_pushobject(v, room->_table);
 		sq_arrayappend(v, -2);
@@ -268,7 +268,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 		sq_resetobject(&closure);
 		if (SQ_FAILED(sqget(v, 3, closure)))
 			return sq_throwerror(v, "failed to get closure");
-		for (int i = 0; i < g_engine->_room->_triggers.size(); i++) {
+		for (size_t i = 0; i < g_engine->_room->_triggers.size(); i++) {
 			Object *trigger = g_engine->_room->_triggers[i];
 			if ((trigger->_enter._unVal.pClosure == closure._unVal.pClosure) || (trigger->_leave._unVal.pClosure == closure._unVal.pClosure)) {
 				g_engine->_room->_triggers.remove_at(i);
@@ -279,8 +279,8 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 		Object *obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
-		int i = find(g_engine->_room->_triggers, obj);
-		if (i != -1) {
+        size_t i = find(g_engine->_room->_triggers, obj);
+		if (i != (size_t)-1) {
 			debug("Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
 			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
 		}
@@ -303,7 +303,7 @@ static SQInteger roomActors(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get room");
 
 	sq_newarray(v, 0);
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (actor->_room == room) {
 			sqpush(v, actor->_table);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index f27be80d5f1..c9009cfd486 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -32,7 +32,7 @@ namespace Twp {
 const static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
 
 static Object *actor(const Common::String &key) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *a = g_engine->_actors[i];
 		if (a->_key == key)
 			return a;
@@ -41,7 +41,7 @@ static Object *actor(const Common::String &key) {
 }
 
 static Object *actor(int id) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *a = g_engine->_actors[i];
 		if (a->getId() == id)
 			return a;
@@ -69,7 +69,7 @@ static DialogConditionMode parseMode(char mode) {
 
 static DialogConditionState parseState(Common::String &dialog) {
 	Common::String dialogName;
-	int i = 1;
+    size_t i = 1;
 	while (i < dialog.size() && !Common::isDigit(dialog[i])) {
 		dialogName += dialog[i];
 		i++;
@@ -95,7 +95,7 @@ static DialogConditionState parseState(Common::String &dialog) {
 }
 
 static Room *room(const Common::String &name) {
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		if (room->_name == name) {
 			return room;
@@ -105,9 +105,9 @@ static Room *room(const Common::String &name) {
 }
 
 static Object *object(Room *room, const Common::String &key) {
-	for (int i = 0; i < room->_layers.size(); i++) {
+	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Layer *layer = room->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *o = layer->_objects[j];
 			if (o->_key == key)
 				return o;
@@ -117,11 +117,11 @@ static Object *object(Room *room, const Common::String &key) {
 }
 
 static Object *object(const Common::String &key) {
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
-		for (int j = 0; j < room->_layers.size(); j++) {
+		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Layer *layer = room->_layers[j];
-			for (int k = 0; k < layer->_objects.size(); k++) {
+			for (size_t k = 0; k < layer->_objects.size(); k++) {
 				Object *o = layer->_objects[k];
 				if (o->_key == key)
 					return o;
@@ -151,7 +151,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 	} else if (json->isArray()) {
 		sq_newarray(v, 0);
 		const Common::JSONArray &jArr = json->asArray();
-		for (int i = 0; i < jArr.size(); i++) {
+		for (size_t i = 0; i < jArr.size(); i++) {
 			HSQOBJECT tmp;
 			toSquirrel(jArr[i], tmp);
 			sqpush(v, tmp);
@@ -370,7 +370,7 @@ static void loadRoom(Room *room, const Common::JSONObject &json) {
 }
 
 static void setActor(const Common::String &key) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *a = g_engine->_actors[i];
 		if (a->_key == key) {
 			g_engine->setActor(a, false);
@@ -454,7 +454,7 @@ void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
 	g_engine->_actorSwitcher._mode = mode;
 	// TODO: tmpPrefs().forceTalkieText = json["forceTalkieText"].getInt() != 0;
 	const Common::JSONArray &jSelectableActors = json["selectableActors"]->asArray();
-	for (int i = 0; i < jSelectableActors.size(); i++) {
+	for (size_t i = 0; i < jSelectableActors.size(); i++) {
 		const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
 		Object *act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
 		g_engine->_hud._actorSlots[i].actor = act;
@@ -487,7 +487,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 	g_engine->_callbacks.clear();
 	if (!json["callbacks"]->isNull()) {
 		const Common::JSONArray &jCallbacks = json["callbacks"]->asArray();
-		for (int i = 0; i < jCallbacks.size(); i++) {
+		for (size_t i = 0; i < jCallbacks.size(); i++) {
 			const Common::JSONObject &jCallBackHash = jCallbacks[i]->asObject();
 			int id = jCallBackHash["guid"]->asIntegerNumber();
 			float time = ((float)jCallBackHash["time"]->asIntegerNumber()) / 1000.f;
@@ -518,7 +518,7 @@ void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
 }
 
 void SaveGameManager::loadActors(const Common::JSONObject &json) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *a = g_engine->_actors[i];
 		if (a->_key.size() > 0) {
 			loadActor(a, json[a->_key]->asObject());
@@ -538,7 +538,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 				if (jSlot.contains("objects")) {
 					if (jSlot["objects"]->isArray()) {
 						const Common::JSONArray &jSlotObjects = jSlot["objects"]->asArray();
-						for (int j = 0; j < jSlotObjects.size(); j++) {
+						for (size_t j = 0; j < jSlotObjects.size(); j++) {
 							const Common::JSONValue *jObj = jSlotObjects[j];
 							Object *obj = object(jObj->asString());
 							if (!obj)
@@ -675,7 +675,7 @@ static Common::JSONValue *createJActor(Object *actor) {
 
 static Common::JSONValue *createJActors() {
 	Common::JSONObject jActors;
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (actor->_key.size() > 0) {
 			jActors[actor->_key] = createJActor(actor);
@@ -692,7 +692,7 @@ static Common::JSONValue *createJCallback(const Callback &callback) {
 	result["time"] = new Common::JSONValue(MAX(0.0, (double)(callback.getDuration() - callback.getElapsed())));
 	Common::JSONArray jArgs;
 	const Common::Array<HSQOBJECT> &args = callback.getArgs();
-	for (int i = 0; i < args.size(); i++) {
+	for (size_t i = 0; i < args.size(); i++) {
 		jArgs.push_back(tojson(args[i], false));
 	}
 	if (jArgs.size() > 0)
@@ -702,7 +702,7 @@ static Common::JSONValue *createJCallback(const Callback &callback) {
 
 static Common::JSONValue *createJCallbackArray() {
 	Common::JSONArray result;
-	for (int i = 0; i < g_engine->_callbacks.size(); i++) {
+	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
 		result.push_back(createJCallback(*g_engine->_callbacks[i]));
 	}
 	return new Common::JSONValue(result);
@@ -742,7 +742,7 @@ Common::String createJDlgStateKey(const DialogConditionState &state) {
 
 static Common::JSONValue *createJDialog() {
 	Common::JSONObject json;
-	for (int i = 0; i < g_engine->_dialog._states.size(); i++) {
+	for (size_t i = 0; i < g_engine->_dialog._states.size(); i++) {
 		const DialogConditionState &state = g_engine->_dialog._states[i];
 		if (state.mode != TempOnce) {
 			// TODO: value should be 1 or another value ?
@@ -809,7 +809,7 @@ static Common::JSONValue *createJInventory(const ActorSlot &slot) {
 		Common::JSONArray jiggleArray;
 		bool anyJiggle = false;
 		//  for (obj in slot.actor.inventory) {
-		for (int i = 0; i < slot.actor->_inventory.size(); i++) {
+		for (size_t i = 0; i < slot.actor->_inventory.size(); i++) {
 			Object *obj = slot.actor->_inventory[i];
 			// TODO: jiggle
 			// let jiggle = obj.getJiggle()
@@ -896,7 +896,7 @@ static Common::JSONValue *createJRoom(Room *room) {
 
 static Common::JSONValue *createJRooms() {
 	Common::JSONObject json;
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		if (room)
 			json[room->_name] = createJRoom(room);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 7008efaf820..423f5db5889 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -79,7 +79,7 @@ const Node *Node::getRoot() const {
 }
 
 int Node::find(Node *other) {
-	for (int i = 0; i < _children.size(); i++) {
+	for (size_t i = 0; i < _children.size(); i++) {
 		if (_children[i] == other) {
 			return i;
 		}
@@ -96,7 +96,7 @@ void Node::removeChild(Node *child) {
 void Node::clear() {
 	if (_children.size() > 0) {
 		Common::Array<Node *> children(_children);
-		for (int i = 0; i < children.size(); i++) {
+		for (size_t i = 0; i < children.size(); i++) {
 			children[i]->remove();
 		}
 	}
@@ -139,7 +139,7 @@ void Node::updateColor(Color parentColor) {
 	_computedColor.rgba.g = _color.rgba.g * parentColor.rgba.g;
 	_computedColor.rgba.b = _color.rgba.b * parentColor.rgba.b;
 	onColorUpdated(_computedColor);
-	for (int i = 0; i < _children.size(); i++) {
+	for (size_t i = 0; i < _children.size(); i++) {
 		Node *child = _children[i];
 		child->updateColor(_computedColor);
 	}
@@ -148,7 +148,7 @@ void Node::updateColor(Color parentColor) {
 void Node::updateAlpha(float parentAlpha) {
 	_computedColor.rgba.a = _color.rgba.a * parentAlpha;
 	onColorUpdated(_computedColor);
-	for (int i = 0; i < _children.size(); i++) {
+	for (size_t i = 0; i < _children.size(); i++) {
 		Node *child = _children[i];
 		child->updateAlpha(_computedColor.rgba.a);
 	}
@@ -187,7 +187,7 @@ void Node::draw(Math::Matrix4 parent) {
 		Common::Array<Node *> children(_children);
 		Common::sort(children.begin(), children.end(), cmpNodes);
 		drawCore(myTrsf);
-		for (int i = 0; i < children.size(); i++) {
+		for (size_t i = 0; i < children.size(); i++) {
 			Node *child = children[i];
 			child->draw(trsf);
 		}
@@ -245,7 +245,7 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 	Math::Matrix4 t = trsf;
 	float x = 0.f;
-	for (int i = 0; i < _frames.size(); i++) {
+	for (size_t i = 0; i < _frames.size(); i++) {
 		const SpriteSheetFrame &frame = sheet->frameTable[_frames[i]];
 		Math::Matrix4 myTrsf = t;
 		myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
@@ -275,10 +275,10 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
 	_loop = loop || anim->loop;
 	_instant = instant;
-	if(_obj) setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == -1);
+	if(_obj) setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
 
 	clear();
-	for (int i = 0; i < _anim->layers.size(); i++) {
+	for (size_t i = 0; i < _anim->layers.size(); i++) {
 		const ObjectAnimation &layer = _anim->layers[i];
 		Anim *node = new Anim(_obj);
 		node->setAnim(&layer, fps, loop, instant);
@@ -297,7 +297,7 @@ void Anim::trigSound() {
 
 void Anim::update(float elapsed) {
 	if (_anim)
-		setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == -1);
+		setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
 	if (_instant)
 		disable();
 	else if (_frames.size() != 0) {
@@ -323,7 +323,7 @@ void Anim::update(float elapsed) {
 		}
 	} else if (_children.size() != 0) {
 		bool disabled = true;
-		for (int i = 0; i < _children.size(); i++) {
+		for (size_t i = 0; i < _children.size(); i++) {
 			Anim *layer = static_cast<Anim *>(_children[i]);
 			layer->update(elapsed);
 			disabled = disabled && layer->_disabled;
@@ -497,7 +497,7 @@ static bool hasUpArrow(Object *actor) {
 }
 
 static bool hasDownArrow(Object *actor) {
-	return actor->_inventory.size() > (actor->_inventoryOffset * NUMOBJECTSBYROW + NUMOBJECTS);
+	return actor->_inventory.size() > (size_t)(actor->_inventoryOffset * NUMOBJECTSBYROW + NUMOBJECTS);
 }
 
 Inventory::Inventory() : Node("Inventory") {
@@ -572,7 +572,7 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 	float startOffsetY = MARGINBOTTOM + 1.5f * BACKHEIGHT + BACKOFFSET;
 	SpriteSheet *itemsSheet = g_engine->_resManager.spriteSheet("InventoryItems");
 	Texture *texture = g_engine->_resManager.texture(itemsSheet->meta.image);
-	int count = MIN(NUMOBJECTS, (int)_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW);
+	int count = MIN(NUMOBJECTS, (int)(_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW));
 
 	for (int i = 0; i < count; i++) {
 		Object *obj = _actor->_inventory[_actor->_inventoryOffset * NUMOBJECTSBYROW + i];
@@ -623,14 +623,14 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 		for (int i = 0; i < NUMOBJECTS; i++) {
 			const Common::Rect &item = _itemRects[i];
 			if (item.contains(scrPos.getX(), scrPos.getY())) {
-				int index = _actor->_inventoryOffset * NUMOBJECTSBYROW + i;
+                size_t index = _actor->_inventoryOffset * NUMOBJECTSBYROW + i;
 				if (index < _actor->_inventory.size())
 					_obj = _actor->_inventory[index];
 				break;
 			}
 		}
 
-		for (int i = 0; i < _actor->_inventory.size(); i++) {
+		for (size_t i = 0; i < _actor->_inventory.size(); i++) {
 			Object *obj = _actor->_inventory[i];
 			obj->update(elapsed);
 		}
@@ -736,9 +736,9 @@ void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
 	SpriteSheetFrame *frame = &gameSheet->frameTable["hotspot_marker"];
 	Color color = Color::create(255, 165, 0);
-	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 		Layer *layer = g_engine->_room->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
 				Math::Vector2d pos = g_engine->roomToScreen(obj->_node->getAbsPos());
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 175484a60ae..7d7c290b772 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -174,7 +174,7 @@ public:
 private:
 	Common::String _sheet;
 	Common::Array<Common::String> _frames;
-	int _frameIndex = 0;
+    size_t _frameIndex = 0;
 	float _elapsed = 0.f;
 	float _frameDuration = 0.f;
 	bool _loop = false;
diff --git a/engines/twp/squirrel/sqvm.cpp b/engines/twp/squirrel/sqvm.cpp
index 4644a518a6d..5bca95e9b71 100755
--- a/engines/twp/squirrel/sqvm.cpp
+++ b/engines/twp/squirrel/sqvm.cpp
@@ -1741,8 +1741,8 @@ SQObjectPtr &SQVM::GetAt(SQInteger n) { CheckStackAccess(n); return _stack[n]; }
 
 void SQVM::CheckStackAccess(SQInteger n) {
     if(n < 0 || n >= _stack.size()){
-        std::ostringstream s;
-        s << "Stack of the VM accessed with n=" << n << " and stacksize=" << _stack.size();
+        //std::ostringstream s;
+        //s << "Stack of the VM accessed with n=" << n << " and stacksize=" << _stack.size();
         //throw std::out_of_range(s.str());
 		exit(-1);
     }
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 74fc2fb3f4f..49ff423ecfd 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -256,7 +256,7 @@ void sqcall(const char *name, const Common::Array<HSQOBJECT> &args) {
 	sqpushfunc(v, o, name);
 
 	sq_pushobject(v, o);
-	for (int i = 0; i < args.size(); i++) {
+	for (size_t i = 0; i < args.size(); i++) {
 		sq_pushobject(v, args[i]);
 	}
 	sq_call(v, 1 + args.size(), SQFalse, SQTrue);
@@ -275,7 +275,7 @@ Room *sqroom(HSQOBJECT table) {
 }
 
 Room *getRoom(int id) {
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
 		if (getId(room->_table) == id)
 			return room;
@@ -292,17 +292,17 @@ Room *sqroom(HSQUIRRELVM v, int i) {
 }
 
 Object *sqobj(int id) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (getId(actor->_table) == id)
 			return actor;
 	}
 
-	for (int i = 0; i < g_engine->_rooms.size(); i++) {
+	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Room *room = g_engine->_rooms[i];
-		for (int j = 0; j < room->_layers.size(); j++) {
+		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Layer *layer = room->_layers[j];
-			for (int k = 0; k < layer->_objects.size(); k++) {
+			for (size_t k = 0; k < layer->_objects.size(); k++) {
 				Object *obj = layer->_objects[k];
 				if (getId(obj->_table) == id)
 					return obj;
@@ -325,7 +325,7 @@ Object *sqobj(HSQUIRRELVM v, int i) {
 
 Object *sqactor(HSQOBJECT table) {
 	int id = getId(table);
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Object *actor = g_engine->_actors[i];
 		if (actor->getId() == id)
 			return actor;
@@ -341,7 +341,7 @@ Object *sqactor(HSQUIRRELVM v, int i) {
 }
 
 SoundDefinition *sqsounddef(int id) {
-	for (int i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
+	for (size_t i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
 		SoundDefinition *sound = g_engine->_audio._soundDefs[i];
 		if (sound->getId() == id)
 			return sound;
@@ -407,7 +407,7 @@ ThreadBase *sqthread(int id) {
 		}
 	}
 
-	for (int i = 0; i < g_engine->_threads.size(); i++) {
+	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
 		ThreadBase *t = g_engine->_threads[i];
 		if (t->getId() == id) {
 			return t;
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index d79b89adb08..2be7b7085fe 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -24,6 +24,7 @@
 
 #include "squirrel/squirrel.h"
 #include "common/str.h"
+#include "common/util.h"
 #include "twp/twp.h"
 #include "twp/vm.h"
 
@@ -172,13 +173,13 @@ ThreadBase *sqthread(int id);
 
 template<typename... T>
 void sqcall(HSQUIRRELVM v, HSQOBJECT o, const char *name, T... args) {
-	constexpr std::size_t n = sizeof...(T);
+	constexpr size_t n = sizeof...(T);
 	SQInteger top = sq_gettop(v);
 	sqpushfunc(v, o, name);
 
 	sq_pushobject(v, o);
 	if (n > 0) {
-		sqpush(v, std::forward<T>(args)...);
+		sqpush(v, Common::forward<T>(args)...);
 	}
 	sq_call(v, 1 + n, SQFalse, SQTrue);
 	sq_settop(v, top);
@@ -186,14 +187,14 @@ void sqcall(HSQUIRRELVM v, HSQOBJECT o, const char *name, T... args) {
 
 template<typename... T>
 void sqcall(HSQOBJECT o, const char *name, T... args) {
-	constexpr std::size_t n = sizeof...(T);
+	constexpr size_t n = sizeof...(T);
 	HSQUIRRELVM v = g_engine->getVm();
 	SQInteger top = sq_gettop(v);
 	sqpushfunc(v, o, name);
 
 	sq_pushobject(v, o);
 	if (n > 0) {
-		sqpush(v, std::forward<T>(args)...);
+		sqpush(v, Common::forward<T>(args)...);
 	}
 	sq_call(v, 1 + n, SQFalse, SQTrue);
 	sq_settop(v, top);
@@ -201,7 +202,7 @@ void sqcall(HSQOBJECT o, const char *name, T... args) {
 
 template<typename... T>
 void sqcall(const char *name, T... args) {
-	constexpr std::size_t n = sizeof...(T);
+	constexpr size_t n = sizeof...(T);
 	HSQUIRRELVM v = g_engine->getVm();
 	HSQOBJECT o = sqrootTbl(v);
 	SQInteger top = sq_gettop(v);
@@ -209,7 +210,7 @@ void sqcall(const char *name, T... args) {
 
 	sq_pushobject(v, o);
 	if (n > 0) {
-		sqpush(v, std::forward<T>(args)...);
+		sqpush(v, Common::forward<T>(args)...);
 	}
 	sq_call(v, 1 + n, SQFalse, SQTrue);
 	sq_settop(v, top);
@@ -217,7 +218,7 @@ void sqcall(const char *name, T... args) {
 
 template<typename TResult, typename... T>
 void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
-	constexpr std::size_t n = sizeof...(T);
+	constexpr size_t n = sizeof...(T);
 	HSQUIRRELVM v = g_engine->getVm();
 	SQInteger top = sq_gettop(v);
 	sqpush(v, o);
@@ -230,7 +231,7 @@ void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
 	sq_remove(v, -2);
 
 	sqpush(v, o);
-	sqpush(v, std::forward<T>(args)...);
+	sqpush(v, Common::forward<T>(args)...);
 	if (SQ_FAILED(sq_call(v, n + 1, SQTrue, SQTrue))) {
 		// sqstd_printcallstack(v);
 		sq_settop(v, top);
@@ -243,7 +244,7 @@ void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
 
 template<typename TResult, typename... T>
 void sqcallfunc(TResult &result, const char *name, T... args) {
-	constexpr std::size_t n = sizeof...(T);
+	constexpr size_t n = sizeof...(T);
 	HSQUIRRELVM v = g_engine->getVm();
 	HSQOBJECT o = sqrootTbl(v);
 	SQInteger top = sq_gettop(v);
@@ -257,7 +258,7 @@ void sqcallfunc(TResult &result, const char *name, T... args) {
 	sq_remove(v, -2);
 
 	sqpush(v, o);
-	sqpush(v, std::forward<T>(args)...);
+	sqpush(v, Common::forward<T>(args)...);
 	if (SQ_FAILED(sq_call(v, n + 1, SQTrue, SQTrue))) {
 		// sqstd_printcallstack(v);
 		sq_settop(v, top);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 8860216a2ce..87c26baca39 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -633,7 +633,7 @@ static SQInteger removeCallback(HSQUIRRELVM v) {
 	int id = 0;
 	if (SQ_FAILED(sqget(v, 2, id)))
 		return sq_throwerror(v, "failed to get callback");
-	for (int i = 0; i < g_engine->_callbacks.size(); i++) {
+	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
 		Callback *cb = g_engine->_callbacks[i];
 		if (cb->getId() == id) {
 			g_engine->_callbacks.remove_at(i);
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index ab625265d83..87ac4eae1fe 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -59,7 +59,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 	_pauseable = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
-	for (int i = 0; i < _args.size(); i++) {
+	for (size_t i = 0; i < _args.size(); i++) {
 		sq_addref(v, &_args[i]);
 	}
 	sq_addref(v, &_threadObj);
@@ -70,7 +70,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 Thread::~Thread() {
 	debug("delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
 	HSQUIRRELVM v = g_engine->getVm();
-	for (int i = 0; i < _args.size(); i++) {
+	for (size_t i = 0; i < _args.size(); i++) {
 		sq_release(v, &_args[i]);
 	}
 	sq_release(v, &_threadObj);
@@ -84,7 +84,7 @@ bool Thread::call() {
 	SQInteger top = sq_gettop(v);
 	sq_pushobject(v, _closureObj);
 	sq_pushobject(v, _envObj);
-	for (int i = 0; i < _args.size(); i++) {
+	for (size_t i = 0; i < _args.size(); i++) {
 		sq_pushobject(v, _args[i]);
 	}
 	if (SQ_FAILED(sq_call(v, 1 + _args.size(), SQFalse, SQTrue))) {
@@ -131,7 +131,7 @@ Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJ
 	debug("Create cutscene %d with input: 0x%X", _id, _inputState);
 	g_engine->_inputState.setInputActive(false);
 	g_engine->_inputState.setShowCursor(false);
-	for (int i = 0; i < g_engine->_threads.size(); i++) {
+	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
 		ThreadBase *thread = g_engine->_threads[i];
 		if (thread->isGlobal())
 			thread->pause();
@@ -175,7 +175,7 @@ void Cutscene::stop() {
 	debug("Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
 	Common::Array<ThreadBase *> threads(g_engine->_threads);
-	for (int i = 0; i < threads.size(); i++) {
+	for (size_t i = 0; i < threads.size(); i++) {
 		ThreadBase *thread = threads[i];
 		if (thread->isGlobal())
 			thread->unpause();
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7d5c0c5a1bc..8d159d128cb 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -288,9 +288,9 @@ template<typename TFunc>
 void objsAt(Math::Vector2d pos, TFunc func) {
 	if (g_engine->_uiInv.getObject() && g_engine->_room->_fullscreen == FULLSCREENROOM)
 		func(g_engine->_uiInv.getObject());
-	for (int i = 0; i < g_engine->_room->_layers.size(); i++) {
+	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 		Layer *layer = g_engine->_room->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if ((obj != g_engine->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
 				if (func(obj))
@@ -312,7 +312,7 @@ Object *inventoryAt(Math::Vector2d pos) {
 }
 
 static void selectSlotActor(int id) {
-	for (int i = 0; i < g_engine->_actors.size(); i++) {
+	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		if (g_engine->_actors[i]->getId() == id) {
 			g_engine->setActor(g_engine->_actors[i]);
 			break;
@@ -747,7 +747,7 @@ Common::Error TwpEngine::run() {
 								actors.push_back(slot->actor);
 							}
 						}
-						int index = find(actors, _actor) + 1;
+                        size_t index = find(actors, _actor) + 1;
 						if (index >= actors.size())
 							index -= actors.size();
 						setActor(actors[index], true);
@@ -941,7 +941,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 		result->load(entry);
 		result->_name = name;
 		result->_pseudo = pseudo;
-		for (int i = 0; i < result->_layers.size(); i++) {
+		for (size_t i = 0; i < result->_layers.size(); i++) {
 			Layer *layer = result->_layers[i];
 			// create layer node
 			ParallaxNode *layerNode = new ParallaxNode(layer->_parallax, result->_sheet, layer->_names);
@@ -950,7 +950,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 			layer->_node = layerNode;
 			result->_scene->addChild(layerNode);
 
-			for (int j = 0; j < layer->_objects.size(); j++) {
+			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Object *obj = layer->_objects[j];
 				if (!sqrawexists(table, obj->_key)) {
 					// this object does not exist, so create it
@@ -980,9 +980,9 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 	}
 
 	// assign parent node
-	for (int i = 0; i < result->_layers.size(); i++) {
+	for (size_t i = 0; i < result->_layers.size(); i++) {
 		Layer *layer = result->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (obj->_parent.size() > 0) {
 				Object *parent = result->getObj(obj->_parent);
@@ -1117,9 +1117,9 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 
 	// call actor enter function and objects enter function
 	actorEnter();
-	for (int i = 0; i < room->_layers.size(); i++) {
+	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Layer *layer = room->_layers[i];
-		for (int j = 0; j < layer->_objects.size(); j++) {
+		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			// add all scaling triggers
 			if (obj->_objType == ObjectType::otTrigger) {
@@ -1180,9 +1180,9 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 		}
 
 		// delete all temporary objects
-		for (int i = 0; i < _room->_layers.size(); i++) {
+		for (size_t i = 0; i < _room->_layers.size(); i++) {
 			Layer *layer = _room->_layers[i];
-			for (int j = 0; j < layer->_objects.size(); j++) {
+			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Object *obj = layer->_objects[j];
 				if (obj->_temporary) {
 					delete obj;
@@ -1196,7 +1196,7 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 		sqcall("exitedRoom", _room->_table);
 
 		// stop all local threads
-		for (int i = 0; i < _threads.size(); i++) {
+		for (size_t i = 0; i < _threads.size(); i++) {
 			ThreadBase *thread = _threads[i];
 			if (!thread->isGlobal()) {
 				thread->stop();
@@ -1480,7 +1480,7 @@ void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
 void TwpEngine::updateTriggers() {
 	if (_actor) {
 		// check if actor enters or leaves an object trigger
-		for (int i = 0; i < _room->_triggers.size(); i++) {
+		for (size_t i = 0; i < _room->_triggers.size(); i++) {
 			Object *trigger = _room->_triggers[i];
 			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
 				debug("call enter trigger %s", trigger->_name.c_str());
@@ -1494,7 +1494,7 @@ void TwpEngine::updateTriggers() {
 		}
 
 		// check if actor enters or leaves a scaling trigger
-		for (int i = 0; i < _room->_scalingTriggers.size(); i++) {
+		for (size_t i = 0; i < _room->_scalingTriggers.size(); i++) {
 			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
 			if (trigger->_obj->_triggerActive && !trigger->_obj->contains(_actor->_node->getAbsPos())) {
 				debug("leave scaling trigger %s", trigger->_obj->_key.c_str());
@@ -1502,7 +1502,7 @@ void TwpEngine::updateTriggers() {
 				_room->_scaling = _room->_scalings[0];
 			}
 		}
-		for (int i = 0; i < _room->_scalingTriggers.size(); i++) {
+		for (size_t i = 0; i < _room->_scalingTriggers.size(); i++) {
 			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
 			if (!trigger->_obj->_triggerActive && trigger->_obj->contains(_actor->_node->getAbsPos())) {
 				debug("enter scaling trigger %s", trigger->_obj->_key.c_str());
@@ -1542,7 +1542,7 @@ void TwpEngine::skipCutscene() {
 }
 
 Scaling *TwpEngine::getScaling(const Common::String &name) {
-	for (int i = 0; i < _room->_scalings.size(); i++) {
+	for (size_t i = 0; i < _room->_scalings.size(); i++) {
 		Scaling *scaling = &_room->_scalings[i];
 		if (scaling->trigger == name) {
 			return scaling;
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index c15474f66d1..636f34f599a 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -160,7 +160,7 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 	Common::String result;
 	if (array.size() > 0) {
 		result += array[0];
-		for (int i = 1; i < array.size(); i++) {
+		for (size_t i = 1; i < array.size(); i++) {
 			result += (sep + array[i]);
 		}
 	}
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 1d5b99f8031..04c4ac60101 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -59,13 +59,13 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 
 // array util
 template<typename T>
-int find(const Common::Array<T>& array, const T& o) {
-	for (int i = 0; i < array.size(); i++) {
+size_t find(const Common::Array<T>& array, const T& o) {
+	for (size_t i = 0; i < array.size(); i++) {
 		if (array[i] == o) {
 			return i;
 		}
 	}
-	return -1;
+	return (size_t)-1;
 }
 
 // string util
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index db6bca7de6f..e70277d2a25 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -91,7 +91,7 @@ PathNode::PathNode() : Node("Path") {
 }
 
 Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
-	for (int i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
+	for (size_t i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
 		Walkbox &wb = g_engine->_room->_mergedPolygon[i];
 		if (!wb.isVisible() && wb.contains(pos)) {
 			return wb.getClosestPointOnEdge(pos);
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 3a5a73fb5bf..7323af6306e 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -452,7 +452,7 @@ YExp *YackParser::parseInstructionExpression() {
 		auto pExp = new YLimit();
 		if (_it->id == YackTokenId::Int) {
 			auto node = _reader.readText(*_it++);
-			pExp->_max = std::strtol(node.c_str(), nullptr, 10);
+			pExp->_max = strtol(node.c_str(), nullptr, 10);
 		}
 		return pExp;
 	}


Commit: b3db6469691e1724a4221afe3367e358b064f00b
    https://github.com/scummvm/scummvm/commit/b3db6469691e1724a4221afe3367e358b064f00b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add some checks and fix some array access

Changed paths:
    engines/twp/audio.cpp
    engines/twp/gfx.cpp
    engines/twp/ggpack.cpp
    engines/twp/motor.cpp
    engines/twp/resmanager.cpp
    engines/twp/savegame.cpp
    engines/twp/util.cpp
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index ce741e4d0e0..953a51a2a2b 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -34,7 +34,7 @@ namespace Twp {
 
 void SoundStream::open(SoundDefinition *sndDef) {
 	sndDef->load();
-	_stream.open(&sndDef->_buffer[0], sndDef->_buffer.size());
+	_stream.open(sndDef->_buffer.data(), sndDef->_buffer.size());
 }
 
 uint32 SoundStream::read(void *dataPtr, uint32 dataSize) {
@@ -65,7 +65,7 @@ void SoundDefinition::load() {
 		GGPackEntryReader entry;
 		entry.open(g_engine->_pack, _name);
 		_buffer.resize(entry.size());
-		entry.read(&_buffer[0], entry.size());
+		entry.read(_buffer.data(), entry.size());
 	}
 }
 
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 6e3d9fd5e1b..63e1eaaf17a 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -99,12 +99,12 @@ void Texture::capture(Graphics::Surface &surface) {
 	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
 	}
-	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]);
+	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
 	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, boundFrameBuffer);
 	}
 	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
-	surface.init(width, height, 4 * width, &pixels[0], fmt);
+	surface.init(width, height, 4 * width, pixels.data(), fmt);
 }
 
 RenderTexture::RenderTexture(Math::Vector2d size) {
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index cb1113e615e..bcdec82fcf7 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -670,15 +670,17 @@ bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
 	pack._s->seek(e.offset);
 
 	RangeStream rs;
-	rs.open(pack._s, e.size);
+	if(!rs.open(pack._s, e.size))
+		return false;
+
 	XorStream xs;
-	xs.open(&rs, e.size, pack._key);
+	if(!xs.open(&rs, e.size, pack._key))
+		return false;
 
 	_buf.resize(e.size);
-	xs.read(&_buf[0], e.size);
+	xs.read(_buf.data(), e.size);
 
-	_ms.open(&_buf[0], e.size);
-	return true;
+	return _ms.open(_buf.data(), e.size);
 }
 
 bool GGPackEntryReader::open(GGPackSet &packs, const Common::String &entry) {
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index b8de94564ed..91bd66ec8fe 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -337,6 +337,8 @@ int Talking::loadActorSpeech(const Common::String &name) {
 }
 
 void Talking::say(const Common::String &text) {
+	if(text.empty()) return;
+
 	Common::String txt(text);
 	if (text[0] == '@') {
 		int id = atoi(text.c_str() + 1);
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 1d6b2b757b4..672830d0886 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -73,9 +73,9 @@ void ResManager::loadSpriteSheet(const Common::String &name) {
 
 	// read all contents
 	Common::Array<char> data(r.size());
-	r.read(&data[0], r.size());
+	r.read(data.data(), r.size());
 
-	Common::String s(&data[0], r.size());
+	Common::String s(data.data(), r.size());
 	_spriteSheets[name].parseSpriteSheet(s);
 }
 
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index c9009cfd486..9399f859a9a 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -422,16 +422,17 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 
 bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame) {
 	Common::Array<byte> data(stream->size());
-	stream->read(&data[0], data.size());
-	BTEACrypto::decrypt((uint32 *)&data[0], data.size() / 4, savegameKey);
+	stream->read(data.data(), data.size());
+	BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
 	savegame.hashData = *(int32_t *)(&data[data.size() - 16]);
 	savegame.time = *(int32_t *)&data[data.size() - 12];
-	int32_t hashCheck = computeHash(&data[0], data.size() - 16);
+	int32_t hashCheck = computeHash(data.data(), data.size() - 16);
 	if (savegame.hashData != hashCheck)
 		return false;
 
 	MemStream ms;
-	ms.open(&data[0], data.size() - 16);
+	if(!ms.open(data.data(), data.size() - 16))
+		return false;
 
 	GGHashMapDecoder decoder;
 	savegame.jSavegame = decoder.open(&ms);
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 636f34f599a..6dec8af3096 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -167,26 +167,27 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 	return result;
 }
 
-Common::String replace(const Common::String& s, const Common::String& what, const Common::String& by) {
+Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by) {
 	Common::String result;
 	uint i = 0;
 	size_t whatSize = what.size();
 	while (true) {
-      uint j = s.find(what, i);
-      if (j == Common::String::npos) break;
-      result += s.substr(i, j - i);
-      result += by;
-      i = j + whatSize;
+		uint j = s.find(what, i);
+		if (j == Common::String::npos)
+			break;
+		result += s.substr(i, j - i);
+		result += by;
+		i = j + whatSize;
 	}
-    result += s.substr(i);
+	result += s.substr(i);
 	return result;
 }
 
 Common::String remove(const Common::String &txt, char startC, char endC) {
-	if (txt[0] == startC) {
+	if ((txt.size() > 0) && txt[0] == startC) {
 		uint32 i = txt.find(endC);
 		if (i != Common::String::npos) {
-			return txt.substr(i+1);
+			return txt.substr(i + 1);
 		}
 	}
 	return txt;
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index e70277d2a25..128796bdf38 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -55,7 +55,7 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
 				}
-				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
+				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
 		} break;
 		case WalkboxMode::Merged: {
@@ -77,7 +77,7 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
 				}
-				g_engine->getGfx().drawLinesLoop(&vertices[0], vertices.size(), transf);
+				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
 		} break;
 		default:
@@ -124,7 +124,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 				t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 				g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 			}
-			g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+			g_engine->getGfx().drawLines(vertices.data(), vertices.size());
 		}
 	}
 
@@ -182,7 +182,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 			vertices.push_back(Vertex(g_engine->roomToScreen(path[i]), yellow));
 		}
 		if (vertices.size() > 0) {
-			g_engine->getGfx().drawLines(&vertices[0], vertices.size());
+			g_engine->getGfx().drawLines(vertices.data(), vertices.size());
 		}
 	}
 }


Commit: 20cd0507e1bf238dfcbba0f35c52e04fafd476ad
    https://github.com/scummvm/scummvm/commit/20cd0507e1bf238dfcbba0f35c52e04fafd476ad
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix some compilation errors on Windows

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/savegame.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 25b7c11dba2..dfd16d366c6 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -754,8 +754,8 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
 		Facing *facing = nullptr;
+		int dir;
 		if (nArgs == 5) {
-			int dir;
 			if (SQ_FAILED(sqget(v, 5, dir)))
 				return sq_throwerror(v, "failed to get dir");
 			facing = (Facing *)&dir;
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 9399f859a9a..5c19c51cf47 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -922,7 +922,7 @@ static Common::JSONValue *createSaveGame() {
 	json["objects"] = createJObjects();
 	json["rooms"] = createJRooms();
 	json["savebuild"] = new Common::JSONValue(958LL);
-	json["savetime"] = new Common::JSONValue(getTime());
+	json["savetime"] = new Common::JSONValue((long long)getTime());
 	json["selectedActor"] = new Common::JSONValue(g_engine->_actor ? g_engine->_actor->_key : "");
 	json["version"] = new Common::JSONValue((long long int)2);
 	return new Common::JSONValue(json);


Commit: 49b359b567205bbb2a39f3a3df0e056aab96946e
    https://github.com/scummvm/scummvm/commit/49b359b567205bbb2a39f3a3df0e056aab96946e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove lambdas

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/audio.cpp
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/savegame.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index dfd16d366c6..28df21703f2 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -812,6 +812,17 @@ static SQInteger flashSelectableActor(HSQUIRRELVM v) {
 	return 0;
 }
 
+struct GetStrings {
+	GetStrings(Common::StringArray &texts) : _texts(texts) {}
+
+	void operator()(HSQOBJECT item) {
+		_texts.push_back(sq_objtostring(&item));
+	}
+
+private:
+	Common::StringArray &_texts;
+};
+
 static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 	Object *obj;
 	int index;
@@ -827,7 +838,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 	if (sq_gettype(v, index) == OT_ARRAY) {
 		HSQOBJECT arr;
 		sq_getstackobj(v, index, &arr);
-		sqgetitems(arr, [&](HSQOBJECT item) { texts.push_back(sq_objtostring(&item)); });
+		sqgetitems(arr, GetStrings(texts));
 	} else {
 		int numIds = sq_gettop(v) - index + 1;
 		for (int i = 0; i < numIds; i++) {
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 953a51a2a2b..238c695fe15 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -30,6 +30,10 @@
 #include "twp/ids.h"
 #include "twp/squtil.h"
 
+#ifndef USE_VORBIS
+	#error TWP engine requires USE_VORBIS flag
+#endif
+
 namespace Twp {
 
 void SoundStream::open(SoundDefinition *sndDef) {
@@ -229,18 +233,17 @@ int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int
 	const Common::String &name = sndDef->getName();
 	Audio::SeekableAudioStream *audioStream;
 	if (name.hasSuffixIgnoreCase(".ogg")) {
-#ifdef USE_VORBIS
 		slot->stream.open(sndDef);
 		audioStream = Audio::makeVorbisStream(&slot->stream, DisposeAfterUse::NO);
-#else
-		audioStream = nullptr;
-#endif
 	} else if (name.hasSuffixIgnoreCase(".wav")) {
 		slot->stream.open(sndDef);
 		audioStream = Audio::makeWAVStream(&slot->stream, DisposeAfterUse::NO);
 	} else {
-		error("Unexpedted audio format: %s", name.c_str());
+		error("Unexpected audio format: %s", name.c_str());
 	}
+	if(!audioStream)
+		error("Failed to load audio: %s", name.c_str());
+
 	byte vol = (byte)(volume * 255);
 	int id = newSoundId();
 	if (fadeInTimeMs > 0.f) {
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index bebcf4d4a41..a33428fc6f2 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -403,11 +403,6 @@ static SQInteger integer(HSQUIRRELVM v) {
 	return 1;
 }
 
-static SQInteger is_oftype(HSQUIRRELVM v, bool pred(SQObjectType)) {
-	sqpush(v, pred(sq_gettype(v, 2)));
-	return 1;
-}
-
 static SQInteger in_array(HSQUIRRELVM v) {
 	HSQOBJECT obj;
 	sq_resetobject(&obj);
@@ -445,19 +440,20 @@ static SQInteger in_array(HSQUIRRELVM v) {
 }
 
 static SQInteger is_array(HSQUIRRELVM v) {
-	return is_oftype(v, [](SQObjectType type) { return type == OT_ARRAY; });
+	return sqpush(v, sq_gettype(v, 2) == OT_ARRAY);
 }
 
 static SQInteger is_function(HSQUIRRELVM v) {
-	return is_oftype(v, [](SQObjectType type) { return (type == OT_CLOSURE) || (type == OT_NATIVECLOSURE); });
+	SQObjectType type = sq_gettype(v, 2);
+	return sqpush(v, type == OT_CLOSURE || type == OT_NATIVECLOSURE);
 }
 
 static SQInteger is_string(HSQUIRRELVM v) {
-	return is_oftype(v, [](SQObjectType type) { return type == OT_STRING; });
+	return sqpush(v, sq_gettype(v, 2) == OT_STRING);
 }
 
 static SQInteger is_table(HSQUIRRELVM v) {
-	return is_oftype(v, [](SQObjectType type) { return type == OT_TABLE; });
+	return sqpush(v, sq_gettype(v, 2) == OT_TABLE);
 }
 
 // Returns a random number from from to to inclusively.
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 7123c78cdb2..81d69dc4af8 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -306,6 +306,26 @@ void Object::setIcon(const Common::String &icon) {
 	sqsetf(_table, "icon", icon);
 }
 
+struct GetIcons {
+	GetIcons(int &fps, Common::StringArray &icons) : _fps(fps), _icons(icons) { _fps = 0; }
+	void operator()(HSQOBJECT item) {
+		if (_index == 0) {
+			_fps = sq_objtointeger(&item);
+		} else {
+			Common::String icon = sq_objtostring(&item);
+			_icons.push_back(icon);
+		}
+		_index++;
+	}
+
+public:
+	int &_fps;
+	Common::StringArray &_icons;
+
+private:
+	int _index = 0;
+};
+
 Common::String Object::getIcon() {
 	if (_icons.size() > 0)
 		return _icons[_iconIndex];
@@ -321,18 +341,9 @@ Common::String Object::getIcon() {
 		return result;
 	}
 	if (iconTable._type == OT_ARRAY) {
-		int i = 0;
-		int fps = 0;
+		int fps;
 		Common::StringArray icons;
-		sqgetitems(iconTable, [&](HSQOBJECT &item) {
-			if (i == 0) {
-				fps = sq_objtointeger(&item);
-			} else {
-				Common::String icon = sq_objtostring(&item);
-				icons.push_back(icon);
-			}
-			i++;
-		});
+		sqgetitems(iconTable, GetIcons(fps,icons));
 		setIcon(fps, icons);
 		return getIcon();
 	}
@@ -481,7 +492,7 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 
 	GGHashMapDecoder dec;
 	Common::JSONValue *json = dec.open(&entry);
-	if(!json) {
+	if (!json) {
 		warning("Costume %s(%s) for actor %s not found", name.c_str(), sheet.c_str(), _key.c_str());
 		return;
 	}
@@ -749,7 +760,7 @@ void Object::turn(Object *obj) {
 }
 
 void Object::jiggle(float amount) {
-  _jiggleTo = new Jiggle(_node, amount);
+	_jiggleTo = new Jiggle(_node, amount);
 }
 
 void Object::inventoryScrollUp() {
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 5c19c51cf47..a45608397e0 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -69,7 +69,7 @@ static DialogConditionMode parseMode(char mode) {
 
 static DialogConditionState parseState(Common::String &dialog) {
 	Common::String dialogName;
-    size_t i = 1;
+	size_t i = 1;
 	while (i < dialog.size() && !Common::isDigit(dialog[i])) {
 		dialogName += dialog[i];
 		i++;
@@ -431,12 +431,12 @@ bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &
 		return false;
 
 	MemStream ms;
-	if(!ms.open(data.data(), data.size() - 16))
+	if (!ms.open(data.data(), data.size() - 16))
 		return false;
 
 	GGHashMapDecoder decoder;
 	savegame.jSavegame = decoder.open(&ms);
-	if(!savegame.jSavegame)
+	if (!savegame.jSavegame)
 		return false;
 
 	const Common::JSONObject &jSavegame = savegame.jSavegame->asObject();
@@ -483,6 +483,17 @@ void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 	}
 }
 
+struct GetHObjects {
+	GetHObjects(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
+
+	void operator()(HSQOBJECT item) {
+		_objs.push_back(item);
+	}
+
+private:
+	Common::Array<HSQOBJECT> &_objs;
+};
+
 void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 	debug("loadCallbacks");
 	g_engine->_callbacks.clear();
@@ -497,7 +508,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 			if (jCallBackHash.contains("param")) {
 				HSQOBJECT arg;
 				toSquirrel(jCallBackHash["param"], arg);
-				sqgetitems(arg, [&args](HSQOBJECT &o) { args.push_back(o); });
+				sqgetitems(arg, GetHObjects(args));
 			}
 			g_engine->_callbacks.push_back(new Callback(id, time, name, args));
 		}
@@ -572,7 +583,39 @@ void SaveGameManager::loadObjects(const Common::JSONObject &json) {
 	}
 }
 
-static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false) {
+struct JsonCallback {
+	bool skipObj;
+	bool pseudo;
+	HSQOBJECT* rootTable;
+	Common::JSONObject* jObj;
+};
+
+static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false);
+
+static void fillMissingProperties(const Common::String &k, HSQOBJECT &oTable, void *data) {
+	JsonCallback* params = static_cast<JsonCallback*>(data);
+	if ((k.size() > 0) && (!k.hasPrefix("_"))) {
+		if (!(params->skipObj && isObject(getId(oTable)) && (params->pseudo || sqrawexists(*params->rootTable, k)))) {
+			Common::JSONValue *json = tojson(oTable, true);
+			if (json) {
+				(*params->jObj)[k] = json;
+			}
+		}
+	}
+}
+
+struct GetJArray {
+	GetJArray(Common::JSONArray &arr) : _arr(arr) {}
+
+	void operator()(HSQOBJECT item) {
+		_arr.push_back(tojson(item, true));
+	}
+
+private:
+	Common::JSONArray &_arr;
+};
+
+static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj, bool pseudo) {
 	switch (obj._type) {
 	case OT_INTEGER:
 		return new Common::JSONValue(sq_objtointeger(&obj));
@@ -584,7 +627,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 		return new Common::JSONValue();
 	case OT_ARRAY: {
 		Common::JSONArray arr;
-		sqgetitems(obj, [&arr](HSQOBJECT &item) { arr.push_back(tojson(item, true)); });
+		sqgetitems(obj, GetJArray(arr));
 		return new Common::JSONValue(arr);
 	}
 	case OT_TABLE: {
@@ -613,16 +656,14 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 
 		HSQUIRRELVM v = g_engine->getVm();
 		HSQOBJECT rootTbl = sqrootTbl(v);
-		sqgetpairs(obj, [&](const Common::String &k, HSQOBJECT &oTable) {
-			if ((k.size() > 0) && (!k.hasPrefix("_"))) {
-				if (!(skipObj && isObject(getId(oTable)) && (pseudo || sqrawexists(rootTbl, k)))) {
-					Common::JSONValue *json = tojson(oTable, true);
-					if (json) {
-						jObj[k] = json;
-					}
-				}
-			}
-		});
+
+		JsonCallback params;
+		params.jObj = &jObj;
+		params.pseudo = pseudo;
+		params.rootTable = &rootTbl;
+		params.skipObj = skipObj;
+		sqgetpairs(obj, fillMissingProperties, &params);
+
 		return new Common::JSONValue(jObj);
 	}
 	default:
@@ -858,30 +899,36 @@ static Common::JSONValue *createJObject(HSQOBJECT &table, Object *obj) {
 	return new Common::JSONValue(json);
 }
 
+static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
+	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
+	if (isObject(getId(v))) {
+		Object *obj = sqobj(v);
+		if (!obj || (obj->_objType == otNone)) {
+			// info fmt"obj: createJObject({k})"
+			(*jObj)[k] = createJObject(v, obj);
+		}
+	}
+}
+
 static Common::JSONValue *createJObjects() {
 	Common::JSONObject json;
-	sqgetpairs(sqrootTbl(g_engine->getVm()), [&](const Common::String &k, HSQOBJECT &v) {
-		if (isObject(getId(v))) {
-			Object *obj = sqobj(v);
-			if (!obj || (obj->_objType == otNone)) {
-				// info fmt"obj: createJObject({k})"
-				json[k] = createJObject(v, obj);
-			}
-		}
-	});
+	sqgetpairs(sqrootTbl(g_engine->getVm()), fillObjects, &json);
 	//   result.fields.sort(cmpKey)
 	return new Common::JSONValue(json);
 }
 
-static Common::JSONValue *createJPseudoObjects(Room *room) {
-	Common::JSONObject json;
-	sqgetpairs(room->_table, [&](const Common::String &k, HSQOBJECT &v) {
-		if (isObject(getId(v))) {
+static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void* data) {
+	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
+	if (isObject(getId(v))) {
 			Object *obj = sqobj(v);
 			// info fmt"pseudoObj: createJObject({k})"
-			json[k] = createJObject(v, obj);
+			(*jObj)[k] = createJObject(v, obj);
 		}
-	});
+}
+
+static Common::JSONValue *createJPseudoObjects(Room *room) {
+	Common::JSONObject json;
+	sqgetpairs(room->_table, fillPseudoObjects, &json);
 	//   result.fields.sort(cmpKey)
 	return new Common::JSONValue(json);
 }
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 49ff423ecfd..552eabe7761 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -75,7 +75,7 @@ SQInteger sqpush(HSQUIRRELVM v, Common::String value) {
 }
 
 template<>
-SQInteger sqpush(HSQUIRRELVM v, const char* value) {
+SQInteger sqpush(HSQUIRRELVM v, const char *value) {
 	sq_pushstring(v, value, -1);
 	return 1;
 }
@@ -416,6 +416,16 @@ ThreadBase *sqthread(int id) {
 	return nullptr;
 }
 
+struct GetThread {
+	GetThread(HSQUIRRELVM v) : _v(v) {}
+	bool operator()(ThreadBase *t) {
+		return t->getThread() == _v;
+	}
+
+private:
+	HSQUIRRELVM _v;
+};
+
 ThreadBase *sqthread(HSQUIRRELVM v) {
 	if (g_engine->_cutscene) {
 		if (g_engine->_cutscene->getThread() == v) {
@@ -423,9 +433,7 @@ ThreadBase *sqthread(HSQUIRRELVM v) {
 		}
 	}
 
-	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), [&](ThreadBase *t) {
-		return t->getThread() == v;
-	});
+	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), GetThread(v));
 }
 
 static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<SoundDefinition *> &arr) {
@@ -445,4 +453,19 @@ SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<SoundDefinition *> &arr)
 	return result;
 }
 
+void sqgetpairs(HSQOBJECT obj, void func(const Common::String &key, HSQOBJECT &obj, void *data), void *data) {
+	HSQUIRRELVM v = g_engine->getVm();
+	sq_pushobject(v, obj);
+	sq_pushnull(v);
+	while (SQ_SUCCEEDED(sq_next(v, -2))) {
+		Common::String key;
+		HSQOBJECT o;
+		sqget(v, -1, o);
+		sqget(v, -2, key);
+		func(key, o, data);
+		sq_pop(v, 2);
+	}
+	sq_pop(v, 2);
+}
+
 } // namespace Twp
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 2be7b7085fe..930cab3aeee 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -105,6 +105,8 @@ void sqgetitems(HSQOBJECT o, TFunc func) {
 	sq_pop(v, 2);
 }
 
+void sqgetpairs(HSQOBJECT obj, void func(const Common::String& key, HSQOBJECT& obj, void* data), void* data);
+
 template<typename TFunc>
 void sqgetpairs(HSQOBJECT obj, TFunc func) {
 	HSQUIRRELVM v = g_engine->getVm();
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 87c26baca39..f0dfc6d27ad 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -95,13 +95,12 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	return 1;
 }
 
-template<typename F>
-static SQInteger breakfunc(HSQUIRRELVM v, const F &func) {
+static SQInteger breakfunc(HSQUIRRELVM v, void func(ThreadBase *t, void *data), void *data) {
 	ThreadBase *t = sqthread(v);
 	if (!t)
 		return sq_throwerror(v, "failed to get thread");
 	t->suspend();
-	func(t);
+	func(t, data);
 	return -666;
 }
 
@@ -167,6 +166,16 @@ static SQInteger addFolder(HSQUIRRELVM v) {
 	return 0;
 }
 
+static void threadFrames(ThreadBase *tb, void *data) {
+	int numFrames = *(int *)data;
+	((Thread *)tb)->_numFrames = numFrames;
+}
+
+static void threadTime(ThreadBase *tb, void *data) {
+	float time = *(float *)data;
+	((Thread *)tb)->_waitTime = time;
+}
+
 // When called in a function started with startthread, execution is suspended for count frames.
 // It is an error to call breakhere in a function that was not started with startthread.
 // Particularly useful instead of breaktime if you just want to wait 1 frame, since not all machines run at the same speed.
@@ -180,13 +189,13 @@ static SQInteger breakhere(HSQUIRRELVM v) {
 		int numFrames;
 		if (SQ_FAILED(sqget(v, 2, numFrames)))
 			return sq_throwerror(v, "failed to get numFrames");
-		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_numFrames = numFrames; });
+		return breakfunc(v, threadFrames, &numFrames);
 	}
 	if (t == OT_FLOAT) {
 		float time;
 		if (SQ_FAILED(sqget(v, 2, time)))
 			return sq_throwerror(v, "failed to get time");
-		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_waitTime = time; });
+		return breakfunc(v, threadTime, &time);
 	}
 	return sq_throwerror(v, Common::String::format("failed to get numFrames (wrong type = {%d})", t).c_str());
 }
@@ -203,10 +212,11 @@ static SQInteger breaktime(HSQUIRRELVM v) {
 	SQFloat time;
 	if (SQ_FAILED(sq_getfloat(v, 2, &time)))
 		return sq_throwerror(v, "failed to get time");
-	if (time == 0.f)
-		return breakfunc(v, [](ThreadBase *t) { ((Thread *)t)->_numFrames = 1; });
-	else
-		return breakfunc(v, [&](ThreadBase *t) { ((Thread *)t)->_waitTime = time; });
+	if (time == 0.f) {
+		int frame = 1;
+		return breakfunc(v, threadFrames, &frame);
+	}
+	return breakfunc(v, threadTime, &time);
 }
 
 template<typename Predicate>
@@ -220,8 +230,7 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	if (!curThread)
 		return sq_throwerror(v, "Current thread should be created with startthread");
 
-	debug("curThread.id: %d, %s", curThread->getId(), curThread->getName().c_str());
-	debug("add breakwhilecond name=%s pid=%d", name.c_str(), curThread->getId());
+	debug("add breakwhilecond name=%s pid=%d, %s", name.c_str(), curThread->getId(), curThread->getName().c_str());
 	g_engine->_tasks.push_back(new BreakWhileCond<Predicate>(curThread->getId(), name, pred));
 	return -666;
 }
@@ -230,6 +239,16 @@ static bool isAnimating(Object *obj) {
 	return obj->_nodeAnim->_anim && !obj->_nodeAnim->_disabled && obj->_animName != obj->getAnimName(STAND_ANIMNAME);
 }
 
+struct ObjAnimating {
+	ObjAnimating(Object *obj) : _obj(obj) {}
+	bool operator()() {
+		return isAnimating(_obj);
+	}
+
+private:
+	Object *_obj;
+};
+
 // When called in a function started with startthread, execution is suspended until animatingItem has completed its animation.
 // Note, animatingItem can be an actor or an object.
 // It is an error to call breakwhileanimating in a function that was not started with `startthread`.
@@ -244,40 +263,59 @@ static SQInteger breakwhileanimating(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	return breakwhilecond(
-		v, [obj]() { return isAnimating(obj); }, "breakwhileanimating(%s)", obj->_key.c_str());
+	return breakwhilecond(v, ObjAnimating(obj), "breakwhileanimating(%s)", obj->_key.c_str());
 }
 
+struct CameraMoving {
+	bool operator()() {
+		return g_engine->_camera.isMoving();
+	}
+};
+
 // Breaks while a camera is moving.
 // Once the thread finishes execution, the method will continue running.
 // It is an error to call breakwhilecamera in a function that was not started with startthread.
 static SQInteger breakwhilecamera(HSQUIRRELVM v) {
-	return breakwhilecond(
-		v, [] { return g_engine->_camera.isMoving(); }, "breakwhilecamera()");
+	return breakwhilecond(v, CameraMoving(), "breakwhilecamera()");
 }
 
+struct CutsceneRunning {
+	bool operator()() {
+		return g_engine->_cutscene != nullptr;
+	}
+};
+
 // Breaks while a cutscene is running.
 // Once the thread finishes execution, the method will continue running.
 // It is an error to call breakwhilecutscene in a function that was not started with startthread.
 static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
-	return breakwhilecond(
-		v, [] { return g_engine->_cutscene != nullptr; }, "breakwhilecutscene()");
+	return breakwhilecond(v, CutsceneRunning(), "breakwhilecutscene()");
 }
 
+struct DialogRunning {
+	bool operator()() {
+		return g_engine->_dialog.getState() != DialogState::None;
+	}
+};
+
 // Breaks while a dialog is running.
 // Once the thread finishes execution, the method will continue running.
 // It is an error to call breakwhiledialog in a function that was not started with startthread.
 static SQInteger breakwhiledialog(HSQUIRRELVM v) {
-	return breakwhilecond(
-		v, [] { return g_engine->_dialog.getState() != DialogState::None; }, "breakwhiledialog()");
+	return breakwhilecond(v, DialogRunning(), "breakwhiledialog()");
 }
 
+struct InputOff {
+	bool operator()() {
+		return !g_engine->_inputState.getInputActive();
+	}
+};
+
 // Breaks while input is not active.
 // Once the thread finishes execution, the method will continue running.
 // It is an error to call breakwhileinputoff in a function that was not started with startthread.
 static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
-	return breakwhilecond(
-		v, [] { return !g_engine->_inputState.getInputActive(); }, "breakwhileinputoff()");
+	return breakwhilecond(v, InputOff(), "breakwhileinputoff()");
 }
 
 // Breaks while the thread referenced by threadId is running.
@@ -332,6 +370,23 @@ static bool isSomeoneTalking() {
 	return false;
 }
 
+struct SomeoneTalking {
+	bool operator()() {
+		return isSomeoneTalking();
+	}
+};
+
+struct ActorTalking {
+	ActorTalking(Object *obj) : _obj(obj) {}
+
+	bool operator()() {
+		return _obj->getTalking() && _obj->getTalking()->isEnabled();
+	}
+
+private:
+	Object *_obj;
+};
+
 // If an actor is specified, breaks until actor has finished talking.
 // If no actor is specified, breaks until ALL actors have finished talking.
 // Once talking finishes, the method will continue running.
@@ -347,20 +402,29 @@ static bool isSomeoneTalking() {
 static SQInteger breakwhiletalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 1) {
-		return breakwhilecond(
-			v, []() { return isSomeoneTalking(); }, "breakwhiletalking(all)");
+		return breakwhilecond(v, SomeoneTalking(), "breakwhiletalking(all)");
 	}
 	if (nArgs == 2) {
 		Object *obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
-		return breakwhilecond(
-			v, [obj]() { return obj->getTalking() && obj->getTalking()->isEnabled(); }, "breakwhiletalking(%s)", obj->_name.c_str());
+		return breakwhilecond(v, ActorTalking(obj), "breakwhiletalking(%s)", obj->_name.c_str());
 	}
 
 	return sq_throwerror(v, "Invalid number of arguments for breakwhiletalking");
 }
 
+struct ActorWalking {
+	ActorWalking(Object *obj) : _obj(obj) {}
+
+	bool operator()() {
+		return _obj->getWalkTo() && _obj->getWalkTo()->isEnabled();
+	}
+
+private:
+	Object *_obj;
+};
+
 // If an actor is specified, breaks until actor has finished walking.
 // Once arrived at destination, the method will continue running.
 // It is an error to call breakwhilewalking in a function that was not started with `startthread`.
@@ -375,18 +439,27 @@ static SQInteger breakwhilewalking(HSQUIRRELVM v) {
 	Object *obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	return breakwhilecond(
-		v, [obj]() { return obj->getWalkTo() && obj->getWalkTo()->isEnabled(); }, "breakwhilewalking(%s)", obj->_name.c_str());
+	return breakwhilecond(v, ActorWalking(obj), "breakwhilewalking(%s)", obj->_name.c_str());
 }
 
+struct SoundPlaying {
+	SoundPlaying(int soundId) : _soundId(soundId) {}
+
+	bool operator()() {
+		return g_engine->_audio.playing(_soundId);
+	}
+
+private:
+	int _soundId;
+};
+
 // Breaks until specified sound has finished playing.
 // Once sound finishes, the method will continue running.
 static SQInteger breakwhilesound(HSQUIRRELVM v) {
 	int soundId = 0;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-	return breakwhilecond(
-		v, [soundId]() { return g_engine->_audio.playing(soundId); }, "breakwhilesound(%d)", soundId);
+	return breakwhilecond(v, SoundPlaying(soundId), "breakwhilesound(%d)", soundId);
 }
 
 static SQInteger cutscene(HSQUIRRELVM v) {
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 8d159d128cb..d43f30c01ec 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -299,15 +299,23 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 	}
 }
 
-Object *inventoryAt(Math::Vector2d pos) {
-	Object *result = nullptr;
-	objsAt(pos, [&](Object *x) {
-		if (x->inInventory()) {
-			result = x;
+struct InInventory {
+	InInventory(Object *&obj) : _obj(obj) { _obj = nullptr; }
+	bool operator()(Object *obj) {
+		if (obj->inInventory()) {
+			_obj = obj;
 			return true;
 		}
 		return false;
-	});
+	}
+
+public:
+	Object *&_obj;
+};
+
+Object *inventoryAt(Math::Vector2d pos) {
+	Object *result;
+	objsAt(pos, InInventory(result));
 	return result;
 }
 
@@ -353,6 +361,21 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 	return result;
 }
 
+struct GetNoun2 {
+	GetNoun2(Object *&obj) : _noun2(obj) {}
+
+	bool operator()(Object *obj) {
+		if (obj != g_engine->_actor && obj->getFlags() & GIVEABLE) {
+			_noun2 = obj;
+			return true;
+		}
+		return false;
+	}
+
+public:
+	Object *&_noun2;
+};
+
 void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
@@ -379,13 +402,7 @@ void TwpEngine::update(float elapsed) {
 					_useFlag = ufNone;
 					_noun2 = nullptr;
 				} else {
-					objsAt(roomPos, [&](Object *x) {
-						if (x != _actor && x->getFlags() & GIVEABLE) {
-							_noun2 = x;
-							return true;
-						}
-						return false;
-					});
+					objsAt(roomPos, GetNoun2(_noun2));
 					if (_noun2)
 						debug("Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
 				}
@@ -561,7 +578,7 @@ void TwpEngine::setShaderEffect(RoomEffect effect) {
 	}
 }
 
-void TwpEngine::draw(RenderTexture* outTexture) {
+void TwpEngine::draw(RenderTexture *outTexture) {
 	if (_room) {
 		Math::Vector2d screenSize = _room->getScreenSize();
 		_gfx.camera(screenSize);
@@ -747,7 +764,7 @@ Common::Error TwpEngine::run() {
 								actors.push_back(slot->actor);
 							}
 						}
-                        size_t index = find(actors, _actor) + 1;
+						size_t index = find(actors, _actor) + 1;
 						if (index >= actors.size())
 							index -= actors.size();
 						setActor(actors[index], true);
@@ -920,6 +937,88 @@ Common::Error TwpEngine::saveGameStream(Common::WriteStream *stream, bool isAuto
 	return Common::kNoError;
 }
 
+struct DefineObjectParams {
+	HSQUIRRELVM v;
+	bool pseudo;
+	Room *room;
+	HSQOBJECT *roomTable;
+};
+
+static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
+	DefineObjectParams *params = static_cast<DefineObjectParams *>(data);
+	HSQUIRRELVM v = params->v;
+	if (oTable._type == OT_TABLE) {
+		if (params->pseudo) {
+			// if it's a pseudo room we need to clone each object
+			sq_pushobject(v, oTable);
+			sq_clone(v, -1);
+			sq_getstackobj(v, -1, &oTable);
+			sq_addref(v, &oTable);
+			sq_pop(v, 2);
+			sqsetf(params->room->_table, k, oTable);
+		}
+
+		if (sqrawexists(oTable, "icon")) {
+			// Add inventory object to root table
+			debug("Add %s to inventory", k.c_str());
+			sqsetf(sqrootTbl(params->v), k, oTable);
+
+			// set room as delegate
+			sqsetdelegate(oTable, *params->roomTable);
+
+			// declare flags if does not exist
+			if (!sqrawexists(oTable, "flags"))
+				sqsetf(oTable, "flags", 0);
+			Object *obj = new Object(oTable, k);
+			setId(obj->_table, newObjId());
+			obj->_node = new Node(k);
+			obj->_nodeAnim = new Anim(obj);
+			obj->_node->addChild(obj->_nodeAnim);
+			obj->setRoom(params->room);
+			// set room as delegate
+			sqsetdelegate(obj->_table, *params->roomTable);
+		} else {
+			Object *obj = params->room->getObj(k);
+			if (!obj) {
+				debug("object: %s not found in wimpy", k.c_str());
+				if (sqrawexists(oTable, "name")) {
+					obj = new Object();
+					obj->_key = k;
+					obj->_layer = params->room->layer(0);
+					params->room->layer(0)->_objects.push_back(obj);
+				} else {
+					return;
+				}
+			}
+
+			sqgetf(params->room->_table, k, obj->_table);
+			setId(obj->_table, newObjId());
+			debug("Create object: %s #%d", k.c_str(), obj->getId());
+
+			// add it to the root table if not a pseudo room
+			if (!params->pseudo)
+				sqsetf(sqrootTbl(params->v), k, obj->_table);
+
+			if (sqrawexists(obj->_table, "initState")) {
+				// info fmt"initState {obj.key}"
+				int state;
+				sqgetf(obj->_table, "initState", state);
+				obj->setState(state, true);
+			} else {
+				obj->setState(0, true);
+			}
+			obj->setRoom(params->room);
+
+			// set room as delegate
+			sqsetdelegate(obj->_table, *params->roomTable);
+
+			// declare flags if does not exist
+			if (!sqrawexists(obj->_table, "flags"))
+				sqsetf(obj->_table, "flags", 0);
+		}
+	}
+}
+
 Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
 	HSQUIRRELVM v = _vm.get();
 	debug("Load room: %s", name.c_str());
@@ -995,78 +1094,12 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 		}
 	}
 
-	sqgetpairs(result->_table, [&](const Common::String &k, HSQOBJECT &oTable) {
-		if (oTable._type == OT_TABLE) {
-			if (pseudo) {
-				// if it's a pseudo room we need to clone each object
-				sq_pushobject(v, oTable);
-				sq_clone(v, -1);
-				sq_getstackobj(v, -1, &oTable);
-				sq_addref(v, &oTable);
-				sq_pop(v, 2);
-				sqsetf(result->_table, k, oTable);
-			}
-
-			if (sqrawexists(oTable, "icon")) {
-				// Add inventory object to root table
-				debug("Add %s to inventory", k.c_str());
-				sqsetf(sqrootTbl(v), k, oTable);
-
-				// set room as delegate
-				sqsetdelegate(oTable, table);
-
-				// declare flags if does not exist
-				if (!sqrawexists(oTable, "flags"))
-					sqsetf(oTable, "flags", 0);
-				Object *obj = new Object(oTable, k);
-				setId(obj->_table, newObjId());
-				obj->_node = new Node(k);
-				obj->_nodeAnim = new Anim(obj);
-				obj->_node->addChild(obj->_nodeAnim);
-				obj->setRoom(result);
-				// set room as delegate
-				sqsetdelegate(obj->_table, table);
-			} else {
-				Object *obj = result->getObj(k);
-				if (!obj) {
-					debug("object: %s not found in wimpy", k.c_str());
-					if (sqrawexists(oTable, "name")) {
-						obj = new Object();
-						obj->_key = k;
-						obj->_layer = result->layer(0);
-						result->layer(0)->_objects.push_back(obj);
-					} else {
-						return;
-					}
-				}
-
-				sqgetf(result->_table, k, obj->_table);
-				setId(obj->_table, newObjId());
-				debug("Create object: %s #%d", k.c_str(), obj->getId());
-
-				// add it to the root table if not a pseudo room
-				if (!pseudo)
-					sqsetf(sqrootTbl(v), k, obj->_table);
-
-				if (sqrawexists(obj->_table, "initState")) {
-					// info fmt"initState {obj.key}"
-					int state;
-					sqgetf(obj->_table, "initState", state);
-					obj->setState(state, true);
-				} else {
-					obj->setState(0, true);
-				}
-				obj->setRoom(result);
-
-				// set room as delegate
-				sqsetdelegate(obj->_table, table);
-
-				// declare flags if does not exist
-				if (!sqrawexists(obj->_table, "flags"))
-					sqsetf(obj->_table, "flags", 0);
-			}
-		}
-	});
+	DefineObjectParams params;
+	params.pseudo = pseudo;
+	params.v = v;
+	params.room = result;
+	params.roomTable = &table;
+	sqgetpairs(result->_table, onGetPairs, &params);
 
 	// declare the room in the root table
 	setId(result->_table, newRoomId());
@@ -1291,16 +1324,27 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 	_fadeShader->_elapsed = 0.f;
 }
 
-Object *TwpEngine::objAt(Math::Vector2d pos) {
-	int zOrder = INT_MAX;
-	Object *result = nullptr;
-	objsAt(pos, [&](Object *obj) {
-		if (obj->_node->getZSort() < zOrder) {
-			result = obj;
-			zOrder = obj->_node->getZSort();
+struct GetByZorder {
+	GetByZorder(Object *&result) : _result(result) { result = nullptr; }
+
+	bool operator()(Object *obj) {
+		if (obj->_node->getZSort() < _zOrder) {
+			_result = obj;
+			_zOrder = obj->_node->getZSort();
 		}
 		return false;
-	});
+	}
+
+public:
+	Object *&_result;
+
+private:
+	int _zOrder = INT_MAX;
+};
+
+Object *TwpEngine::objAt(Math::Vector2d pos) {
+	Object *result;
+	objsAt(pos, GetByZorder(result));
 	return result;
 }
 
@@ -1557,7 +1601,7 @@ void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
 
 	Graphics::Surface s;
 	rt.capture(s);
-	s.flipVertical(Common::Rect(size.getX(), size.getY()));
+	s.flipVertical(Common::Rect(s.w, s.h));
 
 	Image::writePNG(stream, s);
 }
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 128796bdf38..0466f561363 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -104,6 +104,8 @@ Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
 }
 
 void PathNode::drawCore(Math::Matrix4 trsf) {
+	if(!g_engine->_room) return;
+
 	Color red(1.f, 0.f, 0.f);
 	Color yellow(1.f, 1.f, 0.f);
 	Color blue(0.f, 0.f, 1.f);


Commit: add0288deff802ea957ad73f44ec6a70250c2558
    https://github.com/scummvm/scummvm/commit/add0288deff802ea957ad73f44ec6a70250c2558
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix invalid light id

Changed paths:
    engines/twp/room.cpp
    engines/twp/room.h


diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 04f3c414d9e..0f43ebe6b38 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -415,6 +415,7 @@ Object *Room::getObj(const Common::String &key) {
 
 Light *Room::createLight(Color color, Math::Vector2d pos) {
 	Light *result = &_lights._lights[_lights._numLights];
+	result->id = 100000 + _lights._numLights;
 	result->color = color;
 	result->pos = pos;
 	_lights._numLights++;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 3ab6666732b..4f5db6cdb9c 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -81,7 +81,7 @@ struct Light {
 	float coneDirection; // cone direction 0...360.f
 	float coneAngle;     // cone angle 0...360.f
 	float coneFalloff;   // cone falloff 0.f...1.0f
-	float cutOffRadius;  // cutoff raduus
+	float cutOffRadius;  // cutoff radius
 	float halfRadius;    // cone half radius 0.0f...1.0f
 	bool on;
 	int id;


Commit: 6ccb14d83864df9868329989312b4828c60ea880
    https://github.com/scummvm/scummvm/commit/6ccb14d83864df9868329989312b4828c60ea880
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash on Linux when capturing screen

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 63e1eaaf17a..b336c31ade5 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -108,7 +108,6 @@ void Texture::capture(Graphics::Surface &surface) {
 }
 
 RenderTexture::RenderTexture(Math::Vector2d size) {
-	// result = RenderTexture(width: size.x, height: size.y)
 	width = size.getX();
 	height = size.getY();
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d43f30c01ec..06f0f2754d3 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1596,12 +1596,13 @@ Scaling *TwpEngine::getScaling(const Common::String &name) {
 }
 
 void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
-	RenderTexture rt(size);
+	RenderTexture rt(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	draw(&rt);
 
 	Graphics::Surface s;
 	rt.capture(s);
 	s.flipVertical(Common::Rect(s.w, s.h));
+	s.scale(size.getX(), size.getY());
 
 	Image::writePNG(stream, s);
 }


Commit: 71e44fe5e0056156c8a9e8889173c9a726dff3c3
    https://github.com/scummvm/scummvm/commit/71e44fe5e0056156c8a9e8889173c9a726dff3c3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix missing warnings on Linux

Changed paths:
    engines/twp/font.cpp
    engines/twp/room.cpp


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index aeb199487d3..5192ff759d9 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -308,10 +308,10 @@ void Text::update() {
 			for (size_t j = 0; j < line.tokens.size(); j++) {
 				tok = line.tokens[j];
 				if (tok.id == tiColor) {
-					int iColor;
+					uint iColor;
 					Common::String s = reader.substr(tok);
 					sscanf(s.c_str() + 1, "%x", &iColor);
-					color = Color::withAlpha(Color::rgb(iColor & 0x00FFFFFF), color.rgba.a);
+					color = Color::withAlpha(Color::rgb((int)(iColor & 0x00FFFFFF)), color.rgba.a);
 				} else {
 					Common::U32String s = reader.substr(tok);
 					for (size_t k = 0; k < s.size(); k++) {
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 0f43ebe6b38..f96b4dc6422 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -72,8 +72,8 @@ static Math::Vector2d parseParallax(const Common::JSONValue &v) {
 
 static Walkbox parseWalkbox(const Common::String &text) {
 	Common::Array<Math::Vector2d> points;
-    size_t i = 1;
-    size_t endPos;
+	size_t i = 1;
+	size_t endPos;
 	do {
 		uint32 commaPos = text.find(',', i);
 		long x = strtol(text.substr(i, commaPos - i).c_str(), nullptr, 10);
@@ -416,6 +416,7 @@ Object *Room::getObj(const Common::String &key) {
 Light *Room::createLight(Color color, Math::Vector2d pos) {
 	Light *result = &_lights._lights[_lights._numLights];
 	result->id = 100000 + _lights._numLights;
+	result->on = true;
 	result->color = color;
 	result->pos = pos;
 	_lights._numLights++;


Commit: a35d873ba8887c29e8a6f126ffa741ade4a461b0
    https://github.com/scummvm/scummvm/commit/a35d873ba8887c29e8a6f126ffa741ade4a461b0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash with sound not initialized

Changed paths:
    engines/twp/audio.h


diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index 7fdb7e7c7b4..f6203eccc9e 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -73,7 +73,7 @@ private:
     int _id;						// identifier for this sound
     Common::String _name;		    // name of the sound to load
     Common::Array<byte> _buffer;	// buffer containing the sound data
-    bool _loaded;				    // indicates whether or not the sound buffer has been loaded
+    bool _loaded = false;		    // indicates whether or not the sound buffer has been loaded
 };
 
 struct AudioSlot {


Commit: 8e49787281daa650191bfdd2d31f87ca559f5b6c
    https://github.com/scummvm/scummvm/commit/8e49787281daa650191bfdd2d31f87ca559f5b6c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove static variable in gfx.cpp

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index b336c31ade5..1f5c7e86f2e 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -28,8 +28,6 @@
 
 namespace Twp {
 
-static Texture gEmptyTexture;
-
 int Color::toInt() const {
 	int r = (rgba.r * 255.f);
 	int g = (rgba.g * 255.f);
@@ -224,7 +222,7 @@ void Gfx::init() {
 	empty.h = 1;
 	empty.format = fmt;
 	empty.setPixels(pixels);
-	gEmptyTexture.load(empty);
+	_emptyTexture.load(empty);
 
 	const char *fragmentSrc = R"(#version 110
 		varying vec4 v_color;
@@ -270,8 +268,8 @@ Math::Matrix4 Gfx::getFinalTransform(Math::Matrix4 trsf) {
 }
 
 void Gfx::noTexture() {
-	_texture = &gEmptyTexture;
-	GL_CALL(glBindTexture(GL_TEXTURE_2D, gEmptyTexture.id));
+	_texture = &_emptyTexture;
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, _emptyTexture.id));
 }
 
 void Gfx::drawLines(Vertex *vertices, int count, Math::Matrix4 trsf) {
@@ -286,7 +284,7 @@ void Gfx::drawLinesLoop(Vertex *vertices, int count, Math::Matrix4 trsf) {
 
 void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Math::Matrix4 trsf, Texture *texture) {
 	if (v_size > 0) {
-		_texture = texture ? texture : &gEmptyTexture;
+		_texture = texture ? texture : &_emptyTexture;
 		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
 
 		// set blending
@@ -333,7 +331,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 	if (i_size > 0) {
 		int num = _shader->getNumTextures();
 		if (num == 0) {
-			_texture = texture ? texture : &gEmptyTexture;
+			_texture = texture ? texture : &_emptyTexture;
 			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
 		} else {
 			for (int i = 0; i < num; i++) {
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 1c922b86e41..ba8db0b8cb9 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -184,6 +184,7 @@ private:
 	void noTexture();
 
 private:
+	Texture _emptyTexture;
 	uint32 _vbo = 0, _ebo = 0;
 	Shader _defaultShader;
 	Shader *_shader = nullptr;


Commit: ebc160da66428a4776b7c388179e1464ff12eadc
    https://github.com/scummvm/scummvm/commit/ebc160da66428a4776b7c388179e1464ff12eadc
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Reset room effect when entering room

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 06f0f2754d3..d7bfc0c3a83 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1123,6 +1123,7 @@ void TwpEngine::enterRoom(Room *room, Object *door) {
 	if (_room)
 		_room->_scene->remove();
 	_room = room;
+	room->_effect = RoomEffect::None;
 	_scene.addChild(_room->_scene);
 	_room->_lights._numLights = 0;
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));


Commit: 478b1a505b180d13d42b0dd309c80ccfd58356e6
    https://github.com/scummvm/scummvm/commit/478b1a505b180d13d42b0dd309c80ccfd58356e6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix some uninitialized variables

Changed paths:
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/room.h
    engines/twp/scenegraph.h
    engines/twp/shaders.h
    engines/twp/walkboxnode.h


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 5192ff759d9..4bd6a0fb6f1 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -298,7 +298,7 @@ void Text::update() {
 		lines.push_back(line1);
 
 		// create quads for all characters
-		float maxW;
+		float maxW = 0.f;
 		float lineHeight = _font->getLineHeight();
 		float y = -lineHeight;
 		for (size_t i = 0; i < lines.size(); i++) {
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index a33428fc6f2..b630cd847a2 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -370,19 +370,18 @@ static SQInteger getUserPref(HSQUIRRELVM v) {
 
 static SQInteger getPrivatePref(HSQUIRRELVM v) {
 	Common::String key;
-	if (SQ_FAILED(sqget(v, 2, key)))
+	if (SQ_FAILED(sqget(v, 2, key))) {
 		return sq_throwerror(v, "failed to get key");
-	debug("TODO: getPrivatePref");
-	// else if (g_engine->getPrefs().hasPrivPref(key))
+	// } else if (g_engine->getPrefs().hasPrivPref(key)) {
 	// 	return sqpush(v, g_engine->getPrefs().privPrefAsJson(key));
-	// else
-	if (sq_gettop(v) == 3) {
+	} else if (sq_gettop(v) == 3) {
 		HSQOBJECT obj;
 		sq_getstackobj(v, 3, &obj);
 		sqpush(v, obj);
 		return 1;
 	}
-	return sq_throwerror(v, "getPrivatePref: invalid argument");
+	sq_pushnull(v);
+	return 1;
 }
 
 static SQInteger incutscene(HSQUIRRELVM v) {
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 81d69dc4af8..bbc55cae6b5 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -330,6 +330,7 @@ Common::String Object::getIcon() {
 	if (_icons.size() > 0)
 		return _icons[_iconIndex];
 	HSQOBJECT iconTable;
+	sq_resetobject(&iconTable);
 	sqgetf(_table, "icon", iconTable);
 	if (iconTable._type == OT_NULL) {
 		warning("object table is null");
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 4f5db6cdb9c..be115f8fadd 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -148,7 +148,7 @@ public:
 	Common::Array<Object *> _objects;
 	Scene *_scene = nullptr;
 	OverlayNode _overlayNode;	// Represents an overlay
-	RoomEffect _effect;
+	RoomEffect _effect = RoomEffect::None;
 	Motor* _overlayTo = nullptr;
 	PathFinder _pathFinder;
 };
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 7d7c290b772..e3041b20604 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -178,7 +178,7 @@ private:
 	float _elapsed = 0.f;
 	float _frameDuration = 0.f;
 	bool _loop = false;
-	bool _instant;
+	bool _instant = false;
 	Object *_obj = nullptr;
 };
 
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 8b748e61c83..07a628499ce 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -33,7 +33,7 @@ extern const char* ghostShader;
 extern const char* sepiaShader;
 
 struct ShaderParams {
-    RoomEffect effect;
+    RoomEffect effect = RoomEffect::None;
     float sepiaFlicker = 1.f;
     float randomValue[5];
     float timeLapse;
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index 99fe2ab3630..fccc4dd8d18 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -64,7 +64,7 @@ private:
 	virtual void drawCore(Math::Matrix4 trsf) override;
 
 private:
-	PathMode _mode;
+	PathMode _mode = PathMode::None;
 };
 
 } // namespace Twp


Commit: 03dd90452d2698af4ed83d8045f7e44118de891f
    https://github.com/scummvm/scummvm/commit/03dd90452d2698af4ed83d8045f7e44118de891f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix resume thread and use of uninitialized variables

Changed paths:
    engines/twp/font.cpp
    engines/twp/font.h
    engines/twp/object.cpp
    engines/twp/objectanimation.h
    engines/twp/savegame.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 4bd6a0fb6f1..2ad88634daa 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -133,8 +133,8 @@ CodePoint TokenReader::readChar() {
 }
 
 TokenId TokenReader::readTokenId() {
-	const char Whitespace[] = {' ', '\t', '\v', '\r', '\f'};
-	const char Whitespace2[] = {' ', '\t', '\v', '\r', '\f', '#', '\n'};
+	const char Whitespace[] = {' ', '\t', '\v', '\r', '\f', '\0'};
+	const char Whitespace2[] = {' ', '\t', '\v', '\r', '\f', '#', '\n', '\0'};
 	if (_off < _text.size()) {
 		char c = readChar();
 		switch (c) {
diff --git a/engines/twp/font.h b/engines/twp/font.h
index 3cfb32ef0f3..da76e9b4e0e 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -190,11 +190,11 @@ private:
 	Texture *_texture = nullptr;
 	Common::String _txt;
 	Color _col;
-	TextHAlignment _hAlign;
-	TextVAlignment _vAlign;
+	TextHAlignment _hAlign = TextHAlignment::thLeft;
+	TextVAlignment _vAlign = TextVAlignment::tvCenter;
 	Common::Array<Vertex> _vertices;
 	Math::Vector2d _bnds;
-	float _maxW = 0;
+	float _maxW = 0.f;
 	Common::Array<Common::Rect> _quads;
 	bool _dirty = false;
 };
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index bbc55cae6b5..4931f0b9200 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -333,7 +333,6 @@ Common::String Object::getIcon() {
 	sq_resetobject(&iconTable);
 	sqgetf(_table, "icon", iconTable);
 	if (iconTable._type == OT_NULL) {
-		warning("object table is null");
 		return "";
 	}
 	if (iconTable._type == OT_STRING) {
diff --git a/engines/twp/objectanimation.h b/engines/twp/objectanimation.h
index b759370b3f7..b4ab7ab2af9 100644
--- a/engines/twp/objectanimation.h
+++ b/engines/twp/objectanimation.h
@@ -35,10 +35,10 @@ struct ObjectAnimation {
 	Common::Array<ObjectAnimation> layers;
 	Common::StringArray triggers;
 	Common::Array<Math::Vector2d> offsets;
-	bool loop;
-	float fps;
-	int flags;
-	int frameIndex;
+	bool loop = false;
+	float fps = 0.f;
+	int flags = 0;
+	int frameIndex = 0;
 };
 } // namespace Twp
 
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index a45608397e0..8736044634c 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -633,7 +633,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 	case OT_TABLE: {
 		Common::JSONObject jObj;
 		if (checkId) {
-			int id;
+			int id = 0;
 			sqgetf(obj, "_id", id);
 			if (isActor(id)) {
 				Object *a = actor(id);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index f0dfc6d27ad..abc33bfd776 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -492,7 +492,8 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get cutscene override closure");
 	}
 
-	Cutscene *cutscene = new Cutscene(v, threadObj, closure, closureOverride, envObj);
+	ThreadBase* parentThread = sqthread(v);
+	Cutscene *cutscene = new Cutscene(parentThread->getId(), threadObj, closure, closureOverride, envObj);
 	g_engine->_cutscene = cutscene;
 
 	// call the closure in the thread
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 87ac4eae1fe..3fb6d3e7001 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -28,12 +28,12 @@ namespace Twp {
 
 bool ThreadBase::isDead() {
 	SQInteger state = sq_getvmstate(getThread());
-	return _stopRequest || state == 0;
+	return _stopRequest || state == SQ_VMSTATE_IDLE;
 }
 
 bool ThreadBase::isSuspended() {
 	SQInteger state = sq_getvmstate(getThread());
-	return state != 1;
+	return state != SQ_VMSTATE_RUNNING;
 }
 
 void ThreadBase::suspend() {
@@ -115,8 +115,8 @@ void Thread::stop() {
 	suspend();
 }
 
-Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
-	: _v(v),
+Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
+	: _parentThreadId(parentThreadId),
 	  _threadObj(threadObj),
 	  _closure(closure),
 	  _closureOverride(closureOverride),
@@ -128,7 +128,7 @@ Cutscene::Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJ
 	_actor = g_engine->_followActor;
 	_showCursor = g_engine->_inputState.getShowCursor();
 	_state = csStart;
-	debug("Create cutscene %d with input: 0x%X", _id, _inputState);
+	debug("Create cutscene %d with input: 0x%X from parent thread: %d", _id, _inputState, _parentThreadId);
 	g_engine->_inputState.setInputActive(false);
 	g_engine->_inputState.setShowCursor(false);
 	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
@@ -182,7 +182,7 @@ void Cutscene::stop() {
 	}
 	sqcall("onCutsceneEnded");
 
-	ThreadBase *t = sqthread(_v);
+	ThreadBase *t = sqthread(_parentThreadId);
 	if (t && t->getId())
 		t->unpause();
 	sq_suspendvm(getThread());
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index feed4ab7169..ae01aa7168c 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -105,7 +105,7 @@ enum CutsceneState {
 class Object;
 class Cutscene final : public ThreadBase {
 public:
-	Cutscene(HSQUIRRELVM v, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
+	Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
 	virtual ~Cutscene() override final;
 
 	void start();
@@ -127,7 +127,7 @@ private:
 	void doCutsceneOverride();
 
 private:
-	HSQUIRRELVM _v;
+	int _parentThreadId = 0;
 	HSQOBJECT _threadObj, _closure, _closureOverride, _envObj;
 	CutsceneState _state;
 	bool _showCursor = false;


Commit: 97c435373801e9fe737b9d2d5addb656abb2388a
    https://github.com/scummvm/scummvm/commit/97c435373801e9fe737b9d2d5addb656abb2388a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix pathfinding

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/clipper/clipper.hpp
    engines/twp/graph.cpp
    engines/twp/graph.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/squtil.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp
    engines/twp/util.h
    engines/twp/walkboxnode.cpp
    engines/twp/walkboxnode.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 28df21703f2..a62d13686ba 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -204,7 +204,7 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get object");
 	else
 		obj = g_engine->_actor;
-	sqpush(v, distance(actor->_node->getPos(), obj->getUsePos()));
+	sqpush(v, distance((Vector2i)actor->_node->getPos(), (Vector2i)obj->getUsePos()));
 	return 1;
 }
 
@@ -219,7 +219,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot");
 		// not sure about this, needs to be check one day ;)
-		sqpush(v, distance(actor1->_node->getAbsPos(), obj->getUsePos()) < distance(actor2->_node->getAbsPos(), obj->getUsePos()));
+		sqpush(v, distance((Vector2i)actor1->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < distance((Vector2i)actor2->_node->getAbsPos(), (Vector2i)obj->getUsePos()));
 		return 1;
 	} else if (nArgs == 4) {
 		Object *actor = sqactor(v, 2);
@@ -231,7 +231,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		int dist;
 		if (SQ_FAILED(sqget(v, 4, dist)))
 			return sq_throwerror(v, "failed to get distance");
-		sqpush(v, distance(actor->_node->getAbsPos(), obj->getUsePos()) < dist);
+		sqpush(v, distance((Vector2i)actor->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < dist);
 		return 1;
 	} else {
 		return sq_throwerror(v, "actorDistanceWithin not implemented");
@@ -336,7 +336,7 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 	for (size_t i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
 		const Walkbox &walkbox = g_engine->_room->_walkboxes[i];
 		if (walkbox._name == name) {
-			if (walkbox.contains(actor->_node->getAbsPos())) {
+			if (walkbox.contains((Vector2i)actor->_node->getAbsPos())) {
 				sqpush(v, true);
 				return 1;
 			}
@@ -689,7 +689,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 		dir = Math::Vector2d(dist, 0);
 		break;
 	}
-	actor->walk(actor->_node->getAbsPos() + dir);
+	actor->walk((Vector2i)(actor->_node->getAbsPos() + dir));
 	return 0;
 }
 
@@ -760,7 +760,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get dir");
 			facing = (Facing *)&dir;
 		}
-		actor->walk(Math::Vector2d(x, y), facing ? *facing : 0);
+		actor->walk(Vector2i(x, y), facing ? *facing : 0);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
diff --git a/engines/twp/clipper/clipper.hpp b/engines/twp/clipper/clipper.hpp
index c036c130f9f..1193a33b335 100644
--- a/engines/twp/clipper/clipper.hpp
+++ b/engines/twp/clipper/clipper.hpp
@@ -38,7 +38,7 @@
 
 //use_int32: When enabled 32bit ints are used instead of 64bit ints. This
 //improve performance but coordinate values are limited to the range +/- 46340
-//#define use_int32
+#define use_int32
 
 //use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
 //#define use_xyz
@@ -47,7 +47,7 @@
 #define use_lines
 
 //use_deprecated: Enables temporary support for the obsolete functions
-//#define use_deprecated  
+//#define use_deprecated
 
 #include <vector>
 #include <list>
@@ -302,7 +302,7 @@ private:
   bool m_UsingPolyTree;
   bool m_StrictSimple;
 #ifdef use_xyz
-  ZFillCallback   m_ZFill; //custom callback 
+  ZFillCallback   m_ZFill; //custom callback
 #endif
   void SetWindingCount(TEdge &edge);
   bool IsEvenOddFillType(const TEdge &edge) const;
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 130849c31dc..3a544c7aeb3 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -19,49 +19,14 @@
  *
  */
 
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
 #include "twp/graph.h"
 #include "twp/util.h"
-
-#define EPSILON 1e-9
+#include "twp/clipper/clipper.hpp"
 
 namespace Twp {
 
-struct Segment {
-	Segment(Math::Vector2d s, Math::Vector2d t);
-	void normalize();
-	float distance(Math::Vector2d p);
-
-	Math::Vector2d start, to;
-	float left, right, top, bottom;
-	float a, b, c;
-};
-
-Segment::Segment(Math::Vector2d s, Math::Vector2d t) {
-	start = s;
-	to = t;
-	left = MIN(s.getX(), t.getX());
-	right = MAX(s.getX(), t.getX());
-	top = MIN(s.getY(), t.getY());
-	bottom = MAX(s.getY(), t.getY());
-	a = s.getY() - t.getY();
-	b = t.getX() - s.getX();
-	c = -a * s.getX() - b * s.getY();
-	normalize();
-}
-
-void Segment::normalize() {
-	float z = sqrt(a * a + b * b);
-	if (abs(z) > EPSILON) {
-		a /= z;
-		b /= z;
-		c /= z;
-	}
-}
-
-float Segment::distance(Math::Vector2d p) {
-	return a * p.getX() + b * p.getY() + c;
-}
-
 IndexedPriorityQueue::IndexedPriorityQueue(Common::Array<float> &keys)
 	: _keys(keys) {
 }
@@ -111,25 +76,11 @@ bool IndexedPriorityQueue::isEmpty() {
 
 Graph::Graph() {}
 
-Graph::Graph(const Graph &graph) {
-	_nodes = graph._nodes;
-	_concaveVertices = graph._concaveVertices;
-	for (size_t i = 0; i < graph._edges.size(); i++) {
-		const Common::Array<GraphEdge> &e = graph._edges[i];
-		Common::Array<GraphEdge> sEdges;
-		for (size_t j = 0; j < e.size(); j++) {
-			const GraphEdge &se = e[j];
-			sEdges.push_back(GraphEdge(se.start, se.to, se.cost));
-		}
-		_edges.push_back(sEdges);
-	}
-}
-
 GraphEdge::GraphEdge(int s, int t, float c)
 	: start(s), to(t), cost(c) {
 }
 
-void Graph::addNode(Math::Vector2d node) {
+void Graph::addNode(Vector2i node) {
 	_nodes.push_back(node);
 	_edges.push_back(Common::Array<GraphEdge>());
 }
@@ -139,16 +90,11 @@ AStar::AStar(Graph *graph)
 	_graph = graph;
 }
 
-// TODO this really should have some simd optimization
-// matrix multiplication is based on this
-static float dot(Math::Vector2d u, Math::Vector2d v) {
-	float result = 0.f;
-	result += u.getX() * v.getX();
-	result += u.getY() * v.getY();
-	return result;
+static float dot(Vector2i u, Vector2i v) {
+	return (u.x * v.x) + (u.y * v.y);
 }
 
-static float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
+static float length(Vector2i v) { return sqrt(dot(v, v)); }
 
 void AStar::search(int source, int target) {
 	IndexedPriorityQueue pq(_fCost);
@@ -157,7 +103,6 @@ void AStar::search(int source, int target) {
 		int NCN = pq.pop();
 		_spt[NCN] = _sf[NCN];
 		if (NCN != target) {
-			// for (edge in _graph->edges[NCN]) {
 			for (size_t i = 0; i < _graph->_edges[NCN].size(); i++) {
 				GraphEdge &edge = _graph->_edges[NCN][i];
 				float Hcost = length(_graph->_nodes[edge.to] - _graph->_nodes[target]);
@@ -227,54 +172,18 @@ void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
 	_graph = nullptr;
 }
 
-// Indicates whether or not the specified position is inside this walkbox.
-static bool inside(const Walkbox &self, Math::Vector2d position, bool toleranceOnOutside = true) {
-	bool result = false;
-	Math::Vector2d point = position;
-	const float epsilon = 1.0f;
-
-	// Must have 3 or more edges
-	const Common::Array<Math::Vector2d> &polygon = self.getPoints();
-	if (polygon.size() < 3)
-		return false;
-
-	Math::Vector2d oldPoint(polygon[polygon.size() - 1]);
-	float oldSqDist = distanceSquared(oldPoint, point);
-
-	for (size_t i = 0; i < polygon.size(); i++) {
-		Math::Vector2d newPoint = polygon[i];
-		float newSqDist = distanceSquared(newPoint, point);
-
-		if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
-			return toleranceOnOutside;
-
-		Math::Vector2d left;
-		Math::Vector2d right;
-		if (newPoint.getX() > oldPoint.getX()) {
-			left = oldPoint;
-			right = newPoint;
-		} else {
-			left = newPoint;
-			right = oldPoint;
-		}
-
-		if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX()) < (right.getY() - left.getY()) * (point.getX() - left.getX())))
-			result = !result;
-
-		oldPoint = newPoint;
-		oldSqDist = newSqDist;
-	}
-	return result;
+static Vector2i toVector2i(float x, float y) {
+	return Vector2i(round(x), round(y));
 }
 
-Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
+Vector2i Walkbox::getClosestPointOnEdge(Vector2i p) const {
 	int vi1 = -1;
 	int vi2 = -1;
 	float minDist = 100000.0f;
 
-	const Common::Array<Math::Vector2d> &polygon = getPoints();
+	const Common::Array<Vector2i> &polygon = getPoints();
 	for (size_t i = 0; i < polygon.size(); i++) {
-		float dist = distanceToSegment(p3, polygon[i], polygon[(i + 1) % polygon.size()]);
+		float dist = distanceToSegment(p, polygon[i], polygon[(i + 1) % polygon.size()]);
 		if (dist < minDist) {
 			minDist = dist;
 			vi1 = i;
@@ -282,15 +191,15 @@ Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
 		}
 	}
 
-	Math::Vector2d p1 = polygon[vi1];
-	Math::Vector2d p2 = polygon[vi2];
+	Vector2i p1 = polygon[vi1];
+	Vector2i p2 = polygon[vi2];
 
-	float x1 = p1.getX();
-	float y1 = p1.getY();
-	float x2 = p2.getX();
-	float y2 = p2.getY();
-	float x3 = p3.getX();
-	float y3 = p3.getY();
+	float x1 = p1.x;
+	float y1 = p1.y;
+	float x2 = p2.x;
+	float y2 = p2.y;
+	float x3 = p.x;
+	float y3 = p.y;
 
 	float u = (((x3 - x1) * (x2 - x1)) + ((y3 - y1) * (y2 - y1))) / (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
 
@@ -298,67 +207,31 @@ Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p3) const {
 	float yu = y1 + u * (y2 - y1);
 
 	if (u < 0)
-		return Math::Vector2d(x1, y1);
+		return toVector2i(x1, y1);
 	if (u > 1)
-		return Math::Vector2d(x2, y2);
-	return Math::Vector2d(xu, yu);
-}
-
-static bool less(Math::Vector2d p1, Math::Vector2d p2) {
-	return (((p1.getX() < p2.getX() - EPSILON) ||
-			 (abs(p1.getX() - p2.getX()) < EPSILON)) &&
-			(p1.getY() < p2.getY() - EPSILON));
-}
-
-static float det(float a, float b, float c, float d) {
-	return a * d - b * c;
-}
-
-static bool betw(float l, float r, float x) {
-	return (MIN(l, r) <= x + EPSILON) && (x <= MAX(l, r) + EPSILON);
-}
-
-static bool intersect_1d(float a, float b, float c, float d) {
-	float a2 = a;
-	float b2 = b;
-	float c2 = c;
-	float d2 = d;
-	if (a2 > b2)
-		SWAP(a2, b2);
-	if (c2 > d2)
-		SWAP(c2, d2);
-	return MAX(a2, c2) <= MIN(b2, d2) + EPSILON;
+		return toVector2i(x2, y2);
+	return toVector2i(xu, yu);
 }
 
-static bool lineSegmentsCross(Math::Vector2d a1, Math::Vector2d b1, Math::Vector2d c1, Math::Vector2d d1) {
-	Math::Vector2d a = a1;
-	Math::Vector2d b = b1;
-	Math::Vector2d c = c1;
-	Math::Vector2d d = d1;
-	if ((!intersect_1d(a.getX(), b.getX(), c.getX(), d.getX())) || (!intersect_1d(a.getY(), b.getY(), c.getY(), d.getY())))
+static bool lineSegmentsCross(Vector2i a, Vector2i b, Vector2i c, Vector2i d) {
+	const float EPSILON = 4.f;
+	const float denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x));
+	if (abs(denominator) < EPSILON) {
 		return false;
+	}
 
-	Segment m(a, b);
-	Segment n(c, d);
-	float zn = det(m.a, m.b, n.a, n.b);
-
-	if (abs(zn) < EPSILON) {
-		if ((abs(m.distance(c)) > EPSILON) || (abs(n.distance(a)) > EPSILON))
-			return false;
-
-		if (less(b, a))
-			SWAP(a, b);
-		if (less(d, c))
-			SWAP(c, d);
-		return true;
+	const float numerator1 = ((a.y - c.y) * (d.x - c.x)) - ((a.x - c.x) * (d.y - c.y));
+	const float numerator2 = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y));
+	if ((abs(numerator1) < EPSILON) || (abs(numerator2) < EPSILON)) {
+		return false;
 	}
 
-	float lx = -det(m.c, m.b, n.c, n.b) / zn;
-	float ly = -det(m.a, m.c, n.a, n.c) / zn;
-	return betw(a.getX(), b.getX(), lx) && betw(a.getY(), b.getY(), ly) && betw(c.getX(), d.getX(), lx) && betw(c.getY(), d.getY(), ly);
+	const float r = numerator1 / denominator;
+	const float s = numerator2 / denominator;
+	return ((r > 0.f) && (r < 1.f)) && ((s > 0.f) && (s < 1.f));
 }
 
-bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
+bool PathFinder::inLineOfSight(Vector2i start, Vector2i to) {
 	const float epsilon = 0.5f;
 
 	// Not in LOS if any of the ends is outside the polygon
@@ -370,13 +243,13 @@ bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 		return true;
 
 	// Not in LOS if any edge is intersected by the start-end line segment
-	for (size_t i = 0; i < _walkboxes.size(); i++) {
-		Walkbox &walkbox = _walkboxes[i];
-		const Common::Array<Math::Vector2d> &polygon = walkbox.getPoints();
-		int size = polygon.size();
-		for (int j = 0; j < size; j++) {
-			Math::Vector2d v1 = polygon[j];
-			Math::Vector2d v2 = polygon[(j + 1) % size];
+	for (uint i = 0; i < _walkboxes.size(); i++) {
+		const Walkbox &walkbox = _walkboxes[i];
+		const Common::Array<Vector2i> &polygon = walkbox.getPoints();
+		const uint size = polygon.size();
+		for (uint j = 0; j < size; j++) {
+			Vector2i v1 = polygon[j];
+			Vector2i v2 = polygon[(j + 1) % size];
 			if (!lineSegmentsCross(start, to, v1, v2))
 				continue;
 
@@ -387,19 +260,20 @@ bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 	}
 
 	// Finally the middle point in the segment determines if in LOS or not
-	Math::Vector2d v2 = (start + to) / 2.0f;
-	bool result = _walkboxes[0].contains(v2);
-	for (size_t i = 1; i < _walkboxes.size(); i++) {
+	const Vector2i v2 = (start + to) / 2.0f;
+	if (!_walkboxes[0].contains(v2))
+		return false;
+	for (uint i = 1; i < _walkboxes.size(); i++) {
 		if (_walkboxes[i].contains(v2, false))
-			result = false;
+			return false;
 	}
-	return result;
+	return true;
 }
 
-static int minIndex(const Common::Array<float> values) {
+static uint minIndex(const Common::Array<float> &values) {
 	float min = values[0];
-	int index = 0;
-	for (size_t i = 1; i < values.size(); i++) {
+	uint index = 0;
+	for (uint i = 1; i < values.size(); i++) {
 		if (values[i] < min) {
 			index = i;
 			min = values[i];
@@ -410,13 +284,15 @@ static int minIndex(const Common::Array<float> values) {
 
 Graph *PathFinder::createGraph() {
 	Graph *result = new Graph();
-	for (size_t i = 0; i < _walkboxes.size(); i++) {
-		Walkbox &walkbox = _walkboxes[i];
+	for (uint i = 0; i < _walkboxes.size(); i++) {
+		const Walkbox &walkbox = _walkboxes[i];
 		if (walkbox.getPoints().size() > 2) {
-			bool visible = walkbox.isVisible();
-			for (size_t j = 0; j < walkbox.getPoints().size(); j++) {
-				if (walkbox.concave(j) == visible) {
-					Math::Vector2d vertex = walkbox.getPoints()[j];
+			bool firstWalkbox = (i == 0);
+			if (!walkbox.isVisible())
+				firstWalkbox = true;
+			for (uint j = 0; j < walkbox.getPoints().size(); j++) {
+				if (walkbox.concave(j) == firstWalkbox) {
+					const Vector2i &vertex = walkbox.getPoints()[j];
 					result->_concaveVertices.push_back(vertex);
 					result->addNode(vertex);
 				}
@@ -424,12 +300,12 @@ Graph *PathFinder::createGraph() {
 		}
 	}
 
-	for (size_t i = 0; i < result->_concaveVertices.size(); i++) {
-		for (size_t j = 0; j < result->_concaveVertices.size(); j++) {
-			Math::Vector2d c1(result->_concaveVertices[i]);
-			Math::Vector2d c2(result->_concaveVertices[j]);
+	for (uint i = 0; i < result->_concaveVertices.size(); i++) {
+		for (uint j = 0; j < result->_concaveVertices.size(); j++) {
+			const Vector2i c1(result->_concaveVertices[i]);
+			const Vector2i c2(result->_concaveVertices[j]);
 			if (inLineOfSight(c1, c2)) {
-				float d = distance(c1, c2);
+				const float d = distance(c1, c2);
 				result->addEdge(GraphEdge(i, j, d));
 			}
 		}
@@ -437,27 +313,27 @@ Graph *PathFinder::createGraph() {
 	return result;
 }
 
-Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Math::Vector2d to) {
-	Common::Array<Math::Vector2d> result;
+Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
+	Common::Array<Vector2i> result;
 	if (_walkboxes.size() > 0) {
 		// find the walkbox where the actor is and put it first
-		for (size_t i = 0; i < _walkboxes.size(); i++) {
+		for (uint i = 0; i < _walkboxes.size(); i++) {
 			const Walkbox &wb = _walkboxes[i];
-			if (inside(wb, start) && (i != 0)) {
+			if (wb.contains(start) && (i != 0)) {
 				SWAP(_walkboxes[0], _walkboxes[i]);
 				break;
 			}
 		}
 
 		// if no walkbox has been found => find the nearest walkbox
-		if (!inside(_walkboxes[0], start)) {
+		if (!_walkboxes[0].contains(start)) {
 			Common::Array<float> dists(_walkboxes.size());
-			for (size_t i = 0; i < _walkboxes.size(); i++) {
-				Walkbox wb = _walkboxes[i];
+			for (uint i = 0; i < _walkboxes.size(); i++) {
+				const Walkbox &wb = _walkboxes[i];
 				dists[i] = distance(wb.getClosestPointOnEdge(start), start);
 			}
 
-			int index = minIndex(dists);
+			const uint index = minIndex(dists);
 			if (index != 0)
 				SWAP(_walkboxes[0], _walkboxes[index]);
 		}
@@ -466,39 +342,51 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 			_graph = createGraph();
 
 		// create new node on start position
-		Graph *walkgraph = new Graph(*_graph);
-		int startNodeIndex = walkgraph->_nodes.size();
+		_walkgraph = *_graph;
+		const uint startNodeIndex = _walkgraph._nodes.size();
 
 		// if destination is not inside current walkable area, then get the closest point
 		const Walkbox &wb = _walkboxes[0];
-		if (wb.isVisible() && !wb.contains(to))
+		if (wb.isVisible() && !wb.contains(start)) {
+			start = wb.getClosestPointOnEdge(start);
+		}
+		if (wb.isVisible() && !wb.contains(to)) {
 			to = wb.getClosestPointOnEdge(to);
+		}
+		// we don't want the actor to walk in a different walkbox
+		// then check if endpoint is inside one of the other walkboxes and find closest point on edge
+		for (uint i = 1; i < _walkboxes.size(); i++) {
+			if (_walkboxes[i].contains(to)) {
+				to = _walkboxes[i].getClosestPointOnEdge(to);
+				break;
+			}
+		}
 
-		walkgraph->addNode(start);
+		_walkgraph.addNode(start);
 
-		for (size_t i = 0; i < walkgraph->_concaveVertices.size(); i++) {
-			Math::Vector2d c = walkgraph->_concaveVertices[i];
+		for (uint i = 0; i < _walkgraph._concaveVertices.size(); i++) {
+			const Vector2i c = _walkgraph._concaveVertices[i];
 			if (inLineOfSight(start, c))
-				walkgraph->addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
+				_walkgraph.addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
 		}
 
 		// create new node on end position
-		int endNodeIndex = walkgraph->_nodes.size();
-		walkgraph->addNode(to);
+		const uint endNodeIndex = _walkgraph._nodes.size();
+		_walkgraph.addNode(to);
 
-		for (size_t i = 0; i < walkgraph->_concaveVertices.size(); i++) {
-			Math::Vector2d c = walkgraph->_concaveVertices[i];
+		for (uint i = 0; i < _walkgraph._concaveVertices.size(); i++) {
+			const Vector2i c = _walkgraph._concaveVertices[i];
 			if (inLineOfSight(to, c))
-				walkgraph->addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
+				_walkgraph.addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
 		}
 
 		if (inLineOfSight(start, to))
-			walkgraph->addEdge(GraphEdge(startNodeIndex, endNodeIndex, distance(start, to)));
+			_walkgraph.addEdge(GraphEdge(startNodeIndex, endNodeIndex, distance(start, to)));
 
-		Common::Array<int> indices = walkgraph->getPath(startNodeIndex, endNodeIndex);
-		for (size_t i = 0; i < indices.size(); i++) {
-			int index = indices[i];
-			result.push_back(walkgraph->_nodes[index]);
+		const Common::Array<int> indices = _walkgraph.getPath(startNodeIndex, endNodeIndex);
+		for (uint i = 0; i < indices.size(); i++) {
+			const int index = indices[i];
+			result.push_back(_walkgraph._nodes[index]);
 		}
 	}
 	return result;
diff --git a/engines/twp/graph.h b/engines/twp/graph.h
index 38f9d4a2783..e4bfb8ece5b 100644
--- a/engines/twp/graph.h
+++ b/engines/twp/graph.h
@@ -24,6 +24,7 @@
 
 #include "common/array.h"
 #include "math/vector2d.h"
+#include "twp/util.h"
 
 namespace Twp {
 
@@ -62,16 +63,15 @@ struct GraphEdge {
 class Graph {
 public:
 	Graph();
-	Graph(const Graph &graph);
-	void addNode(Math::Vector2d node);
+	void addNode(Vector2i node);
 	void addEdge(GraphEdge edge);
 	// Gets the edge from 'from' index to 'to' index.
 	GraphEdge *edge(int start, int to);
 	Common::Array<int> getPath(int source, int target);
 
-	Common::Array<Math::Vector2d> _nodes;
+	Common::Array<Vector2i> _nodes;
 	Common::Array<Common::Array<GraphEdge> > _edges;
-	Common::Array<Math::Vector2d> _concaveVertices;
+	Common::Array<Vector2i> _concaveVertices;
 };
 
 class AStar {
@@ -83,27 +83,27 @@ public:
 	Common::Array<GraphEdge *> _spt; // The Shortest Path Tree
 	Common::Array<float> _gCost;     // This array will store the G cost of each node
 	Common::Array<float> _fCost;     // This array will store the F cost of each node
-	Common::Array<GraphEdge*> _sf;    // The Search Frontier
+	Common::Array<GraphEdge *> _sf;  // The Search Frontier
 };
 
 // Represents an area where an actor can or cannot walk
 class Walkbox {
 public:
-	Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible = true);
+	Walkbox(const Common::Array<Vector2i> &polygon, bool visible = true);
 
-  	// Indicates whether or not the specified position is inside this walkbox.
-	bool contains(Math::Vector2d position, bool toleranceOnOutside = true) const;
+	// Indicates whether or not the specified position is inside this walkbox.
+	bool contains(Vector2i position, bool toleranceOnOutside = true) const;
 	bool concave(int vertex) const;
 	void setVisible(bool visible) { _visible = visible; }
 	bool isVisible() const { return _visible; }
-	const Common::Array<Math::Vector2d>& getPoints() const { return _polygon; }
-	Math::Vector2d getClosestPointOnEdge(Math::Vector2d p3) const;
+	const Common::Array<Vector2i> &getPoints() const { return _polygon; }
+	Vector2i getClosestPointOnEdge(Vector2i p) const;
 
 public:
 	Common::String _name;
 
 private:
-	Common::Array<Math::Vector2d> _polygon;
+	Common::Array<Vector2i> _polygon;
 	bool _visible;
 };
 
@@ -111,18 +111,20 @@ private:
 class PathFinder {
 public:
 	void setWalkboxes(const Common::Array<Walkbox> &walkboxes);
-	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d start, Math::Vector2d to);
+	Common::Array<Walkbox> getWalkboxes() const { return _walkboxes; }
+	Common::Array<Vector2i> calculatePath(Vector2i start, Vector2i to);
 	void setDirty(bool dirty) { _isDirty = dirty; }
 	bool isDirty() const { return _isDirty; }
-	const Graph* getGraph() const { return _graph; }
+	const Graph &getGraph() const { return _walkgraph; }
 
 private:
 	Graph *createGraph();
-	bool inLineOfSight(Math::Vector2d start, Math::Vector2d to);
+	bool inLineOfSight(Vector2i start, Vector2i to);
 
 private:
 	Common::Array<Walkbox> _walkboxes;
 	Graph *_graph = nullptr;
+	Graph _walkgraph;
 	bool _isDirty = true;
 };
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 91bd66ec8fe..758ab725b65 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -164,13 +164,14 @@ void ReachAnim::update(float elapsed) {
 	}
 }
 
-WalkTo::WalkTo(Object *obj, Math::Vector2d dest, int facing)
+WalkTo::WalkTo(Object *obj, Vector2i dest, int facing)
 	: _obj(obj), _facing(facing) {
 	if (obj->_useWalkboxes) {
-		_path = obj->_room->calculatePath(obj->_node->getAbsPos(), dest);
+		_path = obj->_room->calculatePath((Vector2i)obj->_node->getAbsPos(), dest);
 	} else {
-		_path = {obj->_node->getAbsPos(), dest};
+		_path = {(Vector2i)obj->_node->getAbsPos(), dest};
 	}
+
 	_wsd = sqrt(obj->_walkSpeed.getX() * obj->_walkSpeed.getX() + obj->_walkSpeed.getY() * obj->_walkSpeed.getY());
 	if (sqrawexists(obj->_table, "preWalking"))
 		sqcall(obj->_table, "preWalking");
@@ -234,18 +235,18 @@ void WalkTo::actorArrived() {
 
 void WalkTo::update(float elapsed) {
 	if (_path.size() != 0) {
-		Math::Vector2d dest = _path[0];
-		float d = distance(dest, _obj->_node->getAbsPos());
+		Vector2i dest = _path[0];
+		float d = distance(dest, (Vector2i)_obj->_node->getAbsPos());
 
 		// arrived at destination ?
 		if (d < 1.0) {
-			_obj->_node->setPos(_path[0]);
+			_obj->_node->setPos((Math::Vector2d)_path[0]);
 			_path.remove_at(0);
 			if (_path.size() == 0) {
 				actorArrived();
 			}
 		} else {
-			Math::Vector2d delta = dest - _obj->_node->getAbsPos();
+			Math::Vector2d delta = (Math::Vector2d)dest - _obj->_node->getAbsPos();
 			float duration = d / _wsd;
 			float factor = Twp::clamp(elapsed / duration, 0.f, 1.f);
 
@@ -337,7 +338,8 @@ int Talking::loadActorSpeech(const Common::String &name) {
 }
 
 void Talking::say(const Common::String &text) {
-	if(text.empty()) return;
+	if (text.empty())
+		return;
 
 	Common::String txt(text);
 	if (text[0] == '@') {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index f3c6b30597d..e979aff9100 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -205,10 +205,10 @@ private:
 
 class WalkTo : public Motor {
 public:
-	WalkTo(Object *obj, Math::Vector2d dest, int facing = 0);
+	WalkTo(Object *obj, Vector2i dest, int facing = 0);
 	virtual void disable() override;
 
-	const Common::Array<Math::Vector2d> &getPath() const { return _path; }
+	const Common::Array<Vector2i> &getPath() const { return _path; }
 
 private:
 	void actorArrived();
@@ -216,7 +216,7 @@ private:
 
 private:
 	Object *_obj = nullptr;
-	Common::Array<Math::Vector2d> _path;
+	Common::Array<Vector2i> _path;
 	int _facing = 0;
 	float _wsd;
 };
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 4931f0b9200..773b6dabd76 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -665,10 +665,9 @@ static bool verbNotClose(VerbId id) {
 }
 
 static void cantReach(Object *self, Object *noun2) {
-	// TODO: check if we need to use sqrawexists or sqexists
 	if (sqrawexists(self->_table, "verbCantReach")) {
 		int nParams = sqparamCount(g_engine->getVm(), self->_table, "verbCantReach");
-		debug("verbCantReach found in obj '{self.key}' with {nParams} params");
+		debug("verbCantReach found in obj '%s' with %d params", self->_key.c_str(), nParams);
 		if (nParams == 1) {
 			sqcall(self->_table, "verbCantReach");
 		} else {
@@ -676,12 +675,13 @@ static void cantReach(Object *self, Object *noun2) {
 			sq_resetobject(&table);
 			if (noun2)
 				table = noun2->_table;
-			sqcall(self->_table, "verbCantReach", self->_table, table);
+			sqcall(self->_table, "verbCantReach", table);
 		}
-	} else if (!noun2) {
+	} else if (noun2) {
 		cantReach(noun2, nullptr);
 	} else {
 		HSQOBJECT nilTbl;
+		sq_resetobject(&nilTbl);
 		sqcall(g_engine->_defaultObj, "verbCantReach", self->_table, !noun2 ? nilTbl : noun2->_table);
 	}
 }
@@ -701,11 +701,11 @@ void Object::execVerb() {
 				return;
 			}
 			// Did we get close enough?
-			float dist = distance(getUsePos(), noun1->getUsePos());
+			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun1->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debug("actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
-				cantReach(this, noun1);
+				cantReach(noun1, noun2);
 				return;
 			}
 			if (noun1->_useDir != dNone) {
@@ -719,11 +719,11 @@ void Object::execVerb() {
 				_exec.enabled = false;
 				return;
 			}
-			float dist = distance(getUsePos(), noun2->getUsePos());
+			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun2->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debug("actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
 			if (dist > min_dist) {
-				cantReach(this, noun2);
+				cantReach(noun1, noun2);
 				return;
 			}
 		}
@@ -735,8 +735,8 @@ void Object::execVerb() {
 }
 
 // Walks an actor to the `pos` or actor `obj` and then faces `dir`.
-void Object::walk(Math::Vector2d pos, int facing) {
-	debug("walk to obj %s: %f,%f, %d", _key.c_str(), pos.getX(), pos.getY(), facing);
+void Object::walk(Vector2i pos, int facing) {
+	debug("walk to obj %s: %d,%d, %d", _key.c_str(), pos.x, pos.y, facing);
 	if (!_walkTo || (!_walkTo->isEnabled())) {
 		play(getAnimName(WALK_ANIMNAME), true);
 	}
@@ -747,7 +747,7 @@ void Object::walk(Math::Vector2d pos, int facing) {
 void Object::walk(Object *obj) {
 	debug("walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
 	Facing facing = (Facing)obj->_useDir;
-	walk(obj->getUsePos(), facing);
+	walk((Vector2i)obj->getUsePos(), facing);
 }
 
 void Object::turn(Facing facing) {
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 5c9b5454a95..28767ec043d 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -187,7 +187,7 @@ public:
 	void setReach(Motor *reach);
 	Motor *getWalkTo() const { return _walkTo; }
 	Motor *getReach() const { return _reach; }
-	void walk(Math::Vector2d pos, int facing = 0);
+	void walk(Vector2i pos, int facing = 0);
 	void walk(Object* obj);
 
 	void setTalking(Motor *talking);
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index f96b4dc6422..975316a246c 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -71,16 +71,16 @@ static Math::Vector2d parseParallax(const Common::JSONValue &v) {
 }
 
 static Walkbox parseWalkbox(const Common::String &text) {
-	Common::Array<Math::Vector2d> points;
+	Common::Array<Vector2i> points;
 	size_t i = 1;
 	size_t endPos;
 	do {
 		uint32 commaPos = text.find(',', i);
-		long x = strtol(text.substr(i, commaPos - i).c_str(), nullptr, 10);
+		int x = (int)strtol(text.substr(i, commaPos - i).c_str(), nullptr, 10);
 		endPos = text.find('}', commaPos + 1);
-		long y = strtol(text.substr(commaPos + 1, endPos - commaPos - 1).c_str(), nullptr, 10);
+		int y = (int)strtol(text.substr(commaPos + 1, endPos - commaPos - 1).c_str(), nullptr, 10);
 		i = endPos + 3;
-		points.push_back({(float)x, (float)y});
+		points.push_back({x, y});
 	} while ((text.size() - 1) != endPos);
 	return Walkbox(points);
 }
@@ -99,18 +99,18 @@ static Scaling parseScaling(const Common::JSONArray &jScalings) {
 
 static ClipperLib::Path toPolygon(const Walkbox &walkbox) {
 	ClipperLib::Path path;
-	const Common::Array<Math::Vector2d> &points = walkbox.getPoints();
+	const Common::Array<Vector2i> &points = walkbox.getPoints();
 	for (size_t i = 0; i < points.size(); i++) {
-		path.push_back(ClipperLib::IntPoint(points[i].getX(), points[i].getY()));
+		path.push_back(ClipperLib::IntPoint(points[i].x, points[i].y));
 	}
 	return path;
 }
 
 static Walkbox toWalkbox(const ClipperLib::Path &path) {
-	Common::Array<Math::Vector2d> pts;
+	Common::Array<Vector2i> pts;
 	for (size_t i = 0; i < path.size(); i++) {
 		const ClipperLib::IntPoint &pt = path[i];
-		pts.push_back(Math::Vector2d(pt.X, pt.Y));
+		pts.push_back(Vector2i{pt.X, pt.Y});
 	}
 	return Walkbox(pts, ClipperLib::Orientation(path));
 }
@@ -131,11 +131,16 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 		ClipperLib::Paths solutions;
 		ClipperLib::Clipper c;
 		c.AddPaths(subjects, ClipperLib::ptSubject, true);
-		c.AddPaths(clips, ClipperLib::ptClip, true);
-		c.Execute(ClipperLib::ClipType::ctDifference, solutions, ClipperLib::pftEvenOdd);
+		c.Execute(ClipperLib::ClipType::ctUnion, solutions, ClipperLib::pftEvenOdd);
 
-		for (size_t i = 0; i < solutions.size(); i++) {
-			result.push_back(toWalkbox(solutions[i]));
+		ClipperLib::Paths solutions2;
+		ClipperLib::Clipper c2;
+		c2.AddPaths(solutions, ClipperLib::ptSubject, true);
+		c2.AddPaths(clips, ClipperLib::ptClip, true);
+        c2.Execute(ClipperLib::ClipType::ctDifference, solutions2, ClipperLib::pftEvenOdd);
+
+		for (size_t i = 0; i < solutions2.size(); i++) {
+			result.push_back(toWalkbox(solutions2[i]));
 		}
 	}
 	return result;
@@ -472,16 +477,16 @@ void Room::walkboxHidden(const Common::String &name, bool hidden) {
 		if (wb._name == name) {
 			wb.setVisible(!hidden);
 			// 1 walkbox has change so update merged polygon
-			_mergedPolygon = merge(_walkboxes);
 			_pathFinder.setDirty(true);
 			return;
 		}
 	}
 }
 
-Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
+Common::Array<Vector2i> Room::calculatePath(Vector2i frm, Vector2i to) {
 	if (_mergedPolygon.size() > 0) {
 		if (_pathFinder.isDirty()) {
+			_mergedPolygon = merge(_walkboxes);
 			_pathFinder.setWalkboxes(_mergedPolygon);
 			_pathFinder.setDirty(false);
 		}
@@ -502,44 +507,44 @@ Layer::Layer(const Common::StringArray &name, Math::Vector2d parallax, int zsort
 	_zsort = zsort;
 }
 
-Walkbox::Walkbox(const Common::Array<Math::Vector2d> &polygon, bool visible)
+Walkbox::Walkbox(const Common::Array<Vector2i> &polygon, bool visible)
 	: _polygon(polygon), _visible(visible) {
 }
 
 bool Walkbox::concave(int vertex) const {
-	Math::Vector2d current = _polygon[vertex];
-	Math::Vector2d next = _polygon[(vertex + 1) % _polygon.size()];
-	Math::Vector2d previous = _polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
+	Vector2i current = _polygon[vertex];
+	Vector2i next = _polygon[(vertex + 1) % _polygon.size()];
+	Vector2i previous = _polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
 
-	Math::Vector2d left(current.getX() - previous.getX(), current.getY() - previous.getY());
-	Math::Vector2d right(next.getX() - current.getX(), next.getY() - current.getY());
+	Vector2i left{current.x - previous.x, current.y - previous.y};
+	Vector2i right{next.x - current.x, next.y - current.y};
 
-	float cross = (left.getX() * right.getY()) - (left.getY() * right.getX());
-	return _visible ? cross < 0 : cross >= 0;
+	float cross = (left.x * right.y) - (left.y * right.x);
+	return cross < 0;
 }
 
-bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
-	Math::Vector2d point = position;
-	const float epsilon = 1.0f;
+bool Walkbox::contains(Vector2i position, bool toleranceOnOutside) const {
+	Vector2i point = position;
+	const float epsilon = 2.0f;
 	bool result = false;
 
 	// Must have 3 or more edges
 	if (_polygon.size() < 3)
 		return false;
 
-	Math::Vector2d oldPoint(_polygon[_polygon.size() - 1]);
+	Vector2i oldPoint(_polygon[_polygon.size() - 1]);
 	float oldSqDist = distanceSquared(oldPoint, point);
 
 	for (size_t i = 0; i < _polygon.size(); i++) {
-		Math::Vector2d newPoint = _polygon[i];
+		Vector2i newPoint = _polygon[i];
 		float newSqDist = distanceSquared(newPoint, point);
 
 		if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
 			return toleranceOnOutside;
 
-		Math::Vector2d left;
-		Math::Vector2d right;
-		if (newPoint.getX() > oldPoint.getX()) {
+		Vector2i left;
+		Vector2i right;
+		if (newPoint.x > oldPoint.x) {
 			left = oldPoint;
 			right = newPoint;
 		} else {
@@ -547,7 +552,7 @@ bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
 			right = oldPoint;
 		}
 
-		if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX())) < ((right.getY() - left.getY()) * (point.getX() - left.getX())))
+		if ((left.x < point.x) && (point.x <= right.x) && ((point.y - left.y) * (right.x - left.x)) < ((right.y - left.y) * (point.x - left.x)))
 			result = !result;
 
 		oldPoint = newPoint;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index be115f8fadd..34f73ac7c58 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -126,7 +126,7 @@ public:
 	Color getOverlay() const;
 
 	void walkboxHidden(const Common::String &name, bool hidden);
-	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d frm, Math::Vector2d to);
+	Common::Array<Vector2i> calculatePath(Vector2i frm, Vector2i to);
 
 public:
 	Common::String _name;              // Name of the room
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 13ad1d1d39e..ba4561d9915 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -49,7 +49,7 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 
 static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
-	Math::Vector2d pos1, pos2;
+	Vector2i pos1, pos2;
 	if (numArgs == 3) {
 		int x = 0;
 		if (SQ_FAILED(sqget(v, 2, x)))
@@ -57,7 +57,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 		int y = 0;
 		if (SQ_FAILED(sqget(v, 3, y)))
 			return sq_throwerror(v, "failed to get y");
-		pos1 = Math::Vector2d(x, y);
+		pos1 = Vector2i(x, y);
 		pos2 = pos1;
 	} else if (numArgs == 5) {
 		int x1 = 0;
@@ -66,14 +66,14 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 		int y1 = 0;
 		if (SQ_FAILED(sqget(v, 3, y1)))
 			return sq_throwerror(v, "failed to get y1");
-		pos1 = Math::Vector2d(x1, y1);
+		pos1 = Vector2i(x1, y1);
 		int x2 = 0;
 		if (SQ_FAILED(sqget(v, 4, x2)))
 			return sq_throwerror(v, "failed to get x2");
 		int y2 = 0;
 		if (SQ_FAILED(sqget(v, 5, y1)))
 			return sq_throwerror(v, "failed to get y2");
-		pos2 = Math::Vector2d(x2, y2);
+		pos2 = Vector2i(x2, y2);
 	} else {
 		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
 	}
@@ -85,7 +85,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 			return 1;
 		}
 	}
-	Math::Vector2d pos = walkboxes[0].getClosestPointOnEdge(pos2);
+	Vector2i pos = walkboxes[0].getClosestPointOnEdge(pos2);
 	sqpush(v, pos);
 	return 1;
 }
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 552eabe7761..dc18d17a0aa 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -87,17 +87,22 @@ SQInteger sqpush(HSQUIRRELVM v, HSQOBJECT value) {
 }
 
 template<>
-SQInteger sqpush(HSQUIRRELVM v, Math::Vector2d value) {
+SQInteger sqpush(HSQUIRRELVM v, Vector2i value) {
 	sq_newtable(v);
 	sq_pushstring(v, "x", -1);
-	sq_pushinteger(v, value.getX());
+	sq_pushinteger(v, value.x);
 	sq_newslot(v, -3, SQFalse);
 	sq_pushstring(v, "y", -1);
-	sq_pushinteger(v, value.getY());
+	sq_pushinteger(v, value.y);
 	sq_newslot(v, -3, SQFalse);
 	return 1;
 }
 
+template<>
+SQInteger sqpush(HSQUIRRELVM v, Math::Vector2d value) {
+	return sqpush(v, (Vector2i)value);
+}
+
 template<>
 SQInteger sqpush(HSQUIRRELVM v, Rectf value) {
 	sq_newtable(v);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d7bfc0c3a83..ff0fb76243d 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -230,7 +230,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					// Just clicking on the ground
 					cancelSentence(_actor);
 					if (_actor->_room == _room)
-						_actor->walk(roomPos);
+						_actor->walk((Vector2i)roomPos);
 					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 					_holdToMove = true;
 				}
@@ -454,8 +454,8 @@ void TwpEngine::update(float elapsed) {
 					if (_holdToMove && (_time > _nextHoldToMoveTime)) {
 						walkFast();
 						cancelSentence(_actor);
-						if (_actor->_room == _room && (distance(_actor->_node->getAbsPos(), roomPos) > 5)) {
-							_actor->walk(roomPos);
+						if (_actor->_room == _room && (distance((Vector2i)_actor->_node->getAbsPos(), (Vector2i)roomPos) > 5)) {
+							_actor->walk((Vector2i)roomPos);
 						}
 						_nextHoldToMoveTime = _time + 0.250f;
 					}
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 6dec8af3096..877b6c8a796 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -130,29 +130,29 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 	}
 }
 
-float distanceSquared(Math::Vector2d p1, Math::Vector2d p2) {
-	float dx = p1.getX() - p2.getX();
-	float dy = p1.getY() - p2.getY();
+float distanceSquared(Vector2i p1, Vector2i p2) {
+	const float dx = p1.x - p2.x;
+	const float dy = p1.y - p2.y;
 	return dx * dx + dy * dy;
 }
 
-float distanceToSegmentSquared(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
-	float l2 = distanceSquared(v, w);
+float distanceToSegmentSquared(Vector2i p, Vector2i v, Vector2i w) {
+	const float l2 = distanceSquared(v, w);
 	if (l2 == 0)
 		return distanceSquared(p, v);
-	float t = ((p.getX() - v.getX()) * (w.getX() - v.getX()) + (p.getY() - v.getY()) * (w.getY() - v.getY())) / l2;
+	const float t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
 	if (t < 0)
 		return distanceSquared(p, v);
 	if (t > 1)
 		return distanceSquared(p, w);
-	return distanceSquared(p, Math::Vector2d(v.getX() + t * (w.getX() - v.getX()), v.getY() + t * (w.getY() - v.getY())));
+	return distanceSquared(p, Vector2i(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)));
 }
 
-float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
+float distanceToSegment(Vector2i p, Vector2i v, Vector2i w) {
 	return sqrt(distanceToSegmentSquared(p, v, w));
 }
 
-float distance(Math::Vector2d p1, Math::Vector2d p2) {
+float distance(Vector2i p1, Vector2i p2) {
 	return sqrt(distanceSquared(p1, p2));
 }
 
@@ -160,7 +160,7 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 	Common::String result;
 	if (array.size() > 0) {
 		result += array[0];
-		for (size_t i = 1; i < array.size(); i++) {
+		for (uint i = 1; i < array.size(); i++) {
 			result += (sep + array[i]);
 		}
 	}
@@ -170,7 +170,7 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by) {
 	Common::String result;
 	uint i = 0;
-	size_t whatSize = what.size();
+	uint whatSize = what.size();
 	while (true) {
 		uint j = s.find(what, i);
 		if (j == Common::String::npos)
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 04c4ac60101..fd0f5bac6da 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -33,6 +33,35 @@ namespace Twp {
 
 class Object;
 
+struct Vector2i {
+	int x = 0;
+	int y = 0;
+
+	Vector2i() {}
+	Vector2i(int x_, int y_) : x(x_), y(y_) {}
+	Vector2i(float x_, float y_) : x(round(x_)), y(round(y_)) {}
+	explicit Vector2i(const Math::Vector2d &p) : x(round(p.getX())), y(round(p.getY())) {}
+	explicit operator Math::Vector2d() const {
+		return Math::Vector2d(x, y);
+	}
+
+	Vector2i operator-(const Vector2i &v) const {
+		return Vector2i(x - v.x, y - v.y);
+	}
+
+	Vector2i operator+(const Vector2i &v) const {
+		return Vector2i(x + v.x, y + v.y);
+	}
+
+	Vector2i operator*(float f) const {
+		return Vector2i(x * f, y * f);
+	}
+
+	Vector2i operator/(float f) const {
+		return Vector2i(x / f, y / f);
+	}
+};
+
 // general util
 template<typename T, class DL = Common::DefaultDeleter<T> >
 using unique_ptr = Common::ScopedPtr<T, DL>;
@@ -59,7 +88,7 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 
 // array util
 template<typename T>
-size_t find(const Common::Array<T>& array, const T& o) {
+size_t find(const Common::Array<T> &array, const T &o) {
 	for (size_t i = 0; i < array.size(); i++) {
 		if (array[i] == o) {
 			return i;
@@ -69,16 +98,16 @@ size_t find(const Common::Array<T>& array, const T& o) {
 }
 
 // string util
-Common::String join(const Common::Array<Common::String>& array, const Common::String& sep);
-Common::String replace(const Common::String& s, const Common::String& what, const Common::String& by);
+Common::String join(const Common::Array<Common::String> &array, const Common::String &sep);
+Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by);
 Common::String remove(const Common::String &txt, char startC, char endC);
 
 // math util
 void scale(Math::Matrix4 &m, const Math::Vector2d &v);
 Math::Vector2d operator*(Math::Vector2d v, float f);
-float distance(Math::Vector2d p1, Math::Vector2d p2);
-float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
-float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
+float distance(Vector2i p1, Vector2i p2);
+float distanceSquared(Vector2i p1, Vector2i p2);
+float distanceToSegment(Vector2i p, Vector2i v, Vector2i w);
 
 } // namespace Twp
 
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 0466f561363..62c277f0d9d 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -35,25 +35,27 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 		Color red(1.f, 0.f, 0.f);
 		Color green(0.f, 1.f, 0.f);
 		Color yellow(1.f, 1.f, 0.f);
+		Common::Array<Walkbox> walkboxes = g_engine->_room ? g_engine->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
+
 		switch (_mode) {
 		case WalkboxMode::All: {
 			Math::Matrix4 transf;
 			// cancel camera pos
 			Math::Vector2d pos = g_engine->getGfx().cameraPos();
-			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
-			for (uint i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
-				Walkbox &wb = g_engine->_room->_walkboxes[i];
-				Color color = wb.isVisible() ? green : red;
+			transf.translate(Math::Vector3d(-pos.getX(), -pos.getY(), 0.f));
+			for (uint i = 0; i < walkboxes.size(); i++) {
+				const Walkbox &wb = walkboxes[i];
+				const Color color = wb.isVisible() ? green : red;
 				Common::Array<Vertex> vertices;
-				for (uint j = 0; j < wb.getPoints().size(); j++) {
-					Math::Vector2d p = wb.getPoints()[j];
-					vertices.push_back(Vertex(p, color));
+				const Common::Array<Vector2i>& points = wb.getPoints();
+				for (uint j = 0; j < points.size(); j++) {
+					Vector2i pInt = points[j];
+					Math::Vector2d p = (Math::Vector2d)pInt;
+					vertices.push_back(Vertex((Math::Vector2d)p, color));
 
-					Color vertexColor = wb.concave(j) ? white : yellow;
 					Math::Matrix4 t(transf);
-					p -= Math::Vector2d(2.f, 2.f);
+					p -= Math::Vector2d(1.f, 1.f);
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
-					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
 				}
 				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
@@ -62,20 +64,22 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 			Math::Matrix4 transf;
 			Math::Vector2d pos = g_engine->getGfx().cameraPos();
 			// cancel camera pos
-			transf.translate(Math::Vector3d(-pos.getX(), pos.getY(), 0.f));
-			for (uint i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
-				Walkbox &wb = g_engine->_room->_mergedPolygon[i];
-				Color color = wb.isVisible() ? green : red;
+			transf.translate(Math::Vector3d(-pos.getX(), -pos.getY(), 0.f));
+			for (uint i = 0; i < walkboxes.size(); i++) {
+				const Walkbox &wb = walkboxes[i];
+				const Color color = i == 0 ? green : red;
 				Common::Array<Vertex> vertices;
-				for (uint j = 0; j < wb.getPoints().size(); j++) {
-					Math::Vector2d p = wb.getPoints()[j];
+				const Common::Array<Vector2i>& points = wb.getPoints();
+				for (uint j = 0; j < points.size(); j++) {
+					Vector2i pInt = points[j];
+					Math::Vector2d p = (Math::Vector2d)pInt;
 					vertices.push_back(Vertex(p, color));
 
-					Color vertexColor = wb.concave(j) ? white : yellow;
 					Math::Matrix4 t(transf);
-					p -= Math::Vector2d(2.f, 2.f);
+					p -= Math::Vector2d(1.f, 1.f);
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
-					g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), vertexColor, t);
+					// if (wb.concave(j) == (i == 0))
+					// 	g_engine->getGfx().drawQuad(Math::Vector2d(3.f, 3.f), yellow, t);
 				}
 				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
@@ -90,11 +94,11 @@ PathNode::PathNode() : Node("Path") {
 	_zOrder = -1000;
 }
 
-Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
+Vector2i PathNode::fixPos(Vector2i pos) {
 	for (size_t i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
 		Walkbox &wb = g_engine->_room->_mergedPolygon[i];
 		if (!wb.isVisible() && wb.contains(pos)) {
-			return wb.getClosestPointOnEdge(pos);
+			return wb.getClosestPointOnEdge((Vector2i)pos);
 		}
 	}
 	//   for wb in gEngine.room.mergedPolygon:
@@ -104,21 +108,24 @@ Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
 }
 
 void PathNode::drawCore(Math::Matrix4 trsf) {
-	if(!g_engine->_room) return;
+	if (!g_engine->_room)
+		return;
+
+	const Color green(0.f, 1.f, 0.f);
+	const Color red(1.f, 0.f, 0.f);
+	const Color yellow(1.f, 1.f, 0.f);
+	const Color blue(0.f, 0.f, 1.f);
+	const Object *actor = g_engine->_actor;
 
-	Color red(1.f, 0.f, 0.f);
-	Color yellow(1.f, 1.f, 0.f);
-	Color blue(0.f, 0.f, 1.f);
-	Object *actor = g_engine->_actor;
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
-		WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
-		const Common::Array<Math::Vector2d> &path = walkTo->getPath();
+		const WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
+		const Common::Array<Vector2i> &path = walkTo->getPath();
 		if (path.size() > 0) {
 			Common::Array<Vertex> vertices;
 			vertices.push_back(Vertex(g_engine->roomToScreen(actor->_node->getPos()), yellow));
 			for (uint i = 0; i < path.size(); i++) {
-				Math::Vector2d p = g_engine->roomToScreen(path[i]);
+				Math::Vector2d p = g_engine->roomToScreen((Math::Vector2d)path[i]);
 				vertices.push_back(Vertex(p, yellow));
 
 				Math::Matrix4 t;
@@ -131,23 +138,22 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	}
 
 	// draw graph nodes
-	const Twp::Graph *graph = g_engine->_room->_pathFinder.getGraph();
-	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && graph) {
-		for (uint i = 0; i < graph->_concaveVertices.size(); i++) {
-			Math::Vector2d v = graph->_concaveVertices[i];
+	const Twp::Graph& graph = g_engine->_room->_pathFinder.getGraph();
+	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All))) {
+		for (uint i = 0; i < graph._concaveVertices.size(); i++) {
+			const Math::Vector2d p = g_engine->roomToScreen((Math::Vector2d)graph._concaveVertices[i]) - Math::Vector2d(2.f, 2.f);
 			Math::Matrix4 t;
-			Math::Vector2d p = g_engine->roomToScreen(v) - Math::Vector2d(2.f, 2.f);
 			t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
-			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow);
+			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 		}
 
 		if (_mode == PathMode::All) {
-			for (uint i = 0; i < graph->_edges.size(); i++) {
-				const Common::Array<GraphEdge> &edges = graph->_edges[i];
+			for (uint i = 0; i < graph._edges.size(); i++) {
+				const Common::Array<GraphEdge> &edges = graph._edges[i];
 				for (uint j = 0; j < edges.size(); j++) {
 					const GraphEdge &edge = edges[j];
-					Math::Vector2d p1 = g_engine->roomToScreen(graph->_nodes[edge.start]);
-					Math::Vector2d p2 = g_engine->roomToScreen(graph->_nodes[edge.to]);
+					const Math::Vector2d p1 = g_engine->roomToScreen((Math::Vector2d)graph._nodes[edge.start]);
+					const Math::Vector2d p2 = g_engine->roomToScreen((Math::Vector2d)graph._nodes[edge.to]);
 					Vertex vertices[] = {Vertex(p1), Vertex(p2)};
 					g_engine->getGfx().drawLines(&vertices[0], 2);
 				}
@@ -162,30 +168,47 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 
-		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
-		Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
-		Math::Vector2d p = fixPos(roomPos);
+		const Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
+		const Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
+		Vector2i p = fixPos((Vector2i)roomPos);
 		t = Math::Matrix4();
-		pos = g_engine->roomToScreen(p) - Math::Vector2d(4.f, 4.f);
+		pos = g_engine->roomToScreen((Math::Vector2d)p) - Math::Vector2d(4.f, 4.f);
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
 
-		Object* obj = g_engine->objAt(roomPos);
-		if(obj) {
+		Object *obj = g_engine->objAt(roomPos);
+		if (obj) {
 			t = Math::Matrix4();
 			pos = g_engine->roomToScreen(obj->getUsePos()) - Math::Vector2d(4.f, 4.f);
 			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 			g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), red, t);
 		}
 
-		Common::Array<Math::Vector2d> path = g_engine->_room->calculatePath(fixPos(actor->_node->getPos()), p);
+		const Common::Array<Vector2i> path = g_engine->_room->calculatePath(fixPos((Vector2i)actor->_node->getPos()), p);
 		Common::Array<Vertex> vertices;
 		for (uint i = 0; i < path.size(); i++) {
-			vertices.push_back(Vertex(g_engine->roomToScreen(path[i]), yellow));
+			vertices.push_back(Vertex(g_engine->roomToScreen((Math::Vector2d)path[i]), yellow));
 		}
 		if (vertices.size() > 0) {
 			g_engine->getGfx().drawLines(vertices.data(), vertices.size());
 		}
+
+		// draw a green square if inside walkbox, red if not
+		Common::Array<Walkbox> walkboxes = g_engine->_room ? g_engine->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
+		if(walkboxes.empty())
+			return;
+
+		const bool inside = (walkboxes.size() > 0) && walkboxes[0].contains((Vector2i)roomPos);
+		pos = scrPos - Math::Vector2d(4.f, 4.f);
+		t = Math::Matrix4();
+		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), inside ? green : red, t);
+
+		// draw a blue square on the closest point
+		pos = g_engine->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge((Vector2i)roomPos));
+		t = Math::Matrix4();
+		t.translate(Math::Vector3d(pos.getX()-2.f, pos.getY()-2.f, 0.f));
+		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), blue, t);
 	}
 }
 
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index fccc4dd8d18..a022386f003 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -60,7 +60,7 @@ public:
 	PathMode getMode() const { return _mode; }
 
 private:
-	Math::Vector2d fixPos(Math::Vector2d pos);
+	Vector2i fixPos(Vector2i pos);
 	virtual void drawCore(Math::Matrix4 trsf) override;
 
 private:


Commit: 05a055b3e7d11c9816a567428fe30cc21aabcdc7
    https://github.com/scummvm/scummvm/commit/05a055b3e7d11c9816a567428fe30cc21aabcdc7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix twp not building after merge

Changed paths:
    engines/twp/detection.cpp
    engines/twp/twp.h


diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 9fe09c930e3..6b1f3a64b5c 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -43,7 +43,7 @@ TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(T
 
 ADDetectedGame TwpMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
 	for (auto it = allFiles.begin(); it != allFiles.end(); it++) {
-		if (it->_key.hasSuffix(".ggpack1")) {
+		if (it->_key.toString().hasSuffixIgnoreCase(".ggpack1")) {
 			return ADDetectedGame(Twp::gameDescriptions);
 		}
 	}
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 2d9ff32f01b..db17e6bdb6d 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -101,12 +101,12 @@ public:
 		return (f == kSupportsLoadingDuringRuntime) ||
 			   (f == kSupportsSavingDuringRuntime) ||
 			   (f == kSupportsReturnToLauncher);
-	};
+	}
 
-	bool canLoadGameStateCurrently() override {
+	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
 		return true;
 	}
-	bool canSaveGameStateCurrently() override {
+	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
 		return true;
 	}
 


Commit: 99170bf2f1f0752aace46ce6f7b69433674ce630
    https://github.com/scummvm/scummvm/commit/99170bf2f1f0752aace46ce6f7b69433674ce630
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add roomRotateTo

Changed paths:
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 758ab725b65..c39614f8021 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -87,6 +87,20 @@ void RotateTo::update(float elapsed) {
 		disable();
 }
 
+RoomRotateTo::~RoomRotateTo() {}
+
+RoomRotateTo::RoomRotateTo(Room *room, float to)
+	: _room(room),
+	  _tween(room->_rotation, to, 0.200f, intToInterpolationMethod(0)) {
+}
+
+void RoomRotateTo::update(float elapsed) {
+	_tween.update(elapsed);
+	_room->_rotation = _tween.current();
+	if (!_tween.running())
+		disable();
+}
+
 ScaleTo::~ScaleTo() {}
 
 ScaleTo::ScaleTo(float duration, Node *node, float to, InterpolationMethod im)
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index e979aff9100..8c4a77833ad 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -145,6 +145,19 @@ private:
 	Tween<float> _tween;
 };
 
+class RoomRotateTo : public Motor {
+public:
+	virtual ~RoomRotateTo();
+	RoomRotateTo(Room *room, float to);
+
+private:
+	virtual void update(float elasped) override;
+
+private:
+	Room *_room = nullptr;
+	Tween<float> _tween;
+};
+
 class ScaleTo : public Motor {
 public:
 	virtual ~ScaleTo();
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 975316a246c..7a3c5d0486f 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -460,8 +460,8 @@ Color Room::getOverlay() const {
 void Room::update(float elapsed) {
 	if (_overlayTo)
 		_overlayTo->update(elapsed);
-	// if (_rotateTo)
-	// 	_rotateTo->update(elapsedSec);
+	if (_rotateTo)
+		_rotateTo->update(elapsed);
 	for (size_t j = 0; j < _layers.size(); j++) {
 		Layer *layer = _layers[j];
 		for (size_t k = 0; k < layer->_objects.size(); k++) {
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 34f73ac7c58..f99710a83f6 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -150,6 +150,8 @@ public:
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
 	Motor* _overlayTo = nullptr;
+	Motor* _rotateTo = nullptr;
+	float _rotation = 0.f;
 	PathFinder _pathFinder;
 };
 
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index ba4561d9915..3dfefe5a12b 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -279,7 +279,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 		Object *obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
-        size_t i = find(g_engine->_room->_triggers, obj);
+		size_t i = find(g_engine->_room->_triggers, obj);
 		if (i != (size_t)-1) {
 			debug("Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
 			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
@@ -443,7 +443,10 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 }
 
 static SQInteger roomRotateTo(HSQUIRRELVM v) {
-	warning("TODO: roomRotateTo not implemented");
+	float rotation;
+	if (SQ_FAILED(sqget(v, 2, rotation)))
+		return sq_throwerror(v, "failed to get rotation");
+	g_engine->_room->_rotateTo = new RoomRotateTo(g_engine->_room, rotation);
 	return 0;
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index ff0fb76243d..33c03ab15bd 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -661,8 +661,14 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	// draw to screen
 	_gfx.use(nullptr);
 	_gfx.setRenderTarget(outTexture);
+	_gfx.clear(Color(0, 0, 0));
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
-	_gfx.drawSprite(*screenTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
+	Math::Matrix4 m1, m2, m;
+	m1.setPosition(Math::Vector3d(-SCREEN_WIDTH/2.f, -SCREEN_HEIGHT/2.f, 0.f));
+	m2.setPosition(Math::Vector3d(SCREEN_WIDTH/2.f, SCREEN_HEIGHT/2.f, 0.f));
+	Math::Angle angle;
+	m.buildAroundZ(Math::Angle(-_room->_rotation));
+	_gfx.drawSprite(*screenTexture, Color(), m2*m*m1, false, _fadeShader->_effect != FadeEffect::None);
 
 	// draw UI
 	_gfx.cameraPos(camPos);


Commit: 38e58778b6f56e939ff878a79daa0a0ed8cb963a
    https://github.com/scummvm/scummvm/commit/38e58778b6f56e939ff878a79daa0a0ed8cb963a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix talking timings

Changed paths:
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 238c695fe15..d4e56b7b59f 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -260,4 +260,23 @@ int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int
 	return id;
 }
 
+int AudioSystem::getElapsed(int id) const {
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].id == id) {
+			Audio::Timestamp t = g_engine->_mixer->getElapsedTime(_slots[i].handle);
+			return t.msecs();
+		}
+	}
+	return 0;
+}
+
+int AudioSystem::getDuration(int id) const {
+	for (int i = 0; i < 32; i++) {
+		if (_slots[i].id == id) {
+			return _slots[i].total;
+		}
+	}
+	return 0;
+}
+
 } // namespace Twp
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index f6203eccc9e..1a379ee37f4 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -103,6 +103,9 @@ public:
 	float getMasterVolume() const;
 	void setVolume(int id, float vol);
 
+	int getElapsed(int id) const;
+	int getDuration(int id) const;
+
 	void update(float elapsed);
 
 	Common::Array<SoundDefinition*> _soundDefs;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index c39614f8021..94dc65cd453 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -289,6 +289,11 @@ Talking::Talking(Object *obj, const Common::StringArray &texts, Color color) {
 	say(texts[0]);
 }
 
+void Talking::append(const Common::StringArray &texts) {
+	_texts.push_back(texts);
+	_enabled = !_texts.empty();
+}
+
 static int letterToIndex(char c) {
 	switch (c) {
 	case 'A':
@@ -315,25 +320,37 @@ static int letterToIndex(char c) {
 }
 
 void Talking::update(float elapsed) {
-	if (isEnabled()) {
-		_elapsed += elapsed;
-		if (_elapsed < _duration) {
-			char letter = _lip.letter(_elapsed);
-			_obj->setHeadIndex(letterToIndex(letter));
+	if (!isEnabled())
+		return;
+
+	_elapsed += elapsed;
+	if (_obj->_sound) {
+		if (!g_engine->_audio.playing(_obj->_sound)) {
+			debug("talking %s audio stopped", _obj->_key.c_str());
+			_obj->_sound = 0;
 		} else {
-			if (_texts.size() > 0) {
-				say(_texts[0]);
-				_texts.remove_at(0);
-			} else {
-				disable();
-			}
+			float e = g_engine->_audio.getElapsed(_obj->_sound) / 1000.f;
+			char letter = _lip.letter(e);
+			_obj->setHeadIndex(letterToIndex(letter));
 		}
+	} else if (_elapsed < _duration) {
+		char letter = _lip.letter(_elapsed);
+		_obj->setHeadIndex(letterToIndex(letter));
+	} else if (!_texts.empty()) {
+		debug("talking %s: %s", _obj->_key.c_str(), _texts[0].c_str());
+		say(_texts[0]);
+		_texts.remove_at(0);
+	} else {
+		debug("talking %s: ended", _obj->_key.c_str());
+		disable();
 	}
 }
 
 int Talking::loadActorSpeech(const Common::String &name) {
-	if (!ConfMan.getBool("talkiesHearVoice"))
+	if (!ConfMan.getBool("talkiesHearVoice")) {
+		debug("talking %s: talkiesHearVoice: false", _obj->_key.c_str());
 		return 0;
+	}
 
 	debug("loadActorSpeech %s.ogg", name.c_str());
 	Common::String filename(name);
@@ -345,7 +362,12 @@ int Talking::loadActorSpeech(const Common::String &name) {
 			debug("File %s.ogg not found", name.c_str());
 		} else {
 			g_engine->_audio._soundDefs.push_back(soundDefinition);
-			return g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
+			int id = g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
+			int duration = g_engine->_audio.getDuration(id);
+			debug("talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
+			if (duration)
+				_duration = duration / 1000.f;
+			return id;
 		}
 	}
 	return 0;
@@ -406,7 +428,8 @@ void Talking::say(const Common::String &text) {
 		}
 	}
 
-	setDuration(txt);
+	if (!_obj->_sound)
+		setDuration(txt);
 
 	if (_obj->_sayNode) {
 		_obj->_sayNode->remove();
@@ -425,6 +448,7 @@ void Talking::say(const Common::String &text) {
 	_obj->_sayNode->setPos(pos);
 	_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
 	g_engine->_screenScene.addChild(_obj->_sayNode);
+	_elapsed = 0.f;
 }
 
 void Talking::disable() {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 8c4a77833ad..345edd4945c 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -87,7 +87,7 @@ public:
 	virtual bool isEnabled() const { return _enabled; }
 	virtual void update(float elapsed) = 0;
 
-private:
+protected:
 	bool _enabled = true;
 };
 
@@ -240,13 +240,16 @@ public:
 	Talking(Object *obj, const Common::StringArray &texts, Color color);
 	virtual ~Talking() {}
 
-private:
+	void append(const Common::StringArray &texts);
+
 	virtual void update(float elapsed) override;
 	virtual void disable() override;
+
+private:
+	void say(const Common::String &text);
 	int onTalkieId(int id);
 	Common::String talkieKey();
 	void setDuration(const Common::String &text);
-	void say(const Common::String &text);
 	int loadActorSpeech(const Common::String &name);
 
 private:
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 773b6dabd76..a7313b44b2f 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -343,7 +343,7 @@ Common::String Object::getIcon() {
 	if (iconTable._type == OT_ARRAY) {
 		int fps;
 		Common::StringArray icons;
-		sqgetitems(iconTable, GetIcons(fps,icons));
+		sqgetitems(iconTable, GetIcons(fps, icons));
 		setIcon(fps, icons);
 		return getIcon();
 	}
@@ -775,7 +775,12 @@ void Object::inventoryScrollDown() {
 }
 
 void TalkingState::say(const Common::StringArray &texts, Object *obj) {
-	obj->setTalking(new Talking(obj, texts, _color));
+	Talking *talking = static_cast<Talking *>(obj->getTalking());
+	if (!talking) {
+		obj->setTalking(new Talking(obj, texts, _color));
+	} else {
+		talking->append(texts);
+	}
 }
 
 } // namespace Twp


Commit: 087679d6f92eb6f190dc36c850aa56ef2118351f
    https://github.com/scummvm/scummvm/commit/087679d6f92eb6f190dc36c850aa56ef2118351f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix cursor text in closeup room

Changed paths:
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/twp.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index a7313b44b2f..c6ca09050ef 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -112,7 +112,7 @@ int Object::getId() const {
 	return (int)result;
 }
 
-Common::String Object::getname() const {
+Common::String Object::getName() const {
 	if ((_table._type == OT_TABLE) && (sqrawexists(_table, "name"))) {
 		Common::String result;
 		sqgetf(_table, "name", result);
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 28767ec043d..ff5241c01b9 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -106,7 +106,7 @@ public:
 
 	static Object *createActor();
 
-	Common::String getname() const;
+	Common::String getName() const;
 	int getId() const;
 
 	// Changes the `state` of an object, although this can just be a internal state,
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 33c03ab15bd..1ef599eff38 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -262,7 +262,7 @@ Common::String TwpEngine::cursorText() {
 		}
 
 		// give can be used only on inventory and talkto to talkable objects (actors)
-		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->getname());
+		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->getName());
 
 		// add verb if not walk to or if noun1 is present
 		if ((_hud._verb.id.id > 1) || (result.size() > 0)) {
@@ -278,7 +278,7 @@ Common::String TwpEngine::cursorText() {
 			else if (_useFlag == ufGiveTo)
 				result += " " + _textDb.getText(10003);
 			if (_noun2)
-				result += " " + _textDb.getText(_noun2->getname());
+				result += " " + _textDb.getText(_noun2->getName());
 		}
 	}
 	return result;
@@ -468,7 +468,7 @@ void TwpEngine::update(float elapsed) {
 			_hud.setVisible(false);
 			_uiInv.setVisible(false);
 			_noun1 = objAt(roomPos);
-			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->_name);
+			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->getName());
 			_sentence.setText(cText);
 			_inputState.setCursorShape(CursorShape::Normal);
 			if (_cursor.leftDown)


Commit: 1783a2f731668bf964fb68c99290d449b1f29bac
    https://github.com/scummvm/scummvm/commit/1783a2f731668bf964fb68c99290d449b1f29bac
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix trigger not working

Changed paths:
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/soundlib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 3dfefe5a12b..97050b789af 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -43,6 +43,7 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, obj->_leave)))
 			return sq_throwerror(v, "failed to get leave");
 	sq_addref(g_engine->getVm(), &obj->_leave);
+	obj->_triggerActive = true;
 	g_engine->_room->_triggers.push_back(obj);
 	return 0;
 }
@@ -113,9 +114,9 @@ static SQInteger enableTrigger(HSQUIRRELVM v) {
 	bool enabled;
 	if (SQ_FAILED(sqget(v, 3, enabled)))
 		return sq_throwerror(v, "failed to get enabled");
-	if (enabled)
+	if (enabled) {
 		g_engine->_room->_triggers.push_back(obj);
-	else {
+	} else {
 		int index = find(g_engine->_room->_triggers, obj);
 		if (index != -1)
 			g_engine->_room->_triggers.remove_at(index);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 8736044634c..24232292b03 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -410,6 +410,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	loadObjects(json["objects"]->asObject());
 	setActor(json["selectedActor"]->asString());
 	g_engine->setRoom(room(json["currentRoom"]->asString()));
+	g_engine->cameraAt(g_engine->_actor->_node->getPos());
 
 	HSQUIRRELVM v = g_engine->getVm();
 	sqsetf(sqrootTbl(v), "SAVEBUILD", json["savebuild"]->asIntegerNumber());
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index b140c388355..f779b5dcb1e 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -37,7 +37,7 @@ public:
 
 	virtual void trig() override {
 		int i = g_engine->getRandomSource().getRandomNumber(_sounds.size() - 1);
-		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 0.f, _objId);
+		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 1.f, _objId);
 	}
 
 private:
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1ef599eff38..51507a808c1 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1534,11 +1534,11 @@ void TwpEngine::updateTriggers() {
 		for (size_t i = 0; i < _room->_triggers.size(); i++) {
 			Object *trigger = _room->_triggers[i];
 			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
-				debug("call enter trigger %s", trigger->_name.c_str());
+				debug("call enter trigger %s", trigger->_key.c_str());
 				trigger->_triggerActive = true;
 				callTrigger(trigger, trigger->_enter);
 			} else if (trigger->_triggerActive && !trigger->contains(_actor->_node->getAbsPos())) {
-				debug("call leave trigger %s", trigger->_name.c_str());
+				debug("call leave trigger %s", trigger->_key.c_str());
 				trigger->_triggerActive = false;
 				callTrigger(trigger, trigger->_leave);
 			}


Commit: 9c50ad6348e567a52f3e6394da9210b8f2a6888a
    https://github.com/scummvm/scummvm/commit/9c50ad6348e567a52f3e6394da9210b8f2a6888a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Click only if button state changed

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 51507a808c1..cc96b6b5136 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -217,7 +217,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 			return;
 		}
 
-		if (_cursor.leftDown) {
+		if (_cursor.isLeftDown()) {
 			// button left: execute selected verb
 			bool handled = clickedAtHandled(roomPos);
 			if (!handled && obj) {
@@ -236,7 +236,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 				}
 			}
 
-		} else if (_cursor.rightDown) {
+		} else if (_cursor.isRightDown()) {
 			// button right: execute default verb
 			if (obj) {
 				VerbId verb;


Commit: 9d5de99e36b23b2c971a3099213f933dd7a579a2
    https://github.com/scummvm/scummvm/commit/9d5de99e36b23b2c971a3099213f933dd7a579a2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Update exCommand

Changed paths:
    engines/twp/audio.h
    engines/twp/syslib.cpp


diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index 1a379ee37f4..27b35114005 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -109,6 +109,7 @@ public:
 	void update(float elapsed);
 
 	Common::Array<SoundDefinition*> _soundDefs;
+	SoundDefinition* _soundHover = nullptr;	// not used yet, should be used in the GUI
 
 private:
 	void updateVolume(AudioSlot* slot);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index abc33bfd776..a5dcf2721f0 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -525,23 +525,38 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 	} break;
 	case EX_POP_CHARACTER_SELECTION:
 		// seems not to be used
-		warning("TODO: exCommand EX_POP_CHARACTER_SELECTION: not implemented");
+		warning("exCommand EX_POP_CHARACTER_SELECTION: not implemented");
 		break;
 	case EX_CAMERA_TRACKING:
 		warning("TODO: exCommand EX_CAMERA_TRACKING: not implemented");
 		break;
+	case EX_BUTTON_HOVER_SOUND: {
+		SoundDefinition* sound = sqsounddef(v, 3);
+		if (!sound)
+			return sq_throwerror(v, "failed to get sound for EX_BUTTON_HOVER_SOUND");
+		g_engine->_audio._soundHover = sound;
+	} break;
 	case EX_RESTART:
 		warning("TODO: exCommand EX_RESTART: not implemented");
 		break;
 	case EX_IDLE_TIME:
+		// used in demo only in watchForIdle
 		warning("TODO: exCommand EX_IDLE_TIME: not implemented");
 		break;
 	case EX_DISABLE_SAVESYSTEM:
-		warning("TODO: exCommand EX_DISABLE_SAVESYSTEM: not implemented");
+		// seems not to be used
+		warning("exCommand EX_DISABLE_SAVESYSTEM: not implemented");
+		break;
+	case EX_SHOW_OPTIONS:
+    	g_engine->openMainMenuDialog();
 		break;
 	case EX_OPTIONS_MUSIC:
 		warning("TODO: exCommand EX_OPTIONS_MUSIC: not implemented");
 		break;
+	case EX_FORCE_TALKIE_TEXT:
+		// seems not to be used
+		warning("exCommand EX_FORCE_TALKIE_TEXT: not implemented");
+		break;
 	default:
 		warning("exCommand(%dd) not implemented", cmd);
 		break;


Commit: bc9c277cb6bc87ce9c5297f04c49822dfa2087a0
    https://github.com/scummvm/scummvm/commit/bc9c277cb6bc87ce9c5297f04c49822dfa2087a0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add jiggleInventory

Changed paths:
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/object.h b/engines/twp/object.h
index ff5241c01b9..261a584ac33 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -265,6 +265,7 @@ public:
 	int _popCount = 0;
 	Sentence _exec;
 	int _sound = 0;
+	bool _jiggle = false;
 
 private:
 	Motor *_alphaTo = nullptr;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index edffdbfaf69..d35cd8c8ea3 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -210,9 +210,16 @@ static SQInteger isObject(HSQUIRRELVM v) {
 }
 
 static SQInteger jiggleInventory(HSQUIRRELVM v) {
-	// TODO: jiggleInventory
-	warning("jiggleInventory not implemented");
-	return 0;
+	Object *obj = sqobj(v, 2);
+    if (!obj) {
+      return sq_throwerror(v, "failed to get object");
+    }
+    SQInteger enabled = 0;
+    if (SQ_FAILED(sq_getinteger(v, 3, &enabled))) {
+      return sq_throwerror(v, "failed to get enabled");
+    }
+    obj->_jiggle = enabled != 0;
+    return 0;
 }
 
 // Rotate the object around its origin back and forth by the specified amount of pixels.
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 423f5db5889..74b39dad84d 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -582,6 +582,11 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 			Math::Vector2d pos(startOffsetX + ((float)(i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET)), startOffsetY - ((float)(i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET)));
 			Math::Matrix4 t(trsf);
 			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
+			if (obj->_jiggle) {
+				Math::Matrix3 rot;
+				rot.buildAroundZ(18.f * sin(_jiggleTime));
+				t.setRotation(rot);
+			}
 			float s = obj->getScale();
 			Twp::scale(t, Math::Vector2d(s, s));
 			drawSprite(*itemFrame, texture, Color(), t);
@@ -598,6 +603,8 @@ void Inventory::drawCore(Math::Matrix4 trsf) {
 }
 
 void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
+	_jiggleTime += 10.f * elapsed;
+
 	// udate colors
 	_actor = actor;
 	_backColor = backColor;
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index e3041b20604..a9df0506e6e 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -312,6 +312,7 @@ private:
 	Common::Rect _itemRects[NUMOBJECTS];
 	Common::Rect _arrowUpRect;
 	Common::Rect _arrowDnRect;
+	float _jiggleTime = 0.f;
 };
 
 class SentenceNode: public Node {


Commit: 8f946e4a0dc5497ee8253abf21b3d5bc49cd0127
    https://github.com/scummvm/scummvm/commit/8f946e4a0dc5497ee8253abf21b3d5bc49cd0127
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Call sayingLine if any

Changed paths:
    engines/twp/motor.cpp


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 94dc65cd453..8ff7c852656 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -396,10 +396,10 @@ void Talking::say(const Common::String &text) {
 			debug("Lip %s loaded", path.c_str());
 		}
 
-		// TODO: call sayingLine
 		if (_obj->_sound) {
 			g_engine->_audio.stop(_obj->_sound);
 		}
+
 		_obj->_sound = loadActorSpeech(name);
 	} else if (text[0] == '^') {
 		txt = text.substr(1);
@@ -414,6 +414,11 @@ void Talking::say(const Common::String &text) {
 
 	debug("sayLine '%s'", txt.c_str());
 
+	if (sqrawexists(_obj->_table, "sayingLine")) {
+		const char *anim = _obj->_animName.empty() ? nullptr : _obj->_animName.c_str();
+		sqcall(_obj->_table, "sayingLine", anim, txt);
+	}
+
 	// modify state ?
 	Common::String state;
 	if (txt[0] == '{') {


Commit: c8acfe20837036c47829aaf3fae5394c787e31c8
    https://github.com/scummvm/scummvm/commit/c8acfe20837036c47829aaf3fae5394c787e31c8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Load and save jiggle

Changed paths:
    engines/twp/savegame.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 24232292b03..7607ec12143 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -546,6 +546,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 		for (int i = 0; i < NUMACTORS; i++) {
 			Object *actor = g_engine->_hud._actorSlots[i].actor;
 			if (actor) {
+				int jiggleCount = 0;
 				actor->_inventory.clear();
 				const Common::JSONObject &jSlot = jSlots[i]->asObject();
 				if (jSlot.contains("objects")) {
@@ -556,11 +557,12 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 							Object *obj = object(jObj->asString());
 							if (!obj)
 								warning("inventory obj '%s' not found", jObj->asString().c_str());
-							else
+							else {
 								actor->pickupObject(obj);
+								obj->_jiggle = jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
+							}
 						}
 					}
-					// TODO: "jiggle"
 				}
 				actor->_inventoryOffset = jSlot["scroll"]->asIntegerNumber();
 			}
@@ -851,15 +853,11 @@ static Common::JSONValue *createJInventory(const ActorSlot &slot) {
 		Common::JSONArray objKeys;
 		Common::JSONArray jiggleArray;
 		bool anyJiggle = false;
-		//  for (obj in slot.actor.inventory) {
 		for (size_t i = 0; i < slot.actor->_inventory.size(); i++) {
 			Object *obj = slot.actor->_inventory[i];
-			// TODO: jiggle
-			// let jiggle = obj.getJiggle()
-			bool jiggle = false;
-			// if (jiggle)
-			// 	anyJiggle = true;
-			jiggleArray.push_back(createJBool(jiggle));
+			if (obj->_jiggle)
+				anyJiggle = true;
+			jiggleArray.push_back(createJBool(obj->_jiggle));
 			objKeys.push_back(new Common::JSONValue(obj->_key));
 		}
 


Commit: 243776e27c9bf66b29d27a0f48a42f2569f27040
    https://github.com/scummvm/scummvm/commit/243776e27c9bf66b29d27a0f48a42f2569f27040
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix jiggle sometimes not present

Changed paths:
    engines/twp/savegame.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 7607ec12143..fc5c051082f 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -559,7 +559,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 								warning("inventory obj '%s' not found", jObj->asString().c_str());
 							else {
 								actor->pickupObject(obj);
-								obj->_jiggle = jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
+								obj->_jiggle = jSlot.contains("jiggle") && jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
 							}
 						}
 					}


Commit: a912e659e39be6b4ca4589887534e2ecf23ee6e3
    https://github.com/scummvm/scummvm/commit/a912e659e39be6b4ca4589887534e2ecf23ee6e3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add HUD fade animation

Changed paths:
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/twp.cpp


diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 28b10cc48ec..02446ef2670 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -154,7 +154,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	const SpriteSheetFrame &backingFrame = gameSheet->frameTable[classic ? "ui_backing_tall" : "ui_backing"];
 	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
-	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha), trsf);
+	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha*getAlpha()), trsf);
 
 	bool verbHlt = ConfMan.getBool("invertVerbHighlight");
 	Color verbHighlight = verbHlt ? Color() : slot->verbUiColors.verbHighlight;
@@ -185,17 +185,45 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 			if (_mouseClick && over) {
 				_verb = verb;
 			}
-			drawSprite(verbFrame, verbTexture, color, trsf);
+			drawSprite(verbFrame, verbTexture, Color::withAlpha(color, getAlpha()), trsf);
 		}
 	}
 	g_engine->getGfx().use(saveShader);
 	_over = isOver;
 }
 
-void Hud::update(Math::Vector2d pos, Object *hotspot, bool mouseClick) {
+void Hud::update(float elapsed, Math::Vector2d pos, Object *hotspot, bool mouseClick) {
 	_mousePos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
 	_defaultVerbId = !hotspot ? 0 : hotspot->defaultVerbId();
 	_mouseClick = mouseClick;
+
+	_fadeTime += elapsed;
+
+	if (_fadeTime > 2.f) {
+		_fadeTime = 2.f;
+		if (!_fadeIn) {
+			setVisible(false);
+		}
+	}
+
+	if (_fadeIn) {
+		float alpha = MIN(_fadeTime, 2.0f) / 2.0f;
+		setAlpha(alpha);
+	} else {
+		float alpha = MAX(2.0f - _fadeTime, 0.0f) / 2.0f;
+		setAlpha(alpha);
+	}
 }
 
+void Hud::setVisible(bool visible) {
+	if (_fadeIn != visible) {
+		_fadeIn = visible;
+		if (visible) {
+			Node::setVisible(visible);
+		}
+		_fadeTime = 0;
+	}
+}
+
+
 } // namespace Twp
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index c8b6b6fee50..7b6ddbf54ef 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -116,7 +116,9 @@ public:
 	void init();
 	ActorSlot *actorSlot(Object *actor);
 	bool isOver() const { return _over; }
-	void update(Math::Vector2d pos, Object *hotspot, bool mouseClick);
+	void update(float elapsed, Math::Vector2d pos, Object *hotspot, bool mouseClick);
+
+	void setVisible(bool visible) override;
 
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
@@ -132,6 +134,8 @@ public:
 	bool _mouseClick = false;
 	bool _over = false;
 	int _defaultVerbId = 0;
+	float _fadeTime = 0.f;
+	bool _fadeIn = false;
 };
 } // namespace Twp
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 74b39dad84d..56d80fc29eb 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -275,7 +275,8 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	_frameDuration = 1.0 / _getFps(fps, anim->fps);
 	_loop = loop || anim->loop;
 	_instant = instant;
-	if(_obj) setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
+	if (_obj)
+		setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
 
 	clear();
 	for (size_t i = 0; i < _anim->layers.size(); i++) {
@@ -537,8 +538,8 @@ void Inventory::drawArrows(Math::Matrix4 trsf) {
 	Math::Matrix4 tDn(trsf);
 	tDn.translate(Math::Vector3d(SCREEN_WIDTH / 2.f + ARROWWIDTH / 2.f + MARGIN, 0.5f * ARROWHEIGHT, 0.f));
 
-	drawSprite(*arrowUp, texture, Color::withAlpha(_verbNormal, alphaUp), tUp);
-	drawSprite(*arrowDn, texture, Color::withAlpha(_verbNormal, alphaDn), tDn);
+	drawSprite(*arrowUp, texture, Color::withAlpha(_verbNormal, alphaUp * getAlpha()), tUp);
+	drawSprite(*arrowDn, texture, Color::withAlpha(_verbNormal, alphaDn * getAlpha()), tDn);
 }
 
 void Inventory::drawBack(Math::Matrix4 trsf) {
@@ -553,7 +554,7 @@ void Inventory::drawBack(Math::Matrix4 trsf) {
 	for (int i = 0; i < 4; i++) {
 		Math::Matrix4 t(trsf);
 		t.translate(Math::Vector3d(offsetX, offsetY, 0.f));
-		drawSprite(*back, texture, _backColor, t);
+		drawSprite(*back, texture, Color::withAlpha(_backColor, getAlpha()), t);
 		offsetX += back->sourceSize.getX() + BACKOFFSET;
 	}
 
@@ -562,7 +563,7 @@ void Inventory::drawBack(Math::Matrix4 trsf) {
 	for (int i = 0; i < 4; i++) {
 		Math::Matrix4 t(trsf);
 		t.translate(Math::Vector3d(offsetX, offsetY, 0.f));
-		drawSprite(*back, texture, _backColor, t);
+		drawSprite(*back, texture, Color::withAlpha(_backColor, getAlpha()), t);
 		offsetX += back->sourceSize.getX() + BACKOFFSET;
 	}
 }
@@ -589,7 +590,7 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 			}
 			float s = obj->getScale();
 			Twp::scale(t, Math::Vector2d(s, s));
-			drawSprite(*itemFrame, texture, Color(), t);
+			drawSprite(*itemFrame, texture, Color::withAlpha(Color(), getAlpha()), t);
 		}
 	}
 }
@@ -604,6 +605,22 @@ void Inventory::drawCore(Math::Matrix4 trsf) {
 
 void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
 	_jiggleTime += 10.f * elapsed;
+	_fadeTime += elapsed;
+
+	if (_fadeTime > 2.f) {
+		_fadeTime = 2.f;
+		if (!_fadeIn) {
+			setVisible(false);
+		}
+	}
+
+	if (_fadeIn) {
+		float alpha = MIN(_fadeTime, 2.0f) / 2.0f;
+		setAlpha(alpha);
+	} else {
+		float alpha = MAX(2.0f - _fadeTime, 0.0f) / 2.0f;
+		setAlpha(alpha);
+	}
 
 	// udate colors
 	_actor = actor;
@@ -630,7 +647,7 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 		for (int i = 0; i < NUMOBJECTS; i++) {
 			const Common::Rect &item = _itemRects[i];
 			if (item.contains(scrPos.getX(), scrPos.getY())) {
-                size_t index = _actor->_inventoryOffset * NUMOBJECTSBYROW + i;
+				size_t index = _actor->_inventoryOffset * NUMOBJECTSBYROW + i;
 				if (index < _actor->_inventory.size())
 					_obj = _actor->_inventory[index];
 				break;
@@ -644,6 +661,16 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 	}
 }
 
+void Inventory::setVisible(bool visible) {
+	if (_fadeIn != visible) {
+		_fadeIn = visible;
+		if (visible) {
+			Node::setVisible(visible);
+		}
+		_fadeTime = 0;
+	}
+}
+
 SentenceNode::SentenceNode() : Node("Sentence") {
 	_zOrder = -100;
 }
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index a9df0506e6e..be579aeb2b8 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -45,7 +45,7 @@ public:
 	void setName(const Common::String &name) { _name = name; }
 	const Common::String &getName() const { return _name; }
 
-	void setVisible(bool visible) { _visible = visible; }
+	virtual void setVisible(bool visible) { _visible = visible; }
 	bool isVisible() const { return _visible; }
 
 	// Adds new child in current node.
@@ -297,6 +297,8 @@ public:
 	Object* getObject() const { return _obj; }
 	Math::Vector2d getPos(Object* inv) const;
 
+	void setVisible(bool visible) override;
+
 private:
 	virtual void drawCore(Math::Matrix4 trsf) override final;
 	void drawArrows(Math::Matrix4 trsf);
@@ -313,6 +315,8 @@ private:
 	Common::Rect _arrowUpRect;
 	Common::Rect _arrowDnRect;
 	float _jiggleTime = 0.f;
+	float _fadeTime = 0.f;
+	bool _fadeIn = false;
 };
 
 class SentenceNode: public Node {
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cc96b6b5136..1a50e1b3d46 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -438,9 +438,10 @@ void TwpEngine::update(float elapsed) {
 			}
 
 			_inputState.setHotspot(_noun1 != nullptr);
-			_hud.setVisible(_inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None);
+			bool hudVisible = _inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None  && !_cutscene;
+			_hud.setVisible(hudVisible);
 			_sentence.setVisible(_hud.isVisible());
-			_uiInv.setVisible(_hud.isVisible() && !_cutscene);
+			_uiInv.setVisible(hudVisible);
 			_actorSwitcher.setVisible((_dialog.getState() == DialogState::None) && !_cutscene);
 			// Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
 			//_sentence.setText(cursortxt.c_str());
@@ -545,7 +546,7 @@ void TwpEngine::update(float elapsed) {
 	if (!_actor) {
 		_uiInv.update(elapsed);
 	} else {
-		_hud.update(scrPos, _noun1, _cursor.isLeftDown());
+		_hud.update(elapsed, scrPos, _noun1, _cursor.isLeftDown());
 		VerbUiColors *verbUI = &_hud.actorSlot(_actor)->verbUiColors;
 		_uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
 	}


Commit: afcea8524699c54a8d2c2455a564bde47c87e607
    https://github.com/scummvm/scummvm/commit/afcea8524699c54a8d2c2455a564bde47c87e607
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add debug channels

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/dialog.cpp
    engines/twp/enginedialogtarget.cpp
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/ggpack.cpp
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/task.h
    engines/twp/thread.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/vm.cpp
    engines/twp/yack.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index a62d13686ba..487eb012c8b 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -38,7 +38,7 @@ static SQInteger actorAlpha(HSQUIRRELVM v) {
 	float alpha;
 	if (SQ_FAILED(sqget(v, 3, alpha)))
 		return sq_throwerror(v, "failed to get alpha");
-	debug("actorAlpha(%s, %f)", actor->_key.c_str(), alpha);
+	debugC(kDebugActScript, "actorAlpha(%s, %f)", actor->_key.c_str(), alpha);
 	actor->_node->setAlpha(alpha);
 	return 0;
 }
@@ -89,14 +89,14 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 			Math::Vector2d pos = spot->_node->getPos() + spot->_usePos;
 			actor->setRoom(spot->_room);
 			actor->stopWalking();
-			debug("actorAt %s at %s (%d, %d), room '%s'", actor->_key.c_str(), spot->_key.c_str(), (int)pos.getX(), (int)pos.getY(), spot->_room->_name.c_str());
+			debugC(kDebugActScript, "actorAt %s at %s (%d, %d), room '%s'", actor->_key.c_str(), spot->_key.c_str(), (int)pos.getX(), (int)pos.getY(), spot->_room->_name.c_str());
 			actor->_node->setPos(pos);
 			actor->setFacing(getFacing(spot->_useDir, actor->getFacing()));
 		} else {
 			Room *room = sqroom(v, 3);
 			if (!room)
 				return sq_throwerror(v, "failed to get spot or room");
-			debug("actorAt %s room '%s'", actor->_key.c_str(), room->_name.c_str());
+			debugC(kDebugActScript, "actorAt %s room '%s'", actor->_key.c_str(), room->_name.c_str());
 			actor->stopWalking();
 			actor->setRoom(room);
 		}
@@ -111,7 +111,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
-		debug("actorAt %s room %d, %d", actor->_key.c_str(), x, y);
+		debugC(kDebugActScript, "actorAt %s room %d, %d", actor->_key.c_str(), x, y);
 		actor->stopWalking();
 		actor->_node->setPos(Math::Vector2d(x, y));
 		return 0;
@@ -132,7 +132,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 		int dir = 0;
 		if ((numArgs == 6) && SQ_FAILED(sqget(v, 6, dir)))
 			return sq_throwerror(v, "failed to get direction");
-		debug("actorAt %s, pos = (%d,%d), dir = %d", actor->_key.c_str(), x, y, dir);
+		debugC(kDebugActScript, "actorAt %s, pos = (%d,%d), dir = %d", actor->_key.c_str(), x, y, dir);
 		actor->stopWalking();
 		actor->_node->setPos(Math::Vector2d(x, y));
 		actor->setFacing(getFacing(dir, actor->getFacing()));
@@ -188,7 +188,7 @@ static SQInteger actorCostume(HSQUIRRELVM v) {
 	Common::String sheet;
 	if (sq_gettop(v) == 4)
 		sqget(v, 4, sheet);
-	debug("Actor costume %s %s", name.c_str(), sheet.c_str());
+	debugC(kDebugActScript, "Actor costume %s %s", name.c_str(), sheet.c_str());
 	actor->setCostume(name, sheet);
 	return 0;
 }
@@ -418,7 +418,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get actor");
 			Common::String key;
 			sqgetf(actor->_table, "_key", key);
-			debug("actorSlotSelectable(%s, %s)", key.c_str(), selectable ? "yes" : "no");
+			debugC(kDebugActScript, "actorSlotSelectable(%s, %s)", key.c_str(), selectable ? "yes" : "no");
 			ActorSlot *slot = g_engine->_hud.actorSlot(actor);
 			if (!slot)
 				warning("slot for actor %s not found", key.c_str());
@@ -498,7 +498,7 @@ static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
 	int loop = 0;
 	if ((sq_gettop(v) >= 4) && (SQ_FAILED(sqget(v, 4, loop))))
 		return sq_throwerror(v, "failed to get loop");
-	debug("Play anim %s %s loop=%s", actor->_key.c_str(), animation.c_str(), loop ? "yes" : "no");
+	debugC(kDebugActScript, "Play anim %s %s loop=%s", actor->_key.c_str(), animation.c_str(), loop ? "yes" : "no");
 	actor->play(animation, loop != 0);
 	return 0;
 }
@@ -794,7 +794,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	sqgetf(actor->_table, "_key", key);
 	actor->_key = key;
 
-	debug("Create actor %s %d", key.c_str(), actor->getId());
+	debugC(kDebugActScript, "Create actor %s %d", key.c_str(), actor->getId());
 	actor->_node = new ActorNode(actor);
 	actor->_nodeAnim = new Anim(actor);
 	actor->_node->addChild(actor->_nodeAnim);
@@ -850,7 +850,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 			}
 		}
 	}
-	debug("sayline: %s, %s", obj->_key.c_str(), join(texts, "|").c_str());
+	debugC(kDebugActScript, "sayline: %s, %s", obj->_key.c_str(), join(texts, "|").c_str());
 	obj->say(texts, obj->_talkColor);
 	return 0;
 }
diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 6b1f3a64b5c..f69d761414f 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -30,11 +30,17 @@
 #include "twp/detection_tables.h"
 
 const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
-	{Twp::kDebugGraphics, "Graphics", "Graphics debug level"},
-	{Twp::kDebugPath, "Path", "Pathfinding debug level"},
-	{Twp::kDebugFilePath, "FilePath", "File path debug level"},
-	{Twp::kDebugScan, "Scan", "Scan for unrecognised games"},
-	{Twp::kDebugScript, "Script", "Enable debug script dump"},
+	{Twp::kDebugText, "Text", "Text debug level"},
+	{Twp::kDebugGGPack, "GGPack", "GGPack debug level"},
+	{Twp::kDebugRes, "Res", "Resource debug level"},
+	{Twp::kDebugDialog, "Dialog", "Dialog debug level"},
+	{Twp::kDebugGenScript, "General", "Enable debug general script dump"},
+	{Twp::kDebugObjScript, "Object", "Enable debug object script dump"},
+	{Twp::kDebugSysScript, "System", "Enable debug system script dump"},
+	{Twp::kDebugRoomScript, "Room", "Enable debug room script dump"},
+	{Twp::kDebugActScript, "Actor", "Enable debug actor script dump"},
+	{Twp::kDebugSndScript, "Sound", "Enable debug sound script dump"},
+	{Twp::kDebugGame, "Game", "Game debug level"},
 	DEBUG_CHANNEL_END};
 
 TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(Twp::gameDescriptions,
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index af490ef77e2..4fb6d06d884 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -27,11 +27,17 @@
 namespace Twp {
 
 enum TwpDebugChannels {
-	kDebugGraphics = 1 << 0,
-	kDebugPath     = 1 << 1,
-	kDebugScan     = 1 << 2,
-	kDebugFilePath = 1 << 3,
-	kDebugScript   = 1 << 4,
+	kDebugText	     	= 1 << 0,
+	kDebugGGPack     	= 1 << 1,
+	kDebugRes     		= 1 << 2,
+	kDebugDialog 		= 1 << 3,
+	kDebugGenScript 	= 1 << 4,
+	kDebugObjScript 	= 1 << 5,
+	kDebugSysScript 	= 1 << 6,
+	kDebugRoomScript	= 1 << 7,
+	kDebugActScript		= 1 << 8,
+	kDebugSndScript		= 1 << 9,
+	kDebugGame			= 1 << 10,
 };
 
 extern const PlainGameDescriptor twpGames[];
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index db37bae0017..7d29626cb42 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -36,11 +36,11 @@ public:
 		if (_motors.size() > 0) {
 			_motors[0]->update(elapsed);
 			if (!_motors[0]->isEnabled()) {
-				debug("SerialMotors next");
+				debugC(kDebugDialog, "SerialMotors next");
 				_motors.remove_at(0);
 			}
 		} else {
-			debug("SerialMotors is over");
+			debugC(kDebugDialog, "SerialMotors is over");
 			disable();
 		}
 	}
@@ -103,27 +103,27 @@ ExpVisitor::ExpVisitor(Dialog *dialog) : _dialog(dialog) {}
 ExpVisitor::~ExpVisitor() {}
 
 void ExpVisitor::visit(const YCodeExp &node) {
-	debug("execute code %s", node._code.c_str());
+	debugC(kDebugDialog, "execute code %s", node._code.c_str());
 	sqexec(g_engine->getVm(), node._code.c_str(), "dialog");
 }
 
 void ExpVisitor::visit(const YGoto &node) {
-	debug("execute goto %s", node._name.c_str());
+	debugC(kDebugDialog, "execute goto %s", node._name.c_str());
 	_dialog->selectLabel(node._line, node._name);
 }
 
 void ExpVisitor::visit(const YShutup &node) {
-	debug("shutup");
+	debugC(kDebugDialog, "shutup");
 	_dialog->_tgt->shutup();
 }
 
 void ExpVisitor::visit(const YPause &node) {
-	debug("pause %d", node._time);
+	debugC(kDebugDialog, "pause %d", node._time);
 	_dialog->_action = _dialog->_tgt->pause(node._time);
 }
 
 void ExpVisitor::visit(const YWaitFor &node) {
-	debug("TODO: waitFor {%s}", node._actor.c_str());
+	debugC(kDebugDialog, "TODO: waitFor {%s}", node._actor.c_str());
 }
 
 void ExpVisitor::visit(const YParrot &node) {
@@ -143,12 +143,12 @@ void ExpVisitor::visit(const YAllowObjects &node) {
 }
 
 void ExpVisitor::visit(const YWaitWhile &node) {
-	debug("wait while");
+	debugC(kDebugDialog, "wait while");
 	_dialog->_action = _dialog->_tgt->waitWhile(node._cond);
 }
 
 void ExpVisitor::visit(const YLimit &node) {
-	debug("limit");
+	debugC(kDebugDialog, "limit");
 	_dialog->_context.limit = node._max;
 }
 
@@ -219,7 +219,7 @@ void Dialog::start(const Common::String &actor, const Common::String &name, cons
 	_context = DialogContext{.actor = actor, .dialogName = name, .parrot = true, .limit = MAXCHOICES};
 	// keepIf(self.states, proc(x: DialogConditionState): bool = x.mode != TempOnce);
 	Common::String path = name + ".byack";
-	debug("start dialog %s", path.c_str());
+	debugC(kDebugDialog, "start dialog %s", path.c_str());
 	GGPackEntryReader reader;
 	reader.open(g_engine->_pack, path);
 	YackParser parser;
@@ -271,11 +271,11 @@ bool Dialog::isOnce(int line) const {
 	for (size_t i = 0; i < _states.size(); i++) {
 		const DialogConditionState &state = _states[i];
 		if (state.mode == Once && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
-			debug("isOnce %d: false", line);
+			debugC(kDebugDialog, "isOnce %d: false", line);
 			return false;
 		}
 	}
-	debug("isOnce %d: true", line);
+	debugC(kDebugDialog, "isOnce %d: true", line);
 	return true;
 }
 
@@ -283,11 +283,11 @@ bool Dialog::isShowOnce(int line) const {
 	for (size_t i = 0; i < _states.size(); i++) {
 		const DialogConditionState &state = _states[i];
 		if (state.mode == ShowOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
-			debug("isShowOnce %d: false", line);
+			debugC(kDebugDialog, "isShowOnce %d: false", line);
 			return false;
 		}
 	}
-	debug("isShowOnce %d: true", line);
+	debugC(kDebugDialog, "isShowOnce %d: true", line);
 	return true;
 }
 
@@ -295,11 +295,11 @@ bool Dialog::isOnceEver(int line) const {
 	for (size_t i = 0; i < _states.size(); i++) {
 		const DialogConditionState &state = _states[i];
 		if (state.mode == OnceEver && state.dialog == _context.dialogName && state.line == line) {
-			debug("isOnceEver %d: false", line);
+			debugC(kDebugDialog, "isOnceEver %d: false", line);
 			return false;
 		}
 	}
-	debug("isOnceEver %d: true", line);
+	debugC(kDebugDialog, "isOnceEver %d: true", line);
 	return true;
 }
 
@@ -307,17 +307,17 @@ bool Dialog::isTempOnce(int line) const {
 	for (size_t i = 0; i < _states.size(); i++) {
 		const DialogConditionState &state = _states[i];
 		if (state.mode == TempOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
-			debug("isTempOnce %d: false", line);
+			debugC(kDebugDialog, "isTempOnce %d: false", line);
 			return false;
 		}
 	}
-	debug("isTempOnce %d: true", line);
+	debugC(kDebugDialog, "isTempOnce %d: true", line);
 	return true;
 }
 
 bool Dialog::isCond(const Common::String &cond) const {
 	bool result = _tgt->execCond(cond);
-	debug("isCond '%s': %s", cond.c_str(), result ? "TRUE" : "FALSE");
+	debugC(kDebugDialog, "isCond '%s': %s", cond.c_str(), result ? "TRUE" : "FALSE");
 	return result;
 }
 
@@ -339,7 +339,7 @@ YLabel *Dialog::label(int line, const Common::String &name) const {
 }
 
 void Dialog::selectLabel(int line, const Common::String &name) {
-	debug("select label %s", name.c_str());
+	debugC(kDebugDialog, "select label %s", name.c_str());
 	_lbl = label(line, name);
 	_currentStatement = 0;
 	clearSlots();
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 7193d2e2634..77b86b3e799 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -80,7 +80,7 @@ Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
 }
 
 Motor *EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
-	debug("say %s: %s", actor.c_str(), text.c_str());
+	debugC(kDebugDialog, "say %s: %s", actor.c_str(), text.c_str());
 	Object *act = actorOrCurrent(actor);
 	act->say({text}, act->_talkColor);
 	return act->getTalking();
@@ -113,14 +113,14 @@ bool EngineDialogTarget::execCond(const Common::String &cond) {
 	sq_pushroottable(v);
 	Common::String code = Common::String::format("return %s", cond.c_str());
 	if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "condition", SQTrue))) {
-		debug("Error executing code %s", code.c_str());
+		debugC(kDebugDialog, "Error executing code %s", code.c_str());
 		return false;
 	}
 
 	sq_push(v, -2);
 	// call
 	if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
-		debug("Error calling code {code}");
+		debugC(kDebugDialog, "Error calling code {code}");
 		return false;
 	}
 
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 2ad88634daa..3432b0d5710 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -201,7 +201,7 @@ void BmFont::load(const Common::String &name) {
 	if (!g_engine->_pack.assetExists(path.c_str())) {
 		path = name + "Font.fnt";
 	}
-	debug("Load font %s", path.c_str());
+	debugC(kDebugRes, "Load font %s", path.c_str());
 	GGPackEntryReader entry;
 	if (!entry.open(g_engine->_pack, path)) {
 		error("error loading font %s", path.c_str());
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index b630cd847a2..8a65e2e6cd9 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -251,7 +251,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
 	Math::Vector2d halfScreen(g_engine->_room->getScreenSize() / 2.f);
-	debug("cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
+	debugC(kDebugGenScript, "cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
 	g_engine->follow(nullptr);
 	g_engine->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
 	return 0;
@@ -323,7 +323,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 				SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
 				SpriteSheetFrame *verbFrame = &verbSheet->frameTable[Common::String::format("%s_en", vb.image.c_str())];
 				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
-				debug("findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
+				debugC(kDebugGenScript, "findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
 				sqpush(v, pos);
 				return 1;
 			}
@@ -340,7 +340,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 
 	Math::Vector2d rPos = g_engine->roomToScreen(obj->_node->getAbsPos());
 	Math::Vector2d pos(rPos.getX() + obj->_node->getSize().getX() / 2.f, rPos.getY() + obj->_node->getSize().getY() / 2.f);
-	debug("findScreenPosition(%s) => (%f,%f)", obj->_name.c_str(), pos.getX(), pos.getY());
+	debugC(kDebugGenScript, "findScreenPosition(%s) => (%f,%f)", obj->_name.c_str(), pos.getX(), pos.getY());
 	sqpush(v, pos);
 	return 1;
 }
@@ -486,7 +486,7 @@ static SQInteger loadArray(HSQUIRRELVM v) {
 	const SQChar *orgFilename = nullptr;
 	if (SQ_FAILED(sqget(v, 2, orgFilename)))
 		return sq_throwerror(v, "failed to get filename");
-	debug("loadArray: %s", orgFilename);
+	debugC(kDebugGenScript, "loadArray: %s", orgFilename);
 	Common::String filename = g_engine->getPrefs().getKey(orgFilename);
 	GGPackEntryReader entry;
 	entry.open(g_engine->_pack, g_engine->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
@@ -711,7 +711,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 		sqgetf(table, "key", key);
 	if (sqrawexists(table, "flags"))
 		sqgetf(table, "flags", flags);
-	debug("setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
+	debugC(kDebugGenScript, "setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
 	VerbId verbId;
 	verbId.id = id;
 	g_engine->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
@@ -904,7 +904,7 @@ static SQInteger translate(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, text)))
 		return sq_throwerror(v, "Failed to get text");
 	Common::String newText = g_engine->getTextDb().getText(text);
-	debug("translate(%s): %s", text, newText.c_str());
+	debugC(kDebugGenScript, "translate(%s): %s", text, newText.c_str());
 	sqpush(v, newText);
 	return 1;
 }
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index bcdec82fcf7..03576cdbff9 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -22,6 +22,7 @@
 #include "twp/ggpack.h"
 #include "common/archive.h"
 #include "common/debug.h"
+#include "twp/detection.h"
 
 namespace Twp {
 
@@ -654,7 +655,7 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 		int offset = (int)file["offset"]->asIntegerNumber();
 		int size = (int)file["size"]->asIntegerNumber();
 		_entries[filename] = GGPackEntry{offset, size};
-		// debug("filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
+		debug(kDebugGGPack, "filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
 	}
 	delete value;
 
@@ -758,7 +759,7 @@ void GGPackSet::init() {
 
 		if (_packs.size() > 0) {
 			// the game has been detected because we have at least 1 ggpack file.
-			debug("Thimbleweed Park detected with key %s", key_names[i]);
+			debugC(kDebugGGPack, "Thimbleweed Park detected with key %s", key_names[i]);
 			return;
 		}
 	}
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 8ff7c852656..3cfa520b193 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -194,7 +194,7 @@ WalkTo::WalkTo(Object *obj, Vector2i dest, int facing)
 void WalkTo::disable() {
 	Motor::disable();
 	if (_path.size() != 0) {
-		debug("actor walk cancelled");
+		debugC(kDebugGame, "actor walk cancelled");
 	}
 	if (_obj->isWalking())
 		_obj->play("stand");
@@ -209,17 +209,17 @@ void WalkTo::actorArrived() {
 	if (!needsReach)
 		disable();
 
-	debug("actorArrived");
+	debugC(kDebugGame, "actorArrived");
 	_obj->play("stand");
 	// the faces to the specified direction (if any)
 	if (_facing) {
-		debug("actor arrived with facing %d", _facing);
+		debugC(kDebugGame, "actor arrived with facing %d", _facing);
 		_obj->setFacing((Facing)_facing);
 	}
 
 	// call `actorArrived` callback
 	if (sqrawexists(_obj->_table, "actorArrived")) {
-		debug("call actorArrived callback");
+		debugC(kDebugGame, "call actorArrived callback");
 		sqcall(_obj->_table, "actorArrived");
 	}
 
@@ -231,7 +231,7 @@ void WalkTo::actorArrived() {
 		// call `postWalk`callback
 		Common::String funcName = isActor(noun1->getId()) ? "actorPostWalk" : "objectPostWalk";
 		if (sqrawexists(_obj->_table, funcName)) {
-			debug("call %s callback", funcName.c_str());
+			debugC(kDebugGame, "call %s callback", funcName.c_str());
 			HSQOBJECT n2Table;
 			if (noun2)
 				n2Table = noun2->_table;
@@ -326,7 +326,7 @@ void Talking::update(float elapsed) {
 	_elapsed += elapsed;
 	if (_obj->_sound) {
 		if (!g_engine->_audio.playing(_obj->_sound)) {
-			debug("talking %s audio stopped", _obj->_key.c_str());
+			debugC(kDebugGame, "talking %s audio stopped", _obj->_key.c_str());
 			_obj->_sound = 0;
 		} else {
 			float e = g_engine->_audio.getElapsed(_obj->_sound) / 1000.f;
@@ -337,34 +337,34 @@ void Talking::update(float elapsed) {
 		char letter = _lip.letter(_elapsed);
 		_obj->setHeadIndex(letterToIndex(letter));
 	} else if (!_texts.empty()) {
-		debug("talking %s: %s", _obj->_key.c_str(), _texts[0].c_str());
+		debugC(kDebugGame, "talking %s: %s", _obj->_key.c_str(), _texts[0].c_str());
 		say(_texts[0]);
 		_texts.remove_at(0);
 	} else {
-		debug("talking %s: ended", _obj->_key.c_str());
+		debugC(kDebugGame, "talking %s: ended", _obj->_key.c_str());
 		disable();
 	}
 }
 
 int Talking::loadActorSpeech(const Common::String &name) {
 	if (!ConfMan.getBool("talkiesHearVoice")) {
-		debug("talking %s: talkiesHearVoice: false", _obj->_key.c_str());
+		debugC(kDebugGame, "talking %s: talkiesHearVoice: false", _obj->_key.c_str());
 		return 0;
 	}
 
-	debug("loadActorSpeech %s.ogg", name.c_str());
+	debugC(kDebugGame, "loadActorSpeech %s.ogg", name.c_str());
 	Common::String filename(name);
 	filename.toUppercase();
 	filename += ".ogg";
 	if (g_engine->_pack.assetExists(filename.c_str())) {
 		SoundDefinition *soundDefinition = new SoundDefinition(filename);
 		if (!soundDefinition) {
-			debug("File %s.ogg not found", name.c_str());
+			debugC(kDebugGame, "File %s.ogg not found", name.c_str());
 		} else {
 			g_engine->_audio._soundDefs.push_back(soundDefinition);
 			int id = g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
 			int duration = g_engine->_audio.getDuration(id);
-			debug("talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
+			debugC(kDebugGame, "talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
 			if (duration)
 				_duration = duration / 1000.f;
 			return id;
@@ -388,12 +388,12 @@ void Talking::say(const Common::String &text) {
 		Common::String name = Common::String::format("%s_%d", key.c_str(), id);
 		Common::String path = name + ".lip";
 
-		debug("Load lip %s", path.c_str());
+		debugC(kDebugGame, "Load lip %s", path.c_str());
 		if (g_engine->_pack.assetExists(path.c_str())) {
 			GGPackEntryReader entry;
 			entry.open(g_engine->_pack, path);
 			_lip.load(&entry);
-			debug("Lip %s loaded", path.c_str());
+			debugC(kDebugGame, "Lip %s loaded", path.c_str());
 		}
 
 		if (_obj->_sound) {
@@ -412,7 +412,7 @@ void Talking::say(const Common::String &text) {
 			txt = txt.substr(i + 1);
 	}
 
-	debug("sayLine '%s'", txt.c_str());
+	debugC(kDebugGame, "sayLine '%s'", txt.c_str());
 
 	if (sqrawexists(_obj->_table, "sayingLine")) {
 		const char *anim = _obj->_animName.empty() ? nullptr : _obj->_animName.c_str();
@@ -425,7 +425,7 @@ void Talking::say(const Common::String &text) {
 		int i = txt.find('}');
 		if (i != -1) {
 			state = txt.substr(1, txt.size() - 2);
-			debug("Set state from anim '%s'", state.c_str());
+			debugC(kDebugGame, "Set state from anim '%s'", state.c_str());
 			if (state != "notalk") {
 				_obj->play(state);
 			}
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index c6ca09050ef..4a28d0b874f 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -364,7 +364,7 @@ void Object::setRoom(Room *room) {
 		}
 		Room *oldRoom = _room;
 		if (oldRoom && _node->getParent()) {
-			debug("Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
+			debugC(kDebugGame, "Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
 			Layer *layer = oldRoom->layer(0);
 			if (layer) {
 				int index = find(layer->_objects, this);
@@ -375,7 +375,7 @@ void Object::setRoom(Room *room) {
 			}
 		}
 		if (room && room->layer(0) && room->layer(0)->_node) {
-			debug("Add %s in room %s", _key.c_str(), room->_name.c_str());
+			debugC(kDebugGame, "Add %s in room %s", _key.c_str(), room->_name.c_str());
 			Layer *layer = room->layer(0);
 			if (layer) {
 				int index = find(layer->_objects, this);
@@ -407,7 +407,7 @@ void Object::stopObjectMotors() {
 
 void Object::setFacing(Facing facing) {
 	if (_facing != facing) {
-		debug("set facing: %d", facing);
+		debugC(kDebugGame, "set facing: %d", facing);
 		bool update = !(((_facing == FACE_LEFT) && (facing == FACE_RIGHT)) || ((_facing == FACE_RIGHT) && (facing == FACE_LEFT)));
 		_facing = facing;
 		if (update && _nodeAnim)
@@ -667,7 +667,7 @@ static bool verbNotClose(VerbId id) {
 static void cantReach(Object *self, Object *noun2) {
 	if (sqrawexists(self->_table, "verbCantReach")) {
 		int nParams = sqparamCount(g_engine->getVm(), self->_table, "verbCantReach");
-		debug("verbCantReach found in obj '%s' with %d params", self->_key.c_str(), nParams);
+		debugC(kDebugGame, "verbCantReach found in obj '%s' with %d params", self->_key.c_str(), nParams);
 		if (nParams == 1) {
 			sqcall(self->_table, "verbCantReach");
 		} else {
@@ -692,18 +692,18 @@ void Object::execVerb() {
 		Object *noun1 = _exec.noun1;
 		Object *noun2 = _exec.noun2;
 
-		debug("actorArrived: exec sentence");
+		debugC(kDebugGame, "actorArrived: exec sentence");
 		if (!noun1->inInventory()) {
 			// Object became untouchable as we were walking there
 			if (!noun1->isTouchable()) {
-				debug("actorArrived: noun1 untouchable");
+				debugC(kDebugGame, "actorArrived: noun1 untouchable");
 				_exec.enabled = false;
 				return;
 			}
 			// Did we get close enough?
 			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun1->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
-			debug("actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
+			debugC(kDebugGame, "actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
 				cantReach(noun1, noun2);
 				return;
@@ -715,20 +715,20 @@ void Object::execVerb() {
 		if (noun2 && !noun2->inInventory()) {
 			if (!noun2->isTouchable()) {
 				// Object became untouchable as we were walking there.
-				debug("actorArrived: noun2 untouchable");
+				debugC(kDebugGame, "actorArrived: noun2 untouchable");
 				_exec.enabled = false;
 				return;
 			}
 			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun2->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
-			debug("actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
+			debugC(kDebugGame, "actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
 			if (dist > min_dist) {
 				cantReach(noun1, noun2);
 				return;
 			}
 		}
 
-		debug("actorArrived: callVerb");
+		debugC(kDebugGame, "actorArrived: callVerb");
 		_exec.enabled = false;
 		g_engine->callVerb(this, verb, noun1, noun2);
 	}
@@ -736,7 +736,7 @@ void Object::execVerb() {
 
 // Walks an actor to the `pos` or actor `obj` and then faces `dir`.
 void Object::walk(Vector2i pos, int facing) {
-	debug("walk to obj %s: %d,%d, %d", _key.c_str(), pos.x, pos.y, facing);
+	debugC(kDebugGame, "walk to obj %s: %d,%d, %d", _key.c_str(), pos.x, pos.y, facing);
 	if (!_walkTo || (!_walkTo->isEnabled())) {
 		play(getAnimName(WALK_ANIMNAME), true);
 	}
@@ -745,7 +745,7 @@ void Object::walk(Vector2i pos, int facing) {
 
 // Walks an actor to the `obj` and then faces it.
 void Object::walk(Object *obj) {
-	debug("walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
+	debugC(kDebugGame, "walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
 	Facing facing = (Facing)obj->_useDir;
 	walk((Vector2i)obj->getUsePos(), facing);
 }
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index d35cd8c8ea3..63472519b22 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -79,7 +79,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 		}
 	}
 
-	debug("Create object: %s, %u", sheet.c_str(), frames.size());
+	debugC(kDebugObjScript, "Create object: %s, %u", sheet.c_str(), frames.size());
 	Object *obj = g_engine->_room->createObject(sheet, frames);
 	sq_pushobject(v, obj->_table);
 
@@ -133,7 +133,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 			break;
 		}
 	}
-	debug("Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
+	debugC(kDebugObjScript, "Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
 	Object *obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
 	sqpush(v, obj->_table);
 	return 1;
@@ -187,14 +187,14 @@ static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (!obj->_owner || (obj->_owner != g_engine->_actor)) {
-		debug("Is '%s(%s)' in inventory: no", obj->_name.c_str(), obj->_key.c_str());
+		debugC(kDebugObjScript, "Is '%s(%s)' in inventory: no", obj->_name.c_str(), obj->_key.c_str());
 		sqpush(v, false);
 		return 1;
 	}
 	int offset = obj->_owner->_inventoryOffset;
 	int index = find(obj->_owner->_inventory, obj);
 	int res = index >= offset * 4 && index < (offset * 4 + 8);
-	debug("Is '%s(%s)' in inventory: {%d}", obj->_name.c_str(), obj->_key.c_str(), res);
+	debugC(kDebugObjScript, "Is '%s(%s)' in inventory: {%d}", obj->_name.c_str(), obj->_key.c_str(), res);
 	sqpush(v, res);
 	return 1;
 }
@@ -402,7 +402,7 @@ static SQInteger objectHidden(HSQUIRRELVM v) {
 	if (obj) {
 		int hidden = 0;
 		sqget(v, 3, hidden);
-		debug("Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(), hidden == 0 ? "true" : "false");
+		debugC(kDebugObjScript, "Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(), hidden == 0 ? "true" : "false");
 		obj->_node->setVisible(hidden == 0);
 	}
 	return 0;
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 672830d0886..3f20537e71d 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -44,7 +44,7 @@ Common::String getKey(const char *path) {
 }
 
 void ResManager::loadTexture(const Common::String &name) {
-	debug("Load texture %s", name.c_str());
+	debugC(kDebugRes, "Load texture %s", name.c_str());
 	GGPackEntryReader r;
 	if(!r.open(g_engine->_pack, name)) {
 		error("Texture %s not found", name.c_str());
@@ -81,12 +81,12 @@ void ResManager::loadSpriteSheet(const Common::String &name) {
 
 void ResManager::loadFont(const Common::String &name) {
 	if (name == "sayline") {
-		debug("Load font %s", name.c_str());
+		debugC(kDebugRes, "Load font %s", name.c_str());
 		Common::String resName = ConfMan.getBool("retroFonts") ? "FontRetroSheet": "FontModernSheet";
 		_fontModernSheet.load(resName);
 		_fonts[name] = &_fontModernSheet;
 	} else if (name == "C64Font") {
-		debug("Load font %s", name.c_str());
+		debugC(kDebugRes, "Load font %s", name.c_str());
 		_fontC64TermSheet.load("FontC64TermSheet");
 		_fonts[name] = &_fontC64TermSheet;
 	} else {
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 7a3c5d0486f..a2c93d55096 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -178,7 +178,7 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	sqsetf(obj->_table, "name", name);
 	obj->_key = name;
 	obj->_node->setName(name);
-	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
+	debugC(kDebugGame, "Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 
 	obj->_room = this;
 	obj->_sheet = sheet;
@@ -216,7 +216,7 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 
 	// assign an id
 	setId(obj->_table, newObjId());
-	debug("Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
+	debugC(kDebugGame, "Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
 	obj->_node->setName(obj->_key);
 
@@ -260,7 +260,7 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 void Room::load(Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
-	// debug("Room: %s", value->stringify().c_str());
+	// debugC(kDebugGame, "Room: %s", value->stringify().c_str());
 	const Common::JSONObject &jRoom = value->asObject();
 
 	_name = jRoom["name"]->asString();
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 97050b789af..97c6bd81ef6 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -102,7 +102,7 @@ static SQInteger createLight(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 4, y)))
 		return sq_throwerror(v, "failed to get y");
 	Light *light = g_engine->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
-	debug("createLight(%d) -> %d", color, light->id);
+	debugC(kDebugRoomScript, "createLight(%d) -> %d", color, light->id);
 	sqpush(v, light->id);
 	return 1;
 }
@@ -186,7 +186,7 @@ static SQInteger defineRoom(HSQUIRRELVM v) {
 	if (name.size() == 0)
 		sqgetf(v, table, "background", name);
 	Room *room = g_engine->defineRoom(name, table);
-	debug("Define room: %s", name.c_str());
+	debugC(kDebugRoomScript, "Define room: %s", name.c_str());
 	g_engine->_rooms.push_back(room);
 	sqpush(v, room->_table);
 	return 1;
@@ -214,7 +214,7 @@ static SQInteger definePseudoRoom(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get room table");
 
 	Room *room = g_engine->defineRoom(name, table, true);
-	debug("Define pseudo room: %s", name);
+	debugC(kDebugRoomScript, "Define pseudo room: %s", name);
 	g_engine->_rooms.push_back(room);
 	sqpush(v, room->_table);
 	return 1;
@@ -282,7 +282,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get object");
 		size_t i = find(g_engine->_room->_triggers, obj);
 		if (i != (size_t)-1) {
-			debug("Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
+			debugC(kDebugRoomScript, "Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
 			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
 		}
 		return 0;
@@ -437,7 +437,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 		float duration;
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
-		debug("start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
+		debugC(kDebugRoomScript, "start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
 		g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
 	}
 	return 0;
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index fc5c051082f..1aa4dfb3024 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -465,7 +465,7 @@ void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
 }
 
 void SaveGameManager::loadDialog(const Common::JSONObject &json) {
-	debug("loadDialog");
+	debugC(kDebugGame, "loadDialog");
 	g_engine->_dialog._states.clear();
 	for (auto it = json.begin(); it != json.end(); it++) {
 		Common::String dialog(it->_key);
@@ -496,7 +496,7 @@ private:
 };
 
 void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
-	debug("loadCallbacks");
+	debugC(kDebugGame, "loadCallbacks");
 	g_engine->_callbacks.clear();
 	if (!json["callbacks"]->isNull()) {
 		const Common::JSONArray &jCallbacks = json["callbacks"]->asArray();
@@ -518,7 +518,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 }
 
 void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
-	debug("loadGlobals");
+	debugC(kDebugGame, "loadGlobals");
 	HSQUIRRELVM v = g_engine->getVm();
 	HSQOBJECT g;
 	sqgetf("g", g);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 56d80fc29eb..130e5a675c1 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -748,7 +748,7 @@ bool NoOverrideNode::update(float elapsed) {
 	}
 	_elapsed += elapsed;
 	setAlpha(clamp((2.f - _elapsed) / 2.f, 0.f, 1.f));
-	debug("no override: %.2f, %.2f", _elapsed, getAlpha());
+	debugC(kDebugGame, "no override: %.2f, %.2f", _elapsed, getAlpha());
 	return true;
 }
 
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index f779b5dcb1e..83c864f01e4 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -91,7 +91,7 @@ static SQInteger defineSound(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get filename");
 	SoundDefinition *sound = new SoundDefinition(filename);
 	g_engine->_audio._soundDefs.push_back(sound);
-	debug("defineSound(%s)-> %d", filename.c_str(), sound->getId());
+	debugC(kDebugSndScript, "defineSound(%s)-> %d", filename.c_str(), sound->getId());
 	sqpush(v, sound->getId());
 	return 1;
 }
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index dc18d17a0aa..2a3a3ae382b 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -367,12 +367,12 @@ int sqparamCount(HSQUIRRELVM v, HSQOBJECT obj, const Common::String &name) {
 	sq_pushstring(v, name.c_str(), -1);
 	if (SQ_FAILED(sq_get(v, -2))) {
 		sq_settop(v, top);
-		debug("can't find %s function", name.c_str());
+		debugC(kDebugGame, "can't find %s function", name.c_str());
 		return 0;
 	}
 	SQInteger nparams, nfreevars;
 	sq_getclosureinfo(v, -1, &nparams, &nfreevars);
-	debug("%s function found with %lld parameters", name.c_str(), nparams);
+	debugC(kDebugGame, "%s function found with %lld parameters", name.c_str(), nparams);
 	sq_settop(v, top);
 	return nparams;
 }
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index a5dcf2721f0..8c8fa27f15f 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -85,7 +85,7 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 
 	g_engine->_threads.push_back(t);
 
-	debug("create thread %s", t->getName().c_str());
+	debugC(kDebugSysScript, "create thread %s", t->getName().c_str());
 
 	// call the closure in the thread
 	if (!t->call())
@@ -230,7 +230,7 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	if (!curThread)
 		return sq_throwerror(v, "Current thread should be created with startthread");
 
-	debug("add breakwhilecond name=%s pid=%d, %s", name.c_str(), curThread->getId(), curThread->getName().c_str());
+	debugC(kDebugSysScript, "add breakwhilecond name=%s pid=%d, %s", name.c_str(), curThread->getId(), curThread->getName().c_str());
 	g_engine->_tasks.push_back(new BreakWhileCond<Predicate>(curThread->getId(), name, pred));
 	return -666;
 }
@@ -337,7 +337,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 	int id = 0;
 	if (sq_gettype(v, 2) == OT_INTEGER)
 		sqget(v, 2, id);
-	debug("breakwhilerunning: %d", id);
+	debugC(kDebugSysScript, "breakwhilerunning: %d", id);
 
 	ThreadBase *t = sqthread(id);
 	if (!t) {
@@ -502,7 +502,7 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger cutsceneOverride(HSQUIRRELVM v) {
-	debug("cutsceneOverride");
+	debugC(kDebugSysScript, "cutsceneOverride");
 	g_engine->_cutscene->cutsceneOverride();
 	return 0;
 }
@@ -650,7 +650,7 @@ static SQInteger inputVerbs(HSQUIRRELVM v) {
 	bool on;
 	if (SQ_FAILED(sqget(v, 2, on)))
 		return sq_throwerror(v, "failed to get isActive");
-	debug("inputVerbs: %s", on ? "yes" : "no");
+	debugC(kDebugSysScript, "inputVerbs: %s", on ? "yes" : "no");
 	g_engine->_inputState.setInputVerbsActive(on);
 	return 1;
 }
@@ -671,7 +671,7 @@ static SQInteger logEvent(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get message");
 		msg = event + ": " + msg;
 	}
-	debug("%s", msg.c_str());
+	debugC(kDebugSysScript, "%s", msg.c_str());
 	return 0;
 }
 
@@ -681,7 +681,7 @@ static SQInteger logInfo(HSQUIRRELVM v) {
 	Common::String msg;
 	if (SQ_FAILED(sqget(v, 2, msg)))
 		return sq_throwerror(v, "failed to get message");
-	debug("%s", msg.c_str());
+	debugC(kDebugSysScript, "%s", msg.c_str());
 	return 0;
 }
 
diff --git a/engines/twp/task.h b/engines/twp/task.h
index 3eb11684482..646ff76ca62 100644
--- a/engines/twp/task.h
+++ b/engines/twp/task.h
@@ -51,7 +51,7 @@ public:
 			return false;
 		ThreadBase *pt = sqthread(_parentId);
 		if (pt) {
-			debug("Resume task: %d, %s", _parentId, pt->getName().c_str());
+			debugC(kDebugGame, "Resume task: %d, %s", _parentId, pt->getName().c_str());
 			pt->resume();
 		}
 		return true;
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 3fb6d3e7001..f2da6bbed03 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -68,7 +68,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 }
 
 Thread::~Thread() {
-	debug("delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
+	debugC(kDebugGame, "delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
 	HSQUIRRELVM v = g_engine->getVm();
 	for (size_t i = 0; i < _args.size(); i++) {
 		sq_release(v, &_args[i]);
@@ -128,7 +128,7 @@ Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, H
 	_actor = g_engine->_followActor;
 	_showCursor = g_engine->_inputState.getShowCursor();
 	_state = csStart;
-	debug("Create cutscene %d with input: 0x%X from parent thread: %d", _id, _inputState, _parentThreadId);
+	debugC(kDebugGame, "Create cutscene %d with input: 0x%X from parent thread: %d", _id, _inputState, _parentThreadId);
 	g_engine->_inputState.setInputActive(false);
 	g_engine->_inputState.setShowCursor(false);
 	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
@@ -144,7 +144,7 @@ Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, H
 }
 
 Cutscene::~Cutscene() {
-	debug("destroy cutscene %d", _id);
+	debugC(kDebugGame, "destroy cutscene %d", _id);
 	HSQUIRRELVM vm = g_engine->getVm();
 	sq_release(vm, &_threadObj);
 	sq_release(vm, &_closure);
@@ -167,12 +167,12 @@ void Cutscene::start() {
 
 void Cutscene::stop() {
 	_state = csQuit;
-	debug("End cutscene: %d", getId());
+	debugC(kDebugGame, "End cutscene: %d", getId());
 	g_engine->_inputState.setState(_inputState);
 	g_engine->_inputState.setShowCursor(_showCursor);
 	if (_showCursor)
 		g_engine->_inputState.setInputActive(true);
-	debug("Restore cutscene input: %X", _inputState);
+	debugC(kDebugGame, "Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
 	Common::Array<ThreadBase *> threads(g_engine->_threads);
 	for (size_t i = 0; i < threads.size(); i++) {
@@ -191,7 +191,7 @@ void Cutscene::stop() {
 void Cutscene::checkEndCutsceneOverride() {
 	if (isStopped()) {
 		_state = csEnd;
-		debug("end checkEndCutsceneOverride");
+		debugC(kDebugGame, "end checkEndCutsceneOverride");
 	}
 }
 
@@ -210,18 +210,18 @@ bool Cutscene::update(float elapsed) {
 
 	switch (_state) {
 	case csStart:
-		debug("startCutscene");
+		debugC(kDebugGame, "startCutscene");
 		start();
 		return false;
 	case csCheckEnd:
 		checkEndCutscene();
 		return false;
 	case csOverride:
-		debug("doCutsceneOverride");
+		debugC(kDebugGame, "doCutsceneOverride");
 		doCutsceneOverride();
 		return false;
 	case csCheckOverride:
-		debug("checkEndCutsceneOverride");
+		debugC(kDebugGame, "checkEndCutsceneOverride");
 		checkEndCutsceneOverride();
 		return false;
 	case csEnd:
@@ -239,7 +239,7 @@ bool Cutscene::hasOverride() const {
 void Cutscene::doCutsceneOverride() {
 	if (hasOverride()) {
 		_state = csCheckOverride;
-		debug("start cutsceneOverride");
+		debugC(kDebugGame, "start cutsceneOverride");
 		sq_pushobject(getThread(), _closureOverride);
 		sq_pushobject(getThread(), _envObj);
 		if (SQ_FAILED(sq_call(getThread(), 1, SQFalse, SQTrue)))
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 8f1d7bb2a56..ff74ef0279e 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -23,6 +23,7 @@
 #include "twp/tsv.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/squirrel.h"
+#include "twp/detection.h"
 
 namespace Twp {
 
@@ -34,7 +35,7 @@ void TextDb::parseTsv(Common::SeekableReadStream &stream) {
 		int id = atoi(line.c_str());
 		Common::String s = line.substr(pos + 1);
 		_texts[id] = s;
-		debug("%d: %s", id, _texts[id].c_str());
+		debugC(kDebugText, "%d: %s", id, _texts[id].c_str());
 	}
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1a50e1b3d46..c324d6c5f06 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -104,7 +104,7 @@ bool TwpEngine::clickedAtHandled(Math::Vector2d roomPos) {
 	int x = roomPos.getX();
 	int y = roomPos.getY();
 	if (sqrawexists(_room->_table, "clickedAt")) {
-		debug("clickedAt %d, %d", x, y);
+		debugC(kDebugGame, "clickedAt %d, %d", x, y);
 		sqcallfunc(result, _room->_table, "clickedAt", x, y);
 	}
 	if (!result) {
@@ -126,14 +126,14 @@ bool TwpEngine::preWalk(Object *actor, VerbId verbId, Object *noun1, Object *nou
 		sq_resetobject(&n2Table);
 	}
 	if (sqrawexists(actor->_table, "actorPreWalk")) {
-		debug("actorPreWalk %d n1=%s(%s) n2=%s", verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str());
+		debugC(kDebugGame, "actorPreWalk %d n1=%s(%s) n2=%s", verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str());
 		sqcallfunc(result, actor->_table, "actorPreWalk", verbId.id, noun1->_table, n2Table);
 	}
 	if (!result) {
 		Common::String funcName = isActor(noun1->getId()) ? "actorPreWalk" : "objectPreWalk";
 		if (sqrawexists(noun1->_table, funcName)) {
 			sqcallfunc(result, noun1->_table, funcName.c_str(), verbId.id, noun1->_table, n2Table);
-			debug("%s %d n1=%s(%s) n2=%s -> %s", funcName.c_str(), verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str(), result ? "yes" : "no");
+			debugC(kDebugGame, "%s %d n1=%s(%s) n2=%s -> %s", funcName.c_str(), verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str(), result ? "yes" : "no");
 		}
 	}
 	return result;
@@ -151,12 +151,12 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	debug("exec(%s,%d,%s,%s)", name.c_str(), verbId.id, noun1name.c_str(), noun2name.c_str());
+	debugC(kDebugGame, "exec(%s,%d,%s,%s)", name.c_str(), verbId.id, noun1name.c_str(), noun2name.c_str());
 	actor = !actor ? g_engine->_actor : actor;
 	if ((verbId.id <= 0) || (verbId.id > MAX_VERBS) || (!noun1) || (!actor))
 		return false;
 
-	debug("noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory() ? "YES" : "NO", noun1->isTouchable() ? "YES" : "NO", verbNoWalkTo(verbId, noun1) ? "YES" : "NO");
+	debugC(kDebugGame, "noun1.inInventory: %s and noun1.touchable: %s nowalk: %s", noun1->inInventory() ? "YES" : "NO", noun1->isTouchable() ? "YES" : "NO", verbNoWalkTo(verbId, noun1) ? "YES" : "NO");
 
 	// test if object became untouchable
 	if (!noun1->inInventory() && !noun1->isTouchable())
@@ -199,7 +199,7 @@ void TwpEngine::flashSelectableActor(int flash) {
 
 void TwpEngine::walkFast(bool state) {
 	if (_walkFastState != state) {
-		debug("walk fast: %s", state ? "yes" : "no");
+		debugC(kDebugGame, "walk fast: %s", state ? "yes" : "no");
 		_walkFastState = state;
 		if (_actor)
 			sqcall(_actor->_table, "run", state);
@@ -404,7 +404,7 @@ void TwpEngine::update(float elapsed) {
 				} else {
 					objsAt(roomPos, GetNoun2(_noun2));
 					if (_noun2)
-						debug("Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
+						debugC(kDebugGame, "Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
 				}
 			} else {
 				_noun1 = objAt(roomPos);
@@ -820,7 +820,7 @@ Common::Error TwpEngine::run() {
 				case Common::KEYCODE_w:
 					if (control) {
 						WalkboxMode mode = (WalkboxMode)(((int)_walkboxNode.getMode() + 1) % 3);
-						debug("set walkbox mode to: %s", (mode == WalkboxMode::Merged ? "merged" : mode == WalkboxMode::All ? "all"
+						debugC(kDebugGame, "set walkbox mode to: %s", (mode == WalkboxMode::Merged ? "merged" : mode == WalkboxMode::All ? "all"
 																															: "none"));
 						_walkboxNode.setMode(mode);
 					}
@@ -828,7 +828,7 @@ Common::Error TwpEngine::run() {
 				case Common::KEYCODE_g:
 					if (control) {
 						PathMode mode = (PathMode)(((int)_pathNode.getMode() + 1) % 3);
-						debug("set path mode to: %s", (mode == PathMode::GraphMode ? "graph" : mode == PathMode::All ? "all"
+						debugC(kDebugGame, "set path mode to: %s", (mode == PathMode::GraphMode ? "graph" : mode == PathMode::All ? "all"
 																													 : "none"));
 						_pathNode.setMode(mode);
 					}
@@ -967,7 +967,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 
 		if (sqrawexists(oTable, "icon")) {
 			// Add inventory object to root table
-			debug("Add %s to inventory", k.c_str());
+			debugC(kDebugGame, "Add %s to inventory", k.c_str());
 			sqsetf(sqrootTbl(params->v), k, oTable);
 
 			// set room as delegate
@@ -987,7 +987,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 		} else {
 			Object *obj = params->room->getObj(k);
 			if (!obj) {
-				debug("object: %s not found in wimpy", k.c_str());
+				debugC(kDebugGame, "object: %s not found in wimpy", k.c_str());
 				if (sqrawexists(oTable, "name")) {
 					obj = new Object();
 					obj->_key = k;
@@ -1000,7 +1000,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 
 			sqgetf(params->room->_table, k, obj->_table);
 			setId(obj->_table, newObjId());
-			debug("Create object: %s #%d", k.c_str(), obj->getId());
+			debugC(kDebugGame, "Create object: %s #%d", k.c_str(), obj->getId());
 
 			// add it to the root table if not a pseudo room
 			if (!params->pseudo)
@@ -1028,7 +1028,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 
 Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
 	HSQUIRRELVM v = _vm.get();
-	debug("Load room: %s", name.c_str());
+	debugC(kDebugGame, "Load room: %s", name.c_str());
 	Room *result;
 	if (name == "Void") {
 		result = new Room(name, table);
@@ -1118,7 +1118,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 void TwpEngine::enterRoom(Room *room, Object *door) {
 	HSQUIRRELVM v = getVm();
 	// Called when the room is entered.
-	debug("call enter room function of %s", room->_name.c_str());
+	debugC(kDebugGame, "call enter room function of %s", room->_name.c_str());
 
 	// exit current room
 	exitRoom(_room);
@@ -1263,7 +1263,7 @@ void TwpEngine::actorExit() {
 }
 
 void TwpEngine::cancelSentence(Object *actor) {
-	debug("cancelSentence");
+	debugC(kDebugGame, "cancelSentence");
 	if (!actor)
 		actor = _actor;
 	if (actor)
@@ -1282,14 +1282,14 @@ void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
 void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 	if (_pack.assetExists(entry.c_str())) {
 		GGPackEntryReader reader;
-		debug("read existing '%s'", entry.c_str());
+		debugC(kDebugGame, "read existing '%s'", entry.c_str());
 		reader.open(_pack, entry);
 		Common::String code = reader.readString();
-		// debug("%s", code.c_str());
+		// debugC(kDebugGame, "%s", code.c_str());
 		sqexec(v, code.c_str(), entry.c_str());
 	} else {
 		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
-		debug("read existing '%s'", newEntry.c_str());
+		debugC(kDebugGame, "read existing '%s'", newEntry.c_str());
 		if (_pack.assetExists(newEntry.c_str())) {
 			execBnutEntry(v, newEntry);
 		} else {
@@ -1396,7 +1396,7 @@ static void giveTo(Object *actor1, Object *actor2, Object *obj) {
 }
 
 void TwpEngine::resetVerb() {
-	debug("reset nouns");
+	debugC(kDebugGame, "reset nouns");
 	_noun1 = nullptr;
 	_noun2 = nullptr;
 	_useFlag = ufNone;
@@ -1413,7 +1413,7 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	ActorSlot *slot = _hud.actorSlot(actor);
 	Verb *verb = slot->getVerb(verbId.id);
 	Common::String verbFuncName = verb ? verb->fun : slot->getVerb(0)->fun;
-	debug("callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
+	debugC(kDebugGame, "callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable
 	if (!noun1->inInventory() && !noun1->isTouchable())
@@ -1432,23 +1432,23 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 
 	if (verbId.id == VERB_GIVE) {
 		if (!noun2) {
-			debug("set use flag to ufGiveTo");
+			debugC(kDebugGame, "set use flag to ufGiveTo");
 			_useFlag = ufGiveTo;
 			_noun1 = noun1;
 		} else {
 			bool handled = false;
 			if (sqrawexists(noun2->_table, verbFuncName)) {
-				debug("call %s on %s", verbFuncName.c_str(), noun2->_key.c_str());
+				debugC(kDebugGame, "call %s on %s", verbFuncName.c_str(), noun2->_key.c_str());
 				sqcallfunc(handled, noun2->_table, verbFuncName.c_str(), noun1->_table);
 			}
 			// verbGive is called on object only for non selectable actors
 			if (!handled && !selectable(noun2) && sqrawexists(noun1->_table, verbFuncName)) {
-				debug("call %s on %s", verbFuncName.c_str(), noun1->_key.c_str());
+				debugC(kDebugGame, "call %s on %s", verbFuncName.c_str(), noun1->_key.c_str());
 				sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
 				handled = true;
 			}
 			if (!handled) {
-				debug("call objectGive");
+				debugC(kDebugGame, "call objectGive");
 				sqcall("objectGive", noun1->_table, _actor->_table, noun2->_table);
 				giveTo(_actor, noun2, noun1);
 			}
@@ -1460,7 +1460,7 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	if (!noun2) {
 		if (sqrawexists(noun1->_table, verbFuncName)) {
 			int count = sqparamCount(getVm(), noun1->_table, verbFuncName);
-			debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
+			debugC(kDebugGame, "call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
 			if (count == 1) {
 				sqcall(noun1->_table, verbFuncName.c_str());
 			} else {
@@ -1469,17 +1469,17 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
 			sqcall(noun1->_table, VERBDEFAULT);
 		} else {
-			debug("call defaultObject.%s", verbFuncName.c_str());
+			debugC(kDebugGame, "call defaultObject.%s", verbFuncName.c_str());
 			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, actor->_table);
 		}
 	} else {
 		if (sqrawexists(noun1->_table, verbFuncName)) {
-			debug("call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
+			debugC(kDebugGame, "call %s.%s", noun1->_key.c_str(), verbFuncName.c_str());
 			sqcall(noun1->_table, verbFuncName.c_str(), noun2->_table);
 		} else if (sqrawexists(noun1->_table, VERBDEFAULT)) {
 			sqcall(noun1->_table, VERBDEFAULT);
 		} else {
-			debug("call defaultObject.%s", verbFuncName.c_str());
+			debugC(kDebugGame, "call defaultObject.%s", verbFuncName.c_str());
 			sqcall(_defaultObj, verbFuncName.c_str(), noun1->_table, noun2->_table);
 		}
 	}
@@ -1519,7 +1519,7 @@ void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
 
 		Thread *thread = new Thread("Trigger", false, threadObj, obj->_table, trigger, args);
 
-		debug("create triggerthread id: %d}", thread->getId());
+		debugC(kDebugGame, "create triggerthread id: %d}", thread->getId());
 		g_engine->_threads.push_back(thread);
 
 		// call the closure in the thread
@@ -1535,11 +1535,11 @@ void TwpEngine::updateTriggers() {
 		for (size_t i = 0; i < _room->_triggers.size(); i++) {
 			Object *trigger = _room->_triggers[i];
 			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
-				debug("call enter trigger %s", trigger->_key.c_str());
+				debugC(kDebugGame, "call enter trigger %s", trigger->_key.c_str());
 				trigger->_triggerActive = true;
 				callTrigger(trigger, trigger->_enter);
 			} else if (trigger->_triggerActive && !trigger->contains(_actor->_node->getAbsPos())) {
-				debug("call leave trigger %s", trigger->_key.c_str());
+				debugC(kDebugGame, "call leave trigger %s", trigger->_key.c_str());
 				trigger->_triggerActive = false;
 				callTrigger(trigger, trigger->_leave);
 			}
@@ -1549,7 +1549,7 @@ void TwpEngine::updateTriggers() {
 		for (size_t i = 0; i < _room->_scalingTriggers.size(); i++) {
 			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
 			if (trigger->_obj->_triggerActive && !trigger->_obj->contains(_actor->_node->getAbsPos())) {
-				debug("leave scaling trigger %s", trigger->_obj->_key.c_str());
+				debugC(kDebugGame, "leave scaling trigger %s", trigger->_obj->_key.c_str());
 				trigger->_obj->_triggerActive = false;
 				_room->_scaling = _room->_scalings[0];
 			}
@@ -1557,7 +1557,7 @@ void TwpEngine::updateTriggers() {
 		for (size_t i = 0; i < _room->_scalingTriggers.size(); i++) {
 			ScalingTrigger *trigger = &_room->_scalingTriggers[i];
 			if (!trigger->_obj->_triggerActive && trigger->_obj->contains(_actor->_node->getAbsPos())) {
-				debug("enter scaling trigger %s", trigger->_obj->_key.c_str());
+				debugC(kDebugGame, "enter scaling trigger %s", trigger->_obj->_key.c_str());
 				trigger->_obj->_triggerActive = true;
 				_room->_scaling = *trigger->_scaling;
 			}
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 25ec0a66ff1..9612091a2ca 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -48,7 +48,7 @@ static HSQUIRRELVM gVm = nullptr;
 
 static void errorHandler(HSQUIRRELVM v, const SQChar *desc, const SQChar *source, SQInteger line,
 						 SQInteger column) {
-	debug("TWP: desc %s, source: %s (%lld,%lld)", desc, source, line, column);
+	debugN("TWP: desc %s, source: %s (%lld,%lld)", desc, source, line, column);
 }
 
 static SQInteger aux_printerror(HSQUIRRELVM v) {
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 7323af6306e..2361b3bdba8 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twp/yack.h"
+#include "twp/detection.h"
 #include "common/util.h"
 
 namespace Twp {
@@ -226,7 +227,7 @@ YackTokenId YackTokenReader::readYackTokenId() {
 		} else if (Common::isAlpha(c)) {
 			return readIdentifier(c);
 		}
-		debug("unknown character: %c", c);
+		debugC(kDebugDialog, "unknown character: %c", c);
 		return YackTokenId::None;
 	}
 }
@@ -325,7 +326,7 @@ YLabel *YackParser::parseLabel() {
 	// label
 	pLabel.reset(new YLabel(_it->line));
 	pLabel->_name = _reader.readText(*_it++);
-	// debug("label %s", pLabel->_name.c_str());
+	// debugC(kDebugDialog, "label %s", pLabel->_name.c_str());
 	do {
 		if (match({YackTokenId::Colon}) || match({YackTokenId::End}))
 			break;


Commit: 3c56af0e2933ee56ecc4d501e03b950f2a1924dd
    https://github.com/scummvm/scummvm/commit/3c56af0e2933ee56ecc4d501e03b950f2a1924dd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add DLC option

Changed paths:
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/twp.cpp


diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index 90e8ddfc263..fb22a7681d8 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -57,6 +57,7 @@ TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Comm
 
 	_enableDisplayTextGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Display Text"), _(""));
 	_enableHearVoiceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck2", _("Hear Voice"), _(""));
+	_enableDLC = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck3", _("Ransome *unbeeped* (DLC)"), _(""));
 
 	_langGUIDropdown = new GUI::PopUpWidget(widgetsBoss(), "TwpGameOptionsDialog.LangDropDown");
 	_langGUIDropdown->appendEntry(_("English"));
@@ -83,7 +84,8 @@ void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Comm
 		.addWidget("TextAndSpeechLabel", "OptionsLabel")
 		.addWidget("LangDropDown", "PopUp")
 		.addWidget("TextCheck1", "Checkbox")
-		.addWidget("TextCheck2", "Checkbox");
+		.addWidget("TextCheck2", "Checkbox")
+		.addWidget("TextCheck3", "Checkbox");
 
 	layouts.closeLayout().closeDialog();
 }
@@ -97,6 +99,7 @@ void TwpOptionsContainerWidget::load() {
 	_enableClassicSentenceGUICheckbox->setState(ConfMan.getBool("hudSentence", _domain));
 	_enableDisplayTextGUICheckbox->setState(ConfMan.getBool("talkiesShowText", _domain));
 	_enableHearVoiceGUICheckbox->setState(ConfMan.getBool("talkiesHearVoice", _domain));
+	_enableDLC->setState(ConfMan.getBool("ransomeUnbeeped", _domain));
 	Common::String lang = ConfMan.get("language", _domain);
 	int index = 0;
 	for (int i = 0; i < ARRAYSIZE(lang_items); i++) {
@@ -117,7 +120,7 @@ bool TwpOptionsContainerWidget::save() {
 	ConfMan.setBool("hudSentence", _enableClassicSentenceGUICheckbox->getState(), _domain);
 	ConfMan.setBool("talkiesShowText", _enableDisplayTextGUICheckbox->getState(), _domain);
 	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
-	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
+	ConfMan.setBool("ransomeUnbeeped", _enableDLC->getState(), _domain);
 	ConfMan.set("language", lang_items[_langGUIDropdown->getSelected()], _domain);
 	return true;
 }
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index 8ad8922848b..434cccf720e 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -47,6 +47,7 @@ private:
 	GUI::CheckboxWidget *_enableClassicSentenceGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableDisplayTextGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableHearVoiceGUICheckbox = nullptr;
+	GUI::CheckboxWidget *_enableDLC = nullptr;
 	GUI::PopUpWidget *_langGUIDropdown = nullptr;
 };
 
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 03576cdbff9..19da3b38e0a 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -458,7 +458,8 @@ Common::JSONValue *GGHashMapDecoder::open(Common::SeekableReadStream *s) {
 		return nullptr;
 	}
 	_stream = s;
-	(void)s->readUint32LE();
+	/*uint32 numEntries =*/(void)s->readUint32LE();
+
 	if (!_readPlo(s, _offsets))
 		return nullptr;
 	return readHash();
@@ -671,11 +672,11 @@ bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
 	pack._s->seek(e.offset);
 
 	RangeStream rs;
-	if(!rs.open(pack._s, e.size))
+	if (!rs.open(pack._s, e.size))
 		return false;
 
 	XorStream xs;
-	if(!xs.open(&rs, e.size, pack._key))
+	if (!xs.open(&rs, e.size, pack._key))
 		return false;
 
 	_buf.resize(e.size);
@@ -685,8 +686,8 @@ bool GGPackEntryReader::open(GGPackDecoder &pack, const Common::String &entry) {
 }
 
 bool GGPackEntryReader::open(GGPackSet &packs, const Common::String &entry) {
-	for (size_t i = 0; i < packs._packs.size(); i++) {
-		GGPackDecoder *pack = &packs._packs[i];
+	for (auto it = packs._packs.begin(); it != packs._packs.end(); it++) {
+		GGPackDecoder *pack = &it->second;
 		if (open(*pack, entry))
 			return true;
 	}
@@ -733,6 +734,10 @@ uint32 GGBnutReader::read(void *dataPtr, uint32 dataSize) {
 
 bool GGBnutReader::eos() const { return _s->eos(); }
 
+bool GGPackSet::containsDLC() const {
+	return _packs.find(3) != _packs.end();
+}
+
 void GGPackSet::init() {
 	// try to auto-detect which XOR key to use to decrypt the resources of the game
 	const XorKey key1{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
@@ -750,14 +755,21 @@ void GGPackSet::init() {
 		const XorKey *key = &keys[i];
 		for (auto it = fileList.begin(); it != fileList.end(); ++it) {
 			const Common::ArchiveMember &m = **it;
+			Common::String fileName = m.getFileName();
+			size_t pos = fileName.findLastOf("ggpack");
+			if (pos == Common::String::npos)
+				continue;
+
+			long index = atol(fileName.c_str() + pos + 1);
+
 			Common::SeekableReadStream *stream = m.createReadStream();
 			GGPackDecoder pack;
 			if (stream && pack.open(stream, *key)) {
-				_packs.push_back(pack);
+				_packs[index] = pack;
 			}
 		}
 
-		if (_packs.size() > 0) {
+		if (!_packs.empty()) {
 			// the game has been detected because we have at least 1 ggpack file.
 			debugC(kDebugGGPack, "Thimbleweed Park detected with key %s", key_names[i]);
 			return;
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index 48c71249214..edcdc0c549c 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -178,8 +178,10 @@ public:
 	void init();
 	bool assetExists(const char* asset);
 
+	bool containsDLC() const;
+
 public:
-	Common::Array<GGPackDecoder> _packs;
+	Common::StableMap<long, GGPackDecoder, Common::Greater<long>> _packs;
 };
 
 class GGBnutReader: public Common::ReadStream {
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c324d6c5f06..9af56362ffd 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -717,6 +717,7 @@ Common::Error TwpEngine::run() {
 
 	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
 	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
+	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped"));
 
 	static int speed = 1;
 	static bool control = false;


Commit: 154131e505f1dfa174a7dd9f36a947c16a04d27e
    https://github.com/scummvm/scummvm/commit/154131e505f1dfa174a7dd9f36a947c16a04d27e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add dear imgui

Changed paths:
  A engines/twp/debugtools.cpp
  A engines/twp/debugtools.h
  A engines/twp/imgui/.editorconfig
  A engines/twp/imgui/.gitattributes
  A engines/twp/imgui/.github/FUNDING.yml
  A engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
  A engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
  A engines/twp/imgui/.github/pull_request_template.md
  A engines/twp/imgui/.github/workflows/build.yml
  A engines/twp/imgui/.github/workflows/scheduled.yml
  A engines/twp/imgui/.github/workflows/static-analysis.yml
  A engines/twp/imgui/.gitignore
  A engines/twp/imgui/LICENSE.txt
  A engines/twp/imgui/backends/imgui_impl_allegro5.cpp
  A engines/twp/imgui/backends/imgui_impl_allegro5.h
  A engines/twp/imgui/backends/imgui_impl_android.cpp
  A engines/twp/imgui/backends/imgui_impl_android.h
  A engines/twp/imgui/backends/imgui_impl_dx10.cpp
  A engines/twp/imgui/backends/imgui_impl_dx10.h
  A engines/twp/imgui/backends/imgui_impl_dx11.cpp
  A engines/twp/imgui/backends/imgui_impl_dx11.h
  A engines/twp/imgui/backends/imgui_impl_dx12.cpp
  A engines/twp/imgui/backends/imgui_impl_dx12.h
  A engines/twp/imgui/backends/imgui_impl_dx9.cpp
  A engines/twp/imgui/backends/imgui_impl_dx9.h
  A engines/twp/imgui/backends/imgui_impl_glfw.cpp
  A engines/twp/imgui/backends/imgui_impl_glfw.h
  A engines/twp/imgui/backends/imgui_impl_glut.cpp
  A engines/twp/imgui/backends/imgui_impl_glut.h
  A engines/twp/imgui/backends/imgui_impl_metal.h
  A engines/twp/imgui/backends/imgui_impl_metal.mm
  A engines/twp/imgui/backends/imgui_impl_opengl2.cpp
  A engines/twp/imgui/backends/imgui_impl_opengl2.h
  A engines/twp/imgui/backends/imgui_impl_opengl3.cpp
  A engines/twp/imgui/backends/imgui_impl_opengl3.h
  A engines/twp/imgui/backends/imgui_impl_opengl3_loader.h
  A engines/twp/imgui/backends/imgui_impl_osx.h
  A engines/twp/imgui/backends/imgui_impl_osx.mm
  A engines/twp/imgui/backends/imgui_impl_sdl2.cpp
  A engines/twp/imgui/backends/imgui_impl_sdl2.h
  A engines/twp/imgui/backends/imgui_impl_sdl3.cpp
  A engines/twp/imgui/backends/imgui_impl_sdl3.h
  A engines/twp/imgui/backends/imgui_impl_sdlrenderer2.cpp
  A engines/twp/imgui/backends/imgui_impl_sdlrenderer2.h
  A engines/twp/imgui/backends/imgui_impl_sdlrenderer3.cpp
  A engines/twp/imgui/backends/imgui_impl_sdlrenderer3.h
  A engines/twp/imgui/backends/imgui_impl_vulkan.cpp
  A engines/twp/imgui/backends/imgui_impl_vulkan.h
  A engines/twp/imgui/backends/imgui_impl_wgpu.cpp
  A engines/twp/imgui/backends/imgui_impl_wgpu.h
  A engines/twp/imgui/backends/imgui_impl_win32.cpp
  A engines/twp/imgui/backends/imgui_impl_win32.h
  A engines/twp/imgui/backends/vulkan/generate_spv.sh
  A engines/twp/imgui/backends/vulkan/glsl_shader.frag
  A engines/twp/imgui/backends/vulkan/glsl_shader.vert
  A engines/twp/imgui/docs/BACKENDS.md
  A engines/twp/imgui/docs/CHANGELOG.txt
  A engines/twp/imgui/docs/CONTRIBUTING.md
  A engines/twp/imgui/docs/EXAMPLES.md
  A engines/twp/imgui/docs/FAQ.md
  A engines/twp/imgui/docs/FONTS.md
  A engines/twp/imgui/docs/README.md
  A engines/twp/imgui/docs/TODO.txt
  A engines/twp/imgui/examples/README.txt
  A engines/twp/imgui/examples/example_allegro5/README.md
  A engines/twp/imgui/examples/example_allegro5/imconfig_allegro5.h
  A engines/twp/imgui/examples/example_allegro5/main.cpp
  A engines/twp/imgui/examples/example_android_opengl3/CMakeLists.txt
  A engines/twp/imgui/examples/example_android_opengl3/android/.gitignore
  A engines/twp/imgui/examples/example_android_opengl3/android/app/build.gradle
  A engines/twp/imgui/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml
  A engines/twp/imgui/examples/example_android_opengl3/android/app/src/main/java/MainActivity.kt
  A engines/twp/imgui/examples/example_android_opengl3/android/build.gradle
  A engines/twp/imgui/examples/example_android_opengl3/android/settings.gradle
  A engines/twp/imgui/examples/example_android_opengl3/main.cpp
  A engines/twp/imgui/examples/example_apple_metal/README.md
  A engines/twp/imgui/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
  A engines/twp/imgui/examples/example_apple_metal/iOS/Info-iOS.plist
  A engines/twp/imgui/examples/example_apple_metal/iOS/LaunchScreen.storyboard
  A engines/twp/imgui/examples/example_apple_metal/macOS/Info-macOS.plist
  A engines/twp/imgui/examples/example_apple_metal/macOS/MainMenu.storyboard
  A engines/twp/imgui/examples/example_apple_metal/main.mm
  A engines/twp/imgui/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
  A engines/twp/imgui/examples/example_apple_opengl2/main.mm
  A engines/twp/imgui/examples/example_emscripten_wgpu/Makefile
  A engines/twp/imgui/examples/example_emscripten_wgpu/README.md
  A engines/twp/imgui/examples/example_emscripten_wgpu/main.cpp
  A engines/twp/imgui/examples/example_glfw_metal/Makefile
  A engines/twp/imgui/examples/example_glfw_metal/main.mm
  A engines/twp/imgui/examples/example_glfw_opengl2/Makefile
  A engines/twp/imgui/examples/example_glfw_opengl2/main.cpp
  A engines/twp/imgui/examples/example_glfw_opengl3/Makefile
  A engines/twp/imgui/examples/example_glfw_opengl3/Makefile.emscripten
  A engines/twp/imgui/examples/example_glfw_opengl3/main.cpp
  A engines/twp/imgui/examples/example_glfw_vulkan/CMakeLists.txt
  A engines/twp/imgui/examples/example_glfw_vulkan/main.cpp
  A engines/twp/imgui/examples/example_glut_opengl2/Makefile
  A engines/twp/imgui/examples/example_glut_opengl2/main.cpp
  A engines/twp/imgui/examples/example_null/Makefile
  A engines/twp/imgui/examples/example_null/main.cpp
  A engines/twp/imgui/examples/example_sdl2_directx11/main.cpp
  A engines/twp/imgui/examples/example_sdl2_metal/Makefile
  A engines/twp/imgui/examples/example_sdl2_metal/main.mm
  A engines/twp/imgui/examples/example_sdl2_opengl2/Makefile
  A engines/twp/imgui/examples/example_sdl2_opengl2/README.md
  A engines/twp/imgui/examples/example_sdl2_opengl2/main.cpp
  A engines/twp/imgui/examples/example_sdl2_opengl3/Makefile
  A engines/twp/imgui/examples/example_sdl2_opengl3/Makefile.emscripten
  A engines/twp/imgui/examples/example_sdl2_opengl3/README.md
  A engines/twp/imgui/examples/example_sdl2_opengl3/main.cpp
  A engines/twp/imgui/examples/example_sdl2_sdlrenderer2/Makefile
  A engines/twp/imgui/examples/example_sdl2_sdlrenderer2/README.md
  A engines/twp/imgui/examples/example_sdl2_sdlrenderer2/main.cpp
  A engines/twp/imgui/examples/example_sdl2_vulkan/main.cpp
  A engines/twp/imgui/examples/example_sdl3_opengl3/Makefile
  A engines/twp/imgui/examples/example_sdl3_opengl3/Makefile.emscripten
  A engines/twp/imgui/examples/example_sdl3_opengl3/README.md
  A engines/twp/imgui/examples/example_sdl3_opengl3/main.cpp
  A engines/twp/imgui/examples/example_sdl3_sdlrenderer3/Makefile
  A engines/twp/imgui/examples/example_sdl3_sdlrenderer3/main.cpp
  A engines/twp/imgui/examples/example_win32_directx10/main.cpp
  A engines/twp/imgui/examples/example_win32_directx11/main.cpp
  A engines/twp/imgui/examples/example_win32_directx12/main.cpp
  A engines/twp/imgui/examples/example_win32_directx9/main.cpp
  A engines/twp/imgui/examples/example_win32_opengl3/main.cpp
  A engines/twp/imgui/examples/libs/emscripten/emscripten_mainloop_stub.h
  A engines/twp/imgui/examples/libs/emscripten/shell_minimal.html
  A engines/twp/imgui/examples/libs/glfw/COPYING.txt
  A engines/twp/imgui/examples/libs/glfw/include/GLFW/glfw3.h
  A engines/twp/imgui/examples/libs/glfw/include/GLFW/glfw3native.h
  A engines/twp/imgui/examples/libs/glfw/lib-vc2010-32/glfw3.lib
  A engines/twp/imgui/examples/libs/glfw/lib-vc2010-64/glfw3.lib
  A engines/twp/imgui/examples/libs/usynergy/README.txt
  A engines/twp/imgui/examples/libs/usynergy/uSynergy.c
  A engines/twp/imgui/examples/libs/usynergy/uSynergy.h
  A engines/twp/imgui/imconfig.h
  A engines/twp/imgui/imgui.cpp
  A engines/twp/imgui/imgui.h
  A engines/twp/imgui/imgui_demo.cpp
  A engines/twp/imgui/imgui_draw.cpp
  A engines/twp/imgui/imgui_internal.h
  A engines/twp/imgui/imgui_tables.cpp
  A engines/twp/imgui/imgui_widgets.cpp
  A engines/twp/imgui/imstb_rectpack.h
  A engines/twp/imgui/imstb_textedit.h
  A engines/twp/imgui/imstb_truetype.h
  A engines/twp/imgui/misc/README.txt
  A engines/twp/imgui/misc/cpp/README.txt
  A engines/twp/imgui/misc/cpp/imgui_stdlib.cpp
  A engines/twp/imgui/misc/cpp/imgui_stdlib.h
  A engines/twp/imgui/misc/debuggers/README.txt
  A engines/twp/imgui/misc/debuggers/imgui.gdb
  A engines/twp/imgui/misc/debuggers/imgui.natstepfilter
  A engines/twp/imgui/misc/debuggers/imgui.natvis
  A engines/twp/imgui/misc/fonts/Cousine-Regular.ttf
  A engines/twp/imgui/misc/fonts/DroidSans.ttf
  A engines/twp/imgui/misc/fonts/Karla-Regular.ttf
  A engines/twp/imgui/misc/fonts/ProggyClean.ttf
  A engines/twp/imgui/misc/fonts/ProggyTiny.ttf
  A engines/twp/imgui/misc/fonts/Roboto-Medium.ttf
  A engines/twp/imgui/misc/fonts/binary_to_compressed_c.cpp
  A engines/twp/imgui/misc/freetype/README.md
  A engines/twp/imgui/misc/freetype/imgui_freetype.cpp
  A engines/twp/imgui/misc/freetype/imgui_freetype.h
  A engines/twp/imgui/misc/single_file/imgui_single_file.h
  A engines/twp/imgui_impl_opengl3_scummvm.cpp
  A engines/twp/imgui_impl_opengl3_scummvm.h
  A engines/twp/imgui_impl_sdl2_scummvm.cpp
  A engines/twp/imgui_impl_sdl2_scummvm.h
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/module.mk
    engines/twp/resmanager.h
    engines/twp/scenegraph.h
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index d4e56b7b59f..f28ebee81c9 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -203,7 +203,22 @@ void AudioSystem::setVolume(int id, float vol) {
 void AudioSystem::update(float elapsed) {
 	for (int i = 0; i < 32; i++) {
 		if (_slots[i].busy && !g_engine->_mixer->isSoundHandleActive(_slots[i].handle)) {
-			_slots[i].busy = false;
+			if(_slots[i].loopTimes > 0) {
+				_slots[i].loopTimes--;
+				Audio::SeekableAudioStream *audioStream;
+				Common::String name = _slots[i].sndDef->getName();
+				_slots[i].stream.seek(0);
+				if (name.hasSuffixIgnoreCase(".ogg")) {
+					audioStream = Audio::makeVorbisStream(&_slots[i].stream, DisposeAfterUse::NO);
+				} else if (name.hasSuffixIgnoreCase(".wav")) {
+					audioStream = Audio::makeWAVStream(&_slots[i].stream, DisposeAfterUse::NO);
+				} else {
+					error("Unexpected audio format: %s", name.c_str());
+				}
+				g_engine->_mixer->playStream(_slots[i].soundType, &_slots[i].handle, audioStream, _slots[i].id, _slots[i].volume);
+			} else {
+				_slots[i].busy = false;
+			}
 		}
 	}
 	// sound definition ID or sound ID ?
@@ -257,6 +272,8 @@ int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int
 	slot->volume = volume;
 	slot->fadeInTimeMs = fadeInTimeMs;
 	slot->total = audioStream->getLength().msecs();
+	slot->loopTimes = loopTimes;
+	slot->soundType = cat;
 	return id;
 }
 
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index 27b35114005..c800fa1681a 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -28,20 +28,15 @@
 #include "audio/mixer.h"
 #include "twp/ggpack.h"
 
+namespace Audio {
+	class SeekableAudioStream;
+}
+
 namespace Twp {
 
 class AudioChannel;
 class SoundDefinition;
 
-struct SoundId {
-    int id;
-    int objId;
-    SoundDefinition* sndDef;
-    Audio::Mixer::SoundType cat;
-    AudioChannel* chan;
-    float pan;
-};
-
 class SoundDefinition;
 class SoundStream: public Common::SeekableReadStream {
 public:
@@ -77,16 +72,18 @@ private:
 };
 
 struct AudioSlot {
-	Audio::SoundHandle handle;			// handle returned when this sound has been played
-	SoundDefinition *sndDef = nullptr;	// sound definition associated to this slot
-	SoundStream stream;					// audio stream
-	bool busy = false;					// is sound active
-	float volume = 1.f;					// actual volume for this slot
-	float fadeInTimeMs = 0.f;			// fade-in time in milliseconds
-	float fadeOutTimeMs = 0.f;			// fade-out time in milliseconds
+	Audio::SoundHandle handle;					// handle returned when this sound has been played
+	SoundDefinition *sndDef = nullptr;			// sound definition associated to this slot
+	SoundStream stream;							// audio stream
+	bool busy = false;							// is sound active
+	float volume = 1.f;							// actual volume for this slot
+	float fadeInTimeMs = 0.f;					// fade-in time in milliseconds
+	float fadeOutTimeMs = 0.f;					// fade-out time in milliseconds
 	int total = 0;
-	int id = 0;							// unique sound ID
-	int objId = 0;						// object ID or 0 if none
+	int id = 0;									// unique sound ID
+	int objId = 0;								// object ID or 0 if none
+	int loopTimes = 0;							//
+	Audio::Mixer::SoundType soundType;			//
 };
 
 class AudioSystem {
@@ -109,6 +106,7 @@ public:
 	void update(float elapsed);
 
 	Common::Array<SoundDefinition*> _soundDefs;
+	AudioSlot _slots[32];
 	SoundDefinition* _soundHover = nullptr;	// not used yet, should be used in the GUI
 
 private:
@@ -116,7 +114,6 @@ private:
 	AudioSlot* getFreeSlot();
 
 private:
-	AudioSlot _slots[32];
 	float _masterVolume = 1.f;
 };
 
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
new file mode 100644
index 00000000000..84b0d6f68b6
--- /dev/null
+++ b/engines/twp/debugtools.cpp
@@ -0,0 +1,396 @@
+#include "twp/debugtools.h"
+#include "imgui/imgui.h"
+#include "twp/twp.h"
+#include "twp/thread.h"
+#include "twp/dialog.h"
+
+namespace Twp {
+
+static struct {
+	bool _showThreads = false;
+	bool _showObjects = false;
+	bool _showStack = false;
+	bool _showAudio = false;
+	bool _showResources = false;
+	ImGuiTextFilter _objFilter;
+	int _fadeEffect = 0;
+	float _fadeDuration = 0.f;
+	bool _fadeToSepia = false;
+	Common::String _textureSelected;
+} state;
+
+ImVec4 gray(0.6f, 0.6f, 0.6f, 1.f);
+
+static void drawThreads() {
+	if (!state._showThreads)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	const auto &threads = g_engine->_threads;
+	if (ImGui::Begin("Threads", &state._showThreads)) {
+		ImGui::Text("# threads: %u", threads.size());
+		ImGui::Separator();
+
+		if (ImGui::BeginTable("Threads", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
+			ImGui::TableSetupColumn("Id");
+			ImGui::TableSetupColumn("Name");
+			ImGui::TableSetupColumn("Type");
+			ImGui::TableSetupColumn("Func");
+			ImGui::TableSetupColumn("Src");
+			ImGui::TableSetupColumn("Line");
+			ImGui::TableHeadersRow();
+
+			for (const auto &thread : threads) {
+				SQStackInfos infos;
+				sq_stackinfos(thread->getThread(), 0, &infos);
+
+				ImGui::TableNextRow();
+				ImGui::TableNextColumn();
+				ImGui::Text("%5d", thread->getId());
+				ImGui::TableNextColumn();
+				ImGui::Text("%-56s", thread->getName().c_str());
+				ImGui::TableNextColumn();
+				ImGui::Text("%-6s", thread->isGlobal() ? "global" : "local");
+				ImGui::TableNextColumn();
+				ImGui::Text("%-9s", infos.funcname);
+				ImGui::TableNextColumn();
+				ImGui::Text("%-9s", infos.source);
+				ImGui::TableNextColumn();
+				ImGui::Text("%5lld", infos.line);
+			}
+			ImGui::EndTable();
+		}
+	}
+	ImGui::End();
+}
+
+static void drawObjects() {
+	if (!state._showObjects)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Objects", &state._showObjects);
+	state._objFilter.Draw();
+
+	// show object list
+	for (auto &layer : g_engine->_room->_layers) {
+		for (auto &obj : layer->_objects) {
+			if (state._objFilter.PassFilter(obj->_key.c_str())) {
+				ImGui::PushID(obj->getId());
+				bool visible = obj->_node->isVisible();
+				if (ImGui::Checkbox("", &visible)) {
+					obj->_node->setVisible(visible);
+				}
+				ImGui::SameLine();
+				Common::String name = obj->_key == "" ? obj->getName() : Common::String::format("%s(%s) %d", obj->getName().c_str(), obj->_key.c_str(), obj->getId());
+				bool selected = false;
+				if (ImGui::Selectable(name.c_str(), &selected)) {
+					// gObject = obj;
+				}
+				ImGui::PopID();
+			}
+		}
+	}
+
+	ImGui::End();
+}
+
+static Common::String toString(Audio::Mixer::SoundType type) {
+	switch (type) {
+	case Audio::Mixer::kPlainSoundType:
+		return "sound";
+	case Audio::Mixer::kMusicSoundType:
+		return "music";
+	case Audio::Mixer::kSFXSoundType:
+		return "sfx";
+	case Audio::Mixer::kSpeechSoundType:
+		return "speech";
+	}
+	return "?";
+}
+
+static ImVec4 getCategoryColor(Audio::Mixer::SoundType type) {
+	switch (type) {
+	case Audio::Mixer::kPlainSoundType:
+		return ImVec4(0.f, 1.f, 0.f, 1.f);
+	case Audio::Mixer::kMusicSoundType:
+		return ImVec4(1.f, 0.f, 0.f, 1.f);
+	case Audio::Mixer::kSFXSoundType:
+		return ImVec4(1.f, 0.f, 1.f, 1.f);
+	case Audio::Mixer::kSpeechSoundType:
+		return ImVec4(1.f, 1.f, 0.f, 1.f);
+	}
+	return ImVec4(1.f, 1.f, 1.f, 1.f);
+}
+
+static void drawStack() {
+	if (!state._showStack)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Stack");
+	ImGui::BeginChild("ScrollingRegion");
+	SQInteger size = sq_gettop(g_engine->getVm());
+	ImGui::Text("size: %lld", size);
+	HSQOBJECT obj;
+	for (SQInteger i = 1; i < size; i++) {
+		sq_getstackobj(g_engine->getVm(), -i, &obj);
+		ImGui::Text("obj type: 0x%X", obj._type);
+	}
+	ImGui::EndChild();
+	ImGui::End();
+}
+
+static void drawResources() {
+	if (!state._showResources)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Resources", &state._showResources);
+
+	if (ImGui::BeginTable("Resources", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
+		ImGui::TableSetupColumn("Name");
+		ImGui::TableSetupColumn("Resolution");
+		ImGui::TableHeadersRow();
+
+		for (auto &res : g_engine->_resManager._textures) {
+			ImGui::TableNextRow();
+			ImGui::TableNextColumn();
+			bool selected = state._textureSelected == res._key;
+			if (ImGui::Selectable(res._key.c_str(), selected)) {
+				state._textureSelected = res._key;
+			}
+			ImGui::TableNextColumn();
+			ImGui::Text("%s", Common::String::format("%d x %d", res._value.width, res._value.height).c_str());
+		}
+
+		ImGui::EndTable();
+	}
+	ImGui::Separator();
+
+	ImVec2 cursor = ImGui::GetCursorPos();
+	ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 10.f));
+	ImGui::Text("Preview:");
+	ImGui::BeginChild("TexturePreview", ImVec2(0, 0), ImGuiChildFlags_Border|ImGuiChildFlags_ResizeX|ImGuiChildFlags_ResizeY);
+	for (auto &res : g_engine->_resManager._textures) {
+		if (state._textureSelected == res._key) {
+			ImGui::Image((ImTextureID)(intptr_t)res._value.id, ImVec2(res._value.width, res._value.height));
+			break;
+		}
+	}
+	ImGui::EndChild();
+
+	ImGui::End();
+}
+
+static void drawAudio() {
+	if (!state._showAudio)
+		return;
+
+	// count the number of active sounds
+	int count = 0;
+	for (auto &s : g_engine->_audio._slots) {
+		if (s.busy)
+			count++;
+	}
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Sounds", &state._showAudio);
+	ImGui::Text("# sounds: %d/32", count);
+	ImGui::Separator();
+
+	if (ImGui::BeginTable("Threads", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
+		ImGui::TableSetupColumn("");
+		ImGui::TableSetupColumn("Id");
+		ImGui::TableSetupColumn("Category");
+		ImGui::TableSetupColumn("Name");
+		ImGui::TableSetupColumn("Loops");
+		ImGui::TableSetupColumn("Volume");
+		ImGui::TableSetupColumn("Pan");
+		ImGui::TableHeadersRow();
+
+		for (int i = 0; i < 32; i++) {
+			auto &sound = g_engine->_audio._slots[i];
+			ImGui::TableNextRow();
+			ImGui::TableNextColumn();
+			ImGui::Text("#%d", i);
+			if (sound.busy) {
+				float pan = g_engine->_mixer->getChannelBalance(sound.handle) / 128.f;
+				float vol = g_engine->_mixer->getChannelVolume(sound.handle) / 255.f;
+				ImGui::TableNextColumn();
+				ImGui::Text("%d", sound.id);
+				ImGui::TableNextColumn();
+				ImGui::TextColored(getCategoryColor(sound.soundType), "%s", toString(sound.soundType).c_str());
+				ImGui::TableNextColumn();
+				ImGui::Text("%s", sound.sndDef->getName().c_str());
+				ImGui::TableNextColumn();
+				ImGui::Text("%d", sound.loopTimes);
+				ImGui::TableNextColumn();
+				ImGui::Text("%0.1f", vol);
+				ImGui::TableNextColumn();
+				ImGui::Text("%0.1f", pan);
+				ImGui::SameLine();
+				if (ImGui::SmallButton("STOP")) {
+					g_engine->_audio.stop(sound.id);
+				}
+			}
+		}
+
+		ImGui::EndTable();
+	}
+
+	ImGui::End();
+}
+
+static void drawGeneral() {
+	ImGuiIO &io = ImGui::GetIO();
+
+	ImGui::Begin("General");
+
+	SQInteger size = sq_gettop(g_engine->getVm());
+	ImGui::TextColored(gray, "Stack:");
+	ImGui::SameLine();
+	ImGui::Text("%lld", size);
+	ImGui::TextColored(gray, "In cutscene:");
+	ImGui::SameLine();
+	ImGui::Text("%s", g_engine->_cutscene ? "yes" : "no");
+	DialogState dialogState = g_engine->_dialog.getState();
+	ImGui::TextColored(gray, "In dialog:");
+	ImGui::SameLine();
+	ImGui::Text("%s", ((dialogState == Active) ? "yes" : (dialogState == WaitingForChoice ? "waiting for choice" : "no")));
+	ImGui::TextColored(gray, "Verb:");
+	ImGui::SameLine();
+	Common::String verb = g_engine->getTextDb().getText(g_engine->_hud._verb.text);
+	ImGui::Text("%s %d", verb.c_str(), g_engine->_hud._verb.id.id);
+
+	auto mousePos = g_engine->_cursor.pos;
+	ImGui::TextColored(gray, "Pos (screen):");
+	ImGui::SameLine();
+	ImGui::Text("(%.0f, %0.f)", mousePos.getX(), mousePos.getY());
+	if (g_engine->_room) {
+		auto pos = g_engine->screenToRoom(mousePos);
+		ImGui::TextColored(gray, "Pos (room):");
+		ImGui::SameLine();
+		ImGui::Text("(%.0f, %0.f)", pos.getX(), pos.getY());
+	}
+	ImGui::Separator();
+	ImGui::Checkbox("HUD", &g_engine->_inputState._inputHUD);
+	ImGui::SameLine();
+	ImGui::Checkbox("Input", &g_engine->_inputState._inputActive);
+	ImGui::SameLine();
+	ImGui::Checkbox("Cursor", &g_engine->_inputState._showCursor);
+	ImGui::SameLine();
+	ImGui::Checkbox("Verbs", &g_engine->_inputState._inputVerbsActive);
+	ImGui::Separator();
+
+	// Camera
+	if (ImGui::CollapsingHeader("Camera")) {
+		ImGui::TextColored(gray, "follow:");
+		ImGui::SameLine();
+		ImGui::Text("%s", !g_engine->_followActor ? "(none)" : g_engine->_followActor->_key.c_str());
+		ImGui::TextColored(gray, "moving:");
+		ImGui::SameLine();
+		ImGui::Text("%s", g_engine->_camera.isMoving() ? "yes" : "no");
+		auto halfScreenSize = g_engine->_room->getScreenSize() / 2.0f;
+		auto camPos = g_engine->cameraPos() - halfScreenSize;
+		if (ImGui::DragFloat2("Camera pos", camPos.getData())) {
+			g_engine->follow(nullptr);
+			g_engine->cameraAt(camPos);
+		}
+		auto bounds = g_engine->_camera.getBounds();
+		if (ImGui::DragFloat4("Bounds", bounds.v)) {
+			g_engine->_camera.setBounds(bounds);
+		}
+	}
+
+	// Room
+	Room *room = g_engine->_room;
+	if (room) {
+		if (ImGui::CollapsingHeader("Room")) {
+			ImGui::TextColored(gray, "Sheet:");
+			ImGui::SameLine();
+			ImGui::Text("%s", room->_sheet.c_str());
+			ImGui::TextColored(gray, "Size:");
+			ImGui::SameLine();
+			ImGui::Text("%0.f x %0.f", room->_roomSize.getX(), room->_roomSize.getY());
+			ImGui::TextColored(gray, "Fullscreen:");
+			ImGui::SameLine();
+			ImGui::Text("%d", room->_fullscreen);
+			ImGui::TextColored(gray, "Height:");
+			ImGui::SameLine();
+			ImGui::Text("%d", room->_height);
+			Color overlay = room->_overlayNode.getOverlayColor();
+			if (ImGui::ColorEdit4("Overlay", overlay.v))
+				room->_overlayNode.setOverlayColor(overlay);
+			ImGui::ColorEdit4("Ambient Light", room->_lights._ambientLight.v);
+			for (int i = 0; i < room->_lights._numLights; ++i) {
+				Common::String ss = Common::String::format("Light %d", i + 1);
+				if (ImGui::TreeNode(ss.c_str())) {
+					auto &light = room->_lights._lights[i];
+					ImGui::DragFloat2("Position", light.pos.getData());
+					ImGui::ColorEdit4("Color", light.color.v);
+					ImGui::DragFloat("Direction angle", &light.coneDirection, 1.0f, 0.0f, 360.f);
+					ImGui::DragFloat("Angle", &light.coneAngle, 1.0f, 0.0f, 360.f);
+					ImGui::DragFloat("Cutoff", &light.cutOffRadius, 1.0f);
+					ImGui::DragFloat("Falloff", &light.coneFalloff, 0.1f, 0.f, 1.0f);
+					ImGui::DragFloat("Brightness", &light.brightness, 1.0f, 1.0f, 100.f);
+					ImGui::DragFloat("Half Radius", &light.halfRadius, 1.0f, 0.01f, 0.99f);
+					ImGui::TreePop();
+				}
+			}
+		}
+	}
+	ImGui::Separator();
+
+	// Windows
+	if (ImGui::CollapsingHeader("Windows")) {
+		ImGui::Checkbox("Threads", &state._showThreads);
+		ImGui::Checkbox("Objects", &state._showObjects);
+		ImGui::Checkbox("Stack", &state._showStack);
+		ImGui::Checkbox("Audio", &state._showAudio);
+		ImGui::Checkbox("Resources", &state._showResources);
+	}
+	ImGui::Separator();
+
+	// Room shader
+	const char *RoomEffects = "None\0Sepia\0EGA\0VHS\0Ghost\0Black & White\0\0";
+	if (ImGui::CollapsingHeader("Room Shader")) {
+		int effect = static_cast<int>(room->_effect);
+		if (ImGui::Combo("effect", &effect, RoomEffects))
+			room->_effect = (RoomEffect)effect;
+		ImGui::DragFloat("iFade", &g_engine->_shaderParams.iFade, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat("wobbleIntensity", &g_engine->_shaderParams.wobbleIntensity, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat3("shadows", g_engine->_shaderParams.shadows.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("midtones", g_engine->_shaderParams.midtones.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("highlights", g_engine->_shaderParams.highlights.v, 0.01f, -1.f, 1.f);
+	}
+
+	// Fade Effects
+	const char *FadeEffects = "None\0In\0Out\0Wobble\0\0";
+	if (ImGui::CollapsingHeader("Fade Shader")) {
+		ImGui::Separator();
+		ImGui::Combo("Fade effect", &state._fadeEffect, FadeEffects);
+		ImGui::DragFloat("Duration", &state._fadeDuration, 0.1f, 0.f, 10.f);
+		ImGui::Checkbox("Fade to sepia", &state._fadeToSepia);
+		ImGui::Text("Elapsed %f", g_engine->_fadeShader->_elapsed);
+		ImGui::Text("Fade %f", g_engine->_fadeShader->_fade);
+		if (ImGui::Button("GO")) {
+			g_engine->fadeTo((FadeEffect)state._fadeEffect, state._fadeDuration, state._fadeToSepia);
+		}
+	}
+	ImGui::Separator();
+
+	ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
+	ImGui::End();
+}
+
+void onImGuiRender() {
+	drawGeneral();
+	drawThreads();
+	drawObjects();
+	drawStack();
+	drawAudio();
+	drawResources();
+}
+
+} // namespace Twp
diff --git a/engines/twp/debugtools.h b/engines/twp/debugtools.h
new file mode 100644
index 00000000000..1a79a77e3d6
--- /dev/null
+++ b/engines/twp/debugtools.h
@@ -0,0 +1,29 @@
+/* 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 TWP_DEBUGTOOLS_H
+#define TWP_DEBUGTOOLS_H
+
+namespace Twp {
+	void onImGuiRender();
+}
+
+#endif
diff --git a/engines/twp/imgui/.editorconfig b/engines/twp/imgui/.editorconfig
new file mode 100644
index 00000000000..5adfefa20d8
--- /dev/null
+++ b/engines/twp/imgui/.editorconfig
@@ -0,0 +1,28 @@
+# See http://editorconfig.org to read about the EditorConfig format.
+# - In theory automatically supported by VS2017+ and most common IDE or text editors.
+# - In practice VS2019-VS2022 stills don't trim trailing whitespaces correctly :(
+#   - Suggest installing this to trim whitespaces:
+#      GitHub https://github.com/madskristensen/TrailingWhitespace
+#      VS2019 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer
+#      VS2022 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespace64
+#     (in spite of its name doesn't only visualize but also trims)
+#   - Alternative for older VS2010 to VS2015: https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# Use 4 spaces as indentation
+[*]
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[imstb_*]
+indent_size = 3
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
+indent_size = 4
diff --git a/engines/twp/imgui/.gitattributes b/engines/twp/imgui/.gitattributes
new file mode 100644
index 00000000000..d48470eeff1
--- /dev/null
+++ b/engines/twp/imgui/.gitattributes
@@ -0,0 +1,30 @@
+* text=auto
+
+*.c text
+*.cpp text
+*.h text
+*.m text
+*.mm text
+*.md text
+*.txt text
+*.html text
+*.bat text
+*.frag text
+*.vert text
+*.mkb text
+*.icf text
+
+*.sln text eol=crlf
+*.vcxproj text eol=crlf
+*.vcxproj.filters text eol=crlf
+*.natvis text eol=crlf
+
+Makefile text eol=lf
+*.sh text eol=lf
+*.pbxproj text eol=lf
+*.storyboard text eol=lf
+*.plist text eol=lf
+
+*.png binary
+*.ttf binary
+*.lib binary
diff --git a/engines/twp/imgui/.github/FUNDING.yml b/engines/twp/imgui/.github/FUNDING.yml
new file mode 100644
index 00000000000..2aa08b4474c
--- /dev/null
+++ b/engines/twp/imgui/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: ['https://github.com/ocornut/imgui/wiki/Sponsors']
diff --git a/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml b/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..3ba13e0cec6
--- /dev/null
+++ b/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml b/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
new file mode 100644
index 00000000000..792d8f63ed4
--- /dev/null
+++ b/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
@@ -0,0 +1,90 @@
+name: "Ask a question, report a bug, request a feature, etc."
+description: "Ask any question, discuss best practices, report a bug, request a feature."
+body:
+  - type: markdown
+    attributes:
+      value: |
+        FOR FIRST-TIME USERS ISSUES COMPILING/LINKING/RUNNING or LOADING FONTS, please use [GitHub Discussions](https://github.com/ocornut/imgui/discussions)
+        For anything else: we are happy to use 'GitHub Issues' for many types of open-ended questions. We are encouraging 'Issues' becoming a large, centralized and cross-referenced database of Dear ImGui contents.
+
+        Be mindful that messages are being sent to the e-mail box of "Watching" users. Try to proof-read your messages before sending them. Edits are not seen by those users.
+  - type: markdown
+    attributes:
+      value: |
+        **Prerequisites:**
+        - I have read [Frequently Asked Questions](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md).
+        - I have read [Contributing Guidelines -> General Advices](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#getting-started--general-advice).
+        - I have read [Contributing Guidelines -> How to open an Issue](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#how-to-open-an-issue).
+        - I have searched [Github Issues and PR](https://github.com/ocornut/imgui/issues?q=) for discussion of similar topics.
+
+        ----
+  - type: input
+    id: specs_version
+    attributes:
+      label: "Version/Branch of Dear ImGui:"
+      description: "(please specify if you have made substantial modifications to your copy)"
+      value: "Version 1.XX, Branch: XXX (master/docking/etc.)"
+      placeholder: "Version 1.XX, Branch: XXX (master/docking/etc.)"
+    validations:
+      required: true
+  - type: input
+    id: specs_backend
+    attributes:
+      label: "Back-ends:"
+      description: (or specify when using custom engine/back-ends)
+      value: "imgui_impl_XXX.cpp + imgui_impl_XXX.cpp"
+      placeholder: "imgui_impl_XXX.cpp  + imgui_impl_XXX.cpp or n/a"
+    validations:
+      required: true
+  - type: input
+    id: specs_compiler_os
+    attributes:
+      label: "Compiler, OS:"
+      placeholder: "e.g. Windows 11 + MSVC 2022, macOS + Clang 12, Linux + GCC etc."
+    validations:
+      required: true
+  - type: textarea
+    id: specs_full
+    attributes:
+      label: "Full config/build information:"
+      placeholder: |
+        (If you can run, you may go to 'Demo->Tools->About Dear ImGui->Config/Build Info' to obtain detailed information that you can paste here)
+    validations:
+      required: false
+  - type: textarea
+    id: issue_description
+    attributes:
+      label: "Details:"
+      description: "Try to be explicit with your goals, your expectations and what you have tried. Be mindful of [The XY Problem](https://xyproblem.info). What you have in mind or in your code is not obvious to other people. People frequently discuss problems and suggest incorrect solutions without first clarifying their goals. When requesting a new feature, please describe the usage context (how you intend to use it, why you need it, etc.). If you tried something and it failed, show us what you tried. If you are reporting a bug, explain what's the bug, how does it occur, etc. If you are reporting a crash, please include a debugger callstack."
+      value: |
+        **My Issue/Question:**
+
+        XXX _(please provide as much context as possible)_
+    validations:
+      required: true
+  - type: textarea
+    id: screenshots
+    attributes:
+      label: "Screenshots/Video:"
+      description: "Attach screenshots or gif/videos to clarify the context. They often convey useful information that is omitted by the description."
+      placeholder: "(You can drag files here)"
+    validations:
+      required: false
+  - type: textarea
+    id: repro_code
+    attributes:
+      label: "Minimal, Complete and Verifiable Example code:"
+      description: "Provide an [MCVE](https://stackoverflow.com/help/mcve) to demonstrate your problem. An ideal submission includes a small piece of code that anyone can paste into one of the examples applications (examples/*/main.cpp) or the demo (imgui_demo.cpp) to understand and reproduce it. Narrowing your problem to its shortest and purest form is the easiest way to understand it, explain it and fix it. Please test your shortened code to ensure it exhibits the problem. Often while creating the MCVE you will solve the problem! Many questions that are missing a standalone verifiable example are missing the actual cause of their issue in the description, which ends up wasting everyone's time."
+      value: |
+        ```cpp
+        // Here's some code anyone can copy and paste to reproduce your issue
+        ImGui::Begin("Example Bug");
+        MoreCodeToExplainMyIssue();
+        ImGui::End();
+        ```
+    validations:
+      required: false
+  - type: markdown
+    attributes:
+      value: |
+        Thank you for taking the time to read prerequisites, filling this template and double-checking your message and your code!
diff --git a/engines/twp/imgui/.github/pull_request_template.md b/engines/twp/imgui/.github/pull_request_template.md
new file mode 100644
index 00000000000..638545bd6d3
--- /dev/null
+++ b/engines/twp/imgui/.github/pull_request_template.md
@@ -0,0 +1,6 @@
+(Click "Preview" to turn any http URL into a clickable link)
+
+1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md)
+
+2. Clear this template before submitting your PR.
+
diff --git a/engines/twp/imgui/.github/workflows/build.yml b/engines/twp/imgui/.github/workflows/build.yml
new file mode 100644
index 00000000000..45688c47034
--- /dev/null
+++ b/engines/twp/imgui/.github/workflows/build.yml
@@ -0,0 +1,507 @@
+name: build
+
+on:
+  push:
+  pull_request:
+  workflow_run:
+    # Use a workflow as a trigger of scheduled builds. Forked repositories can disable scheduled builds by disabling
+    # "scheduled" workflow, while maintaining ability to perform local CI builds.
+    workflows:
+      - scheduled
+    branches:
+      - master
+      - docking
+    types:
+      - requested
+
+jobs:
+  Windows:
+    runs-on: windows-2019
+    env:
+      VS_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\
+      MSBUILD_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\
+    steps:
+      - uses: actions/checkout at v3
+
+      - name: Install Dependencies
+        shell: powershell
+        run: |
+          Invoke-WebRequest -Uri "https://www.libsdl.org/release/SDL2-devel-2.26.3-VC.zip" -OutFile "SDL2-devel-2.26.3-VC.zip"
+          Expand-Archive -Path SDL2-devel-2.26.3-VC.zip
+          echo "SDL2_DIR=$(pwd)\SDL2-devel-2.26.3-VC\SDL2-2.26.3\" >>${env:GITHUB_ENV}
+
+          Invoke-WebRequest -Uri "https://github.com/ocornut/imgui/files/3789205/vulkan-sdk-1.1.121.2.zip" -OutFile vulkan-sdk-1.1.121.2.zip
+          Expand-Archive -Path vulkan-sdk-1.1.121.2.zip
+          echo "VULKAN_SDK=$(pwd)\vulkan-sdk-1.1.121.2\" >>${env:GITHUB_ENV}
+
+      - name: Fix Projects
+        shell: powershell
+        run: |
+          # CI workers do not supporter older Visual Studio versions. Fix projects to target newer available version.
+          gci -recurse -filter "*.vcxproj" | ForEach-Object {
+            (Get-Content $_.FullName) -Replace "<PlatformToolset>v\d{3}</PlatformToolset>","<PlatformToolset>v142</PlatformToolset>" | Set-Content -Path $_.FullName
+            (Get-Content $_.FullName) -Replace "<WindowsTargetPlatformVersion>[\d\.]+</WindowsTargetPlatformVersion>","<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>" | Set-Content -Path $_.FullName
+          }
+
+      # Not using matrix here because it would inflate job count too much. Check out and setup is done for every job and that makes build times way too long.
+      - name: Build example_null (extra warnings, mingw 64-bit)
+        run: mingw32-make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+      - name: Build example_null (mingw 64-bit, as DLL)
+        shell: bash
+        run: |
+          echo '#ifdef _EXPORT'                                  >  example_single_file.cpp
+          echo '#  define IMGUI_API __declspec(dllexport)'       >> example_single_file.cpp
+          echo '#else'                                           >> example_single_file.cpp
+          echo '#  define IMGUI_API __declspec(dllimport)'       >> example_single_file.cpp
+          echo '#endif'                                          >> example_single_file.cpp
+          echo '#define IMGUI_IMPLEMENTATION'                    >> example_single_file.cpp
+          echo '#include "misc/single_file/imgui_single_file.h"' >> example_single_file.cpp
+          g++ -I. -Wall -Wformat -D_EXPORT -shared -o libimgui.dll -Wl,--out-implib,libimgui.a example_single_file.cpp -limm32
+          g++ -I. -Wall -Wformat -o example_null.exe examples/example_null/main.cpp -L. -limgui
+          rm -f example_null.exe libimgui.* example_single_file.*
+
+      - name: Build example_null (extra warnings, msvc 64-bit)
+        shell: cmd
+        run: |
+          cd examples\example_null
+          call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat"
+          .\build_win32.bat /W4
+
+      - name: Build example_null (single file build)
+        shell: bash
+        run: |
+          cat > example_single_file.cpp <<'EOF'
+
+          #define IMGUI_IMPLEMENTATION
+          #include "misc/single_file/imgui_single_file.h"
+          #include "examples/example_null/main.cpp"
+
+          EOF
+          g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32
+
+      - name: Build example_null (with IMGUI_DISABLE_WIN32_FUNCTIONS)
+        shell: bash
+        run: |
+          cat > example_single_file.cpp <<'EOF'
+
+          #define IMGUI_DISABLE_WIN32_FUNCTIONS
+          #define IMGUI_IMPLEMENTATION
+          #include "misc/single_file/imgui_single_file.h"
+          #include "examples/example_null/main.cpp"
+
+          EOF
+          g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32
+
+      - name: Build example_null (as DLL)
+        shell: cmd
+        run: |
+          call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat"
+
+          echo #ifdef _EXPORT                                  >  example_single_file.cpp
+          echo #  define IMGUI_API __declspec(dllexport)       >> example_single_file.cpp
+          echo #else                                           >> example_single_file.cpp
+          echo #  define IMGUI_API __declspec(dllimport)       >> example_single_file.cpp
+          echo #endif                                          >> example_single_file.cpp
+          echo #define IMGUI_IMPLEMENTATION                    >> example_single_file.cpp
+          echo #include "misc/single_file/imgui_single_file.h" >> example_single_file.cpp
+
+          cl.exe /D_USRDLL /D_WINDLL /D_EXPORT /I. example_single_file.cpp /LD /FeImGui.dll /link
+          cl.exe /I. ImGui.lib /Feexample_null.exe examples/example_null/main.cpp
+
+      - name: Build Win32 example_glfw_opengl2
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+
+      - name: Build Win32 example_glfw_opengl3
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build Win32 example_glfw_vulkan
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build Win32 example_sdl2_vulkan
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build Win32 example_sdl2_opengl2
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build Win32 example_sdl2_opengl3
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+
+      - name: Build Win32 example_sdl2_directx11
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build Win32 example_win32_directx9
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx9/example_win32_directx9.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+
+      - name: Build Win32 example_win32_directx10
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx10/example_win32_directx10.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+
+      - name: Build Win32 example_win32_directx11
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_glfw_opengl2
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_glfw_opengl3
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release'
+
+      - name: Build x64 example_glfw_vulkan
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj /p:Platform=x64 /p:Configuration=Release'
+
+      - name: Build x64 example_sdl2_vulkan
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_sdl2_opengl2
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_sdl2_opengl3
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_sdl2_directx11
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release'
+
+      - name: Build x64 example_win32_directx9
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx9/example_win32_directx9.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_win32_directx10
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx10/example_win32_directx10.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_win32_directx11
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release'
+        if: github.event_name == 'workflow_run'
+
+      - name: Build x64 example_win32_directx12
+        shell: cmd
+        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx12/example_win32_directx12.vcxproj /p:Platform=x64 /p:Configuration=Release'
+
+  Linux:
+    runs-on: ubuntu-22.04
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Install Dependencies
+      run: |
+        sudo apt-get update
+        sudo apt-get install -y libglfw3-dev libsdl2-dev gcc-multilib g++-multilib libfreetype6-dev libvulkan-dev
+
+    - name: Build example_null (extra warnings, gcc 32-bit)
+      run: |
+        make -C examples/example_null clean
+        CXXFLAGS="$CXXFLAGS -m32 -Werror" make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+    - name: Build example_null (extra warnings, gcc 64-bit)
+      run: |
+        make -C examples/example_null clean
+        CXXFLAGS="$CXXFLAGS -m64 -Werror" make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+    - name: Build example_null (extra warnings, clang 32-bit)
+      run: |
+        make -C examples/example_null clean
+        CXXFLAGS="$CXXFLAGS -m32 -Werror" CXX=clang++ make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+    - name: Build example_null (extra warnings, clang 64-bit)
+      run: |
+        make -C examples/example_null clean
+        CXXFLAGS="$CXXFLAGS -m64 -Werror" CXX=clang++ make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+    - name: Build example_null (extra warnings, empty IM_ASSERT)
+      run: |
+          cat > example_single_file.cpp <<'EOF'
+
+          #define IM_ASSERT(x)
+          #define IMGUI_IMPLEMENTATION
+          #include "misc/single_file/imgui_single_file.h"
+          #include "examples/example_null/main.cpp"
+
+          EOF
+          g++ -I. -std=c++11 -Wall -Wformat -Wextra -Werror -Wno-zero-as-null-pointer-constant -Wno-double-promotion -Wno-variadic-macros -Wno-empty-body -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (freetype)
+      run: |
+        make -C examples/example_null clean
+        make -C examples/example_null WITH_FREETYPE=1
+
+    - name: Build example_null (single file build)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with ImWchar32)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_USE_WCHAR32
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with large ImDrawIdx + pointer ImTextureID)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define ImTextureID void*
+        #define ImDrawIdx unsigned int
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IMGUI_DISABLE_OBSOLETE_KEYIO)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_DISABLE_OBSOLETE_KEYIO
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IMGUI_DISABLE_DEMO_WINDOWS and IMGUI_DISABLE_DEBUG_TOOLS)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_DISABLE_DEMO_WINDOWS
+        #define IMGUI_DISABLE_DEBUG_TOOLS
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IMGUI_DISABLE_FILE_FUNCTIONS)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_DISABLE_FILE_FUNCTIONS
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IMGUI_USE_BGRA_PACKED_COLOR)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_USE_BGRA_PACKED_COLOR
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (with IM_VEC2_CLASS_EXTRA and IM_VEC4_CLASS_EXTRA)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        struct MyVec2 { float x; float y; MyVec2(float x, float y) : x(x), y(y) { } };
+        struct MyVec4 { float x; float y; float z; float w;
+        MyVec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } };
+        #define IM_VEC2_CLASS_EXTRA                                             \
+                ImVec2(const MyVec2& f) { x = f.x; y = f.y; }                   \
+                operator MyVec2() const { return MyVec2(x, y); }
+        #define IM_VEC4_CLASS_EXTRA                                             \
+                ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \
+                operator MyVec4() const { return MyVec4(x, y, z, w); }
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (without c++ runtime, Clang)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_IMPLEMENTATION
+        #define IMGUI_DISABLE_DEMO_WINDOWS
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        clang++ -I. -std=c++11 -Wall -Wformat -nodefaultlibs -fno-rtti -fno-exceptions -fno-threadsafe-statics -lc -lm -o example_single_file example_single_file.cpp
+
+    - name: Build example_glfw_opengl2
+      run: make -C examples/example_glfw_opengl2
+
+    - name: Build example_glfw_opengl3
+      run: make -C examples/example_glfw_opengl3
+      if: github.event_name == 'workflow_run'
+
+    - name: Build example_sdl2_opengl2
+      run: make -C examples/example_sdl2_opengl2
+      if: github.event_name == 'workflow_run'
+
+    - name: Build example_sdl2_opengl3
+      run: make -C examples/example_sdl2_opengl3
+
+    - name: Build with IMGUI_IMPL_VULKAN_NO_PROTOTYPES
+      run: g++ -c -I. -std=c++11 -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp
+
+  MacOS:
+    runs-on: macos-latest
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Install Dependencies
+      run: |
+        brew install glfw3 sdl2
+
+    - name: Build example_null (extra warnings, clang 64-bit)
+      run: make -C examples/example_null WITH_EXTRA_WARNINGS=1
+
+    - name: Build example_null (single file build)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        clang++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
+
+    - name: Build example_null (without c++ runtime)
+      run: |
+        cat > example_single_file.cpp <<'EOF'
+
+        #define IMGUI_IMPLEMENTATION
+        #include "misc/single_file/imgui_single_file.h"
+        #include "examples/example_null/main.cpp"
+
+        EOF
+        clang++ -I. -std=c++11 -Wall -Wformat -nodefaultlibs -fno-rtti -fno-exceptions -fno-threadsafe-statics -lc -lm -o example_single_file example_single_file.cpp
+
+    - name: Build example_glfw_opengl2
+      run: make -C examples/example_glfw_opengl2
+
+    - name: Build example_glfw_opengl3
+      run: make -C examples/example_glfw_opengl3
+      if: github.event_name == 'workflow_run'
+
+    - name: Build example_glfw_metal
+      run: make -C examples/example_glfw_metal
+
+    - name: Build example_sdl2_metal
+      run: make -C examples/example_sdl2_metal
+
+    - name: Build example_sdl2_opengl2
+      run: make -C examples/example_sdl2_opengl2
+      if: github.event_name == 'workflow_run'
+
+    - name: Build example_sdl2_opengl3
+      run: make -C examples/example_sdl2_opengl3
+
+    - name: Build example_apple_metal
+      run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos
+
+    - name: Build example_apple_opengl2
+      run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2
+
+  iOS:
+    runs-on: macos-latest
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Build example_apple_metal
+      run: |
+        # Code signing is required, but we disable it because it is irrelevant for CI builds.
+        xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_ios CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
+
+  Emscripten:
+    runs-on: ubuntu-22.04
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Install Dependencies
+      run: |
+        wget -q https://github.com/emscripten-core/emsdk/archive/master.tar.gz
+        tar -xvf master.tar.gz
+        emsdk-master/emsdk update
+        emsdk-master/emsdk install latest
+        emsdk-master/emsdk activate latest
+
+    - name: Build example_sdl2_opengl3 with Emscripten
+      run: |
+        pushd emsdk-master
+        source ./emsdk_env.sh
+        popd
+        make -C examples/example_sdl2_opengl3 -f Makefile.emscripten
+
+    - name: Build example_emscripten_wgpu
+      run: |
+        pushd emsdk-master
+        source ./emsdk_env.sh
+        popd
+        make -C examples/example_emscripten_wgpu
+
+  Android:
+    runs-on: ubuntu-22.04
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Build example_android_opengl3
+      run: |
+        cd examples/example_android_opengl3/android
+        gradle assembleDebug --stacktrace
diff --git a/engines/twp/imgui/.github/workflows/scheduled.yml b/engines/twp/imgui/.github/workflows/scheduled.yml
new file mode 100644
index 00000000000..2a08578fb65
--- /dev/null
+++ b/engines/twp/imgui/.github/workflows/scheduled.yml
@@ -0,0 +1,15 @@
+#
+# This is a dummy workflow used to trigger scheduled builds. Forked repositories most likely should disable this
+# workflow to avoid daily builds of inactive repositories.
+#
+name: scheduled
+
+on:
+  schedule:
+    - cron:  '0 9 * * *'
+
+jobs:
+  scheduled:
+    runs-on: ubuntu-latest
+    steps:
+      - run: exit 0
diff --git a/engines/twp/imgui/.github/workflows/static-analysis.yml b/engines/twp/imgui/.github/workflows/static-analysis.yml
new file mode 100644
index 00000000000..caa9b3a4e44
--- /dev/null
+++ b/engines/twp/imgui/.github/workflows/static-analysis.yml
@@ -0,0 +1,46 @@
+name: static-analysis
+
+on:
+  workflow_run:
+    # Perform static analysis together with build workflow. Build triggers of "build" workflow do not need to be repeated here.
+    workflows:
+      - build
+    types:
+      - requested
+
+jobs:
+  PVS-Studio:
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout at v3
+        with:
+          fetch-depth: 1
+
+      - name: Install Dependencies
+        env:
+          # The Secret variable setup in GitHub must be in format: "name_or_email key", on a single line
+          PVS_STUDIO_LICENSE: ${{ secrets.PVS_STUDIO_LICENSE }}
+        run: |
+          if [[ "$PVS_STUDIO_LICENSE" != "" ]];
+          then
+            wget -q https://files.viva64.com/etc/pubkey.txt
+            sudo apt-key add pubkey.txt
+            sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list
+            sudo apt-get update
+            sudo apt-get install -y pvs-studio
+            pvs-studio-analyzer credentials -o pvs-studio.lic $PVS_STUDIO_LICENSE
+          fi
+
+      - name: PVS-Studio static analysis
+        run: |
+          if [[ ! -f pvs-studio.lic ]];
+          then
+            echo "PVS Studio license is missing. No analysis will be performed."
+            echo "If you have a PVS Studio license please create a project secret named PVS_STUDIO_LICENSE with your license."
+            echo "You may use a free license. More information at https://www.viva64.com/en/b/0457/"
+            exit 0
+          fi
+          cd examples/example_null
+          pvs-studio-analyzer trace -- make WITH_EXTRA_WARNINGS=1
+          pvs-studio-analyzer analyze -e ../../imstb_rectpack.h -e ../../imstb_textedit.h -e ../../imstb_truetype.h -l ../../pvs-studio.lic -o pvs-studio.log
+          plog-converter -a 'GA:1,2;OP:1' -d V1071 -t errorfile -w pvs-studio.log
diff --git a/engines/twp/imgui/.gitignore b/engines/twp/imgui/.gitignore
new file mode 100644
index 00000000000..211d21dda88
--- /dev/null
+++ b/engines/twp/imgui/.gitignore
@@ -0,0 +1,59 @@
+## OSX artifacts
+.DS_Store
+
+## Dear ImGui artifacts
+imgui.ini
+
+## General build artifacts
+*.o
+*.obj
+*.exe
+examples/*/Debug/*
+examples/*/Release/*
+examples/*/x64/*
+
+## Visual Studio artifacts
+.vs
+ipch
+*.opensdf
+*.log
+*.pdb
+*.ilk
+*.user
+*.sdf
+*.suo
+*.VC.db
+*.VC.VC.opendb
+
+## Getting files created in JSON/Schemas/Catalog/ from a VS2022 update
+JSON/
+
+## Commonly used CMake directories
+build*/
+
+## Xcode artifacts
+project.xcworkspace
+xcuserdata
+
+## Emscripten artifacts
+examples/*.o.tmp
+examples/*.out.js
+examples/*.out.wasm
+examples/example_glfw_opengl3/web/*
+examples/example_sdl2_opengl3/web/*
+examples/example_emscripten_wgpu/web/*
+
+## JetBrains IDE artifacts
+.idea
+cmake-build-*
+
+## Unix executables from our example Makefiles
+examples/example_glfw_metal/example_glfw_metal
+examples/example_glfw_opengl2/example_glfw_opengl2
+examples/example_glfw_opengl3/example_glfw_opengl3
+examples/example_glut_opengl2/example_glut_opengl2
+examples/example_null/example_null
+examples/example_sdl2_metal/example_sdl2_metal
+examples/example_sdl2_opengl2/example_sdl2_opengl2
+examples/example_sdl2_opengl3/example_sdl2_opengl3
+examples/example_sdl2_sdlrenderer/example_sdl2_sdlrenderer
diff --git a/engines/twp/imgui/LICENSE.txt b/engines/twp/imgui/LICENSE.txt
new file mode 100644
index 00000000000..3282f5b5b10
--- /dev/null
+++ b/engines/twp/imgui/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2024 Omar Cornut
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/engines/twp/imgui/backends/imgui_impl_allegro5.cpp b/engines/twp/imgui/backends/imgui_impl_allegro5.cpp
new file mode 100644
index 00000000000..abb6b9ad0ba
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_allegro5.cpp
@@ -0,0 +1,613 @@
+// dear imgui: Renderer + Platform Backend for Allegro 5
+// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Clipboard support (from Allegro 5.1.12)
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+// Issues:
+//  [ ] Renderer: The renderer is suboptimal as we need to convert vertices manually.
+//  [ ] Platform: Missing gamepad support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-11-30: Renderer: Restoring using al_draw_indexed_prim() when Allegro version is >= 5.2.5.
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-17: Inputs: always calling io.AddKeyModsEvent() next and before key event (not in NewFrame) to fix input queue with very low framerates.
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2021-12-08: Renderer: Fixed mishandling of the the ImDrawCmd::IdxOffset field! This is an old bug but it never had an effect until some internal rendering changes in 1.86.
+//  2021-08-17: Calling io.AddFocusEvent() on ALLEGRO_EVENT_DISPLAY_SWITCH_OUT/ALLEGRO_EVENT_DISPLAY_SWITCH_IN events.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-05-19: Renderer: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-02-18: Change blending equation to preserve alpha in output buffer.
+//  2020-08-10: Inputs: Fixed horizontal mouse wheel direction.
+//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
+//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
+//  2019-05-11: Inputs: Don't filter character value from ALLEGRO_EVENT_KEY_CHAR before calling AddInputCharacter().
+//  2019-04-30: Renderer: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2018-11-30: Platform: Added touchscreen support.
+//  2018-11-30: Misc: Setting up io.BackendPlatformName/io.BackendRendererName so they can be displayed in the About Window.
+//  2018-06-13: Platform: Added clipboard support (from Allegro 5.1.12).
+//  2018-06-13: Renderer: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-06-13: Renderer: Stopped using al_draw_indexed_prim() as it is buggy in Allegro's DX9 backend.
+//  2018-06-13: Renderer: Backup/restore transform and clipping rectangle.
+//  2018-06-11: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
+//  2018-04-18: Misc: Renamed file from imgui_impl_a5.cpp to imgui_impl_allegro5.cpp.
+//  2018-04-18: Misc: Added support for 32-bit vertex indices to avoid conversion at runtime. Added imconfig_allegro5.h to enforce 32-bit indices when included from imgui.h.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplAllegro5_RenderDrawData() in the .h file so you can call it yourself.
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_allegro5.h"
+#include <stdint.h>     // uint64_t
+#include <cstring>      // memcpy
+
+// Allegro
+#include <allegro5/allegro.h>
+#include <allegro5/allegro_primitives.h>
+#ifdef _WIN32
+#include <allegro5/allegro_windows.h>
+#endif
+#define ALLEGRO_HAS_CLIPBOARD           (ALLEGRO_VERSION_INT >= ((5 << 24) | (1 << 16) | (12 << 8))) // Clipboard only supported from Allegro 5.1.12
+#define ALLEGRO_HAS_DRAW_INDEXED_PRIM   (ALLEGRO_VERSION_INT >= ((5 << 24) | (2 << 16) | ( 5 << 8))) // DX9 implementation of al_draw_indexed_prim() got fixed in Allegro 5.2.5
+
+// Visual Studio warnings
+#ifdef _MSC_VER
+#pragma warning (disable: 4127) // condition expression is constant
+#endif
+
+struct ImDrawVertAllegro
+{
+    ImVec2          pos;
+    ImVec2          uv;
+    ALLEGRO_COLOR   col;
+};
+
+// FIXME-OPT: Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 float as well..
+// FIXME-OPT: Consider inlining al_map_rgba()?
+// see https://github.com/liballeg/allegro5/blob/master/src/pixels.c#L554
+// and https://github.com/liballeg/allegro5/blob/master/include/allegro5/internal/aintern_pixels.h
+#define DRAW_VERT_IMGUI_TO_ALLEGRO(DST, SRC)  { (DST)->pos = (SRC)->pos; (DST)->uv = (SRC)->uv; unsigned char* c = (unsigned char*)&(SRC)->col; (DST)->col = al_map_rgba(c[0], c[1], c[2], c[3]); }
+
+// Allegro Data
+struct ImGui_ImplAllegro5_Data
+{
+    ALLEGRO_DISPLAY*            Display;
+    ALLEGRO_BITMAP*             Texture;
+    double                      Time;
+    ALLEGRO_MOUSE_CURSOR*       MouseCursorInvisible;
+    ALLEGRO_VERTEX_DECL*        VertexDecl;
+    char*                       ClipboardTextData;
+
+    ImVector<ImDrawVertAllegro> BufVertices;
+    ImVector<int>               BufIndices;
+
+    ImGui_ImplAllegro5_Data()   { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
+static ImGui_ImplAllegro5_Data* ImGui_ImplAllegro5_GetBackendData()     { return ImGui::GetCurrentContext() ? (ImGui_ImplAllegro5_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; }
+
+static void ImGui_ImplAllegro5_SetupRenderState(ImDrawData* draw_data)
+{
+    // Setup blending
+    al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
+
+    // Setup orthographic projection matrix
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
+    {
+        float L = draw_data->DisplayPos.x;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+        float T = draw_data->DisplayPos.y;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+        ALLEGRO_TRANSFORM transform;
+        al_identity_transform(&transform);
+        al_use_transform(&transform);
+        al_orthographic_transform(&transform, L, T, 1.0f, R, B, -1.0f);
+        al_use_projection_transform(&transform);
+    }
+}
+
+// Render function.
+void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    // Backup Allegro state that will be modified
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    ALLEGRO_TRANSFORM last_transform = *al_get_current_transform();
+    ALLEGRO_TRANSFORM last_projection_transform = *al_get_current_projection_transform();
+    int last_clip_x, last_clip_y, last_clip_w, last_clip_h;
+    al_get_clipping_rectangle(&last_clip_x, &last_clip_y, &last_clip_w, &last_clip_h);
+    int last_blender_op, last_blender_src, last_blender_dst;
+    al_get_blender(&last_blender_op, &last_blender_src, &last_blender_dst);
+
+    // Setup desired render state
+    ImGui_ImplAllegro5_SetupRenderState(draw_data);
+
+    // Render command lists
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+
+        ImVector<ImDrawVertAllegro>& vertices = bd->BufVertices;
+#if ALLEGRO_HAS_DRAW_INDEXED_PRIM
+        vertices.resize(cmd_list->VtxBuffer.Size);
+        for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
+        {
+            const ImDrawVert* src_v = &cmd_list->VtxBuffer[i];
+            ImDrawVertAllegro* dst_v = &vertices[i];
+            DRAW_VERT_IMGUI_TO_ALLEGRO(dst_v, src_v);
+        }
+        const int* indices = nullptr;
+        if (sizeof(ImDrawIdx) == 2)
+        {
+            // FIXME-OPT: Allegro doesn't support 16-bit indices.
+            // You can '#define ImDrawIdx int' in imconfig.h to request Dear ImGui to output 32-bit indices.
+            // Otherwise, we convert them from 16-bit to 32-bit at runtime here, which works perfectly but is a little wasteful.
+            bd->BufIndices.resize(cmd_list->IdxBuffer.Size);
+            for (int i = 0; i < cmd_list->IdxBuffer.Size; ++i)
+                bd->BufIndices[i] = (int)cmd_list->IdxBuffer.Data[i];
+            indices = bd->BufIndices.Data;
+        }
+        else if (sizeof(ImDrawIdx) == 4)
+        {
+            indices = (const int*)cmd_list->IdxBuffer.Data;
+        }
+#else
+        // Allegro's implementation of al_draw_indexed_prim() for DX9 was broken until 5.2.5. Unindex buffers ourselves while converting vertex format.
+        vertices.resize(cmd_list->IdxBuffer.Size);
+        for (int i = 0; i < cmd_list->IdxBuffer.Size; i++)
+        {
+            const ImDrawVert* src_v = &cmd_list->VtxBuffer[cmd_list->IdxBuffer[i]];
+            ImDrawVertAllegro* dst_v = &vertices[i];
+            DRAW_VERT_IMGUI_TO_ALLEGRO(dst_v, src_v);
+        }
+#endif
+
+        // Render command lists
+        ImVec2 clip_off = draw_data->DisplayPos;
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplAllegro5_SetupRenderState(draw_data);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
+                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply scissor/clipping rectangle, Draw
+                ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->GetTexID();
+                al_set_clipping_rectangle(clip_min.x, clip_min.y, clip_max.x - clip_min.x, clip_max.y - clip_min.y);
+#if ALLEGRO_HAS_DRAW_INDEXED_PRIM
+                al_draw_indexed_prim(&vertices[0], bd->VertexDecl, texture, &indices[pcmd->IdxOffset], pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST);
+#else
+                al_draw_prim(&vertices[0], bd->VertexDecl, texture, pcmd->IdxOffset, pcmd->IdxOffset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST);
+#endif
+            }
+        }
+    }
+
+    // Restore modified Allegro state
+    al_set_blender(last_blender_op, last_blender_src, last_blender_dst);
+    al_set_clipping_rectangle(last_clip_x, last_clip_y, last_clip_w, last_clip_h);
+    al_use_transform(&last_transform);
+    al_use_projection_transform(&last_projection_transform);
+}
+
+bool ImGui_ImplAllegro5_CreateDeviceObjects()
+{
+    // Build texture atlas
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+    // Create texture
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    int flags = al_get_new_bitmap_flags();
+    int fmt = al_get_new_bitmap_format();
+    al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR);
+    al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE);
+    ALLEGRO_BITMAP* img = al_create_bitmap(width, height);
+    al_set_new_bitmap_flags(flags);
+    al_set_new_bitmap_format(fmt);
+    if (!img)
+        return false;
+
+    ALLEGRO_LOCKED_REGION* locked_img = al_lock_bitmap(img, al_get_bitmap_format(img), ALLEGRO_LOCK_WRITEONLY);
+    if (!locked_img)
+    {
+        al_destroy_bitmap(img);
+        return false;
+    }
+    memcpy(locked_img->data, pixels, sizeof(int) * width * height);
+    al_unlock_bitmap(img);
+
+    // Convert software texture to hardware texture.
+    ALLEGRO_BITMAP* cloned_img = al_clone_bitmap(img);
+    al_destroy_bitmap(img);
+    if (!cloned_img)
+        return false;
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)(intptr_t)cloned_img);
+    bd->Texture = cloned_img;
+
+    // Create an invisible mouse cursor
+    // Because al_hide_mouse_cursor() seems to mess up with the actual inputs..
+    ALLEGRO_BITMAP* mouse_cursor = al_create_bitmap(8, 8);
+    bd->MouseCursorInvisible = al_create_mouse_cursor(mouse_cursor, 0, 0);
+    al_destroy_bitmap(mouse_cursor);
+
+    return true;
+}
+
+void ImGui_ImplAllegro5_InvalidateDeviceObjects()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    if (bd->Texture)
+    {
+        io.Fonts->SetTexID(0);
+        al_destroy_bitmap(bd->Texture);
+        bd->Texture = nullptr;
+    }
+    if (bd->MouseCursorInvisible)
+    {
+        al_destroy_mouse_cursor(bd->MouseCursorInvisible);
+        bd->MouseCursorInvisible = nullptr;
+    }
+}
+
+#if ALLEGRO_HAS_CLIPBOARD
+static const char* ImGui_ImplAllegro5_GetClipboardText(void*)
+{
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    if (bd->ClipboardTextData)
+        al_free(bd->ClipboardTextData);
+    bd->ClipboardTextData = al_get_clipboard_text(bd->Display);
+    return bd->ClipboardTextData;
+}
+
+static void ImGui_ImplAllegro5_SetClipboardText(void*, const char* text)
+{
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    al_set_clipboard_text(bd->Display, text);
+}
+#endif
+
+static ImGuiKey ImGui_ImplAllegro5_KeyCodeToImGuiKey(int key_code)
+{
+    switch (key_code)
+    {
+        case ALLEGRO_KEY_TAB: return ImGuiKey_Tab;
+        case ALLEGRO_KEY_LEFT: return ImGuiKey_LeftArrow;
+        case ALLEGRO_KEY_RIGHT: return ImGuiKey_RightArrow;
+        case ALLEGRO_KEY_UP: return ImGuiKey_UpArrow;
+        case ALLEGRO_KEY_DOWN: return ImGuiKey_DownArrow;
+        case ALLEGRO_KEY_PGUP: return ImGuiKey_PageUp;
+        case ALLEGRO_KEY_PGDN: return ImGuiKey_PageDown;
+        case ALLEGRO_KEY_HOME: return ImGuiKey_Home;
+        case ALLEGRO_KEY_END: return ImGuiKey_End;
+        case ALLEGRO_KEY_INSERT: return ImGuiKey_Insert;
+        case ALLEGRO_KEY_DELETE: return ImGuiKey_Delete;
+        case ALLEGRO_KEY_BACKSPACE: return ImGuiKey_Backspace;
+        case ALLEGRO_KEY_SPACE: return ImGuiKey_Space;
+        case ALLEGRO_KEY_ENTER: return ImGuiKey_Enter;
+        case ALLEGRO_KEY_ESCAPE: return ImGuiKey_Escape;
+        case ALLEGRO_KEY_QUOTE: return ImGuiKey_Apostrophe;
+        case ALLEGRO_KEY_COMMA: return ImGuiKey_Comma;
+        case ALLEGRO_KEY_MINUS: return ImGuiKey_Minus;
+        case ALLEGRO_KEY_FULLSTOP: return ImGuiKey_Period;
+        case ALLEGRO_KEY_SLASH: return ImGuiKey_Slash;
+        case ALLEGRO_KEY_SEMICOLON: return ImGuiKey_Semicolon;
+        case ALLEGRO_KEY_EQUALS: return ImGuiKey_Equal;
+        case ALLEGRO_KEY_OPENBRACE: return ImGuiKey_LeftBracket;
+        case ALLEGRO_KEY_BACKSLASH: return ImGuiKey_Backslash;
+        case ALLEGRO_KEY_CLOSEBRACE: return ImGuiKey_RightBracket;
+        case ALLEGRO_KEY_TILDE: return ImGuiKey_GraveAccent;
+        case ALLEGRO_KEY_CAPSLOCK: return ImGuiKey_CapsLock;
+        case ALLEGRO_KEY_SCROLLLOCK: return ImGuiKey_ScrollLock;
+        case ALLEGRO_KEY_NUMLOCK: return ImGuiKey_NumLock;
+        case ALLEGRO_KEY_PRINTSCREEN: return ImGuiKey_PrintScreen;
+        case ALLEGRO_KEY_PAUSE: return ImGuiKey_Pause;
+        case ALLEGRO_KEY_PAD_0: return ImGuiKey_Keypad0;
+        case ALLEGRO_KEY_PAD_1: return ImGuiKey_Keypad1;
+        case ALLEGRO_KEY_PAD_2: return ImGuiKey_Keypad2;
+        case ALLEGRO_KEY_PAD_3: return ImGuiKey_Keypad3;
+        case ALLEGRO_KEY_PAD_4: return ImGuiKey_Keypad4;
+        case ALLEGRO_KEY_PAD_5: return ImGuiKey_Keypad5;
+        case ALLEGRO_KEY_PAD_6: return ImGuiKey_Keypad6;
+        case ALLEGRO_KEY_PAD_7: return ImGuiKey_Keypad7;
+        case ALLEGRO_KEY_PAD_8: return ImGuiKey_Keypad8;
+        case ALLEGRO_KEY_PAD_9: return ImGuiKey_Keypad9;
+        case ALLEGRO_KEY_PAD_DELETE: return ImGuiKey_KeypadDecimal;
+        case ALLEGRO_KEY_PAD_SLASH: return ImGuiKey_KeypadDivide;
+        case ALLEGRO_KEY_PAD_ASTERISK: return ImGuiKey_KeypadMultiply;
+        case ALLEGRO_KEY_PAD_MINUS: return ImGuiKey_KeypadSubtract;
+        case ALLEGRO_KEY_PAD_PLUS: return ImGuiKey_KeypadAdd;
+        case ALLEGRO_KEY_PAD_ENTER: return ImGuiKey_KeypadEnter;
+        case ALLEGRO_KEY_PAD_EQUALS: return ImGuiKey_KeypadEqual;
+        case ALLEGRO_KEY_LCTRL: return ImGuiKey_LeftCtrl;
+        case ALLEGRO_KEY_LSHIFT: return ImGuiKey_LeftShift;
+        case ALLEGRO_KEY_ALT: return ImGuiKey_LeftAlt;
+        case ALLEGRO_KEY_LWIN: return ImGuiKey_LeftSuper;
+        case ALLEGRO_KEY_RCTRL: return ImGuiKey_RightCtrl;
+        case ALLEGRO_KEY_RSHIFT: return ImGuiKey_RightShift;
+        case ALLEGRO_KEY_ALTGR: return ImGuiKey_RightAlt;
+        case ALLEGRO_KEY_RWIN: return ImGuiKey_RightSuper;
+        case ALLEGRO_KEY_MENU: return ImGuiKey_Menu;
+        case ALLEGRO_KEY_0: return ImGuiKey_0;
+        case ALLEGRO_KEY_1: return ImGuiKey_1;
+        case ALLEGRO_KEY_2: return ImGuiKey_2;
+        case ALLEGRO_KEY_3: return ImGuiKey_3;
+        case ALLEGRO_KEY_4: return ImGuiKey_4;
+        case ALLEGRO_KEY_5: return ImGuiKey_5;
+        case ALLEGRO_KEY_6: return ImGuiKey_6;
+        case ALLEGRO_KEY_7: return ImGuiKey_7;
+        case ALLEGRO_KEY_8: return ImGuiKey_8;
+        case ALLEGRO_KEY_9: return ImGuiKey_9;
+        case ALLEGRO_KEY_A: return ImGuiKey_A;
+        case ALLEGRO_KEY_B: return ImGuiKey_B;
+        case ALLEGRO_KEY_C: return ImGuiKey_C;
+        case ALLEGRO_KEY_D: return ImGuiKey_D;
+        case ALLEGRO_KEY_E: return ImGuiKey_E;
+        case ALLEGRO_KEY_F: return ImGuiKey_F;
+        case ALLEGRO_KEY_G: return ImGuiKey_G;
+        case ALLEGRO_KEY_H: return ImGuiKey_H;
+        case ALLEGRO_KEY_I: return ImGuiKey_I;
+        case ALLEGRO_KEY_J: return ImGuiKey_J;
+        case ALLEGRO_KEY_K: return ImGuiKey_K;
+        case ALLEGRO_KEY_L: return ImGuiKey_L;
+        case ALLEGRO_KEY_M: return ImGuiKey_M;
+        case ALLEGRO_KEY_N: return ImGuiKey_N;
+        case ALLEGRO_KEY_O: return ImGuiKey_O;
+        case ALLEGRO_KEY_P: return ImGuiKey_P;
+        case ALLEGRO_KEY_Q: return ImGuiKey_Q;
+        case ALLEGRO_KEY_R: return ImGuiKey_R;
+        case ALLEGRO_KEY_S: return ImGuiKey_S;
+        case ALLEGRO_KEY_T: return ImGuiKey_T;
+        case ALLEGRO_KEY_U: return ImGuiKey_U;
+        case ALLEGRO_KEY_V: return ImGuiKey_V;
+        case ALLEGRO_KEY_W: return ImGuiKey_W;
+        case ALLEGRO_KEY_X: return ImGuiKey_X;
+        case ALLEGRO_KEY_Y: return ImGuiKey_Y;
+        case ALLEGRO_KEY_Z: return ImGuiKey_Z;
+        case ALLEGRO_KEY_F1: return ImGuiKey_F1;
+        case ALLEGRO_KEY_F2: return ImGuiKey_F2;
+        case ALLEGRO_KEY_F3: return ImGuiKey_F3;
+        case ALLEGRO_KEY_F4: return ImGuiKey_F4;
+        case ALLEGRO_KEY_F5: return ImGuiKey_F5;
+        case ALLEGRO_KEY_F6: return ImGuiKey_F6;
+        case ALLEGRO_KEY_F7: return ImGuiKey_F7;
+        case ALLEGRO_KEY_F8: return ImGuiKey_F8;
+        case ALLEGRO_KEY_F9: return ImGuiKey_F9;
+        case ALLEGRO_KEY_F10: return ImGuiKey_F10;
+        case ALLEGRO_KEY_F11: return ImGuiKey_F11;
+        case ALLEGRO_KEY_F12: return ImGuiKey_F12;
+        default: return ImGuiKey_None;
+    }
+}
+
+bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplAllegro5_Data* bd = IM_NEW(ImGui_ImplAllegro5_Data)();
+    io.BackendPlatformUserData = (void*)bd;
+    io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5";
+    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)
+
+    bd->Display = display;
+
+    // Create custom vertex declaration.
+    // Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 floats.
+    // We still use a custom declaration to use 'ALLEGRO_PRIM_TEX_COORD' instead of 'ALLEGRO_PRIM_TEX_COORD_PIXEL' else we can't do a reliable conversion.
+    ALLEGRO_VERTEX_ELEMENT elems[] =
+    {
+        { ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_2, offsetof(ImDrawVertAllegro, pos) },
+        { ALLEGRO_PRIM_TEX_COORD, ALLEGRO_PRIM_FLOAT_2, offsetof(ImDrawVertAllegro, uv) },
+        { ALLEGRO_PRIM_COLOR_ATTR, 0, offsetof(ImDrawVertAllegro, col) },
+        { 0, 0, 0 }
+    };
+    bd->VertexDecl = al_create_vertex_decl(elems, sizeof(ImDrawVertAllegro));
+
+#if ALLEGRO_HAS_CLIPBOARD
+    io.SetClipboardTextFn = ImGui_ImplAllegro5_SetClipboardText;
+    io.GetClipboardTextFn = ImGui_ImplAllegro5_GetClipboardText;
+    io.ClipboardUserData = nullptr;
+#endif
+
+    return true;
+}
+
+void ImGui_ImplAllegro5_Shutdown()
+{
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplAllegro5_InvalidateDeviceObjects();
+    if (bd->VertexDecl)
+        al_destroy_vertex_decl(bd->VertexDecl);
+    if (bd->ClipboardTextData)
+        al_free(bd->ClipboardTextData);
+
+    io.BackendPlatformName = io.BackendRendererName = nullptr;
+    io.BackendPlatformUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors;
+    IM_DELETE(bd);
+}
+
+// ev->keyboard.modifiers seems always zero so using that...
+static void ImGui_ImplAllegro5_UpdateKeyModifiers()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ALLEGRO_KEYBOARD_STATE keys;
+    al_get_keyboard_state(&keys);
+    io.AddKeyEvent(ImGuiMod_Ctrl, al_key_down(&keys, ALLEGRO_KEY_LCTRL) || al_key_down(&keys, ALLEGRO_KEY_RCTRL));
+    io.AddKeyEvent(ImGuiMod_Shift, al_key_down(&keys, ALLEGRO_KEY_LSHIFT) || al_key_down(&keys, ALLEGRO_KEY_RSHIFT));
+    io.AddKeyEvent(ImGuiMod_Alt, al_key_down(&keys, ALLEGRO_KEY_ALT) || al_key_down(&keys, ALLEGRO_KEY_ALTGR));
+    io.AddKeyEvent(ImGuiMod_Super, al_key_down(&keys, ALLEGRO_KEY_LWIN) || al_key_down(&keys, ALLEGRO_KEY_RWIN));
+}
+
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* ev)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+
+    switch (ev->type)
+    {
+    case ALLEGRO_EVENT_MOUSE_AXES:
+        if (ev->mouse.display == bd->Display)
+        {
+            io.AddMousePosEvent(ev->mouse.x, ev->mouse.y);
+            io.AddMouseWheelEvent(-ev->mouse.dw, ev->mouse.dz);
+        }
+        return true;
+    case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
+    case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
+        if (ev->mouse.display == bd->Display && ev->mouse.button > 0 && ev->mouse.button <= 5)
+            io.AddMouseButtonEvent(ev->mouse.button - 1, ev->type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN);
+        return true;
+    case ALLEGRO_EVENT_TOUCH_MOVE:
+        if (ev->touch.display == bd->Display)
+            io.AddMousePosEvent(ev->touch.x, ev->touch.y);
+        return true;
+    case ALLEGRO_EVENT_TOUCH_BEGIN:
+    case ALLEGRO_EVENT_TOUCH_END:
+    case ALLEGRO_EVENT_TOUCH_CANCEL:
+        if (ev->touch.display == bd->Display && ev->touch.primary)
+            io.AddMouseButtonEvent(0, ev->type == ALLEGRO_EVENT_TOUCH_BEGIN);
+        return true;
+    case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY:
+        if (ev->mouse.display == bd->Display)
+            io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+        return true;
+    case ALLEGRO_EVENT_KEY_CHAR:
+        if (ev->keyboard.display == bd->Display)
+            if (ev->keyboard.unichar != 0)
+                io.AddInputCharacter((unsigned int)ev->keyboard.unichar);
+        return true;
+    case ALLEGRO_EVENT_KEY_DOWN:
+    case ALLEGRO_EVENT_KEY_UP:
+        if (ev->keyboard.display == bd->Display)
+        {
+            ImGui_ImplAllegro5_UpdateKeyModifiers();
+            ImGuiKey key = ImGui_ImplAllegro5_KeyCodeToImGuiKey(ev->keyboard.keycode);
+            io.AddKeyEvent(key, (ev->type == ALLEGRO_EVENT_KEY_DOWN));
+            io.SetKeyEventNativeData(key, ev->keyboard.keycode, -1); // To support legacy indexing (<1.87 user code)
+        }
+        return true;
+    case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT:
+        if (ev->display.source == bd->Display)
+            io.AddFocusEvent(false);
+        return true;
+    case ALLEGRO_EVENT_DISPLAY_SWITCH_IN:
+        if (ev->display.source == bd->Display)
+        {
+            io.AddFocusEvent(true);
+#if defined(ALLEGRO_UNSTABLE)
+            al_clear_keyboard_state(bd->Display);
+#endif
+        }
+        return true;
+    }
+    return false;
+}
+
+static void ImGui_ImplAllegro5_UpdateMouseCursor()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+        return;
+
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
+    {
+        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+        al_set_mouse_cursor(bd->Display, bd->MouseCursorInvisible);
+    }
+    else
+    {
+        ALLEGRO_SYSTEM_MOUSE_CURSOR cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_DEFAULT;
+        switch (imgui_cursor)
+        {
+        case ImGuiMouseCursor_TextInput:    cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_EDIT; break;
+        case ImGuiMouseCursor_ResizeAll:    cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_MOVE; break;
+        case ImGuiMouseCursor_ResizeNS:     cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_N; break;
+        case ImGuiMouseCursor_ResizeEW:     cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_E; break;
+        case ImGuiMouseCursor_ResizeNESW:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NE; break;
+        case ImGuiMouseCursor_ResizeNWSE:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NW; break;
+        case ImGuiMouseCursor_NotAllowed:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_UNAVAILABLE; break;
+        }
+        al_set_system_mouse_cursor(bd->Display, cursor_id);
+    }
+}
+
+void ImGui_ImplAllegro5_NewFrame()
+{
+    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplAllegro5_Init()?");
+
+    if (!bd->Texture)
+        ImGui_ImplAllegro5_CreateDeviceObjects();
+
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size (every frame to accommodate for window resizing)
+    int w, h;
+    w = al_get_display_width(bd->Display);
+    h = al_get_display_height(bd->Display);
+    io.DisplaySize = ImVec2((float)w, (float)h);
+
+    // Setup time step
+    double current_time = al_get_time();
+    io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
+    bd->Time = current_time;
+
+    // Setup mouse cursor shape
+    ImGui_ImplAllegro5_UpdateMouseCursor();
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_allegro5.h b/engines/twp/imgui/backends/imgui_impl_allegro5.h
new file mode 100644
index 00000000000..5b63654ef9c
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_allegro5.h
@@ -0,0 +1,38 @@
+// dear imgui: Renderer + Platform Backend for Allegro 5
+// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Clipboard support (from Allegro 5.1.12)
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+// Issues:
+//  [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually.
+//  [ ] Platform: Missing gamepad support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct ALLEGRO_DISPLAY;
+union ALLEGRO_EVENT;
+
+IMGUI_IMPL_API bool     ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display);
+IMGUI_IMPL_API void     ImGui_ImplAllegro5_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplAllegro5_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data);
+IMGUI_IMPL_API bool     ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* event);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API bool     ImGui_ImplAllegro5_CreateDeviceObjects();
+IMGUI_IMPL_API void     ImGui_ImplAllegro5_InvalidateDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_android.cpp b/engines/twp/imgui/backends/imgui_impl_android.cpp
new file mode 100644
index 00000000000..7dd2afc2efc
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_android.cpp
@@ -0,0 +1,306 @@
+// dear imgui: Platform Binding for Android native app
+// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
+
+// Implemented features:
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
+// Missing features:
+//  [ ] Platform: Clipboard support.
+//  [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
+// Important:
+//  - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
+//  - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
+//  - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2021-03-04: Initial version.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_android.h"
+#include <time.h>
+#include <android/native_window.h>
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <android/log.h>
+
+// Android data
+static double                                   g_Time = 0.0;
+static ANativeWindow*                           g_Window;
+static char                                     g_LogTag[] = "ImGuiExample";
+
+static ImGuiKey ImGui_ImplAndroid_KeyCodeToImGuiKey(int32_t key_code)
+{
+    switch (key_code)
+    {
+        case AKEYCODE_TAB:                  return ImGuiKey_Tab;
+        case AKEYCODE_DPAD_LEFT:            return ImGuiKey_LeftArrow;
+        case AKEYCODE_DPAD_RIGHT:           return ImGuiKey_RightArrow;
+        case AKEYCODE_DPAD_UP:              return ImGuiKey_UpArrow;
+        case AKEYCODE_DPAD_DOWN:            return ImGuiKey_DownArrow;
+        case AKEYCODE_PAGE_UP:              return ImGuiKey_PageUp;
+        case AKEYCODE_PAGE_DOWN:            return ImGuiKey_PageDown;
+        case AKEYCODE_MOVE_HOME:            return ImGuiKey_Home;
+        case AKEYCODE_MOVE_END:             return ImGuiKey_End;
+        case AKEYCODE_INSERT:               return ImGuiKey_Insert;
+        case AKEYCODE_FORWARD_DEL:          return ImGuiKey_Delete;
+        case AKEYCODE_DEL:                  return ImGuiKey_Backspace;
+        case AKEYCODE_SPACE:                return ImGuiKey_Space;
+        case AKEYCODE_ENTER:                return ImGuiKey_Enter;
+        case AKEYCODE_ESCAPE:               return ImGuiKey_Escape;
+        case AKEYCODE_APOSTROPHE:           return ImGuiKey_Apostrophe;
+        case AKEYCODE_COMMA:                return ImGuiKey_Comma;
+        case AKEYCODE_MINUS:                return ImGuiKey_Minus;
+        case AKEYCODE_PERIOD:               return ImGuiKey_Period;
+        case AKEYCODE_SLASH:                return ImGuiKey_Slash;
+        case AKEYCODE_SEMICOLON:            return ImGuiKey_Semicolon;
+        case AKEYCODE_EQUALS:               return ImGuiKey_Equal;
+        case AKEYCODE_LEFT_BRACKET:         return ImGuiKey_LeftBracket;
+        case AKEYCODE_BACKSLASH:            return ImGuiKey_Backslash;
+        case AKEYCODE_RIGHT_BRACKET:        return ImGuiKey_RightBracket;
+        case AKEYCODE_GRAVE:                return ImGuiKey_GraveAccent;
+        case AKEYCODE_CAPS_LOCK:            return ImGuiKey_CapsLock;
+        case AKEYCODE_SCROLL_LOCK:          return ImGuiKey_ScrollLock;
+        case AKEYCODE_NUM_LOCK:             return ImGuiKey_NumLock;
+        case AKEYCODE_SYSRQ:                return ImGuiKey_PrintScreen;
+        case AKEYCODE_BREAK:                return ImGuiKey_Pause;
+        case AKEYCODE_NUMPAD_0:             return ImGuiKey_Keypad0;
+        case AKEYCODE_NUMPAD_1:             return ImGuiKey_Keypad1;
+        case AKEYCODE_NUMPAD_2:             return ImGuiKey_Keypad2;
+        case AKEYCODE_NUMPAD_3:             return ImGuiKey_Keypad3;
+        case AKEYCODE_NUMPAD_4:             return ImGuiKey_Keypad4;
+        case AKEYCODE_NUMPAD_5:             return ImGuiKey_Keypad5;
+        case AKEYCODE_NUMPAD_6:             return ImGuiKey_Keypad6;
+        case AKEYCODE_NUMPAD_7:             return ImGuiKey_Keypad7;
+        case AKEYCODE_NUMPAD_8:             return ImGuiKey_Keypad8;
+        case AKEYCODE_NUMPAD_9:             return ImGuiKey_Keypad9;
+        case AKEYCODE_NUMPAD_DOT:           return ImGuiKey_KeypadDecimal;
+        case AKEYCODE_NUMPAD_DIVIDE:        return ImGuiKey_KeypadDivide;
+        case AKEYCODE_NUMPAD_MULTIPLY:      return ImGuiKey_KeypadMultiply;
+        case AKEYCODE_NUMPAD_SUBTRACT:      return ImGuiKey_KeypadSubtract;
+        case AKEYCODE_NUMPAD_ADD:           return ImGuiKey_KeypadAdd;
+        case AKEYCODE_NUMPAD_ENTER:         return ImGuiKey_KeypadEnter;
+        case AKEYCODE_NUMPAD_EQUALS:        return ImGuiKey_KeypadEqual;
+        case AKEYCODE_CTRL_LEFT:            return ImGuiKey_LeftCtrl;
+        case AKEYCODE_SHIFT_LEFT:           return ImGuiKey_LeftShift;
+        case AKEYCODE_ALT_LEFT:             return ImGuiKey_LeftAlt;
+        case AKEYCODE_META_LEFT:            return ImGuiKey_LeftSuper;
+        case AKEYCODE_CTRL_RIGHT:           return ImGuiKey_RightCtrl;
+        case AKEYCODE_SHIFT_RIGHT:          return ImGuiKey_RightShift;
+        case AKEYCODE_ALT_RIGHT:            return ImGuiKey_RightAlt;
+        case AKEYCODE_META_RIGHT:           return ImGuiKey_RightSuper;
+        case AKEYCODE_MENU:                 return ImGuiKey_Menu;
+        case AKEYCODE_0:                    return ImGuiKey_0;
+        case AKEYCODE_1:                    return ImGuiKey_1;
+        case AKEYCODE_2:                    return ImGuiKey_2;
+        case AKEYCODE_3:                    return ImGuiKey_3;
+        case AKEYCODE_4:                    return ImGuiKey_4;
+        case AKEYCODE_5:                    return ImGuiKey_5;
+        case AKEYCODE_6:                    return ImGuiKey_6;
+        case AKEYCODE_7:                    return ImGuiKey_7;
+        case AKEYCODE_8:                    return ImGuiKey_8;
+        case AKEYCODE_9:                    return ImGuiKey_9;
+        case AKEYCODE_A:                    return ImGuiKey_A;
+        case AKEYCODE_B:                    return ImGuiKey_B;
+        case AKEYCODE_C:                    return ImGuiKey_C;
+        case AKEYCODE_D:                    return ImGuiKey_D;
+        case AKEYCODE_E:                    return ImGuiKey_E;
+        case AKEYCODE_F:                    return ImGuiKey_F;
+        case AKEYCODE_G:                    return ImGuiKey_G;
+        case AKEYCODE_H:                    return ImGuiKey_H;
+        case AKEYCODE_I:                    return ImGuiKey_I;
+        case AKEYCODE_J:                    return ImGuiKey_J;
+        case AKEYCODE_K:                    return ImGuiKey_K;
+        case AKEYCODE_L:                    return ImGuiKey_L;
+        case AKEYCODE_M:                    return ImGuiKey_M;
+        case AKEYCODE_N:                    return ImGuiKey_N;
+        case AKEYCODE_O:                    return ImGuiKey_O;
+        case AKEYCODE_P:                    return ImGuiKey_P;
+        case AKEYCODE_Q:                    return ImGuiKey_Q;
+        case AKEYCODE_R:                    return ImGuiKey_R;
+        case AKEYCODE_S:                    return ImGuiKey_S;
+        case AKEYCODE_T:                    return ImGuiKey_T;
+        case AKEYCODE_U:                    return ImGuiKey_U;
+        case AKEYCODE_V:                    return ImGuiKey_V;
+        case AKEYCODE_W:                    return ImGuiKey_W;
+        case AKEYCODE_X:                    return ImGuiKey_X;
+        case AKEYCODE_Y:                    return ImGuiKey_Y;
+        case AKEYCODE_Z:                    return ImGuiKey_Z;
+        case AKEYCODE_F1:                   return ImGuiKey_F1;
+        case AKEYCODE_F2:                   return ImGuiKey_F2;
+        case AKEYCODE_F3:                   return ImGuiKey_F3;
+        case AKEYCODE_F4:                   return ImGuiKey_F4;
+        case AKEYCODE_F5:                   return ImGuiKey_F5;
+        case AKEYCODE_F6:                   return ImGuiKey_F6;
+        case AKEYCODE_F7:                   return ImGuiKey_F7;
+        case AKEYCODE_F8:                   return ImGuiKey_F8;
+        case AKEYCODE_F9:                   return ImGuiKey_F9;
+        case AKEYCODE_F10:                  return ImGuiKey_F10;
+        case AKEYCODE_F11:                  return ImGuiKey_F11;
+        case AKEYCODE_F12:                  return ImGuiKey_F12;
+        default:                            return ImGuiKey_None;
+    }
+}
+
+int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    int32_t event_type = AInputEvent_getType(input_event);
+    switch (event_type)
+    {
+    case AINPUT_EVENT_TYPE_KEY:
+    {
+        int32_t event_key_code = AKeyEvent_getKeyCode(input_event);
+        int32_t event_scan_code = AKeyEvent_getScanCode(input_event);
+        int32_t event_action = AKeyEvent_getAction(input_event);
+        int32_t event_meta_state = AKeyEvent_getMetaState(input_event);
+
+        io.AddKeyEvent(ImGuiMod_Ctrl,  (event_meta_state & AMETA_CTRL_ON)  != 0);
+        io.AddKeyEvent(ImGuiMod_Shift, (event_meta_state & AMETA_SHIFT_ON) != 0);
+        io.AddKeyEvent(ImGuiMod_Alt,   (event_meta_state & AMETA_ALT_ON)   != 0);
+        io.AddKeyEvent(ImGuiMod_Super, (event_meta_state & AMETA_META_ON)  != 0);
+
+        switch (event_action)
+        {
+        // FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer
+        // goes up from a key. We use a simple key event queue/ and process one event per key per frame in
+        // ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787
+        case AKEY_EVENT_ACTION_DOWN:
+        case AKEY_EVENT_ACTION_UP:
+        {
+            ImGuiKey key = ImGui_ImplAndroid_KeyCodeToImGuiKey(event_key_code);
+            if (key != ImGuiKey_None)
+            {
+                io.AddKeyEvent(key, event_action == AKEY_EVENT_ACTION_DOWN);
+                io.SetKeyEventNativeData(key, event_key_code, event_scan_code);
+            }
+
+            break;
+        }
+        default:
+            break;
+        }
+        break;
+    }
+    case AINPUT_EVENT_TYPE_MOTION:
+    {
+        int32_t event_action = AMotionEvent_getAction(input_event);
+        int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+        event_action &= AMOTION_EVENT_ACTION_MASK;
+
+        switch (AMotionEvent_getToolType(input_event, event_pointer_index))
+        {
+        case AMOTION_EVENT_TOOL_TYPE_MOUSE:
+            io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
+            break;
+        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
+        case AMOTION_EVENT_TOOL_TYPE_ERASER:
+            io.AddMouseSourceEvent(ImGuiMouseSource_Pen);
+            break;
+        case AMOTION_EVENT_TOOL_TYPE_FINGER:
+        default:
+            io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
+            break;
+        }
+
+        switch (event_action)
+        {
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_UP:
+        {
+            // Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP,
+            // but we have to process them separately to identify the actual button pressed. This is done below via
+            // AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback).
+            int tool_type = AMotionEvent_getToolType(input_event, event_pointer_index);
+            if (tool_type == AMOTION_EVENT_TOOL_TYPE_FINGER || tool_type == AMOTION_EVENT_TOOL_TYPE_UNKNOWN)
+            {
+                io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
+                io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN);
+            }
+            break;
+        }
+        case AMOTION_EVENT_ACTION_BUTTON_PRESS:
+        case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
+        {
+            int32_t button_state = AMotionEvent_getButtonState(input_event);
+            io.AddMouseButtonEvent(0, (button_state & AMOTION_EVENT_BUTTON_PRIMARY) != 0);
+            io.AddMouseButtonEvent(1, (button_state & AMOTION_EVENT_BUTTON_SECONDARY) != 0);
+            io.AddMouseButtonEvent(2, (button_state & AMOTION_EVENT_BUTTON_TERTIARY) != 0);
+            break;
+        }
+        case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse)
+        case AMOTION_EVENT_ACTION_MOVE:       // Touch pointer moves while DOWN
+            io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
+            break;
+        case AMOTION_EVENT_ACTION_SCROLL:
+            io.AddMouseWheelEvent(AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index), AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index));
+            break;
+        default:
+            break;
+        }
+    }
+        return 1;
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+bool ImGui_ImplAndroid_Init(ANativeWindow* window)
+{
+    g_Window = window;
+    g_Time = 0.0;
+
+    // Setup backend capabilities flags
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendPlatformName = "imgui_impl_android";
+
+    return true;
+}
+
+void ImGui_ImplAndroid_Shutdown()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendPlatformName = nullptr;
+}
+
+void ImGui_ImplAndroid_NewFrame()
+{
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size (every frame to accommodate for window resizing)
+    int32_t window_width = ANativeWindow_getWidth(g_Window);
+    int32_t window_height = ANativeWindow_getHeight(g_Window);
+    int display_width = window_width;
+    int display_height = window_height;
+
+    io.DisplaySize = ImVec2((float)window_width, (float)window_height);
+    if (window_width > 0 && window_height > 0)
+        io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
+
+    // Setup time step
+    struct timespec current_timespec;
+    clock_gettime(CLOCK_MONOTONIC, &current_timespec);
+    double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
+    io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
+    g_Time = current_time;
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_android.h b/engines/twp/imgui/backends/imgui_impl_android.h
new file mode 100644
index 00000000000..557bf0e0921
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_android.h
@@ -0,0 +1,36 @@
+// dear imgui: Platform Binding for Android native app
+// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
+
+// Implemented features:
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
+// Missing features:
+//  [ ] Platform: Clipboard support.
+//  [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
+// Important:
+//  - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
+//  - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
+//  - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct ANativeWindow;
+struct AInputEvent;
+
+IMGUI_IMPL_API bool     ImGui_ImplAndroid_Init(ANativeWindow* window);
+IMGUI_IMPL_API int32_t  ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event);
+IMGUI_IMPL_API void     ImGui_ImplAndroid_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplAndroid_NewFrame();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx10.cpp b/engines/twp/imgui/backends/imgui_impl_dx10.cpp
new file mode 100644
index 00000000000..96e27471c4d
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx10.cpp
@@ -0,0 +1,589 @@
+// dear imgui: Renderer Backend for DirectX10
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-05-19: DirectX10: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-02-18: DirectX10: Change blending equation to preserve alpha in output buffer.
+//  2019-07-21: DirectX10: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData().
+//  2019-05-29: DirectX10: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: DirectX10: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-07-13: DirectX10: Fixed unreleased resources in Init and Shutdown functions.
+//  2018-06-08: Misc: Extracted imgui_impl_dx10.cpp/.h away from the old combined DX10+Win32 example.
+//  2018-06-08: DirectX10: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-04-09: Misc: Fixed erroneous call to io.Fonts->ClearInputData() + ClearTexData() that was left in DX10 example but removed in 1.47 (Nov 2015) on other backends.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX10_RenderDrawData() in the .h file so you can call it yourself.
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+//  2016-05-07: DirectX10: Disabling depth-write.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_dx10.h"
+
+// DirectX
+#include <stdio.h>
+#include <d3d10_1.h>
+#include <d3d10.h>
+#include <d3dcompiler.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
+#endif
+
+// DirectX data
+struct ImGui_ImplDX10_Data
+{
+    ID3D10Device*               pd3dDevice;
+    IDXGIFactory*               pFactory;
+    ID3D10Buffer*               pVB;
+    ID3D10Buffer*               pIB;
+    ID3D10VertexShader*         pVertexShader;
+    ID3D10InputLayout*          pInputLayout;
+    ID3D10Buffer*               pVertexConstantBuffer;
+    ID3D10PixelShader*          pPixelShader;
+    ID3D10SamplerState*         pFontSampler;
+    ID3D10ShaderResourceView*   pFontTextureView;
+    ID3D10RasterizerState*      pRasterizerState;
+    ID3D10BlendState*           pBlendState;
+    ID3D10DepthStencilState*    pDepthStencilState;
+    int                         VertexBufferSize;
+    int                         IndexBufferSize;
+
+    ImGui_ImplDX10_Data()       { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
+};
+
+struct VERTEX_CONSTANT_BUFFER_DX10
+{
+    float   mvp[4][4];
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplDX10_Data* ImGui_ImplDX10_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplDX10_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Functions
+static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* ctx)
+{
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+
+    // Setup viewport
+    D3D10_VIEWPORT vp;
+    memset(&vp, 0, sizeof(D3D10_VIEWPORT));
+    vp.Width = (UINT)draw_data->DisplaySize.x;
+    vp.Height = (UINT)draw_data->DisplaySize.y;
+    vp.MinDepth = 0.0f;
+    vp.MaxDepth = 1.0f;
+    vp.TopLeftX = vp.TopLeftY = 0;
+    ctx->RSSetViewports(1, &vp);
+
+    // Bind shader and vertex buffers
+    unsigned int stride = sizeof(ImDrawVert);
+    unsigned int offset = 0;
+    ctx->IASetInputLayout(bd->pInputLayout);
+    ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
+    ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
+    ctx->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+    ctx->VSSetShader(bd->pVertexShader);
+    ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
+    ctx->PSSetShader(bd->pPixelShader);
+    ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
+    ctx->GSSetShader(nullptr);
+
+    // Setup render state
+    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
+    ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
+    ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
+    ctx->RSSetState(bd->pRasterizerState);
+}
+
+// Render function
+void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    ID3D10Device* ctx = bd->pd3dDevice;
+
+    // Create and grow vertex/index buffers if needed
+    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
+    {
+        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
+        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
+        D3D10_BUFFER_DESC desc;
+        memset(&desc, 0, sizeof(D3D10_BUFFER_DESC));
+        desc.Usage = D3D10_USAGE_DYNAMIC;
+        desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
+        desc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
+        desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
+        desc.MiscFlags = 0;
+        if (ctx->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
+            return;
+    }
+
+    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
+    {
+        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
+        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
+        D3D10_BUFFER_DESC desc;
+        memset(&desc, 0, sizeof(D3D10_BUFFER_DESC));
+        desc.Usage = D3D10_USAGE_DYNAMIC;
+        desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
+        desc.BindFlags = D3D10_BIND_INDEX_BUFFER;
+        desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
+        if (ctx->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
+            return;
+    }
+
+    // Copy and convert all vertices into a single contiguous buffer
+    ImDrawVert* vtx_dst = nullptr;
+    ImDrawIdx* idx_dst = nullptr;
+    bd->pVB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&vtx_dst);
+    bd->pIB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&idx_dst);
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+        vtx_dst += cmd_list->VtxBuffer.Size;
+        idx_dst += cmd_list->IdxBuffer.Size;
+    }
+    bd->pVB->Unmap();
+    bd->pIB->Unmap();
+
+    // Setup orthographic projection matrix into our constant buffer
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+    {
+        void* mapped_resource;
+        if (bd->pVertexConstantBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
+            return;
+        VERTEX_CONSTANT_BUFFER_DX10* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX10*)mapped_resource;
+        float L = draw_data->DisplayPos.x;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+        float T = draw_data->DisplayPos.y;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+        float mvp[4][4] =
+        {
+            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
+            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
+            { 0.0f,         0.0f,           0.5f,       0.0f },
+            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
+        };
+        memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
+        bd->pVertexConstantBuffer->Unmap();
+    }
+
+    // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
+    struct BACKUP_DX10_STATE
+    {
+        UINT                        ScissorRectsCount, ViewportsCount;
+        D3D10_RECT                  ScissorRects[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
+        D3D10_VIEWPORT              Viewports[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
+        ID3D10RasterizerState*      RS;
+        ID3D10BlendState*           BlendState;
+        FLOAT                       BlendFactor[4];
+        UINT                        SampleMask;
+        UINT                        StencilRef;
+        ID3D10DepthStencilState*    DepthStencilState;
+        ID3D10ShaderResourceView*   PSShaderResource;
+        ID3D10SamplerState*         PSSampler;
+        ID3D10PixelShader*          PS;
+        ID3D10VertexShader*         VS;
+        ID3D10GeometryShader*       GS;
+        D3D10_PRIMITIVE_TOPOLOGY    PrimitiveTopology;
+        ID3D10Buffer*               IndexBuffer, *VertexBuffer, *VSConstantBuffer;
+        UINT                        IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
+        DXGI_FORMAT                 IndexBufferFormat;
+        ID3D10InputLayout*          InputLayout;
+    };
+    BACKUP_DX10_STATE old = {};
+    old.ScissorRectsCount = old.ViewportsCount = D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
+    ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
+    ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
+    ctx->RSGetState(&old.RS);
+    ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
+    ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
+    ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
+    ctx->PSGetSamplers(0, 1, &old.PSSampler);
+    ctx->PSGetShader(&old.PS);
+    ctx->VSGetShader(&old.VS);
+    ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
+    ctx->GSGetShader(&old.GS);
+    ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
+    ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
+    ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
+    ctx->IAGetInputLayout(&old.InputLayout);
+
+    // Setup desired DX state
+    ImGui_ImplDX10_SetupRenderState(draw_data, ctx);
+
+    // Render command lists
+    // (Because we merged all buffers into a single one, we maintain our own offset into them)
+    int global_vtx_offset = 0;
+    int global_idx_offset = 0;
+    ImVec2 clip_off = draw_data->DisplayPos;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplDX10_SetupRenderState(draw_data, ctx);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
+                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply scissor/clipping rectangle
+                const D3D10_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
+                ctx->RSSetScissorRects(1, &r);
+
+                // Bind texture, Draw
+                ID3D10ShaderResourceView* texture_srv = (ID3D10ShaderResourceView*)pcmd->GetTexID();
+                ctx->PSSetShaderResources(0, 1, &texture_srv);
+                ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
+            }
+        }
+        global_idx_offset += cmd_list->IdxBuffer.Size;
+        global_vtx_offset += cmd_list->VtxBuffer.Size;
+    }
+
+    // Restore modified DX state
+    ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
+    ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
+    ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
+    ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
+    ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
+    ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
+    ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
+    ctx->PSSetShader(old.PS); if (old.PS) old.PS->Release();
+    ctx->VSSetShader(old.VS); if (old.VS) old.VS->Release();
+    ctx->GSSetShader(old.GS); if (old.GS) old.GS->Release();
+    ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
+    ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
+    ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
+    ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
+    ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
+}
+
+static void ImGui_ImplDX10_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+    // Upload texture to graphics system
+    {
+        D3D10_TEXTURE2D_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.Width = width;
+        desc.Height = height;
+        desc.MipLevels = 1;
+        desc.ArraySize = 1;
+        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        desc.SampleDesc.Count = 1;
+        desc.Usage = D3D10_USAGE_DEFAULT;
+        desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
+        desc.CPUAccessFlags = 0;
+
+        ID3D10Texture2D* pTexture = nullptr;
+        D3D10_SUBRESOURCE_DATA subResource;
+        subResource.pSysMem = pixels;
+        subResource.SysMemPitch = desc.Width * 4;
+        subResource.SysMemSlicePitch = 0;
+        bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
+        IM_ASSERT(pTexture != nullptr);
+
+        // Create texture view
+        D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc;
+        ZeroMemory(&srv_desc, sizeof(srv_desc));
+        srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
+        srv_desc.Texture2D.MipLevels = desc.MipLevels;
+        srv_desc.Texture2D.MostDetailedMip = 0;
+        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &bd->pFontTextureView);
+        pTexture->Release();
+    }
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);
+
+    // Create texture sampler
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    {
+        D3D10_SAMPLER_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR;
+        desc.AddressU = D3D10_TEXTURE_ADDRESS_WRAP;
+        desc.AddressV = D3D10_TEXTURE_ADDRESS_WRAP;
+        desc.AddressW = D3D10_TEXTURE_ADDRESS_WRAP;
+        desc.MipLODBias = 0.f;
+        desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS;
+        desc.MinLOD = 0.f;
+        desc.MaxLOD = 0.f;
+        bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
+    }
+}
+
+bool    ImGui_ImplDX10_CreateDeviceObjects()
+{
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    if (!bd->pd3dDevice)
+        return false;
+    if (bd->pFontSampler)
+        ImGui_ImplDX10_InvalidateDeviceObjects();
+
+    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
+    // If you would like to use this DX10 sample code but remove this dependency you can:
+    //  1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
+    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
+    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
+
+    // Create the vertex shader
+    {
+        static const char* vertexShader =
+            "cbuffer vertexBuffer : register(b0) \
+            {\
+              float4x4 ProjectionMatrix; \
+            };\
+            struct VS_INPUT\
+            {\
+              float2 pos : POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            struct PS_INPUT\
+            {\
+              float4 pos : SV_POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            PS_INPUT main(VS_INPUT input)\
+            {\
+              PS_INPUT output;\
+              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
+              output.col = input.col;\
+              output.uv  = input.uv;\
+              return output;\
+            }";
+
+        ID3DBlob* vertexShaderBlob;
+        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pVertexShader) != S_OK)
+        {
+            vertexShaderBlob->Release();
+            return false;
+        }
+
+        // Create the input layout
+        D3D10_INPUT_ELEMENT_DESC local_layout[] =
+        {
+            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D10_INPUT_PER_VERTEX_DATA, 0 },
+            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D10_INPUT_PER_VERTEX_DATA, 0 },
+            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D10_INPUT_PER_VERTEX_DATA, 0 },
+        };
+        if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
+        {
+            vertexShaderBlob->Release();
+            return false;
+        }
+        vertexShaderBlob->Release();
+
+        // Create the constant buffer
+        {
+            D3D10_BUFFER_DESC desc;
+            desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX10);
+            desc.Usage = D3D10_USAGE_DYNAMIC;
+            desc.BindFlags = D3D10_BIND_CONSTANT_BUFFER;
+            desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
+            desc.MiscFlags = 0;
+            bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
+        }
+    }
+
+    // Create the pixel shader
+    {
+        static const char* pixelShader =
+            "struct PS_INPUT\
+            {\
+            float4 pos : SV_POSITION;\
+            float4 col : COLOR0;\
+            float2 uv  : TEXCOORD0;\
+            };\
+            sampler sampler0;\
+            Texture2D texture0;\
+            \
+            float4 main(PS_INPUT input) : SV_Target\
+            {\
+            float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
+            return out_col; \
+            }";
+
+        ID3DBlob* pixelShaderBlob;
+        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), &bd->pPixelShader) != S_OK)
+        {
+            pixelShaderBlob->Release();
+            return false;
+        }
+        pixelShaderBlob->Release();
+    }
+
+    // Create the blending setup
+    {
+        D3D10_BLEND_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.AlphaToCoverageEnable = false;
+        desc.BlendEnable[0] = true;
+        desc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
+        desc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
+        desc.BlendOp = D3D10_BLEND_OP_ADD;
+        desc.SrcBlendAlpha = D3D10_BLEND_ONE;
+        desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA;
+        desc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
+        desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;
+        bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
+    }
+
+    // Create the rasterizer state
+    {
+        D3D10_RASTERIZER_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.FillMode = D3D10_FILL_SOLID;
+        desc.CullMode = D3D10_CULL_NONE;
+        desc.ScissorEnable = true;
+        desc.DepthClipEnable = true;
+        bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
+    }
+
+    // Create depth-stencil State
+    {
+        D3D10_DEPTH_STENCIL_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.DepthEnable = false;
+        desc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
+        desc.DepthFunc = D3D10_COMPARISON_ALWAYS;
+        desc.StencilEnable = false;
+        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
+        desc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
+        desc.BackFace = desc.FrontFace;
+        bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
+    }
+
+    ImGui_ImplDX10_CreateFontsTexture();
+
+    return true;
+}
+
+void    ImGui_ImplDX10_InvalidateDeviceObjects()
+{
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    if (!bd->pd3dDevice)
+        return;
+
+    if (bd->pFontSampler)           { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
+    if (bd->pFontTextureView)       { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
+    if (bd->pIB)                    { bd->pIB->Release(); bd->pIB = nullptr; }
+    if (bd->pVB)                    { bd->pVB->Release(); bd->pVB = nullptr; }
+    if (bd->pBlendState)            { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
+    if (bd->pDepthStencilState)     { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
+    if (bd->pRasterizerState)       { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
+    if (bd->pPixelShader)           { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
+    if (bd->pVertexConstantBuffer)  { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
+    if (bd->pInputLayout)           { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
+    if (bd->pVertexShader)          { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
+}
+
+bool    ImGui_ImplDX10_Init(ID3D10Device* device)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplDX10_Data* bd = IM_NEW(ImGui_ImplDX10_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_dx10";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    // Get factory from device
+    IDXGIDevice* pDXGIDevice = nullptr;
+    IDXGIAdapter* pDXGIAdapter = nullptr;
+    IDXGIFactory* pFactory = nullptr;
+    if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
+        if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
+            if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
+            {
+                bd->pd3dDevice = device;
+                bd->pFactory = pFactory;
+            }
+    if (pDXGIDevice) pDXGIDevice->Release();
+    if (pDXGIAdapter) pDXGIAdapter->Release();
+    bd->pd3dDevice->AddRef();
+
+    return true;
+}
+
+void ImGui_ImplDX10_Shutdown()
+{
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplDX10_InvalidateDeviceObjects();
+    if (bd->pFactory) { bd->pFactory->Release(); }
+    if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+void ImGui_ImplDX10_NewFrame()
+{
+    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX10_Init()?");
+
+    if (!bd->pFontSampler)
+        ImGui_ImplDX10_CreateDeviceObjects();
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx10.h b/engines/twp/imgui/backends/imgui_impl_dx10.h
new file mode 100644
index 00000000000..e7e798aa496
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx10.h
@@ -0,0 +1,31 @@
+// dear imgui: Renderer Backend for DirectX10
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct ID3D10Device;
+
+IMGUI_IMPL_API bool     ImGui_ImplDX10_Init(ID3D10Device* device);
+IMGUI_IMPL_API void     ImGui_ImplDX10_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplDX10_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API void     ImGui_ImplDX10_InvalidateDeviceObjects();
+IMGUI_IMPL_API bool     ImGui_ImplDX10_CreateDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx11.cpp b/engines/twp/imgui/backends/imgui_impl_dx11.cpp
new file mode 100644
index 00000000000..bbc26a7514e
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx11.cpp
@@ -0,0 +1,605 @@
+// dear imgui: Renderer Backend for DirectX11
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
+//  2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
+//  2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
+//  2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.
+//  2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.
+//  2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.
+//  2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself.
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+//  2016-05-07: DirectX11: Disabling depth-write.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_dx11.h"
+
+// DirectX
+#include <stdio.h>
+#include <d3d11.h>
+#include <d3dcompiler.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
+#endif
+
+// DirectX11 data
+struct ImGui_ImplDX11_Data
+{
+    ID3D11Device*               pd3dDevice;
+    ID3D11DeviceContext*        pd3dDeviceContext;
+    IDXGIFactory*               pFactory;
+    ID3D11Buffer*               pVB;
+    ID3D11Buffer*               pIB;
+    ID3D11VertexShader*         pVertexShader;
+    ID3D11InputLayout*          pInputLayout;
+    ID3D11Buffer*               pVertexConstantBuffer;
+    ID3D11PixelShader*          pPixelShader;
+    ID3D11SamplerState*         pFontSampler;
+    ID3D11ShaderResourceView*   pFontTextureView;
+    ID3D11RasterizerState*      pRasterizerState;
+    ID3D11BlendState*           pBlendState;
+    ID3D11DepthStencilState*    pDepthStencilState;
+    int                         VertexBufferSize;
+    int                         IndexBufferSize;
+
+    ImGui_ImplDX11_Data()       { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
+};
+
+struct VERTEX_CONSTANT_BUFFER_DX11
+{
+    float   mvp[4][4];
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Functions
+static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx)
+{
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+
+    // Setup viewport
+    D3D11_VIEWPORT vp;
+    memset(&vp, 0, sizeof(D3D11_VIEWPORT));
+    vp.Width = draw_data->DisplaySize.x;
+    vp.Height = draw_data->DisplaySize.y;
+    vp.MinDepth = 0.0f;
+    vp.MaxDepth = 1.0f;
+    vp.TopLeftX = vp.TopLeftY = 0;
+    ctx->RSSetViewports(1, &vp);
+
+    // Setup shader and vertex buffers
+    unsigned int stride = sizeof(ImDrawVert);
+    unsigned int offset = 0;
+    ctx->IASetInputLayout(bd->pInputLayout);
+    ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
+    ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
+    ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+    ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
+    ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
+    ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
+    ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
+    ctx->GSSetShader(nullptr, nullptr, 0);
+    ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
+    ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
+    ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
+
+    // Setup blend state
+    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
+    ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
+    ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
+    ctx->RSSetState(bd->pRasterizerState);
+}
+
+// Render function
+void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    ID3D11DeviceContext* ctx = bd->pd3dDeviceContext;
+
+    // Create and grow vertex/index buffers if needed
+    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
+    {
+        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
+        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
+        D3D11_BUFFER_DESC desc;
+        memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
+        desc.Usage = D3D11_USAGE_DYNAMIC;
+        desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
+        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+        desc.MiscFlags = 0;
+        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
+            return;
+    }
+    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
+    {
+        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
+        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
+        D3D11_BUFFER_DESC desc;
+        memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
+        desc.Usage = D3D11_USAGE_DYNAMIC;
+        desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
+        desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
+        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
+            return;
+    }
+
+    // Upload vertex/index data into a single contiguous GPU buffer
+    D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
+    if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
+        return;
+    if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
+        return;
+    ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
+    ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+        vtx_dst += cmd_list->VtxBuffer.Size;
+        idx_dst += cmd_list->IdxBuffer.Size;
+    }
+    ctx->Unmap(bd->pVB, 0);
+    ctx->Unmap(bd->pIB, 0);
+
+    // Setup orthographic projection matrix into our constant buffer
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+    {
+        D3D11_MAPPED_SUBRESOURCE mapped_resource;
+        if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
+            return;
+        VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
+        float L = draw_data->DisplayPos.x;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+        float T = draw_data->DisplayPos.y;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+        float mvp[4][4] =
+        {
+            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
+            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
+            { 0.0f,         0.0f,           0.5f,       0.0f },
+            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
+        };
+        memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
+        ctx->Unmap(bd->pVertexConstantBuffer, 0);
+    }
+
+    // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
+    struct BACKUP_DX11_STATE
+    {
+        UINT                        ScissorRectsCount, ViewportsCount;
+        D3D11_RECT                  ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
+        D3D11_VIEWPORT              Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
+        ID3D11RasterizerState*      RS;
+        ID3D11BlendState*           BlendState;
+        FLOAT                       BlendFactor[4];
+        UINT                        SampleMask;
+        UINT                        StencilRef;
+        ID3D11DepthStencilState*    DepthStencilState;
+        ID3D11ShaderResourceView*   PSShaderResource;
+        ID3D11SamplerState*         PSSampler;
+        ID3D11PixelShader*          PS;
+        ID3D11VertexShader*         VS;
+        ID3D11GeometryShader*       GS;
+        UINT                        PSInstancesCount, VSInstancesCount, GSInstancesCount;
+        ID3D11ClassInstance         *PSInstances[256], *VSInstances[256], *GSInstances[256];   // 256 is max according to PSSetShader documentation
+        D3D11_PRIMITIVE_TOPOLOGY    PrimitiveTopology;
+        ID3D11Buffer*               IndexBuffer, *VertexBuffer, *VSConstantBuffer;
+        UINT                        IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
+        DXGI_FORMAT                 IndexBufferFormat;
+        ID3D11InputLayout*          InputLayout;
+    };
+    BACKUP_DX11_STATE old = {};
+    old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
+    ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
+    ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
+    ctx->RSGetState(&old.RS);
+    ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
+    ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
+    ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
+    ctx->PSGetSamplers(0, 1, &old.PSSampler);
+    old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
+    ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
+    ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
+    ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
+    ctx->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
+
+    ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
+    ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
+    ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
+    ctx->IAGetInputLayout(&old.InputLayout);
+
+    // Setup desired DX state
+    ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
+
+    // Render command lists
+    // (Because we merged all buffers into a single one, we maintain our own offset into them)
+    int global_idx_offset = 0;
+    int global_vtx_offset = 0;
+    ImVec2 clip_off = draw_data->DisplayPos;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback != nullptr)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
+                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply scissor/clipping rectangle
+                const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
+                ctx->RSSetScissorRects(1, &r);
+
+                // Bind texture, Draw
+                ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();
+                ctx->PSSetShaderResources(0, 1, &texture_srv);
+                ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
+            }
+        }
+        global_idx_offset += cmd_list->IdxBuffer.Size;
+        global_vtx_offset += cmd_list->VtxBuffer.Size;
+    }
+
+    // Restore modified DX state
+    ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
+    ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
+    ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
+    ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
+    ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
+    ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
+    ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
+    ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
+    for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
+    ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
+    ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
+    ctx->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
+    for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
+    ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
+    ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
+    ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
+    ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
+}
+
+static void ImGui_ImplDX11_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+    // Upload texture to graphics system
+    {
+        D3D11_TEXTURE2D_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.Width = width;
+        desc.Height = height;
+        desc.MipLevels = 1;
+        desc.ArraySize = 1;
+        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        desc.SampleDesc.Count = 1;
+        desc.Usage = D3D11_USAGE_DEFAULT;
+        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+        desc.CPUAccessFlags = 0;
+
+        ID3D11Texture2D* pTexture = nullptr;
+        D3D11_SUBRESOURCE_DATA subResource;
+        subResource.pSysMem = pixels;
+        subResource.SysMemPitch = desc.Width * 4;
+        subResource.SysMemSlicePitch = 0;
+        bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
+        IM_ASSERT(pTexture != nullptr);
+
+        // Create texture view
+        D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
+        ZeroMemory(&srvDesc, sizeof(srvDesc));
+        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+        srvDesc.Texture2D.MipLevels = desc.MipLevels;
+        srvDesc.Texture2D.MostDetailedMip = 0;
+        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);
+        pTexture->Release();
+    }
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);
+
+    // Create texture sampler
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    {
+        D3D11_SAMPLER_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+        desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
+        desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
+        desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
+        desc.MipLODBias = 0.f;
+        desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
+        desc.MinLOD = 0.f;
+        desc.MaxLOD = 0.f;
+        bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
+    }
+}
+
+bool    ImGui_ImplDX11_CreateDeviceObjects()
+{
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    if (!bd->pd3dDevice)
+        return false;
+    if (bd->pFontSampler)
+        ImGui_ImplDX11_InvalidateDeviceObjects();
+
+    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
+    // If you would like to use this DX11 sample code but remove this dependency you can:
+    //  1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
+    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
+    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
+
+    // Create the vertex shader
+    {
+        static const char* vertexShader =
+            "cbuffer vertexBuffer : register(b0) \
+            {\
+              float4x4 ProjectionMatrix; \
+            };\
+            struct VS_INPUT\
+            {\
+              float2 pos : POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            struct PS_INPUT\
+            {\
+              float4 pos : SV_POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            PS_INPUT main(VS_INPUT input)\
+            {\
+              PS_INPUT output;\
+              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
+              output.col = input.col;\
+              output.uv  = input.uv;\
+              return output;\
+            }";
+
+        ID3DBlob* vertexShaderBlob;
+        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &bd->pVertexShader) != S_OK)
+        {
+            vertexShaderBlob->Release();
+            return false;
+        }
+
+        // Create the input layout
+        D3D11_INPUT_ELEMENT_DESC local_layout[] =
+        {
+            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
+            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D11_INPUT_PER_VERTEX_DATA, 0 },
+            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
+        };
+        if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
+        {
+            vertexShaderBlob->Release();
+            return false;
+        }
+        vertexShaderBlob->Release();
+
+        // Create the constant buffer
+        {
+            D3D11_BUFFER_DESC desc;
+            desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
+            desc.Usage = D3D11_USAGE_DYNAMIC;
+            desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+            desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+            desc.MiscFlags = 0;
+            bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
+        }
+    }
+
+    // Create the pixel shader
+    {
+        static const char* pixelShader =
+            "struct PS_INPUT\
+            {\
+            float4 pos : SV_POSITION;\
+            float4 col : COLOR0;\
+            float2 uv  : TEXCOORD0;\
+            };\
+            sampler sampler0;\
+            Texture2D texture0;\
+            \
+            float4 main(PS_INPUT input) : SV_Target\
+            {\
+            float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
+            return out_col; \
+            }";
+
+        ID3DBlob* pixelShaderBlob;
+        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &bd->pPixelShader) != S_OK)
+        {
+            pixelShaderBlob->Release();
+            return false;
+        }
+        pixelShaderBlob->Release();
+    }
+
+    // Create the blending setup
+    {
+        D3D11_BLEND_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.AlphaToCoverageEnable = false;
+        desc.RenderTarget[0].BlendEnable = true;
+        desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
+        desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
+        desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
+        desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
+        desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
+        desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
+        desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+        bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
+    }
+
+    // Create the rasterizer state
+    {
+        D3D11_RASTERIZER_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.FillMode = D3D11_FILL_SOLID;
+        desc.CullMode = D3D11_CULL_NONE;
+        desc.ScissorEnable = true;
+        desc.DepthClipEnable = true;
+        bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
+    }
+
+    // Create depth-stencil State
+    {
+        D3D11_DEPTH_STENCIL_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.DepthEnable = false;
+        desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
+        desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
+        desc.StencilEnable = false;
+        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
+        desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
+        desc.BackFace = desc.FrontFace;
+        bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
+    }
+
+    ImGui_ImplDX11_CreateFontsTexture();
+
+    return true;
+}
+
+void    ImGui_ImplDX11_InvalidateDeviceObjects()
+{
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    if (!bd->pd3dDevice)
+        return;
+
+    if (bd->pFontSampler)           { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
+    if (bd->pFontTextureView)       { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well.
+    if (bd->pIB)                    { bd->pIB->Release(); bd->pIB = nullptr; }
+    if (bd->pVB)                    { bd->pVB->Release(); bd->pVB = nullptr; }
+    if (bd->pBlendState)            { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
+    if (bd->pDepthStencilState)     { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
+    if (bd->pRasterizerState)       { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
+    if (bd->pPixelShader)           { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
+    if (bd->pVertexConstantBuffer)  { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
+    if (bd->pInputLayout)           { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
+    if (bd->pVertexShader)          { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
+}
+
+bool    ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_dx11";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    // Get factory from device
+    IDXGIDevice* pDXGIDevice = nullptr;
+    IDXGIAdapter* pDXGIAdapter = nullptr;
+    IDXGIFactory* pFactory = nullptr;
+
+    if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
+        if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
+            if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
+            {
+                bd->pd3dDevice = device;
+                bd->pd3dDeviceContext = device_context;
+                bd->pFactory = pFactory;
+            }
+    if (pDXGIDevice) pDXGIDevice->Release();
+    if (pDXGIAdapter) pDXGIAdapter->Release();
+    bd->pd3dDevice->AddRef();
+    bd->pd3dDeviceContext->AddRef();
+
+    return true;
+}
+
+void ImGui_ImplDX11_Shutdown()
+{
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplDX11_InvalidateDeviceObjects();
+    if (bd->pFactory)             { bd->pFactory->Release(); }
+    if (bd->pd3dDevice)           { bd->pd3dDevice->Release(); }
+    if (bd->pd3dDeviceContext)    { bd->pd3dDeviceContext->Release(); }
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+void ImGui_ImplDX11_NewFrame()
+{
+    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?");
+
+    if (!bd->pFontSampler)
+        ImGui_ImplDX11_CreateDeviceObjects();
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx11.h b/engines/twp/imgui/backends/imgui_impl_dx11.h
new file mode 100644
index 00000000000..20887f37070
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx11.h
@@ -0,0 +1,32 @@
+// dear imgui: Renderer Backend for DirectX11
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct ID3D11Device;
+struct ID3D11DeviceContext;
+
+IMGUI_IMPL_API bool     ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
+IMGUI_IMPL_API void     ImGui_ImplDX11_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplDX11_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API void     ImGui_ImplDX11_InvalidateDeviceObjects();
+IMGUI_IMPL_API bool     ImGui_ImplDX11_CreateDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx12.cpp b/engines/twp/imgui/backends/imgui_impl_dx12.cpp
new file mode 100644
index 00000000000..15aadc05807
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx12.cpp
@@ -0,0 +1,761 @@
+// dear imgui: Renderer Backend for DirectX12
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
+// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*.
+// To build this on 32-bit systems:
+// - [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file)
+// - [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like.
+// - [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!)
+// - [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file)
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-02-18: DirectX12: Change blending equation to preserve alpha in output buffer.
+//  2021-01-11: DirectX12: Improve Windows 7 compatibility (for D3D12On7) by loading d3d12.dll dynamically.
+//  2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning.
+//  2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID.
+//  2019-10-18: DirectX12: *BREAKING CHANGE* Added extra ID3D12DescriptorHeap parameter to ImGui_ImplDX12_Init() function.
+//  2019-05-29: DirectX12: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: DirectX12: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2019-03-29: Misc: Various minor tidying up.
+//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-06-12: DirectX12: Moved the ID3D12GraphicsCommandList* parameter from NewFrame() to RenderDrawData().
+//  2018-06-08: Misc: Extracted imgui_impl_dx12.cpp/.h away from the old combined DX12+Win32 example.
+//  2018-06-08: DirectX12: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle (to ease support for future multi-viewport).
+//  2018-02-22: Merged into master with all Win32 code synchronized to other examples.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_dx12.h"
+
+// DirectX
+#include <d3d12.h>
+#include <dxgi1_4.h>
+#include <d3dcompiler.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
+#endif
+
+// DirectX data
+struct ImGui_ImplDX12_RenderBuffers;
+struct ImGui_ImplDX12_Data
+{
+    ID3D12Device*               pd3dDevice;
+    ID3D12RootSignature*        pRootSignature;
+    ID3D12PipelineState*        pPipelineState;
+    DXGI_FORMAT                 RTVFormat;
+    ID3D12Resource*             pFontTextureResource;
+    D3D12_CPU_DESCRIPTOR_HANDLE hFontSrvCpuDescHandle;
+    D3D12_GPU_DESCRIPTOR_HANDLE hFontSrvGpuDescHandle;
+    ID3D12DescriptorHeap*       pd3dSrvDescHeap;
+    UINT                        numFramesInFlight;
+
+    ImGui_ImplDX12_RenderBuffers* pFrameResources;
+    UINT                        frameIndex;
+
+    ImGui_ImplDX12_Data()       { memset((void*)this, 0, sizeof(*this)); frameIndex = UINT_MAX; }
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Buffers used during the rendering of a frame
+struct ImGui_ImplDX12_RenderBuffers
+{
+    ID3D12Resource*     IndexBuffer;
+    ID3D12Resource*     VertexBuffer;
+    int                 IndexBufferSize;
+    int                 VertexBufferSize;
+};
+
+struct VERTEX_CONSTANT_BUFFER_DX12
+{
+    float   mvp[4][4];
+};
+
+// Functions
+static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx, ImGui_ImplDX12_RenderBuffers* fr)
+{
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+
+    // Setup orthographic projection matrix into our constant buffer
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
+    VERTEX_CONSTANT_BUFFER_DX12 vertex_constant_buffer;
+    {
+        float L = draw_data->DisplayPos.x;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+        float T = draw_data->DisplayPos.y;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+        float mvp[4][4] =
+        {
+            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
+            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
+            { 0.0f,         0.0f,           0.5f,       0.0f },
+            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
+        };
+        memcpy(&vertex_constant_buffer.mvp, mvp, sizeof(mvp));
+    }
+
+    // Setup viewport
+    D3D12_VIEWPORT vp;
+    memset(&vp, 0, sizeof(D3D12_VIEWPORT));
+    vp.Width = draw_data->DisplaySize.x;
+    vp.Height = draw_data->DisplaySize.y;
+    vp.MinDepth = 0.0f;
+    vp.MaxDepth = 1.0f;
+    vp.TopLeftX = vp.TopLeftY = 0.0f;
+    ctx->RSSetViewports(1, &vp);
+
+    // Bind shader and vertex buffers
+    unsigned int stride = sizeof(ImDrawVert);
+    unsigned int offset = 0;
+    D3D12_VERTEX_BUFFER_VIEW vbv;
+    memset(&vbv, 0, sizeof(D3D12_VERTEX_BUFFER_VIEW));
+    vbv.BufferLocation = fr->VertexBuffer->GetGPUVirtualAddress() + offset;
+    vbv.SizeInBytes = fr->VertexBufferSize * stride;
+    vbv.StrideInBytes = stride;
+    ctx->IASetVertexBuffers(0, 1, &vbv);
+    D3D12_INDEX_BUFFER_VIEW ibv;
+    memset(&ibv, 0, sizeof(D3D12_INDEX_BUFFER_VIEW));
+    ibv.BufferLocation = fr->IndexBuffer->GetGPUVirtualAddress();
+    ibv.SizeInBytes = fr->IndexBufferSize * sizeof(ImDrawIdx);
+    ibv.Format = sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
+    ctx->IASetIndexBuffer(&ibv);
+    ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+    ctx->SetPipelineState(bd->pPipelineState);
+    ctx->SetGraphicsRootSignature(bd->pRootSignature);
+    ctx->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0);
+
+    // Setup blend factor
+    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
+    ctx->OMSetBlendFactor(blend_factor);
+}
+
+template<typename T>
+static inline void SafeRelease(T*& res)
+{
+    if (res)
+        res->Release();
+    res = nullptr;
+}
+
+// Render function
+void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    // FIXME: I'm assuming that this only gets called once per frame!
+    // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    bd->frameIndex = bd->frameIndex + 1;
+    ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[bd->frameIndex % bd->numFramesInFlight];
+
+    // Create and grow vertex/index buffers if needed
+    if (fr->VertexBuffer == nullptr || fr->VertexBufferSize < draw_data->TotalVtxCount)
+    {
+        SafeRelease(fr->VertexBuffer);
+        fr->VertexBufferSize = draw_data->TotalVtxCount + 5000;
+        D3D12_HEAP_PROPERTIES props;
+        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
+        props.Type = D3D12_HEAP_TYPE_UPLOAD;
+        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
+        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
+        D3D12_RESOURCE_DESC desc;
+        memset(&desc, 0, sizeof(D3D12_RESOURCE_DESC));
+        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+        desc.Width = fr->VertexBufferSize * sizeof(ImDrawVert);
+        desc.Height = 1;
+        desc.DepthOrArraySize = 1;
+        desc.MipLevels = 1;
+        desc.Format = DXGI_FORMAT_UNKNOWN;
+        desc.SampleDesc.Count = 1;
+        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+        if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fr->VertexBuffer)) < 0)
+            return;
+    }
+    if (fr->IndexBuffer == nullptr || fr->IndexBufferSize < draw_data->TotalIdxCount)
+    {
+        SafeRelease(fr->IndexBuffer);
+        fr->IndexBufferSize = draw_data->TotalIdxCount + 10000;
+        D3D12_HEAP_PROPERTIES props;
+        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
+        props.Type = D3D12_HEAP_TYPE_UPLOAD;
+        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
+        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
+        D3D12_RESOURCE_DESC desc;
+        memset(&desc, 0, sizeof(D3D12_RESOURCE_DESC));
+        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+        desc.Width = fr->IndexBufferSize * sizeof(ImDrawIdx);
+        desc.Height = 1;
+        desc.DepthOrArraySize = 1;
+        desc.MipLevels = 1;
+        desc.Format = DXGI_FORMAT_UNKNOWN;
+        desc.SampleDesc.Count = 1;
+        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+        if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fr->IndexBuffer)) < 0)
+            return;
+    }
+
+    // Upload vertex/index data into a single contiguous GPU buffer
+    void* vtx_resource, *idx_resource;
+    D3D12_RANGE range;
+    memset(&range, 0, sizeof(D3D12_RANGE));
+    if (fr->VertexBuffer->Map(0, &range, &vtx_resource) != S_OK)
+        return;
+    if (fr->IndexBuffer->Map(0, &range, &idx_resource) != S_OK)
+        return;
+    ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource;
+    ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+        vtx_dst += cmd_list->VtxBuffer.Size;
+        idx_dst += cmd_list->IdxBuffer.Size;
+    }
+    fr->VertexBuffer->Unmap(0, &range);
+    fr->IndexBuffer->Unmap(0, &range);
+
+    // Setup desired DX state
+    ImGui_ImplDX12_SetupRenderState(draw_data, ctx, fr);
+
+    // Render command lists
+    // (Because we merged all buffers into a single one, we maintain our own offset into them)
+    int global_vtx_offset = 0;
+    int global_idx_offset = 0;
+    ImVec2 clip_off = draw_data->DisplayPos;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback != nullptr)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplDX12_SetupRenderState(draw_data, ctx, fr);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
+                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply Scissor/clipping rectangle, Bind texture, Draw
+                const D3D12_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
+                D3D12_GPU_DESCRIPTOR_HANDLE texture_handle = {};
+                texture_handle.ptr = (UINT64)pcmd->GetTexID();
+                ctx->SetGraphicsRootDescriptorTable(1, texture_handle);
+                ctx->RSSetScissorRects(1, &r);
+                ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);
+            }
+        }
+        global_idx_offset += cmd_list->IdxBuffer.Size;
+        global_vtx_offset += cmd_list->VtxBuffer.Size;
+    }
+}
+
+static void ImGui_ImplDX12_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+    // Upload texture to graphics system
+    {
+        D3D12_HEAP_PROPERTIES props;
+        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
+        props.Type = D3D12_HEAP_TYPE_DEFAULT;
+        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
+        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
+
+        D3D12_RESOURCE_DESC desc;
+        ZeroMemory(&desc, sizeof(desc));
+        desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+        desc.Alignment = 0;
+        desc.Width = width;
+        desc.Height = height;
+        desc.DepthOrArraySize = 1;
+        desc.MipLevels = 1;
+        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        desc.SampleDesc.Count = 1;
+        desc.SampleDesc.Quality = 0;
+        desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+
+        ID3D12Resource* pTexture = nullptr;
+        bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
+            D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture));
+
+        UINT uploadPitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u);
+        UINT uploadSize = height * uploadPitch;
+        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+        desc.Alignment = 0;
+        desc.Width = uploadSize;
+        desc.Height = 1;
+        desc.DepthOrArraySize = 1;
+        desc.MipLevels = 1;
+        desc.Format = DXGI_FORMAT_UNKNOWN;
+        desc.SampleDesc.Count = 1;
+        desc.SampleDesc.Quality = 0;
+        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+
+        props.Type = D3D12_HEAP_TYPE_UPLOAD;
+        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
+        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
+
+        ID3D12Resource* uploadBuffer = nullptr;
+        HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
+            D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer));
+        IM_ASSERT(SUCCEEDED(hr));
+
+        void* mapped = nullptr;
+        D3D12_RANGE range = { 0, uploadSize };
+        hr = uploadBuffer->Map(0, &range, &mapped);
+        IM_ASSERT(SUCCEEDED(hr));
+        for (int y = 0; y < height; y++)
+            memcpy((void*) ((uintptr_t) mapped + y * uploadPitch), pixels + y * width * 4, width * 4);
+        uploadBuffer->Unmap(0, &range);
+
+        D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
+        srcLocation.pResource = uploadBuffer;
+        srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
+        srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        srcLocation.PlacedFootprint.Footprint.Width = width;
+        srcLocation.PlacedFootprint.Footprint.Height = height;
+        srcLocation.PlacedFootprint.Footprint.Depth = 1;
+        srcLocation.PlacedFootprint.Footprint.RowPitch = uploadPitch;
+
+        D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
+        dstLocation.pResource = pTexture;
+        dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+        dstLocation.SubresourceIndex = 0;
+
+        D3D12_RESOURCE_BARRIER barrier = {};
+        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier.Transition.pResource   = pTexture;
+        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
+        barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
+
+        ID3D12Fence* fence = nullptr;
+        hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
+        IM_ASSERT(SUCCEEDED(hr));
+
+        HANDLE event = CreateEvent(0, 0, 0, 0);
+        IM_ASSERT(event != nullptr);
+
+        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
+        queueDesc.Type     = D3D12_COMMAND_LIST_TYPE_DIRECT;
+        queueDesc.Flags    = D3D12_COMMAND_QUEUE_FLAG_NONE;
+        queueDesc.NodeMask = 1;
+
+        ID3D12CommandQueue* cmdQueue = nullptr;
+        hr = bd->pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue));
+        IM_ASSERT(SUCCEEDED(hr));
+
+        ID3D12CommandAllocator* cmdAlloc = nullptr;
+        hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
+        IM_ASSERT(SUCCEEDED(hr));
+
+        ID3D12GraphicsCommandList* cmdList = nullptr;
+        hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList));
+        IM_ASSERT(SUCCEEDED(hr));
+
+        cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr);
+        cmdList->ResourceBarrier(1, &barrier);
+
+        hr = cmdList->Close();
+        IM_ASSERT(SUCCEEDED(hr));
+
+        cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList);
+        hr = cmdQueue->Signal(fence, 1);
+        IM_ASSERT(SUCCEEDED(hr));
+
+        fence->SetEventOnCompletion(1, event);
+        WaitForSingleObject(event, INFINITE);
+
+        cmdList->Release();
+        cmdAlloc->Release();
+        cmdQueue->Release();
+        CloseHandle(event);
+        fence->Release();
+        uploadBuffer->Release();
+
+        // Create texture view
+        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
+        ZeroMemory(&srvDesc, sizeof(srvDesc));
+        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+        srvDesc.Texture2D.MipLevels = desc.MipLevels;
+        srvDesc.Texture2D.MostDetailedMip = 0;
+        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
+        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, bd->hFontSrvCpuDescHandle);
+        SafeRelease(bd->pFontTextureResource);
+        bd->pFontTextureResource = pTexture;
+    }
+
+    // Store our identifier
+    // READ THIS IF THE STATIC_ASSERT() TRIGGERS:
+    // - Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
+    // - This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*.
+    // [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file)
+    // [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like.
+    // [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!)
+    // [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file)
+    static_assert(sizeof(ImTextureID) >= sizeof(bd->hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
+    io.Fonts->SetTexID((ImTextureID)bd->hFontSrvGpuDescHandle.ptr);
+}
+
+bool    ImGui_ImplDX12_CreateDeviceObjects()
+{
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    if (!bd || !bd->pd3dDevice)
+        return false;
+    if (bd->pPipelineState)
+        ImGui_ImplDX12_InvalidateDeviceObjects();
+
+    // Create the root signature
+    {
+        D3D12_DESCRIPTOR_RANGE descRange = {};
+        descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+        descRange.NumDescriptors = 1;
+        descRange.BaseShaderRegister = 0;
+        descRange.RegisterSpace = 0;
+        descRange.OffsetInDescriptorsFromTableStart = 0;
+
+        D3D12_ROOT_PARAMETER param[2] = {};
+
+        param[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
+        param[0].Constants.ShaderRegister = 0;
+        param[0].Constants.RegisterSpace = 0;
+        param[0].Constants.Num32BitValues = 16;
+        param[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
+
+        param[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+        param[1].DescriptorTable.NumDescriptorRanges = 1;
+        param[1].DescriptorTable.pDescriptorRanges = &descRange;
+        param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
+
+        // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
+        D3D12_STATIC_SAMPLER_DESC staticSampler = {};
+        staticSampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
+        staticSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
+        staticSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
+        staticSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
+        staticSampler.MipLODBias = 0.f;
+        staticSampler.MaxAnisotropy = 0;
+        staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
+        staticSampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
+        staticSampler.MinLOD = 0.f;
+        staticSampler.MaxLOD = 0.f;
+        staticSampler.ShaderRegister = 0;
+        staticSampler.RegisterSpace = 0;
+        staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
+
+        D3D12_ROOT_SIGNATURE_DESC desc = {};
+        desc.NumParameters = _countof(param);
+        desc.pParameters = param;
+        desc.NumStaticSamplers = 1;
+        desc.pStaticSamplers = &staticSampler;
+        desc.Flags =
+            D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
+            D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
+            D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
+            D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS;
+
+        // Load d3d12.dll and D3D12SerializeRootSignature() function address dynamically to facilitate using with D3D12On7.
+        // See if any version of d3d12.dll is already loaded in the process. If so, give preference to that.
+        static HINSTANCE d3d12_dll = ::GetModuleHandleA("d3d12.dll");
+        if (d3d12_dll == nullptr)
+        {
+            // Attempt to load d3d12.dll from local directories. This will only succeed if
+            // (1) the current OS is Windows 7, and
+            // (2) there exists a version of d3d12.dll for Windows 7 (D3D12On7) in one of the following directories.
+            // See https://github.com/ocornut/imgui/pull/3696 for details.
+            const char* localD3d12Paths[] = { ".\\d3d12.dll", ".\\d3d12on7\\d3d12.dll", ".\\12on7\\d3d12.dll" }; // A. current directory, B. used by some games, C. used in Microsoft D3D12On7 sample
+            for (int i = 0; i < IM_ARRAYSIZE(localD3d12Paths); i++)
+                if ((d3d12_dll = ::LoadLibraryA(localD3d12Paths[i])) != nullptr)
+                    break;
+
+            // If failed, we are on Windows >= 10.
+            if (d3d12_dll == nullptr)
+                d3d12_dll = ::LoadLibraryA("d3d12.dll");
+
+            if (d3d12_dll == nullptr)
+                return false;
+        }
+
+        PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignatureFn = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)::GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature");
+        if (D3D12SerializeRootSignatureFn == nullptr)
+            return false;
+
+        ID3DBlob* blob = nullptr;
+        if (D3D12SerializeRootSignatureFn(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, nullptr) != S_OK)
+            return false;
+
+        bd->pd3dDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&bd->pRootSignature));
+        blob->Release();
+    }
+
+    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
+    // If you would like to use this DX12 sample code but remove this dependency you can:
+    //  1) compile once, save the compiled shader blobs into a file or source code and assign them to psoDesc.VS/PS [preferred solution]
+    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
+    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
+
+    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
+    memset(&psoDesc, 0, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
+    psoDesc.NodeMask = 1;
+    psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
+    psoDesc.pRootSignature = bd->pRootSignature;
+    psoDesc.SampleMask = UINT_MAX;
+    psoDesc.NumRenderTargets = 1;
+    psoDesc.RTVFormats[0] = bd->RTVFormat;
+    psoDesc.SampleDesc.Count = 1;
+    psoDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
+
+    ID3DBlob* vertexShaderBlob;
+    ID3DBlob* pixelShaderBlob;
+
+    // Create the vertex shader
+    {
+        static const char* vertexShader =
+            "cbuffer vertexBuffer : register(b0) \
+            {\
+              float4x4 ProjectionMatrix; \
+            };\
+            struct VS_INPUT\
+            {\
+              float2 pos : POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            struct PS_INPUT\
+            {\
+              float4 pos : SV_POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            \
+            PS_INPUT main(VS_INPUT input)\
+            {\
+              PS_INPUT output;\
+              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
+              output.col = input.col;\
+              output.uv  = input.uv;\
+              return output;\
+            }";
+
+        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vertexShaderBlob, nullptr)))
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        psoDesc.VS = { vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize() };
+
+        // Create the input layout
+        static D3D12_INPUT_ELEMENT_DESC local_layout[] =
+        {
+            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
+        };
+        psoDesc.InputLayout = { local_layout, 3 };
+    }
+
+    // Create the pixel shader
+    {
+        static const char* pixelShader =
+            "struct PS_INPUT\
+            {\
+              float4 pos : SV_POSITION;\
+              float4 col : COLOR0;\
+              float2 uv  : TEXCOORD0;\
+            };\
+            SamplerState sampler0 : register(s0);\
+            Texture2D texture0 : register(t0);\
+            \
+            float4 main(PS_INPUT input) : SV_Target\
+            {\
+              float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
+              return out_col; \
+            }";
+
+        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &pixelShaderBlob, nullptr)))
+        {
+            vertexShaderBlob->Release();
+            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
+        }
+        psoDesc.PS = { pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize() };
+    }
+
+    // Create the blending setup
+    {
+        D3D12_BLEND_DESC& desc = psoDesc.BlendState;
+        desc.AlphaToCoverageEnable = false;
+        desc.RenderTarget[0].BlendEnable = true;
+        desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
+        desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
+        desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
+        desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
+        desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
+        desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
+        desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
+    }
+
+    // Create the rasterizer state
+    {
+        D3D12_RASTERIZER_DESC& desc = psoDesc.RasterizerState;
+        desc.FillMode = D3D12_FILL_MODE_SOLID;
+        desc.CullMode = D3D12_CULL_MODE_NONE;
+        desc.FrontCounterClockwise = FALSE;
+        desc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
+        desc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
+        desc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
+        desc.DepthClipEnable = true;
+        desc.MultisampleEnable = FALSE;
+        desc.AntialiasedLineEnable = FALSE;
+        desc.ForcedSampleCount = 0;
+        desc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
+    }
+
+    // Create depth-stencil State
+    {
+        D3D12_DEPTH_STENCIL_DESC& desc = psoDesc.DepthStencilState;
+        desc.DepthEnable = false;
+        desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
+        desc.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
+        desc.StencilEnable = false;
+        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
+        desc.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
+        desc.BackFace = desc.FrontFace;
+    }
+
+    HRESULT result_pipeline_state = bd->pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&bd->pPipelineState));
+    vertexShaderBlob->Release();
+    pixelShaderBlob->Release();
+    if (result_pipeline_state != S_OK)
+        return false;
+
+    ImGui_ImplDX12_CreateFontsTexture();
+
+    return true;
+}
+
+void    ImGui_ImplDX12_InvalidateDeviceObjects()
+{
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    if (!bd || !bd->pd3dDevice)
+        return;
+
+    ImGuiIO& io = ImGui::GetIO();
+    SafeRelease(bd->pRootSignature);
+    SafeRelease(bd->pPipelineState);
+    SafeRelease(bd->pFontTextureResource);
+    io.Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
+
+    for (UINT i = 0; i < bd->numFramesInFlight; i++)
+    {
+        ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i];
+        SafeRelease(fr->IndexBuffer);
+        SafeRelease(fr->VertexBuffer);
+    }
+}
+
+bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap,
+                         D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_dx12";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    bd->pd3dDevice = device;
+    bd->RTVFormat = rtv_format;
+    bd->hFontSrvCpuDescHandle = font_srv_cpu_desc_handle;
+    bd->hFontSrvGpuDescHandle = font_srv_gpu_desc_handle;
+    bd->pFrameResources = new ImGui_ImplDX12_RenderBuffers[num_frames_in_flight];
+    bd->numFramesInFlight = num_frames_in_flight;
+    bd->pd3dSrvDescHeap = cbv_srv_heap;
+    bd->frameIndex = UINT_MAX;
+
+    // Create buffers with a default size (they will later be grown as needed)
+    for (int i = 0; i < num_frames_in_flight; i++)
+    {
+        ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i];
+        fr->IndexBuffer = nullptr;
+        fr->VertexBuffer = nullptr;
+        fr->IndexBufferSize = 10000;
+        fr->VertexBufferSize = 5000;
+    }
+
+    return true;
+}
+
+void ImGui_ImplDX12_Shutdown()
+{
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Clean up windows and device objects
+    ImGui_ImplDX12_InvalidateDeviceObjects();
+    delete[] bd->pFrameResources;
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+void ImGui_ImplDX12_NewFrame()
+{
+    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX12_Init()?");
+
+    if (!bd->pPipelineState)
+        ImGui_ImplDX12_CreateDeviceObjects();
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx12.h b/engines/twp/imgui/backends/imgui_impl_dx12.h
new file mode 100644
index 00000000000..8710910b4ce
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx12.h
@@ -0,0 +1,44 @@
+// dear imgui: Renderer Backend for DirectX12
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
+// See imgui_impl_dx12.cpp file for details.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+#include <dxgiformat.h> // DXGI_FORMAT
+
+struct ID3D12Device;
+struct ID3D12DescriptorHeap;
+struct ID3D12GraphicsCommandList;
+struct D3D12_CPU_DESCRIPTOR_HANDLE;
+struct D3D12_GPU_DESCRIPTOR_HANDLE;
+
+// cmd_list is the command list that the implementation will use to render imgui draw lists.
+// Before calling the render function, caller must prepare cmd_list by resetting it and setting the appropriate
+// render target and descriptor heap that contains font_srv_cpu_desc_handle/font_srv_gpu_desc_handle.
+// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture.
+IMGUI_IMPL_API bool     ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap,
+                                            D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle);
+IMGUI_IMPL_API void     ImGui_ImplDX12_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplDX12_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API void     ImGui_ImplDX12_InvalidateDeviceObjects();
+IMGUI_IMPL_API bool     ImGui_ImplDX12_CreateDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx9.cpp b/engines/twp/imgui/backends/imgui_impl_dx9.cpp
new file mode 100644
index 00000000000..48fee4bd5b2
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx9.cpp
@@ -0,0 +1,388 @@
+// dear imgui: Renderer Backend for DirectX9
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
+//  2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
+//  2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
+//  2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
+//  2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
+//  2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
+//  2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288.
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example.
+//  2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_dx9.h"
+
+// DirectX
+#include <d3d9.h>
+
+// DirectX data
+struct ImGui_ImplDX9_Data
+{
+    LPDIRECT3DDEVICE9           pd3dDevice;
+    LPDIRECT3DVERTEXBUFFER9     pVB;
+    LPDIRECT3DINDEXBUFFER9      pIB;
+    LPDIRECT3DTEXTURE9          FontTexture;
+    int                         VertexBufferSize;
+    int                         IndexBufferSize;
+
+    ImGui_ImplDX9_Data()        { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
+};
+
+struct CUSTOMVERTEX
+{
+    float    pos[3];
+    D3DCOLOR col;
+    float    uv[2];
+};
+#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
+
+#ifdef IMGUI_USE_BGRA_PACKED_COLOR
+#define IMGUI_COL_TO_DX9_ARGB(_COL)     (_COL)
+#else
+#define IMGUI_COL_TO_DX9_ARGB(_COL)     (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
+#endif
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Functions
+static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
+{
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+
+    // Setup viewport
+    D3DVIEWPORT9 vp;
+    vp.X = vp.Y = 0;
+    vp.Width = (DWORD)draw_data->DisplaySize.x;
+    vp.Height = (DWORD)draw_data->DisplaySize.y;
+    vp.MinZ = 0.0f;
+    vp.MaxZ = 1.0f;
+    bd->pd3dDevice->SetViewport(&vp);
+
+    // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
+    bd->pd3dDevice->SetPixelShader(nullptr);
+    bd->pd3dDevice->SetVertexShader(nullptr);
+    bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
+    bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
+    bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
+    bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
+    bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
+    bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
+    bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+    bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
+    bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
+    bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
+    bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
+    bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
+    bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE);
+    bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
+    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
+    bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
+    bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
+    bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
+    bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
+
+    // Setup orthographic projection matrix
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+    // Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH()
+    {
+        float L = draw_data->DisplayPos.x + 0.5f;
+        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
+        float T = draw_data->DisplayPos.y + 0.5f;
+        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
+        D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f, 1.0f } } };
+        D3DMATRIX mat_projection =
+        { { {
+            2.0f/(R-L),   0.0f,         0.0f,  0.0f,
+            0.0f,         2.0f/(T-B),   0.0f,  0.0f,
+            0.0f,         0.0f,         0.5f,  0.0f,
+            (L+R)/(L-R),  (T+B)/(B-T),  0.5f,  1.0f
+        } } };
+        bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
+        bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
+        bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
+    }
+}
+
+// Render function.
+void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized
+    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
+        return;
+
+    // Create and grow buffers if needed
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
+    {
+        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
+        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
+        if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
+            return;
+    }
+    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
+    {
+        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
+        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
+        if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
+            return;
+    }
+
+    // Backup the DX9 state
+    IDirect3DStateBlock9* d3d9_state_block = nullptr;
+    if (bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
+        return;
+    if (d3d9_state_block->Capture() < 0)
+    {
+        d3d9_state_block->Release();
+        return;
+    }
+
+    // Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
+    D3DMATRIX last_world, last_view, last_projection;
+    bd->pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
+    bd->pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
+    bd->pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
+
+    // Allocate buffers
+    CUSTOMVERTEX* vtx_dst;
+    ImDrawIdx* idx_dst;
+    if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
+    {
+        d3d9_state_block->Release();
+        return;
+    }
+    if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
+    {
+        bd->pVB->Unlock();
+        d3d9_state_block->Release();
+        return;
+    }
+
+    // Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
+    // FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
+    //  1) to avoid repacking colors:   #define IMGUI_USE_BGRA_PACKED_COLOR
+    //  2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
+        for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
+        {
+            vtx_dst->pos[0] = vtx_src->pos.x;
+            vtx_dst->pos[1] = vtx_src->pos.y;
+            vtx_dst->pos[2] = 0.0f;
+            vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
+            vtx_dst->uv[0] = vtx_src->uv.x;
+            vtx_dst->uv[1] = vtx_src->uv.y;
+            vtx_dst++;
+            vtx_src++;
+        }
+        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+        idx_dst += cmd_list->IdxBuffer.Size;
+    }
+    bd->pVB->Unlock();
+    bd->pIB->Unlock();
+    bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
+    bd->pd3dDevice->SetIndices(bd->pIB);
+    bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
+
+    // Setup desired DX state
+    ImGui_ImplDX9_SetupRenderState(draw_data);
+
+    // Render command lists
+    // (Because we merged all buffers into a single one, we maintain our own offset into them)
+    int global_vtx_offset = 0;
+    int global_idx_offset = 0;
+    ImVec2 clip_off = draw_data->DisplayPos;
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback != nullptr)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplDX9_SetupRenderState(draw_data);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
+                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply Scissor/clipping rectangle, Bind texture, Draw
+                const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
+                const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
+                bd->pd3dDevice->SetTexture(0, texture);
+                bd->pd3dDevice->SetScissorRect(&r);
+                bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
+            }
+        }
+        global_idx_offset += cmd_list->IdxBuffer.Size;
+        global_vtx_offset += cmd_list->VtxBuffer.Size;
+    }
+
+    // Restore the DX9 transform
+    bd->pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
+    bd->pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
+    bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
+
+    // Restore the DX9 state
+    d3d9_state_block->Apply();
+    d3d9_state_block->Release();
+}
+
+bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_dx9";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    bd->pd3dDevice = device;
+    bd->pd3dDevice->AddRef();
+
+    return true;
+}
+
+void ImGui_ImplDX9_Shutdown()
+{
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplDX9_InvalidateDeviceObjects();
+    if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+static bool ImGui_ImplDX9_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    unsigned char* pixels;
+    int width, height, bytes_per_pixel;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
+
+    // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
+#ifndef IMGUI_USE_BGRA_PACKED_COLOR
+    if (io.Fonts->TexPixelsUseColors)
+    {
+        ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel);
+        for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++)
+            *dst = IMGUI_COL_TO_DX9_ARGB(*src);
+        pixels = (unsigned char*)dst_start;
+    }
+#endif
+
+    // Upload texture to graphics system
+    bd->FontTexture = nullptr;
+    if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0)
+        return false;
+    D3DLOCKED_RECT tex_locked_rect;
+    if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK)
+        return false;
+    for (int y = 0; y < height; y++)
+        memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel);
+    bd->FontTexture->UnlockRect(0);
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)bd->FontTexture);
+
+#ifndef IMGUI_USE_BGRA_PACKED_COLOR
+    if (io.Fonts->TexPixelsUseColors)
+        ImGui::MemFree(pixels);
+#endif
+
+    return true;
+}
+
+bool ImGui_ImplDX9_CreateDeviceObjects()
+{
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    if (!bd || !bd->pd3dDevice)
+        return false;
+    if (!ImGui_ImplDX9_CreateFontsTexture())
+        return false;
+    return true;
+}
+
+void ImGui_ImplDX9_InvalidateDeviceObjects()
+{
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    if (!bd || !bd->pd3dDevice)
+        return;
+    if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
+    if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
+    if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
+}
+
+void ImGui_ImplDX9_NewFrame()
+{
+    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX9_Init()?");
+
+    if (!bd->FontTexture)
+        ImGui_ImplDX9_CreateDeviceObjects();
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx9.h b/engines/twp/imgui/backends/imgui_impl_dx9.h
new file mode 100644
index 00000000000..3965583bd86
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_dx9.h
@@ -0,0 +1,31 @@
+// dear imgui: Renderer Backend for DirectX9
+// This needs to be used along with a Platform Backend (e.g. Win32)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct IDirect3DDevice9;
+
+IMGUI_IMPL_API bool     ImGui_ImplDX9_Init(IDirect3DDevice9* device);
+IMGUI_IMPL_API void     ImGui_ImplDX9_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplDX9_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
+
+// Use if you want to reset your rendering device without losing Dear ImGui state.
+IMGUI_IMPL_API bool     ImGui_ImplDX9_CreateDeviceObjects();
+IMGUI_IMPL_API void     ImGui_ImplDX9_InvalidateDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glfw.cpp b/engines/twp/imgui/backends/imgui_impl_glfw.cpp
new file mode 100644
index 00000000000..b49c99bb883
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_glfw.cpp
@@ -0,0 +1,856 @@
+// dear imgui: Platform Backend for GLFW
+// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
+// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
+// (Requires: GLFW 3.1+. Prefer GLFW 3.3+ or GLFW 3.4+ for full feature support.)
+
+// Implemented features:
+//  [X] Platform: Clipboard support.
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only).
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2023-12-19: Emscripten: Added ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback() to register canvas selector and auto-resize GLFW window.
+//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys.
+//  2023-07-18: Inputs: Revert ignoring mouse data on GLFW_CURSOR_DISABLED as it can be used differently. User may set ImGuiConfigFLags_NoMouse if desired. (#5625, #6609)
+//  2023-06-12: Accept glfwGetTime() not returning a monotonically increasing value. This seems to happens on some Windows setup when peripherals disconnect, and is likely to also happen on browser + Emscripten. (#6491)
+//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen on Windows ONLY, using a custom WndProc hook. (#2702)
+//  2023-03-16: Inputs: Fixed key modifiers handling on secondary viewports (docking branch). Broken on 2023/01/04. (#6248, #6034)
+//  2023-03-14: Emscripten: Avoid using glfwGetError() and glfwGetGamepadState() which are not correctly implemented in Emscripten emulation. (#6240)
+//  2023-02-03: Emscripten: Registering custom low-level mouse wheel handler to get more accurate scrolling impulses on Emscripten. (#4019, #6096)
+//  2023-01-04: Inputs: Fixed mods state on Linux when using Alt-GR text input (e.g. German keyboard layout), could lead to broken text input. Revert a 2022/01/17 change were we resumed using mods provided by GLFW, turns out they were faulty.
+//  2022-11-22: Perform a dummy glfwGetError() read to cancel missing names with glfwGetKeyName(). (#5908)
+//  2022-10-18: Perform a dummy glfwGetError() read to cancel missing mouse cursors errors. Using GLFW_VERSION_COMBINED directly. (#5785)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-09-01: Inputs: Honor GLFW_CURSOR_DISABLED by not setting mouse position *EDIT* Reverted 2023-07-18.
+//  2022-04-30: Inputs: Fixed ImGui_ImplGlfw_TranslateUntranslatedKey() for lower case letters on OSX.
+//  2022-03-23: Inputs: Fixed a regression in 1.87 which resulted in keyboard modifiers events being reported incorrectly on Linux/X11.
+//  2022-02-07: Added ImGui_ImplGlfw_InstallCallbacks()/ImGui_ImplGlfw_RestoreCallbacks() helpers to facilitate user installing callbacks after initializing backend.
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
+//  2022-01-12: *BREAKING CHANGE*: Now using glfwSetCursorPosCallback(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetCursorPosCallback() and forward it to the backend via ImGui_ImplGlfw_CursorPosCallback().
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2022-01-05: Inputs: Converting GLFW untranslated keycodes back to translated keycodes (in the ImGui_ImplGlfw_KeyCallback() function) in order to match the behavior of every other backend, and facilitate the use of GLFW with lettered-shortcuts API.
+//  2021-08-17: *BREAKING CHANGE*: Now using glfwSetWindowFocusCallback() to calling io.AddFocusEvent(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() and forward it to the backend via ImGui_ImplGlfw_WindowFocusCallback().
+//  2021-07-29: *BREAKING CHANGE*: Now using glfwSetCursorEnterCallback(). MousePos is correctly reported when the host platform window is hovered but not focused. If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() callback and forward it to the backend via ImGui_ImplGlfw_CursorEnterCallback().
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors.
+//  2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor).
+//  2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown.
+//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
+//  2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter().
+//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
+//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
+//  2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them.
+//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
+//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
+//  2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples.
+//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
+//  2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()).
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
+//  2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set.
+//  2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
+//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
+//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
+//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
+//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_glfw.h"
+
+// Clang warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wold-style-cast"     // warning: use of old-style cast
+#pragma clang diagnostic ignored "-Wsign-conversion"    // warning: implicit conversion changes signedness
+#endif
+
+// GLFW
+#include <GLFW/glfw3.h>
+
+#ifdef _WIN32
+#undef APIENTRY
+#define GLFW_EXPOSE_NATIVE_WIN32
+#include <GLFW/glfw3native.h>   // for glfwGetWin32Window()
+#endif
+#ifdef __APPLE__
+#define GLFW_EXPOSE_NATIVE_COCOA
+#include <GLFW/glfw3native.h>   // for glfwGetCocoaWindow()
+#endif
+
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#endif
+
+// We gather version tests as define in order to easily see which features are version-dependent.
+#define GLFW_VERSION_COMBINED           (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 + GLFW_VERSION_REVISION)
+#ifdef GLFW_RESIZE_NESW_CURSOR          // Let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released?
+#define GLFW_HAS_NEW_CURSORS            (GLFW_VERSION_COMBINED >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR
+#else
+#define GLFW_HAS_NEW_CURSORS            (0)
+#endif
+#define GLFW_HAS_GAMEPAD_API            (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetGamepadState() new api
+#define GLFW_HAS_GETKEYNAME             (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName()
+#define GLFW_HAS_GETERROR               (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError()
+
+// GLFW data
+enum GlfwClientApi
+{
+    GlfwClientApi_Unknown,
+    GlfwClientApi_OpenGL,
+    GlfwClientApi_Vulkan
+};
+
+struct ImGui_ImplGlfw_Data
+{
+    GLFWwindow*             Window;
+    GlfwClientApi           ClientApi;
+    double                  Time;
+    GLFWwindow*             MouseWindow;
+    GLFWcursor*             MouseCursors[ImGuiMouseCursor_COUNT];
+    ImVec2                  LastValidMousePos;
+    bool                    InstalledCallbacks;
+    bool                    CallbacksChainForAllWindows;
+#ifdef __EMSCRIPTEN__
+    const char*             CanvasSelector;
+#endif
+
+    // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
+    GLFWwindowfocusfun      PrevUserCallbackWindowFocus;
+    GLFWcursorposfun        PrevUserCallbackCursorPos;
+    GLFWcursorenterfun      PrevUserCallbackCursorEnter;
+    GLFWmousebuttonfun      PrevUserCallbackMousebutton;
+    GLFWscrollfun           PrevUserCallbackScroll;
+    GLFWkeyfun              PrevUserCallbackKey;
+    GLFWcharfun             PrevUserCallbackChar;
+    GLFWmonitorfun          PrevUserCallbackMonitor;
+#ifdef _WIN32
+    WNDPROC                 GlfwWndProc;
+#endif
+
+    ImGui_ImplGlfw_Data()   { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
+// - Because glfwPollEvents() process all windows and some events may be called outside of it, you will need to register your own callbacks
+//   (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks.
+// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it.
+// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
+static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
+}
+
+// Functions
+static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
+{
+    return glfwGetClipboardString((GLFWwindow*)user_data);
+}
+
+static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
+{
+    glfwSetClipboardString((GLFWwindow*)user_data, text);
+}
+
+static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
+{
+    switch (key)
+    {
+        case GLFW_KEY_TAB: return ImGuiKey_Tab;
+        case GLFW_KEY_LEFT: return ImGuiKey_LeftArrow;
+        case GLFW_KEY_RIGHT: return ImGuiKey_RightArrow;
+        case GLFW_KEY_UP: return ImGuiKey_UpArrow;
+        case GLFW_KEY_DOWN: return ImGuiKey_DownArrow;
+        case GLFW_KEY_PAGE_UP: return ImGuiKey_PageUp;
+        case GLFW_KEY_PAGE_DOWN: return ImGuiKey_PageDown;
+        case GLFW_KEY_HOME: return ImGuiKey_Home;
+        case GLFW_KEY_END: return ImGuiKey_End;
+        case GLFW_KEY_INSERT: return ImGuiKey_Insert;
+        case GLFW_KEY_DELETE: return ImGuiKey_Delete;
+        case GLFW_KEY_BACKSPACE: return ImGuiKey_Backspace;
+        case GLFW_KEY_SPACE: return ImGuiKey_Space;
+        case GLFW_KEY_ENTER: return ImGuiKey_Enter;
+        case GLFW_KEY_ESCAPE: return ImGuiKey_Escape;
+        case GLFW_KEY_APOSTROPHE: return ImGuiKey_Apostrophe;
+        case GLFW_KEY_COMMA: return ImGuiKey_Comma;
+        case GLFW_KEY_MINUS: return ImGuiKey_Minus;
+        case GLFW_KEY_PERIOD: return ImGuiKey_Period;
+        case GLFW_KEY_SLASH: return ImGuiKey_Slash;
+        case GLFW_KEY_SEMICOLON: return ImGuiKey_Semicolon;
+        case GLFW_KEY_EQUAL: return ImGuiKey_Equal;
+        case GLFW_KEY_LEFT_BRACKET: return ImGuiKey_LeftBracket;
+        case GLFW_KEY_BACKSLASH: return ImGuiKey_Backslash;
+        case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey_RightBracket;
+        case GLFW_KEY_GRAVE_ACCENT: return ImGuiKey_GraveAccent;
+        case GLFW_KEY_CAPS_LOCK: return ImGuiKey_CapsLock;
+        case GLFW_KEY_SCROLL_LOCK: return ImGuiKey_ScrollLock;
+        case GLFW_KEY_NUM_LOCK: return ImGuiKey_NumLock;
+        case GLFW_KEY_PRINT_SCREEN: return ImGuiKey_PrintScreen;
+        case GLFW_KEY_PAUSE: return ImGuiKey_Pause;
+        case GLFW_KEY_KP_0: return ImGuiKey_Keypad0;
+        case GLFW_KEY_KP_1: return ImGuiKey_Keypad1;
+        case GLFW_KEY_KP_2: return ImGuiKey_Keypad2;
+        case GLFW_KEY_KP_3: return ImGuiKey_Keypad3;
+        case GLFW_KEY_KP_4: return ImGuiKey_Keypad4;
+        case GLFW_KEY_KP_5: return ImGuiKey_Keypad5;
+        case GLFW_KEY_KP_6: return ImGuiKey_Keypad6;
+        case GLFW_KEY_KP_7: return ImGuiKey_Keypad7;
+        case GLFW_KEY_KP_8: return ImGuiKey_Keypad8;
+        case GLFW_KEY_KP_9: return ImGuiKey_Keypad9;
+        case GLFW_KEY_KP_DECIMAL: return ImGuiKey_KeypadDecimal;
+        case GLFW_KEY_KP_DIVIDE: return ImGuiKey_KeypadDivide;
+        case GLFW_KEY_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
+        case GLFW_KEY_KP_SUBTRACT: return ImGuiKey_KeypadSubtract;
+        case GLFW_KEY_KP_ADD: return ImGuiKey_KeypadAdd;
+        case GLFW_KEY_KP_ENTER: return ImGuiKey_KeypadEnter;
+        case GLFW_KEY_KP_EQUAL: return ImGuiKey_KeypadEqual;
+        case GLFW_KEY_LEFT_SHIFT: return ImGuiKey_LeftShift;
+        case GLFW_KEY_LEFT_CONTROL: return ImGuiKey_LeftCtrl;
+        case GLFW_KEY_LEFT_ALT: return ImGuiKey_LeftAlt;
+        case GLFW_KEY_LEFT_SUPER: return ImGuiKey_LeftSuper;
+        case GLFW_KEY_RIGHT_SHIFT: return ImGuiKey_RightShift;
+        case GLFW_KEY_RIGHT_CONTROL: return ImGuiKey_RightCtrl;
+        case GLFW_KEY_RIGHT_ALT: return ImGuiKey_RightAlt;
+        case GLFW_KEY_RIGHT_SUPER: return ImGuiKey_RightSuper;
+        case GLFW_KEY_MENU: return ImGuiKey_Menu;
+        case GLFW_KEY_0: return ImGuiKey_0;
+        case GLFW_KEY_1: return ImGuiKey_1;
+        case GLFW_KEY_2: return ImGuiKey_2;
+        case GLFW_KEY_3: return ImGuiKey_3;
+        case GLFW_KEY_4: return ImGuiKey_4;
+        case GLFW_KEY_5: return ImGuiKey_5;
+        case GLFW_KEY_6: return ImGuiKey_6;
+        case GLFW_KEY_7: return ImGuiKey_7;
+        case GLFW_KEY_8: return ImGuiKey_8;
+        case GLFW_KEY_9: return ImGuiKey_9;
+        case GLFW_KEY_A: return ImGuiKey_A;
+        case GLFW_KEY_B: return ImGuiKey_B;
+        case GLFW_KEY_C: return ImGuiKey_C;
+        case GLFW_KEY_D: return ImGuiKey_D;
+        case GLFW_KEY_E: return ImGuiKey_E;
+        case GLFW_KEY_F: return ImGuiKey_F;
+        case GLFW_KEY_G: return ImGuiKey_G;
+        case GLFW_KEY_H: return ImGuiKey_H;
+        case GLFW_KEY_I: return ImGuiKey_I;
+        case GLFW_KEY_J: return ImGuiKey_J;
+        case GLFW_KEY_K: return ImGuiKey_K;
+        case GLFW_KEY_L: return ImGuiKey_L;
+        case GLFW_KEY_M: return ImGuiKey_M;
+        case GLFW_KEY_N: return ImGuiKey_N;
+        case GLFW_KEY_O: return ImGuiKey_O;
+        case GLFW_KEY_P: return ImGuiKey_P;
+        case GLFW_KEY_Q: return ImGuiKey_Q;
+        case GLFW_KEY_R: return ImGuiKey_R;
+        case GLFW_KEY_S: return ImGuiKey_S;
+        case GLFW_KEY_T: return ImGuiKey_T;
+        case GLFW_KEY_U: return ImGuiKey_U;
+        case GLFW_KEY_V: return ImGuiKey_V;
+        case GLFW_KEY_W: return ImGuiKey_W;
+        case GLFW_KEY_X: return ImGuiKey_X;
+        case GLFW_KEY_Y: return ImGuiKey_Y;
+        case GLFW_KEY_Z: return ImGuiKey_Z;
+        case GLFW_KEY_F1: return ImGuiKey_F1;
+        case GLFW_KEY_F2: return ImGuiKey_F2;
+        case GLFW_KEY_F3: return ImGuiKey_F3;
+        case GLFW_KEY_F4: return ImGuiKey_F4;
+        case GLFW_KEY_F5: return ImGuiKey_F5;
+        case GLFW_KEY_F6: return ImGuiKey_F6;
+        case GLFW_KEY_F7: return ImGuiKey_F7;
+        case GLFW_KEY_F8: return ImGuiKey_F8;
+        case GLFW_KEY_F9: return ImGuiKey_F9;
+        case GLFW_KEY_F10: return ImGuiKey_F10;
+        case GLFW_KEY_F11: return ImGuiKey_F11;
+        case GLFW_KEY_F12: return ImGuiKey_F12;
+        case GLFW_KEY_F13: return ImGuiKey_F13;
+        case GLFW_KEY_F14: return ImGuiKey_F14;
+        case GLFW_KEY_F15: return ImGuiKey_F15;
+        case GLFW_KEY_F16: return ImGuiKey_F16;
+        case GLFW_KEY_F17: return ImGuiKey_F17;
+        case GLFW_KEY_F18: return ImGuiKey_F18;
+        case GLFW_KEY_F19: return ImGuiKey_F19;
+        case GLFW_KEY_F20: return ImGuiKey_F20;
+        case GLFW_KEY_F21: return ImGuiKey_F21;
+        case GLFW_KEY_F22: return ImGuiKey_F22;
+        case GLFW_KEY_F23: return ImGuiKey_F23;
+        case GLFW_KEY_F24: return ImGuiKey_F24;
+        default: return ImGuiKey_None;
+    }
+}
+
+// X11 does not include current pressed/released modifier key in 'mods' flags submitted by GLFW
+// See https://github.com/ocornut/imgui/issues/6034 and https://github.com/glfw/glfw/issues/1630
+static void ImGui_ImplGlfw_UpdateKeyModifiers(GLFWwindow* window)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddKeyEvent(ImGuiMod_Ctrl,  (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS));
+    io.AddKeyEvent(ImGuiMod_Shift, (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT)   == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT)   == GLFW_PRESS));
+    io.AddKeyEvent(ImGuiMod_Alt,   (glfwGetKey(window, GLFW_KEY_LEFT_ALT)     == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_ALT)     == GLFW_PRESS));
+    io.AddKeyEvent(ImGuiMod_Super, (glfwGetKey(window, GLFW_KEY_LEFT_SUPER)   == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SUPER)   == GLFW_PRESS));
+}
+
+static bool ImGui_ImplGlfw_ShouldChainCallback(GLFWwindow* window)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    return bd->CallbacksChainForAllWindows ? true : (window == bd->Window);
+}
+
+void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackMousebutton != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackMousebutton(window, button, action, mods);
+
+    ImGui_ImplGlfw_UpdateKeyModifiers(window);
+
+    ImGuiIO& io = ImGui::GetIO();
+    if (button >= 0 && button < ImGuiMouseButton_COUNT)
+        io.AddMouseButtonEvent(button, action == GLFW_PRESS);
+}
+
+void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackScroll != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackScroll(window, xoffset, yoffset);
+
+#ifdef __EMSCRIPTEN__
+    // Ignore GLFW events: will be processed in ImGui_ImplEmscripten_WheelCallback().
+    return;
+#endif
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMouseWheelEvent((float)xoffset, (float)yoffset);
+}
+
+static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode)
+{
+#if GLFW_HAS_GETKEYNAME && !defined(__EMSCRIPTEN__)
+    // GLFW 3.1+ attempts to "untranslate" keys, which goes the opposite of what every other framework does, making using lettered shortcuts difficult.
+    // (It had reasons to do so: namely GLFW is/was more likely to be used for WASD-type game controls rather than lettered shortcuts, but IHMO the 3.1 change could have been done differently)
+    // See https://github.com/glfw/glfw/issues/1502 for details.
+    // Adding a workaround to undo this (so our keys are translated->untranslated->translated, likely a lossy process).
+    // This won't cover edge cases but this is at least going to cover common cases.
+    if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL)
+        return key;
+    GLFWerrorfun prev_error_callback = glfwSetErrorCallback(nullptr);
+    const char* key_name = glfwGetKeyName(key, scancode);
+    glfwSetErrorCallback(prev_error_callback);
+#if GLFW_HAS_GETERROR && !defined(__EMSCRIPTEN__) // Eat errors (see #5908)
+    (void)glfwGetError(nullptr);
+#endif
+    if (key_name && key_name[0] != 0 && key_name[1] == 0)
+    {
+        const char char_names[] = "`-=[]\\,;\'./";
+        const int char_keys[] = { GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_MINUS, GLFW_KEY_EQUAL, GLFW_KEY_LEFT_BRACKET, GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_BACKSLASH, GLFW_KEY_COMMA, GLFW_KEY_SEMICOLON, GLFW_KEY_APOSTROPHE, GLFW_KEY_PERIOD, GLFW_KEY_SLASH, 0 };
+        IM_ASSERT(IM_ARRAYSIZE(char_names) == IM_ARRAYSIZE(char_keys));
+        if (key_name[0] >= '0' && key_name[0] <= '9')               { key = GLFW_KEY_0 + (key_name[0] - '0'); }
+        else if (key_name[0] >= 'A' && key_name[0] <= 'Z')          { key = GLFW_KEY_A + (key_name[0] - 'A'); }
+        else if (key_name[0] >= 'a' && key_name[0] <= 'z')          { key = GLFW_KEY_A + (key_name[0] - 'a'); }
+        else if (const char* p = strchr(char_names, key_name[0]))   { key = char_keys[p - char_names]; }
+    }
+    // if (action == GLFW_PRESS) printf("key %d scancode %d name '%s'\n", key, scancode, key_name);
+#else
+    IM_UNUSED(scancode);
+#endif
+    return key;
+}
+
+void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackKey != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackKey(window, keycode, scancode, action, mods);
+
+    if (action != GLFW_PRESS && action != GLFW_RELEASE)
+        return;
+
+    ImGui_ImplGlfw_UpdateKeyModifiers(window);
+
+    keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode);
+
+    ImGuiIO& io = ImGui::GetIO();
+    ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode);
+    io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
+    io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code)
+}
+
+void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackWindowFocus != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackWindowFocus(window, focused);
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddFocusEvent(focused != 0);
+}
+
+void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackCursorPos != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackCursorPos(window, x, y);
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMousePosEvent((float)x, (float)y);
+    bd->LastValidMousePos = ImVec2((float)x, (float)y);
+}
+
+// Workaround: X11 seems to send spurious Leave/Enter events which would make us lose our position,
+// so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984)
+void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackCursorEnter != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackCursorEnter(window, entered);
+
+    ImGuiIO& io = ImGui::GetIO();
+    if (entered)
+    {
+        bd->MouseWindow = window;
+        io.AddMousePosEvent(bd->LastValidMousePos.x, bd->LastValidMousePos.y);
+    }
+    else if (!entered && bd->MouseWindow == window)
+    {
+        bd->LastValidMousePos = io.MousePos;
+        bd->MouseWindow = nullptr;
+        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+    }
+}
+
+void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if (bd->PrevUserCallbackChar != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
+        bd->PrevUserCallbackChar(window, c);
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddInputCharacter(c);
+}
+
+void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int)
+{
+	// Unused in 'master' branch but 'docking' branch will use this, so we declare it ahead of it so if you have to install callbacks you can install this one too.
+}
+
+#ifdef __EMSCRIPTEN__
+static EM_BOOL ImGui_ImplEmscripten_WheelCallback(int, const EmscriptenWheelEvent* ev, void*)
+{
+    // Mimic Emscripten_HandleWheel() in SDL.
+    // Corresponding equivalent in GLFW JS emulation layer has incorrect quantizing preventing small values. See #6096
+    float multiplier = 0.0f;
+    if (ev->deltaMode == DOM_DELTA_PIXEL)       { multiplier = 1.0f / 100.0f; } // 100 pixels make up a step.
+    else if (ev->deltaMode == DOM_DELTA_LINE)   { multiplier = 1.0f / 3.0f; }   // 3 lines make up a step.
+    else if (ev->deltaMode == DOM_DELTA_PAGE)   { multiplier = 80.0f; }         // A page makes up 80 steps.
+    float wheel_x = ev->deltaX * -multiplier;
+    float wheel_y = ev->deltaY * -multiplier;
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMouseWheelEvent(wheel_x, wheel_y);
+    //IMGUI_DEBUG_LOG("[Emsc] mode %d dx: %.2f, dy: %.2f, dz: %.2f --> feed %.2f %.2f\n", (int)ev->deltaMode, ev->deltaX, ev->deltaY, ev->deltaZ, wheel_x, wheel_y);
+    return EM_TRUE;
+}
+#endif
+
+#ifdef _WIN32
+// GLFW doesn't allow to distinguish Mouse vs TouchScreen vs Pen.
+// Add support for Win32 (based on imgui_impl_win32), because we rely on _TouchScreen info to trickle inputs differently.
+static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo()
+{
+    LPARAM extra_info = ::GetMessageExtraInfo();
+    if ((extra_info & 0xFFFFFF80) == 0xFF515700)
+        return ImGuiMouseSource_Pen;
+    if ((extra_info & 0xFFFFFF80) == 0xFF515780)
+        return ImGuiMouseSource_TouchScreen;
+    return ImGuiMouseSource_Mouse;
+}
+static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    switch (msg)
+    {
+    case WM_MOUSEMOVE: case WM_NCMOUSEMOVE:
+    case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_LBUTTONUP:
+    case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_RBUTTONUP:
+    case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_MBUTTONUP:
+    case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: case WM_XBUTTONUP:
+        ImGui::GetIO().AddMouseSourceEvent(GetMouseSourceFromMessageExtraInfo());
+        break;
+    }
+    return ::CallWindowProcW(bd->GlfwWndProc, hWnd, msg, wParam, lParam);
+}
+#endif
+
+void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!");
+    IM_ASSERT(bd->Window == window);
+
+    bd->PrevUserCallbackWindowFocus = glfwSetWindowFocusCallback(window, ImGui_ImplGlfw_WindowFocusCallback);
+    bd->PrevUserCallbackCursorEnter = glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback);
+    bd->PrevUserCallbackCursorPos = glfwSetCursorPosCallback(window, ImGui_ImplGlfw_CursorPosCallback);
+    bd->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
+    bd->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
+    bd->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
+    bd->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
+    bd->PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
+    bd->InstalledCallbacks = true;
+}
+
+void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!");
+    IM_ASSERT(bd->Window == window);
+
+    glfwSetWindowFocusCallback(window, bd->PrevUserCallbackWindowFocus);
+    glfwSetCursorEnterCallback(window, bd->PrevUserCallbackCursorEnter);
+    glfwSetCursorPosCallback(window, bd->PrevUserCallbackCursorPos);
+    glfwSetMouseButtonCallback(window, bd->PrevUserCallbackMousebutton);
+    glfwSetScrollCallback(window, bd->PrevUserCallbackScroll);
+    glfwSetKeyCallback(window, bd->PrevUserCallbackKey);
+    glfwSetCharCallback(window, bd->PrevUserCallbackChar);
+    glfwSetMonitorCallback(bd->PrevUserCallbackMonitor);
+    bd->InstalledCallbacks = false;
+    bd->PrevUserCallbackWindowFocus = nullptr;
+    bd->PrevUserCallbackCursorEnter = nullptr;
+    bd->PrevUserCallbackCursorPos = nullptr;
+    bd->PrevUserCallbackMousebutton = nullptr;
+    bd->PrevUserCallbackScroll = nullptr;
+    bd->PrevUserCallbackKey = nullptr;
+    bd->PrevUserCallbackChar = nullptr;
+    bd->PrevUserCallbackMonitor = nullptr;
+}
+
+// Set to 'true' to enable chaining installed callbacks for all windows (including secondary viewports created by backends or by user.
+// This is 'false' by default meaning we only chain callbacks for the main viewport.
+// We cannot set this to 'true' by default because user callbacks code may be not testing the 'window' parameter of their callback.
+// If you set this to 'true' your user callback code will need to make sure you are testing the 'window' parameter.
+void ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows)
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    bd->CallbacksChainForAllWindows = chain_for_all_windows;
+}
+
+static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+    //printf("GLFW_VERSION: %d.%d.%d (%d)", GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION, GLFW_VERSION_COMBINED);
+
+    // Setup backend capabilities flags
+    ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)();
+    io.BackendPlatformUserData = (void*)bd;
+    io.BackendPlatformName = "imgui_impl_glfw";
+    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;         // We can honor GetMouseCursor() values (optional)
+    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;          // We can honor io.WantSetMousePos requests (optional, rarely used)
+
+    bd->Window = window;
+    bd->Time = 0.0;
+
+    io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
+    io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
+    io.ClipboardUserData = bd->Window;
+
+    // Create mouse cursors
+    // (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
+    // GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
+    // Missing cursors will return nullptr and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
+    GLFWerrorfun prev_error_callback = glfwSetErrorCallback(nullptr);
+    bd->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
+#if GLFW_HAS_NEW_CURSORS
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
+#else
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
+#endif
+    glfwSetErrorCallback(prev_error_callback);
+#if GLFW_HAS_GETERROR && !defined(__EMSCRIPTEN__) // Eat errors (see #5908)
+    (void)glfwGetError(nullptr);
+#endif
+
+    // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
+    if (install_callbacks)
+        ImGui_ImplGlfw_InstallCallbacks(window);
+    // Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096)
+    // We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves.
+    // FIXME: May break chaining in case user registered their own Emscripten callback?
+#ifdef __EMSCRIPTEN__
+    emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, ImGui_ImplEmscripten_WheelCallback);
+#endif
+
+    // Set platform dependent data in viewport
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+#ifdef _WIN32
+    main_viewport->PlatformHandleRaw = glfwGetWin32Window(bd->Window);
+#elif defined(__APPLE__)
+    main_viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(bd->Window);
+#else
+    IM_UNUSED(main_viewport);
+#endif
+
+    // Windows: register a WndProc hook so we can intercept some messages.
+#ifdef _WIN32
+    bd->GlfwWndProc = (WNDPROC)::GetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC);
+    IM_ASSERT(bd->GlfwWndProc != nullptr);
+    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc);
+#endif
+
+    bd->ClientApi = client_api;
+    return true;
+}
+
+bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks)
+{
+    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL);
+}
+
+bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks)
+{
+    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan);
+}
+
+bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks)
+{
+    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
+}
+
+void ImGui_ImplGlfw_Shutdown()
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    if (bd->InstalledCallbacks)
+        ImGui_ImplGlfw_RestoreCallbacks(bd->Window);
+#ifdef __EMSCRIPTEN__
+    emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr);
+#endif
+
+    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
+        glfwDestroyCursor(bd->MouseCursors[cursor_n]);
+
+    // Windows: register a WndProc hook so we can intercept some messages.
+#ifdef _WIN32
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->GlfwWndProc);
+    bd->GlfwWndProc = nullptr;
+#endif
+
+    io.BackendPlatformName = nullptr;
+    io.BackendPlatformUserData = nullptr;
+    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
+    IM_DELETE(bd);
+}
+
+static void ImGui_ImplGlfw_UpdateMouseData()
+{
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+
+
+    // (those braces are here to reduce diff with multi-viewports support in 'docking' branch)
+    {
+        GLFWwindow* window = bd->Window;
+
+#ifdef __EMSCRIPTEN__
+        const bool is_window_focused = true;
+#else
+        const bool is_window_focused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
+#endif
+        if (is_window_focused)
+        {
+            // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
+            if (io.WantSetMousePos)
+                glfwSetCursorPos(window, (double)io.MousePos.x, (double)io.MousePos.y);
+
+            // (Optional) Fallback to provide mouse position when focused (ImGui_ImplGlfw_CursorPosCallback already provides this when hovered or captured)
+            if (bd->MouseWindow == nullptr)
+            {
+                double mouse_x, mouse_y;
+                glfwGetCursorPos(window, &mouse_x, &mouse_y);
+                bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y);
+                io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
+            }
+        }
+    }
+}
+
+static void ImGui_ImplGlfw_UpdateMouseCursor()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
+        return;
+
+    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+    // (those braces are here to reduce diff with multi-viewports support in 'docking' branch)
+    {
+        GLFWwindow* window = bd->Window;
+        if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
+        {
+            // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
+        }
+        else
+        {
+            // Show OS mouse cursor
+            // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
+            glfwSetCursor(window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]);
+            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+        }
+    }
+}
+
+// Update gamepad inputs
+static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v  > 1.0f ? 1.0f : v; }
+static void ImGui_ImplGlfw_UpdateGamepads()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
+        return;
+
+    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+#if GLFW_HAS_GAMEPAD_API && !defined(__EMSCRIPTEN__)
+    GLFWgamepadstate gamepad;
+    if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad))
+        return;
+    #define MAP_BUTTON(KEY_NO, BUTTON_NO, _UNUSED)          do { io.AddKeyEvent(KEY_NO, gamepad.buttons[BUTTON_NO] != 0); } while (0)
+    #define MAP_ANALOG(KEY_NO, AXIS_NO, _UNUSED, V0, V1)    do { float v = gamepad.axes[AXIS_NO]; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
+#else
+    int axes_count = 0, buttons_count = 0;
+    const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
+    const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
+    if (axes_count == 0 || buttons_count == 0)
+        return;
+    #define MAP_BUTTON(KEY_NO, _UNUSED, BUTTON_NO)          do { io.AddKeyEvent(KEY_NO, (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS)); } while (0)
+    #define MAP_ANALOG(KEY_NO, _UNUSED, AXIS_NO, V0, V1)    do { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
+#endif
+    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+    MAP_BUTTON(ImGuiKey_GamepadStart,       GLFW_GAMEPAD_BUTTON_START,          7);
+    MAP_BUTTON(ImGuiKey_GamepadBack,        GLFW_GAMEPAD_BUTTON_BACK,           6);
+    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,    GLFW_GAMEPAD_BUTTON_X,              2);     // Xbox X, PS Square
+    MAP_BUTTON(ImGuiKey_GamepadFaceRight,   GLFW_GAMEPAD_BUTTON_B,              1);     // Xbox B, PS Circle
+    MAP_BUTTON(ImGuiKey_GamepadFaceUp,      GLFW_GAMEPAD_BUTTON_Y,              3);     // Xbox Y, PS Triangle
+    MAP_BUTTON(ImGuiKey_GamepadFaceDown,    GLFW_GAMEPAD_BUTTON_A,              0);     // Xbox A, PS Cross
+    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,    GLFW_GAMEPAD_BUTTON_DPAD_LEFT,      13);
+    MAP_BUTTON(ImGuiKey_GamepadDpadRight,   GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,     11);
+    MAP_BUTTON(ImGuiKey_GamepadDpadUp,      GLFW_GAMEPAD_BUTTON_DPAD_UP,        10);
+    MAP_BUTTON(ImGuiKey_GamepadDpadDown,    GLFW_GAMEPAD_BUTTON_DPAD_DOWN,      12);
+    MAP_BUTTON(ImGuiKey_GamepadL1,          GLFW_GAMEPAD_BUTTON_LEFT_BUMPER,    4);
+    MAP_BUTTON(ImGuiKey_GamepadR1,          GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER,   5);
+    MAP_ANALOG(ImGuiKey_GamepadL2,          GLFW_GAMEPAD_AXIS_LEFT_TRIGGER,     4,      -0.75f,  +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadR2,          GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER,    5,      -0.75f,  +1.0f);
+    MAP_BUTTON(ImGuiKey_GamepadL3,          GLFW_GAMEPAD_BUTTON_LEFT_THUMB,     8);
+    MAP_BUTTON(ImGuiKey_GamepadR3,          GLFW_GAMEPAD_BUTTON_RIGHT_THUMB,    9);
+    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,  GLFW_GAMEPAD_AXIS_LEFT_X,           0,      -0.25f,  -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickRight, GLFW_GAMEPAD_AXIS_LEFT_X,           0,      +0.25f,  +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickUp,    GLFW_GAMEPAD_AXIS_LEFT_Y,           1,      -0.25f,  -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickDown,  GLFW_GAMEPAD_AXIS_LEFT_Y,           1,      +0.25f,  +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,  GLFW_GAMEPAD_AXIS_RIGHT_X,          2,      -0.25f,  -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickRight, GLFW_GAMEPAD_AXIS_RIGHT_X,          2,      +0.25f,  +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickUp,    GLFW_GAMEPAD_AXIS_RIGHT_Y,          3,      -0.25f,  -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickDown,  GLFW_GAMEPAD_AXIS_RIGHT_Y,          3,      +0.25f,  +1.0f);
+    #undef MAP_BUTTON
+    #undef MAP_ANALOG
+}
+
+void ImGui_ImplGlfw_NewFrame()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?");
+
+    // Setup display size (every frame to accommodate for window resizing)
+    int w, h;
+    int display_w, display_h;
+    glfwGetWindowSize(bd->Window, &w, &h);
+    glfwGetFramebufferSize(bd->Window, &display_w, &display_h);
+    io.DisplaySize = ImVec2((float)w, (float)h);
+    if (w > 0 && h > 0)
+        io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h);
+
+    // Setup time step
+    // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644)
+    double current_time = glfwGetTime();
+    if (current_time <= bd->Time)
+        current_time = bd->Time + 0.00001f;
+    io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
+    bd->Time = current_time;
+
+    ImGui_ImplGlfw_UpdateMouseData();
+    ImGui_ImplGlfw_UpdateMouseCursor();
+
+    // Update game controllers (if enabled and available)
+    ImGui_ImplGlfw_UpdateGamepads();
+}
+
+#ifdef __EMSCRIPTEN__
+static EM_BOOL ImGui_ImplGlfw_OnCanvasSizeChange(int event_type, const EmscriptenUiEvent* event, void* user_data)
+{
+    ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data;
+    double canvas_width, canvas_height;
+    emscripten_get_element_css_size(bd->CanvasSelector, &canvas_width, &canvas_height);
+    glfwSetWindowSize(bd->Window, (int)canvas_width, (int)canvas_height);
+    return true;
+}
+
+static EM_BOOL ImGui_ImplEmscripten_FullscreenChangeCallback(int event_type, const EmscriptenFullscreenChangeEvent* event, void* user_data)
+{
+    ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data;
+    double canvas_width, canvas_height;
+    emscripten_get_element_css_size(bd->CanvasSelector, &canvas_width, &canvas_height);
+    glfwSetWindowSize(bd->Window, (int)canvas_width, (int)canvas_height);
+    return true;
+}
+
+// 'canvas_selector' is a CSS selector. The event listener is applied to the first element that matches the query.
+// STRING MUST PERSIST FOR THE APPLICATION DURATION. PLEASE USE A STRING LITERAL OR ENSURE POINTER WILL STAY VALID.
+void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector)
+{
+    IM_ASSERT(canvas_selector != nullptr);
+    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?");
+
+    bd->CanvasSelector = canvas_selector;
+    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, bd, false, ImGui_ImplGlfw_OnCanvasSizeChange);
+    emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, bd, false, ImGui_ImplEmscripten_FullscreenChangeCallback);
+
+    // Change the size of the GLFW window according to the size of the canvas
+    ImGui_ImplGlfw_OnCanvasSizeChange(EMSCRIPTEN_EVENT_RESIZE, {}, bd);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glfw.h b/engines/twp/imgui/backends/imgui_impl_glfw.h
new file mode 100644
index 00000000000..6a9acd05b27
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_glfw.h
@@ -0,0 +1,58 @@
+// dear imgui: Platform Backend for GLFW
+// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
+// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
+
+// Implemented features:
+//  [X] Platform: Clipboard support.
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only).
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+struct GLFWwindow;
+struct GLFWmonitor;
+
+IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
+IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
+IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplGlfw_NewFrame();
+
+// Emscripten related initialization phase methods
+#ifdef __EMSCRIPTEN__
+IMGUI_IMPL_API void     ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector);
+#endif
+
+// GLFW callbacks install
+// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any.
+// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks.
+IMGUI_IMPL_API void     ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window);
+
+// GFLW callbacks options:
+// - Set 'chain_for_all_windows=true' to enable chaining callbacks for all windows (including secondary viewports created by backends or by user)
+IMGUI_IMPL_API void     ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows);
+
+// GLFW callbacks (individual callbacks to call yourself if you didn't install callbacks)
+IMGUI_IMPL_API void     ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused);        // Since 1.84
+IMGUI_IMPL_API void     ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered);        // Since 1.84
+IMGUI_IMPL_API void     ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y);   // Since 1.87
+IMGUI_IMPL_API void     ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
+IMGUI_IMPL_API void     ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glut.cpp b/engines/twp/imgui/backends/imgui_impl_glut.cpp
new file mode 100644
index 00000000000..5dada3037a3
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_glut.cpp
@@ -0,0 +1,308 @@
+// dear imgui: Platform Backend for GLUT/FreeGLUT
+// This needs to be used along with a Renderer (e.g. OpenGL2)
+
+// !!! GLUT/FreeGLUT IS OBSOLETE PREHISTORIC SOFTWARE. Using GLUT is not recommended unless you really miss the 90's. !!!
+// !!! If someone or something is teaching you GLUT today, you are being abused. Please show some resistance. !!!
+// !!! Nowadays, prefer using GLFW or SDL instead!
+
+// Implemented features:
+//  [X] Platform: Partial keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLUT values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+// Issues:
+//  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
+//  [ ] Platform: Missing horizontal mouse wheel support.
+//  [ ] Platform: Missing mouse cursor shape/visibility support.
+//  [ ] Platform: Missing clipboard support (not supported by Glut).
+//  [ ] Platform: Missing gamepad support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2023-04-17: BREAKING: Removed call to ImGui::NewFrame() from ImGui_ImplGLUT_NewFrame(). Needs to be called from the main application loop, like with every other backends.
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2019-04-03: Misc: Renamed imgui_impl_freeglut.cpp/.h to imgui_impl_glut.cpp/.h.
+//  2019-03-25: Misc: Made io.DeltaTime always above zero.
+//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
+//  2018-03-22: Added GLUT Platform binding.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_glut.h"
+#define GL_SILENCE_DEPRECATION
+#ifdef __APPLE__
+#include <GLUT/glut.h>
+#else
+#include <GL/freeglut.h>
+#endif
+
+#ifdef _MSC_VER
+#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
+#endif
+
+static int g_Time = 0;          // Current time, in milliseconds
+
+// Glut has 1 function for characters and one for "special keys". We map the characters in the 0..255 range and the keys above.
+static ImGuiKey ImGui_ImplGLUT_KeyToImGuiKey(int key)
+{
+    switch (key)
+    {
+        case '\t':                      return ImGuiKey_Tab;
+        case 256 + GLUT_KEY_LEFT:       return ImGuiKey_LeftArrow;
+        case 256 + GLUT_KEY_RIGHT:      return ImGuiKey_RightArrow;
+        case 256 + GLUT_KEY_UP:         return ImGuiKey_UpArrow;
+        case 256 + GLUT_KEY_DOWN:       return ImGuiKey_DownArrow;
+        case 256 + GLUT_KEY_PAGE_UP:    return ImGuiKey_PageUp;
+        case 256 + GLUT_KEY_PAGE_DOWN:  return ImGuiKey_PageDown;
+        case 256 + GLUT_KEY_HOME:       return ImGuiKey_Home;
+        case 256 + GLUT_KEY_END:        return ImGuiKey_End;
+        case 256 + GLUT_KEY_INSERT:     return ImGuiKey_Insert;
+        case 127:                       return ImGuiKey_Delete;
+        case 8:                         return ImGuiKey_Backspace;
+        case ' ':                       return ImGuiKey_Space;
+        case 13:                        return ImGuiKey_Enter;
+        case 27:                        return ImGuiKey_Escape;
+        case 39:                        return ImGuiKey_Apostrophe;
+        case 44:                        return ImGuiKey_Comma;
+        case 45:                        return ImGuiKey_Minus;
+        case 46:                        return ImGuiKey_Period;
+        case 47:                        return ImGuiKey_Slash;
+        case 59:                        return ImGuiKey_Semicolon;
+        case 61:                        return ImGuiKey_Equal;
+        case 91:                        return ImGuiKey_LeftBracket;
+        case 92:                        return ImGuiKey_Backslash;
+        case 93:                        return ImGuiKey_RightBracket;
+        case 96:                        return ImGuiKey_GraveAccent;
+        //case 0:                         return ImGuiKey_CapsLock;
+        //case 0:                         return ImGuiKey_ScrollLock;
+        case 256 + 0x006D:              return ImGuiKey_NumLock;
+        //case 0:                         return ImGuiKey_PrintScreen;
+        //case 0:                         return ImGuiKey_Pause;
+        //case '0':                       return ImGuiKey_Keypad0;
+        //case '1':                       return ImGuiKey_Keypad1;
+        //case '2':                       return ImGuiKey_Keypad2;
+        //case '3':                       return ImGuiKey_Keypad3;
+        //case '4':                       return ImGuiKey_Keypad4;
+        //case '5':                       return ImGuiKey_Keypad5;
+        //case '6':                       return ImGuiKey_Keypad6;
+        //case '7':                       return ImGuiKey_Keypad7;
+        //case '8':                       return ImGuiKey_Keypad8;
+        //case '9':                       return ImGuiKey_Keypad9;
+        //case 46:                        return ImGuiKey_KeypadDecimal;
+        //case 47:                        return ImGuiKey_KeypadDivide;
+        case 42:                        return ImGuiKey_KeypadMultiply;
+        //case 45:                        return ImGuiKey_KeypadSubtract;
+        case 43:                        return ImGuiKey_KeypadAdd;
+        //case 13:                        return ImGuiKey_KeypadEnter;
+        //case 0:                         return ImGuiKey_KeypadEqual;
+        case 256 + 0x0072:              return ImGuiKey_LeftCtrl;
+        case 256 + 0x0070:              return ImGuiKey_LeftShift;
+        case 256 + 0x0074:              return ImGuiKey_LeftAlt;
+        //case 0:                         return ImGuiKey_LeftSuper;
+        case 256 + 0x0073:              return ImGuiKey_RightCtrl;
+        case 256 + 0x0071:              return ImGuiKey_RightShift;
+        case 256 + 0x0075:              return ImGuiKey_RightAlt;
+        //case 0:                         return ImGuiKey_RightSuper;
+        //case 0:                         return ImGuiKey_Menu;
+        case '0':                       return ImGuiKey_0;
+        case '1':                       return ImGuiKey_1;
+        case '2':                       return ImGuiKey_2;
+        case '3':                       return ImGuiKey_3;
+        case '4':                       return ImGuiKey_4;
+        case '5':                       return ImGuiKey_5;
+        case '6':                       return ImGuiKey_6;
+        case '7':                       return ImGuiKey_7;
+        case '8':                       return ImGuiKey_8;
+        case '9':                       return ImGuiKey_9;
+        case 'A': case 'a':             return ImGuiKey_A;
+        case 'B': case 'b':             return ImGuiKey_B;
+        case 'C': case 'c':             return ImGuiKey_C;
+        case 'D': case 'd':             return ImGuiKey_D;
+        case 'E': case 'e':             return ImGuiKey_E;
+        case 'F': case 'f':             return ImGuiKey_F;
+        case 'G': case 'g':             return ImGuiKey_G;
+        case 'H': case 'h':             return ImGuiKey_H;
+        case 'I': case 'i':             return ImGuiKey_I;
+        case 'J': case 'j':             return ImGuiKey_J;
+        case 'K': case 'k':             return ImGuiKey_K;
+        case 'L': case 'l':             return ImGuiKey_L;
+        case 'M': case 'm':             return ImGuiKey_M;
+        case 'N': case 'n':             return ImGuiKey_N;
+        case 'O': case 'o':             return ImGuiKey_O;
+        case 'P': case 'p':             return ImGuiKey_P;
+        case 'Q': case 'q':             return ImGuiKey_Q;
+        case 'R': case 'r':             return ImGuiKey_R;
+        case 'S': case 's':             return ImGuiKey_S;
+        case 'T': case 't':             return ImGuiKey_T;
+        case 'U': case 'u':             return ImGuiKey_U;
+        case 'V': case 'v':             return ImGuiKey_V;
+        case 'W': case 'w':             return ImGuiKey_W;
+        case 'X': case 'x':             return ImGuiKey_X;
+        case 'Y': case 'y':             return ImGuiKey_Y;
+        case 'Z': case 'z':             return ImGuiKey_Z;
+        case 256 + GLUT_KEY_F1:         return ImGuiKey_F1;
+        case 256 + GLUT_KEY_F2:         return ImGuiKey_F2;
+        case 256 + GLUT_KEY_F3:         return ImGuiKey_F3;
+        case 256 + GLUT_KEY_F4:         return ImGuiKey_F4;
+        case 256 + GLUT_KEY_F5:         return ImGuiKey_F5;
+        case 256 + GLUT_KEY_F6:         return ImGuiKey_F6;
+        case 256 + GLUT_KEY_F7:         return ImGuiKey_F7;
+        case 256 + GLUT_KEY_F8:         return ImGuiKey_F8;
+        case 256 + GLUT_KEY_F9:         return ImGuiKey_F9;
+        case 256 + GLUT_KEY_F10:        return ImGuiKey_F10;
+        case 256 + GLUT_KEY_F11:        return ImGuiKey_F11;
+        case 256 + GLUT_KEY_F12:        return ImGuiKey_F12;
+        default:                        return ImGuiKey_None;
+    }
+}
+
+bool ImGui_ImplGLUT_Init()
+{
+    ImGuiIO& io = ImGui::GetIO();
+
+#ifdef FREEGLUT
+    io.BackendPlatformName = "imgui_impl_glut (freeglut)";
+#else
+    io.BackendPlatformName = "imgui_impl_glut";
+#endif
+    g_Time = 0;
+
+    return true;
+}
+
+void ImGui_ImplGLUT_InstallFuncs()
+{
+    glutReshapeFunc(ImGui_ImplGLUT_ReshapeFunc);
+    glutMotionFunc(ImGui_ImplGLUT_MotionFunc);
+    glutPassiveMotionFunc(ImGui_ImplGLUT_MotionFunc);
+    glutMouseFunc(ImGui_ImplGLUT_MouseFunc);
+#ifdef __FREEGLUT_EXT_H__
+    glutMouseWheelFunc(ImGui_ImplGLUT_MouseWheelFunc);
+#endif
+    glutKeyboardFunc(ImGui_ImplGLUT_KeyboardFunc);
+    glutKeyboardUpFunc(ImGui_ImplGLUT_KeyboardUpFunc);
+    glutSpecialFunc(ImGui_ImplGLUT_SpecialFunc);
+    glutSpecialUpFunc(ImGui_ImplGLUT_SpecialUpFunc);
+}
+
+void ImGui_ImplGLUT_Shutdown()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendPlatformName = nullptr;
+}
+
+void ImGui_ImplGLUT_NewFrame()
+{
+    // Setup time step
+    ImGuiIO& io = ImGui::GetIO();
+    int current_time = glutGet(GLUT_ELAPSED_TIME);
+    int delta_time_ms = (current_time - g_Time);
+    if (delta_time_ms <= 0)
+        delta_time_ms = 1;
+    io.DeltaTime = delta_time_ms / 1000.0f;
+    g_Time = current_time;
+}
+
+static void ImGui_ImplGLUT_UpdateKeyModifiers()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    int glut_key_mods = glutGetModifiers();
+    io.AddKeyEvent(ImGuiMod_Ctrl, (glut_key_mods & GLUT_ACTIVE_CTRL) != 0);
+    io.AddKeyEvent(ImGuiMod_Shift, (glut_key_mods & GLUT_ACTIVE_SHIFT) != 0);
+    io.AddKeyEvent(ImGuiMod_Alt, (glut_key_mods & GLUT_ACTIVE_ALT) != 0);
+}
+
+static void ImGui_ImplGLUT_AddKeyEvent(ImGuiKey key, bool down, int native_keycode)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddKeyEvent(key, down);
+    io.SetKeyEventNativeData(key, native_keycode, -1); // To support legacy indexing (<1.87 user code)
+}
+
+void ImGui_ImplGLUT_KeyboardFunc(unsigned char c, int x, int y)
+{
+    // Send character to imgui
+    //printf("char_down_func %d '%c'\n", c, c);
+    ImGuiIO& io = ImGui::GetIO();
+    if (c >= 32)
+        io.AddInputCharacter((unsigned int)c);
+
+    ImGuiKey key = ImGui_ImplGLUT_KeyToImGuiKey(c);
+    ImGui_ImplGLUT_AddKeyEvent(key, true, c);
+    ImGui_ImplGLUT_UpdateKeyModifiers();
+    (void)x; (void)y; // Unused
+}
+
+void ImGui_ImplGLUT_KeyboardUpFunc(unsigned char c, int x, int y)
+{
+    //printf("char_up_func %d '%c'\n", c, c);
+    ImGuiKey key = ImGui_ImplGLUT_KeyToImGuiKey(c);
+    ImGui_ImplGLUT_AddKeyEvent(key, false, c);
+    ImGui_ImplGLUT_UpdateKeyModifiers();
+    (void)x; (void)y; // Unused
+}
+
+void ImGui_ImplGLUT_SpecialFunc(int key, int x, int y)
+{
+    //printf("key_down_func %d\n", key);
+    ImGuiKey imgui_key = ImGui_ImplGLUT_KeyToImGuiKey(key + 256);
+    ImGui_ImplGLUT_AddKeyEvent(imgui_key, true, key + 256);
+    ImGui_ImplGLUT_UpdateKeyModifiers();
+    (void)x; (void)y; // Unused
+}
+
+void ImGui_ImplGLUT_SpecialUpFunc(int key, int x, int y)
+{
+    //printf("key_up_func %d\n", key);
+    ImGuiKey imgui_key = ImGui_ImplGLUT_KeyToImGuiKey(key + 256);
+    ImGui_ImplGLUT_AddKeyEvent(imgui_key, false, key + 256);
+    ImGui_ImplGLUT_UpdateKeyModifiers();
+    (void)x; (void)y; // Unused
+}
+
+void ImGui_ImplGLUT_MouseFunc(int glut_button, int state, int x, int y)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMousePosEvent((float)x, (float)y);
+    int button = -1;
+    if (glut_button == GLUT_LEFT_BUTTON) button = 0;
+    if (glut_button == GLUT_RIGHT_BUTTON) button = 1;
+    if (glut_button == GLUT_MIDDLE_BUTTON) button = 2;
+    if (button != -1 && (state == GLUT_DOWN || state == GLUT_UP))
+        io.AddMouseButtonEvent(button, state == GLUT_DOWN);
+}
+
+#ifdef __FREEGLUT_EXT_H__
+void ImGui_ImplGLUT_MouseWheelFunc(int button, int dir, int x, int y)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMousePosEvent((float)x, (float)y);
+    if (dir != 0)
+        io.AddMouseWheelEvent(0.0f, dir > 0 ? 1.0f : -1.0f);
+    (void)button; // Unused
+}
+#endif
+
+void ImGui_ImplGLUT_ReshapeFunc(int w, int h)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.DisplaySize = ImVec2((float)w, (float)h);
+}
+
+void ImGui_ImplGLUT_MotionFunc(int x, int y)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddMousePosEvent((float)x, (float)y);
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glut.h b/engines/twp/imgui/backends/imgui_impl_glut.h
new file mode 100644
index 00000000000..062110edd62
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_glut.h
@@ -0,0 +1,46 @@
+// dear imgui: Platform Backend for GLUT/FreeGLUT
+// This needs to be used along with a Renderer (e.g. OpenGL2)
+
+// !!! GLUT/FreeGLUT IS OBSOLETE PREHISTORIC SOFTWARE. Using GLUT is not recommended unless you really miss the 90's. !!!
+// !!! If someone or something is teaching you GLUT today, you are being abused. Please show some resistance. !!!
+// !!! Nowadays, prefer using GLFW or SDL instead!
+
+// Implemented features:
+//  [X] Platform: Partial keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLUT values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+// Issues:
+//  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
+//  [ ] Platform: Missing horizontal mouse wheel support.
+//  [ ] Platform: Missing mouse cursor shape/visibility support.
+//  [ ] Platform: Missing clipboard support (not supported by Glut).
+//  [ ] Platform: Missing gamepad support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#pragma once
+#ifndef IMGUI_DISABLE
+#include "imgui.h"      // IMGUI_IMPL_API
+
+IMGUI_IMPL_API bool     ImGui_ImplGLUT_Init();
+IMGUI_IMPL_API void     ImGui_ImplGLUT_InstallFuncs();
+IMGUI_IMPL_API void     ImGui_ImplGLUT_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplGLUT_NewFrame();
+
+// You can call ImGui_ImplGLUT_InstallFuncs() to get all those functions installed automatically,
+// or call them yourself from your own GLUT handlers. We are using the same weird names as GLUT for consistency..
+//------------------------------------ GLUT name ---------------------------------------------- Decent Name ---------
+IMGUI_IMPL_API void     ImGui_ImplGLUT_ReshapeFunc(int w, int h);                           // ~ ResizeFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_MotionFunc(int x, int y);                            // ~ MouseMoveFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_MouseFunc(int button, int state, int x, int y);      // ~ MouseButtonFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_MouseWheelFunc(int button, int dir, int x, int y);   // ~ MouseWheelFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_KeyboardFunc(unsigned char c, int x, int y);         // ~ CharPressedFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_KeyboardUpFunc(unsigned char c, int x, int y);       // ~ CharReleasedFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_SpecialFunc(int key, int x, int y);                  // ~ KeyPressedFunc
+IMGUI_IMPL_API void     ImGui_ImplGLUT_SpecialUpFunc(int key, int x, int y);                // ~ KeyReleasedFunc
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_metal.h b/engines/twp/imgui/backends/imgui_impl_metal.h
new file mode 100644
index 00000000000..53706d1f716
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_metal.h
@@ -0,0 +1,72 @@
+// dear imgui: Renderer Backend for Metal
+// This needs to be used along with a Platform Backend (e.g. OSX)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+//-----------------------------------------------------------------------------
+// ObjC API
+//-----------------------------------------------------------------------------
+
+#ifdef __OBJC__
+
+ at class MTLRenderPassDescriptor;
+ at protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder;
+
+IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device);
+IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor);
+IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
+                                                   id<MTLCommandBuffer> commandBuffer,
+                                                   id<MTLRenderCommandEncoder> commandEncoder);
+
+// Called by Init/NewFrame/Shutdown
+IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device);
+IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
+IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device);
+IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
+
+#endif
+
+//-----------------------------------------------------------------------------
+// C++ API
+//-----------------------------------------------------------------------------
+
+// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file
+// More info about using Metal from C++: https://developer.apple.com/metal/cpp/
+
+#ifdef IMGUI_IMPL_METAL_CPP
+#include <Metal/Metal.hpp>
+#ifndef __OBJC__
+
+IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device);
+IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor);
+IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
+                                                   MTL::CommandBuffer* commandBuffer,
+                                                   MTL::RenderCommandEncoder* commandEncoder);
+
+// Called by Init/NewFrame/Shutdown
+IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device);
+IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
+IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device);
+IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
+
+#endif
+#endif
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_metal.mm b/engines/twp/imgui/backends/imgui_impl_metal.mm
new file mode 100644
index 00000000000..fd5686be6cd
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_metal.mm
@@ -0,0 +1,590 @@
+// dear imgui: Renderer Backend for Metal
+// This needs to be used along with a Platform Backend (e.g. OSX)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID!
+//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'.
+//  2022-07-05: Metal: Add dispatch synchronization.
+//  2022-06-30: Metal: Use __bridge for ARC based systems.
+//  2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler.
+//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
+//  2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code).
+//  2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file.
+//  2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
+//  2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-02-18: Metal: Change blending equation to preserve alpha in output buffer.
+//  2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst.
+//  2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-07-05: Metal: Added new Metal backend implementation.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_metal.h"
+#import <time.h>
+#import <Metal/Metal.h>
+
+#pragma mark - Support classes
+
+// A wrapper around a MTLBuffer object that knows the last time it was reused
+ at interface MetalBuffer : NSObject
+ at property (nonatomic, strong) id<MTLBuffer> buffer;
+ at property (nonatomic, assign) double        lastReuseTime;
+- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer;
+ at end
+
+// An object that encapsulates the data necessary to uniquely identify a
+// render pipeline state. These are used as cache keys.
+ at interface FramebufferDescriptor : NSObject<NSCopying>
+ at property (nonatomic, assign) unsigned long  sampleCount;
+ at property (nonatomic, assign) MTLPixelFormat colorPixelFormat;
+ at property (nonatomic, assign) MTLPixelFormat depthPixelFormat;
+ at property (nonatomic, assign) MTLPixelFormat stencilPixelFormat;
+- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor;
+ at end
+
+// A singleton that stores long-lived objects that are needed by the Metal
+// renderer backend. Stores the render pipeline state cache and the default
+// font texture, and manages the reusable buffer cache.
+ at interface MetalContext : NSObject
+ at property (nonatomic, strong) id<MTLDevice>                 device;
+ at property (nonatomic, strong) id<MTLDepthStencilState>      depthStencilState;
+ at property (nonatomic, strong) FramebufferDescriptor*        framebufferDescriptor; // framebuffer descriptor for current frame; transient
+ at property (nonatomic, strong) NSMutableDictionary*          renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
+ at property (nonatomic, strong, nullable) id<MTLTexture>      fontTexture;
+ at property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
+ at property (nonatomic, assign) double                        lastBufferCachePurge;
+- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
+- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device;
+ at end
+
+struct ImGui_ImplMetal_Data
+{
+    MetalContext*               SharedMetalContext;
+
+    ImGui_ImplMetal_Data()      { memset(this, 0, sizeof(*this)); }
+};
+
+static ImGui_ImplMetal_Data*    ImGui_ImplMetal_CreateBackendData() { return IM_NEW(ImGui_ImplMetal_Data)(); }
+static ImGui_ImplMetal_Data*    ImGui_ImplMetal_GetBackendData()    { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; }
+static void                     ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); }
+
+static inline CFTimeInterval    GetMachAbsoluteTimeInSeconds()      { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
+
+#ifdef IMGUI_IMPL_METAL_CPP
+
+#pragma mark - Dear ImGui Metal C++ Backend API
+
+bool ImGui_ImplMetal_Init(MTL::Device* device)
+{
+    return ImGui_ImplMetal_Init((__bridge id<MTLDevice>)(device));
+}
+
+void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor)
+{
+    ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor));
+}
+
+void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
+                                    MTL::CommandBuffer* commandBuffer,
+                                    MTL::RenderCommandEncoder* commandEncoder)
+{
+    ImGui_ImplMetal_RenderDrawData(draw_data,
+                                   (__bridge id<MTLCommandBuffer>)(commandBuffer),
+                                   (__bridge id<MTLRenderCommandEncoder>)(commandEncoder));
+
+}
+
+bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device)
+{
+    return ImGui_ImplMetal_CreateFontsTexture((__bridge id<MTLDevice>)(device));
+}
+
+bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device)
+{
+    return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device));
+}
+
+#endif // #ifdef IMGUI_IMPL_METAL_CPP
+
+#pragma mark - Dear ImGui Metal Backend API
+
+bool ImGui_ImplMetal_Init(id<MTLDevice> device)
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_CreateBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_metal";
+    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+
+    bd->SharedMetalContext = [[MetalContext alloc] init];
+    bd->SharedMetalContext.device = device;
+
+    return true;
+}
+
+void ImGui_ImplMetal_Shutdown()
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGui_ImplMetal_DestroyDeviceObjects();
+    ImGui_ImplMetal_DestroyBackendData();
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+}
+
+void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    IM_ASSERT(bd->SharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?");
+    bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
+
+    if (bd->SharedMetalContext.depthStencilState == nil)
+        ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device);
+}
+
+static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer,
+    id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState,
+    MetalBuffer* vertexBuffer, size_t vertexBufferOffset)
+{
+    IM_UNUSED(commandBuffer);
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    [commandEncoder setCullMode:MTLCullModeNone];
+    [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState];
+
+    // Setup viewport, orthographic projection matrix
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to
+    // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
+    MTLViewport viewport =
+    {
+        .originX = 0.0,
+        .originY = 0.0,
+        .width = (double)(drawData->DisplaySize.x * drawData->FramebufferScale.x),
+        .height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y),
+        .znear = 0.0,
+        .zfar = 1.0
+    };
+    [commandEncoder setViewport:viewport];
+
+    float L = drawData->DisplayPos.x;
+    float R = drawData->DisplayPos.x + drawData->DisplaySize.x;
+    float T = drawData->DisplayPos.y;
+    float B = drawData->DisplayPos.y + drawData->DisplaySize.y;
+    float N = (float)viewport.znear;
+    float F = (float)viewport.zfar;
+    const float ortho_projection[4][4] =
+    {
+        { 2.0f/(R-L),   0.0f,           0.0f,   0.0f },
+        { 0.0f,         2.0f/(T-B),     0.0f,   0.0f },
+        { 0.0f,         0.0f,        1/(F-N),   0.0f },
+        { (R+L)/(L-R),  (T+B)/(B-T), N/(F-N),   1.0f },
+    };
+    [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
+
+    [commandEncoder setRenderPipelineState:renderPipelineState];
+
+    [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0];
+    [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0];
+}
+
+// Metal Render function.
+void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    MetalContext* ctx = bd->SharedMetalContext;
+
+    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+    int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x);
+    int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y);
+    if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0)
+        return;
+
+    // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
+    // The hit rate for this cache should be very near 100%.
+    id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor];
+    if (renderPipelineState == nil)
+    {
+        // No luck; make a new render pipeline state
+        renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device];
+
+        // Cache render pipeline state for later reuse
+        ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState;
+    }
+
+    size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert);
+    size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx);
+    MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
+    MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
+
+    ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
+
+    // Will project scissor/clipping rectangles into framebuffer space
+    ImVec2 clip_off = drawData->DisplayPos;         // (0,0) unless using multi-viewports
+    ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
+
+    // Render command lists
+    size_t vertexBufferOffset = 0;
+    size_t indexBufferOffset = 0;
+    for (int n = 0; n < drawData->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = drawData->CmdLists[n];
+
+        memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, cmd_list->VtxBuffer.Data, (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+        memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, cmd_list->IdxBuffer.Data, (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+
+                // Clamp to viewport as setScissorRect() won't accept values that are off bounds
+                if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
+                if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
+                if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
+                if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+                if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this
+                    continue;
+
+                // Apply scissor/clipping rectangle
+                MTLScissorRect scissorRect =
+                {
+                    .x = NSUInteger(clip_min.x),
+                    .y = NSUInteger(clip_min.y),
+                    .width = NSUInteger(clip_max.x - clip_min.x),
+                    .height = NSUInteger(clip_max.y - clip_min.y)
+                };
+                [commandEncoder setScissorRect:scissorRect];
+
+                // Bind texture, Draw
+                if (ImTextureID tex_id = pcmd->GetTexID())
+                    [commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(tex_id) atIndex:0];
+
+                [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];
+                [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
+                                           indexCount:pcmd->ElemCount
+                                            indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
+                                          indexBuffer:indexBuffer.buffer
+                                    indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)];
+            }
+        }
+
+        vertexBufferOffset += (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
+        indexBufferOffset += (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx);
+    }
+
+    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>)
+    {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+            if (bd != nullptr)
+            {
+                @synchronized(bd->SharedMetalContext.bufferCache)
+                {
+                    [bd->SharedMetalContext.bufferCache addObject:vertexBuffer];
+                    [bd->SharedMetalContext.bufferCache addObject:indexBuffer];
+                }
+            }
+        });
+    }];
+}
+
+bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device)
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+
+    // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
+    // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
+    // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
+    // You can make that change in your implementation.
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+    MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
+                                                                                                 width:(NSUInteger)width
+                                                                                                height:(NSUInteger)height
+                                                                                             mipmapped:NO];
+    textureDescriptor.usage = MTLTextureUsageShaderRead;
+#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+    textureDescriptor.storageMode = MTLStorageModeManaged;
+#else
+    textureDescriptor.storageMode = MTLStorageModeShared;
+#endif
+    id <MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor];
+    [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4];
+    bd->SharedMetalContext.fontTexture = texture;
+    io.Fonts->SetTexID((__bridge void*)bd->SharedMetalContext.fontTexture); // ImTextureID == void*
+
+    return (bd->SharedMetalContext.fontTexture != nil);
+}
+
+void ImGui_ImplMetal_DestroyFontsTexture()
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    bd->SharedMetalContext.fontTexture = nil;
+    io.Fonts->SetTexID(0);
+}
+
+bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
+    depthStencilDescriptor.depthWriteEnabled = NO;
+    depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
+    bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
+    ImGui_ImplMetal_CreateFontsTexture(device);
+
+    return true;
+}
+
+void ImGui_ImplMetal_DestroyDeviceObjects()
+{
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    ImGui_ImplMetal_DestroyFontsTexture();
+    [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
+}
+
+#pragma mark - MetalBuffer implementation
+
+ at implementation MetalBuffer
+- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer
+{
+    if ((self = [super init]))
+    {
+        _buffer = buffer;
+        _lastReuseTime = GetMachAbsoluteTimeInSeconds();
+    }
+    return self;
+}
+ at end
+
+#pragma mark - FramebufferDescriptor implementation
+
+ at implementation FramebufferDescriptor
+- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor
+{
+    if ((self = [super init]))
+    {
+        _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount;
+        _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat;
+        _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat;
+        _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat;
+    }
+    return self;
+}
+
+- (nonnull id)copyWithZone:(nullable NSZone*)zone
+{
+    FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init];
+    copy.sampleCount = self.sampleCount;
+    copy.colorPixelFormat = self.colorPixelFormat;
+    copy.depthPixelFormat = self.depthPixelFormat;
+    copy.stencilPixelFormat = self.stencilPixelFormat;
+    return copy;
+}
+
+- (NSUInteger)hash
+{
+    NSUInteger sc = _sampleCount & 0x3;
+    NSUInteger cf = _colorPixelFormat & 0x3FF;
+    NSUInteger df = _depthPixelFormat & 0x3FF;
+    NSUInteger sf = _stencilPixelFormat & 0x3FF;
+    NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc;
+    return hash;
+}
+
+- (BOOL)isEqual:(id)object
+{
+    FramebufferDescriptor* other = object;
+    if (![other isKindOfClass:[FramebufferDescriptor class]])
+        return NO;
+    return other.sampleCount == self.sampleCount      &&
+    other.colorPixelFormat   == self.colorPixelFormat &&
+    other.depthPixelFormat   == self.depthPixelFormat &&
+    other.stencilPixelFormat == self.stencilPixelFormat;
+}
+
+ at end
+
+#pragma mark - MetalContext implementation
+
+ at implementation MetalContext
+- (instancetype)init
+{
+    if ((self = [super init]))
+    {
+        self.renderPipelineStateCache = [NSMutableDictionary dictionary];
+        self.bufferCache = [NSMutableArray array];
+        _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds();
+    }
+    return self;
+}
+
+- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device
+{
+    uint64_t now = GetMachAbsoluteTimeInSeconds();
+
+    @synchronized(self.bufferCache)
+    {
+        // Purge old buffers that haven't been useful for a while
+        if (now - self.lastBufferCachePurge > 1.0)
+        {
+            NSMutableArray* survivors = [NSMutableArray array];
+            for (MetalBuffer* candidate in self.bufferCache)
+                if (candidate.lastReuseTime > self.lastBufferCachePurge)
+                    [survivors addObject:candidate];
+            self.bufferCache = [survivors mutableCopy];
+            self.lastBufferCachePurge = now;
+        }
+
+        // See if we have a buffer we can reuse
+        MetalBuffer* bestCandidate = nil;
+        for (MetalBuffer* candidate in self.bufferCache)
+            if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime))
+                bestCandidate = candidate;
+
+        if (bestCandidate != nil)
+        {
+            [self.bufferCache removeObject:bestCandidate];
+            bestCandidate.lastReuseTime = now;
+            return bestCandidate;
+        }
+    }
+
+    // No luck; make a new buffer
+    id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared];
+    return [[MetalBuffer alloc] initWithBuffer:backing];
+}
+
+// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
+- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device
+{
+    NSError* error = nil;
+
+    NSString* shaderSource = @""
+    "#include <metal_stdlib>\n"
+    "using namespace metal;\n"
+    "\n"
+    "struct Uniforms {\n"
+    "    float4x4 projectionMatrix;\n"
+    "};\n"
+    "\n"
+    "struct VertexIn {\n"
+    "    float2 position  [[attribute(0)]];\n"
+    "    float2 texCoords [[attribute(1)]];\n"
+    "    uchar4 color     [[attribute(2)]];\n"
+    "};\n"
+    "\n"
+    "struct VertexOut {\n"
+    "    float4 position [[position]];\n"
+    "    float2 texCoords;\n"
+    "    float4 color;\n"
+    "};\n"
+    "\n"
+    "vertex VertexOut vertex_main(VertexIn in                 [[stage_in]],\n"
+    "                             constant Uniforms &uniforms [[buffer(1)]]) {\n"
+    "    VertexOut out;\n"
+    "    out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
+    "    out.texCoords = in.texCoords;\n"
+    "    out.color = float4(in.color) / float4(255.0);\n"
+    "    return out;\n"
+    "}\n"
+    "\n"
+    "fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
+    "                             texture2d<half, access::sample> texture [[texture(0)]]) {\n"
+    "    constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
+    "    half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
+    "    return half4(in.color) * texColor;\n"
+    "}\n";
+
+    id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
+    if (library == nil)
+    {
+        NSLog(@"Error: failed to create Metal library: %@", error);
+        return nil;
+    }
+
+    id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
+    id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
+
+    if (vertexFunction == nil || fragmentFunction == nil)
+    {
+        NSLog(@"Error: failed to find Metal shader functions in library: %@", error);
+        return nil;
+    }
+
+    MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
+    vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos);
+    vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
+    vertexDescriptor.attributes[0].bufferIndex = 0;
+    vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv);
+    vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
+    vertexDescriptor.attributes[1].bufferIndex = 0;
+    vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col);
+    vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
+    vertexDescriptor.attributes[2].bufferIndex = 0;
+    vertexDescriptor.layouts[0].stepRate = 1;
+    vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
+    vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
+
+    MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
+    pipelineDescriptor.vertexFunction = vertexFunction;
+    pipelineDescriptor.fragmentFunction = fragmentFunction;
+    pipelineDescriptor.vertexDescriptor = vertexDescriptor;
+    pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount;
+    pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat;
+    pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
+    pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
+    pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
+    pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
+    pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
+    pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
+    pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat;
+    pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat;
+
+    id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
+    if (error != nil)
+        NSLog(@"Error: failed to create Metal pipeline state: %@", error);
+
+    return renderPipelineState;
+}
+
+ at end
+
+//-----------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl2.cpp b/engines/twp/imgui/backends/imgui_impl_opengl2.cpp
new file mode 100644
index 00000000000..0d703769084
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_opengl2.cpp
@@ -0,0 +1,302 @@
+// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline)
+// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)**
+// **Prefer using the code in imgui_impl_opengl3.cpp**
+// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read.
+// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more
+// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might
+// confuse your GPU driver.
+// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API.
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2021-12-08: OpenGL: Fixed mishandling of the the ImDrawCmd::IdxOffset field! This is an old bug but it never had an effect until some internal rendering changes in 1.86.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-01-03: OpenGL: Backup, setup and restore GL_SHADE_MODEL state, disable GL_STENCIL_TEST and disable GL_NORMAL_ARRAY client state to increase compatibility with legacy OpenGL applications.
+//  2020-01-23: OpenGL: Backup, setup and restore GL_TEXTURE_ENV to increase compatibility with legacy OpenGL applications.
+//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-08-03: OpenGL: Disabling/restoring GL_LIGHTING and GL_COLOR_MATERIAL to increase compatibility with legacy OpenGL applications.
+//  2018-06-08: Misc: Extracted imgui_impl_opengl2.cpp/.h away from the old combined GLFW/SDL+OpenGL2 examples.
+//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplOpenGL2_RenderDrawData() in the .h file so you can call it yourself.
+//  2017-09-01: OpenGL: Save and restore current polygon mode.
+//  2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal).
+//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_opengl2.h"
+#include <stdint.h>     // intptr_t
+
+// Clang/GCC warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-macros"                      // warning: macro is not used
+#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
+#endif
+
+// Include OpenGL header (without an OpenGL loader) requires a bit of fiddling
+#if defined(_WIN32) && !defined(APIENTRY)
+#define APIENTRY __stdcall                  // It is customary to use APIENTRY for OpenGL function pointer declarations on all platforms.  Additionally, the Windows OpenGL header needs APIENTRY.
+#endif
+#if defined(_WIN32) && !defined(WINGDIAPI)
+#define WINGDIAPI __declspec(dllimport)     // Some Windows OpenGL headers need this
+#endif
+#if defined(__APPLE__)
+#define GL_SILENCE_DEPRECATION
+#include <OpenGL/gl.h>
+#else
+#include <GL/gl.h>
+#endif
+
+struct ImGui_ImplOpenGL2_Data
+{
+    GLuint       FontTexture;
+
+    ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplOpenGL2_Data* ImGui_ImplOpenGL2_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL2_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// Functions
+bool    ImGui_ImplOpenGL2_Init()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Setup backend capabilities flags
+    ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_opengl2";
+
+    return true;
+}
+
+void    ImGui_ImplOpenGL2_Shutdown()
+{
+    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplOpenGL2_DestroyDeviceObjects();
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    IM_DELETE(bd);
+}
+
+void    ImGui_ImplOpenGL2_NewFrame()
+{
+    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL2_Init()?");
+
+    if (!bd->FontTexture)
+        ImGui_ImplOpenGL2_CreateDeviceObjects();
+}
+
+static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height)
+{
+    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill.
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    //glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // In order to composite our output buffer we need to preserve alpha
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_STENCIL_TEST);
+    glDisable(GL_LIGHTING);
+    glDisable(GL_COLOR_MATERIAL);
+    glEnable(GL_SCISSOR_TEST);
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+    glEnableClientState(GL_COLOR_ARRAY);
+    glDisableClientState(GL_NORMAL_ARRAY);
+    glEnable(GL_TEXTURE_2D);
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    glShadeModel(GL_SMOOTH);
+    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+    // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!),
+    // you may need to backup/reset/restore other state, e.g. for current shader using the commented lines below.
+    // (DO NOT MODIFY THIS FILE! Add the code in your calling function)
+    //   GLint last_program;
+    //   glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
+    //   glUseProgram(0);
+    //   ImGui_ImplOpenGL2_RenderDrawData(...);
+    //   glUseProgram(last_program)
+    // There are potentially many more states you could need to clear/setup that we can't access from default headers.
+    // e.g. glBindBuffer(GL_ARRAY_BUFFER, 0), glDisable(GL_TEXTURE_CUBE_MAP).
+
+    // Setup viewport, orthographic projection matrix
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+    glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
+    glMatrixMode(GL_PROJECTION);
+    glPushMatrix();
+    glLoadIdentity();
+    glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f);
+    glMatrixMode(GL_MODELVIEW);
+    glPushMatrix();
+    glLoadIdentity();
+}
+
+// OpenGL2 Render function.
+// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
+// This is in order to be able to run within an OpenGL engine that doesn't do so.
+void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
+    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
+    if (fb_width == 0 || fb_height == 0)
+        return;
+
+    // Backup GL state
+    GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
+    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
+    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
+    GLint last_shade_model; glGetIntegerv(GL_SHADE_MODEL, &last_shade_model);
+    GLint last_tex_env_mode; glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &last_tex_env_mode);
+    glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
+
+    // Setup desired GL state
+    ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height);
+
+    // Will project scissor/clipping rectangles into framebuffer space
+    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
+    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
+
+    // Render command lists
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+        const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;
+        const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;
+        glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, pos)));
+        glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, uv)));
+        glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, col)));
+
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)
+                glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y));
+
+                // Bind texture, Draw
+                glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
+                glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset);
+            }
+        }
+    }
+
+    // Restore modified GL state
+    glDisableClientState(GL_COLOR_ARRAY);
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture);
+    glMatrixMode(GL_MODELVIEW);
+    glPopMatrix();
+    glMatrixMode(GL_PROJECTION);
+    glPopMatrix();
+    glPopAttrib();
+    glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
+    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
+    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
+    glShadeModel(last_shade_model);
+    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode);
+}
+
+bool ImGui_ImplOpenGL2_CreateFontsTexture()
+{
+    // Build texture atlas
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
+
+    // Upload texture to graphics system
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    GLint last_texture;
+    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+    glGenTextures(1, &bd->FontTexture);
+    glBindTexture(GL_TEXTURE_2D, bd->FontTexture);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
+
+    // Restore state
+    glBindTexture(GL_TEXTURE_2D, last_texture);
+
+    return true;
+}
+
+void ImGui_ImplOpenGL2_DestroyFontsTexture()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
+    if (bd->FontTexture)
+    {
+        glDeleteTextures(1, &bd->FontTexture);
+        io.Fonts->SetTexID(0);
+        bd->FontTexture = 0;
+    }
+}
+
+bool    ImGui_ImplOpenGL2_CreateDeviceObjects()
+{
+    return ImGui_ImplOpenGL2_CreateFontsTexture();
+}
+
+void    ImGui_ImplOpenGL2_DestroyDeviceObjects()
+{
+    ImGui_ImplOpenGL2_DestroyFontsTexture();
+}
+
+//-----------------------------------------------------------------------------
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl2.h b/engines/twp/imgui/backends/imgui_impl_opengl2.h
new file mode 100644
index 00000000000..9c756c71d6b
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_opengl2.h
@@ -0,0 +1,38 @@
+// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline)
+// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)**
+// **Prefer using the code in imgui_impl_opengl3.cpp**
+// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read.
+// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more
+// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might
+// confuse your GPU driver.
+// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API.
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_Init();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL2_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL2_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data);
+
+// Called by Init/NewFrame/Shutdown
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_CreateFontsTexture();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL2_DestroyFontsTexture();
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_CreateDeviceObjects();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL2_DestroyDeviceObjects();
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl3.cpp b/engines/twp/imgui/backends/imgui_impl_opengl3.cpp
new file mode 100644
index 00000000000..a36a7ac278b
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_opengl3.cpp
@@ -0,0 +1,948 @@
+// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
+// - Desktop GL: 2.x 3.x 4.x
+// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
+// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
+//  [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+
+// About WebGL/ES:
+// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
+// - This is done automatically on iOS, Android and Emscripten targets.
+// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
+//  2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983)
+//  2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)
+//  2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)
+//  2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)
+//  2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)
+//  2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224)
+//  2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)
+//  2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)
+//  2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.
+//  2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).
+//  2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.
+//  2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
+//  2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
+//  2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.
+//  2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.
+//  2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version.
+//  2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
+//  2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
+//  2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
+//  2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
+//  2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
+//  2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
+//  2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader.
+//  2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
+//  2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
+//  2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
+//  2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
+//  2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
+//  2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
+//  2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
+//  2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
+//  2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
+//  2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
+//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
+//  2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
+//  2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
+//  2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
+//  2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
+//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
+//  2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
+//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
+//  2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
+//  2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
+//  2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
+//  2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
+//  2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.
+//  2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
+//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
+//  2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
+//  2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.
+//  2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
+//  2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
+//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
+//  2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.
+//  2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.
+//  2017-05-01: OpenGL: Fixed save and restore of current blend func state.
+//  2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.
+//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
+//  2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)
+
+//----------------------------------------
+// OpenGL    GLSL      GLSL
+// version   version   string
+//----------------------------------------
+//  2.0       110       "#version 110"
+//  2.1       120       "#version 120"
+//  3.0       130       "#version 130"
+//  3.1       140       "#version 140"
+//  3.2       150       "#version 150"
+//  3.3       330       "#version 330 core"
+//  4.0       400       "#version 400 core"
+//  4.1       410       "#version 410 core"
+//  4.2       420       "#version 410 core"
+//  4.3       430       "#version 430 core"
+//  ES 2.0    100       "#version 100"      = WebGL 1.0
+//  ES 3.0    300       "#version 300 es"   = WebGL 2.0
+//----------------------------------------
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+#include "imgui_impl_opengl3.h"
+#include <stdio.h>
+#include <stdint.h>     // intptr_t
+#if defined(__APPLE__)
+#include <TargetConditionals.h>
+#endif
+
+// Clang/GCC warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wold-style-cast"         // warning: use of old-style cast
+#pragma clang diagnostic ignored "-Wsign-conversion"        // warning: implicit conversion changes signedness
+#pragma clang diagnostic ignored "-Wunused-macros"          // warning: macro is not used
+#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
+#pragma clang diagnostic ignored "-Wcast-function-type"     // warning: cast between incompatible function types (for loader)
+#endif
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"                  // warning: unknown option after '#pragma GCC diagnostic' kind
+#pragma GCC diagnostic ignored "-Wunknown-warning-option"   // warning: unknown warning group 'xxx'
+#pragma GCC diagnostic ignored "-Wcast-function-type"       // warning: cast between incompatible function types (for loader)
+#endif
+
+// GL includes
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
+#include <OpenGLES/ES2/gl.h>    // Use GL ES 2
+#else
+#include <GLES2/gl2.h>          // Use GL ES 2
+#endif
+#if defined(__EMSCRIPTEN__)
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+#include <GLES2/gl2ext.h>
+#endif
+#elif defined(IMGUI_IMPL_OPENGL_ES3)
+#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
+#include <OpenGLES/ES3/gl.h>    // Use GL ES 3
+#else
+#include <GLES3/gl3.h>          // Use GL ES 3
+#endif
+#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
+// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
+// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w.
+// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.).
+// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp):
+// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped
+// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases
+// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version.
+#define IMGL3W_IMPL
+#include "imgui_impl_opengl3_loader.h"
+#endif
+
+// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension
+#ifndef IMGUI_IMPL_OPENGL_ES2
+#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+#elif defined(__EMSCRIPTEN__)
+#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+#define glBindVertexArray       glBindVertexArrayOES
+#define glGenVertexArrays       glGenVertexArraysOES
+#define glDeleteVertexArrays    glDeleteVertexArraysOES
+#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES
+#endif
+
+// Desktop GL 2.0+ has glPolygonMode() which GL ES and WebGL don't have.
+#ifdef GL_POLYGON_MODE
+#define IMGUI_IMPL_HAS_POLYGON_MODE
+#endif
+
+// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2)
+#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
+#endif
+
+// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler()
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3))
+#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
+#endif
+
+// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1)
+#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
+#endif
+
+// Desktop GL use extension detection
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)
+#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
+#endif
+
+// [Debugging]
+//#define IMGUI_IMPL_OPENGL_DEBUG
+#ifdef IMGUI_IMPL_OPENGL_DEBUG
+#include <stdio.h>
+#define GL_CALL(_CALL)      do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0)  // Call with error check
+#else
+#define GL_CALL(_CALL)      _CALL   // Call without error check
+#endif
+
+// OpenGL Data
+struct ImGui_ImplOpenGL3_Data
+{
+    GLuint          GlVersion;               // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
+    char            GlslVersionString[32];   // Specified by user or detected based on compile time GL settings.
+    bool            GlProfileIsES2;
+    bool            GlProfileIsES3;
+    bool            GlProfileIsCompat;
+    GLint           GlProfileMask;
+    GLuint          FontTexture;
+    GLuint          ShaderHandle;
+    GLint           AttribLocationTex;       // Uniforms location
+    GLint           AttribLocationProjMtx;
+    GLuint          AttribLocationVtxPos;    // Vertex attributes location
+    GLuint          AttribLocationVtxUV;
+    GLuint          AttribLocationVtxColor;
+    unsigned int    VboHandle, ElementsHandle;
+    GLsizeiptr      VertexBufferSize;
+    GLsizeiptr      IndexBufferSize;
+    bool            HasClipOrigin;
+    bool            UseBufferSubData;
+
+    ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+}
+
+// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)
+#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+struct ImGui_ImplOpenGL3_VtxAttribState
+{
+    GLint   Enabled, Size, Type, Normalized, Stride;
+    GLvoid* Ptr;
+
+    void GetState(GLint index)
+    {
+        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);
+        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);
+        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);
+        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);
+        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);
+        glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);
+    }
+    void SetState(GLint index)
+    {
+        glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);
+        if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index);
+    }
+};
+#endif
+
+// Functions
+bool    ImGui_ImplOpenGL3_Init(const char* glsl_version)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+
+    // Initialize our loader
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
+    if (imgl3wInit() != 0)
+    {
+        fprintf(stderr, "Failed to initialize OpenGL loader!\n");
+        return false;
+    }
+#endif
+
+    // Setup backend capabilities flags
+    ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
+    io.BackendRendererUserData = (void*)bd;
+    io.BackendRendererName = "imgui_impl_opengl3";
+
+    // Query for GL version (e.g. 320 for GL 3.2)
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+    // GLES 2
+    bd->GlVersion = 200;
+    bd->GlProfileIsES2 = true;
+#else
+    // Desktop or GLES 3
+    GLint major = 0;
+    GLint minor = 0;
+    glGetIntegerv(GL_MAJOR_VERSION, &major);
+    glGetIntegerv(GL_MINOR_VERSION, &minor);
+    if (major == 0 && minor == 0)
+    {
+        // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
+        const char* gl_version = (const char*)glGetString(GL_VERSION);
+        sscanf(gl_version, "%d.%d", &major, &minor);
+    }
+    bd->GlVersion = (GLuint)(major * 100 + minor * 10);
+#if defined(GL_CONTEXT_PROFILE_MASK)
+    if (bd->GlVersion >= 320)
+        glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
+    bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
+#endif
+
+#if defined(IMGUI_IMPL_OPENGL_ES3)
+    bd->GlProfileIsES3 = true;
+#endif
+
+    bd->UseBufferSubData = false;
+    /*
+    // Query vendor to enable glBufferSubData kludge
+#ifdef _WIN32
+    if (const char* vendor = (const char*)glGetString(GL_VENDOR))
+        if (strncmp(vendor, "Intel", 5) == 0)
+            bd->UseBufferSubData = true;
+#endif
+    */
+#endif
+
+#ifdef IMGUI_IMPL_OPENGL_DEBUG
+    printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
+#endif
+
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
+    if (bd->GlVersion >= 320)
+        io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+#endif
+
+    // Store GLSL version string so we can refer to it later in case we recreate shaders.
+    // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
+    if (glsl_version == nullptr)
+    {
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+        glsl_version = "#version 100";
+#elif defined(IMGUI_IMPL_OPENGL_ES3)
+        glsl_version = "#version 300 es";
+#elif defined(__APPLE__)
+        glsl_version = "#version 150";
+#else
+        glsl_version = "#version 130";
+#endif
+    }
+    IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
+    strcpy(bd->GlslVersionString, glsl_version);
+    strcat(bd->GlslVersionString, "\n");
+
+    // Make an arbitrary GL call (we don't actually need the result)
+    // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!
+    GLint current_texture;
+    glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
+
+    // Detect extensions we support
+    bd->HasClipOrigin = (bd->GlVersion >= 450);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
+    GLint num_extensions = 0;
+    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+    for (GLint i = 0; i < num_extensions; i++)
+    {
+        const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
+        if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0)
+            bd->HasClipOrigin = true;
+    }
+#endif
+
+    return true;
+}
+
+void    ImGui_ImplOpenGL3_Shutdown()
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    ImGui_ImplOpenGL3_DestroyDeviceObjects();
+    io.BackendRendererName = nullptr;
+    io.BackendRendererUserData = nullptr;
+    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+    IM_DELETE(bd);
+}
+
+void    ImGui_ImplOpenGL3_NewFrame()
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?");
+
+    if (!bd->ShaderHandle)
+        ImGui_ImplOpenGL3_CreateDeviceObjects();
+}
+
+static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+
+    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
+    glEnable(GL_BLEND);
+    glBlendEquation(GL_FUNC_ADD);
+    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_STENCIL_TEST);
+    glEnable(GL_SCISSOR_TEST);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
+    if (bd->GlVersion >= 310)
+        glDisable(GL_PRIMITIVE_RESTART);
+#endif
+#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+#endif
+
+    // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
+#if defined(GL_CLIP_ORIGIN)
+    bool clip_origin_lower_left = true;
+    if (bd->HasClipOrigin)
+    {
+        GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
+        if (current_clip_origin == GL_UPPER_LEFT)
+            clip_origin_lower_left = false;
+    }
+#endif
+
+    // Setup viewport, orthographic projection matrix
+    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+    GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));
+    float L = draw_data->DisplayPos.x;
+    float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+    float T = draw_data->DisplayPos.y;
+    float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+#if defined(GL_CLIP_ORIGIN)
+    if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
+#endif
+    const float ortho_projection[4][4] =
+    {
+        { 2.0f/(R-L),   0.0f,         0.0f,   0.0f },
+        { 0.0f,         2.0f/(T-B),   0.0f,   0.0f },
+        { 0.0f,         0.0f,        -1.0f,   0.0f },
+        { (R+L)/(L-R),  (T+B)/(B-T),  0.0f,   1.0f },
+    };
+    glUseProgram(bd->ShaderHandle);
+    glUniform1i(bd->AttribLocationTex, 0);
+    glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
+
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
+    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
+        glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
+#endif
+
+    (void)vertex_array_object;
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    glBindVertexArray(vertex_array_object);
+#endif
+
+    // Bind vertex/index buffers and setup attributes for ImDrawVert
+    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));
+    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));
+    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
+    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
+    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
+    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos,   2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
+    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV,    2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
+    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
+}
+
+// OpenGL3 Render function.
+// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
+// This is in order to be able to run within an OpenGL engine that doesn't do so.
+void    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
+{
+    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
+    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
+    if (fb_width <= 0 || fb_height <= 0)
+        return;
+
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+
+    // Backup GL state
+    GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
+    glActiveTexture(GL_TEXTURE0);
+    GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);
+    GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
+    GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }
+#endif
+    GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
+#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.
+    GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
+    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);
+    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);
+    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);
+#endif
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);
+#endif
+#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
+    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
+#endif
+    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
+    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
+    GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);
+    GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);
+    GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);
+    GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);
+    GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);
+    GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);
+    GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
+    GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
+    GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
+    GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
+    GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
+    GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
+#endif
+
+    // Setup desired GL state
+    // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
+    // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
+    GLuint vertex_array_object = 0;
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    GL_CALL(glGenVertexArrays(1, &vertex_array_object));
+#endif
+    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
+
+    // Will project scissor/clipping rectangles into framebuffer space
+    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
+    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
+
+    // Render command lists
+    for (int n = 0; n < draw_data->CmdListsCount; n++)
+    {
+        const ImDrawList* cmd_list = draw_data->CmdLists[n];
+
+        // Upload vertex/index buffers
+        // - OpenGL drivers are in a very sorry state nowadays....
+        //   During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports
+        //   of leaks on Intel GPU when using multi-viewports on Windows.
+        // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.
+        // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.
+        //   We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.
+        // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.
+        const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
+        const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
+        if (bd->UseBufferSubData)
+        {
+            if (bd->VertexBufferSize < vtx_buffer_size)
+            {
+                bd->VertexBufferSize = vtx_buffer_size;
+                GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));
+            }
+            if (bd->IndexBufferSize < idx_buffer_size)
+            {
+                bd->IndexBufferSize = idx_buffer_size;
+                GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));
+            }
+            GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data));
+            GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data));
+        }
+        else
+        {
+            GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
+            GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
+        }
+
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+            if (pcmd->UserCallback != nullptr)
+            {
+                // User callback, registered via ImDrawList::AddCallback()
+                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+                    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
+                else
+                    pcmd->UserCallback(cmd_list, pcmd);
+            }
+            else
+            {
+                // Project scissor/clipping rectangles into framebuffer space
+                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+                    continue;
+
+                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)
+                GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
+
+                // Bind texture, Draw
+                GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
+                if (bd->GlVersion >= 320)
+                    GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
+                else
+#endif
+                GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
+            }
+        }
+    }
+
+    // Destroy the temporary VAO
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));
+#endif
+
+    // Restore modified GL state
+    // This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.
+    if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program);
+    glBindTexture(GL_TEXTURE_2D, last_texture);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
+    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
+        glBindSampler(0, last_sampler);
+#endif
+    glActiveTexture(last_active_texture);
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    glBindVertexArray(last_vertex_array_object);
+#endif
+    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
+    last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);
+    last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);
+    last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);
+#endif
+    glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
+    glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
+    if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
+    if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
+    if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
+    if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
+    if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
+#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
+    if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
+#endif
+
+#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
+    // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons
+    if (bd->GlVersion <= 310 || bd->GlProfileIsCompat)
+    {
+        glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]);
+        glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
+    }
+    else
+    {
+        glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);
+    }
+#endif // IMGUI_IMPL_HAS_POLYGON_MODE
+
+    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
+    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
+    (void)bd; // Not all compilation paths use this
+}
+
+bool ImGui_ImplOpenGL3_CreateFontsTexture()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+
+    // Build texture atlas
+    unsigned char* pixels;
+    int width, height;
+    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
+
+    // Upload texture to graphics system
+    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+    GLint last_texture;
+    GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
+    GL_CALL(glGenTextures(1, &bd->FontTexture));
+    GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
+    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
+    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
+#endif
+    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
+
+    // Store our identifier
+    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
+
+    // Restore state
+    GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
+
+    return true;
+}
+
+void ImGui_ImplOpenGL3_DestroyFontsTexture()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    if (bd->FontTexture)
+    {
+        glDeleteTextures(1, &bd->FontTexture);
+        io.Fonts->SetTexID(0);
+        bd->FontTexture = 0;
+    }
+}
+
+// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
+static bool CheckShader(GLuint handle, const char* desc)
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    GLint status = 0, log_length = 0;
+    glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
+    glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
+    if ((GLboolean)status == GL_FALSE)
+        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString);
+    if (log_length > 1)
+    {
+        ImVector<char> buf;
+        buf.resize((int)(log_length + 1));
+        glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
+        fprintf(stderr, "%s\n", buf.begin());
+    }
+    return (GLboolean)status == GL_TRUE;
+}
+
+// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
+static bool CheckProgram(GLuint handle, const char* desc)
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    GLint status = 0, log_length = 0;
+    glGetProgramiv(handle, GL_LINK_STATUS, &status);
+    glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
+    if ((GLboolean)status == GL_FALSE)
+        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString);
+    if (log_length > 1)
+    {
+        ImVector<char> buf;
+        buf.resize((int)(log_length + 1));
+        glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
+        fprintf(stderr, "%s\n", buf.begin());
+    }
+    return (GLboolean)status == GL_TRUE;
+}
+
+bool    ImGui_ImplOpenGL3_CreateDeviceObjects()
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+
+    // Backup GL state
+    GLint last_texture, last_array_buffer;
+    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    GLint last_vertex_array;
+    glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
+#endif
+
+    // Parse GLSL version string
+    int glsl_version = 130;
+    sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
+
+    const GLchar* vertex_shader_glsl_120 =
+        "uniform mat4 ProjMtx;\n"
+        "attribute vec2 Position;\n"
+        "attribute vec2 UV;\n"
+        "attribute vec4 Color;\n"
+        "varying vec2 Frag_UV;\n"
+        "varying vec4 Frag_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Frag_UV = UV;\n"
+        "    Frag_Color = Color;\n"
+        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+        "}\n";
+
+    const GLchar* vertex_shader_glsl_130 =
+        "uniform mat4 ProjMtx;\n"
+        "in vec2 Position;\n"
+        "in vec2 UV;\n"
+        "in vec4 Color;\n"
+        "out vec2 Frag_UV;\n"
+        "out vec4 Frag_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Frag_UV = UV;\n"
+        "    Frag_Color = Color;\n"
+        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+        "}\n";
+
+    const GLchar* vertex_shader_glsl_300_es =
+        "precision highp float;\n"
+        "layout (location = 0) in vec2 Position;\n"
+        "layout (location = 1) in vec2 UV;\n"
+        "layout (location = 2) in vec4 Color;\n"
+        "uniform mat4 ProjMtx;\n"
+        "out vec2 Frag_UV;\n"
+        "out vec4 Frag_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Frag_UV = UV;\n"
+        "    Frag_Color = Color;\n"
+        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+        "}\n";
+
+    const GLchar* vertex_shader_glsl_410_core =
+        "layout (location = 0) in vec2 Position;\n"
+        "layout (location = 1) in vec2 UV;\n"
+        "layout (location = 2) in vec4 Color;\n"
+        "uniform mat4 ProjMtx;\n"
+        "out vec2 Frag_UV;\n"
+        "out vec4 Frag_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Frag_UV = UV;\n"
+        "    Frag_Color = Color;\n"
+        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+        "}\n";
+
+    const GLchar* fragment_shader_glsl_120 =
+        "#ifdef GL_ES\n"
+        "    precision mediump float;\n"
+        "#endif\n"
+        "uniform sampler2D Texture;\n"
+        "varying vec2 Frag_UV;\n"
+        "varying vec4 Frag_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
+        "}\n";
+
+    const GLchar* fragment_shader_glsl_130 =
+        "uniform sampler2D Texture;\n"
+        "in vec2 Frag_UV;\n"
+        "in vec4 Frag_Color;\n"
+        "out vec4 Out_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+        "}\n";
+
+    const GLchar* fragment_shader_glsl_300_es =
+        "precision mediump float;\n"
+        "uniform sampler2D Texture;\n"
+        "in vec2 Frag_UV;\n"
+        "in vec4 Frag_Color;\n"
+        "layout (location = 0) out vec4 Out_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+        "}\n";
+
+    const GLchar* fragment_shader_glsl_410_core =
+        "in vec2 Frag_UV;\n"
+        "in vec4 Frag_Color;\n"
+        "uniform sampler2D Texture;\n"
+        "layout (location = 0) out vec4 Out_Color;\n"
+        "void main()\n"
+        "{\n"
+        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+        "}\n";
+
+    // Select shaders matching our GLSL versions
+    const GLchar* vertex_shader = nullptr;
+    const GLchar* fragment_shader = nullptr;
+    if (glsl_version < 130)
+    {
+        vertex_shader = vertex_shader_glsl_120;
+        fragment_shader = fragment_shader_glsl_120;
+    }
+    else if (glsl_version >= 410)
+    {
+        vertex_shader = vertex_shader_glsl_410_core;
+        fragment_shader = fragment_shader_glsl_410_core;
+    }
+    else if (glsl_version == 300)
+    {
+        vertex_shader = vertex_shader_glsl_300_es;
+        fragment_shader = fragment_shader_glsl_300_es;
+    }
+    else
+    {
+        vertex_shader = vertex_shader_glsl_130;
+        fragment_shader = fragment_shader_glsl_130;
+    }
+
+    // Create shaders
+    const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
+    GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
+    glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
+    glCompileShader(vert_handle);
+    CheckShader(vert_handle, "vertex shader");
+
+    const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
+    GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
+    glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
+    glCompileShader(frag_handle);
+    CheckShader(frag_handle, "fragment shader");
+
+    // Link
+    bd->ShaderHandle = glCreateProgram();
+    glAttachShader(bd->ShaderHandle, vert_handle);
+    glAttachShader(bd->ShaderHandle, frag_handle);
+    glLinkProgram(bd->ShaderHandle);
+    CheckProgram(bd->ShaderHandle, "shader program");
+
+    glDetachShader(bd->ShaderHandle, vert_handle);
+    glDetachShader(bd->ShaderHandle, frag_handle);
+    glDeleteShader(vert_handle);
+    glDeleteShader(frag_handle);
+
+    bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
+    bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
+    bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
+    bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
+    bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
+
+    // Create buffers
+    glGenBuffers(1, &bd->VboHandle);
+    glGenBuffers(1, &bd->ElementsHandle);
+
+    ImGui_ImplOpenGL3_CreateFontsTexture();
+
+    // Restore modified GL state
+    glBindTexture(GL_TEXTURE_2D, last_texture);
+    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
+    glBindVertexArray(last_vertex_array);
+#endif
+
+    return true;
+}
+
+void    ImGui_ImplOpenGL3_DestroyDeviceObjects()
+{
+    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+    if (bd->VboHandle)      { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
+    if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
+    if (bd->ShaderHandle)   { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
+    ImGui_ImplOpenGL3_DestroyFontsTexture();
+}
+
+//-----------------------------------------------------------------------------
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl3.h b/engines/twp/imgui/backends/imgui_impl_opengl3.h
new file mode 100644
index 00000000000..23eb924791e
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_opengl3.h
@@ -0,0 +1,66 @@
+// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
+// - Desktop GL: 2.x 3.x 4.x
+// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
+// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
+
+// Implemented features:
+//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
+//  [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+
+// About WebGL/ES:
+// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
+// - This is done automatically on iOS, Android and Emscripten targets.
+// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// About GLSL version:
+//  The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string.
+//  On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
+//  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
+
+#pragma once
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+// Backend API
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
+IMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL3_NewFrame();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
+
+// (Optional) Called by Init/NewFrame/Shutdown
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateFontsTexture();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyFontsTexture();
+IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateDeviceObjects();
+IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyDeviceObjects();
+
+// Specific OpenGL ES versions
+//#define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten
+//#define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android
+
+// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
+#if !defined(IMGUI_IMPL_OPENGL_ES2) \
+ && !defined(IMGUI_IMPL_OPENGL_ES3)
+
+// Try to detect GLES on matching platforms
+#if defined(__APPLE__)
+#include <TargetConditionals.h>
+#endif
+#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
+#define IMGUI_IMPL_OPENGL_ES3               // iOS, Android  -> GL ES 3, "#version 300 es"
+#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
+#define IMGUI_IMPL_OPENGL_ES2               // Emscripten    -> GL ES 2, "#version 100"
+#else
+// Otherwise imgui_impl_opengl3_loader.h will be used.
+#endif
+
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl3_loader.h b/engines/twp/imgui/backends/imgui_impl_opengl3_loader.h
new file mode 100644
index 00000000000..85c58c4e27c
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_opengl3_loader.h
@@ -0,0 +1,814 @@
+//-----------------------------------------------------------------------------
+// About imgui_impl_opengl3_loader.h:
+//
+// We embed our own OpenGL loader to not require user to provide their own or to have to use ours,
+// which proved to be endless problems for users.
+// Our loader is custom-generated, based on gl3w but automatically filtered to only include
+// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small.
+//
+// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY.
+// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE.
+//
+// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions):
+// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCUDING 'imgui_impl_opengl3_loader.h'
+// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER.
+// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS)
+// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT.
+// BUT IF YOU REALLY WANT TO, you can '#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM' and imgui_impl_opengl3.cpp
+// WILL NOT BE USING OUR LOADER, AND INSTEAD EXPECT ANOTHER/YOUR LOADER TO BE AVAILABLE IN THE COMPILATION UNIT.
+//
+// Regenerate with:
+//   python gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt
+//
+// More info:
+//   https://github.com/dearimgui/gl3w_stripped
+//   https://github.com/ocornut/imgui/issues/4445
+//-----------------------------------------------------------------------------
+
+/*
+ * This file was generated with gl3w_gen.py, part of imgl3w
+ * (hosted at https://github.com/dearimgui/gl3w_stripped)
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __gl3w_h_
+#define __gl3w_h_
+
+// Adapted from KHR/khrplatform.h to avoid including entire file.
+#ifndef __khrplatform_h_
+typedef          float         khronos_float_t;
+typedef signed   char          khronos_int8_t;
+typedef unsigned char          khronos_uint8_t;
+typedef signed   short int     khronos_int16_t;
+typedef unsigned short int     khronos_uint16_t;
+#ifdef _WIN64
+typedef signed   long long int khronos_intptr_t;
+typedef signed   long long int khronos_ssize_t;
+#else
+typedef signed   long  int     khronos_intptr_t;
+typedef signed   long  int     khronos_ssize_t;
+#endif
+
+#if defined(_MSC_VER) && !defined(__clang__)
+typedef signed   __int64       khronos_int64_t;
+typedef unsigned __int64       khronos_uint64_t;
+#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100)
+#include <stdint.h>
+typedef          int64_t       khronos_int64_t;
+typedef          uint64_t      khronos_uint64_t;
+#else
+typedef signed   long long     khronos_int64_t;
+typedef unsigned long long     khronos_uint64_t;
+#endif
+#endif  // __khrplatform_h_
+
+#ifndef __gl_glcorearb_h_
+#define __gl_glcorearb_h_ 1
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+** Copyright 2013-2020 The Khronos Group Inc.
+** SPDX-License-Identifier: MIT
+**
+** This header is generated from the Khronos OpenGL / OpenGL ES XML
+** API Registry. The current version of the Registry, generator scripts
+** used to make the header, and the header can be found at
+**   https://github.com/KhronosGroup/OpenGL-Registry
+*/
+#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <windows.h>
+#endif
+#ifndef APIENTRY
+#define APIENTRY
+#endif
+#ifndef APIENTRYP
+#define APIENTRYP APIENTRY *
+#endif
+#ifndef GLAPI
+#define GLAPI extern
+#endif
+/* glcorearb.h is for use with OpenGL core profile implementations.
+** It should should be placed in the same directory as gl.h and
+** included as <GL/glcorearb.h>.
+**
+** glcorearb.h includes only APIs in the latest OpenGL core profile
+** implementation together with APIs in newer ARB extensions which
+** can be supported by the core profile. It does not, and never will
+** include functionality removed from the core profile, such as
+** fixed-function vertex and fragment processing.
+**
+** Do not #include both <GL/glcorearb.h> and either of <GL/gl.h> or
+** <GL/glext.h> in the same source file.
+*/
+/* Generated C header for:
+ * API: gl
+ * Profile: core
+ * Versions considered: .*
+ * Versions emitted: .*
+ * Default extensions included: glcore
+ * Additional extensions included: _nomatch_^
+ * Extensions removed: _nomatch_^
+ */
+#ifndef GL_VERSION_1_0
+typedef void GLvoid;
+typedef unsigned int GLenum;
+
+typedef khronos_float_t GLfloat;
+typedef int GLint;
+typedef int GLsizei;
+typedef unsigned int GLbitfield;
+typedef double GLdouble;
+typedef unsigned int GLuint;
+typedef unsigned char GLboolean;
+typedef khronos_uint8_t GLubyte;
+#define GL_COLOR_BUFFER_BIT               0x00004000
+#define GL_FALSE                          0
+#define GL_TRUE                           1
+#define GL_TRIANGLES                      0x0004
+#define GL_ONE                            1
+#define GL_SRC_ALPHA                      0x0302
+#define GL_ONE_MINUS_SRC_ALPHA            0x0303
+#define GL_FRONT                          0x0404
+#define GL_BACK                           0x0405
+#define GL_FRONT_AND_BACK                 0x0408
+#define GL_POLYGON_MODE                   0x0B40
+#define GL_CULL_FACE                      0x0B44
+#define GL_DEPTH_TEST                     0x0B71
+#define GL_STENCIL_TEST                   0x0B90
+#define GL_VIEWPORT                       0x0BA2
+#define GL_BLEND                          0x0BE2
+#define GL_SCISSOR_BOX                    0x0C10
+#define GL_SCISSOR_TEST                   0x0C11
+#define GL_UNPACK_ROW_LENGTH              0x0CF2
+#define GL_PACK_ALIGNMENT                 0x0D05
+#define GL_TEXTURE_2D                     0x0DE1
+#define GL_UNSIGNED_BYTE                  0x1401
+#define GL_UNSIGNED_SHORT                 0x1403
+#define GL_UNSIGNED_INT                   0x1405
+#define GL_FLOAT                          0x1406
+#define GL_RGBA                           0x1908
+#define GL_FILL                           0x1B02
+#define GL_VENDOR                         0x1F00
+#define GL_RENDERER                       0x1F01
+#define GL_VERSION                        0x1F02
+#define GL_EXTENSIONS                     0x1F03
+#define GL_LINEAR                         0x2601
+#define GL_TEXTURE_MAG_FILTER             0x2800
+#define GL_TEXTURE_MIN_FILTER             0x2801
+typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode);
+typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height);
+typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param);
+typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
+typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask);
+typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
+typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap);
+typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap);
+typedef void (APIENTRYP PFNGLFLUSHPROC) (void);
+typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param);
+typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
+typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void);
+typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data);
+typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name);
+typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap);
+typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode);
+GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height);
+GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);
+GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
+GLAPI void APIENTRY glClear (GLbitfield mask);
+GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
+GLAPI void APIENTRY glDisable (GLenum cap);
+GLAPI void APIENTRY glEnable (GLenum cap);
+GLAPI void APIENTRY glFlush (void);
+GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param);
+GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
+GLAPI GLenum APIENTRY glGetError (void);
+GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data);
+GLAPI const GLubyte *APIENTRY glGetString (GLenum name);
+GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap);
+GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);
+#endif
+#endif /* GL_VERSION_1_0 */
+#ifndef GL_VERSION_1_1
+typedef khronos_float_t GLclampf;
+typedef double GLclampd;
+#define GL_TEXTURE_BINDING_2D             0x8069
+typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices);
+typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture);
+typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures);
+typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);
+GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture);
+GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures);
+GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures);
+#endif
+#endif /* GL_VERSION_1_1 */
+#ifndef GL_VERSION_1_3
+#define GL_TEXTURE0                       0x84C0
+#define GL_ACTIVE_TEXTURE                 0x84E0
+typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glActiveTexture (GLenum texture);
+#endif
+#endif /* GL_VERSION_1_3 */
+#ifndef GL_VERSION_1_4
+#define GL_BLEND_DST_RGB                  0x80C8
+#define GL_BLEND_SRC_RGB                  0x80C9
+#define GL_BLEND_DST_ALPHA                0x80CA
+#define GL_BLEND_SRC_ALPHA                0x80CB
+#define GL_FUNC_ADD                       0x8006
+typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
+typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
+GLAPI void APIENTRY glBlendEquation (GLenum mode);
+#endif
+#endif /* GL_VERSION_1_4 */
+#ifndef GL_VERSION_1_5
+typedef khronos_ssize_t GLsizeiptr;
+typedef khronos_intptr_t GLintptr;
+#define GL_ARRAY_BUFFER                   0x8892
+#define GL_ELEMENT_ARRAY_BUFFER           0x8893
+#define GL_ARRAY_BUFFER_BINDING           0x8894
+#define GL_ELEMENT_ARRAY_BUFFER_BINDING   0x8895
+#define GL_STREAM_DRAW                    0x88E0
+typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
+typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
+typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
+typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
+typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer);
+GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers);
+GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);
+GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
+GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
+#endif
+#endif /* GL_VERSION_1_5 */
+#ifndef GL_VERSION_2_0
+typedef char GLchar;
+typedef khronos_int16_t GLshort;
+typedef khronos_int8_t GLbyte;
+typedef khronos_uint16_t GLushort;
+#define GL_BLEND_EQUATION_RGB             0x8009
+#define GL_VERTEX_ATTRIB_ARRAY_ENABLED    0x8622
+#define GL_VERTEX_ATTRIB_ARRAY_SIZE       0x8623
+#define GL_VERTEX_ATTRIB_ARRAY_STRIDE     0x8624
+#define GL_VERTEX_ATTRIB_ARRAY_TYPE       0x8625
+#define GL_VERTEX_ATTRIB_ARRAY_POINTER    0x8645
+#define GL_BLEND_EQUATION_ALPHA           0x883D
+#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A
+#define GL_FRAGMENT_SHADER                0x8B30
+#define GL_VERTEX_SHADER                  0x8B31
+#define GL_COMPILE_STATUS                 0x8B81
+#define GL_LINK_STATUS                    0x8B82
+#define GL_INFO_LOG_LENGTH                0x8B84
+#define GL_CURRENT_PROGRAM                0x8B8D
+#define GL_UPPER_LEFT                     0x8CA2
+typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha);
+typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
+typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
+typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
+typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
+typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
+typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
+typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader);
+typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index);
+typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
+typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);
+typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
+typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
+typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
+typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params);
+typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer);
+typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program);
+typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
+typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
+typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
+typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
+typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha);
+GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader);
+GLAPI void APIENTRY glCompileShader (GLuint shader);
+GLAPI GLuint APIENTRY glCreateProgram (void);
+GLAPI GLuint APIENTRY glCreateShader (GLenum type);
+GLAPI void APIENTRY glDeleteProgram (GLuint program);
+GLAPI void APIENTRY glDeleteShader (GLuint shader);
+GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader);
+GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index);
+GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index);
+GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name);
+GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params);
+GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params);
+GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
+GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name);
+GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params);
+GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer);
+GLAPI GLboolean APIENTRY glIsProgram (GLuint program);
+GLAPI void APIENTRY glLinkProgram (GLuint program);
+GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
+GLAPI void APIENTRY glUseProgram (GLuint program);
+GLAPI void APIENTRY glUniform1i (GLint location, GLint v0);
+GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
+GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
+#endif
+#endif /* GL_VERSION_2_0 */
+#ifndef GL_VERSION_3_0
+typedef khronos_uint16_t GLhalf;
+#define GL_MAJOR_VERSION                  0x821B
+#define GL_MINOR_VERSION                  0x821C
+#define GL_NUM_EXTENSIONS                 0x821D
+#define GL_FRAMEBUFFER_SRGB               0x8DB9
+#define GL_VERTEX_ARRAY_BINDING           0x85B5
+typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data);
+typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data);
+typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index);
+typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);
+typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);
+typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index);
+GLAPI void APIENTRY glBindVertexArray (GLuint array);
+GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays);
+GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays);
+#endif
+#endif /* GL_VERSION_3_0 */
+#ifndef GL_VERSION_3_1
+#define GL_VERSION_3_1 1
+#define GL_PRIMITIVE_RESTART              0x8F9D
+#endif /* GL_VERSION_3_1 */
+#ifndef GL_VERSION_3_2
+#define GL_VERSION_3_2 1
+typedef struct __GLsync *GLsync;
+typedef khronos_uint64_t GLuint64;
+typedef khronos_int64_t GLint64;
+#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
+#define GL_CONTEXT_PROFILE_MASK           0x9126
+typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);
+typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);
+#endif
+#endif /* GL_VERSION_3_2 */
+#ifndef GL_VERSION_3_3
+#define GL_VERSION_3_3 1
+#define GL_SAMPLER_BINDING                0x8919
+typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler);
+#ifdef GL_GLEXT_PROTOTYPES
+GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler);
+#endif
+#endif /* GL_VERSION_3_3 */
+#ifndef GL_VERSION_4_1
+typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data);
+typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data);
+#endif /* GL_VERSION_4_1 */
+#ifndef GL_VERSION_4_3
+typedef void (APIENTRY  *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
+#endif /* GL_VERSION_4_3 */
+#ifndef GL_VERSION_4_5
+#define GL_CLIP_ORIGIN                    0x935C
+typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param);
+typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param);
+#endif /* GL_VERSION_4_5 */
+#ifndef GL_ARB_bindless_texture
+typedef khronos_uint64_t GLuint64EXT;
+#endif /* GL_ARB_bindless_texture */
+#ifndef GL_ARB_cl_event
+struct _cl_context;
+struct _cl_event;
+#endif /* GL_ARB_cl_event */
+#ifndef GL_ARB_clip_control
+#define GL_ARB_clip_control 1
+#endif /* GL_ARB_clip_control */
+#ifndef GL_ARB_debug_output
+typedef void (APIENTRY  *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
+#endif /* GL_ARB_debug_output */
+#ifndef GL_EXT_EGL_image_storage
+typedef void *GLeglImageOES;
+#endif /* GL_EXT_EGL_image_storage */
+#ifndef GL_EXT_direct_state_access
+typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params);
+typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params);
+typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params);
+typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param);
+typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param);
+#endif /* GL_EXT_direct_state_access */
+#ifndef GL_NV_draw_vulkan_image
+typedef void (APIENTRY  *GLVULKANPROCNV)(void);
+#endif /* GL_NV_draw_vulkan_image */
+#ifndef GL_NV_gpu_shader5
+typedef khronos_int64_t GLint64EXT;
+#endif /* GL_NV_gpu_shader5 */
+#ifndef GL_NV_vertex_buffer_unified_memory
+typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result);
+#endif /* GL_NV_vertex_buffer_unified_memory */
+#ifdef __cplusplus
+}
+#endif
+#endif
+
+#ifndef GL3W_API
+#define GL3W_API
+#endif
+
+#ifndef __gl_h_
+#define __gl_h_
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GL3W_OK 0
+#define GL3W_ERROR_INIT -1
+#define GL3W_ERROR_LIBRARY_OPEN -2
+#define GL3W_ERROR_OPENGL_VERSION -3
+
+typedef void (*GL3WglProc)(void);
+typedef GL3WglProc (*GL3WGetProcAddressProc)(const char *proc);
+
+/* gl3w api */
+GL3W_API int imgl3wInit(void);
+GL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc);
+GL3W_API int imgl3wIsSupported(int major, int minor);
+GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc);
+
+/* gl3w internal state */
+union ImGL3WProcs {
+    GL3WglProc ptr[59];
+    struct {
+        PFNGLACTIVETEXTUREPROC            ActiveTexture;
+        PFNGLATTACHSHADERPROC             AttachShader;
+        PFNGLBINDBUFFERPROC               BindBuffer;
+        PFNGLBINDSAMPLERPROC              BindSampler;
+        PFNGLBINDTEXTUREPROC              BindTexture;
+        PFNGLBINDVERTEXARRAYPROC          BindVertexArray;
+        PFNGLBLENDEQUATIONPROC            BlendEquation;
+        PFNGLBLENDEQUATIONSEPARATEPROC    BlendEquationSeparate;
+        PFNGLBLENDFUNCSEPARATEPROC        BlendFuncSeparate;
+        PFNGLBUFFERDATAPROC               BufferData;
+        PFNGLBUFFERSUBDATAPROC            BufferSubData;
+        PFNGLCLEARPROC                    Clear;
+        PFNGLCLEARCOLORPROC               ClearColor;
+        PFNGLCOMPILESHADERPROC            CompileShader;
+        PFNGLCREATEPROGRAMPROC            CreateProgram;
+        PFNGLCREATESHADERPROC             CreateShader;
+        PFNGLDELETEBUFFERSPROC            DeleteBuffers;
+        PFNGLDELETEPROGRAMPROC            DeleteProgram;
+        PFNGLDELETESHADERPROC             DeleteShader;
+        PFNGLDELETETEXTURESPROC           DeleteTextures;
+        PFNGLDELETEVERTEXARRAYSPROC       DeleteVertexArrays;
+        PFNGLDETACHSHADERPROC             DetachShader;
+        PFNGLDISABLEPROC                  Disable;
+        PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray;
+        PFNGLDRAWELEMENTSPROC             DrawElements;
+        PFNGLDRAWELEMENTSBASEVERTEXPROC   DrawElementsBaseVertex;
+        PFNGLENABLEPROC                   Enable;
+        PFNGLENABLEVERTEXATTRIBARRAYPROC  EnableVertexAttribArray;
+        PFNGLFLUSHPROC                    Flush;
+        PFNGLGENBUFFERSPROC               GenBuffers;
+        PFNGLGENTEXTURESPROC              GenTextures;
+        PFNGLGENVERTEXARRAYSPROC          GenVertexArrays;
+        PFNGLGETATTRIBLOCATIONPROC        GetAttribLocation;
+        PFNGLGETERRORPROC                 GetError;
+        PFNGLGETINTEGERVPROC              GetIntegerv;
+        PFNGLGETPROGRAMINFOLOGPROC        GetProgramInfoLog;
+        PFNGLGETPROGRAMIVPROC             GetProgramiv;
+        PFNGLGETSHADERINFOLOGPROC         GetShaderInfoLog;
+        PFNGLGETSHADERIVPROC              GetShaderiv;
+        PFNGLGETSTRINGPROC                GetString;
+        PFNGLGETSTRINGIPROC               GetStringi;
+        PFNGLGETUNIFORMLOCATIONPROC       GetUniformLocation;
+        PFNGLGETVERTEXATTRIBPOINTERVPROC  GetVertexAttribPointerv;
+        PFNGLGETVERTEXATTRIBIVPROC        GetVertexAttribiv;
+        PFNGLISENABLEDPROC                IsEnabled;
+        PFNGLISPROGRAMPROC                IsProgram;
+        PFNGLLINKPROGRAMPROC              LinkProgram;
+        PFNGLPIXELSTOREIPROC              PixelStorei;
+        PFNGLPOLYGONMODEPROC              PolygonMode;
+        PFNGLREADPIXELSPROC               ReadPixels;
+        PFNGLSCISSORPROC                  Scissor;
+        PFNGLSHADERSOURCEPROC             ShaderSource;
+        PFNGLTEXIMAGE2DPROC               TexImage2D;
+        PFNGLTEXPARAMETERIPROC            TexParameteri;
+        PFNGLUNIFORM1IPROC                Uniform1i;
+        PFNGLUNIFORMMATRIX4FVPROC         UniformMatrix4fv;
+        PFNGLUSEPROGRAMPROC               UseProgram;
+        PFNGLVERTEXATTRIBPOINTERPROC      VertexAttribPointer;
+        PFNGLVIEWPORTPROC                 Viewport;
+    } gl;
+};
+
+GL3W_API extern union ImGL3WProcs imgl3wProcs;
+
+/* OpenGL functions */
+#define glActiveTexture                   imgl3wProcs.gl.ActiveTexture
+#define glAttachShader                    imgl3wProcs.gl.AttachShader
+#define glBindBuffer                      imgl3wProcs.gl.BindBuffer
+#define glBindSampler                     imgl3wProcs.gl.BindSampler
+#define glBindTexture                     imgl3wProcs.gl.BindTexture
+#define glBindVertexArray                 imgl3wProcs.gl.BindVertexArray
+#define glBlendEquation                   imgl3wProcs.gl.BlendEquation
+#define glBlendEquationSeparate           imgl3wProcs.gl.BlendEquationSeparate
+#define glBlendFuncSeparate               imgl3wProcs.gl.BlendFuncSeparate
+#define glBufferData                      imgl3wProcs.gl.BufferData
+#define glBufferSubData                   imgl3wProcs.gl.BufferSubData
+#define glClear                           imgl3wProcs.gl.Clear
+#define glClearColor                      imgl3wProcs.gl.ClearColor
+#define glCompileShader                   imgl3wProcs.gl.CompileShader
+#define glCreateProgram                   imgl3wProcs.gl.CreateProgram
+#define glCreateShader                    imgl3wProcs.gl.CreateShader
+#define glDeleteBuffers                   imgl3wProcs.gl.DeleteBuffers
+#define glDeleteProgram                   imgl3wProcs.gl.DeleteProgram
+#define glDeleteShader                    imgl3wProcs.gl.DeleteShader
+#define glDeleteTextures                  imgl3wProcs.gl.DeleteTextures
+#define glDeleteVertexArrays              imgl3wProcs.gl.DeleteVertexArrays
+#define glDetachShader                    imgl3wProcs.gl.DetachShader
+#define glDisable                         imgl3wProcs.gl.Disable
+#define glDisableVertexAttribArray        imgl3wProcs.gl.DisableVertexAttribArray
+#define glDrawElements                    imgl3wProcs.gl.DrawElements
+#define glDrawElementsBaseVertex          imgl3wProcs.gl.DrawElementsBaseVertex
+#define glEnable                          imgl3wProcs.gl.Enable
+#define glEnableVertexAttribArray         imgl3wProcs.gl.EnableVertexAttribArray
+#define glFlush                           imgl3wProcs.gl.Flush
+#define glGenBuffers                      imgl3wProcs.gl.GenBuffers
+#define glGenTextures                     imgl3wProcs.gl.GenTextures
+#define glGenVertexArrays                 imgl3wProcs.gl.GenVertexArrays
+#define glGetAttribLocation               imgl3wProcs.gl.GetAttribLocation
+#define glGetError                        imgl3wProcs.gl.GetError
+#define glGetIntegerv                     imgl3wProcs.gl.GetIntegerv
+#define glGetProgramInfoLog               imgl3wProcs.gl.GetProgramInfoLog
+#define glGetProgramiv                    imgl3wProcs.gl.GetProgramiv
+#define glGetShaderInfoLog                imgl3wProcs.gl.GetShaderInfoLog
+#define glGetShaderiv                     imgl3wProcs.gl.GetShaderiv
+#define glGetString                       imgl3wProcs.gl.GetString
+#define glGetStringi                      imgl3wProcs.gl.GetStringi
+#define glGetUniformLocation              imgl3wProcs.gl.GetUniformLocation
+#define glGetVertexAttribPointerv         imgl3wProcs.gl.GetVertexAttribPointerv
+#define glGetVertexAttribiv               imgl3wProcs.gl.GetVertexAttribiv
+#define glIsEnabled                       imgl3wProcs.gl.IsEnabled
+#define glIsProgram                       imgl3wProcs.gl.IsProgram
+#define glLinkProgram                     imgl3wProcs.gl.LinkProgram
+#define glPixelStorei                     imgl3wProcs.gl.PixelStorei
+#define glPolygonMode                     imgl3wProcs.gl.PolygonMode
+#define glReadPixels                      imgl3wProcs.gl.ReadPixels
+#define glScissor                         imgl3wProcs.gl.Scissor
+#define glShaderSource                    imgl3wProcs.gl.ShaderSource
+#define glTexImage2D                      imgl3wProcs.gl.TexImage2D
+#define glTexParameteri                   imgl3wProcs.gl.TexParameteri
+#define glUniform1i                       imgl3wProcs.gl.Uniform1i
+#define glUniformMatrix4fv                imgl3wProcs.gl.UniformMatrix4fv
+#define glUseProgram                      imgl3wProcs.gl.UseProgram
+#define glVertexAttribPointer             imgl3wProcs.gl.VertexAttribPointer
+#define glViewport                        imgl3wProcs.gl.Viewport
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#ifdef IMGL3W_IMPL
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+
+#define GL3W_ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
+
+#if defined(_WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <windows.h>
+
+static HMODULE libgl;
+typedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR);
+static GL3WglGetProcAddr wgl_get_proc_address;
+
+static int open_libgl(void)
+{
+    libgl = LoadLibraryA("opengl32.dll");
+    if (!libgl)
+        return GL3W_ERROR_LIBRARY_OPEN;
+    wgl_get_proc_address = (GL3WglGetProcAddr)GetProcAddress(libgl, "wglGetProcAddress");
+    return GL3W_OK;
+}
+
+static void close_libgl(void) { FreeLibrary(libgl); }
+static GL3WglProc get_proc(const char *proc)
+{
+    GL3WglProc res;
+    res = (GL3WglProc)wgl_get_proc_address(proc);
+    if (!res)
+        res = (GL3WglProc)GetProcAddress(libgl, proc);
+    return res;
+}
+#elif defined(__APPLE__)
+#include <dlfcn.h>
+
+static void *libgl;
+static int open_libgl(void)
+{
+    libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL);
+    if (!libgl)
+        return GL3W_ERROR_LIBRARY_OPEN;
+    return GL3W_OK;
+}
+
+static void close_libgl(void) { dlclose(libgl); }
+
+static GL3WglProc get_proc(const char *proc)
+{
+    GL3WglProc res;
+    *(void **)(&res) = dlsym(libgl, proc);
+    return res;
+}
+#else
+#include <dlfcn.h>
+
+static void *libgl;
+static GL3WglProc (*glx_get_proc_address)(const GLubyte *);
+
+static int open_libgl(void)
+{
+    // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983
+    libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
+    if (!libgl)
+        libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL);
+    if (!libgl)
+        libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL);
+    if (!libgl)
+        return GL3W_ERROR_LIBRARY_OPEN;
+    *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB");
+    return GL3W_OK;
+}
+
+static void close_libgl(void) { dlclose(libgl); }
+
+static GL3WglProc get_proc(const char *proc)
+{
+    GL3WglProc res;
+    res = glx_get_proc_address((const GLubyte *)proc);
+    if (!res)
+        *(void **)(&res) = dlsym(libgl, proc);
+    return res;
+}
+#endif
+
+static struct { int major, minor; } version;
+
+static int parse_version(void)
+{
+    if (!glGetIntegerv)
+        return GL3W_ERROR_INIT;
+    glGetIntegerv(GL_MAJOR_VERSION, &version.major);
+    glGetIntegerv(GL_MINOR_VERSION, &version.minor);
+    if (version.major == 0 && version.minor == 0)
+    {
+        // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
+        if (const char* gl_version = (const char*)glGetString(GL_VERSION))
+            sscanf(gl_version, "%d.%d", &version.major, &version.minor);
+    }
+    if (version.major < 2)
+        return GL3W_ERROR_OPENGL_VERSION;
+    return GL3W_OK;
+}
+
+static void load_procs(GL3WGetProcAddressProc proc);
+
+int imgl3wInit(void)
+{
+    int res = open_libgl();
+    if (res)
+        return res;
+    atexit(close_libgl);
+    return imgl3wInit2(get_proc);
+}
+
+int imgl3wInit2(GL3WGetProcAddressProc proc)
+{
+    load_procs(proc);
+    return parse_version();
+}
+
+int imgl3wIsSupported(int major, int minor)
+{
+    if (major < 2)
+        return 0;
+    if (version.major == major)
+        return version.minor >= minor;
+    return version.major >= major;
+}
+
+GL3WglProc imgl3wGetProcAddress(const char *proc) { return get_proc(proc); }
+
+static const char *proc_names[] = {
+    "glActiveTexture",
+    "glAttachShader",
+    "glBindBuffer",
+    "glBindSampler",
+    "glBindTexture",
+    "glBindVertexArray",
+    "glBlendEquation",
+    "glBlendEquationSeparate",
+    "glBlendFuncSeparate",
+    "glBufferData",
+    "glBufferSubData",
+    "glClear",
+    "glClearColor",
+    "glCompileShader",
+    "glCreateProgram",
+    "glCreateShader",
+    "glDeleteBuffers",
+    "glDeleteProgram",
+    "glDeleteShader",
+    "glDeleteTextures",
+    "glDeleteVertexArrays",
+    "glDetachShader",
+    "glDisable",
+    "glDisableVertexAttribArray",
+    "glDrawElements",
+    "glDrawElementsBaseVertex",
+    "glEnable",
+    "glEnableVertexAttribArray",
+    "glFlush",
+    "glGenBuffers",
+    "glGenTextures",
+    "glGenVertexArrays",
+    "glGetAttribLocation",
+    "glGetError",
+    "glGetIntegerv",
+    "glGetProgramInfoLog",
+    "glGetProgramiv",
+    "glGetShaderInfoLog",
+    "glGetShaderiv",
+    "glGetString",
+    "glGetStringi",
+    "glGetUniformLocation",
+    "glGetVertexAttribPointerv",
+    "glGetVertexAttribiv",
+    "glIsEnabled",
+    "glIsProgram",
+    "glLinkProgram",
+    "glPixelStorei",
+    "glPolygonMode",
+    "glReadPixels",
+    "glScissor",
+    "glShaderSource",
+    "glTexImage2D",
+    "glTexParameteri",
+    "glUniform1i",
+    "glUniformMatrix4fv",
+    "glUseProgram",
+    "glVertexAttribPointer",
+    "glViewport",
+};
+
+GL3W_API union ImGL3WProcs imgl3wProcs;
+
+static void load_procs(GL3WGetProcAddressProc proc)
+{
+    size_t i;
+    for (i = 0; i < GL3W_ARRAY_SIZE(proc_names); i++)
+        imgl3wProcs.ptr[i] = proc(proc_names[i]);
+}
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engines/twp/imgui/backends/imgui_impl_osx.h b/engines/twp/imgui/backends/imgui_impl_osx.h
new file mode 100644
index 00000000000..4033ad93e8a
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_osx.h
@@ -0,0 +1,51 @@
+// dear imgui: Platform Backend for OSX / Cocoa
+// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..)
+// - Not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac.
+// - Requires linking with the GameController framework ("-framework GameController").
+
+// Implemented features:
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+//  [X] Platform: Mouse support. Can discriminate Mouse/Pen.
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
+//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: IME support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#include "imgui.h"      // IMGUI_IMPL_API
+#ifndef IMGUI_DISABLE
+
+#ifdef __OBJC__
+
+ at class NSEvent;
+ at class NSView;
+
+IMGUI_IMPL_API bool     ImGui_ImplOSX_Init(NSView* _Nonnull view);
+IMGUI_IMPL_API void     ImGui_ImplOSX_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplOSX_NewFrame(NSView* _Nullable view);
+
+#endif
+
+//-----------------------------------------------------------------------------
+// C++ API
+//-----------------------------------------------------------------------------
+
+#ifdef IMGUI_IMPL_METAL_CPP_EXTENSIONS
+// #include <AppKit/AppKit.hpp>
+#ifndef __OBJC__
+
+IMGUI_IMPL_API bool     ImGui_ImplOSX_Init(void* _Nonnull view);
+IMGUI_IMPL_API void     ImGui_ImplOSX_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplOSX_NewFrame(void* _Nullable view);
+
+#endif
+#endif
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_osx.mm b/engines/twp/imgui/backends/imgui_impl_osx.mm
new file mode 100644
index 00000000000..b1ca5b03673
--- /dev/null
+++ b/engines/twp/imgui/backends/imgui_impl_osx.mm
@@ -0,0 +1,812 @@
+// dear imgui: Platform Backend for OSX / Cocoa
+// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..)
+// - Not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac.
+// - Requires linking with the GameController framework ("-framework GameController").
+
+// Implemented features:
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+//  [X] Platform: Mouse support. Can discriminate Mouse/Pen.
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
+//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: IME support.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+#import "imgui.h"
+#ifndef IMGUI_DISABLE
+#import "imgui_impl_osx.h"
+#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+#import <GameController/GameController.h>
+#import <time.h>
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F20 function keys. Stopped mapping F13 into PrintScreen.
+//  2023-04-09: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_Pen.
+//  2023-02-01: Fixed scroll wheel scaling for devices emitting events with hasPreciseScrollingDeltas==false (e.g. non-Apple mices).
+//  2022-11-02: Fixed mouse coordinates before clicking the host window.
+//  2022-10-06: Fixed mouse inputs on flipped views.
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-05-03: Inputs: Removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture.
+//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
+//  2022-03-22: Inputs: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key
+//  2022-02-07: Inputs: Forward keyDown/keyUp events to OS when unused by dear imgui.
+//  2022-01-31: Fixed building with old Xcode versions that are missing gamepad features.
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-12: Inputs: Added basic Platform IME support, hooking the io.SetPlatformImeDataFn() function.
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2021-12-13: *BREAKING CHANGE* Add NSView parameter to ImGui_ImplOSX_Init(). Generally fix keyboard support. Using kVK_* codes for keyboard keys.
+//  2021-12-13: Add game controller support.
+//  2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards.
+//  2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events.
+//  2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key.
+//  2021-04-19: Inputs: Added a fix for keys remaining stuck in pressed state when CMD-tabbing into different application.
+//  2021-01-27: Inputs: Added a fix for mouse position not being reported when mouse buttons other than left one are down.
+//  2020-10-28: Inputs: Added a fix for handling keypad-enter key.
+//  2020-05-25: Inputs: Added a fix for missing trackpad clicks when done with "soft tap".
+//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
+//  2019-10-11: Inputs:  Fix using Backspace key.
+//  2019-07-21: Re-added clipboard handlers as they are not enabled by default in core imgui.cpp (reverted 2019-05-18 change).
+//  2019-05-28: Inputs: Added mouse cursor shape and visibility support.
+//  2019-05-18: Misc: Removed clipboard handlers as they are now supported by core imgui.cpp.
+//  2019-05-11: Inputs: Don't filter character values before calling AddInputCharacter() apart from 0xF700..0xFFFF range.
+//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
+//  2018-07-07: Initial version.
+
+#define APPLE_HAS_BUTTON_OPTIONS (__IPHONE_OS_VERSION_MIN_REQUIRED >= 130000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 || __TV_OS_VERSION_MIN_REQUIRED >= 130000)
+#define APPLE_HAS_CONTROLLER     (__IPHONE_OS_VERSION_MIN_REQUIRED >= 140000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 110000 || __TV_OS_VERSION_MIN_REQUIRED >= 140000)
+#define APPLE_HAS_THUMBSTICKS    (__IPHONE_OS_VERSION_MIN_REQUIRED >= 120100 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101401 || __TV_OS_VERSION_MIN_REQUIRED >= 120100)
+
+ at class ImGuiObserver;
+ at class KeyEventResponder;
+
+// Data
+struct ImGui_ImplOSX_Data
+{
+    CFTimeInterval              Time;
+    NSCursor*                   MouseCursors[ImGuiMouseCursor_COUNT];
+    bool                        MouseCursorHidden;
+    ImGuiObserver*              Observer;
+    KeyEventResponder*          KeyEventResponder;
+    NSTextInputContext*         InputContext;
+    id                          Monitor;
+
+    ImGui_ImplOSX_Data()        { memset(this, 0, sizeof(*this)); }
+};
+
+static ImGui_ImplOSX_Data*      ImGui_ImplOSX_CreateBackendData()   { return IM_NEW(ImGui_ImplOSX_Data)(); }
+static ImGui_ImplOSX_Data*      ImGui_ImplOSX_GetBackendData()      { return (ImGui_ImplOSX_Data*)ImGui::GetIO().BackendPlatformUserData; }
+static void                     ImGui_ImplOSX_DestroyBackendData()  { IM_DELETE(ImGui_ImplOSX_GetBackendData()); }
+
+static inline CFTimeInterval    GetMachAbsoluteTimeInSeconds()      { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
+
+// Forward Declarations
+static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view);
+static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view);
+
+// Undocumented methods for creating cursors.
+ at interface NSCursor()
++ (id)_windowResizeNorthWestSouthEastCursor;
++ (id)_windowResizeNorthEastSouthWestCursor;
++ (id)_windowResizeNorthSouthCursor;
++ (id)_windowResizeEastWestCursor;
+ at end
+
+/**
+ KeyEventResponder implements the NSTextInputClient protocol as is required by the macOS text input manager.
+
+ The macOS text input manager is invoked by calling the interpretKeyEvents method from the keyDown method.
+ Keyboard events are then evaluated by the macOS input manager and valid text input is passed back via the
+ insertText:replacementRange method.
+
+ This is the same approach employed by other cross-platform libraries such as SDL2:
+  https://github.com/spurious/SDL-mirror/blob/e17aacbd09e65a4fd1e166621e011e581fb017a8/src/video/cocoa/SDL_cocoakeyboard.m#L53
+ and GLFW:
+  https://github.com/glfw/glfw/blob/b55a517ae0c7b5127dffa79a64f5406021bf9076/src/cocoa_window.m#L722-L723
+ */
+ at interface KeyEventResponder: NSView<NSTextInputClient>
+ at end
+
+ at implementation KeyEventResponder
+{
+    float _posX;
+    float _posY;
+    NSRect _imeRect;
+}
+
+#pragma mark - Public
+
+- (void)setImePosX:(float)posX imePosY:(float)posY
+{
+    _posX = posX;
+    _posY = posY;
+}
+
+- (void)updateImePosWithView:(NSView *)view
+{
+    NSWindow *window = view.window;
+    if (!window)
+        return;
+    NSRect contentRect = [window contentRectForFrameRect:window.frame];
+    NSRect rect = NSMakeRect(_posX, contentRect.size.height - _posY, 0, 0);
+    _imeRect = [window convertRectToScreen:rect];
+}
+
+- (void)viewDidMoveToWindow
+{
+    // Ensure self is a first responder to receive the input events.
+    [self.window makeFirstResponder:self];
+}
+
+- (void)keyDown:(NSEvent*)event
+{
+    if (!ImGui_ImplOSX_HandleEvent(event, self))
+        [super keyDown:event];
+
+    // Call to the macOS input manager system.
+    [self interpretKeyEvents:@[event]];
+}
+
+- (void)keyUp:(NSEvent*)event
+{
+    if (!ImGui_ImplOSX_HandleEvent(event, self))
+        [super keyUp:event];
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+    ImGuiIO& io = ImGui::GetIO();
+
+    NSString* characters;
+    if ([aString isKindOfClass:[NSAttributedString class]])
+        characters = [aString string];
+    else
+        characters = (NSString*)aString;
+
+    io.AddInputCharactersUTF8(characters.UTF8String);
+}
+
+- (BOOL)acceptsFirstResponder
+{
+    return YES;
+}
+
+- (void)doCommandBySelector:(SEL)myselector
+{
+}
+
+- (nullable NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
+{
+    return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+    return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
+{
+    return _imeRect;
+}
+
+- (BOOL)hasMarkedText
+{
+    return NO;
+}
+
+- (NSRange)markedRange
+{
+    return NSMakeRange(NSNotFound, 0);
+}
+
+- (NSRange)selectedRange
+{
+    return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
+{
+}
+
+- (void)unmarkText
+{
+}
+
+- (nonnull NSArray<NSAttributedStringKey>*)validAttributesForMarkedText
+{
+    return @[];
+}
+
+ at end
+
+ at interface ImGuiObserver : NSObject
+
+- (void)onApplicationBecomeActive:(NSNotification*)aNotification;
+- (void)onApplicationBecomeInactive:(NSNotification*)aNotification;
+
+ at end
+
+ at implementation ImGuiObserver
+
+- (void)onApplicationBecomeActive:(NSNotification*)aNotification
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddFocusEvent(true);
+}
+
+- (void)onApplicationBecomeInactive:(NSNotification*)aNotification
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddFocusEvent(false);
+}
+
+ at end
+
+// Functions
+static ImGuiKey ImGui_ImplOSX_KeyCodeToImGuiKey(int key_code)
+{
+    switch (key_code)
+    {
+        case kVK_ANSI_A: return ImGuiKey_A;
+        case kVK_ANSI_S: return ImGuiKey_S;
+        case kVK_ANSI_D: return ImGuiKey_D;
+        case kVK_ANSI_F: return ImGuiKey_F;
+        case kVK_ANSI_H: return ImGuiKey_H;
+        case kVK_ANSI_G: return ImGuiKey_G;
+        case kVK_ANSI_Z: return ImGuiKey_Z;
+        case kVK_ANSI_X: return ImGuiKey_X;
+        case kVK_ANSI_C: return ImGuiKey_C;
+        case kVK_ANSI_V: return ImGuiKey_V;
+        case kVK_ANSI_B: return ImGuiKey_B;
+        case kVK_ANSI_Q: return ImGuiKey_Q;
+        case kVK_ANSI_W: return ImGuiKey_W;
+        case kVK_ANSI_E: return ImGuiKey_E;
+        case kVK_ANSI_R: return ImGuiKey_R;
+        case kVK_ANSI_Y: return ImGuiKey_Y;
+        case kVK_ANSI_T: return ImGuiKey_T;
+        case kVK_ANSI_1: return ImGuiKey_1;
+        case kVK_ANSI_2: return ImGuiKey_2;
+        case kVK_ANSI_3: return ImGuiKey_3;
+        case kVK_ANSI_4: return ImGuiKey_4;
+        case kVK_ANSI_6: return ImGuiKey_6;
+        case kVK_ANSI_5: return ImGuiKey_5;
+        case kVK_ANSI_Equal: return ImGuiKey_Equal;
+        case kVK_ANSI_9: return ImGuiKey_9;
+        case kVK_ANSI_7: return ImGuiKey_7;
+        case kVK_ANSI_Minus: return ImGuiKey_Minus;
+        case kVK_ANSI_8: return ImGuiKey_8;
+        case kVK_ANSI_0: return ImGuiKey_0;
+        case kVK_ANSI_RightBracket: return ImGuiKey_RightBracket;
+        case kVK_ANSI_O: return ImGuiKey_O;
+        case kVK_ANSI_U: return ImGuiKey_U;
+        case kVK_ANSI_LeftBracket: return ImGuiKey_LeftBracket;
+        case kVK_ANSI_I: return ImGuiKey_I;
+        case kVK_ANSI_P: return ImGuiKey_P;
+        case kVK_ANSI_L: return ImGuiKey_L;
+        case kVK_ANSI_J: return ImGuiKey_J;
+        case kVK_ANSI_Quote: return ImGuiKey_Apostrophe;
+        case kVK_ANSI_K: return ImGuiKey_K;
+        case kVK_ANSI_Semicolon: return ImGuiKey_Semicolon;
+        case kVK_ANSI_Backslash: return ImGuiKey_Backslash;
+        case kVK_ANSI_Comma: return ImGuiKey_Comma;
+        case kVK_ANSI_Slash: return ImGuiKey_Slash;
+        case kVK_ANSI_N: return ImGuiKey_N;
+        case kVK_ANSI_M: return ImGuiKey_M;
+        case kVK_ANSI_Period: return ImGuiKey_Period;
+        case kVK_ANSI_Grave: return ImGuiKey_GraveAccent;
+        case kVK_ANSI_KeypadDecimal: return ImGuiKey_KeypadDecimal;
+        case kVK_ANSI_KeypadMultiply: return ImGuiKey_KeypadMultiply;
+        case kVK_ANSI_KeypadPlus: return ImGuiKey_KeypadAdd;
+        case kVK_ANSI_KeypadClear: return ImGuiKey_NumLock;
+        case kVK_ANSI_KeypadDivide: return ImGuiKey_KeypadDivide;
+        case kVK_ANSI_KeypadEnter: return ImGuiKey_KeypadEnter;
+        case kVK_ANSI_KeypadMinus: return ImGuiKey_KeypadSubtract;
+        case kVK_ANSI_KeypadEquals: return ImGuiKey_KeypadEqual;
+        case kVK_ANSI_Keypad0: return ImGuiKey_Keypad0;
+        case kVK_ANSI_Keypad1: return ImGuiKey_Keypad1;
+        case kVK_ANSI_Keypad2: return ImGuiKey_Keypad2;
+        case kVK_ANSI_Keypad3: return ImGuiKey_Keypad3;
+        case kVK_ANSI_Keypad4: return ImGuiKey_Keypad4;
+        case kVK_ANSI_Keypad5: return ImGuiKey_Keypad5;
+        case kVK_ANSI_Keypad6: return ImGuiKey_Keypad6;
+        case kVK_ANSI_Keypad7: return ImGuiKey_Keypad7;
+        case kVK_ANSI_Keypad8: return ImGuiKey_Keypad8;
+        case kVK_ANSI_Keypad9: return ImGuiKey_Keypad9;
+        case kVK_Return: return ImGuiKey_Enter;
+        case kVK_Tab: return ImGuiKey_Tab;
+        case kVK_Space: return ImGuiKey_Space;
+        case kVK_Delete: return ImGuiKey_Backspace;
+        case kVK_Escape: return ImGuiKey_Escape;
+        case kVK_CapsLock: return ImGuiKey_CapsLock;
+        case kVK_Control: return ImGuiKey_LeftCtrl;
+        case kVK_Shift: return ImGuiKey_LeftShift;
+        case kVK_Option: return ImGuiKey_LeftAlt;
+        case kVK_Command: return ImGuiKey_LeftSuper;
+        case kVK_RightControl: return ImGuiKey_RightCtrl;
+        case kVK_RightShift: return ImGuiKey_RightShift;
+        case kVK_RightOption: return ImGuiKey_RightAlt;
+        case kVK_RightCommand: return ImGuiKey_RightSuper;
+//      case kVK_Function: return ImGuiKey_;
+//      case kVK_VolumeUp: return ImGuiKey_;
+//      case kVK_VolumeDown: return ImGuiKey_;
+//      case kVK_Mute: return ImGuiKey_;
+        case kVK_F1: return ImGuiKey_F1;
+        case kVK_F2: return ImGuiKey_F2;
+        case kVK_F3: return ImGuiKey_F3;
+        case kVK_F4: return ImGuiKey_F4;
+        case kVK_F5: return ImGuiKey_F5;
+        case kVK_F6: return ImGuiKey_F6;
+        case kVK_F7: return ImGuiKey_F7;
+        case kVK_F8: return ImGuiKey_F8;
+        case kVK_F9: return ImGuiKey_F9;
+        case kVK_F10: return ImGuiKey_F10;
+        case kVK_F11: return ImGuiKey_F11;
+        case kVK_F12: return ImGuiKey_F12;
+        case kVK_F13: return ImGuiKey_F13;
+        case kVK_F14: return ImGuiKey_F14;
+        case kVK_F15: return ImGuiKey_F15;
+        case kVK_F16: return ImGuiKey_F16;
+        case kVK_F17: return ImGuiKey_F17;
+        case kVK_F18: return ImGuiKey_F18;
+        case kVK_F19: return ImGuiKey_F19;
+        case kVK_F20: return ImGuiKey_F20;
+        case 0x6E: return ImGuiKey_Menu;
+        case kVK_Help: return ImGuiKey_Insert;
+        case kVK_Home: return ImGuiKey_Home;
+        case kVK_PageUp: return ImGuiKey_PageUp;
+        case kVK_ForwardDelete: return ImGuiKey_Delete;
+        case kVK_End: return ImGuiKey_End;
+        case kVK_PageDown: return ImGuiKey_PageDown;
+        case kVK_LeftArrow: return ImGuiKey_LeftArrow;
+        case kVK_RightArrow: return ImGuiKey_RightArrow;
+        case kVK_DownArrow: return ImGuiKey_DownArrow;
+        case kVK_UpArrow: return ImGuiKey_UpArrow;
+        default: return ImGuiKey_None;
+    }
+}
+
+#ifdef IMGUI_IMPL_METAL_CPP_EXTENSIONS
+
+IMGUI_IMPL_API bool ImGui_ImplOSX_Init(void* _Nonnull view) {
+    return ImGui_ImplOSX_Init((__bridge NSView*)(view));
+}
+
+IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(void* _Nullable view) {
+    return ImGui_ImplOSX_NewFrame((__bridge NSView*)(view));
+}
+
+#endif
+
+
+bool ImGui_ImplOSX_Init(NSView* view)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_CreateBackendData();
+    io.BackendPlatformUserData = (void*)bd;
+
+    // Setup backend capabilities flags
+    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
+    //io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;          // We can honor io.WantSetMousePos requests (optional, rarely used)
+    io.BackendPlatformName = "imgui_impl_osx";
+
+    bd->Observer = [ImGuiObserver new];
+
+    // Load cursors. Some of them are undocumented.
+    bd->MouseCursorHidden = false;
+    bd->MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor];
+    bd->MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
+
+    // Note that imgui.cpp also include default OSX clipboard handlers which can be enabled
+    // by adding '#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS' in imconfig.h and adding '-framework ApplicationServices' to your linker command-line.
+    // Since we are already in ObjC land here, it is easy for us to add a clipboard handler using the NSPasteboard api.
+    io.SetClipboardTextFn = [](void*, const char* str) -> void
+    {
+        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+        [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
+        [pasteboard setString:[NSString stringWithUTF8String:str] forType:NSPasteboardTypeString];
+    };
+
+    io.GetClipboardTextFn = [](void*) -> const char*
+    {
+        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+        NSString* available = [pasteboard availableTypeFromArray: [NSArray arrayWithObject:NSPasteboardTypeString]];
+        if (![available isEqualToString:NSPasteboardTypeString])
+            return nullptr;
+
+        NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
+        if (string == nil)
+            return nullptr;
+
+        const char* string_c = (const char*)[string UTF8String];
+        size_t string_len = strlen(string_c);
+        static ImVector<char> s_clipboard;
+        s_clipboard.resize((int)string_len + 1);
+        strcpy(s_clipboard.Data, string_c);
+        return s_clipboard.Data;
+    };
+
+    [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
+                                             selector:@selector(onApplicationBecomeActive:)
+                                                 name:NSApplicationDidBecomeActiveNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
+                                             selector:@selector(onApplicationBecomeInactive:)
+                                                 name:NSApplicationDidResignActiveNotification
+                                               object:nil];
+
+    // Add the NSTextInputClient to the view hierarchy,
+    // to receive keyboard events and translate them to input text.
+    bd->KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
+    bd->InputContext = [[NSTextInputContext alloc] initWithClient:bd->KeyEventResponder];
+    [view addSubview:bd->KeyEventResponder];
+    ImGui_ImplOSX_AddTrackingArea(view);
+
+    io.SetPlatformImeDataFn = [](ImGuiViewport* viewport, ImGuiPlatformImeData* data) -> void
+    {
+        ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+        if (data->WantVisible)
+        {
+            [bd->InputContext activate];
+        }
+        else
+        {
+            [bd->InputContext discardMarkedText];
+            [bd->InputContext invalidateCharacterCoordinates];
+            [bd->InputContext deactivate];
+        }
+        [bd->KeyEventResponder setImePosX:data->InputPos.x imePosY:data->InputPos.y + data->InputLineHeight];
+    };
+
+    return true;
+}
+
+void ImGui_ImplOSX_Shutdown()
+{
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+
+    bd->Observer = nullptr;
+    if (bd->Monitor != nullptr)
+    {
+        [NSEvent removeMonitor:bd->Monitor];
+        bd->Monitor = nullptr;
+    }
+
+    ImGui_ImplOSX_DestroyBackendData();
+
+    ImGuiIO& io = ImGui::GetIO();
+    io.BackendPlatformName = nullptr;
+    io.BackendPlatformUserData = nullptr;
+    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasGamepad);
+}
+
+static void ImGui_ImplOSX_UpdateMouseCursor()
+{
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+        return;
+
+    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
+    {
+        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+        if (!bd->MouseCursorHidden)
+        {
+            bd->MouseCursorHidden = true;
+            [NSCursor hide];
+        }
+    }
+    else
+    {
+        NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow];
+        // -[NSCursor set] generates measureable overhead if called unconditionally.
+        if (desired != NSCursor.currentCursor)
+        {
+            [desired set];
+        }
+        if (bd->MouseCursorHidden)
+        {
+            bd->MouseCursorHidden = false;
+            [NSCursor unhide];
+        }
+    }
+}
+
+static void ImGui_ImplOSX_UpdateGamepads()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    memset(io.NavInputs, 0, sizeof(io.NavInputs));
+    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
+        return;
+
+#if APPLE_HAS_CONTROLLER
+    GCController* controller = GCController.current;
+#else
+    GCController* controller = GCController.controllers.firstObject;
+#endif
+    if (controller == nil || controller.extendedGamepad == nil)
+    {
+        io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+        return;
+    }
+
+    GCExtendedGamepad* gp = controller.extendedGamepad;
+
+    // Update gamepad inputs
+    #define IM_SATURATE(V)                        (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
+    #define MAP_BUTTON(KEY_NO, BUTTON_NAME)       { io.AddKeyEvent(KEY_NO, gp.BUTTON_NAME.isPressed); }
+    #define MAP_ANALOG(KEY_NO, AXIS_NAME, V0, V1) { float vn = (float)(gp.AXIS_NAME.value - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
+    const float thumb_dead_zone = 0.0f;
+
+#if APPLE_HAS_BUTTON_OPTIONS
+    MAP_BUTTON(ImGuiKey_GamepadBack,            buttonOptions);
+#endif
+    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        buttonX);              // Xbox X, PS Square
+    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       buttonB);              // Xbox B, PS Circle
+    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          buttonY);              // Xbox Y, PS Triangle
+    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        buttonA);              // Xbox A, PS Cross
+    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        dpad.left);
+    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       dpad.right);
+    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          dpad.up);
+    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        dpad.down);
+    MAP_ANALOG(ImGuiKey_GamepadL1,              leftShoulder, 0.0f, 1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadR1,              rightShoulder, 0.0f, 1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadL2,              leftTrigger,  0.0f, 1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadR2,              rightTrigger, 0.0f, 1.0f);
+#if APPLE_HAS_THUMBSTICKS
+    MAP_BUTTON(ImGuiKey_GamepadL3,              leftThumbstickButton);
+    MAP_BUTTON(ImGuiKey_GamepadR3,              rightThumbstickButton);
+#endif
+    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      leftThumbstick.xAxis,  -thumb_dead_zone, -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     leftThumbstick.xAxis,  +thumb_dead_zone, +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        leftThumbstick.yAxis,  +thumb_dead_zone, +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      leftThumbstick.yAxis,  -thumb_dead_zone, -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      rightThumbstick.xAxis, -thumb_dead_zone, -1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     rightThumbstick.xAxis, +thumb_dead_zone, +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        rightThumbstick.yAxis, +thumb_dead_zone, +1.0f);
+    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      rightThumbstick.yAxis, -thumb_dead_zone, -1.0f);
+    #undef MAP_BUTTON
+    #undef MAP_ANALOG
+
+    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+}
+
+static void ImGui_ImplOSX_UpdateImePosWithView(NSView* view)
+{
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.WantTextInput)
+        [bd->KeyEventResponder updateImePosWithView:view];
+}
+
+void ImGui_ImplOSX_NewFrame(NSView* view)
+{
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size
+    if (view)
+    {
+        const float dpi = (float)[view.window backingScaleFactor];
+        io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height);
+        io.DisplayFramebufferScale = ImVec2(dpi, dpi);
+    }
+
+    // Setup time step
+    if (bd->Time == 0.0)
+        bd->Time = GetMachAbsoluteTimeInSeconds();
+
+    double current_time = GetMachAbsoluteTimeInSeconds();
+    io.DeltaTime = (float)(current_time - bd->Time);
+    bd->Time = current_time;
+
+    ImGui_ImplOSX_UpdateMouseCursor();
+    ImGui_ImplOSX_UpdateGamepads();
+    ImGui_ImplOSX_UpdateImePosWithView(view);
+}
+
+// Must only be called for a mouse event, otherwise an exception occurs
+// (Note that NSEventTypeScrollWheel is considered "other input". Oddly enough an exception does not occur with it, but the value will sometimes be wrong!)
+static ImGuiMouseSource GetMouseSource(NSEvent* event)
+{
+    switch (event.subtype)
+    {
+        case NSEventSubtypeTabletPoint:
+            return ImGuiMouseSource_Pen;
+        // macOS considers input from relative touch devices (like the trackpad or Apple Magic Mouse) to be touch input.
+        // This doesn't really make sense for Dear ImGui, which expects absolute touch devices only.
+        // There does not seem to be a simple way to disambiguate things here so we consider NSEventSubtypeTouch events to always come from mice.
+        // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingTouchEvents/HandlingTouchEvents.html#//apple_ref/doc/uid/10000060i-CH13-SW24
+        //case NSEventSubtypeTouch:
+        //    return ImGuiMouseSource_TouchScreen;
+        case NSEventSubtypeMouseEvent:
+        default:
+            return ImGuiMouseSource_Mouse;
+    }
+}
+
+static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
+{
+    ImGuiIO& io = ImGui::GetIO();
+
+    if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown)
+    {
+        int button = (int)[event buttonNumber];
+        if (button >= 0 && button < ImGuiMouseButton_COUNT)
+        {
+            io.AddMouseSourceEvent(GetMouseSource(event));
+            io.AddMouseButtonEvent(button, true);
+        }
+        return io.WantCaptureMouse;
+    }
+
+    if (event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp)
+    {
+        int button = (int)[event buttonNumber];
+        if (button >= 0 && button < ImGuiMouseButton_COUNT)
+        {
+            io.AddMouseSourceEvent(GetMouseSource(event));
+            io.AddMouseButtonEvent(button, false);
+        }
+        return io.WantCaptureMouse;
+    }
+
+    if (event.type == NSEventTypeMouseMoved || event.type == NSEventTypeLeftMouseDragged || event.type == NSEventTypeRightMouseDragged || event.type == NSEventTypeOtherMouseDragged)
+    {
+        NSPoint mousePoint = event.locationInWindow;
+        if (event.window == nil)
+            mousePoint = [[view window] convertPointFromScreen:mousePoint];
+        mousePoint = [view convertPoint:mousePoint fromView:nil];
+        if ([view isFlipped])
+            mousePoint = NSMakePoint(mousePoint.x, mousePoint.y);
+        else
+            mousePoint = NSMakePoint(mousePoint.x, view.bounds.size.height - mousePoint.y);
+        io.AddMouseSourceEvent(GetMouseSource(event));
+        io.AddMousePosEvent((float)mousePoint.x, (float)mousePoint.y);
+        return io.WantCaptureMouse;
+    }
+
+    if (event.type == NSEventTypeScrollWheel)
+    {
+        // Ignore canceled events.
+        //
+        // From macOS 12.1, scrolling with two fingers and then decelerating
+        // by tapping two fingers results in two events appearing:
+        //
+        // 1. A scroll wheel NSEvent, with a phase == NSEventPhaseMayBegin, when the user taps
+        // two fingers to decelerate or stop the scroll events.
+        //
+        // 2. A scroll wheel NSEvent, with a phase == NSEventPhaseCancelled, when the user releases the
+        // two-finger tap. It is this event that sometimes contains large values for scrollingDeltaX and
+        // scrollingDeltaY. When these are added to the current x and y positions of the scrolling view,
+        // it appears to jump up or down. It can be observed in Preview, various JetBrains IDEs and here.
+        if (event.phase == NSEventPhaseCancelled)
+            return false;
+
+        double wheel_dx = 0.0;
+        double wheel_dy = 0.0;
+
+        #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
+        if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6)
+        {
+            wheel_dx = [event scrollingDeltaX];
+            wheel_dy = [event scrollingDeltaY];
+            if ([event hasPreciseScrollingDeltas])
+            {
+                wheel_dx *= 0.01;
+                wheel_dy *= 0.01;
+            }
+        }
+        else
+        #endif // MAC_OS_X_VERSION_MAX_ALLOWED
+        {
+            wheel_dx = [event deltaX] * 0.1;
+            wheel_dy = [event deltaY] * 0.1;
+        }
+        if (wheel_dx != 0.0 || wheel_dy != 0.0)
+            io.AddMouseWheelEvent((float)wheel_dx, (float)wheel_dy);
+
+        return io.WantCaptureMouse;
+    }
+
+    if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp)
+    {
+        if ([event isARepeat])
+            return io.WantCaptureKeyboard;
+
+        int key_code = (int)[event keyCode];
+        ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
+        io.AddKeyEvent(key, event.type == NSEventTypeKeyDown);
+        io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
+
+        return io.WantCaptureKeyboard;
+    }
+
+    if (event.type == NSEventTypeFlagsChanged)
+    {
+        unsigned short key_code = [event keyCode];
+        NSEventModifierFlags modifier_flags = [event modifierFlags];
+
+        io.AddKeyEvent(ImGuiMod_Shift, (modifier_flags & NSEventModifierFlagShift)   != 0);
+        io.AddKeyEvent(ImGuiMod_Ctrl,  (modifier_flags & NSEventModifierFlagControl) != 0);
+        io.AddKeyEvent(ImGuiMod_Alt,   (modifier_flags & NSEventModifierFlagOption)  != 0);
+        io.AddKeyEvent(ImGuiMod_Super, (modifier_flags & NSEventModifierFlagCommand) != 0);
+
+        ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
+        if (key != ImGuiKey_None)
+        {
+            // macOS does not generate down/up event for modifiers. We're trying
+            // to use hardware dependent masks to extract that information.
+            // 'imgui_mask' is left as a fallback.
+            NSEventModifierFlags mask = 0;
+            switch (key)
+            {
+                case ImGuiKey_LeftCtrl:   mask = 0x0001; break;
+                case ImGuiKey_RightCtrl:  mask = 0x2000; break;
+                case ImGuiKey_LeftShift:  mask = 0x0002; break;
+                case ImGuiKey_RightShift: mask = 0x0004; break;
+                case ImGuiKey_LeftSuper:  mask = 0x0008; break;
+                case ImGuiKey_RightSuper: mask = 0x0010; break;
+                case ImGuiKey_LeftAlt:    mask = 0x0020; break;
+                case ImGuiKey_RightAlt:   mask = 0x0040; break;
+                default:
+                    return io.WantCaptureKeyboard;
+            }
+
+            NSEventModifierFlags modifier_flags = [event modifierFlags];
+            io.AddKeyEvent(key, (modifier_flags & mask) != 0);
+            io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
+        }
+
+        return io.WantCaptureKeyboard;
+    }
+
+    return false;
+}
+
+static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view)
+{
+    // If we want to receive key events, we either need to be in the responder chain of the key view,
+    // or else we can install a local monitor. The consequence of this heavy-handed approach is that
+    // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our
+    // window, we'd want to be much more careful than just ingesting the complete event stream.
+    // To match the behavior of other backends, we pass every event down to the OS.
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    if (bd->Monitor)
+        return;
+    NSEventMask eventMask = 0;
+    eventMask |= NSEventMaskMouseMoved | NSEventMaskScrollWheel;
+    eventMask |= NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
+    eventMask |= NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | NSEventMaskRightMouseDragged;
+    eventMask |= NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp | NSEventMaskOtherMouseDragged;
+    eventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
+    bd->Monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask
+                                                        handler:^NSEvent* _Nullable(NSEvent* event)
+    {
+        ImGui_ImplOSX_HandleEvent(event, view);
+        return event;
+    }];
+}
+
+//-----------------------------------------------------------------------------


Commit: 2cb52baa86e027a79650c141b195bc3566b25d7f
    https://github.com/scummvm/scummvm/commit/2cb52baa86e027a79650c141b195bc3566b25d7f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix delay between frames

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cc1f8e15a2c..668c003570b 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -693,17 +693,14 @@ Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
 
+	// Setup Dear ImGui
 	OpenGLSdlGraphics3dManager *manager = dynamic_cast<OpenGLSdlGraphics3dManager *>(g_system->getPaletteManager());
-
 	IMGUI_CHECKVERSION();
 	ImGui::CreateContext();
-
 	g_window = manager->getWindow()->getSDLWindow();
 	SDL_GLContext glContext = SDL_GL_GetCurrentContext();
 	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
 	ImGui_ImplOpenGL3_Init("#version 110");
-
-	// Setup Dear ImGui style
 	ImGui::StyleColorsDark();
 
 	// Set the engine's debugger console
@@ -922,7 +919,9 @@ Common::Error TwpEngine::run() {
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
-		g_system->delayMillis(10);
+		if(delta < 10) {
+			g_system->delayMillis(10 - delta);
+		}
 	}
 
 	// Cleanup


Commit: 27968433deaa198ebe671d43688e64625546db54
    https://github.com/scummvm/scummvm/commit/27968433deaa198ebe671d43688e64625546db54
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use OpenGL::Shader class

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/lighting.cpp
    engines/twp/lighting.h
    engines/twp/shaders.cpp
    engines/twp/shaders.h
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 1f5c7e86f2e..5b83f510e8d 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -139,79 +139,43 @@ Shader::Shader() {
 Shader::~Shader() {
 }
 
-void Shader::init(const char *vertex, const char *fragment) {
-	if (vertex) {
-		_vertex = loadShader(vertex, GL_VERTEX_SHADER);
-	}
-	if (fragment) {
-		_fragment = loadShader(fragment, GL_FRAGMENT_SHADER);
-	}
-	GL_CALL(program = glCreateProgram());
-	GL_CALL(glAttachShader(program, _vertex));
-	GL_CALL(glAttachShader(program, _fragment));
-	GL_CALL(glLinkProgram(program));
-}
-
-uint32 Shader::loadShader(const char *code, uint32 shaderType) {
-	uint32 result;
-	GL_CALL(result = glCreateShader(shaderType));
-	GL_CALL(glShaderSource(result, 1, &code, nullptr));
-	GL_CALL(glCompileShader(result));
-	statusShader(result);
-	return result;
+void Shader::init(const char *name, const char *vertex, const char *fragment, const char *const *attributes) {
+	_shader.loadFromStrings(name, vertex, fragment, attributes);
+
+	uint32 vbo = g_engine->getGfx()._vbo;
+	_shader.enableVertexAttribute("a_position", vbo, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)0);
+	_shader.enableVertexAttribute("a_color", vbo, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)(2 * sizeof(float)));
+	_shader.enableVertexAttribute("a_texCoords", vbo, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)(6 * sizeof(float)));
 }
 
-void Shader::statusShader(uint32 shader) {
-	int status;
-	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
-	if (status != GL_TRUE) {
-		int logLength;
-		char message[1024];
-		glGetShaderInfoLog(shader, 1024, &logLength, &message[0]);
-		debug("%s", message);
-	}
+int Shader::getUniformLocation(const char *name) const {
+	return _shader.getUniformLocation(name);
+}
+
+void Shader::setUniform(const char * name, int value) {
+	_shader.setUniform(name, value);
 }
 
-int Shader::getUniformLocation(const char *name) {
-	int loc;
-	GL_CALL(loc = glGetUniformLocation(program, name));
-	return loc;
+void Shader::setUniform(const char * name, float value) {
+	_shader.setUniform1f(name, value);
 }
 
-void Shader::setUniform(const char *name, Math::Matrix4 value) {
-	GLint prev;
-	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
-	glUseProgram(program);
-	int loc = getUniformLocation(name);
-	GL_CALL(glUniformMatrix4fv(loc, 1, GL_FALSE, value.getData()));
-	glUseProgram(prev);
+void Shader::setUniform(const char * name, float* value, size_t count) {
+	GLint loc = _shader.getUniformLocation(name);
+	GL_CALL(glUniform1fv(loc, count, value));
 }
 
-void Shader::setUniform(const char *name, float value) {
-	GLint prev;
-	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
-	glUseProgram(program);
-	int loc = getUniformLocation(name);
-	GL_CALL(glUniform1f(loc, value));
-	glUseProgram(prev);
+
+void Shader::setUniform(const char *name, Math::Vector2d value) {
+	_shader.setUniform(name, value);
 }
 
-void Shader::setUniform(const char *name, Math::Vector3d value) {
-	GLint prev;
-	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
-	glUseProgram(program);
-	int loc = getUniformLocation(name);
-	GL_CALL(glUniform3fv(loc, 1, value.getData()));
-	glUseProgram(prev);
+void Shader::setUniform3(const char *name, Color value) {
+	_shader.setUniform(name, Math::Vector3d(value.v));
 }
 
-void Shader::setUniform(const char *name, Color value) {
-	GLint prev;
-	glGetIntegerv(GL_CURRENT_PROGRAM, &prev);
-	glUseProgram(program);
-	int loc = getUniformLocation(name);
-	GL_CALL(glUniform3fv(loc, 1, value.v));
-	glUseProgram(prev);
+void Shader::setUniform4(const char *name, Color value) {
+	_shader.setUniform(name, Math::Vector4d(value.v));
 }
 
 void Gfx::init() {
@@ -224,34 +188,24 @@ void Gfx::init() {
 	empty.setPixels(pixels);
 	_emptyTexture.load(empty);
 
+	GL_CALL(glGenBuffers(1, &_vbo));
+	GL_CALL(glGenBuffers(1, &_ebo));
+	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
+	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
+
 	const char *fragmentSrc = R"(#version 110
-		varying vec4 v_color;
+	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	uniform sampler2D u_texture;
 	void main() {
 		vec4 tex_color = texture2D(u_texture, v_texCoords);
 		gl_FragColor = v_color * tex_color;
 	})";
-	_defaultShader.init(vsrc, fragmentSrc);
+	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
+	_defaultShader.init("default", vsrc, fragmentSrc, attributes);
 	_shader = &_defaultShader;
 	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
 
-	GL_CALL(glGenBuffers(1, &_vbo));
-	GL_CALL(glGenBuffers(1, &_ebo));
-	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
-	GL_CALL(_posLoc = glGetAttribLocation(_defaultShader.program, "a_position"));
-	GL_CALL(_colLoc = glGetAttribLocation(_defaultShader.program, "a_color"));
-	GL_CALL(_texCoordsLoc = glGetAttribLocation(_defaultShader.program, "a_texCoords"));
-	GL_CALL(glGetUniformLocation(_defaultShader.program, "u_texture"));
-	GL_CALL(glGetUniformLocation(_defaultShader.program, "u_transform"));
-	GL_CALL(glVertexAttribPointer(_posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-	GL_CALL(glEnableVertexAttribArray(_posLoc));
-	GL_CALL(glVertexAttribPointer(_colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-	GL_CALL(glEnableVertexAttribArray(_colLoc));
-	GL_CALL(glVertexAttribPointer(_texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
-	GL_CALL(glEnableVertexAttribArray(_texCoordsLoc));
-
 	glBindBuffer(GL_ARRAY_BUFFER, 0);
 	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFbo);
 }
@@ -292,18 +246,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
 		GL_CALL(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 
-		GL_CALL(glUseProgram(_shader->program));
-
-		int posLoc = glGetAttribLocation(_defaultShader.program, "a_position");
-		int colLoc = glGetAttribLocation(_defaultShader.program, "a_color");
-		int texCoordsLoc = glGetAttribLocation(_defaultShader.program, "a_texCoords");
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-		GL_CALL(glEnableVertexAttribArray(posLoc));
-		GL_CALL(glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-		GL_CALL(glEnableVertexAttribArray(colLoc));
-		GL_CALL(glVertexAttribPointer(colLoc, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-		GL_CALL(glEnableVertexAttribArray(texCoordsLoc));
-		GL_CALL(glVertexAttribPointer(texCoordsLoc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
 
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
@@ -313,10 +256,10 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 		GL_CALL(glUniform1i(0, 0));
 
 		Math::Matrix4 m = getFinalTransform(trsf);
-		_shader->setUniform("u_transform", m);
+		_shader->_shader.setUniform("u_transform", m);
 		GL_CALL(glDrawArrays((GLenum)primitivesType, 0, v_size));
+		_shader->_shader.unbind();
 
-		glUseProgram(0);
 		glBindBuffer(GL_ARRAY_BUFFER, 0);
 
 		GL_CALL(glDisableVertexAttribArray(0));
@@ -344,16 +287,11 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 		GL_CALL(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD));
 		GL_CALL(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 
-		GL_CALL(glUseProgram(_shader->program));
+		_shader->_shader.use();
 
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 
-		GL_CALL(glEnableVertexAttribArray(0));
-		GL_CALL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0));
-		GL_CALL(glEnableVertexAttribArray(2));
-		GL_CALL(glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(2 * sizeof(float))));
-		GL_CALL(glEnableVertexAttribArray(1));
-		GL_CALL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(6 * sizeof(float))));
+		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
@@ -363,7 +301,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 		if (num == 0) {
 			GL_CALL(glActiveTexture(GL_TEXTURE0));
 			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
-			GL_CALL(glUniform1i(_shader->getUniformLocation("u_texture"), 0));
+			GL_CALL(glUniform1i(_shader->_shader.getUniformLocation("u_texture"), 0));
 		} else {
 			for (int i = 0; i < num; i++) {
 				GL_CALL(glActiveTexture(GL_TEXTURE0 + i));
@@ -372,16 +310,12 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 			}
 		}
 
-		_shader->setUniform("u_transform", getFinalTransform(trsf));
+		_shader->_shader.setUniform("u_transform", getFinalTransform(trsf));
 		_shader->applyUniforms();
 		GL_CALL(glDrawElements(primitivesType, i_size, GL_UNSIGNED_INT, NULL));
+		_shader->_shader.unbind();
 
-		glUseProgram(0);
 		glBindBuffer(GL_ARRAY_BUFFER, 0);
-		GL_CALL(glDisableVertexAttribArray(0));
-		GL_CALL(glDisableVertexAttribArray(1));
-		GL_CALL(glDisableVertexAttribArray(2));
-
 		glDisable(GL_BLEND);
 	}
 }
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index ba8db0b8cb9..caf94c480f3 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -28,6 +28,7 @@
 #include "common/rect.h"
 #include "math/vector2d.h"
 #include "math/matrix4.h"
+#include "graphics/opengl/shader.h"
 
 namespace Twp {
 
@@ -123,40 +124,42 @@ struct TextureSlot {
 };
 
 class Shader {
+public:
+friend class Gfx;
 public:
 	Shader();
 	virtual ~Shader();
 
-	void init(const char *vertex, const char *fragment);
+	void init(const char *name, const char *vertex, const char *fragment, const char *const *attributes);
 
-	void setUniform(const char *name, Math::Matrix4 value);
+	int getUniformLocation(const char *name) const;
+
+	void setUniform(const char * name, int value);
 	void setUniform(const char * name, float value);
+	void setUniform(const char * name, float* value, size_t count);
+
+	void setUniform(const char *name, Math::Matrix4 value);
+	void setUniform(const char * name, Math::Vector2d value);
 	void setUniform(const char * name, Math::Vector3d value);
-	void setUniform(const char * name, Color value);
+	void setUniform3(const char * name, Color value);
+	void setUniform4(const char * name, Color value);
 
 	virtual void applyUniforms() {}
 	virtual int getNumTextures() { return 0;};
 	virtual int getTexture(int index) { return 0;};
 	virtual int getTextureLoc(int index) { return 0;};
 
-	int getUniformLocation(const char *name);
-
 private:
-	uint32 loadShader(const char *code, uint32 shaderType);
-	void statusShader(uint32 shader);
-
-public:
-	uint32 program;
-
-private:
-	uint32 _vertex;
-	uint32 _fragment;
 	Common::HashMap<int, TextureSlot> _textures;
+	OpenGL::Shader _shader;
 };
 
 typedef Common::HashMap<int, TextureSlot> Textures;
 
 class Gfx {
+public:
+	friend class Shader;
+
 public:
 	void init();
 
@@ -193,7 +196,6 @@ private:
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture *_texture = nullptr;
-	int32 _posLoc = 0, _colLoc = 0, _texCoordsLoc = 0;
 	int32 _oldFbo = 0;
 };
 } // namespace Twp
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 02446ef2670..31933a5ab76 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -98,21 +98,17 @@ void HudShader::init() {
 		texColor *= v_color;
 		gl_FragColor = texColor;
 	})";
-	Shader::init(vsrc, fsrc);
-
-	GL_CALL(_rangesLoc = glGetUniformLocation(program, "u_ranges"));
-	GL_CALL(_shadowColorLoc = glGetUniformLocation(program, "u_shadowColor"));
-	GL_CALL(_normalColorLoc = glGetUniformLocation(program, "u_normalColor"));
-	GL_CALL(_highlightColorLoc = glGetUniformLocation(program, "u_highlightColor"));
+	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
+	Shader::init("hud", v_source, f_source, attributes);
 }
 
 HudShader::~HudShader() {}
 
 void HudShader::applyUniforms() {
-	GL_CALL(glUniform2f(_rangesLoc, 0.8f, 0.8f));
-	GL_CALL(glUniform4f(_shadowColorLoc, _shadowColor.rgba.r, _shadowColor.rgba.g, _shadowColor.rgba.b, _shadowColor.rgba.a));
-	GL_CALL(glUniform4f(_normalColorLoc, _normalColor.rgba.r, _normalColor.rgba.g, _normalColor.rgba.b, _normalColor.rgba.a));
-	GL_CALL(glUniform4f(_highlightColorLoc, _highlightColor.rgba.r, _highlightColor.rgba.g, _highlightColor.rgba.b, _highlightColor.rgba.a));
+	setUniform("u_ranges", Math::Vector2d(0.8f, 0.8f));
+	setUniform4("u_shadowColor", _shadowColor);
+	setUniform4("u_normalColor", _normalColor);
+	setUniform4("u_highlightColor", _highlightColor);
 }
 
 Hud::Hud() : Node("hud") {
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 7b6ddbf54ef..1eb97bf688d 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -101,12 +101,6 @@ public:
 	Color _shadowColor;
 	Color _normalColor;
 	Color _highlightColor;
-
-private:
-	int _rangesLoc;
-	int _shadowColorLoc;
-	int _normalColorLoc;
-	int _highlightColorLoc;
 };
 
 class Hud : public Node {
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 8af671fa2af..2a892967d56 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -4,113 +4,113 @@
 
 namespace Twp {
 
-Lighting::Lighting() {
-	const char *vshader = R"(#version 110
-		uniform mat4 u_transform;
-	attribute vec2 a_position;
-	attribute vec4 a_color;
-	attribute vec2 a_texCoords;
-	varying vec4 v_color;
-	varying vec2 v_texCoords;
-	void main() {
-		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-		v_color = a_color;
-		v_texCoords = a_texCoords;
-	})";
-
-	const char *fshader = R"(#version 110
-	varying vec2 v_texCoords;
-	varying vec4 v_color;
-
-	uniform sampler2D u_texture;
-
-	uniform vec2 u_contentSize;
-	uniform vec3 u_ambientColor;
-	uniform vec2 u_spritePosInSheet;
-	uniform vec2 u_spriteSizeRelToSheet;
-	uniform vec2 u_spriteOffset;
-
-	uniform int u_numberLights;
-	uniform vec3 u_lightPos[50];
-	uniform vec3 u_lightColor[50];
-	uniform float u_brightness[50];
-	uniform float u_cutoffRadius[50];
-	uniform float u_halfRadius[50];
-	uniform float u_coneCosineHalfConeAngle[50];
-	uniform float u_coneFalloff[50];
-	uniform vec2 u_coneDirection[50];
-
-	void main(void) {
-		vec4 texColor = texture2D(u_texture, v_texCoords);
-
-		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
-		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
-		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
-
-		vec3 diffuse = vec3(0, 0, 0);
-		for (int i = 0; i < u_numberLights; i++) {
-			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
-			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
-			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
-				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
-				float dx_1 = 0.5 / intercept;
-				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
-				float offset = 0.5 + intercept * dx_2;
-
-				float lightDist = length(lightVec);
-				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
-				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
-				float falloffSelect = step(intercept, lightDist);
-				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
-				float spotLight = u_brightness[i] * falloffTerm;
-
-				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
-
-				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
-				float halfConeRange = coneRange * u_coneFalloff[i];
-				float conePos = 1.0 - coneValue;
-				float coneFalloff = 1.0;
-				if (conePos > halfConeRange) {
-					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
-				}
-
-				diffuse += ltdiffuse * coneFalloff;
-				;
-			}
-		}
-		vec3 finalLight = (diffuse);
-		vec4 finalCol = texColor * v_color;
-		finalCol.rgb = finalCol.rgb * u_ambientColor;
-		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
-	})";
-	init(vshader, fshader);
-
-	GL_CALL(_contentSizeLoc = glGetUniformLocation(program, "u_contentSize"));
-	GL_CALL(_spriteOffsetLoc = glGetUniformLocation(program, "u_spriteOffset"));
-	GL_CALL(_spritePosInSheetLoc = glGetUniformLocation(program, "u_spritePosInSheet"));
-	GL_CALL(_spriteSizeRelToSheetLoc = glGetUniformLocation(program, "u_spriteSizeRelToSheet"));
-	GL_CALL(_numberLightsLoc = glGetUniformLocation(program, "u_numberLights"));
-	GL_CALL(_ambientColorLoc = glGetUniformLocation(program, "u_ambientColor"));
-}
-
-Lighting::~Lighting() {}
-
-void Lighting::setSpriteSheetFrame(const SpriteSheetFrame& frame, const Texture& texture) {
-	_contentSize = frame.sourceSize;
-	//_spriteOffset = {-frame.frame.width() / 2.f, -frame.frame.height() / 2.f};
-	_spriteOffset = {0, (float)frame.frame.height()};
-	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
-	_spriteSizeRelToSheet = {(float)(frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height};
-}
-
-void Lighting::applyUniforms() {
-	GL_CALL(glUniform1i(_numberLightsLoc, 0));
-	float ambientColor[] = {1,1,1};
-	GL_CALL(glUniform3fv(_ambientColorLoc, 1, ambientColor));
-	GL_CALL(glUniform2fv(_contentSizeLoc, 1, _contentSize.getData()));
-	GL_CALL(glUniform2fv(_spriteOffsetLoc, 1, _spriteOffset.getData()));
-	GL_CALL(glUniform2fv(_spritePosInSheetLoc, 1, _spritePosInSheet.getData()));
-	GL_CALL(glUniform2fv(_spriteSizeRelToSheetLoc, 1, _spriteSizeRelToSheet.getData()));
-}
+// Lighting::Lighting() {
+// 	const char *vshader = R"(#version 110
+// 		uniform mat4 u_transform;
+// 	attribute vec2 a_position;
+// 	attribute vec4 a_color;
+// 	attribute vec2 a_texCoords;
+// 	varying vec4 v_color;
+// 	varying vec2 v_texCoords;
+// 	void main() {
+// 		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+// 		v_color = a_color;
+// 		v_texCoords = a_texCoords;
+// 	})";
+
+// 	const char *fshader = R"(#version 110
+// 	varying vec2 v_texCoords;
+// 	varying vec4 v_color;
+
+// 	uniform sampler2D u_texture;
+
+// 	uniform vec2 u_contentSize;
+// 	uniform vec3 u_ambientColor;
+// 	uniform vec2 u_spritePosInSheet;
+// 	uniform vec2 u_spriteSizeRelToSheet;
+// 	uniform vec2 u_spriteOffset;
+
+// 	uniform int u_numberLights;
+// 	uniform vec3 u_lightPos[50];
+// 	uniform vec3 u_lightColor[50];
+// 	uniform float u_brightness[50];
+// 	uniform float u_cutoffRadius[50];
+// 	uniform float u_halfRadius[50];
+// 	uniform float u_coneCosineHalfConeAngle[50];
+// 	uniform float u_coneFalloff[50];
+// 	uniform vec2 u_coneDirection[50];
+
+// 	void main(void) {
+// 		vec4 texColor = texture2D(u_texture, v_texCoords);
+
+// 		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+// 		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
+// 		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+// 		vec3 diffuse = vec3(0, 0, 0);
+// 		for (int i = 0; i < u_numberLights; i++) {
+// 			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+// 			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
+// 			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
+// 				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+// 				float dx_1 = 0.5 / intercept;
+// 				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+// 				float offset = 0.5 + intercept * dx_2;
+
+// 				float lightDist = length(lightVec);
+// 				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+// 				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+// 				float falloffSelect = step(intercept, lightDist);
+// 				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+// 				float spotLight = u_brightness[i] * falloffTerm;
+
+// 				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+// 				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
+// 				float halfConeRange = coneRange * u_coneFalloff[i];
+// 				float conePos = 1.0 - coneValue;
+// 				float coneFalloff = 1.0;
+// 				if (conePos > halfConeRange) {
+// 					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
+// 				}
+
+// 				diffuse += ltdiffuse * coneFalloff;
+// 				;
+// 			}
+// 		}
+// 		vec3 finalLight = (diffuse);
+// 		vec4 finalCol = texColor * v_color;
+// 		finalCol.rgb = finalCol.rgb * u_ambientColor;
+// 		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
+// 	})";
+// 	init(vshader, fshader);
+
+// 	GL_CALL(_contentSizeLoc = glGetUniformLocation(program, "u_contentSize"));
+// 	GL_CALL(_spriteOffsetLoc = glGetUniformLocation(program, "u_spriteOffset"));
+// 	GL_CALL(_spritePosInSheetLoc = glGetUniformLocation(program, "u_spritePosInSheet"));
+// 	GL_CALL(_spriteSizeRelToSheetLoc = glGetUniformLocation(program, "u_spriteSizeRelToSheet"));
+// 	GL_CALL(_numberLightsLoc = glGetUniformLocation(program, "u_numberLights"));
+// 	GL_CALL(_ambientColorLoc = glGetUniformLocation(program, "u_ambientColor"));
+// }
+
+// Lighting::~Lighting() {}
+
+// void Lighting::setSpriteSheetFrame(const SpriteSheetFrame& frame, const Texture& texture) {
+// 	_contentSize = frame.sourceSize;
+// 	//_spriteOffset = {-frame.frame.width() / 2.f, -frame.frame.height() / 2.f};
+// 	_spriteOffset = {0, (float)frame.frame.height()};
+// 	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
+// 	_spriteSizeRelToSheet = {(float)(frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height};
+// }
+
+// void Lighting::applyUniforms() {
+// 	GL_CALL(glUniform1i(_numberLightsLoc, 0));
+// 	float ambientColor[] = {1,1,1};
+// 	GL_CALL(glUniform3fv(_ambientColorLoc, 1, ambientColor));
+// 	GL_CALL(glUniform2fv(_contentSizeLoc, 1, _contentSize.getData()));
+// 	GL_CALL(glUniform2fv(_spriteOffsetLoc, 1, _spriteOffset.getData()));
+// 	GL_CALL(glUniform2fv(_spritePosInSheetLoc, 1, _spritePosInSheet.getData()));
+// 	GL_CALL(glUniform2fv(_spriteSizeRelToSheetLoc, 1, _spriteSizeRelToSheet.getData()));
+// }
 
 } // namespace Twp
diff --git a/engines/twp/lighting.h b/engines/twp/lighting.h
index 969d0197840..895a194a49e 100644
--- a/engines/twp/lighting.h
+++ b/engines/twp/lighting.h
@@ -27,29 +27,29 @@
 
 namespace Twp {
 
-class Lighting: public Shader {
-public:
-	Lighting();
-	virtual ~Lighting();
-
-	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture);
-
-private:
-	virtual void applyUniforms() final;
-
-private:
-	int32 _contentSizeLoc;
-	int32 _spriteOffsetLoc;
-	int32 _spritePosInSheetLoc;
-	int32 _spriteSizeRelToSheetLoc;
-	int32 _numberLightsLoc;
-	int32 _ambientColorLoc;
-
-	Math::Vector2d _contentSize;
-	Math::Vector2d _spriteOffset;
-	Math::Vector2d _spritePosInSheet;
-	Math::Vector2d _spriteSizeRelToSheet;
-};
+// class Lighting {
+// public:
+// 	Lighting();
+// 	virtual ~Lighting();
+
+// 	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture);
+
+// private:
+// 	virtual void applyUniforms() final;
+
+// private:
+// 	int32 _contentSizeLoc;
+// 	int32 _spriteOffsetLoc;
+// 	int32 _spritePosInSheetLoc;
+// 	int32 _spriteSizeRelToSheetLoc;
+// 	int32 _numberLightsLoc;
+// 	int32 _ambientColorLoc;
+
+// 	Math::Vector2d _contentSize;
+// 	Math::Vector2d _spriteOffset;
+// 	Math::Vector2d _spritePosInSheet;
+// 	Math::Vector2d _spriteSizeRelToSheet;
+// };
 
 } // namespace Twp
 
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 5c4ed4e8163..791fa628582 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -355,24 +355,21 @@ FadeShader::FadeShader() {
 		}
 		gl_FragColor.a = 1.0;
 	})";
-	init(vsrc, fadeShader);
-
-	GL_CALL(_textureLoc[0] = glGetUniformLocation(program, "u_texture"));
-	GL_CALL(_textureLoc[1] = glGetUniformLocation(program, "u_texture2"));
-	GL_CALL(_timerLoc = glGetUniformLocation(program, "u_timer"));
-	GL_CALL(_fadeLoc = glGetUniformLocation(program, "u_fade"));
-	GL_CALL(_fadeToSepLoc = glGetUniformLocation(program, "u_fadeToSep"));
-	GL_CALL(_movementLoc = glGetUniformLocation(program, "u_movement"));
+	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
+	init("fadeShader", vsrc, fadeShader, attributes);
+
+	GL_CALL(_textureLoc[0] = getUniformLocation("u_texture"));
+	GL_CALL(_textureLoc[1] = getUniformLocation("u_texture2"));
 }
 
 FadeShader::~FadeShader() {}
 
 void FadeShader::applyUniforms() {
 	float movement = (sin(M_PI * _fade) * _movement);
-	GL_CALL(glUniform1f(_timerLoc, _elapsed));
-	GL_CALL(glUniform1f(_fadeLoc, _fade));
-	GL_CALL(glUniform1i(_fadeToSepLoc, _fadeToSepia ? 1 : 0));
-	GL_CALL(glUniform1f(_movementLoc, movement));
+	setUniform("u_timer", _elapsed);
+	setUniform("u_fade", _fade);
+	setUniform("u_fadeToSep", _fadeToSepia ? 1 : 0);
+	setUniform("u_movement", movement);
 }
 
 int FadeShader::getNumTextures() { return 2; }
@@ -392,7 +389,7 @@ int FadeShader::getTextureLoc(int index) { return _textureLoc[index]; }
 void ShaderParams::updateShader() {
 	if (effect == RoomEffect::Sepia) {
 		Shader *shader = g_engine->getGfx().getShader();
-		shader->setUniform("RandomValue", randomValue);
+		shader->setUniform("RandomValue", randomValue, 5);
 		shader->setUniform("TimeLapse", timeLapse);
 	}
 	//   } else if (effect == RoomEffect::Vhs) {
@@ -405,9 +402,9 @@ void ShaderParams::updateShader() {
 		shader->setUniform("iGlobalTime", iGlobalTime);
 		shader->setUniform("iFade", iFade);
 		shader->setUniform("wobbleIntensity", wobbleIntensity);
-		shader->setUniform("shadows", shadows);
-		shader->setUniform("midtones", midtones);
-		shader->setUniform("highlights", highlights);
+		shader->setUniform3("shadows", shadows);
+		shader->setUniform3("midtones", midtones);
+		shader->setUniform3("highlights", highlights);
 	}
 }
 
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 07a628499ce..94bd26cceca 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -82,10 +82,6 @@ public:
 
 private:
 	int _textureLoc[2];
-	int _timerLoc;
-	int _fadeLoc;
-	int _fadeToSepLoc;
-	int _movementLoc;
 };
 
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 668c003570b..976d12951e5 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -708,12 +708,14 @@ Common::Error TwpEngine::run() {
 
 	_gfx.init();
 	_hud.init();
-	_bwShader.init(vsrc, bwShader);
-	_ghostShader.init(vsrc, ghostShader);
-	_sepiaShader.init(vsrc, sepiaShader);
+
+	const char* attributes[]={"a_position", "a_color", "a_texCoords", nullptr};
+	_bwShader.init("black&white", vsrc, bwShader, attributes);
+	_ghostShader.init("ghost", vsrc, ghostShader, attributes);
+	_sepiaShader.init("sepia", vsrc, sepiaShader, attributes);
 	_fadeShader.reset(new FadeShader());
 
-	_lighting = new Lighting();
+	// _lighting = new Lighting();
 
 	_pack.init();
 


Commit: ef2d87e1d77c92114c355b0f6e98c268e8def4bb
    https://github.com/scummvm/scummvm/commit/ef2d87e1d77c92114c355b0f6e98c268e8def4bb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Simplify shader class

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/hud.cpp
    engines/twp/shaders.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 5b83f510e8d..3a6cf8b27cb 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -139,7 +139,8 @@ Shader::Shader() {
 Shader::~Shader() {
 }
 
-void Shader::init(const char *name, const char *vertex, const char *fragment, const char *const *attributes) {
+void Shader::init(const char *name, const char *vertex, const char *fragment) {
+	const char* attributes[]={"a_position", "a_color", "a_texCoords", nullptr};
 	_shader.loadFromStrings(name, vertex, fragment, attributes);
 
 	uint32 vbo = g_engine->getGfx()._vbo;
@@ -201,8 +202,7 @@ void Gfx::init() {
 		vec4 tex_color = texture2D(u_texture, v_texCoords);
 		gl_FragColor = v_color * tex_color;
 	})";
-	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
-	_defaultShader.init("default", vsrc, fragmentSrc, attributes);
+	_defaultShader.init("default", vsrc, fragmentSrc);
 	_shader = &_defaultShader;
 	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
 
@@ -301,7 +301,7 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 		if (num == 0) {
 			GL_CALL(glActiveTexture(GL_TEXTURE0));
 			GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
-			GL_CALL(glUniform1i(_shader->_shader.getUniformLocation("u_texture"), 0));
+			GL_CALL(glUniform1i(_shader->getUniformLocation("u_texture"), 0));
 		} else {
 			for (int i = 0; i < num; i++) {
 				GL_CALL(glActiveTexture(GL_TEXTURE0 + i));
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index caf94c480f3..be6c6da0948 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -130,7 +130,7 @@ public:
 	Shader();
 	virtual ~Shader();
 
-	void init(const char *name, const char *vertex, const char *fragment, const char *const *attributes);
+	void init(const char *name, const char *vertex, const char *fragment);
 
 	int getUniformLocation(const char *name) const;
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 31933a5ab76..3bb3ccfdd17 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -98,8 +98,7 @@ void HudShader::init() {
 		texColor *= v_color;
 		gl_FragColor = texColor;
 	})";
-	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
-	Shader::init("hud", v_source, f_source, attributes);
+	Shader::init("hud", v_source, f_source);
 }
 
 HudShader::~HudShader() {}
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 791fa628582..946e47e9a4e 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -355,8 +355,7 @@ FadeShader::FadeShader() {
 		}
 		gl_FragColor.a = 1.0;
 	})";
-	const char* attributes[]={"a_position","a_color","a_texCoords",nullptr};
-	init("fadeShader", vsrc, fadeShader, attributes);
+	init("fadeShader", vsrc, fadeShader);
 
 	GL_CALL(_textureLoc[0] = getUniformLocation("u_texture"));
 	GL_CALL(_textureLoc[1] = getUniformLocation("u_texture2"));
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 976d12951e5..0f226b03578 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -709,10 +709,9 @@ Common::Error TwpEngine::run() {
 	_gfx.init();
 	_hud.init();
 
-	const char* attributes[]={"a_position", "a_color", "a_texCoords", nullptr};
-	_bwShader.init("black&white", vsrc, bwShader, attributes);
-	_ghostShader.init("ghost", vsrc, ghostShader, attributes);
-	_sepiaShader.init("sepia", vsrc, sepiaShader, attributes);
+	_bwShader.init("black&white", vsrc, bwShader);
+	_ghostShader.init("ghost", vsrc, ghostShader);
+	_sepiaShader.init("sepia", vsrc, sepiaShader);
 	_fadeShader.reset(new FadeShader());
 
 	// _lighting = new Lighting();


Commit: 0d87082d114366cf6b5e41064f12b462cdb34412
    https://github.com/scummvm/scummvm/commit/0d87082d114366cf6b5e41064f12b462cdb34412
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Debug lighting shader

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/lighting.cpp
    engines/twp/lighting.h
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/walkboxnode.cpp
    engines/twp/walkboxnode.h


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 3a6cf8b27cb..1e221e6d905 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -140,7 +140,7 @@ Shader::~Shader() {
 }
 
 void Shader::init(const char *name, const char *vertex, const char *fragment) {
-	const char* attributes[]={"a_position", "a_color", "a_texCoords", nullptr};
+	const char *attributes[] = {"a_position", "a_color", "a_texCoords", nullptr};
 	_shader.loadFromStrings(name, vertex, fragment, attributes);
 
 	uint32 vbo = g_engine->getGfx()._vbo;
@@ -153,19 +153,28 @@ int Shader::getUniformLocation(const char *name) const {
 	return _shader.getUniformLocation(name);
 }
 
-void Shader::setUniform(const char * name, int value) {
+void Shader::setUniform(const char *name, int value) {
 	_shader.setUniform(name, value);
 }
 
-void Shader::setUniform(const char * name, float value) {
+void Shader::setUniform(const char *name, float value) {
 	_shader.setUniform1f(name, value);
 }
 
-void Shader::setUniform(const char * name, float* value, size_t count) {
+void Shader::setUniform(const char *name, float *value, size_t count) {
 	GLint loc = _shader.getUniformLocation(name);
 	GL_CALL(glUniform1fv(loc, count, value));
 }
 
+void Shader::setUniform2(const char *name, float *value, size_t count) {
+	GLint loc = _shader.getUniformLocation(name);
+	GL_CALL(glUniform2fv(loc, count, value));
+}
+
+void Shader::setUniform3(const char *name, float *value, size_t count) {
+	GLint loc = _shader.getUniformLocation(name);
+	GL_CALL(glUniform3fv(loc, count, value));
+}
 
 void Shader::setUniform(const char *name, Math::Vector2d value) {
 	_shader.setUniform(name, value);
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index be6c6da0948..07c6e2b8e06 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -137,6 +137,8 @@ public:
 	void setUniform(const char * name, int value);
 	void setUniform(const char * name, float value);
 	void setUniform(const char * name, float* value, size_t count);
+	void setUniform2(const char * name, float* value, size_t count);
+	void setUniform3(const char * name, float* value, size_t count);
 
 	void setUniform(const char *name, Math::Matrix4 value);
 	void setUniform(const char * name, Math::Vector2d value);
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 2a892967d56..091e519fd0b 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -1,116 +1,223 @@
 #include "twp/lighting.h"
+#include "twp/room.h"
 #include "graphics/opengl/debug.h"
 #include "graphics/opengl/system_headers.h"
+#include "common/math.h"
 
 namespace Twp {
 
-// Lighting::Lighting() {
-// 	const char *vshader = R"(#version 110
-// 		uniform mat4 u_transform;
-// 	attribute vec2 a_position;
-// 	attribute vec4 a_color;
-// 	attribute vec2 a_texCoords;
-// 	varying vec4 v_color;
-// 	varying vec2 v_texCoords;
-// 	void main() {
-// 		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-// 		v_color = a_color;
-// 		v_texCoords = a_texCoords;
-// 	})";
+Lighting::Lighting() {
+	const char *vshader = R"(#version 110
+	uniform mat4 u_transform;
+	attribute vec2 a_position;
+	attribute vec4 a_color;
+	attribute vec2 a_texCoords;
+	varying vec4 v_color;
+	varying vec2 v_texCoords;
+	void main() {
+		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+		v_color = a_color;
+		v_texCoords = a_texCoords;
+	})";
+
+	const char *fshader = R"(#version 110
+	varying vec2 v_texCoords;
+	varying vec4 v_color;
+
+	uniform sampler2D u_texture;
+
+	uniform vec2 u_contentSize;
+	uniform vec3 u_ambientColor;
+	uniform vec2 u_spritePosInSheet;
+	uniform vec2 u_spriteSizeRelToSheet;
+	uniform vec2 u_spriteOffset;
+
+	uniform int u_numberLights;
+	uniform vec3 u_lightPos[50];
+	uniform vec3 u_lightColor[50];
+	uniform float u_brightness[50];
+	uniform float u_cutoffRadius[50];
+	uniform float u_halfRadius[50];
+	uniform float u_coneCosineHalfConeAngle[50];
+	uniform float u_coneFalloff[50];
+	uniform vec2 u_coneDirection[50];
+
+	void main(void) {
+		vec4 texColor = texture2D(u_texture, v_texCoords);
+
+		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
+		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+		vec3 diffuse = vec3(0, 0, 0);
+		for (int i = 0; i < u_numberLights; i++) {
+			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
+			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
+				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+				float dx_1 = 0.5 / intercept;
+				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+				float offset = 0.5 + intercept * dx_2;
+
+				float lightDist = length(lightVec);
+				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+				float falloffSelect = step(intercept, lightDist);
+				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+				float spotLight = u_brightness[i] * falloffTerm;
+
+				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
+				float halfConeRange = coneRange * u_coneFalloff[i];
+				float conePos = 1.0 - coneValue;
+				float coneFalloff = 1.0;
+				if (conePos > halfConeRange) {
+					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
+				}
+
+				diffuse += ltdiffuse * coneFalloff;
+				;
+			}
+		}
+		vec3 finalLight = (diffuse);
+		vec4 finalCol = texColor * v_color;
+		finalCol.rgb = finalCol.rgb * u_ambientColor;
+		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
+	})";
 
 // 	const char *fshader = R"(#version 110
-// 	varying vec2 v_texCoords;
-// 	varying vec4 v_color;
-
-// 	uniform sampler2D u_texture;
-
-// 	uniform vec2 u_contentSize;
-// 	uniform vec3 u_ambientColor;
-// 	uniform vec2 u_spritePosInSheet;
-// 	uniform vec2 u_spriteSizeRelToSheet;
-// 	uniform vec2 u_spriteOffset;
-
-// 	uniform int u_numberLights;
-// 	uniform vec3 u_lightPos[50];
-// 	uniform vec3 u_lightColor[50];
-// 	uniform float u_brightness[50];
-// 	uniform float u_cutoffRadius[50];
-// 	uniform float u_halfRadius[50];
-// 	uniform float u_coneCosineHalfConeAngle[50];
-// 	uniform float u_coneFalloff[50];
-// 	uniform vec2 u_coneDirection[50];
-
-// 	void main(void) {
-// 		vec4 texColor = texture2D(u_texture, v_texCoords);
-
-// 		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
-// 		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
-// 		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
-
-// 		vec3 diffuse = vec3(0, 0, 0);
-// 		for (int i = 0; i < u_numberLights; i++) {
-// 			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
-// 			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
-// 			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
-// 				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
-// 				float dx_1 = 0.5 / intercept;
-// 				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
-// 				float offset = 0.5 + intercept * dx_2;
-
-// 				float lightDist = length(lightVec);
-// 				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
-// 				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
-// 				float falloffSelect = step(intercept, lightDist);
-// 				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
-// 				float spotLight = u_brightness[i] * falloffTerm;
-
-// 				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
-
-// 				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
-// 				float halfConeRange = coneRange * u_coneFalloff[i];
-// 				float conePos = 1.0 - coneValue;
-// 				float coneFalloff = 1.0;
-// 				if (conePos > halfConeRange) {
-// 					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
-// 				}
-
-// 				diffuse += ltdiffuse * coneFalloff;
-// 				;
-// 			}
-// 		}
-// 		vec3 finalLight = (diffuse);
-// 		vec4 finalCol = texColor * v_color;
-// 		finalCol.rgb = finalCol.rgb * u_ambientColor;
-// 		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
-// 	})";
-// 	init(vshader, fshader);
-
-// 	GL_CALL(_contentSizeLoc = glGetUniformLocation(program, "u_contentSize"));
-// 	GL_CALL(_spriteOffsetLoc = glGetUniformLocation(program, "u_spriteOffset"));
-// 	GL_CALL(_spritePosInSheetLoc = glGetUniformLocation(program, "u_spritePosInSheet"));
-// 	GL_CALL(_spriteSizeRelToSheetLoc = glGetUniformLocation(program, "u_spriteSizeRelToSheet"));
-// 	GL_CALL(_numberLightsLoc = glGetUniformLocation(program, "u_numberLights"));
-// 	GL_CALL(_ambientColorLoc = glGetUniformLocation(program, "u_ambientColor"));
-// }
-
-// Lighting::~Lighting() {}
-
-// void Lighting::setSpriteSheetFrame(const SpriteSheetFrame& frame, const Texture& texture) {
-// 	_contentSize = frame.sourceSize;
-// 	//_spriteOffset = {-frame.frame.width() / 2.f, -frame.frame.height() / 2.f};
-// 	_spriteOffset = {0, (float)frame.frame.height()};
-// 	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
-// 	_spriteSizeRelToSheet = {(float)(frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height};
-// }
-
-// void Lighting::applyUniforms() {
-// 	GL_CALL(glUniform1i(_numberLightsLoc, 0));
-// 	float ambientColor[] = {1,1,1};
-// 	GL_CALL(glUniform3fv(_ambientColorLoc, 1, ambientColor));
-// 	GL_CALL(glUniform2fv(_contentSizeLoc, 1, _contentSize.getData()));
-// 	GL_CALL(glUniform2fv(_spriteOffsetLoc, 1, _spriteOffset.getData()));
-// 	GL_CALL(glUniform2fv(_spritePosInSheetLoc, 1, _spritePosInSheet.getData()));
-// 	GL_CALL(glUniform2fv(_spriteSizeRelToSheetLoc, 1, _spriteSizeRelToSheet.getData()));
-// }
+// #ifdef GL_ES
+// precision highp float;
+// #endif
+
+// varying vec2 v_texCoords;
+// varying vec4 v_color;
+
+// uniform sampler2D u_texture;
+
+// uniform vec2  u_contentSize;
+// uniform vec3  u_ambientColor;
+// uniform vec2 u_spritePosInSheet;
+// uniform vec2 u_spriteSizeRelToSheet;
+// uniform vec2 u_spriteOffset;
+
+// uniform int  u_numberLights;
+// uniform vec3  u_lightPos[50];
+// uniform vec3  u_lightColor[50];
+// uniform float u_brightness[50];
+// uniform float u_cutoffRadius[50];
+// uniform float u_halfRadius[50];
+// uniform float u_coneCosineHalfConeAngle[50];
+// uniform float u_coneFalloff[50];
+// uniform vec2  u_coneDirection[50];
+
+// void main(void)
+// {
+//     vec4 texColor = texture2D(u_texture, v_texCoords);
+
+//     vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+//     vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset; // [0..origSize]
+//     vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+//     vec3 diffuse = vec3(0,0,0);
+//     for ( int i = 0; i < u_numberLights; i ++)
+//     {
+//         vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+//         float coneValue = dot( normalize(-lightVec), u_coneDirection[i] );
+//         if ( coneValue >= u_coneCosineHalfConeAngle[i] )
+//         {
+//             float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+//             float dx_1 = 0.5 / intercept;
+//             float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+//             float offset = 0.5 + intercept * dx_2;
+
+//             float lightDist = length(lightVec);
+//             float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+//             float falloffTermFar  = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+//             float falloffSelect = step(intercept, lightDist);
+//             float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+//             float spotLight = u_brightness[i] * falloffTerm;
+
+//             vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+//             float coneRange = 1.0-u_coneCosineHalfConeAngle[i];
+//             float halfConeRange = coneRange * u_coneFalloff[i];
+//             float conePos   = 1.0-coneValue;
+//             float coneFalloff = 1.0;
+//             if ( conePos > halfConeRange )
+//             {
+//                 coneFalloff = 1.0-((conePos-halfConeRange)/(coneRange-halfConeRange));
+//             }
+
+//             diffuse += ltdiffuse*coneFalloff;;
+//         }
+//     }
+//     vec4 finalCol = texColor * v_color;
+//     vec3 finalLight = (diffuse+u_ambientColor);
+//     finalLight = min( finalLight, vec3(1,1,1) );
+//     gl_FragColor = vec4(finalCol.rgb*finalLight, finalCol.a);
+// })";
+
+	init("lighting", vshader, fshader);
+}
+
+Lighting::~Lighting() {}
+
+void Lighting::setSpriteOffset(const Math::Vector2d &offset) {
+	_spriteOffset = offset;
+}
+
+void Lighting::setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture) {
+	_contentSize = frame.sourceSize;
+	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
+	_spriteSizeRelToSheet = {(float)(frame.frame.width()) / texture.width, (float)(frame.frame.height()) / texture.height};
+}
+
+void Lighting::update(const Lights &lights) {
+	_ambientLight = lights._ambientLight;
+	u_numberLights = 0;
+	for (int i = 0; i < MIN(MAX_LIGHTS, lights._numLights); ++i) {
+		const Light &light = lights._lights[i];
+		if (!light.on)
+			continue;
+		const float direction = light.coneDirection - 90.f;
+		u_lightPos[u_numberLights * 3 + 0] = light.pos.getX();
+		u_lightPos[u_numberLights * 3 + 1] = light.pos.getY();
+		u_lightPos[u_numberLights * 3 + 2] = 1.f;
+		u_coneDirection[u_numberLights * 2 + 0] = cos(Common::deg2rad(direction));
+		u_coneDirection[u_numberLights * 2 + 1] = sin(Common::deg2rad(direction));
+		u_coneCosineHalfConeAngle[u_numberLights] = cos(Common::deg2rad(light.coneAngle / 2.f));
+		u_coneFalloff[u_numberLights] = light.coneFalloff;
+		u_lightColor[u_numberLights * 3 + 0] = light.color.rgba.r;
+		u_lightColor[u_numberLights * 3 + 1] = light.color.rgba.g;
+		u_lightColor[u_numberLights * 3 + 2] = light.color.rgba.b;
+		u_brightness[u_numberLights] = light.brightness;
+		u_cutoffRadius[u_numberLights] = MAX(1.0f, light.cutOffRadius);
+		u_halfRadius[u_numberLights] = MAX(0.01f, MIN(0.99f, light.halfRadius));
+		u_numberLights++;
+	}
+}
+
+void Lighting::applyUniforms() {
+	setUniform3("u_ambientColor", _ambientLight);
+	setUniform("u_numberLights", u_numberLights);
+
+	if (u_numberLights > 0) {
+		setUniform3("u_lightPos", u_lightPos, MAX_LIGHTS);
+		setUniform2("u_coneDirection", u_coneDirection, MAX_LIGHTS);
+		setUniform("u_coneCosineHalfConeAngle", u_coneCosineHalfConeAngle, MAX_LIGHTS);
+		setUniform("u_coneFalloff", u_coneFalloff, MAX_LIGHTS);
+		setUniform3("u_lightColor", u_lightColor, MAX_LIGHTS);
+		setUniform("u_brightness", u_brightness, MAX_LIGHTS);
+		setUniform("u_cutoffRadius", u_cutoffRadius, MAX_LIGHTS);
+		setUniform("u_halfRadius", u_halfRadius, MAX_LIGHTS);
+	}
+
+	setUniform("u_contentSize", _contentSize);
+	setUniform("u_spriteOffset", _spriteOffset);
+	setUniform("u_spritePosInSheet", _spritePosInSheet);
+	setUniform("u_spriteSizeRelToSheet", _spriteSizeRelToSheet);
+}
 
 } // namespace Twp
diff --git a/engines/twp/lighting.h b/engines/twp/lighting.h
index 895a194a49e..ae862d7fda4 100644
--- a/engines/twp/lighting.h
+++ b/engines/twp/lighting.h
@@ -25,31 +25,42 @@
 #include "twp/gfx.h"
 #include "twp/spritesheet.h"
 
+#define MAX_LIGHTS 50
+
 namespace Twp {
 
-// class Lighting {
-// public:
-// 	Lighting();
-// 	virtual ~Lighting();
-
-// 	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture);
-
-// private:
-// 	virtual void applyUniforms() final;
-
-// private:
-// 	int32 _contentSizeLoc;
-// 	int32 _spriteOffsetLoc;
-// 	int32 _spritePosInSheetLoc;
-// 	int32 _spriteSizeRelToSheetLoc;
-// 	int32 _numberLightsLoc;
-// 	int32 _ambientColorLoc;
-
-// 	Math::Vector2d _contentSize;
-// 	Math::Vector2d _spriteOffset;
-// 	Math::Vector2d _spritePosInSheet;
-// 	Math::Vector2d _spriteSizeRelToSheet;
-// };
+struct Lights;
+
+class Lighting : public Shader {
+public:
+	Lighting();
+	virtual ~Lighting();
+
+	void setSpriteOffset(const Math::Vector2d& offset);
+	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &getNumTextures);
+
+	void update(const Lights& lights);
+
+private:
+	virtual void applyUniforms() final;
+
+public:
+	Math::Vector2d _contentSize;
+	Math::Vector2d _spriteOffset;
+	Math::Vector2d _spritePosInSheet;
+	Math::Vector2d _spriteSizeRelToSheet;
+
+	Color _ambientLight; // Ambient light color
+	int u_numberLights = 0;
+	float u_lightPos[3 * MAX_LIGHTS];
+	float u_coneDirection[2 * MAX_LIGHTS];
+	float u_coneCosineHalfConeAngle[MAX_LIGHTS];
+	float u_coneFalloff[MAX_LIGHTS];
+	float u_lightColor[3 * MAX_LIGHTS];
+	float u_brightness[MAX_LIGHTS];
+	float u_cutoffRadius[MAX_LIGHTS];
+	float u_halfRadius[MAX_LIGHTS];
+};
 
 } // namespace Twp
 
diff --git a/engines/twp/room.h b/engines/twp/room.h
index f99710a83f6..b6313479290 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -77,18 +77,18 @@ struct Scaling {
 struct Light {
 	Color color;
 	Math::Vector2d pos;
-	float brightness;    // light brightness 1.0f...100.f
-	float coneDirection; // cone direction 0...360.f
-	float coneAngle;     // cone angle 0...360.f
-	float coneFalloff;   // cone falloff 0.f...1.0f
-	float cutOffRadius;  // cutoff radius
-	float halfRadius;    // cone half radius 0.0f...1.0f
-	bool on;
-	int id;
+	float brightness = 0.f;    // light brightness 1.0f...100.f
+	float coneDirection = 0.f; // cone direction 0...360.f
+	float coneAngle = 0.f;     // cone angle 0...360.f
+	float coneFalloff = 0.f;   // cone falloff 0.f...1.0f
+	float cutOffRadius = 0.f;  // cutoff radius
+	float halfRadius = 0.f;    // cone half radius 0.0f...1.0f
+	bool on = false;
+	int id = 0;
 };
 
 struct Lights {
-	int _numLights; // Number of lights
+	int _numLights = 0; // Number of lights
 	Light _lights[50];
 	Color _ambientLight; // Ambient light color
 };
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 97c6bd81ef6..0e38ccf53b1 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -133,42 +133,93 @@ static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
 }
 
 static SQInteger lightBrightness(HSQUIRRELVM v) {
-	warning("TODO: lightBrightness not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float brightness;
+		if (SQ_FAILED(sqget(v, 3, brightness)))
+			return sq_throwerror(v, "failed to get brightness");
+		light->brightness = brightness;
+	}
 	return 0;
 }
 
 static SQInteger lightConeDirection(HSQUIRRELVM v) {
-	warning("TODO: lightConeDirection not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float direction;
+		if (SQ_FAILED(sqget(v, 3, direction)))
+			return sq_throwerror(v, "failed to get direction");
+		light->coneDirection = direction;
+	}
 	return 0;
 }
 
 static SQInteger lightConeAngle(HSQUIRRELVM v) {
-	warning("TODO: lightConeAngle not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float angle;
+		if (SQ_FAILED(sqget(v, 3, angle)))
+			return sq_throwerror(v, "failed to get angle");
+		light->coneAngle = angle;
+	}
 	return 0;
 }
 
 static SQInteger lightConeFalloff(HSQUIRRELVM v) {
-	warning("TODO: lightConeFalloff not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float falloff;
+		if (SQ_FAILED(sqget(v, 3, falloff)))
+			return sq_throwerror(v, "failed to get falloff");
+		light->coneFalloff = falloff;
+	}
 	return 0;
 }
 
 static SQInteger lightCutOffRadius(HSQUIRRELVM v) {
-	warning("TODO: lightCutOffRadius not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float cutOffRadius;
+		if (SQ_FAILED(sqget(v, 3, cutOffRadius)))
+			return sq_throwerror(v, "failed to get cutOffRadius");
+		light->cutOffRadius = cutOffRadius;
+	}
 	return 0;
 }
 
 static SQInteger lightHalfRadius(HSQUIRRELVM v) {
-	warning("TODO: lightHalfRadius not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		float halfRadius;
+		if (SQ_FAILED(sqget(v, 3, halfRadius)))
+			return sq_throwerror(v, "failed to get halfRadius");
+		light->halfRadius = halfRadius;
+	}
 	return 0;
 }
 
 static SQInteger lightTurnOn(HSQUIRRELVM v) {
-	warning("TODO: lightTurnOn not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		bool on;
+		if (SQ_FAILED(sqget(v, 3, on)))
+			return sq_throwerror(v, "failed to get on");
+
+		light->on = on;
+	}
 	return 0;
 }
 
 static SQInteger lightZRange(HSQUIRRELVM v) {
-	warning("TODO: lightZRange not implemented");
+	Light *light = sqlight(v, 2);
+	if (light) {
+		int nearY, farY;
+		if (SQ_FAILED(sqget(v, 3, nearY)))
+			return sq_throwerror(v, "failed to get nearY");
+		if (SQ_FAILED(sqget(v, 4, farY)))
+			return sq_throwerror(v, "failed to get farY");
+		warning("lightZRange not implemented");
+	}
 	return 0;
 }
 
@@ -459,6 +510,14 @@ static SQInteger roomSize(HSQUIRRELVM v) {
 	return 1;
 }
 
+static SQInteger setAmbientLight(HSQUIRRELVM v) {
+	int c = 0;
+	if (SQ_FAILED(sqget(v, 2, c)))
+		return sq_throwerror(v, "failed to get color");
+	g_engine->_room->_lights._ambientLight = Color::rgb(c);
+	return 0;
+}
+
 // Sets walkbox to be hidden (YES) or not (NO).
 // If the walkbox is hidden, the actors cannot walk to any point within that area anymore, nor to any walkbox that's connected to it on the other side from the actor.
 // Often used on small walkboxes below a gate or door to keep the actor from crossing that boundary if the gate/door is closed.
@@ -499,6 +558,7 @@ void sqgame_register_roomlib(HSQUIRRELVM v) {
 	regFunc(v, roomRotateTo, "roomRotateTo");
 	regFunc(v, roomSize, "roomSize");
 	regFunc(v, roomOverlayColor, "roomOverlayColor");
+	regFunc(v, setAmbientLight, _SC("setAmbientLight"));
 	regFunc(v, walkboxHidden, "walkboxHidden");
 }
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 130e5a675c1..4c5ab43d800 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -28,6 +28,7 @@
 #include "twp/gfx.h"
 #include "twp/object.h"
 #include "twp/util.h"
+#include "twp/lighting.h"
 
 namespace Twp {
 
@@ -179,18 +180,22 @@ static int cmpNodes(const Node *x, const Node *y) {
 	return y->getZSort() < x->getZSort();
 }
 
+void Node::onDrawChildren(Math::Matrix4 trsf) {
+	Common::Array<Node *> children(_children);
+	Common::sort(children.begin(), children.end(), cmpNodes);
+	for (size_t i = 0; i < children.size(); i++) {
+		Node *child = children[i];
+		child->draw(trsf);
+	}
+}
+
 void Node::draw(Math::Matrix4 parent) {
 	if (_visible) {
 		Math::Matrix4 trsf = getTrsf(parent);
 		Math::Matrix4 myTrsf(trsf);
 		myTrsf.translate(Math::Vector3d(-_anchor.getX(), _anchor.getY(), 0.f));
-		Common::Array<Node *> children(_children);
-		Common::sort(children.begin(), children.end(), cmpNodes);
 		drawCore(myTrsf);
-		for (size_t i = 0; i < children.size(); i++) {
-			Node *child = children[i];
-			child->draw(trsf);
-		}
+		onDrawChildren(trsf);
 	}
 }
 
@@ -239,20 +244,37 @@ Math::Matrix4 ParallaxNode::getTrsf(Math::Matrix4 parentTrsf) {
 	return trsf;
 }
 
+void ParallaxNode::onDrawChildren(Math::Matrix4 trsf) {
+	Node::onDrawChildren(trsf);
+}
+
 void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	Gfx &gfx = g_engine->getGfx();
 	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+
+	// enable debug lighting
+	if (_zOrder == 0) {
+		g_engine->getGfx().use(g_engine->_lighting);
+	} else {
+		g_engine->getGfx().use(nullptr);
+	}
+
 	Math::Matrix4 t = trsf;
 	float x = 0.f;
 	for (size_t i = 0; i < _frames.size(); i++) {
 		const SpriteSheetFrame &frame = sheet->frameTable[_frames[i]];
+		g_engine->_lighting->setSpriteOffset({0.f, static_cast<float>(-frame.frame.height())});
+		g_engine->_lighting->setSpriteSheetFrame(frame, *texture);
+
 		Math::Matrix4 myTrsf = t;
 		myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
 		gfx.drawSprite(frame.frame, *texture, getColor(), myTrsf);
 		t = trsf;
 		x += frame.frame.width();
 	}
+
+	g_engine->getGfx().use(nullptr);
 }
 
 Anim::Anim(Object *obj)
@@ -360,7 +382,16 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			float y = 0.5f * (sf.sourceSize.getY()) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
 			Math::Vector3d pos(int(-x), int(y), 0.f);
 			trsf.translate(pos);
+			if (_obj->_lit) {
+				g_engine->getGfx().use(g_engine->_lighting);
+				Math::Vector2d p = getAbsPos();
+				g_engine->_lighting->setSpriteOffset({(flipX ? 0.f : -sf.frame.width()) + p.getX(), -sf.frame.height() - p.getY()});
+				g_engine->_lighting->setSpriteSheetFrame(sf, *texture);
+			} else {
+				g_engine->getGfx().use(nullptr);
+			}
 			g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
+			g_engine->getGfx().use(nullptr);
 		}
 	}
 }
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 744990351df..4b4d05d2c2a 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -111,6 +111,7 @@ public:
 	void draw(Math::Matrix4 parent = Math::Matrix4());
 
 protected:
+	virtual void onDrawChildren(Math::Matrix4 trsf);
 	virtual void onColorUpdated(Color c) {}
 	virtual void drawCore(Math::Matrix4 trsf) {}
 
@@ -143,6 +144,7 @@ public:
 	Math::Matrix4 getTrsf(Math::Matrix4 parentTrsf) override final;
 
 protected:
+	void onDrawChildren(Math::Matrix4 trsf) override;
 	void drawCore(Math::Matrix4 trsf) override final;
 
 private:
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 2a3a3ae382b..f5225372e40 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "twp/lighting.h"
 #include "twp/squtil.h"
 #include "twp/room.h"
 #include "twp/object.h"
@@ -421,6 +422,26 @@ ThreadBase *sqthread(int id) {
 	return nullptr;
 }
 
+Light *sqlight(int id) {
+	if(!g_engine->_room)
+		return nullptr;
+
+	for (size_t i = 0; i < MAX_LIGHTS; i++) {
+		Light *light = &g_engine->_room->_lights._lights[i];
+		if (light->id == id) {
+			return light;
+		}
+	}
+	return nullptr;
+}
+
+Light *sqlight(HSQUIRRELVM v, int i) {
+	int id;
+	if (SQ_SUCCEEDED(sqget(v, i, id)))
+		return sqlight(id);
+	return nullptr;
+}
+
 struct GetThread {
 	GetThread(HSQUIRRELVM v) : _v(v) {}
 	bool operator()(ThreadBase *t) {
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 930cab3aeee..8d565e0cf22 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -172,6 +172,8 @@ SoundDefinition *sqsounddef(int id);
 ThreadBase *sqthread(HSQUIRRELVM v);
 ThreadBase *sqthread(HSQUIRRELVM v, int id);
 ThreadBase *sqthread(int id);
+Light *sqlight(int id);
+Light *sqlight(HSQUIRRELVM v, int i);
 
 template<typename... T>
 void sqcall(HSQUIRRELVM v, HSQOBJECT o, const char *name, T... args) {
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 8c8fa27f15f..b21c7d450fe 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -733,11 +733,6 @@ static SQInteger removeCallback(HSQUIRRELVM v) {
 	return 0;
 }
 
-static SQInteger setAmbientLight(HSQUIRRELVM v) {
-	warning("TODO: setAmbientLight: not implemented");
-	return 0;
-}
-
 static SQInteger startthread(HSQUIRRELVM v) {
 	return _startthread(v, false);
 }
@@ -847,7 +842,6 @@ void sqgame_register_syslib(HSQUIRRELVM v) {
 	regFunc(v, microTime, _SC("microTime"));
 	regFunc(v, moveCursorTo, _SC("moveCursorTo"));
 	regFunc(v, removeCallback, _SC("removeCallback"));
-	regFunc(v, setAmbientLight, _SC("setAmbientLight"));
 	regFunc(v, startglobalthread, _SC("startglobalthread"));
 	regFunc(v, startthread, _SC("startthread"));
 	regFunc(v, stopthread, _SC("stopthread"));
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0f226b03578..81a9d3142b4 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -70,6 +70,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_screenScene.setName("Screen");
 	_scene.addChild(&_walkboxNode);
 	_screenScene.addChild(&_pathNode);
+	_screenScene.addChild(&_lightingNode);
 	_screenScene.addChild(&_hotspotMarker);
 	_screenScene.addChild(&_inputState);
 	_screenScene.addChild(&_sentence);
@@ -605,6 +606,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	_gfx.setRenderTarget(&renderTexture2);
 	if (_room) {
 		setShaderEffect(_room->_effect);
+		_lighting->update(_room->_lights);
 	}
 	_shaderParams.randomValue[0] = g_engine->getRandom();
 	_shaderParams.timeLapse = fmodf(_time, 1000.f);
@@ -714,7 +716,7 @@ Common::Error TwpEngine::run() {
 	_sepiaShader.init("sepia", vsrc, sepiaShader);
 	_fadeShader.reset(new FadeShader());
 
-	// _lighting = new Lighting();
+	_lighting = new Lighting();
 
 	_pack.init();
 
@@ -861,6 +863,11 @@ Common::Error TwpEngine::run() {
 						_pathNode.setMode(mode);
 					}
 					break;
+				case Common::KEYCODE_l:
+					if (control) {
+						_lightingNode.setVisible(!_lightingNode.isVisible());
+					}
+					break;
 				default:
 					break;
 				}
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 02f0fe698bb..88682b9825c 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -231,6 +231,7 @@ private:
 	SentenceNode _sentence;
 	WalkboxNode _walkboxNode;
 	PathNode _pathNode;
+	LightingNode _lightingNode;
 	Shader _bwShader;
 	Shader _ghostShader;
 	Shader _sepiaShader;
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 62c277f0d9d..9d34a9b6f77 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -21,6 +21,7 @@
 
 #include "twp/twp.h"
 #include "twp/walkboxnode.h"
+#include "twp/lighting.h"
 
 namespace Twp {
 
@@ -47,7 +48,7 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 				const Walkbox &wb = walkboxes[i];
 				const Color color = wb.isVisible() ? green : red;
 				Common::Array<Vertex> vertices;
-				const Common::Array<Vector2i>& points = wb.getPoints();
+				const Common::Array<Vector2i> &points = wb.getPoints();
 				for (uint j = 0; j < points.size(); j++) {
 					Vector2i pInt = points[j];
 					Math::Vector2d p = (Math::Vector2d)pInt;
@@ -69,7 +70,7 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 				const Walkbox &wb = walkboxes[i];
 				const Color color = i == 0 ? green : red;
 				Common::Array<Vertex> vertices;
-				const Common::Array<Vector2i>& points = wb.getPoints();
+				const Common::Array<Vector2i> &points = wb.getPoints();
 				for (uint j = 0; j < points.size(); j++) {
 					Vector2i pInt = points[j];
 					Math::Vector2d p = (Math::Vector2d)pInt;
@@ -138,7 +139,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	}
 
 	// draw graph nodes
-	const Twp::Graph& graph = g_engine->_room->_pathFinder.getGraph();
+	const Twp::Graph &graph = g_engine->_room->_pathFinder.getGraph();
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All))) {
 		for (uint i = 0; i < graph._concaveVertices.size(); i++) {
 			const Math::Vector2d p = g_engine->roomToScreen((Math::Vector2d)graph._concaveVertices[i]) - Math::Vector2d(2.f, 2.f);
@@ -195,7 +196,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 
 		// draw a green square if inside walkbox, red if not
 		Common::Array<Walkbox> walkboxes = g_engine->_room ? g_engine->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
-		if(walkboxes.empty())
+		if (walkboxes.empty())
 			return;
 
 		const bool inside = (walkboxes.size() > 0) && walkboxes[0].contains((Vector2i)roomPos);
@@ -207,9 +208,28 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		// draw a blue square on the closest point
 		pos = g_engine->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge((Vector2i)roomPos));
 		t = Math::Matrix4();
-		t.translate(Math::Vector3d(pos.getX()-2.f, pos.getY()-2.f, 0.f));
+		t.translate(Math::Vector3d(pos.getX() - 2.f, pos.getY() - 2.f, 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), blue, t);
 	}
 }
 
+LightingNode::LightingNode() : Node("Lighting") {
+	setVisible(false);
+}
+
+void LightingNode::drawCore(Math::Matrix4 trsf) {
+	if (!g_engine->_room)
+		return;
+
+	const float size = 6.0f;
+	for (int i = 0; i < MAX_LIGHTS; i++) {
+		float *pos = &g_engine->_lighting->u_lightPos[i * 3];
+		float *color = &g_engine->_lighting->u_lightColor[i * 3];
+		Math::Vector2d p = g_engine->roomToScreen(Math::Vector2d(pos[0], pos[1]));
+		Math::Matrix4 t;
+		t.translate(Math::Vector3d(p.getX() - size / 2.f, p.getY() - size / 2.f, 0.f));
+		g_engine->getGfx().drawQuad(Math::Vector2d(size, size), Color(color[0], color[1], color[2]), t);
+	}
+}
+
 } // namespace Twp
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index a022386f003..cb84075db7d 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -67,6 +67,14 @@ private:
 	PathMode _mode = PathMode::None;
 };
 
+class LightingNode : public Node {
+public:
+	LightingNode();
+
+private:
+	virtual void drawCore(Math::Matrix4 trsf) override;
+};
+
 } // namespace Twp
 
 #endif


Commit: f2521739c1809f384dc267c92f1328f8945aec96
    https://github.com/scummvm/scummvm/commit/f2521739c1809f384dc267c92f1328f8945aec96
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix lighting shader \o/

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/lighting.cpp
    engines/twp/lighting.h
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 84b0d6f68b6..d12264a4eec 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -3,6 +3,7 @@
 #include "twp/twp.h"
 #include "twp/thread.h"
 #include "twp/dialog.h"
+#include "twp/lighting.h"
 
 namespace Twp {
 
@@ -128,7 +129,7 @@ static void drawStack() {
 		return;
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
-	ImGui::Begin("Stack");
+	ImGui::Begin("Stack", &state._showStack);
 	ImGui::BeginChild("ScrollingRegion");
 	SQInteger size = sq_gettop(g_engine->getVm());
 	ImGui::Text("size: %lld", size);
@@ -322,6 +323,7 @@ static void drawGeneral() {
 			Color overlay = room->_overlayNode.getOverlayColor();
 			if (ImGui::ColorEdit4("Overlay", overlay.v))
 				room->_overlayNode.setOverlayColor(overlay);
+			ImGui::Checkbox("Debug Lights", &g_engine->_lighting->_debug);
 			ImGui::ColorEdit4("Ambient Light", room->_lights._ambientLight.v);
 			for (int i = 0; i < room->_lights._numLights; ++i) {
 				Common::String ss = Common::String::format("Light %d", i + 1);
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 091e519fd0b..496bbfb8c4a 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -1,164 +1,162 @@
 #include "twp/lighting.h"
 #include "twp/room.h"
+#include "twp/twp.h"
 #include "graphics/opengl/debug.h"
 #include "graphics/opengl/system_headers.h"
 #include "common/math.h"
 
 namespace Twp {
 
-Lighting::Lighting() {
-	const char *vshader = R"(#version 110
-	uniform mat4 u_transform;
-	attribute vec2 a_position;
-	attribute vec4 a_color;
-	attribute vec2 a_texCoords;
-	varying vec4 v_color;
-	varying vec2 v_texCoords;
-	void main() {
-		gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
-		v_color = a_color;
-		v_texCoords = a_texCoords;
-	})";
-
-	const char *fshader = R"(#version 110
-	varying vec2 v_texCoords;
-	varying vec4 v_color;
-
-	uniform sampler2D u_texture;
-
-	uniform vec2 u_contentSize;
-	uniform vec3 u_ambientColor;
-	uniform vec2 u_spritePosInSheet;
-	uniform vec2 u_spriteSizeRelToSheet;
-	uniform vec2 u_spriteOffset;
-
-	uniform int u_numberLights;
-	uniform vec3 u_lightPos[50];
-	uniform vec3 u_lightColor[50];
-	uniform float u_brightness[50];
-	uniform float u_cutoffRadius[50];
-	uniform float u_halfRadius[50];
-	uniform float u_coneCosineHalfConeAngle[50];
-	uniform float u_coneFalloff[50];
-	uniform vec2 u_coneDirection[50];
-
-	void main(void) {
-		vec4 texColor = texture2D(u_texture, v_texCoords);
-
-		vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
-		vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
-		vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
-
-		vec3 diffuse = vec3(0, 0, 0);
-		for (int i = 0; i < u_numberLights; i++) {
-			vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
-			float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
-			if (coneValue >= u_coneCosineHalfConeAngle[i]) {
-				float intercept = u_cutoffRadius[i] * u_halfRadius[i];
-				float dx_1 = 0.5 / intercept;
-				float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
-				float offset = 0.5 + intercept * dx_2;
-
-				float lightDist = length(lightVec);
-				float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
-				float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
-				float falloffSelect = step(intercept, lightDist);
-				float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
-				float spotLight = u_brightness[i] * falloffTerm;
-
-				vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
-
-				float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
-				float halfConeRange = coneRange * u_coneFalloff[i];
-				float conePos = 1.0 - coneValue;
-				float coneFalloff = 1.0;
-				if (conePos > halfConeRange) {
-					coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
-				}
-
-				diffuse += ltdiffuse * coneFalloff;
-				;
+static const char *fshader = R"(#version 110
+#ifdef GL_ES
+	precision highp float;
+#endif
+
+varying vec2 v_texCoords;
+varying vec4 v_color;
+
+uniform sampler2D u_texture;
+
+uniform vec2 u_contentSize;
+uniform vec3 u_ambientColor;
+uniform vec2 u_spritePosInSheet;
+uniform vec2 u_spriteSizeRelToSheet;
+uniform vec2 u_spriteOffset;
+
+uniform int u_numberLights;
+uniform vec3 u_lightPos[50];
+uniform vec3 u_lightColor[50];
+uniform float u_brightness[50];
+uniform float u_cutoffRadius[50];
+uniform float u_halfRadius[50];
+uniform float u_coneCosineHalfConeAngle[50];
+uniform float u_coneFalloff[50];
+uniform vec2 u_coneDirection[50];
+
+void main(void) {
+	vec4 texColor = texture2D(u_texture, v_texCoords);
+
+	vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+	vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
+	vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+	vec3 diffuse = vec3(0, 0, 0);
+	for (int i = 0; i < u_numberLights; i++) {
+		vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+		float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
+		if (coneValue >= u_coneCosineHalfConeAngle[i]) {
+			float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+			float dx_1 = 0.5 / intercept;
+			float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+			float offset = 0.5 + intercept * dx_2;
+
+			float lightDist = length(lightVec);
+			float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+			float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+			float falloffSelect = step(intercept, lightDist);
+			float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+			float spotLight = u_brightness[i] * falloffTerm;
+
+			vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+			float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
+			float halfConeRange = coneRange * u_coneFalloff[i];
+			float conePos = 1.0 - coneValue;
+			float coneFalloff = 1.0;
+			if (conePos > halfConeRange) {
+				coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
 			}
+
+			diffuse += ltdiffuse * coneFalloff;
+			;
 		}
-		vec3 finalLight = (diffuse);
-		vec4 finalCol = texColor * v_color;
-		finalCol.rgb = finalCol.rgb * u_ambientColor;
-		gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
-	})";
-
-// 	const char *fshader = R"(#version 110
-// #ifdef GL_ES
-// precision highp float;
-// #endif
-
-// varying vec2 v_texCoords;
-// varying vec4 v_color;
-
-// uniform sampler2D u_texture;
-
-// uniform vec2  u_contentSize;
-// uniform vec3  u_ambientColor;
-// uniform vec2 u_spritePosInSheet;
-// uniform vec2 u_spriteSizeRelToSheet;
-// uniform vec2 u_spriteOffset;
-
-// uniform int  u_numberLights;
-// uniform vec3  u_lightPos[50];
-// uniform vec3  u_lightColor[50];
-// uniform float u_brightness[50];
-// uniform float u_cutoffRadius[50];
-// uniform float u_halfRadius[50];
-// uniform float u_coneCosineHalfConeAngle[50];
-// uniform float u_coneFalloff[50];
-// uniform vec2  u_coneDirection[50];
-
-// void main(void)
-// {
-//     vec4 texColor = texture2D(u_texture, v_texCoords);
-
-//     vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
-//     vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset; // [0..origSize]
-//     vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
-
-//     vec3 diffuse = vec3(0,0,0);
-//     for ( int i = 0; i < u_numberLights; i ++)
-//     {
-//         vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
-//         float coneValue = dot( normalize(-lightVec), u_coneDirection[i] );
-//         if ( coneValue >= u_coneCosineHalfConeAngle[i] )
-//         {
-//             float intercept = u_cutoffRadius[i] * u_halfRadius[i];
-//             float dx_1 = 0.5 / intercept;
-//             float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
-//             float offset = 0.5 + intercept * dx_2;
-
-//             float lightDist = length(lightVec);
-//             float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
-//             float falloffTermFar  = clamp((offset - lightDist * dx_2), 0.0, 1.0);
-//             float falloffSelect = step(intercept, lightDist);
-//             float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
-//             float spotLight = u_brightness[i] * falloffTerm;
-
-//             vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
-
-//             float coneRange = 1.0-u_coneCosineHalfConeAngle[i];
-//             float halfConeRange = coneRange * u_coneFalloff[i];
-//             float conePos   = 1.0-coneValue;
-//             float coneFalloff = 1.0;
-//             if ( conePos > halfConeRange )
-//             {
-//                 coneFalloff = 1.0-((conePos-halfConeRange)/(coneRange-halfConeRange));
-//             }
-
-//             diffuse += ltdiffuse*coneFalloff;;
-//         }
-//     }
-//     vec4 finalCol = texColor * v_color;
-//     vec3 finalLight = (diffuse+u_ambientColor);
-//     finalLight = min( finalLight, vec3(1,1,1) );
-//     gl_FragColor = vec4(finalCol.rgb*finalLight, finalCol.a);
-// })";
+	}
+	vec4 finalCol = texColor * v_color;
+	vec3 finalLight = (diffuse + u_ambientColor);
+	finalLight = min(finalLight, vec3(1, 1, 1));
+	gl_FragColor = vec4(finalCol.rgb * finalLight, finalCol.a);
+})";
+
+static const char *debug_fshader = R"(#version 110
+varying vec2 v_texCoords;
+varying vec4 v_color;
+
+uniform sampler2D u_texture;
+
+uniform vec2 u_contentSize;
+uniform vec3 u_ambientColor;
+uniform vec2 u_spritePosInSheet;
+uniform vec2 u_spriteSizeRelToSheet;
+uniform vec2 u_spriteOffset;
+
+uniform int u_numberLights;
+uniform vec3 u_lightPos[50];
+uniform vec3 u_lightColor[50];
+uniform float u_brightness[50];
+uniform float u_cutoffRadius[50];
+uniform float u_halfRadius[50];
+uniform float u_coneCosineHalfConeAngle[50];
+uniform float u_coneFalloff[50];
+uniform vec2 u_coneDirection[50];
+
+void main(void) {
+	vec4 texColor = texture2D(u_texture, v_texCoords);
+
+	vec2 spriteTexCoord = (v_texCoords - u_spritePosInSheet) / u_spriteSizeRelToSheet; // [0..1]
+	vec2 pixelPos = spriteTexCoord * u_contentSize + u_spriteOffset;                   // [0..origSize]
+	vec2 curPixelPosInLocalSpace = vec2(pixelPos.x, -pixelPos.y);
+
+	vec3 diffuse = vec3(0, 0, 0);
+	for (int i = 0; i < u_numberLights; i++) {
+		vec2 lightVec = curPixelPosInLocalSpace.xy - u_lightPos[i].xy;
+		float coneValue = dot(normalize(-lightVec), u_coneDirection[i]);
+		if (coneValue >= u_coneCosineHalfConeAngle[i]) {
+			float intercept = u_cutoffRadius[i] * u_halfRadius[i];
+			float dx_1 = 0.5 / intercept;
+			float dx_2 = 0.5 / (u_cutoffRadius[i] - intercept);
+			float offset = 0.5 + intercept * dx_2;
+
+			float lightDist = length(lightVec);
+			float falloffTermNear = clamp((1.0 - lightDist * dx_1), 0.0, 1.0);
+			float falloffTermFar = clamp((offset - lightDist * dx_2), 0.0, 1.0);
+			float falloffSelect = step(intercept, lightDist);
+			float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;
+			float spotLight = u_brightness[i] * falloffTerm;
+
+			vec3 ltdiffuse = vec3(u_brightness[i] * falloffTerm) * u_lightColor[i];
+
+			float coneRange = 1.0 - u_coneCosineHalfConeAngle[i];
+			float halfConeRange = coneRange * u_coneFalloff[i];
+			float conePos = 1.0 - coneValue;
+			float coneFalloff = 1.0;
+			if (conePos > halfConeRange) {
+				coneFalloff = 1.0 - ((conePos - halfConeRange) / (coneRange - halfConeRange));
+			}
 
+			diffuse += ltdiffuse * coneFalloff;
+			;
+		}
+	}
+	vec3 finalLight = (diffuse);
+	vec4 finalCol = texColor * v_color;
+	finalCol.rgb = finalCol.rgb * u_ambientColor;
+	gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
+})";
+
+static const char *vshader = R"(#version 110
+uniform mat4 u_transform;
+attribute vec2 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texCoords;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+void main() {
+	gl_Position = u_transform * vec4(a_position, 0.0, 1.0);
+	v_color = a_color;
+	v_texCoords = a_texCoords;
+})";
+
+Lighting::Lighting() {
 	init("lighting", vshader, fshader);
 }
 
@@ -168,13 +166,18 @@ void Lighting::setSpriteOffset(const Math::Vector2d &offset) {
 	_spriteOffset = offset;
 }
 
-void Lighting::setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture) {
-	_contentSize = frame.sourceSize;
+void Lighting::setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &texture, bool flipX) {
+	_contentSize = flipX ? Math::Vector2d(frame.sourceSize.getX(), frame.sourceSize.getY()) : frame.sourceSize;
 	_spritePosInSheet = {(float)(frame.frame.left) / texture.width, (float)(frame.frame.top) / texture.height};
-	_spriteSizeRelToSheet = {(float)(frame.frame.width()) / texture.width, (float)(frame.frame.height()) / texture.height};
+	_spriteSizeRelToSheet = flipX ? Math::Vector2d((float)(-frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height) : Math::Vector2d((float)(frame.sourceSize.getX()) / texture.width, (float)(frame.sourceSize.getY()) / texture.height);
 }
 
 void Lighting::update(const Lights &lights) {
+	if (_currentDebug != _debug) {
+		init("lighting", vshader, _debug ? debug_fshader : fshader);
+		_currentDebug = _debug;
+		g_engine->_lightingNode.setVisible(_debug);
+	}
 	_ambientLight = lights._ambientLight;
 	u_numberLights = 0;
 	for (int i = 0; i < MIN(MAX_LIGHTS, lights._numLights); ++i) {
diff --git a/engines/twp/lighting.h b/engines/twp/lighting.h
index ae862d7fda4..a7a69e56c2f 100644
--- a/engines/twp/lighting.h
+++ b/engines/twp/lighting.h
@@ -37,7 +37,7 @@ public:
 	virtual ~Lighting();
 
 	void setSpriteOffset(const Math::Vector2d& offset);
-	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &getNumTextures);
+	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &getNumTextures, bool flipX);
 
 	void update(const Lights& lights);
 
@@ -50,6 +50,7 @@ public:
 	Math::Vector2d _spritePosInSheet;
 	Math::Vector2d _spriteSizeRelToSheet;
 
+	bool _debug = false; // if true change the way we draw the lights to debug them easily
 	Color _ambientLight; // Ambient light color
 	int u_numberLights = 0;
 	float u_lightPos[3 * MAX_LIGHTS];
@@ -60,6 +61,9 @@ public:
 	float u_brightness[MAX_LIGHTS];
 	float u_cutoffRadius[MAX_LIGHTS];
 	float u_halfRadius[MAX_LIGHTS];
+
+private:
+	bool _currentDebug = false;
 };
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 4c5ab43d800..c4613a28a4a 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -253,8 +253,8 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 
-	// enable debug lighting
-	if (_zOrder == 0) {
+	// enable debug lighting ?
+	if (_zOrder == 0 && g_engine->_lighting->_debug) {
 		g_engine->getGfx().use(g_engine->_lighting);
 	} else {
 		g_engine->getGfx().use(nullptr);
@@ -265,7 +265,7 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	for (size_t i = 0; i < _frames.size(); i++) {
 		const SpriteSheetFrame &frame = sheet->frameTable[_frames[i]];
 		g_engine->_lighting->setSpriteOffset({0.f, static_cast<float>(-frame.frame.height())});
-		g_engine->_lighting->setSpriteSheetFrame(frame, *texture);
+		g_engine->_lighting->setSpriteSheetFrame(frame, *texture, false);
 
 		Math::Matrix4 myTrsf = t;
 		myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
@@ -375,21 +375,26 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			trsf.translate(pos);
 			g_engine->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
 		} else {
-			SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+			const SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
 			const SpriteSheetFrame &sf = sheet->frameTable[frame];
 			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
-			float x = flipX ? -0.5f * (-1.f + sf.sourceSize.getX()) + sf.frame.width() + sf.spriteSourceSize.left : 0.5f * (sf.sourceSize.getX()) - sf.spriteSourceSize.left;
-			float y = 0.5f * (sf.sourceSize.getY()) - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
-			Math::Vector3d pos(int(-x), int(y), 0.f);
-			trsf.translate(pos);
 			if (_obj->_lit) {
 				g_engine->getGfx().use(g_engine->_lighting);
-				Math::Vector2d p = getAbsPos();
-				g_engine->_lighting->setSpriteOffset({(flipX ? 0.f : -sf.frame.width()) + p.getX(), -sf.frame.height() - p.getY()});
-				g_engine->_lighting->setSpriteSheetFrame(sf, *texture);
+				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
+				const float left = flipX ?
+					(-1.f + sf.sourceSize.getX()) / 2.f - sf.spriteSourceSize.left :
+					sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f;
+				const float top = -sf.sourceSize.getY() / 2.f + sf.spriteSourceSize.top;
+
+				g_engine->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});
+				g_engine->_lighting->setSpriteSheetFrame(sf, *texture, flipX);
 			} else {
 				g_engine->getGfx().use(nullptr);
 			}
+			const float x = flipX ? (1.f - sf.sourceSize.getX()) / 2.f + sf.frame.width() + sf.spriteSourceSize.left : sf.sourceSize.getX() / 2.f - sf.spriteSourceSize.left;
+			const float y = sf.sourceSize.getY() / 2.f - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
+			Math::Vector3d pos(int(-x), int(y), 0.f);
+			trsf.translate(pos);
 			g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
 			g_engine->getGfx().use(nullptr);
 		}
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 81a9d3142b4..f11cc12b707 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -863,11 +863,6 @@ Common::Error TwpEngine::run() {
 						_pathNode.setMode(mode);
 					}
 					break;
-				case Common::KEYCODE_l:
-					if (control) {
-						_lightingNode.setVisible(!_lightingNode.isVisible());
-					}
-					break;
 				default:
 					break;
 				}
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 88682b9825c..f693b62817c 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -223,6 +223,7 @@ public:
 	ShaderParams _shaderParams;
 	HotspotMarkerNode _hotspotMarker;
 	unique_ptr<FadeShader> _fadeShader;
+	LightingNode _lightingNode;
 
 private:
 	Gfx _gfx;
@@ -231,7 +232,6 @@ private:
 	SentenceNode _sentence;
 	WalkboxNode _walkboxNode;
 	PathNode _pathNode;
-	LightingNode _lightingNode;
 	Shader _bwShader;
 	Shader _ghostShader;
 	Shader _sepiaShader;


Commit: 31a29481e539e09ce97aeb0921b42467b490597b
    https://github.com/scummvm/scummvm/commit/31a29481e539e09ce97aeb0921b42467b490597b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix null frame in animation

Changed paths:
  A imgui.ini
    engines/twp/scenegraph.cpp


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index c4613a28a4a..938130d6c52 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -362,6 +362,8 @@ void Anim::update(float elapsed) {
 void Anim::drawCore(Math::Matrix4 trsf) {
 	if (_frameIndex < _frames.size()) {
 		const Common::String &frame = _frames[_frameIndex];
+		if(frame == "null") return;
+
 		bool flipX = _obj->getFacing() == FACE_LEFT;
 		if (_sheet.size() == 0) {
 			_sheet = _obj->_sheet;
diff --git a/imgui.ini b/imgui.ini
new file mode 100644
index 00000000000..f886929d913
--- /dev/null
+++ b/imgui.ini
@@ -0,0 +1,26 @@
+[Window][Debug##Default]
+Pos=60,60
+Size=400,400
+
+[Window][General]
+Pos=79,25
+Size=363,483
+
+[Window][Objects]
+Pos=60,60
+Size=520,600
+
+[Window][Sounds]
+Pos=60,60
+Size=520,600
+
+[Table][0xBFA6B9D2,7]
+RefScale=13
+Column 0  Width=21
+Column 1  Width=42
+Column 2  Width=56
+Column 3  Width=119
+Column 4  Width=35
+Column 5  Width=42
+Column 6  Width=72
+


Commit: e2d956f3984b6515a691953c09b2fa21959f42a1
    https://github.com/scummvm/scummvm/commit/e2d956f3984b6515a691953c09b2fa21959f42a1
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix room leaks

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/camera.cpp
    engines/twp/camera.h
    engines/twp/debugtools.cpp
    engines/twp/genlib.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/shaders.h
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 487eb012c8b..a726344aaf4 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -93,7 +93,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 			actor->_node->setPos(pos);
 			actor->setFacing(getFacing(spot->_useDir, actor->getFacing()));
 		} else {
-			Room *room = sqroom(v, 3);
+			Common::SharedPtr<Room> room = sqroom(v, 3);
 			if (!room)
 				return sq_throwerror(v, "failed to get spot or room");
 			debugC(kDebugActScript, "actorAt %s room '%s'", actor->_key.c_str(), room->_name.c_str());
@@ -121,7 +121,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 		Object *actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		Room *room = sqroom(v, 3);
+		Common::SharedPtr<Room> room = sqroom(v, 3);
 		if (!room)
 			return sq_throwerror(v, "failed to get room");
 		int x, y;
@@ -350,7 +350,7 @@ static SQInteger actorRoom(HSQUIRRELVM v) {
 	Object *actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	Room *room = actor->_room;
+	Common::SharedPtr<Room> room = actor->_room;
 	if (!room) {
 		sq_pushnull(v);
 	} else {
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 737d45d883a..dbb5ce4614f 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -65,7 +65,7 @@ void Camera::panTo(Math::Vector2d target, float time, InterpolationKind interpol
 	_time = time;
 }
 
-void Camera::update(Room *room, Object *follow, float elapsed) {
+void Camera::update(Common::SharedPtr<Room> room, Object *follow, float elapsed) {
 	_room = room;
 	_elapsed += elapsed;
 	bool isMoving = _elapsed < _time;
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
index c60b3f23be5..264ea456a83 100644
--- a/engines/twp/camera.h
+++ b/engines/twp/camera.h
@@ -99,13 +99,13 @@ public:
 	inline void setBounds(const Rectf &bounds) { _bounds = bounds; }
 	inline Rectf getBounds() const { return _bounds; }
 
-	inline void setRoom(Room *room) { _room = room; }
-	inline Room *getRoom(Room *room) const { return _room; }
+	inline void setRoom(Common::SharedPtr<Room> room) { _room = room; }
+	inline Common::SharedPtr<Room> getRoom() const { return _room; }
 
 	inline bool isMoving() const { return _moving; }
 	void panTo(Math::Vector2d target, float time, InterpolationKind interpolation);
 
-	void update(Room *room, Object *follow, float elapsed);
+	void update(Common::SharedPtr<Room> room, Object *follow, float elapsed);
 
 private:
 	void clamp(Math::Vector2d at);
@@ -118,7 +118,7 @@ private:
 	Math::Vector2d _init, _target;
 	float _elapsed = 0.f;
 	float _time = 0.f;
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	Object *_follow = nullptr;
 	EasingFunc_t _function = {&linear};
 };
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index d12264a4eec..4eb7740ba44 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -305,7 +305,7 @@ static void drawGeneral() {
 	}
 
 	// Room
-	Room *room = g_engine->_room;
+	Common::SharedPtr<Room> room = g_engine->_room;
 	if (room) {
 		if (ImGui::CollapsingHeader("Room")) {
 			ImGui::TextColored(gray, "Sheet:");
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 8a65e2e6cd9..002d78680d8 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -156,7 +156,7 @@ static SQInteger cameraFollow(HSQUIRRELVM v) {
 	Object *actor = sqactor(v, 2);
 	g_engine->follow(actor);
 	Math::Vector2d pos = actor->_node->getPos();
-	Room *oldRoom = g_engine->_room;
+	Common::SharedPtr<Room> oldRoom = g_engine->_room;
 	if (actor->_room)
 		g_engine->setRoom(actor->_room);
 	if (oldRoom != actor->_room)
@@ -178,7 +178,7 @@ static SQInteger cameraFollow(HSQUIRRELVM v) {
 //      }
 // }
 static SQInteger cameraInRoom(HSQUIRRELVM v) {
-	Room *room = sqroom(v, 2);
+	Common::SharedPtr<Room> room = sqroom(v, 2);
 	if (room) {
 		g_engine->setRoom(room);
 	} else {
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 3cfa520b193..c83f0938140 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -89,7 +89,7 @@ void RotateTo::update(float elapsed) {
 
 RoomRotateTo::~RoomRotateTo() {}
 
-RoomRotateTo::RoomRotateTo(Room *room, float to)
+RoomRotateTo::RoomRotateTo(Common::SharedPtr<Room> room, float to)
 	: _room(room),
 	  _tween(room->_rotation, to, 0.200f, intToInterpolationMethod(0)) {
 }
@@ -129,7 +129,7 @@ void Shake::update(float elapsed) {
 	_node->setShakeOffset(Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime)));
 }
 
-OverlayTo::OverlayTo(float duration, Room *room, Color to)
+OverlayTo::OverlayTo(float duration, Common::SharedPtr<Room> room, Color to)
 	: _room(room),
 	  _to(to),
 	  _tween(g_engine->_room->getOverlay(), to, duration, InterpolationMethod()) {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 345edd4945c..02dfd77f222 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -148,13 +148,13 @@ private:
 class RoomRotateTo : public Motor {
 public:
 	virtual ~RoomRotateTo();
-	RoomRotateTo(Room *room, float to);
+	RoomRotateTo(Common::SharedPtr<Room> room, float to);
 
 private:
 	virtual void update(float elasped) override;
 
 private:
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	Tween<float> _tween;
 };
 
@@ -189,12 +189,12 @@ private:
 class OverlayTo : public Motor {
 public:
 	virtual ~OverlayTo();
-	OverlayTo(float duration, Room *room, Color to);
+	OverlayTo(float duration, Common::SharedPtr<Room> room, Color to);
 
 	virtual void update(float elapsed) override;
 
 private:
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	Color _to;
 	Tween<Color> _tween;
 };
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 4a28d0b874f..2482c587ec9 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -357,12 +357,12 @@ int Object::getFlags() {
 	return result;
 }
 
-void Object::setRoom(Room *room) {
+void Object::setRoom(Common::SharedPtr<Room> room) {
 	if ((_room != room) || !_node->getParent()) {
 		if (_room != room) {
 			stopObjectMotors();
 		}
-		Room *oldRoom = _room;
+		Common::SharedPtr<Room> oldRoom = _room;
 		if (oldRoom && _node->getParent()) {
 			debugC(kDebugGame, "Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
 			Layer *layer = oldRoom->layer(0);
@@ -384,7 +384,9 @@ void Object::setRoom(Room *room) {
 				layer->_node->addChild(_node);
 			}
 		}
-		_room = room;
+		if (_room != room) {
+			_room = room;
+		}
 	}
 }
 
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 261a584ac33..50fff013fd5 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -162,7 +162,7 @@ public:
 	UseFlag useFlag();
 
 	bool contains(Math::Vector2d pos);
-	void setRoom(Room *room);
+	void setRoom(Common::SharedPtr<Room> room);
 	void delObject();
 	void stopObjectMotors();
 	void dependentOn(Object *dependentObj, int state);
@@ -227,7 +227,7 @@ public:
 	Direction _useDir = dNone;
 	Common::Rect _hotspot;
 	ObjectType _objType = otNone;
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	Common::Array<ObjectAnimation> _anims;
 	bool _temporary = false;
 	Node *_node = nullptr;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 63472519b22..004ee721291 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -81,6 +81,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 
 	debugC(kDebugObjScript, "Create object: %s, %u", sheet.c_str(), frames.size());
 	Object *obj = g_engine->_room->createObject(sheet, frames);
+	obj->_room = g_engine->_room;
 	sq_pushobject(v, obj->_table);
 
 	return 1;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index a2c93d55096..5a6eaf38d76 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -180,7 +180,6 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	obj->_node->setName(name);
 	debugC(kDebugGame, "Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 
-	obj->_room = this;
 	obj->_sheet = sheet;
 
 	// create anim if any
@@ -345,7 +344,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			obj->_objType = toObjectType(jObject);
 			if (jObject.contains("parent"))
 				obj->_parent = jObject["parent"]->asString();
-			obj->_room = this;
+			obj->_room.reset(this);
 			if (jObject.contains("animations")) {
 				parseObjectAnimations(jObject["animations"]->asArray(), obj->_anims);
 			}
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 0e38ccf53b1..bf286b6fb59 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -236,7 +236,7 @@ static SQInteger defineRoom(HSQUIRRELVM v) {
 	sqgetf(v, table, "name", name);
 	if (name.size() == 0)
 		sqgetf(v, table, "background", name);
-	Room *room = g_engine->defineRoom(name, table);
+	Common::SharedPtr<Room> room = g_engine->defineRoom(name, table);
 	debugC(kDebugRoomScript, "Define room: %s", name.c_str());
 	g_engine->_rooms.push_back(room);
 	sqpush(v, room->_table);
@@ -264,7 +264,7 @@ static SQInteger definePseudoRoom(HSQUIRRELVM v) {
 	if (SQ_FAILED(sq_getstackobj(v, -1, &table)))
 		return sq_throwerror(v, "failed to get room table");
 
-	Room *room = g_engine->defineRoom(name, table, true);
+	Common::SharedPtr<Room> room = g_engine->defineRoom(name, table, true);
 	debugC(kDebugRoomScript, "Define pseudo room: %s", name);
 	g_engine->_rooms.push_back(room);
 	sqpush(v, room->_table);
@@ -281,7 +281,7 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, name)))
 		return sq_throwerror(v, "failed to get name");
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		if (room->_name == name) {
 			sqpush(v, room->_table);
 			return 1;
@@ -305,7 +305,7 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 static SQInteger masterRoomArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		sq_pushobject(v, room->_table);
 		sq_arrayappend(v, -2);
 	}
@@ -350,7 +350,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 // local spotters = roomActors(currentRoom)
 // foreach(actor in spotters) { ...}
 static SQInteger roomActors(HSQUIRRELVM v) {
-	Room *room = sqroom(v, 2);
+	Common::SharedPtr<Room> room = sqroom(v, 2);
 	if (!room)
 		return sq_throwerror(v, "failed to get room");
 
@@ -442,7 +442,7 @@ static SQInteger roomFade(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // roomLayer(GrateEntry, -2, NO)  // Make lights out layer invisible
 static SQInteger roomLayer(HSQUIRRELVM v) {
-	Room *r = sqroom(v, 2);
+	Common::SharedPtr<Room> r = sqroom(v, 2);
 	int layer;
 	SQInteger enabled;
 	if (SQ_FAILED(sqget(v, 3, layer)))
@@ -477,7 +477,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	if (SQ_FAILED(sqget(v, 2, startColor)))
 		return sq_throwerror(v, "failed to get startColor");
-	Room *room = g_engine->_room;
+	Common::SharedPtr<Room> room = g_engine->_room;
 	if (room->_overlayTo)
 		room->_overlayTo->disable();
 	room->setOverlay(Color::fromRgba(startColor));
@@ -503,7 +503,7 @@ static SQInteger roomRotateTo(HSQUIRRELVM v) {
 }
 
 static SQInteger roomSize(HSQUIRRELVM v) {
-	Room *room = sqroom(v, 2);
+	Common::SharedPtr<Room> room = sqroom(v, 2);
 	if (!room)
 		return sq_throwerror(v, "failed to get room");
 	sqpush(v, room->_roomSize);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 1aa4dfb3024..c1c6de80965 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -94,9 +94,9 @@ static DialogConditionState parseState(Common::String &dialog) {
 	return result;
 }
 
-static Room *room(const Common::String &name) {
+static Common::SharedPtr<Room> room(const Common::String &name) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		if (room->_name == name) {
 			return room;
 		}
@@ -104,7 +104,7 @@ static Room *room(const Common::String &name) {
 	return nullptr;
 }
 
-static Object *object(Room *room, const Common::String &key) {
+static Object *object(Common::SharedPtr<Room> room, const Common::String &key) {
 	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Layer *layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
@@ -118,7 +118,7 @@ static Object *object(Room *room, const Common::String &key) {
 
 static Object *object(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Layer *layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
@@ -167,7 +167,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 			Common::String roomName = jObject["_roomKey"]->asString();
 			if (jObject.contains("_objectKey")) {
 				Common::String objName = jObject["_objectKey"]->asString();
-				Room *r = room(roomName);
+				Common::SharedPtr<Room> r = room(roomName);
 				if (!r)
 					warning("room with key=%s not found", roomName.c_str());
 				else {
@@ -178,7 +178,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 						obj = o->_table;
 				}
 			} else {
-				Room *r = room(roomName);
+				Common::SharedPtr<Room> r = room(roomName);
 				if (!r) {
 					warning("room with key=%s not found", roomName.c_str());
 				} else {
@@ -331,7 +331,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 		sqcall(obj->_table, "postLoad");
 }
 
-static void loadPseudoObjects(Room *room, const Common::JSONObject &json) {
+static void loadPseudoObjects(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
 		Object *o = object(room, it->_key);
 		if (!o)
@@ -341,7 +341,7 @@ static void loadPseudoObjects(Room *room, const Common::JSONObject &json) {
 	}
 }
 
-static void loadRoom(Room *room, const Common::JSONObject &json) {
+static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
 		if (it->_key == "_pseudoObjects") {
 			loadPseudoObjects(room, it->_value->asObject());
@@ -651,7 +651,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 					jObj["_roomKey"] = new Common::JSONValue(o->_room->_name);
 				return new Common::JSONValue(jObj);
 			} else if (isRoom(id)) {
-				Room *r = getRoom(id);
+				Common::SharedPtr<Room> r = getRoom(id);
 				jObj["_roomKey"] = new Common::JSONValue(r->_name);
 				return new Common::JSONValue(jObj);
 			}
@@ -760,7 +760,7 @@ static Common::JSONValue *createJCallbacks() {
 	return new Common::JSONValue(json);
 }
 
-static Common::JSONValue *createJRoomKey(Room *room) {
+static Common::JSONValue *createJRoomKey(Common::SharedPtr<Room> room) {
 	return new Common::JSONValue(room ? room->_name : "Void");
 }
 
@@ -925,14 +925,14 @@ static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void* data)
 		}
 }
 
-static Common::JSONValue *createJPseudoObjects(Room *room) {
+static Common::JSONValue *createJPseudoObjects(Common::SharedPtr<Room> room) {
 	Common::JSONObject json;
 	sqgetpairs(room->_table, fillPseudoObjects, &json);
 	//   result.fields.sort(cmpKey)
 	return new Common::JSONValue(json);
 }
 
-static Common::JSONValue *createJRoom(Room *room) {
+static Common::JSONValue *createJRoom(Common::SharedPtr<Room> room) {
 	Common::JSONObject json(tojson(room->_table, false, true, room->_pseudo)->asObject());
 	if (room->_pseudo) {
 		json["_pseudoObjects"] = createJPseudoObjects(room);
@@ -944,7 +944,7 @@ static Common::JSONValue *createJRoom(Room *room) {
 static Common::JSONValue *createJRooms() {
 	Common::JSONObject json;
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		if (room)
 			json[room->_name] = createJRoom(room);
 	}
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 94bd26cceca..cc08564dc98 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -70,7 +70,7 @@ private:
 
 public:
 	FadeEffect _effect = FadeEffect::None;
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	Texture *_texture1 = nullptr;
 	Texture *_texture2 = nullptr;
 	Math::Vector2d _cameraPos;
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index f5225372e40..d31350deb9e 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -275,21 +275,21 @@ int getId(HSQOBJECT table) {
 	return (int)result;
 }
 
-Room *sqroom(HSQOBJECT table) {
+Common::SharedPtr<Room> sqroom(HSQOBJECT table) {
 	int id = getId(table);
 	return getRoom(id);
 }
 
-Room *getRoom(int id) {
+Common::SharedPtr<Room> getRoom(int id) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		if (getId(room->_table) == id)
 			return room;
 	}
 	return nullptr;
 }
 
-Room *sqroom(HSQUIRRELVM v, int i) {
+Common::SharedPtr<Room> sqroom(HSQUIRRELVM v, int i) {
 	HSQOBJECT table;
 	if (SQ_SUCCEEDED(sqget(v, i, table))) {
 		return sqroom(table);
@@ -305,7 +305,7 @@ Object *sqobj(int id) {
 	}
 
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Room *room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Layer *layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 8d565e0cf22..95003858b59 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -159,9 +159,9 @@ class Room;
 class Object;
 
 int getId(HSQOBJECT table);
-Room *sqroom(HSQOBJECT table);
-Room *sqroom(HSQUIRRELVM v, int i);
-Room *getRoom(int id);
+Common::SharedPtr<Room> sqroom(HSQOBJECT table);
+Common::SharedPtr<Room> sqroom(HSQUIRRELVM v, int i);
+Common::SharedPtr<Room> getRoom(int id);
 Object *sqobj(HSQOBJECT table);
 Object *sqobj(HSQUIRRELVM v, int i);
 Object *sqobj(int i);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f11cc12b707..f2645dead1b 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -991,8 +991,7 @@ Common::Error TwpEngine::saveGameStream(Common::WriteStream *stream, bool isAuto
 struct DefineObjectParams {
 	HSQUIRRELVM v;
 	bool pseudo;
-	Room *room;
-	HSQOBJECT *roomTable;
+	Common::SharedPtr<Room> room;
 };
 
 static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
@@ -1015,7 +1014,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			sqsetf(sqrootTbl(params->v), k, oTable);
 
 			// set room as delegate
-			sqsetdelegate(oTable, *params->roomTable);
+			sqsetdelegate(oTable, params->room->_table);
 
 			// declare flags if does not exist
 			if (!sqrawexists(oTable, "flags"))
@@ -1027,7 +1026,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			obj->_node->addChild(obj->_nodeAnim);
 			obj->setRoom(params->room);
 			// set room as delegate
-			sqsetdelegate(obj->_table, *params->roomTable);
+			sqsetdelegate(obj->_table, params->room->_table);
 		} else {
 			Object *obj = params->room->getObj(k);
 			if (!obj) {
@@ -1061,7 +1060,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			obj->setRoom(params->room);
 
 			// set room as delegate
-			sqsetdelegate(obj->_table, *params->roomTable);
+			sqsetdelegate(obj->_table, params->room->_table);
 
 			// declare flags if does not exist
 			if (!sqrawexists(obj->_table, "flags"))
@@ -1070,12 +1069,12 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 	}
 }
 
-Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
+Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
 	HSQUIRRELVM v = _vm.get();
 	debugC(kDebugGame, "Load room: %s", name.c_str());
-	Room *result;
+	Common::SharedPtr<Room> result;
 	if (name == "Void") {
-		result = new Room(name, table);
+		result.reset(new Room(name, table));
 		result->_scene = new Scene();
 		Layer *layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
 		layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray());
@@ -1083,7 +1082,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 		result->_scene->addChild(layer->_node);
 		sqsetf(sqrootTbl(v), name, result->_table);
 	} else {
-		result = new Room(name, table);
+		result.reset(new Room(name, table));
 		Common::String background;
 		sqgetf(table, "background", background);
 		GGPackEntryReader entry;
@@ -1149,7 +1148,6 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 	params.pseudo = pseudo;
 	params.v = v;
 	params.room = result;
-	params.roomTable = &table;
 	sqgetpairs(result->_table, onGetPairs, &params);
 
 	// declare the room in the root table
@@ -1159,7 +1157,7 @@ Room *TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool ps
 	return result;
 }
 
-void TwpEngine::enterRoom(Room *room, Object *door) {
+void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Object *door) {
 	HSQUIRRELVM v = getVm();
 	// Called when the room is entered.
 	debugC(kDebugGame, "call enter room function of %s", room->_name.c_str());
@@ -1247,7 +1245,7 @@ void TwpEngine::actorEnter() {
 	}
 }
 
-void TwpEngine::exitRoom(Room *nextRoom) {
+void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 	HSQUIRRELVM v = getVm();
 	_mixer->stopAll();
 	if (_room) {
@@ -1293,7 +1291,7 @@ void TwpEngine::exitRoom(Room *nextRoom) {
 	}
 }
 
-void TwpEngine::setRoom(Room *room) {
+void TwpEngine::setRoom(Common::SharedPtr<Room> room) {
 	if (room && _room != room)
 		enterRoom(room);
 }
@@ -1359,7 +1357,7 @@ void TwpEngine::follow(Object *actor) {
 	_followActor = actor;
 	if (actor) {
 		Math::Vector2d pos = actor->_node->getPos();
-		Room *oldRoom = _room;
+		Common::SharedPtr<Room> oldRoom = _room;
 		setRoom(actor->_room);
 		if (oldRoom != actor->_room)
 			cameraAt(pos);
@@ -1411,7 +1409,7 @@ void TwpEngine::setActor(Object *actor, bool userSelected) {
 
 	// call onActorSelected callbacks
 	sqcall("onActorSelected", actor->_table, userSelected);
-	Room *room = !actor ? nullptr : actor->_room;
+	Common::SharedPtr<Room> room = !actor ? nullptr : actor->_room;
 	if (room) {
 		if (sqrawexists(room->_table, "onActorSelected")) {
 			sqcall(room->_table, "onActorSelected", actor->_table, userSelected);
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f693b62817c..f7a41571b72 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -28,6 +28,7 @@
 #include "common/random.h"
 #include "common/serializer.h"
 #include "common/util.h"
+#include "common/ptr.h"
 #include "engines/engine.h"
 #include "engines/savestate.h"
 #include "graphics/screen.h"
@@ -128,9 +129,9 @@ public:
 	void stopTalking();
 	void walkFast(bool state = true);
 
-	Room *defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
-	void setRoom(Room *room);
-	void enterRoom(Room *room, Object *door = nullptr);
+	Common::SharedPtr<Room> defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
+	void setRoom(Common::SharedPtr<Room> room);
+	void enterRoom(Common::SharedPtr<Room> room, Object *door = nullptr);
 
 	void cameraAt(Math::Vector2d at);
 	// Returns the camera position: the position of the middle of the screen.
@@ -149,7 +150,7 @@ public:
 private:
 	void update(float elapsedMs);
 	void draw(RenderTexture* texture = nullptr);
-	void exitRoom(Room *nextRoom);
+	void exitRoom(Common::SharedPtr<Room> nextRoom);
 	void actorEnter();
 	void actorExit();
 	void cancelSentence(Object *actor = nullptr);
@@ -172,7 +173,7 @@ public:
 	Graphics::Screen *_screen = nullptr;
 	GGPackSet _pack;
 	ResManager _resManager;
-	Common::Array<Room *> _rooms;
+	Common::Array<Common::SharedPtr<Room>> _rooms;
 	Common::Array<Object *> _actors;
 	Common::Array<Object *> _objects;
 	Common::Array<ThreadBase *> _threads;
@@ -180,7 +181,7 @@ public:
 	Common::Array<Callback *> _callbacks;
 	Object *_actor = nullptr;
 	Object *_followActor = nullptr;
-	Room *_room = nullptr;
+	Common::SharedPtr<Room> _room;
 	float _time = 0.f;
 	Object *_noun1 = nullptr;
 	Object *_noun2 = nullptr;


Commit: 47bf594d73c7f23b9168592a765d18bd5a23d9f2
    https://github.com/scummvm/scummvm/commit/47bf594d73c7f23b9168592a765d18bd5a23d9f2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix layer leaks

Changed paths:
    engines/twp/genlib.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 002d78680d8..aee560426a5 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -740,7 +740,7 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 	switch (nArgs) {
 	case 1: {
 		for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-			Layer *layer = g_engine->_room->_layers[i];
+			Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Object *obj = layer->_objects[j];
 				obj->_exec.enabled = false;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 2482c587ec9..50cafc0d024 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -365,7 +365,7 @@ void Object::setRoom(Common::SharedPtr<Room> room) {
 		Common::SharedPtr<Room> oldRoom = _room;
 		if (oldRoom && _node->getParent()) {
 			debugC(kDebugGame, "Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
-			Layer *layer = oldRoom->layer(0);
+			Common::SharedPtr<Layer>layer = oldRoom->layer(0);
 			if (layer) {
 				int index = find(layer->_objects, this);
 				if (index != -1)
@@ -376,7 +376,7 @@ void Object::setRoom(Common::SharedPtr<Room> room) {
 		}
 		if (room && room->layer(0) && room->layer(0)->_node) {
 			debugC(kDebugGame, "Add %s in room %s", _key.c_str(), room->_name.c_str());
-			Layer *layer = room->layer(0);
+			Common::SharedPtr<Layer> layer = room->layer(0);
 			if (layer) {
 				int index = find(layer->_objects, this);
 				if (index == -1)
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 50fff013fd5..c9c93e9faf7 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -233,7 +233,7 @@ public:
 	Node *_node = nullptr;
 	Node *_sayNode = nullptr;
 	Anim *_nodeAnim = nullptr;
-	Layer *_layer = nullptr;
+	Common::SharedPtr<Layer> _layer;
 	Common::StringArray _hiddenLayers;
 	Common::String _animName;
 	int _animFlags = 0;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 5a6eaf38d76..9f4a83972d7 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -154,9 +154,6 @@ Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 }
 
 Room::~Room() {
-	for (size_t i = 0; i < _layers.size(); i++) {
-		delete _layers[i];
-	}
 	delete _scene;
 }
 
@@ -281,7 +278,7 @@ void Room::load(Common::SeekableReadStream &s) {
 	}
 
 	{
-		Layer *layer = new Layer(backNames, Math::Vector2d(1, 1), 0);
+		 Common::SharedPtr<Layer> layer(new Layer(backNames, Math::Vector2d(1, 1), 0));
 		_layers.push_back(layer);
 	}
 
@@ -302,7 +299,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			Math::Vector2d parallax = parseParallax(*jLayer["parallax"]);
 			int zsort = jLayer["zsort"]->asIntegerNumber();
 
-			Layer *layer = new Layer(names, parallax, zsort);
+			Common::SharedPtr<Layer> layer(new Layer(names, parallax, zsort));
 			_layers.push_back(layer);
 		}
 	}
@@ -383,9 +380,9 @@ void Room::load(Common::SeekableReadStream &s) {
 	delete value;
 }
 
-Layer *Room::layer(int zsort) {
+Common::SharedPtr<Layer> Room::layer(int zsort) {
 	for (size_t i = 0; i < _layers.size(); i++) {
-		Layer *l = _layers[i];
+		Common::SharedPtr<Layer> l = _layers[i];
 		if (l->_zsort == zsort)
 			return l;
 	}
@@ -407,7 +404,7 @@ Math::Vector2d Room::getScreenSize() {
 
 Object *Room::getObj(const Common::String &key) {
 	for (size_t i = 0; i < _layers.size(); i++) {
-		Layer *layer = _layers[i];
+		Common::SharedPtr<Layer> layer = _layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (obj->_key == key)
@@ -432,7 +429,7 @@ float Room::getScaling(float yPos) {
 }
 
 void Room::objectParallaxLayer(Object *obj, int zsort) {
-	Layer *l = layer(zsort);
+	Common::SharedPtr<Layer> l = layer(zsort);
 	if (obj->_layer != l) {
 		// removes object from old layer
 		if (obj->_layer) {
@@ -462,7 +459,7 @@ void Room::update(float elapsed) {
 	if (_rotateTo)
 		_rotateTo->update(elapsed);
 	for (size_t j = 0; j < _layers.size(); j++) {
-		Layer *layer = _layers[j];
+		Common::SharedPtr<Layer> layer = _layers[j];
 		for (size_t k = 0; k < layer->_objects.size(); k++) {
 			Object *obj = layer->_objects[k];
 			obj->update(elapsed);
diff --git a/engines/twp/room.h b/engines/twp/room.h
index b6313479290..78c30e95625 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -25,6 +25,7 @@
 #include "common/array.h"
 #include "common/rect.h"
 #include "common/stream.h"
+#include "common/ptr.h"
 #include "math/vector2d.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/font.h"
@@ -116,7 +117,7 @@ public:
 
 	Math::Vector2d getScreenSize();
 
-	Layer *layer(int zsort);
+	Common::SharedPtr<Layer> layer(int zsort);
 	Object *getObj(const Common::String &key);
 
 	Light *createLight(Color color, Math::Vector2d pos);
@@ -134,7 +135,7 @@ public:
 	Math::Vector2d _roomSize;          // Size of the room
 	int _fullscreen = 0;               // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
 	int _height = 0;                   // Height of the room (what else ?)
-	Common::Array<Layer *> _layers;    // Parallax layers of a room
+	Common::Array<Common::SharedPtr<Layer> > _layers;    // Parallax layers of a room
 	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
 	Common::Array<Walkbox> _mergedPolygon;
 	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index c1c6de80965..6184e7da715 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -106,7 +106,7 @@ static Common::SharedPtr<Room> room(const Common::String &name) {
 
 static Object *object(Common::SharedPtr<Room> room, const Common::String &key) {
 	for (size_t i = 0; i < room->_layers.size(); i++) {
-		Layer *layer = room->_layers[i];
+		Common::SharedPtr<Layer> layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *o = layer->_objects[j];
 			if (o->_key == key)
@@ -120,7 +120,7 @@ static Object *object(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
-			Layer *layer = room->_layers[j];
+			Common::SharedPtr<Layer> layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
 				Object *o = layer->_objects[k];
 				if (o->_key == key)
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 938130d6c52..417ff0be25a 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -809,7 +809,7 @@ void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
 	SpriteSheetFrame *frame = &gameSheet->frameTable["hotspot_marker"];
 	Color color = Color::create(255, 165, 0);
 	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-		Layer *layer = g_engine->_room->_layers[i];
+		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index d31350deb9e..989d734fd74 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -307,7 +307,7 @@ Object *sqobj(int id) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
-			Layer *layer = room->_layers[j];
+			Common::SharedPtr<Layer> layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
 				Object *obj = layer->_objects[k];
 				if (getId(obj->_table) == id)
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index b21c7d450fe..5b86ea6b5bb 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -360,7 +360,7 @@ static bool isSomeoneTalking() {
 			return true;
 	}
 	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
-		Layer *layer = *it;
+		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
 			Object *obj = *it2;
 			if (obj->getTalking() && obj->getTalking()->isEnabled())
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f2645dead1b..0750d6c529c 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -296,7 +296,7 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 	if (g_engine->_uiInv.getObject() && g_engine->_room->_fullscreen == FULLSCREENROOM)
 		func(g_engine->_uiInv.getObject());
 	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-		Layer *layer = g_engine->_room->_layers[i];
+		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if ((obj != g_engine->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
@@ -1076,7 +1076,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 	if (name == "Void") {
 		result.reset(new Room(name, table));
 		result->_scene = new Scene();
-		Layer *layer = new Layer("background", Math::Vector2d(1.f, 1.f), 0);
+		Common::SharedPtr<Layer> layer(new Layer("background", Math::Vector2d(1.f, 1.f), 0));
 		layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray());
 		result->_layers.push_back(layer);
 		result->_scene->addChild(layer->_node);
@@ -1091,7 +1091,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 		result->_name = name;
 		result->_pseudo = pseudo;
 		for (size_t i = 0; i < result->_layers.size(); i++) {
-			Layer *layer = result->_layers[i];
+			Common::SharedPtr<Layer> layer = result->_layers[i];
 			// create layer node
 			ParallaxNode *layerNode = new ParallaxNode(layer->_parallax, result->_sheet, layer->_names);
 			layerNode->setZSort(layer->_zsort);
@@ -1130,7 +1130,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 
 	// assign parent node
 	for (size_t i = 0; i < result->_layers.size(); i++) {
-		Layer *layer = result->_layers[i];
+		Common::SharedPtr<Layer> layer = result->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			if (obj->_parent.size() > 0) {
@@ -1201,7 +1201,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Object *door) {
 	// call actor enter function and objects enter function
 	actorEnter();
 	for (size_t i = 0; i < room->_layers.size(); i++) {
-		Layer *layer = room->_layers[i];
+		Common::SharedPtr<Layer> layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Object *obj = layer->_objects[j];
 			// add all scaling triggers
@@ -1264,7 +1264,7 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 
 		// delete all temporary objects
 		for (size_t i = 0; i < _room->_layers.size(); i++) {
-			Layer *layer = _room->_layers[i];
+			Common::SharedPtr<Layer> layer = _room->_layers[i];
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Object *obj = layer->_objects[j];
 				if (obj->_temporary) {
@@ -1609,7 +1609,7 @@ void TwpEngine::updateTriggers() {
 
 void TwpEngine::stopTalking() {
 	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
-		Layer *layer = *it;
+		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
 			(*it2)->stopTalking();
 		}


Commit: 34387419c2853c6edf62ea88373d390fd489d641
    https://github.com/scummvm/scummvm/commit/34387419c2853c6edf62ea88373d390fd489d641
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with tasks/threess/callbacks

Changed paths:
    engines/twp/savegame.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/task.h
    engines/twp/thread.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 6184e7da715..e2ca8c475cd 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -511,7 +511,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 				toSquirrel(jCallBackHash["param"], arg);
 				sqgetitems(arg, GetHObjects(args));
 			}
-			g_engine->_callbacks.push_back(new Callback(id, time, name, args));
+			g_engine->_callbacks.push_back(Common::SharedPtr<Callback>(new Callback(id, time, name, args)));
 		}
 	}
 	setCallbackId(json["nextGuid"]->asIntegerNumber());
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 989d734fd74..3aca63625a0 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -399,14 +399,14 @@ void sqexec(HSQUIRRELVM v, const char *code, const char *filename) {
 	sq_settop(v, top);
 }
 
-ThreadBase *sqthread(HSQUIRRELVM v, int i) {
+Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v, int i) {
 	int id;
 	if (SQ_SUCCEEDED(sqget(v, i, id)))
 		return sqthread(id);
 	return nullptr;
 }
 
-ThreadBase *sqthread(int id) {
+Common::SharedPtr<ThreadBase> sqthread(int id) {
 	if (g_engine->_cutscene) {
 		if (g_engine->_cutscene->getId() == id) {
 			return g_engine->_cutscene;
@@ -414,7 +414,7 @@ ThreadBase *sqthread(int id) {
 	}
 
 	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
-		ThreadBase *t = g_engine->_threads[i];
+		Common::SharedPtr<ThreadBase> t = g_engine->_threads[i];
 		if (t->getId() == id) {
 			return t;
 		}
@@ -444,7 +444,7 @@ Light *sqlight(HSQUIRRELVM v, int i) {
 
 struct GetThread {
 	GetThread(HSQUIRRELVM v) : _v(v) {}
-	bool operator()(ThreadBase *t) {
+	bool operator()(Common::SharedPtr<ThreadBase> t) {
 		return t->getThread() == _v;
 	}
 
@@ -452,7 +452,7 @@ private:
 	HSQUIRRELVM _v;
 };
 
-ThreadBase *sqthread(HSQUIRRELVM v) {
+Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v) {
 	if (g_engine->_cutscene) {
 		if (g_engine->_cutscene->getThread() == v) {
 			return g_engine->_cutscene;
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 95003858b59..51daaca17cc 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -169,9 +169,9 @@ Object *sqactor(HSQOBJECT table);
 Object *sqactor(HSQUIRRELVM v, int i);
 SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);
 SoundDefinition *sqsounddef(int id);
-ThreadBase *sqthread(HSQUIRRELVM v);
-ThreadBase *sqthread(HSQUIRRELVM v, int id);
-ThreadBase *sqthread(int id);
+Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v);
+Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v, int id);
+Common::SharedPtr<ThreadBase> sqthread(int id);
 Light *sqlight(int id);
 Light *sqlight(HSQUIRRELVM v, int i);
 
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 5b86ea6b5bb..4957c8dd81c 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -83,7 +83,7 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 		sq_pop(v, 1); // pop name
 	sq_pop(v, 1);     // pop closure
 
-	g_engine->_threads.push_back(t);
+	g_engine->_threads.push_back(Common::SharedPtr<ThreadBase>(t));
 
 	debugC(kDebugSysScript, "create thread %s", t->getName().c_str());
 
@@ -95,8 +95,8 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 	return 1;
 }
 
-static SQInteger breakfunc(HSQUIRRELVM v, void func(ThreadBase *t, void *data), void *data) {
-	ThreadBase *t = sqthread(v);
+static SQInteger breakfunc(HSQUIRRELVM v, void func(Common::SharedPtr<ThreadBase> t, void *data), void *data) {
+	Common::SharedPtr<ThreadBase> t = sqthread(v);
 	if (!t)
 		return sq_throwerror(v, "failed to get thread");
 	t->suspend();
@@ -151,7 +151,7 @@ static SQInteger addCallback(HSQUIRRELVM v) {
 	}
 
 	Callback *callback = new Callback(newCallbackId(), duration, methodName, args);
-	g_engine->_callbacks.push_back(callback);
+	g_engine->_callbacks.push_back(Common::SharedPtr<Callback>(callback));
 
 	sqpush(v, callback->getId());
 	return 1;
@@ -166,14 +166,14 @@ static SQInteger addFolder(HSQUIRRELVM v) {
 	return 0;
 }
 
-static void threadFrames(ThreadBase *tb, void *data) {
+static void threadFrames(Common::SharedPtr<ThreadBase> tb, void *data) {
 	int numFrames = *(int *)data;
-	((Thread *)tb)->_numFrames = numFrames;
+	((Thread *)tb.get())->_numFrames = numFrames;
 }
 
-static void threadTime(ThreadBase *tb, void *data) {
+static void threadTime(Common::SharedPtr<ThreadBase> tb, void *data) {
 	float time = *(float *)data;
-	((Thread *)tb)->_waitTime = time;
+	((Thread *)tb.get())->_waitTime = time;
 }
 
 // When called in a function started with startthread, execution is suspended for count frames.
@@ -226,12 +226,12 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	Common::String name = Common::String::format(fmt, va);
 	va_end(va);
 
-	ThreadBase *curThread = sqthread(v);
+	Common::SharedPtr<ThreadBase> curThread = sqthread(v);
 	if (!curThread)
 		return sq_throwerror(v, "Current thread should be created with startthread");
 
 	debugC(kDebugSysScript, "add breakwhilecond name=%s pid=%d, %s", name.c_str(), curThread->getId(), curThread->getName().c_str());
-	g_engine->_tasks.push_back(new BreakWhileCond<Predicate>(curThread->getId(), name, pred));
+	g_engine->_tasks.push_back(Common::SharedPtr<Task>(new BreakWhileCond<Predicate>(curThread->getId(), name, pred)));
 	return -666;
 }
 
@@ -339,7 +339,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 		sqget(v, 2, id);
 	debugC(kDebugSysScript, "breakwhilerunning: %d", id);
 
-	ThreadBase *t = sqthread(id);
+	Common::SharedPtr<ThreadBase> t = sqthread(id);
 	if (!t) {
 		if (!isSound(id)) {
 			warning("thread and sound not found: %d", id);
@@ -492,9 +492,9 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get cutscene override closure");
 	}
 
-	ThreadBase* parentThread = sqthread(v);
+	Common::SharedPtr<ThreadBase> parentThread = sqthread(v);
 	Cutscene *cutscene = new Cutscene(parentThread->getId(), threadObj, closure, closureOverride, envObj);
-	g_engine->_cutscene = cutscene;
+	g_engine->_cutscene.reset(cutscene);
 
 	// call the closure in the thread
 	cutscene->update(0.f);
@@ -608,7 +608,7 @@ static SQInteger inputOff(HSQUIRRELVM v) {
 }
 
 static SQInteger inputOn(HSQUIRRELVM v) {
-	Cutscene *cutscene = g_engine->_cutscene;
+	Common::SharedPtr<Cutscene> cutscene = g_engine->_cutscene;
 	if (!cutscene || cutscene->isStopped()) {
 		g_engine->_inputState.setInputActive(true);
 		g_engine->_inputState.setShowCursor(true);
@@ -723,10 +723,9 @@ static SQInteger removeCallback(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, id)))
 		return sq_throwerror(v, "failed to get callback");
 	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
-		Callback *cb = g_engine->_callbacks[i];
+		Common::SharedPtr<Callback> cb = g_engine->_callbacks[i];
 		if (cb->getId() == id) {
 			g_engine->_callbacks.remove_at(i);
-			delete cb;
 			return 0;
 		}
 	}
@@ -755,7 +754,7 @@ static SQInteger stopthread(HSQUIRRELVM v) {
 		return 1;
 	}
 
-	ThreadBase *t = sqthread(id);
+	Common::SharedPtr<ThreadBase> t = sqthread(id);
 	if (t) {
 		t->stop();
 	}
@@ -783,7 +782,7 @@ static SQInteger stopthread(HSQUIRRELVM v) {
 //     }
 // }
 static SQInteger threadid(HSQUIRRELVM v) {
-	ThreadBase *t = sqthread(v);
+	Common::SharedPtr<ThreadBase> t = sqthread(v);
 	if (t)
 		sqpush(v, t->getId());
 	else
@@ -794,7 +793,7 @@ static SQInteger threadid(HSQUIRRELVM v) {
 // Specify whether a thread should be pauseable or not.
 // If a thread is not pauseable, it won't be possible to pause this thread.
 static SQInteger threadpauseable(HSQUIRRELVM v) {
-	ThreadBase *t = sqthread(v, 2);
+	Common::SharedPtr<ThreadBase> t = sqthread(v, 2);
 	if (!t)
 		return sq_throwerror(v, "failed to get thread");
 	int pauseable = 0;
diff --git a/engines/twp/task.h b/engines/twp/task.h
index 646ff76ca62..79be06cc9a1 100644
--- a/engines/twp/task.h
+++ b/engines/twp/task.h
@@ -49,7 +49,7 @@ public:
 	virtual bool update(float elapsed) override final {
 		if (_cond())
 			return false;
-		ThreadBase *pt = sqthread(_parentId);
+		Common::SharedPtr<ThreadBase> pt = sqthread(_parentId);
 		if (pt) {
 			debugC(kDebugGame, "Resume task: %d, %s", _parentId, pt->getName().c_str());
 			pt->resume();
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index f2da6bbed03..6e19c09723f 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -132,7 +132,7 @@ Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, H
 	g_engine->_inputState.setInputActive(false);
 	g_engine->_inputState.setShowCursor(false);
 	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
-		ThreadBase *thread = g_engine->_threads[i];
+		Common::SharedPtr<ThreadBase> thread = g_engine->_threads[i];
 		if (thread->isGlobal())
 			thread->pause();
 	}
@@ -174,15 +174,15 @@ void Cutscene::stop() {
 		g_engine->_inputState.setInputActive(true);
 	debugC(kDebugGame, "Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
-	Common::Array<ThreadBase *> threads(g_engine->_threads);
+	Common::Array<Common::SharedPtr<ThreadBase> > threads(g_engine->_threads);
 	for (size_t i = 0; i < threads.size(); i++) {
-		ThreadBase *thread = threads[i];
+		Common::SharedPtr<ThreadBase> thread = threads[i];
 		if (thread->isGlobal())
 			thread->unpause();
 	}
 	sqcall("onCutsceneEnded");
 
-	ThreadBase *t = sqthread(_parentThreadId);
+	Common::SharedPtr<ThreadBase> t = sqthread(_parentThreadId);
 	if (t && t->getId())
 		t->unpause();
 	sq_suspendvm(getThread());
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0750d6c529c..cd6e6d8b516 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -496,17 +496,16 @@ void TwpEngine::update(float elapsed) {
 	// update cutscene
 	if (_cutscene) {
 		if (_cutscene->update(elapsed)) {
-			delete _cutscene;
-			_cutscene = nullptr;
+			_cutscene.reset();
 		}
 	}
 
 	// update threads: make a copy of the threads because during threads update, new threads can be added
-	Common::Array<ThreadBase *> threads(_threads);
-	Common::Array<ThreadBase *> threadsToRemove;
+	Common::Array<Common::SharedPtr<ThreadBase> > threads(_threads);
+	Common::Array<Common::SharedPtr<ThreadBase> > threadsToRemove;
 
 	for (auto it = threads.begin(); it != threads.end(); it++) {
-		ThreadBase *thread = *it;
+		Common::SharedPtr<ThreadBase> thread = *it;
 		if (thread->update(elapsed)) {
 			threadsToRemove.push_back(thread);
 		}
@@ -514,20 +513,18 @@ void TwpEngine::update(float elapsed) {
 
 	// remove threads that are terminated
 	for (auto it = threadsToRemove.begin(); it != threadsToRemove.end(); it++) {
-		ThreadBase *thread = *it;
+		Common::SharedPtr<ThreadBase> thread = *it;
 		int i = find(_threads, *it);
 		if (i != -1) {
 			_threads.remove_at(i);
-			delete thread;
 		}
 	}
 
 	// update callbacks
 	for (auto it = _callbacks.begin(); it != _callbacks.end();) {
-		Callback *cb = *it;
+		Common::SharedPtr<Callback> cb = *it;
 		if (cb->update(elapsed)) {
 			it = _callbacks.erase(it);
-			delete cb;
 			continue;
 		}
 		it++;
@@ -535,10 +532,9 @@ void TwpEngine::update(float elapsed) {
 
 	// update tasks
 	for (auto it = _tasks.begin(); it != _tasks.end();) {
-		Task *task = *it;
+		Common::SharedPtr<Task> task = *it;
 		if (task->update(elapsed)) {
 			it = _tasks.erase(it);
-			delete task;
 			continue;
 		}
 		it++;
@@ -1280,7 +1276,7 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 
 		// stop all local threads
 		for (size_t i = 0; i < _threads.size(); i++) {
-			ThreadBase *thread = _threads[i];
+			Common::SharedPtr<ThreadBase> thread = _threads[i];
 			if (!thread->isGlobal()) {
 				thread->stop();
 			}
@@ -1562,7 +1558,7 @@ void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
 		Thread *thread = new Thread("Trigger", false, threadObj, obj->_table, trigger, args);
 
 		debugC(kDebugGame, "create triggerthread id: %d}", thread->getId());
-		g_engine->_threads.push_back(thread);
+		g_engine->_threads.push_back(Common::SharedPtr<ThreadBase>(thread));
 
 		// call the closure in the thread
 		if (!thread->call()) {
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f7a41571b72..567e7ae7d8e 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -149,7 +149,7 @@ public:
 
 private:
 	void update(float elapsedMs);
-	void draw(RenderTexture* texture = nullptr);
+	void draw(RenderTexture *texture = nullptr);
 	void exitRoom(Common::SharedPtr<Room> nextRoom);
 	void actorEnter();
 	void actorExit();
@@ -166,19 +166,22 @@ private:
 	void callTrigger(Object *obj, HSQOBJECT trigger);
 	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
 	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
-	Scaling* getScaling(const Common::String& name);
+	Scaling *getScaling(const Common::String &name);
 	void skipCutscene();
 
+private:
+	Vm _vm;
+
 public:
 	Graphics::Screen *_screen = nullptr;
 	GGPackSet _pack;
 	ResManager _resManager;
-	Common::Array<Common::SharedPtr<Room>> _rooms;
+	Common::Array<Common::SharedPtr<Room> > _rooms;
 	Common::Array<Object *> _actors;
 	Common::Array<Object *> _objects;
-	Common::Array<ThreadBase *> _threads;
-	Common::Array<Task *> _tasks;
-	Common::Array<Callback *> _callbacks;
+	Common::Array<Common::SharedPtr<ThreadBase> > _threads;
+	Common::Array<Common::SharedPtr<Task> > _tasks;
+	Common::Array<Common::SharedPtr<Callback> > _callbacks;
 	Object *_actor = nullptr;
 	Object *_followActor = nullptr;
 	Common::SharedPtr<Room> _room;
@@ -192,7 +195,7 @@ public:
 	float _nextHoldToMoveTime = 0.f;
 	int _frameCounter = 0;
 	Lighting *_lighting = nullptr;
-	Cutscene *_cutscene = nullptr;
+	Common::SharedPtr<Cutscene> _cutscene;
 	Scene _scene;
 	Scene _screenScene;
 	NoOverrideNode _noOverride;
@@ -228,7 +231,6 @@ public:
 
 private:
 	Gfx _gfx;
-	Vm _vm;
 	Preferences _prefs;
 	SentenceNode _sentence;
 	WalkboxNode _walkboxNode;


Commit: ad0dd458b6b0e07f6c716e152981c35b72534fc2
    https://github.com/scummvm/scummvm/commit/ad0dd458b6b0e07f6c716e152981c35b72534fc2
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with objects/actors

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/audio.cpp
    engines/twp/camera.cpp
    engines/twp/camera.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/genlib.cpp
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/util.cpp
    engines/twp/util.h
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index a726344aaf4..a5cce86da43 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -32,7 +32,7 @@ namespace Twp {
 
 // Sets the transparency for an actor's image in [0.0..1.0]
 static SQInteger actorAlpha(HSQUIRRELVM v) {
-	Object *actor = sqobj(v, 2);
+	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	float alpha;
@@ -44,7 +44,7 @@ static SQInteger actorAlpha(HSQUIRRELVM v) {
 }
 
 static SQInteger actorAnimationFlags(HSQUIRRELVM v) {
-	Object *actor = sqobj(v, 2);
+	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	sqpush(v, actor->_animFlags);
@@ -52,7 +52,7 @@ static SQInteger actorAnimationFlags(HSQUIRRELVM v) {
 }
 
 static SQInteger actorAnimationNames(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 
@@ -81,13 +81,13 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	switch (numArgs) {
 	case 3: {
-		Object *actor = sqactor(v, 2);
+		Common::SharedPtr<Object> actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		Object *spot = sqobj(v, 3);
+		Common::SharedPtr<Object> spot = sqobj(v, 3);
 		if (spot) {
 			Math::Vector2d pos = spot->_node->getPos() + spot->_usePos;
-			actor->setRoom(spot->_room);
+			Object::setRoom(actor, spot->_room);
 			actor->stopWalking();
 			debugC(kDebugActScript, "actorAt %s at %s (%d, %d), room '%s'", actor->_key.c_str(), spot->_key.c_str(), (int)pos.getX(), (int)pos.getY(), spot->_room->_name.c_str());
 			actor->_node->setPos(pos);
@@ -98,12 +98,12 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get spot or room");
 			debugC(kDebugActScript, "actorAt %s room '%s'", actor->_key.c_str(), room->_name.c_str());
 			actor->stopWalking();
-			actor->setRoom(room);
+			Object::setRoom(actor, room);
 		}
 		return 0;
 	}
 	case 4: {
-		Object *actor = sqactor(v, 2);
+		Common::SharedPtr<Object> actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
 		int x, y;
@@ -118,7 +118,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 	}
 	case 5:
 	case 6: {
-		Object *actor = sqactor(v, 2);
+		Common::SharedPtr<Object> actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
 		Common::SharedPtr<Room> room = sqroom(v, 3);
@@ -136,7 +136,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 		actor->stopWalking();
 		actor->_node->setPos(Math::Vector2d(x, y));
 		actor->setFacing(getFacing(dir, actor->getFacing()));
-		actor->setRoom(room);
+		Object::setRoom(actor, room);
 		return 0;
 	}
 	default:
@@ -145,7 +145,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 }
 
 static SQInteger actorBlinkRate(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	float min;
@@ -154,7 +154,7 @@ static SQInteger actorBlinkRate(HSQUIRRELVM v) {
 	float max;
 	if (SQ_FAILED(sqget(v, 4, max)))
 		return sq_throwerror(v, "failed to get max");
-	actor->blinkRate(min, max);
+	Object::blinkRate(actor, min, max);
 	return 0;
 }
 
@@ -163,7 +163,7 @@ static SQInteger actorBlinkRate(HSQUIRRELVM v) {
 // . code-block:: Squirrel
 // actorColor(coroner, 0xc0c0c0)
 static SQInteger actorColor(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int c;
@@ -177,7 +177,7 @@ static SQInteger actorColor(HSQUIRRELVM v) {
 // If the actor is expected to preform the standard walk, talk, stand, reach animations, they need to exist in the file.
 // If a sheet is given, this is a sprite sheet containing all the images needed for the animation.
 static SQInteger actorCostume(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 
@@ -194,10 +194,10 @@ static SQInteger actorCostume(HSQUIRRELVM v) {
 }
 
 static SQInteger actorDistanceTo(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	Object *obj = nullptr;
+	Common::SharedPtr<Object> obj = nullptr;
 	if (sq_gettop(v) == 3)
 		obj = sqobj(v, 3);
 	if (!obj)
@@ -211,21 +211,21 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 3) {
-		Object *actor1 = g_engine->_actor;
-		Object *actor2 = sqactor(v, 2);
+		Common::SharedPtr<Object> actor1 = g_engine->_actor;
+		Common::SharedPtr<Object> actor2 = sqactor(v, 2);
 		if (!actor2)
 			return sq_throwerror(v, "failed to get actor");
-		Object *obj = sqobj(v, 3);
+		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot");
 		// not sure about this, needs to be check one day ;)
 		sqpush(v, distance((Vector2i)actor1->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < distance((Vector2i)actor2->_node->getAbsPos(), (Vector2i)obj->getUsePos()));
 		return 1;
 	} else if (nArgs == 4) {
-		Object *actor = sqactor(v, 2);
+		Common::SharedPtr<Object> actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		Object *obj = sqobj(v, 3);
+		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 		int dist;
@@ -242,7 +242,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 // Directions are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT.
 // Similar to actorTurnTo, but will not animate the change, it will instantly be in the specified direction.
 static SQInteger actorFace(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	SQInteger nArgs = sq_gettop(v);
@@ -264,7 +264,7 @@ static SQInteger actorFace(HSQUIRRELVM v) {
 			actor->setFacing((Facing)dir);
 		}
 	} else {
-		Object *actor2 = sqactor(v, 3);
+		Common::SharedPtr<Object> actor2 = sqactor(v, 3);
 		if (!actor2)
 			return sq_throwerror(v, "failed to get actor to face to");
 		Facing facing = getFacingToFaceTo(actor, actor2);
@@ -274,7 +274,7 @@ static SQInteger actorFace(HSQUIRRELVM v) {
 }
 
 static SQInteger actorHidden(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int hidden = 0;
@@ -294,10 +294,10 @@ static SQInteger actorHidden(HSQUIRRELVM v) {
 // if (stepsArray.len()) {    // someone's on the steps
 // }
 static SQInteger actorInTrigger(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	Object *obj = sqobj(v, 3);
+	Common::SharedPtr<Object> obj = sqobj(v, 3);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	bool inside = obj->contains(actor->_node->getAbsPos());
@@ -327,7 +327,7 @@ static SQInteger actorInTrigger(HSQUIRRELVM v) {
 //     }
 // }
 static SQInteger actorInWalkbox(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	Common::String name;
@@ -347,7 +347,7 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 }
 
 static SQInteger actorRoom(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	Common::SharedPtr<Room> room = actor->_room;
@@ -360,7 +360,7 @@ static SQInteger actorRoom(HSQUIRRELVM v) {
 }
 
 static SQInteger actorShowHideLayer(HSQUIRRELVM v, bool visible) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	Common::String layer;
@@ -413,7 +413,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get slot");
 			g_engine->_hud._actorSlots[slot - 1].selectable = selectable;
 		} else {
-			Object *actor = sqactor(v, 2);
+			Common::SharedPtr<Object> actor = sqactor(v, 2);
 			if (!actor)
 				return sq_throwerror(v, "failed to get actor");
 			Common::String key;
@@ -436,7 +436,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 // Directions are: FACE_FRONT, FACE_BACK, FACE_LEFT, FACE_RIGHT.
 // If "NO" is specified, it removes all locking and allows the actor to change its facing direction based on player input or otherwise.
 static SQInteger actorLockFacing(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	switch (sq_gettype(v, 3)) {
@@ -471,7 +471,7 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 }
 
 static SQInteger actorPosX(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	sqpush(v, actor->_node->getPos().getX());
@@ -479,7 +479,7 @@ static SQInteger actorPosX(HSQUIRRELVM v) {
 }
 
 static SQInteger actorPosY(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	sqpush(v, actor->_node->getPos().getY());
@@ -489,7 +489,7 @@ static SQInteger actorPosY(HSQUIRRELVM v) {
 // Plays the specified animation from the player's costume JSON filename.
 // If YES loop the animation. Default is NO.
 static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	Common::String animation;
@@ -509,7 +509,7 @@ static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
 // Actor's are typically adjusted so they are rendered from the middle of the bottom of their feet.
 // To maintain sanity, it is best if all actors have the same image size and are all adjust the same, but this is not a requirement.
 static SQInteger actorRenderOffset(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int x, y;
@@ -522,7 +522,7 @@ static SQInteger actorRenderOffset(HSQUIRRELVM v) {
 }
 
 static SQInteger actorStand(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	actor->stand();
@@ -535,7 +535,7 @@ static SQInteger actorStand(HSQUIRRELVM v) {
 // actorStopWalking(currentActor)
 // actorStopWalking(postalworker)
 static SQInteger actorStopWalking(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	actor->stopWalking();
@@ -545,7 +545,7 @@ static SQInteger actorStopWalking(HSQUIRRELVM v) {
 
 // Set the text color of the specified actor's text that appears when they speak.
 static SQInteger actorTalkColors(HSQUIRRELVM v) {
-	Object *actor = sqobj(v, 2);
+	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int color;
@@ -562,7 +562,7 @@ static SQInteger actorTalkColors(HSQUIRRELVM v) {
 // actorTalking()
 // actorTalking(vo)
 static SQInteger actorTalking(HSQUIRRELVM v) {
-	Object *actor = nullptr;
+	Common::SharedPtr<Object> actor = nullptr;
 	if (sq_gettop(v) == 2) {
 		actor = sqobj(v, 2);
 		if (!actor) {
@@ -579,7 +579,7 @@ static SQInteger actorTalking(HSQUIRRELVM v) {
 
 // Turn to the pos, dir, object or actor over 2 frames.
 static SQInteger actorTurnTo(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	if (sq_gettype(v, 3) == OT_INTEGER) {
@@ -588,17 +588,17 @@ static SQInteger actorTurnTo(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get facing");
 		actor->turn((Facing)facing);
 	} else {
-		Object *obj = sqobj(v, 3);
+		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object to face to");
-		actor->turn(obj);
+		Object::turn(actor, obj);
 	}
 	return 0;
 }
 
 // Specifies the offset that will be applied to the actor's speech text that appears on screen.
 static SQInteger actorTalkOffset(HSQUIRRELVM v) {
-	Object *actor = sqobj(v, 2);
+	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int x, y;
@@ -612,10 +612,10 @@ static SQInteger actorTalkOffset(HSQUIRRELVM v) {
 
 static SQInteger actorUsePos(HSQUIRRELVM v) {
 	Math::Vector2d usePos;
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	Object *obj = sqobj(v, 3);
+	Common::SharedPtr<Object> obj = sqobj(v, 3);
 	if (!obj)
 		usePos = Math::Vector2d();
 	else
@@ -636,7 +636,7 @@ static SQInteger actorUsePos(HSQUIRRELVM v) {
 // . code-block:: Squirrel
 // actorUseWalkboxes(coroner, NO)
 static SQInteger actorUseWalkboxes(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int useWalkboxes = 1;
@@ -647,7 +647,7 @@ static SQInteger actorUseWalkboxes(HSQUIRRELVM v) {
 }
 
 static SQInteger actorVolume(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	float volume = 0.0f;
@@ -668,7 +668,7 @@ static SQInteger actorVolume(HSQUIRRELVM v) {
 //     }
 // }
 static SQInteger actorWalkForward(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int dist;
@@ -689,7 +689,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 		dir = Math::Vector2d(dist, 0);
 		break;
 	}
-	actor->walk((Vector2i)(actor->_node->getAbsPos() + dir));
+	Object::walk(actor, (Vector2i)(actor->_node->getAbsPos() + dir));
 	return 0;
 }
 
@@ -708,7 +708,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 //}
 static SQInteger actorWalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	Object *actor = nullptr;
+	Common::SharedPtr<Object> actor = nullptr;
 	if (nArgs == 1) {
 		actor = g_engine->_actor;
 	} else if (nArgs == 2) {
@@ -723,7 +723,7 @@ static SQInteger actorWalking(HSQUIRRELVM v) {
 // The numbers are in pixel's per second.
 // The vertical movement is typically half (or more) than the horizontal movement to simulate depth in the 2D world.
 static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	int x, y;
@@ -738,15 +738,15 @@ static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
 // Tells the specified actor to walk to an x/y position or to an actor position or to an object position.
 static SQInteger actorWalkTo(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	if (nArgs == 3) {
-		Object *obj = sqobj(v, 3);
+		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get actor or object");
 		else
-			actor->walk(obj);
+			Object::walk(actor, obj);
 	} else if ((nArgs == 4) || (nArgs == 5)) {
 		int x, y;
 		if (SQ_FAILED(sqget(v, 3, x)))
@@ -760,7 +760,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 				return sq_throwerror(v, "failed to get dir");
 			facing = (Facing *)&dir;
 		}
-		actor->walk(Vector2i(x, y), facing ? *facing : 0);
+		Object::walk(actor, Vector2i(x, y), facing ? *facing : 0);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
@@ -771,7 +771,7 @@ static SQInteger addSelectableActor(HSQUIRRELVM v) {
 	int slot;
 	if (SQ_FAILED(sqget(v, 2, slot)))
 		return sq_throwerror(v, "failed to get slot");
-	Object *actor = sqactor(v, 3);
+	Common::SharedPtr<Object> actor = sqactor(v, 3);
 	g_engine->_hud._actorSlots[slot - 1].actor = actor;
 	return 0;
 }
@@ -784,7 +784,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get a table");
 
 	HSQUIRRELVM vm = g_engine->getVm();
-	Object *actor = Object::createActor();
+	Common::SharedPtr<Object> actor = Object::createActor();
 	sq_resetobject(&actor->_table);
 	sq_getstackobj(v, 2, &actor->_table);
 	sq_addref(vm, &actor->_table);
@@ -796,7 +796,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 
 	debugC(kDebugActScript, "Create actor %s %d", key.c_str(), actor->getId());
 	actor->_node = new ActorNode(actor);
-	actor->_nodeAnim = new Anim(actor);
+	actor->_nodeAnim = new Anim(actor.get());
 	actor->_node->addChild(actor->_nodeAnim);
 	g_engine->_actors.push_back(actor);
 
@@ -824,7 +824,7 @@ private:
 };
 
 static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
-	Object *obj;
+	Common::SharedPtr<Object> obj;
 	int index;
 	Common::StringArray texts;
 	if (sq_gettype(v, 2) == OT_TABLE) {
@@ -851,7 +851,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 		}
 	}
 	debugC(kDebugActScript, "sayline: %s, %s", obj->_key.c_str(), join(texts, "|").c_str());
-	obj->say(texts, obj->_talkColor);
+	Object::say(obj, texts, obj->_talkColor);
 	return 0;
 }
 
@@ -891,7 +891,7 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 6, text)))
 			return sq_throwerror(v, "failed to get text");
 	} else {
-		Object *actor = sqactor(v, 4);
+		Common::SharedPtr<Object> actor = sqactor(v, 4);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
 		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getAbsPos());
@@ -908,7 +908,7 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 
 // returns true if the specified actor is currently in the screen.
 static SQInteger isActorOnScreen(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor/object");
 
@@ -924,7 +924,7 @@ static SQInteger isActorOnScreen(HSQUIRRELVM v) {
 }
 
 static SQInteger isActorSelectable(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	ActorSlot *slot = g_engine->_hud.actorSlot(actor);
@@ -935,7 +935,7 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 
 // If an actor is specified, returns true otherwise returns false.
 static SQInteger is_actor(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	sqpush(v, actor != nullptr);
 	return 1;
 }
@@ -945,7 +945,7 @@ static SQInteger is_actor(HSQUIRRELVM v) {
 static SQInteger masterActorArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		sqpush(v, actor->_table);
 		sq_arrayappend(v, -2);
 	}
@@ -970,7 +970,7 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 		if (sq_gettype(v, 2) == OT_INTEGER) {
 			g_engine->stopTalking();
 		} else {
-			Object *actor = sqobj(v, 2);
+			Common::SharedPtr<Object> actor = sqobj(v, 2);
 			if (!actor)
 				return sq_throwerror(v, "failed to get actor/object");
 			actor->stopTalking();
@@ -997,12 +997,12 @@ static SQInteger selectActor(HSQUIRRELVM v) {
 // if (stepsArray.len()) {    // someone's on the steps
 // }
 static SQInteger triggerActors(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sq_newarray(v, 0);
 	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
-		Object *actor = *it;
+		Common::SharedPtr<Object> actor = *it;
 		if (obj->contains(actor->_node->getPos())) {
 			sq_pushobject(v, actor->_table);
 			sq_arrayappend(v, -2);
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index f28ebee81c9..c39ce0f97fc 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -160,7 +160,7 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 		}
 	}
 	if (slot->objId) {
-		Object *obj = sqobj(slot->objId);
+		Common::SharedPtr<Object> obj = sqobj(slot->objId);
 		if (obj) {
 			float volObj = 0.f;
 			if (obj->_room == g_engine->_room) {
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index dbb5ce4614f..80666dae7c9 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -65,7 +65,7 @@ void Camera::panTo(Math::Vector2d target, float time, InterpolationKind interpol
 	_time = time;
 }
 
-void Camera::update(Common::SharedPtr<Room> room, Object *follow, float elapsed) {
+void Camera::update(Common::SharedPtr<Room> room, Common::SharedPtr<Object> follow, float elapsed) {
 	_room = room;
 	_elapsed += elapsed;
 	bool isMoving = _elapsed < _time;
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
index 264ea456a83..09001659490 100644
--- a/engines/twp/camera.h
+++ b/engines/twp/camera.h
@@ -105,7 +105,7 @@ public:
 	inline bool isMoving() const { return _moving; }
 	void panTo(Math::Vector2d target, float time, InterpolationKind interpolation);
 
-	void update(Common::SharedPtr<Room> room, Object *follow, float elapsed);
+	void update(Common::SharedPtr<Room> room, Common::SharedPtr<Object> follow, float elapsed);
 
 private:
 	void clamp(Math::Vector2d at);
@@ -119,7 +119,7 @@ private:
 	float _elapsed = 0.f;
 	float _time = 0.f;
 	Common::SharedPtr<Room> _room;
-	Object *_follow = nullptr;
+	Common::SharedPtr<Object> _follow = nullptr;
 	EasingFunc_t _function = {&linear};
 };
 } // namespace Twp
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 77b86b3e799..4c921592a6c 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -52,37 +52,37 @@ private:
 	Common::String _cond;
 };
 
-static Object *actor(const Common::String &name) {
+static Common::SharedPtr<Object> actor(const Common::String &name) {
 	// for (actor in gEngine.actors) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		if (actor->_key == name)
 			return actor;
 	}
 	return nullptr;
 }
 
-static Object *actorOrCurrent(const Common::String &name) {
-	Object *result = actor(name);
+static Common::SharedPtr<Object> actorOrCurrent(const Common::String &name) {
+	Common::SharedPtr<Object> result = actor(name);
 	if (!result)
 		result = g_engine->_actor;
 	return result;
 }
 
 Color EngineDialogTarget::actorColor(const Common::String &actor) {
-	Object *act = actorOrCurrent(actor);
+	Common::SharedPtr<Object> act = actorOrCurrent(actor);
 	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogNormal;
 }
 
 Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
-	Object *act = actorOrCurrent(actor);
+	Common::SharedPtr<Object> act = actorOrCurrent(actor);
 	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
 }
 
 Motor *EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
 	debugC(kDebugDialog, "say %s: %s", actor.c_str(), text.c_str());
-	Object *act = actorOrCurrent(actor);
-	act->say({text}, act->_talkColor);
+	Common::SharedPtr<Object> act = actorOrCurrent(actor);
+	Object::say(act, {text}, act->_talkColor);
 	return act->getTalking();
 }
 
@@ -100,10 +100,10 @@ Motor *EngineDialogTarget::pause(float time) {
 
 bool EngineDialogTarget::execCond(const Common::String &cond) {
 	// check if the condition corresponds to an actor name
-	Object *act = actor(cond);
+	Common::SharedPtr<Object> act = actor(cond);
 	if (act) {
 		// yes, so we check if the current actor is the given actor name
-		Object *curActor = g_engine->_actor;
+		Common::SharedPtr<Object> curActor = g_engine->_actor;
 		return curActor && curActor->_key == act->_key;
 	}
 
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index aee560426a5..60a5b2bed37 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -123,7 +123,7 @@ static SQInteger cameraAt(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get y");
 		pos = Math::Vector2d(x, y);
 	} else if (numArgs == 2) {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot or actor");
 		g_engine->follow(nullptr);
@@ -153,7 +153,7 @@ static SQInteger cameraBounds(HSQUIRRELVM v) {
 }
 
 static SQInteger cameraFollow(HSQUIRRELVM v) {
-	Object *actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	g_engine->follow(actor);
 	Math::Vector2d pos = actor->_node->getPos();
 	Common::SharedPtr<Room> oldRoom = g_engine->_room;
@@ -182,7 +182,7 @@ static SQInteger cameraInRoom(HSQUIRRELVM v) {
 	if (room) {
 		g_engine->setRoom(room);
 	} else {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj || !obj->_room) {
 			return sq_throwerror(v, "failed to get room");
 		}
@@ -206,7 +206,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 	float duration = 0.f;
 	InterpolationKind interpolation = IK_LINEAR;
 	if (numArgs == 3) {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object/actor");
 		pos = obj->getUsePos();
@@ -225,7 +225,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 			pos = Math::Vector2d(x, g_engine->getGfx().cameraPos().getY());
 			interpolation = (InterpolationKind)im;
 		} else {
-			Object *obj = sqobj(v, 2);
+			Common::SharedPtr<Object> obj = sqobj(v, 2);
 			if (SQ_FAILED(sqget(v, 3, duration)))
 				return sq_throwerror(v, "failed to get duration");
 			int im;
@@ -298,10 +298,10 @@ static SQInteger distance(HSQUIRRELVM v) {
 		return 1;
 	}
 
-	Object *obj1 = sqobj(v, 2);
+	Common::SharedPtr<Object> obj1 = sqobj(v, 2);
 	if (!obj1)
 		return sq_throwerror(v, "failed to get object1 or actor1");
-	Object *obj2 = sqobj(v, 3);
+	Common::SharedPtr<Object> obj2 = sqobj(v, 3);
 	if (!obj2)
 		return sq_throwerror(v, "failed to get object2 or actor2");
 	Math::Vector2d d = obj1->_node->getAbsPos() - obj2->_node->getAbsPos();
@@ -330,7 +330,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		}
 		return sq_throwerror(v, "failed to find verb");
 	}
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	if (obj->inInventory()) {
@@ -548,8 +548,8 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 		return 0;
 	}
 
-	Object *obj1 = nullptr;
-	Object *obj2 = nullptr;
+	Common::SharedPtr<Object> obj1 = nullptr;
+	Common::SharedPtr<Object> obj2 = nullptr;
 	if (nArgs >= 3) {
 		obj1 = sqobj(v, 3);
 		if (!obj1)
@@ -742,13 +742,13 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 		for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 			Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
-				Object *obj = layer->_objects[j];
+				Common::SharedPtr<Object> obj = layer->_objects[j];
 				obj->_exec.enabled = false;
 			}
 		}
 		break;
 	case 2: {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		obj->_exec.enabled = false;
 		break;
 	}
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 3bb3ccfdd17..8f6b87026aa 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -122,7 +122,7 @@ void Hud::init() {
 	_shader.init();
 }
 
-ActorSlot *Hud::actorSlot(Object *actor) {
+ActorSlot *Hud::actorSlot(Common::SharedPtr<Object> actor) {
 	for (int i = 0; i < NUMACTORS; i++) {
 		ActorSlot *slot = &_actorSlots[i];
 		if (slot->actor == actor) {
@@ -187,7 +187,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	_over = isOver;
 }
 
-void Hud::update(float elapsed, Math::Vector2d pos, Object *hotspot, bool mouseClick) {
+void Hud::update(float elapsed, Math::Vector2d pos, Common::SharedPtr<Object> hotspot, bool mouseClick) {
 	_mousePos = Math::Vector2d(pos.getX(), SCREEN_HEIGHT - pos.getY());
 	_defaultVerbId = !hotspot ? 0 : hotspot->defaultVerbId();
 	_mouseClick = mouseClick;
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 1eb97bf688d..a8888a9165a 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -66,7 +66,7 @@ public:
 	VerbUiColors verbUiColors;
 	Verb verbs[MAX_VERBS];
 	bool selectable = false;
-	Object *actor = nullptr;
+	Common::SharedPtr<Object> actor = nullptr;
 
 public:
 	ActorSlot();
@@ -108,9 +108,9 @@ public:
 	Hud();
 
 	void init();
-	ActorSlot *actorSlot(Object *actor);
+	ActorSlot *actorSlot(Common::SharedPtr<Object> actor);
 	bool isOver() const { return _over; }
-	void update(float elapsed, Math::Vector2d pos, Object *hotspot, bool mouseClick);
+	void update(float elapsed, Math::Vector2d pos, Common::SharedPtr<Object> hotspot, bool mouseClick);
 
 	void setVisible(bool visible) override;
 
@@ -120,7 +120,7 @@ private:
 
 public:
 	ActorSlot _actorSlots[NUMACTORS];
-	Object *_actor = nullptr;
+	Common::SharedPtr<Object> _actor = nullptr;
 	VerbRect _verbRects[NUMVERBS];
 	Verb _verb;
 	HudShader _shader;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index c83f0938140..6cc31b7af65 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -32,7 +32,7 @@ namespace Twp {
 
 OffsetTo::~OffsetTo() {}
 
-OffsetTo::OffsetTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im)
+OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getOffset(), pos, duration, im) {
 }
@@ -46,7 +46,7 @@ void OffsetTo::update(float elapsed) {
 
 MoveTo::~MoveTo() {}
 
-MoveTo::MoveTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im)
+MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getPos(), pos, duration, im) {
 }
@@ -60,7 +60,7 @@ void MoveTo::update(float elapsed) {
 
 AlphaTo::~AlphaTo() {}
 
-AlphaTo::AlphaTo(float duration, Object *obj, float to, InterpolationMethod im)
+AlphaTo::AlphaTo(float duration, Common::SharedPtr<Object> obj, float to, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getAlpha(), to, duration, im) {
 }
@@ -144,7 +144,7 @@ void OverlayTo::update(float elapsed) {
 		disable();
 }
 
-ReachAnim::ReachAnim(Object *actor, Object *obj)
+ReachAnim::ReachAnim(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj)
 	: _actor(actor), _obj(obj) {
 }
 
@@ -169,7 +169,7 @@ void ReachAnim::update(float elapsed) {
 		break;
 	case 2:
 		_actor->stand();
-		_actor->execVerb();
+		Object::execVerb(_actor);
 		disable();
 		_state = 3;
 		break;
@@ -178,7 +178,7 @@ void ReachAnim::update(float elapsed) {
 	}
 }
 
-WalkTo::WalkTo(Object *obj, Vector2i dest, int facing)
+WalkTo::WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing)
 	: _obj(obj), _facing(facing) {
 	if (obj->_useWalkboxes) {
 		_path = obj->_room->calculatePath((Vector2i)obj->_node->getAbsPos(), dest);
@@ -226,8 +226,8 @@ void WalkTo::actorArrived() {
 	// we need to execute a sentence when arrived ?
 	if (_obj->_exec.enabled) {
 		VerbId verb = _obj->_exec.verb;
-		Object *noun1 = _obj->_exec.noun1;
-		Object *noun2 = _obj->_exec.noun2;
+		Common::SharedPtr<Object> noun1 = _obj->_exec.noun1;
+		Common::SharedPtr<Object> noun2 = _obj->_exec.noun2;
 		// call `postWalk`callback
 		Common::String funcName = isActor(noun1->getId()) ? "actorPostWalk" : "objectPostWalk";
 		if (sqrawexists(_obj->_table, funcName)) {
@@ -243,7 +243,7 @@ void WalkTo::actorArrived() {
 		if (needsReach)
 			_obj->setReach(new ReachAnim(_obj, noun1));
 		else
-			_obj->execVerb();
+			Object::execVerb(_obj);
 	}
 }
 
@@ -282,7 +282,7 @@ void WalkTo::update(float elapsed) {
 	}
 }
 
-Talking::Talking(Object *obj, const Common::StringArray &texts, Color color) {
+Talking::Talking(Common::SharedPtr<Object> obj, const Common::StringArray &texts, Color color) {
 	_obj = obj;
 	_color = color;
 	_texts.assign(texts.begin() + 1, texts.end());
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 02dfd77f222..9c44d033e1f 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -95,39 +95,39 @@ class Object;
 class OffsetTo : public Motor {
 public:
 	virtual ~OffsetTo();
-	OffsetTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im);
+	OffsetTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	Tween<Math::Vector2d> _tween;
 };
 
 class MoveTo : public Motor {
 public:
 	virtual ~MoveTo();
-	MoveTo(float duration, Object *obj, Math::Vector2d pos, InterpolationMethod im);
+	MoveTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	Tween<Math::Vector2d> _tween;
 };
 
 class AlphaTo : public Motor {
 public:
 	virtual ~AlphaTo();
-	AlphaTo(float duration, Object *obj, float to, InterpolationMethod im);
+	AlphaTo(float duration, Common::SharedPtr<Object> obj, float to, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	Tween<float> _tween;
 };
 
@@ -202,7 +202,7 @@ private:
 class ReachAnim : public Motor {
 public:
 	virtual ~ReachAnim();
-	ReachAnim(Object *actor, Object *obj);
+	ReachAnim(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
 	virtual void update(float elasped) override;
 
@@ -210,15 +210,15 @@ private:
 	void playReachAnim();
 
 private:
-	Object *_actor = nullptr;
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _actor = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	int _state = 0;
 	float _elapsed = 0.f;
 };
 
 class WalkTo : public Motor {
 public:
-	WalkTo(Object *obj, Vector2i dest, int facing = 0);
+	WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing = 0);
 	virtual void disable() override;
 
 	const Common::Array<Vector2i> &getPath() const { return _path; }
@@ -228,7 +228,7 @@ private:
 	virtual void update(float elapsed) override;
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	Common::Array<Vector2i> _path;
 	int _facing = 0;
 	float _wsd;
@@ -237,7 +237,7 @@ private:
 // Creates a talking animation for a specified object.
 class Talking : public Motor {
 public:
-	Talking(Object *obj, const Common::StringArray &texts, Color color);
+	Talking(Common::SharedPtr<Object> obj, const Common::StringArray &texts, Color color);
 	virtual ~Talking() {}
 
 	void append(const Common::StringArray &texts);
@@ -253,7 +253,7 @@ private:
 	int loadActorSpeech(const Common::String &name);
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	Node *_node = nullptr;
 	Lip _lip;
 	float _elapsed = 0.f;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 50cafc0d024..57c95883ed5 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -40,7 +40,7 @@ enum class BlinkState {
 
 class Blink : public Motor {
 public:
-	Blink(Object *obj, float min, float max) : _obj(obj), _min(min), _max(max) {
+	Blink(Common::SharedPtr<Object> obj, float min, float max) : _obj(obj), _min(min), _max(max) {
 		_obj->showLayer("blink", false);
 		_state = BlinkState::Closed;
 		_duration = g_engine->getRandom(min, max);
@@ -68,7 +68,7 @@ public:
 	}
 
 private:
-	Object *_obj = nullptr;
+	Common::SharedPtr<Object> _obj = nullptr;
 	BlinkState _state = BlinkState::Closed;
 	float _min = 0.f;
 	float _max = 0.f;
@@ -89,13 +89,19 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 }
 
 Object::~Object() {
-	_layer->_objects.erase(Common::find(_layer->_objects.begin(), _layer->_objects.end(), this));
+	if (_layer) {
+		size_t i = find(_layer->_objects, this);
+		if (i != (size_t)-1) {
+			_layer->_objects.remove_at(i);
+		}
+	}
+
 	_nodeAnim->remove();
 	_node->remove();
 }
 
-Object *Object::createActor() {
-	Object *result = new Object();
+Common::SharedPtr<Object> Object::createActor() {
+	Common::SharedPtr<Object> result(new Object());
 	result->_hotspot = Common::Rect(-18, 0, 37, 71);
 	result->_facing = FACE_FRONT;
 	result->_useWalkboxes = true;
@@ -357,35 +363,35 @@ int Object::getFlags() {
 	return result;
 }
 
-void Object::setRoom(Common::SharedPtr<Room> room) {
-	if ((_room != room) || !_node->getParent()) {
-		if (_room != room) {
-			stopObjectMotors();
+void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> room) {
+	if ((object->_room != room) || !object->_node->getParent()) {
+		if (object->_room != room) {
+			object->stopObjectMotors();
 		}
-		Common::SharedPtr<Room> oldRoom = _room;
-		if (oldRoom && _node->getParent()) {
-			debugC(kDebugGame, "Remove %s from room %s", _key.c_str(), oldRoom->_name.c_str());
-			Common::SharedPtr<Layer>layer = oldRoom->layer(0);
+		Common::SharedPtr<Room> oldRoom = object->_room;
+		if (oldRoom && object->_node->getParent()) {
+			debugC(kDebugGame, "Remove %s from room %s", object->_key.c_str(), oldRoom->_name.c_str());
+			Common::SharedPtr<Layer> layer = oldRoom->layer(0);
 			if (layer) {
-				int index = find(layer->_objects, this);
+				int index = find(layer->_objects, object);
 				if (index != -1)
 					layer->_objects.remove_at(index);
 				if (layer)
-					layer->_node->removeChild(_node);
+					layer->_node->removeChild(object->_node);
 			}
 		}
 		if (room && room->layer(0) && room->layer(0)->_node) {
-			debugC(kDebugGame, "Add %s in room %s", _key.c_str(), room->_name.c_str());
+			debugC(kDebugGame, "Add %s in room %s", object->_key.c_str(), room->_name.c_str());
 			Common::SharedPtr<Layer> layer = room->layer(0);
 			if (layer) {
-				int index = find(layer->_objects, this);
+				int index = find(layer->_objects, object);
 				if (index == -1)
-					layer->_objects.push_back(this);
-				layer->_node->addChild(_node);
+					layer->_objects.push_back(object);
+				layer->_node->addChild(object->_node);
 			}
 		}
-		if (_room != room) {
-			_room = room;
+		if (object->_room != room) {
+			object->_room = room;
 		}
 	}
 }
@@ -438,7 +444,7 @@ bool Object::contains(Math::Vector2d pos) {
 	return _hotspot.contains(p.getX(), p.getY());
 }
 
-void Object::dependentOn(Object *dependentObj, int state) {
+void Object::dependentOn(Common::SharedPtr<Object> dependentObj, int state) {
 	_dependentState = state;
 	_dependentObj = dependentObj;
 }
@@ -480,11 +486,11 @@ void Object::setAnimationNames(const Common::String &head, const Common::String
 		play(getAnimName(WALK_ANIMNAME), true);
 }
 
-void Object::blinkRate(float min, float max) {
+void Object::blinkRate(Common::SharedPtr<Object> obj, float min, float max) {
 	if (min == 0.0 && max == 0.0) {
-		_blink.reset();
+		obj->_blink.reset();
 	} else {
-		_blink.reset(new Blink(this, min, max));
+		obj->_blink.reset(new Blink(obj, min, max));
 	}
 }
 
@@ -574,14 +580,14 @@ void Object::update(float elapsedSec) {
 	}
 }
 
-void Object::pickupObject(Object *obj) {
-	obj->_owner = this;
-	_inventory.push_back(obj);
+void Object::pickupObject(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
+	obj->_owner.reset(actor);
+	actor->_inventory.push_back(obj);
 
-	sqcall("onPickup", obj->_table, _table);
+	sqcall("onPickup", obj->_table, actor->_table);
 
 	if (sqrawexists(obj->_table, "onPickUp")) {
-		sqcall(obj->_table, "onPickUp", _table);
+		sqcall(obj->_table, "onPickUp", actor->_table);
 	}
 }
 
@@ -592,12 +598,12 @@ void Object::stopTalking() {
 	}
 }
 
-void Object::say(const Common::StringArray &texts, Color color) {
+void Object::say(Common::SharedPtr<Object> obj, const Common::StringArray &texts, Color color) {
 	if (texts.size() == 0)
 		return;
-	_talkingState._obj = this;
-	_talkingState._color = color;
-	_talkingState.say(texts, this);
+	obj->_talkingState._obj.reset(obj);
+	obj->_talkingState._color = color;
+	obj->_talkingState.say(texts, obj);
 }
 
 void Object::resetLockFacing() {
@@ -639,7 +645,7 @@ float Object::getScale() {
 	return 4.f;
 }
 
-void Object::removeInventory(Object *obj) {
+void Object::removeInventory(Common::SharedPtr<Object> obj) {
 	int i = find(_inventory, obj);
 	if (i >= 0) {
 		_inventory.remove_at(i);
@@ -647,11 +653,6 @@ void Object::removeInventory(Object *obj) {
 	}
 }
 
-void Object::removeInventory() {
-	if (_owner)
-		_owner->removeInventory(this);
-}
-
 Common::String Object::getReachAnim() {
 	int flags = getFlags();
 	if (flags & REACH_LOW)
@@ -666,7 +667,7 @@ static bool verbNotClose(VerbId id) {
 	return id.id == VERB_LOOKAT;
 }
 
-static void cantReach(Object *self, Object *noun2) {
+static void cantReach(Common::SharedPtr<Object> self, Common::SharedPtr<Object> noun2) {
 	if (sqrawexists(self->_table, "verbCantReach")) {
 		int nParams = sqparamCount(g_engine->getVm(), self->_table, "verbCantReach");
 		debugC(kDebugGame, "verbCantReach found in obj '%s' with %d params", self->_key.c_str(), nParams);
@@ -688,22 +689,22 @@ static void cantReach(Object *self, Object *noun2) {
 	}
 }
 
-void Object::execVerb() {
-	if (_exec.enabled) {
-		VerbId verb = _exec.verb;
-		Object *noun1 = _exec.noun1;
-		Object *noun2 = _exec.noun2;
+void Object::execVerb(Common::SharedPtr<Object> obj) {
+	if (obj->_exec.enabled) {
+		VerbId verb = obj->_exec.verb;
+		Common::SharedPtr<Object> noun1 = obj->_exec.noun1;
+		Common::SharedPtr<Object> noun2 = obj->_exec.noun2;
 
 		debugC(kDebugGame, "actorArrived: exec sentence");
 		if (!noun1->inInventory()) {
 			// Object became untouchable as we were walking there
 			if (!noun1->isTouchable()) {
 				debugC(kDebugGame, "actorArrived: noun1 untouchable");
-				_exec.enabled = false;
+				obj->_exec.enabled = false;
 				return;
 			}
 			// Did we get close enough?
-			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun1->getUsePos());
+			float dist = distance((Vector2i)obj->getUsePos(), (Vector2i)noun1->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debugC(kDebugGame, "actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
@@ -711,17 +712,17 @@ void Object::execVerb() {
 				return;
 			}
 			if (noun1->_useDir != dNone) {
-				setFacing((Facing)noun1->_useDir);
+				obj->setFacing((Facing)noun1->_useDir);
 			}
 		}
 		if (noun2 && !noun2->inInventory()) {
 			if (!noun2->isTouchable()) {
 				// Object became untouchable as we were walking there.
 				debugC(kDebugGame, "actorArrived: noun2 untouchable");
-				_exec.enabled = false;
+				obj->_exec.enabled = false;
 				return;
 			}
-			float dist = distance((Vector2i)getUsePos(), (Vector2i)noun2->getUsePos());
+			float dist = distance((Vector2i)obj->getUsePos(), (Vector2i)noun2->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debugC(kDebugGame, "actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
 			if (dist > min_dist) {
@@ -731,34 +732,34 @@ void Object::execVerb() {
 		}
 
 		debugC(kDebugGame, "actorArrived: callVerb");
-		_exec.enabled = false;
-		g_engine->callVerb(this, verb, noun1, noun2);
+		obj->_exec.enabled = false;
+		g_engine->callVerb(obj, verb, noun1, noun2);
 	}
 }
 
 // Walks an actor to the `pos` or actor `obj` and then faces `dir`.
-void Object::walk(Vector2i pos, int facing) {
-	debugC(kDebugGame, "walk to obj %s: %d,%d, %d", _key.c_str(), pos.x, pos.y, facing);
-	if (!_walkTo || (!_walkTo->isEnabled())) {
-		play(getAnimName(WALK_ANIMNAME), true);
+void Object::walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing) {
+	debugC(kDebugGame, "walk to obj %s: %d,%d, %d", obj->_key.c_str(), pos.x, pos.y, facing);
+	if (!obj->_walkTo || (!obj->_walkTo->isEnabled())) {
+		obj->play(obj->getAnimName(WALK_ANIMNAME), true);
 	}
-	_walkTo = new WalkTo(this, pos, facing);
+	obj->_walkTo = new WalkTo(obj, pos, facing);
 }
 
 // Walks an actor to the `obj` and then faces it.
-void Object::walk(Object *obj) {
+void Object::walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	debugC(kDebugGame, "walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
 	Facing facing = (Facing)obj->_useDir;
-	walk((Vector2i)obj->getUsePos(), facing);
+	walk(actor, (Vector2i)obj->getUsePos(), facing);
 }
 
 void Object::turn(Facing facing) {
 	setFacing(facing);
 }
 
-void Object::turn(Object *obj) {
-	Facing facing = getFacingToFaceTo(this, obj);
-	setFacing(facing);
+void Object::turn(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
+	Facing facing = getFacingToFaceTo(actor, obj);
+	actor->setFacing(facing);
 }
 
 void Object::jiggle(float amount) {
@@ -776,7 +777,7 @@ void Object::inventoryScrollDown() {
 	_inventoryOffset = clamp(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
 }
 
-void TalkingState::say(const Common::StringArray &texts, Object *obj) {
+void TalkingState::say(const Common::StringArray &texts, Common::SharedPtr<Object> obj) {
 	Talking *talking = static_cast<Talking *>(obj->getTalking());
 	if (!talking) {
 		obj->setTalking(new Talking(obj, texts, _color));
diff --git a/engines/twp/object.h b/engines/twp/object.h
index c9c93e9faf7..f8f59d0dad1 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -75,8 +75,8 @@ struct VerbId {
 class Object;
 struct Sentence {
 	VerbId verb;
-	Object *noun1 = nullptr;
-	Object *noun2 = nullptr;
+	Common::SharedPtr<Object> noun1 = nullptr;
+	Common::SharedPtr<Object> noun2 = nullptr;
 	bool enabled = false;
 };
 
@@ -85,12 +85,13 @@ class Room;
 class Motor;
 class Node;
 class Layer;
+class Blink;
 
 struct TalkingState {
-	Object *_obj;
+	Common::SharedPtr<Object> _obj;
 	Color _color;
 
-	void say(const Common::StringArray &texts, Object *obj);
+	void say(const Common::StringArray &texts, Common::SharedPtr<Object> obj);
 };
 
 struct LockFacing {
@@ -104,7 +105,7 @@ public:
 	Object(HSQOBJECT o, const Common::String &key);
 	~Object();
 
-	static Object *createActor();
+	static Common::SharedPtr<Object> createActor();
 
 	Common::String getName() const;
 	int getId() const;
@@ -155,17 +156,16 @@ public:
 	void setIcon(const Common::String &icon);
 	Common::String getIcon();
 	bool inInventory();
-	void removeInventory(Object *obj);
-	void removeInventory();
+	void removeInventory(Common::SharedPtr<Object> obj);
 
 	int getFlags();
 	UseFlag useFlag();
 
 	bool contains(Math::Vector2d pos);
-	void setRoom(Common::SharedPtr<Room> room);
+	static void setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> room);
 	void delObject();
 	void stopObjectMotors();
-	void dependentOn(Object *dependentObj, int state);
+	void dependentOn(Common::SharedPtr<Object> dependentObj, int state);
 
 	Common::String getAnimName(const Common::String &key);
 	void setHeadIndex(int head);
@@ -174,7 +174,7 @@ public:
 
 	bool isWalking();
 	void stopWalking();
-	void blinkRate(float min, float max);
+	static void blinkRate(Common::SharedPtr<Object>, float min, float max);
 	void setCostume(const Common::String &name, const Common::String &sheet);
 	void stand();
 
@@ -187,8 +187,8 @@ public:
 	void setReach(Motor *reach);
 	Motor *getWalkTo() const { return _walkTo; }
 	Motor *getReach() const { return _reach; }
-	void walk(Vector2i pos, int facing = 0);
-	void walk(Object* obj);
+	static void walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing = 0);
+	static void walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
 	void setTalking(Motor *talking);
 	void setBlink(Motor *blink);
@@ -197,13 +197,13 @@ public:
 
 	Motor *getTalking() { return _talking; }
 	void stopTalking();
-	void say(const Common::StringArray &texts, Color color);
+	static void say(Common::SharedPtr<Object> obj, const Common::StringArray &texts, Color color);
 
-	void pickupObject(Object *obj);
+	static void pickupObject(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
-	void execVerb();
+	static void execVerb(Common::SharedPtr<Object> obj);
 	void turn(Facing facing);
-	void turn(Object* obj);
+	static void turn(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 	void jiggle(float amount);
 
 	void inventoryScrollUp();
@@ -251,16 +251,16 @@ public:
 	Color _talkColor;
 	Common::HashMap<Common::String, Common::String> _animNames;
 	bool _lit = false;
-	Object *_owner = nullptr;
-	Common::Array<Object *> _inventory;
-    int _inventoryOffset = 0;
+	Common::SharedPtr<Object> _owner = nullptr;
+	Common::Array<Common::SharedPtr<Object> > _inventory;
+	int _inventoryOffset = 0;
 	Common::StringArray _icons;
 	int _iconFps = 0;
 	int _iconIndex = 0;
 	float _iconElapsed = 0.f;
 	HSQOBJECT _enter, _leave;
 	int _dependentState = 0;
-	Object *_dependentObj = nullptr;
+	Common::SharedPtr<Object> _dependentObj = nullptr;
 	float _popElapsed = 0.f;
 	int _popCount = 0;
 	Sentence _exec;
@@ -274,7 +274,7 @@ private:
 	Motor *_walkTo = nullptr;
 	Motor *_reach = nullptr;
 	Motor *_talking = nullptr;
-	unique_ptr<Motor> _blink;
+	Common::SharedPtr<Blink> _blink;
 	Motor *_turnTo = nullptr;
 	Motor *_shakeTo = nullptr;
 	Motor *_jiggleTo = nullptr;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 004ee721291..488248488d4 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -80,7 +80,7 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	}
 
 	debugC(kDebugObjScript, "Create object: %s, %u", sheet.c_str(), frames.size());
-	Object *obj = g_engine->_room->createObject(sheet, frames);
+	Common::SharedPtr<Object> obj = g_engine->_room->createObject(sheet, frames);
 	obj->_room = g_engine->_room;
 	sq_pushobject(v, obj->_table);
 
@@ -135,7 +135,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 		}
 	}
 	debugC(kDebugObjScript, "Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
-	Object *obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
+	Common::SharedPtr<Object> obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
 	sqpush(v, obj->_table);
 	return 1;
 }
@@ -151,9 +151,10 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 // playObjectSound(randomfrom(soundDrip1, soundDrip2, soundDrip3), radioStudioBucket)
 // deleteObject(drip)
 static SQInteger deleteObject(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		delete obj;
+		size_t i = find(obj->_layer->_objects, obj);
+		obj->_layer->_objects.remove_at(i);
 	}
 	return 0;
 }
@@ -175,7 +176,7 @@ static SQInteger findObjectAt(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 3, y)))
 		return sq_throwerror(v, "failed to get y");
-	Object *obj = g_engine->objAt(Math::Vector2d(x, y));
+	Common::SharedPtr<Object> obj = g_engine->objAt(Math::Vector2d(x, y));
 	if (!obj)
 		sq_pushnull(v);
 	else
@@ -184,7 +185,7 @@ static SQInteger findObjectAt(HSQUIRRELVM v) {
 }
 
 static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (!obj->_owner || (obj->_owner != g_engine->_actor)) {
@@ -205,13 +206,13 @@ static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // if (isObject(obj) && objectValidUsePos(obj) && objectTouchable(obj)) {
 static SQInteger isObject(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	sqpush(v, obj && isObject(obj->getId()));
 	return 1;
 }
 
 static SQInteger jiggleInventory(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
     if (!obj) {
       return sq_throwerror(v, "failed to get object");
     }
@@ -231,7 +232,7 @@ static SQInteger jiggleInventory(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // jiggleObject(pigeonVan, 0.25)
 static SQInteger jiggleObject(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	float amount;
@@ -247,7 +248,7 @@ static SQInteger jiggleObject(HSQUIRRELVM v) {
 // loopObjectState(aStreetFire, 0)
 // loopObjectState(flies, 3)
 static SQInteger loopObjectState(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (sq_gettype(v, 3) == OT_INTEGER) {
@@ -272,7 +273,7 @@ static SQInteger loopObjectState(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectAlpha(cloud, 0.5)
 static SQInteger objectAlpha(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		float alpha = 0.0f;
 		if (SQ_FAILED(sq_getfloat(v, 3, &alpha)))
@@ -289,7 +290,7 @@ static SQInteger objectAlpha(HSQUIRRELVM v) {
 // See also stopObjectMotors.
 static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 	if (sq_gettype(v, 2) != OT_NULL) {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 		float alpha = 0.0f;
@@ -313,11 +314,11 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 // objectAt(text, 160,90)
 // objectAt(obj, leftMargin, topLinePos)
 static SQInteger objectAt(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (sq_gettop(v) == 3) {
-		Object *spot = sqobj(v, 3);
+		Common::SharedPtr<Object> spot = sqobj(v, 3);
 		if (!spot)
 			return sq_throwerror(v, "failed to get spot");
 		obj->_node->setPos(spot->getUsePos());
@@ -342,7 +343,7 @@ static SQInteger objectBumperCycle(HSQUIRRELVM v) {
 }
 
 static SQInteger objectCenter(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	Math::Vector2d pos = obj->_node->getPos() + obj->_usePos;
@@ -355,7 +356,7 @@ static SQInteger objectCenter(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectColor(warningSign, 0x808000)
 static SQInteger objectColor(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		int color = 0;
 		if (SQ_FAILED(sqget(v, 3, color)))
@@ -366,10 +367,10 @@ static SQInteger objectColor(HSQUIRRELVM v) {
 }
 
 static SQInteger objectDependentOn(HSQUIRRELVM v) {
-	Object *child = sqobj(v, 2);
+	Common::SharedPtr<Object> child = sqobj(v, 2);
 	if (!child)
 		return sq_throwerror(v, "failed to get child object");
-	Object *parent = sqobj(v, 3);
+	Common::SharedPtr<Object> parent = sqobj(v, 3);
 	if (!parent)
 		return sq_throwerror(v, "failed to get parent object");
 	int state = 0;
@@ -384,7 +385,7 @@ static SQInteger objectDependentOn(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectFPS(pigeon1, 15)
 static SQInteger objectFPS(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		float fps = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, fps)))
@@ -399,7 +400,7 @@ static SQInteger objectFPS(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectHidden(oldRags, YES)
 static SQInteger objectHidden(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		int hidden = 0;
 		sqget(v, 3, hidden);
@@ -417,7 +418,7 @@ static SQInteger objectHidden(HSQUIRRELVM v) {
 // objectHotspot(willie, 14, 0, 14, 62)         // Willie standing up
 // objectHotspot(willie, -28, 0, 28, 50)        // Willie lying down drunk
 static SQInteger objectHotspot(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	if (sq_gettop(v) == 2) {
@@ -449,7 +450,7 @@ static SQInteger objectHotspot(HSQUIRRELVM v) {
 // objectIcon(obj, "glowing_spell_book")
 // objectIcon(obj, "spell_book")
 static SQInteger objectIcon(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	switch (sq_gettype(v, 3)) {
@@ -487,7 +488,7 @@ static SQInteger objectIcon(HSQUIRRELVM v) {
 // Note: this is currently used for actor objects, but can also be used for room objects.
 // Lighting background flat art would be hard and probably look odd.
 static SQInteger objectLit(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	bool lit = false;
@@ -510,7 +511,7 @@ static SQInteger objectLit(HSQUIRRELVM v) {
 // - `stopObjectMotors method <#stopObjectMotors.e>`_
 // - `objectOffsetTo method <#objectOffsetTo.e>`_
 static SQInteger objectMoveTo(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		int x = 0;
 		int y = 0;
@@ -536,7 +537,7 @@ static SQInteger objectMoveTo(HSQUIRRELVM v) {
 // objectOffset(coroner, 0, 0)
 // objectOffset(SewerManhole.sewerManholeDime, 0, 96)
 static SQInteger objectOffset(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		int x = 0;
 		int y = 0;
@@ -551,7 +552,7 @@ static SQInteger objectOffset(HSQUIRRELVM v) {
 }
 
 static SQInteger objectOffsetTo(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		int x = 0;
 		int y = 0;
@@ -579,7 +580,7 @@ static SQInteger objectOffsetTo(HSQUIRRELVM v) {
 // objectOwner(dime) == currentActor
 // !objectOwner(countyMap1)
 static SQInteger objectOwner(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (!obj->_owner)
@@ -591,7 +592,7 @@ static SQInteger objectOwner(HSQUIRRELVM v) {
 
 // Changes the object's layer.
 static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	int layer = 0;
@@ -602,10 +603,10 @@ static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
 }
 
 static SQInteger objectParent(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get child");
-	Object *parent = sqobj(v, 3);
+	Common::SharedPtr<Object> parent = sqobj(v, 3);
 	if (!parent)
 		return sq_throwerror(v, "failed to get parent");
 	obj->_parent = parent->_key;
@@ -615,7 +616,7 @@ static SQInteger objectParent(HSQUIRRELVM v) {
 
 // Returns the x-coordinate of the given object or actor.
 static SQInteger objectPosX(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	float x = obj->getUsePos().getX() + obj->_hotspot.left + obj->_hotspot.width() / 2.0f;
@@ -625,7 +626,7 @@ static SQInteger objectPosX(HSQUIRRELVM v) {
 
 // Returns the y-coordinate of the given object or actor.
 static SQInteger objectPosY(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	float y = obj->getUsePos().getY() + obj->_hotspot.top + obj->_hotspot.height() / 2.0f;
@@ -639,7 +640,7 @@ static SQInteger objectPosY(HSQUIRRELVM v) {
 // Actor's are typically adjusted so they are rendered from the middle of the bottom of their feet.
 // To maintain sanity, it is best if all actors have the same image size and are all adjust the same, but this is not a requirement.
 static SQInteger objectRenderOffset(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	SQInteger x, y;
@@ -653,7 +654,7 @@ static SQInteger objectRenderOffset(HSQUIRRELVM v) {
 
 // Returns the room of a given object or actor.
 static SQInteger objectRoom(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (!obj->_room)
@@ -668,7 +669,7 @@ static SQInteger objectRoom(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectRotate(pigeonVanBackWheel, 0)
 static SQInteger objectRotate(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		float rotation = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, rotation)))
@@ -688,7 +689,7 @@ static SQInteger objectRotate(HSQUIRRELVM v) {
 // objectRotateTo(AStreet.aStreetPhoneBook, 6, 2.0, SWING)
 // objectRotateTo(firefly, direction, 12, LOOPING)
 static SQInteger objectRotateTo(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
 		float rotation = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, rotation)))
@@ -706,7 +707,7 @@ static SQInteger objectRotateTo(HSQUIRRELVM v) {
 
 // Sets how scaled the object's image will appear on screen. 1 is no scaling.
 static SQInteger objectScale(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	float scale = 0.0f;
@@ -717,7 +718,7 @@ static SQInteger objectScale(HSQUIRRELVM v) {
 }
 
 static SQInteger objectScaleTo(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj) {
 		float scale = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, scale)))
@@ -736,7 +737,7 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 // Sets the object in the screen space.
 // It means that its position is relative to the screen, not to the room.
 static SQInteger objectScreenSpace(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	g_engine->_screenScene.addChild(obj->_node);
@@ -765,7 +766,7 @@ static SQInteger objectShader(HSQUIRRELVM v) {
 static SQInteger objectState(HSQUIRRELVM v) {
 	if (sq_gettype(v, 2) == OT_NULL)
 		return 0;
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	SQInteger nArgs = sq_gettop(v);
@@ -785,7 +786,7 @@ static SQInteger objectState(HSQUIRRELVM v) {
 
 // Gets or sets if an object is player touchable.
 static SQInteger objectTouchable(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	SQInteger nArgs = sq_gettop(v);
@@ -810,7 +811,7 @@ static SQInteger objectTouchable(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectSort(censorBox, 0)   // Will be on top of everything.
 static SQInteger objectSort(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	int zsort;
@@ -826,7 +827,7 @@ static SQInteger objectSort(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectUsePos(popcornObject, -13, 0, FACE_RIGHT)
 static SQInteger objectUsePos(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	int x, y, dir;
@@ -846,7 +847,7 @@ static SQInteger objectUsePos(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectUsePosX(dimeLoc)
 static SQInteger objectUsePosX(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sqpush(v, obj->getUsePos().getX());
@@ -858,7 +859,7 @@ static SQInteger objectUsePosX(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // objectUsePosY(dimeLoc)
 static SQInteger objectUsePosY(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sqpush(v, obj->getUsePos().getY());
@@ -867,7 +868,7 @@ static SQInteger objectUsePosY(HSQUIRRELVM v) {
 
 // Returns true if the object's use position has been set (ie is not 0,0).
 static SQInteger objectValidUsePos(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sqpush(v, obj->_usePos != Math::Vector2d());
@@ -886,7 +887,7 @@ static SQInteger objectValidUsePos(HSQUIRRELVM v) {
 //    tries = 0
 //}
 static SQInteger objectValidVerb(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	int verb;
@@ -915,7 +916,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 	//
 	// .. code-block:: Squirrel
 	// pickupObject(Dime)
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj) {
 		HSQOBJECT o;
 		sq_getstackobj(v, 2, &o);
@@ -923,7 +924,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 		sqgetf(o, "name", name);
 		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_engine->_textDb.getText(name).c_str()).c_str());
 	}
-	Object *actor = nullptr;
+	Common::SharedPtr<Object> actor = nullptr;
 	if (sq_gettop(v) >= 3) {
 		actor = sqactor(v, 3);
 		if (!actor)
@@ -931,15 +932,15 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 	}
 	if (!actor)
 		actor = g_engine->_actor;
-	actor->pickupObject(obj);
+	Object::pickupObject(actor, obj);
 	return 0;
 }
 
 static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
-	Object *obj1 = sqobj(v, 2);
+	Common::SharedPtr<Object> obj1 = sqobj(v, 2);
 	if (!obj1)
 		return sq_throwerror(v, "failed to get object 1");
-	Object *obj2 = sqobj(v, 3);
+	Common::SharedPtr<Object> obj2 = sqobj(v, 3);
 	if (!obj2)
 		return sq_throwerror(v, "failed to get object 2");
 
@@ -951,7 +952,7 @@ static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
 	}
 
 	// replace obj2 by obj1
-	Object *owner = obj2->_owner;
+	Common::SharedPtr<Object> owner = obj2->_owner;
 	int index = find(obj2->_owner->_inventory, obj2);
 	owner->_inventory[index] = obj1;
 	obj1->_owner = owner;
@@ -965,7 +966,7 @@ static SQInteger pickupReplacementObject(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // playObjectState(Mansion.windowShutters, OPEN)
 static SQInteger playObjectState(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return 0;
 	if (sq_gettype(v, 3) == OT_INTEGER) {
@@ -985,7 +986,7 @@ static SQInteger playObjectState(HSQUIRRELVM v) {
 }
 
 static SQInteger popInventory(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	int count;
@@ -998,10 +999,11 @@ static SQInteger popInventory(HSQUIRRELVM v) {
 // Removes an object from the current actor's inventory.
 // If the object is not in the current actor's inventory, the command silently fails.
 static SQInteger removeInventory(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	obj->removeInventory();
+	if (obj->_owner)
+		obj->_owner->removeInventory(obj);
 	return 0;
 }
 
@@ -1020,7 +1022,7 @@ static SQInteger setDefaultObject(HSQUIRRELVM v) {
 }
 
 static SQInteger shakeObject(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	float amount;
@@ -1031,7 +1033,7 @@ static SQInteger shakeObject(HSQUIRRELVM v) {
 }
 
 static SQInteger stopObjectMotors(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	obj->stopObjectMotors();
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 9f4a83972d7..3683f072c3a 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -157,8 +157,8 @@ Room::~Room() {
 	delete _scene;
 }
 
-Object *Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
-	Object *obj = new Object();
+Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
+	Common::SharedPtr<Object> obj(new Object());
 	obj->_temporary = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
@@ -198,8 +198,8 @@ Object *Room::createObject(const Common::String &sheet, const Common::Array<Comm
 	return obj;
 }
 
-Object *Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
-	Object *obj = new Object();
+Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth) {
+	Common::SharedPtr<Object> obj(new Object());
 	obj->_temporary = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
@@ -253,18 +253,18 @@ Object *Room::createTextObject(const Common::String &fontName, const Common::Str
 	return obj;
 }
 
-void Room::load(Common::SeekableReadStream &s) {
+void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
 	Common::JSONValue *value = d.open(&s);
 	// debugC(kDebugGame, "Room: %s", value->stringify().c_str());
 	const Common::JSONObject &jRoom = value->asObject();
 
-	_name = jRoom["name"]->asString();
-	_sheet = jRoom["sheet"]->asString();
+	room->_name = jRoom["name"]->asString();
+	room->_sheet = jRoom["sheet"]->asString();
 
-	_roomSize = parseVec2(jRoom["roomsize"]->asString());
-	_height = jRoom.contains("height") ? jRoom["height"]->asIntegerNumber() : _roomSize.getY();
-	_fullscreen = jRoom.contains("fullscreen") ? jRoom["fullscreen"]->asIntegerNumber() : 0;
+	room->_roomSize = parseVec2(jRoom["roomsize"]->asString());
+	room->_height = jRoom.contains("height") ? jRoom["height"]->asIntegerNumber() : room->_roomSize.getY();
+	room->_fullscreen = jRoom.contains("fullscreen") ? jRoom["fullscreen"]->asIntegerNumber() : 0;
 
 	// backgrounds
 	Common::StringArray backNames;
@@ -279,7 +279,7 @@ void Room::load(Common::SeekableReadStream &s) {
 
 	{
 		 Common::SharedPtr<Layer> layer(new Layer(backNames, Math::Vector2d(1, 1), 0));
-		_layers.push_back(layer);
+		room->_layers.push_back(layer);
 	}
 
 	// layers
@@ -300,7 +300,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			int zsort = jLayer["zsort"]->asIntegerNumber();
 
 			Common::SharedPtr<Layer> layer(new Layer(names, parallax, zsort));
-			_layers.push_back(layer);
+			room->_layers.push_back(layer);
 		}
 	}
 
@@ -313,7 +313,7 @@ void Room::load(Common::SeekableReadStream &s) {
 			if (jWalkbox.contains("name") && jWalkbox["name"]->isString()) {
 				walkbox._name = jWalkbox["name"]->asString();
 			}
-			_walkboxes.push_back(walkbox);
+			room->_walkboxes.push_back(walkbox);
 		}
 	}
 
@@ -322,14 +322,14 @@ void Room::load(Common::SeekableReadStream &s) {
 		const Common::JSONArray &jobjects = jRoom["objects"]->asArray();
 		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
 			const Common::JSONObject &jObject = (*it)->asObject();
-			Object *obj = new Object();
+			Common::SharedPtr<Object> obj(new Object());
 			Twp::setId(obj->_table, newObjId());
 			obj->_key = jObject["name"]->asString();
 			Node *objNode = new Node(obj->_key);
 			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_node = objNode;
-			obj->_nodeAnim = new Anim(obj);
+			obj->_nodeAnim = new Anim(obj.get());
 			obj->_node->addChild(obj->_nodeAnim);
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
@@ -341,12 +341,12 @@ void Room::load(Common::SeekableReadStream &s) {
 			obj->_objType = toObjectType(jObject);
 			if (jObject.contains("parent"))
 				obj->_parent = jObject["parent"]->asString();
-			obj->_room.reset(this);
+			obj->_room = room;
 			if (jObject.contains("animations")) {
 				parseObjectAnimations(jObject["animations"]->asArray(), obj->_anims);
 			}
-			obj->_layer = layer(0);
-			layer(0)->_objects.push_back(obj);
+			obj->_layer = room->layer(0);
+			room->layer(0)->_objects.push_back(obj);
 		}
 	}
 
@@ -354,28 +354,28 @@ void Room::load(Common::SeekableReadStream &s) {
 	if (jRoom.contains("scaling")) {
 		const Common::JSONArray &jScalings = jRoom["scaling"]->asArray();
 		if (jScalings[0]->isString()) {
-			_scalings.push_back(parseScaling(jScalings));
+			room->_scalings.push_back(parseScaling(jScalings));
 		} else {
 			for (auto it = jScalings.begin(); it != jScalings.end(); it++) {
 				const Common::JSONObject &jScaling = (*it)->asObject();
 				Scaling scaling = parseScaling(jScaling["scaling"]->asArray());
 				if (jScaling.contains("trigger") && jScaling["trigger"]->isString())
 					scaling.trigger = jScaling["trigger"]->asString();
-				_scalings.push_back(scaling);
+				room->_scalings.push_back(scaling);
 			}
 		}
-		_scaling = _scalings[0];
+		room->_scaling = room->_scalings[0];
 	}
 
-	_mergedPolygon = merge(_walkboxes);
+	room->_mergedPolygon = merge(room->_walkboxes);
 
 	// Fix room size (why ?)
 	int width = 0;
 	for (size_t i = 0; i < backNames.size(); i++) {
 		Common::String name = backNames[i];
-		width += g_engine->_resManager.spriteSheet(_sheet)->frameTable[name].sourceSize.getX();
+		width += g_engine->_resManager.spriteSheet(room->_sheet)->frameTable[name].sourceSize.getX();
 	}
-	_roomSize.setX(width);
+	room->_roomSize.setX(width);
 
 	delete value;
 }
@@ -402,11 +402,11 @@ Math::Vector2d Room::getScreenSize() {
 	}
 }
 
-Object *Room::getObj(const Common::String &key) {
+Common::SharedPtr<Object> Room::getObj(const Common::String &key) {
 	for (size_t i = 0; i < _layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = _layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
+			Common::SharedPtr<Object> obj = layer->_objects[j];
 			if (obj->_key == key)
 				return obj;
 		}
@@ -428,7 +428,7 @@ float Room::getScaling(float yPos) {
 	return _scaling.getScaling(yPos);
 }
 
-void Room::objectParallaxLayer(Object *obj, int zsort) {
+void Room::objectParallaxLayer(Common::SharedPtr<Object> obj, int zsort) {
 	Common::SharedPtr<Layer> l = layer(zsort);
 	if (obj->_layer != l) {
 		// removes object from old layer
@@ -461,7 +461,7 @@ void Room::update(float elapsed) {
 	for (size_t j = 0; j < _layers.size(); j++) {
 		Common::SharedPtr<Layer> layer = _layers[j];
 		for (size_t k = 0; k < layer->_objects.size(); k++) {
-			Object *obj = layer->_objects[k];
+			Common::SharedPtr<Object> obj = layer->_objects[k];
 			obj->update(elapsed);
 		}
 	}
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 78c30e95625..adcc2afd636 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -57,7 +57,7 @@ public:
 
 public:
 	Common::Array<Common::String> _names;
-	Common::Array<Object *> _objects;
+	Common::Array<Common::SharedPtr<Object> > _objects;
 	Math::Vector2d _parallax;
 	int _zsort = 0;
 	Node *_node = nullptr;
@@ -95,9 +95,9 @@ struct Lights {
 };
 
 struct ScalingTrigger {
-	ScalingTrigger(Object * obj, Scaling* scaling);
+	ScalingTrigger(Common::SharedPtr<Object>  obj, Scaling* scaling);
 
-	Object * _obj = nullptr;
+	Common::SharedPtr<Object>  _obj = nullptr;
 	Scaling* _scaling = nullptr;
 };
 
@@ -108,21 +108,21 @@ public:
 	Room(const Common::String &name, HSQOBJECT &table);
 	~Room();
 
-	void load(Common::SeekableReadStream &s);
+	static void load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s);
 
 	void update(float elapsedSec);
 
-	Object *createObject(const Common::String &sheet, const Common::Array<Common::String> &frames);
-	Object *createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
+	Common::SharedPtr<Object> createObject(const Common::String &sheet, const Common::Array<Common::String> &frames);
+	Common::SharedPtr<Object> createTextObject(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign = thLeft, TextVAlignment vAlign = tvCenter, float maxWidth = 0.0f);
 
 	Math::Vector2d getScreenSize();
 
 	Common::SharedPtr<Layer> layer(int zsort);
-	Object *getObj(const Common::String &key);
+	Common::SharedPtr<Object> getObj(const Common::String &key);
 
 	Light *createLight(Color color, Math::Vector2d pos);
 	float getScaling(float yPos);
-	void objectParallaxLayer(Object *obj, int zsort);
+	void objectParallaxLayer(Common::SharedPtr<Object> obj, int zsort);
 	void setOverlay(Color color);
 	Color getOverlay() const;
 
@@ -143,10 +143,10 @@ public:
 	HSQOBJECT _table;                  // Squirrel table representing this room
 	bool _entering = false;            // Indicates whether or not an actor is entering this room
 	Lights _lights;                    // Lights of the room
-	Common::Array<Object *> _triggers; // Triggers currently enabled in the room
+	Common::Array<Common::SharedPtr<Object> > _triggers; // Triggers currently enabled in the room
 	Common::Array<ScalingTrigger> _scalingTriggers; // Scaling Triggers of the room
 	bool _pseudo = false;
-	Common::Array<Object *> _objects;
+	Common::Array<Common::SharedPtr<Object> > _objects;
 	Scene *_scene = nullptr;
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index bf286b6fb59..e6f794ee615 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -31,7 +31,7 @@ namespace Twp {
 
 static SQInteger addTrigger(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sq_resetobject(&obj->_enter);
@@ -108,7 +108,7 @@ static SQInteger createLight(HSQUIRRELVM v) {
 }
 
 static SQInteger enableTrigger(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	bool enabled;
@@ -125,7 +125,7 @@ static SQInteger enableTrigger(HSQUIRRELVM v) {
 }
 
 static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	g_engine->enterRoom(obj->_room, obj);
@@ -321,14 +321,14 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 3, closure)))
 			return sq_throwerror(v, "failed to get closure");
 		for (size_t i = 0; i < g_engine->_room->_triggers.size(); i++) {
-			Object *trigger = g_engine->_room->_triggers[i];
+			Common::SharedPtr<Object> trigger = g_engine->_room->_triggers[i];
 			if ((trigger->_enter._unVal.pClosure == closure._unVal.pClosure) || (trigger->_leave._unVal.pClosure == closure._unVal.pClosure)) {
 				g_engine->_room->_triggers.remove_at(i);
 				return 0;
 			}
 		}
 	} else {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 		size_t i = find(g_engine->_room->_triggers, obj);
@@ -356,7 +356,7 @@ static SQInteger roomActors(HSQUIRRELVM v) {
 
 	sq_newarray(v, 0);
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		if (actor->_room == room) {
 			sqpush(v, actor->_table);
 			sq_arrayappend(v, -2);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index e2ca8c475cd..e10793d629d 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -31,18 +31,18 @@ namespace Twp {
 
 const static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
 
-static Object *actor(const Common::String &key) {
+static Common::SharedPtr<Object> actor(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *a = g_engine->_actors[i];
+		Common::SharedPtr<Object> a = g_engine->_actors[i];
 		if (a->_key == key)
 			return a;
 	}
 	return nullptr;
 }
 
-static Object *actor(int id) {
+static Common::SharedPtr<Object> actor(int id) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *a = g_engine->_actors[i];
+		Common::SharedPtr<Object> a = g_engine->_actors[i];
 		if (a->getId() == id)
 			return a;
 	}
@@ -104,11 +104,11 @@ static Common::SharedPtr<Room> room(const Common::String &name) {
 	return nullptr;
 }
 
-static Object *object(Common::SharedPtr<Room> room, const Common::String &key) {
+static Common::SharedPtr<Object> object(Common::SharedPtr<Room> room, const Common::String &key) {
 	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *o = layer->_objects[j];
+			Common::SharedPtr<Object> o = layer->_objects[j];
 			if (o->_key == key)
 				return o;
 		}
@@ -116,13 +116,13 @@ static Object *object(Common::SharedPtr<Room> room, const Common::String &key) {
 	return nullptr;
 }
 
-static Object *object(const Common::String &key) {
+static Common::SharedPtr<Object> object(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
 		Common::SharedPtr<Room> room = g_engine->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Common::SharedPtr<Layer> layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
-				Object *o = layer->_objects[k];
+				Common::SharedPtr<Object> o = layer->_objects[k];
 				if (o->_key == key)
 					return o;
 			}
@@ -171,7 +171,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 				if (!r)
 					warning("room with key=%s not found", roomName.c_str());
 				else {
-					Object *o = object(r, objName);
+					Common::SharedPtr<Object> o = object(r, objName);
 					if (!o)
 						warning("room object with key=%s/%s not found", roomName.c_str(), objName.c_str());
 					else
@@ -187,7 +187,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 			}
 		} else if (jObject.contains("_objectKey")) {
 			Common::String objName = jObject["_objectKey"]->asString();
-			Object *o = object(objName);
+			Common::SharedPtr<Object> o = object(objName);
 			if (!o) {
 				warning("object with key=%s not found", objName.c_str());
 			} else {
@@ -213,7 +213,7 @@ static Common::String sub(const Common::String &s, size_t pos, size_t end) {
 	return s.substr(pos, s.size() - end);
 }
 
-static void setAnimations(Object *actor, const Common::JSONArray &anims) {
+static void setAnimations(Common::SharedPtr<Object> actor, const Common::JSONArray &anims) {
 	Common::String headAnim = anims[0]->asString();
 	Common::String standAnim = sub(anims[9]->asString(), 0, 7);
 	Common::String walkAnim = sub(anims[11]->asString(), 0, 7);
@@ -221,7 +221,7 @@ static void setAnimations(Object *actor, const Common::JSONArray &anims) {
 	actor->setAnimationNames(headAnim, standAnim, walkAnim, reachAnim);
 }
 
-static void loadActor(Object *actor, const Common::JSONObject &json) {
+static void loadActor(Common::SharedPtr<Object> actor, const Common::JSONObject &json) {
 	bool touchable = true;
 	if (json.contains("_untouchable"))
 		touchable = json["_untouchable"]->asIntegerNumber() == 0;
@@ -257,7 +257,7 @@ static void loadActor(Object *actor, const Common::JSONObject &json) {
 		} else if (it->_key == "_renderOffset") {
 			actor->_node->setRenderOffset(parseVec2(it->_value->asString()));
 		} else if (it->_key == "_roomKey") {
-			actor->setRoom(room(it->_value->asString()));
+			Object::setRoom(actor, room(it->_value->asString()));
 		} else if ((it->_key == "_hidden") || (it->_key == "_untouchable")) {
 		} else if (it->_key == "_volume") {
 			actor->_volume = it->_value->asNumber();
@@ -279,7 +279,7 @@ static void loadActor(Object *actor, const Common::JSONObject &json) {
 		sqcall(actor->_table, "postLoad");
 }
 
-static void loadObject(Object *obj, const Common::JSONObject &json) {
+static void loadObject(Common::SharedPtr<Object> obj, const Common::JSONObject &json) {
 	int state = 0;
 	if (json.contains("_state"))
 		state = json["_state"]->asIntegerNumber();
@@ -314,7 +314,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 		} else if (it->_key == "_renderOffset") {
 			obj->_node->setRenderOffset(parseVec2(it->_value->asString()));
 		} else if (it->_key == "_roomKey") {
-			obj->setRoom(room(it->_value->asString()));
+			Object::setRoom(obj, room(it->_value->asString()));
 		} else if (!it->_key.hasPrefix("_")) {
 			HSQOBJECT tmp;
 			toSquirrel(it->_value, tmp);
@@ -333,7 +333,7 @@ static void loadObject(Object *obj, const Common::JSONObject &json) {
 
 static void loadPseudoObjects(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
-		Object *o = object(room, it->_key);
+		Common::SharedPtr<Object> o = object(room, it->_key);
 		if (!o)
 			warning("load: room '%s' object '%s' not loaded because it has not been found", room->_name.c_str(), it->_key.c_str());
 		else
@@ -347,7 +347,7 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 			loadPseudoObjects(room, it->_value->asObject());
 		} else {
 			if (!it->_key.hasPrefix("_")) {
-				Object *o = object(room, it->_key);
+				Common::SharedPtr<Object> o = object(room, it->_key);
 				if (!o) {
 					HSQOBJECT tmp;
 					toSquirrel(it->_value, tmp);
@@ -371,7 +371,7 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 
 static void setActor(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *a = g_engine->_actors[i];
+		Common::SharedPtr<Object> a = g_engine->_actors[i];
 		if (a->_key == key) {
 			g_engine->setActor(a, false);
 			return;
@@ -458,7 +458,7 @@ void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
 	const Common::JSONArray &jSelectableActors = json["selectableActors"]->asArray();
 	for (size_t i = 0; i < jSelectableActors.size(); i++) {
 		const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
-		Object *act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
+		Common::SharedPtr<Object> act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
 		g_engine->_hud._actorSlots[i].actor = act;
 		g_engine->_hud._actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
 	}
@@ -532,7 +532,7 @@ void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
 
 void SaveGameManager::loadActors(const Common::JSONObject &json) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *a = g_engine->_actors[i];
+		Common::SharedPtr<Object> a = g_engine->_actors[i];
 		if (a->_key.size() > 0) {
 			loadActor(a, json[a->_key]->asObject());
 		}
@@ -544,7 +544,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 		const Common::JSONObject &jInventory = json->asObject();
 		const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
 		for (int i = 0; i < NUMACTORS; i++) {
-			Object *actor = g_engine->_hud._actorSlots[i].actor;
+			Common::SharedPtr<Object> actor = g_engine->_hud._actorSlots[i].actor;
 			if (actor) {
 				int jiggleCount = 0;
 				actor->_inventory.clear();
@@ -554,11 +554,11 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 						const Common::JSONArray &jSlotObjects = jSlot["objects"]->asArray();
 						for (size_t j = 0; j < jSlotObjects.size(); j++) {
 							const Common::JSONValue *jObj = jSlotObjects[j];
-							Object *obj = object(jObj->asString());
+							Common::SharedPtr<Object> obj = object(jObj->asString());
 							if (!obj)
 								warning("inventory obj '%s' not found", jObj->asString().c_str());
 							else {
-								actor->pickupObject(obj);
+								Object::pickupObject(actor, obj);
 								obj->_jiggle = jSlot.contains("jiggle") && jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
 							}
 						}
@@ -578,7 +578,7 @@ void SaveGameManager::loadRooms(const Common::JSONObject &json) {
 
 void SaveGameManager::loadObjects(const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
-		Object *o = object(it->_key);
+		Common::SharedPtr<Object> o = object(it->_key);
 		if (o)
 			loadObject(o, it->_value->asObject());
 		else
@@ -639,11 +639,11 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 			int id = 0;
 			sqgetf(obj, "_id", id);
 			if (isActor(id)) {
-				Object *a = actor(id);
+				Common::SharedPtr<Object> a = actor(id);
 				jObj["_actorKey"] = new Common::JSONValue(a->_key);
 				return new Common::JSONValue(jObj);
 			} else if (isObject(id)) {
-				Object *o = sqobj(id);
+				Common::SharedPtr<Object> o = sqobj(id);
 				if (!o)
 					return new Common::JSONValue();
 				jObj["_objectKey"] = new Common::JSONValue(o->_key);
@@ -686,7 +686,7 @@ Common::String toString(const Math::Vector2d &pos) {
 	return Common::String::format("{%d,%d}", (int)pos.getX(), (int)pos.getY());
 }
 
-static Common::JSONValue *createJActor(Object *actor) {
+static Common::JSONValue *createJActor(Common::SharedPtr<Object> actor) {
 	Common::JSONValue *jActorValue = tojson(actor->_table, false);
 	Common::JSONObject jActor(jActorValue->asObject());
 	int color = actor->_node->getComputedColor().toInt();
@@ -721,7 +721,7 @@ static Common::JSONValue *createJActor(Object *actor) {
 static Common::JSONValue *createJActors() {
 	Common::JSONObject jActors;
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		if (actor->_key.size() > 0) {
 			jActors[actor->_key] = createJActor(actor);
 		}
@@ -854,7 +854,7 @@ static Common::JSONValue *createJInventory(const ActorSlot &slot) {
 		Common::JSONArray jiggleArray;
 		bool anyJiggle = false;
 		for (size_t i = 0; i < slot.actor->_inventory.size(); i++) {
-			Object *obj = slot.actor->_inventory[i];
+			Common::SharedPtr<Object> obj = slot.actor->_inventory[i];
 			if (obj->_jiggle)
 				anyJiggle = true;
 			jiggleArray.push_back(createJBool(obj->_jiggle));
@@ -882,7 +882,7 @@ static Common::JSONValue *createJInventory() {
 	return new Common::JSONValue(json);
 }
 
-static Common::JSONValue *createJObject(HSQOBJECT &table, Object *obj) {
+static Common::JSONValue *createJObject(HSQOBJECT &table, Common::SharedPtr<Object> obj) {
 	Common::JSONObject json(tojson(table, false)->asObject());
 	if (obj) {
 		if (!obj->_node->isVisible())
@@ -901,7 +901,7 @@ static Common::JSONValue *createJObject(HSQOBJECT &table, Object *obj) {
 static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
 	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
 	if (isObject(getId(v))) {
-		Object *obj = sqobj(v);
+		Common::SharedPtr<Object> obj = sqobj(v);
 		if (!obj || (obj->_objType == otNone)) {
 			// info fmt"obj: createJObject({k})"
 			(*jObj)[k] = createJObject(v, obj);
@@ -919,7 +919,7 @@ static Common::JSONValue *createJObjects() {
 static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void* data) {
 	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
 	if (isObject(getId(v))) {
-			Object *obj = sqobj(v);
+			Common::SharedPtr<Object> obj = sqobj(v);
 			// info fmt"pseudoObj: createJObject({k})"
 			(*jObj)[k] = createJObject(v, obj);
 		}
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 417ff0be25a..22197058ab2 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -105,7 +105,7 @@ void Node::clear() {
 }
 
 void Node::remove() {
-	if (_parent != NULL)
+	if (_parent)
 		_parent->removeChild(this);
 }
 
@@ -403,7 +403,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 	}
 }
 
-ActorNode::ActorNode(Object *obj)
+ActorNode::ActorNode(Common::SharedPtr<Object> obj)
 	: Node(obj->_key), _object(obj) {
 }
 
@@ -531,11 +531,11 @@ void OverlayNode::drawCore(Math::Matrix4 trsf) {
 	g_engine->getGfx().drawQuad(size, _ovlColor);
 }
 
-static bool hasUpArrow(Object *actor) {
+static bool hasUpArrow(Common::SharedPtr<Object> actor) {
 	return actor->_inventoryOffset != 0;
 }
 
-static bool hasDownArrow(Object *actor) {
+static bool hasDownArrow(Common::SharedPtr<Object> actor) {
 	return actor->_inventory.size() > (size_t)(actor->_inventoryOffset * NUMOBJECTSBYROW + NUMOBJECTS);
 }
 
@@ -549,7 +549,7 @@ Inventory::Inventory() : Node("Inventory") {
 	_arrowDnRect = Common::Rect(SCREEN_WIDTH / 2.f, MARGINBOTTOM, SCREEN_WIDTH / 2.f + ARROWWIDTH, MARGINBOTTOM + ARROWHEIGHT);
 }
 
-Math::Vector2d Inventory::getPos(Object *inv) const {
+Math::Vector2d Inventory::getPos(Common::SharedPtr<Object> inv) const {
 	if (_actor) {
 		int i = Twp::find(_actor->_inventory, inv) - _actor->_inventoryOffset * NUMOBJECTSBYROW;
 		return Math::Vector2d(_itemRects[i].left + _itemRects[i].width() / 2.f, _itemRects[i].bottom + _itemRects[i].height() / 2.f);
@@ -614,7 +614,7 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 	int count = MIN(NUMOBJECTS, (int)(_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW));
 
 	for (int i = 0; i < count; i++) {
-		Object *obj = _actor->_inventory[_actor->_inventoryOffset * NUMOBJECTSBYROW + i];
+		Common::SharedPtr<Object> obj = _actor->_inventory[_actor->_inventoryOffset * NUMOBJECTSBYROW + i];
 		Common::String icon = obj->getIcon();
 		if (itemsSheet->frameTable.contains(icon)) {
 			SpriteSheetFrame *itemFrame = &itemsSheet->frameTable[icon];
@@ -641,7 +641,7 @@ void Inventory::drawCore(Math::Matrix4 trsf) {
 	}
 }
 
-void Inventory::update(float elapsed, Object *actor, Color backColor, Color verbNormal) {
+void Inventory::update(float elapsed, Common::SharedPtr<Object> actor, Color backColor, Color verbNormal) {
 	_jiggleTime += 10.f * elapsed;
 	_fadeTime += elapsed;
 
@@ -693,7 +693,7 @@ void Inventory::update(float elapsed, Object *actor, Color backColor, Color verb
 		}
 
 		for (size_t i = 0; i < _actor->_inventory.size(); i++) {
-			Object *obj = _actor->_inventory[i];
+			Common::SharedPtr<Object> obj = _actor->_inventory[i];
 			obj->update(elapsed);
 		}
 	}
@@ -811,7 +811,7 @@ void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
 	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
+			Common::SharedPtr<Object> obj = layer->_objects[j];
 			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
 				Math::Vector2d pos = g_engine->roomToScreen(obj->_node->getAbsPos());
 				Math::Matrix4 t;
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 4b4d05d2c2a..9910cd3810d 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -158,7 +158,7 @@ struct ObjectAnimation;
 class Object;
 class Anim : public Node {
 public:
-	Anim(Object *obj);
+	Anim(Object* obj);
 
 	void clearFrames();
 	void setAnim(const ObjectAnimation *anim, float fps = 0.f, bool loop = false, bool instant = false);
@@ -186,13 +186,13 @@ private:
 
 class ActorNode final : public Node {
 public:
-	ActorNode(Object *obj);
+	ActorNode(Common::SharedPtr<Object> obj);
 
 	int getZSort() const override final;
 	Math::Vector2d getScale() const override final;
 
 private:
-	Object *_object = nullptr;
+	Common::SharedPtr<Object> _object = nullptr;
 };
 
 class TextNode final : public Node {
@@ -294,10 +294,10 @@ private:
 class Inventory: public Node {
 public:
 	Inventory();
-	void update(float elapsed, Object* actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
+	void update(float elapsed, Common::SharedPtr<Object>  actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
 
-	Object* getObject() const { return _obj; }
-	Math::Vector2d getPos(Object* inv) const;
+	Common::SharedPtr<Object>  getObject() const { return _obj; }
+	Math::Vector2d getPos(Common::SharedPtr<Object>  inv) const;
 
 	void setVisible(bool visible) override;
 
@@ -309,10 +309,10 @@ private:
 	void drawSprite(SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
 
 private:
-	Object* _actor = nullptr;
+	Common::SharedPtr<Object>  _actor = nullptr;
     Color _backColor, _verbNormal;
     bool _down = false;
-    Object* _obj = nullptr;
+    Common::SharedPtr<Object>  _obj = nullptr;
 	Common::Rect _itemRects[NUMOBJECTS];
 	Common::Rect _arrowUpRect;
 	Common::Rect _arrowDnRect;
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 83c864f01e4..498e41bfc61 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -50,7 +50,7 @@ private:
 // If a list of multiple sounds or an array are given, will randomly choose between the sound files.
 // The triggerNumber says which trigger in the animation JSON file should be used as a trigger to play the sound.
 static SQInteger actorSound(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor or object");
 	int trigNum = 0;
@@ -128,7 +128,7 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 	if (!soundDef)
 		return sq_throwerror(v, "failed to get sound");
 
-	Object *obj = sqobj(v, 3);
+	Common::SharedPtr<Object> obj = sqobj(v, 3);
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor or object");
 	int loopTimes = 1;
@@ -233,7 +233,7 @@ static SQInteger loopObjectSound(HSQUIRRELVM v) {
 	SoundDefinition *sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get music");
-	Object *obj = sqobj(v, 3);
+	Common::SharedPtr<Object> obj = sqobj(v, 3);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (numArgs == 4) {
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 3aca63625a0..73780508e2a 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -297,9 +297,9 @@ Common::SharedPtr<Room> sqroom(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
-Object *sqobj(int id) {
+Common::SharedPtr<Object> sqobj(int id) {
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		if (getId(actor->_table) == id)
 			return actor;
 	}
@@ -309,7 +309,7 @@ Object *sqobj(int id) {
 		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Common::SharedPtr<Layer> layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
-				Object *obj = layer->_objects[k];
+				Common::SharedPtr<Object> obj = layer->_objects[k];
 				if (getId(obj->_table) == id)
 					return obj;
 			}
@@ -318,28 +318,28 @@ Object *sqobj(int id) {
 	return nullptr;
 }
 
-Object *sqobj(HSQOBJECT table) {
+Common::SharedPtr<Object> sqobj(HSQOBJECT table) {
 	int id = getId(table);
 	return sqobj(id);
 }
 
-Object *sqobj(HSQUIRRELVM v, int i) {
+Common::SharedPtr<Object> sqobj(HSQUIRRELVM v, int i) {
 	HSQOBJECT table;
 	sq_getstackobj(v, i, &table);
 	return sqobj(table);
 }
 
-Object *sqactor(HSQOBJECT table) {
+Common::SharedPtr<Object> sqactor(HSQOBJECT table) {
 	int id = getId(table);
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Object *actor = g_engine->_actors[i];
+		Common::SharedPtr<Object> actor = g_engine->_actors[i];
 		if (actor->getId() == id)
 			return actor;
 	}
 	return nullptr;
 }
 
-Object *sqactor(HSQUIRRELVM v, int i) {
+Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i) {
 	HSQOBJECT table;
 	if (SQ_SUCCEEDED(sqget(v, i, table)))
 		return sqactor(table);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 51daaca17cc..668f1bb1268 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -162,11 +162,11 @@ int getId(HSQOBJECT table);
 Common::SharedPtr<Room> sqroom(HSQOBJECT table);
 Common::SharedPtr<Room> sqroom(HSQUIRRELVM v, int i);
 Common::SharedPtr<Room> getRoom(int id);
-Object *sqobj(HSQOBJECT table);
-Object *sqobj(HSQUIRRELVM v, int i);
-Object *sqobj(int i);
-Object *sqactor(HSQOBJECT table);
-Object *sqactor(HSQUIRRELVM v, int i);
+Common::SharedPtr<Object> sqobj(HSQOBJECT table);
+Common::SharedPtr<Object> sqobj(HSQUIRRELVM v, int i);
+Common::SharedPtr<Object> sqobj(int i);
+Common::SharedPtr<Object> sqactor(HSQOBJECT table);
+Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i);
 SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);
 SoundDefinition *sqsounddef(int id);
 Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 4957c8dd81c..d61e47677cc 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -235,18 +235,18 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 	return -666;
 }
 
-static bool isAnimating(Object *obj) {
+static bool isAnimating(Common::SharedPtr<Object> obj) {
 	return obj->_nodeAnim->_anim && !obj->_nodeAnim->_disabled && obj->_animName != obj->getAnimName(STAND_ANIMNAME);
 }
 
 struct ObjAnimating {
-	ObjAnimating(Object *obj) : _obj(obj) {}
+	ObjAnimating(Common::SharedPtr<Object> obj) : _obj(obj) {}
 	bool operator()() {
 		return isAnimating(_obj);
 	}
 
 private:
-	Object *_obj;
+	Common::SharedPtr<Object> _obj;
 };
 
 // When called in a function started with startthread, execution is suspended until animatingItem has completed its animation.
@@ -260,7 +260,7 @@ private:
 // breakwhileanimating(ray)
 // actorCostume(ray, "RayAnimation")
 static SQInteger breakwhileanimating(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	return breakwhilecond(v, ObjAnimating(obj), "breakwhileanimating(%s)", obj->_key.c_str());
@@ -355,14 +355,14 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 // Returns true if at least 1 actor is talking.
 static bool isSomeoneTalking() {
 	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
-		Object *obj = *it;
+		Common::SharedPtr<Object> obj = *it;
 		if (obj->getTalking() && obj->getTalking()->isEnabled())
 			return true;
 	}
 	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
 		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
-			Object *obj = *it2;
+			Common::SharedPtr<Object> obj = *it2;
 			if (obj->getTalking() && obj->getTalking()->isEnabled())
 				return true;
 		}
@@ -377,14 +377,14 @@ struct SomeoneTalking {
 };
 
 struct ActorTalking {
-	ActorTalking(Object *obj) : _obj(obj) {}
+	ActorTalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
 
 	bool operator()() {
 		return _obj->getTalking() && _obj->getTalking()->isEnabled();
 	}
 
 private:
-	Object *_obj;
+	Common::SharedPtr<Object> _obj;
 };
 
 // If an actor is specified, breaks until actor has finished talking.
@@ -405,7 +405,7 @@ static SQInteger breakwhiletalking(HSQUIRRELVM v) {
 		return breakwhilecond(v, SomeoneTalking(), "breakwhiletalking(all)");
 	}
 	if (nArgs == 2) {
-		Object *obj = sqobj(v, 2);
+		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 		return breakwhilecond(v, ActorTalking(obj), "breakwhiletalking(%s)", obj->_name.c_str());
@@ -415,14 +415,14 @@ static SQInteger breakwhiletalking(HSQUIRRELVM v) {
 }
 
 struct ActorWalking {
-	ActorWalking(Object *obj) : _obj(obj) {}
+	ActorWalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
 
 	bool operator()() {
 		return _obj->getWalkTo() && _obj->getWalkTo()->isEnabled();
 	}
 
 private:
-	Object *_obj;
+	Common::SharedPtr<Object> _obj;
 };
 
 // If an actor is specified, breaks until actor has finished walking.
@@ -436,7 +436,7 @@ private:
 //    pushSentence(VERB_USE, nickel, Nickel.copyTron)
 //})
 static SQInteger breakwhilewalking(HSQUIRRELVM v) {
-	Object *obj = sqobj(v, 2);
+	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	return breakwhilecond(v, ActorWalking(obj), "breakwhilewalking(%s)", obj->_name.c_str());
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index c33956fdc34..d6018a1349a 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -132,7 +132,7 @@ private:
 	CutsceneState _state;
 	bool _showCursor = false;
 	InputStateFlag _inputState = (InputStateFlag)0;
-	Object *_actor = nullptr;
+	Common::SharedPtr<Object> _actor = nullptr;
 };
 
 } // namespace Twp
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cd6e6d8b516..cf8e635a18a 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -122,7 +122,7 @@ bool TwpEngine::clickedAtHandled(Math::Vector2d roomPos) {
 	return result;
 }
 
-bool TwpEngine::preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+bool TwpEngine::preWalk(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2) {
 	bool result = false;
 	HSQOBJECT n2Table;
 	Common::String n2Name;
@@ -146,7 +146,7 @@ bool TwpEngine::preWalk(Object *actor, VerbId verbId, Object *noun1, Object *nou
 	return result;
 }
 
-static bool verbNoWalkTo(VerbId verbId, Object *noun1) {
+static bool verbNoWalkTo(VerbId verbId, Common::SharedPtr<Object> noun1) {
 	if (verbId.id == VERB_LOOKAT)
 		return (noun1->getFlags() & FAR_LOOK) != 0;
 	return false;
@@ -154,7 +154,7 @@ static bool verbNoWalkTo(VerbId verbId, Object *noun1) {
 
 // Called to execute a sentence and, if needed, start the actor walking.
 // If `actor` is `null` then the selectedActor is assumed.
-bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+bool TwpEngine::execSentence(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2) {
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
@@ -183,7 +183,7 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 
 	if (verbNoWalkTo(verbId, noun1)) {
 		if (!noun1->inInventory()) { // TODO: test if verb.flags != VERB_INSTANT
-			actor->turn(noun1);
+			Object::turn(actor, noun1);
 			callVerb(actor, verbId, noun1, noun2);
 			return true;
 		}
@@ -193,10 +193,7 @@ bool TwpEngine::execSentence(Object *actor, VerbId verbId, Object *noun1, Object
 	actor->_exec.noun1 = noun1;
 	actor->_exec.noun2 = noun2;
 	actor->_exec.enabled = true;
-	if (!noun1->inInventory())
-		actor->walk(noun1);
-	else
-		actor->walk(noun2);
+	Object::walk(actor, noun1->inInventory() ? noun2 : noun1);
 	return true;
 }
 
@@ -216,7 +213,7 @@ void TwpEngine::walkFast(bool state) {
 void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 	if (_room && _inputState.getInputActive() && !_actorSwitcher.isMouseOver()) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
-		Object *obj = objAt(roomPos);
+		Common::SharedPtr<Object> obj = objAt(roomPos);
 
 		if (_cursor.doubleClick) {
 			walkFast(true);
@@ -237,7 +234,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					// Just clicking on the ground
 					cancelSentence(_actor);
 					if (_actor->_room == _room)
-						_actor->walk((Vector2i)roomPos);
+						Object::walk(_actor, (Vector2i)roomPos);
 					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 					_holdToMove = true;
 				}
@@ -298,7 +295,7 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
+			Common::SharedPtr<Object> obj = layer->_objects[j];
 			if ((obj != g_engine->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
 				if (func(obj))
 					return;
@@ -307,8 +304,8 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 }
 
 struct InInventory {
-	InInventory(Object *&obj) : _obj(obj) { _obj = nullptr; }
-	bool operator()(Object *obj) {
+	InInventory(Common::SharedPtr<Object> &obj) : _obj(obj) { _obj = nullptr; }
+	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->inInventory()) {
 			_obj = obj;
 			return true;
@@ -317,11 +314,11 @@ struct InInventory {
 	}
 
 public:
-	Object *&_obj;
+	Common::SharedPtr<Object> &_obj;
 };
 
-Object *inventoryAt(Math::Vector2d pos) {
-	Object *result;
+Common::SharedPtr<Object> inventoryAt(Math::Vector2d pos) {
+	Common::SharedPtr<Object> result;
 	objsAt(pos, InInventory(result));
 	return result;
 }
@@ -369,9 +366,9 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 }
 
 struct GetNoun2 {
-	GetNoun2(Object *&obj) : _noun2(obj) {}
+	GetNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {}
 
-	bool operator()(Object *obj) {
+	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj != g_engine->_actor && obj->getFlags() & GIVEABLE) {
 			_noun2 = obj;
 			return true;
@@ -380,7 +377,7 @@ struct GetNoun2 {
 	}
 
 public:
-	Object *&_noun2;
+	Common::SharedPtr<Object> &_noun2;
 };
 
 void TwpEngine::update(float elapsed) {
@@ -463,7 +460,7 @@ void TwpEngine::update(float elapsed) {
 						walkFast();
 						cancelSentence(_actor);
 						if (_actor->_room == _room && (distance((Vector2i)_actor->_node->getAbsPos(), (Vector2i)roomPos) > 5)) {
-							_actor->walk((Vector2i)roomPos);
+							Object::walk(_actor, (Vector2i)roomPos);
 						}
 						_nextHoldToMoveTime = _time + 0.250f;
 					}
@@ -775,7 +772,7 @@ Common::Error TwpEngine::run() {
 					break;
 				case TwpAction::kSelectPreviousActor:
 					if (_actor) {
-						Common::Array<Object *> actors;
+						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];
 							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
@@ -790,7 +787,7 @@ Common::Error TwpEngine::run() {
 					break;
 				case TwpAction::kSelectNextActor:
 					if (_actor) {
-						Common::Array<Object *> actors;
+						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];
 							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
@@ -918,15 +915,15 @@ Common::Error TwpEngine::run() {
 
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
-		if(delta < 10) {
+		if (delta < 10) {
 			g_system->delayMillis(10 - delta);
 		}
 	}
 
 	// Cleanup
-    ImGui_ImplOpenGL3_Shutdown();
-    ImGui_ImplSDL2_Shutdown();
-    ImGui::DestroyContext();
+	ImGui_ImplOpenGL3_Shutdown();
+	ImGui_ImplSDL2_Shutdown();
+	ImGui::DestroyContext();
 
 	return Common::kNoError;
 }
@@ -1015,20 +1012,20 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			// declare flags if does not exist
 			if (!sqrawexists(oTable, "flags"))
 				sqsetf(oTable, "flags", 0);
-			Object *obj = new Object(oTable, k);
+			Common::SharedPtr<Object> obj(new Object(oTable, k));
 			setId(obj->_table, newObjId());
 			obj->_node = new Node(k);
-			obj->_nodeAnim = new Anim(obj);
+			obj->_nodeAnim = new Anim(obj.get());
 			obj->_node->addChild(obj->_nodeAnim);
-			obj->setRoom(params->room);
+			Object::setRoom(obj, params->room);
 			// set room as delegate
 			sqsetdelegate(obj->_table, params->room->_table);
 		} else {
-			Object *obj = params->room->getObj(k);
+			Common::SharedPtr<Object> obj = params->room->getObj(k);
 			if (!obj) {
 				debugC(kDebugGame, "object: %s not found in wimpy", k.c_str());
 				if (sqrawexists(oTable, "name")) {
-					obj = new Object();
+					obj.reset(new Object());
 					obj->_key = k;
 					obj->_layer = params->room->layer(0);
 					params->room->layer(0)->_objects.push_back(obj);
@@ -1053,7 +1050,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			} else {
 				obj->setState(0, true);
 			}
-			obj->setRoom(params->room);
+			Object::setRoom(obj, params->room);
 
 			// set room as delegate
 			sqsetdelegate(obj->_table, params->room->_table);
@@ -1083,7 +1080,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 		sqgetf(table, "background", background);
 		GGPackEntryReader entry;
 		entry.open(_pack, background + ".wimpy");
-		result->load(entry);
+		Room::load(result, entry);
 		result->_name = name;
 		result->_pseudo = pseudo;
 		for (size_t i = 0; i < result->_layers.size(); i++) {
@@ -1096,7 +1093,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 			result->_scene->addChild(layerNode);
 
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
-				Object *obj = layer->_objects[j];
+				Common::SharedPtr<Object> obj = layer->_objects[j];
 				if (!sqrawexists(table, obj->_key)) {
 					// this object does not exist, so create it
 					sq_newtable(v);
@@ -1110,7 +1107,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 
 					// adds the object to the room table
 					sqsetf(result->_table, obj->_key, obj->_table);
-					obj->setRoom(result);
+					Object::setRoom(obj, result);
 					obj->setState(0, true);
 
 					if (obj->_objType == otNone)
@@ -1128,9 +1125,9 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 	for (size_t i = 0; i < result->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = result->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
+			Common::SharedPtr<Object> obj = layer->_objects[j];
 			if (obj->_parent.size() > 0) {
-				Object *parent = result->getObj(obj->_parent);
+				Common::SharedPtr<Object> parent = result->getObj(obj->_parent);
 				if (!parent) {
 					warning("parent: '%s' not found", obj->_parent.c_str());
 				} else {
@@ -1153,7 +1150,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 	return result;
 }
 
-void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Object *door) {
+void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object> door) {
 	HSQUIRRELVM v = getVm();
 	// Called when the room is entered.
 	debugC(kDebugGame, "call enter room function of %s", room->_name.c_str());
@@ -1182,7 +1179,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Object *door) {
 		cancelSentence();
 		if (door) {
 			Facing facing = getOppositeFacing(door->getDoorFacing());
-			_actor->setRoom(room);
+			Object::setRoom(_actor, room);
 			if (door) {
 				_actor->setFacing(facing);
 				_actor->_node->setPos(door->getUsePos());
@@ -1199,7 +1196,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Object *door) {
 	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
-			Object *obj = layer->_objects[j];
+			Common::SharedPtr<Object> obj = layer->_objects[j];
 			// add all scaling triggers
 			if (obj->_objType == ObjectType::otTrigger) {
 				Scaling *scaling = getScaling(obj->_key);
@@ -1261,13 +1258,15 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 		// delete all temporary objects
 		for (size_t i = 0; i < _room->_layers.size(); i++) {
 			Common::SharedPtr<Layer> layer = _room->_layers[i];
-			for (size_t j = 0; j < layer->_objects.size(); j++) {
-				Object *obj = layer->_objects[j];
+			for (auto it = layer->_objects.begin(); it != layer->_objects.end();) {
+				Common::SharedPtr<Object> obj = *it;
 				if (obj->_temporary) {
-					delete obj;
+					it = it = layer->_objects.erase(it);
+					continue;
 				} else if (isActor(obj->getId()) && _actor != obj) {
 					obj->stopObjectMotors();
 				}
+				it++;
 			}
 		}
 
@@ -1300,7 +1299,7 @@ void TwpEngine::actorExit() {
 	}
 }
 
-void TwpEngine::cancelSentence(Object *actor) {
+void TwpEngine::cancelSentence(Common::SharedPtr<Object> actor) {
 	debugC(kDebugGame, "cancelSentence");
 	if (!actor)
 		actor = _actor;
@@ -1349,7 +1348,7 @@ Math::Vector2d TwpEngine::cameraPos() {
 	return _camera.getAt();
 }
 
-void TwpEngine::follow(Object *actor) {
+void TwpEngine::follow(Common::SharedPtr<Object> actor) {
 	_followActor = actor;
 	if (actor) {
 		Math::Vector2d pos = actor->_node->getPos();
@@ -1371,9 +1370,9 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 }
 
 struct GetByZorder {
-	GetByZorder(Object *&result) : _result(result) { result = nullptr; }
+	GetByZorder(Common::SharedPtr<Object> &result) : _result(result) { result = nullptr; }
 
-	bool operator()(Object *obj) {
+	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() < _zOrder) {
 			_result = obj;
 			_zOrder = obj->_node->getZSort();
@@ -1382,19 +1381,19 @@ struct GetByZorder {
 	}
 
 public:
-	Object *&_result;
+	Common::SharedPtr<Object> &_result;
 
 private:
 	int _zOrder = INT_MAX;
 };
 
-Object *TwpEngine::objAt(Math::Vector2d pos) {
-	Object *result;
+Common::SharedPtr<Object> TwpEngine::objAt(Math::Vector2d pos) {
+	Common::SharedPtr<Object> result;
 	objsAt(pos, GetByZorder(result));
 	return result;
 }
 
-void TwpEngine::setActor(Object *actor, bool userSelected) {
+void TwpEngine::setActor(Common::SharedPtr<Object> actor, bool userSelected) {
 	_actor = actor;
 	_hud._actor = actor;
 	if (!_hud.getParent() && actor) {
@@ -1416,7 +1415,7 @@ void TwpEngine::setActor(Object *actor, bool userSelected) {
 		follow(actor);
 }
 
-bool TwpEngine::selectable(Object *actor) {
+bool TwpEngine::selectable(Common::SharedPtr<Object> actor) {
 	for (int i = 0; i < NUMACTORS; i++) {
 		ActorSlot *slot = &_hud._actorSlots[i];
 		if (slot->actor == actor)
@@ -1425,7 +1424,7 @@ bool TwpEngine::selectable(Object *actor) {
 	return false;
 }
 
-static void giveTo(Object *actor1, Object *actor2, Object *obj) {
+static void giveTo(Common::SharedPtr<Object> actor1, Common::SharedPtr<Object> actor2, Common::SharedPtr<Object> obj) {
 	obj->_owner = actor2;
 	actor2->_inventory.push_back(obj);
 	int index = find(actor1->_inventory, obj);
@@ -1441,7 +1440,7 @@ void TwpEngine::resetVerb() {
 	_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 }
 
-bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2) {
+bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2) {
 	sqcall("onObjectClick", noun1->_table);
 
 	// Called after the actor has walked to the object.
@@ -1530,7 +1529,7 @@ bool TwpEngine::callVerb(Object *actor, VerbId verbId, Object *noun1, Object *no
 	return false;
 }
 
-void TwpEngine::callTrigger(Object *obj, HSQOBJECT trigger) {
+void TwpEngine::callTrigger(Common::SharedPtr<Object> obj, HSQOBJECT trigger) {
 	if (trigger._type != OT_NULL) {
 		HSQUIRRELVM v = getVm();
 		// create trigger thread
@@ -1571,7 +1570,7 @@ void TwpEngine::updateTriggers() {
 	if (_actor) {
 		// check if actor enters or leaves an object trigger
 		for (size_t i = 0; i < _room->_triggers.size(); i++) {
-			Object *trigger = _room->_triggers[i];
+			Common::SharedPtr<Object> trigger = _room->_triggers[i];
 			if (!trigger->_triggerActive && trigger->contains(_actor->_node->getAbsPos())) {
 				debugC(kDebugGame, "call enter trigger %s", trigger->_key.c_str());
 				trigger->_triggerActive = true;
@@ -1653,6 +1652,6 @@ void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
 	Image::writePNG(stream, s);
 }
 
-ScalingTrigger::ScalingTrigger(Object *obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
+ScalingTrigger::ScalingTrigger(Common::SharedPtr<Object> obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 567e7ae7d8e..ea768d333ec 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -123,26 +123,26 @@ public:
 	Math::Vector2d roomToScreen(Math::Vector2d pos);
 	Math::Vector2d screenToRoom(Math::Vector2d pos);
 
-	void setActor(Object *actor, bool userSelected = false);
-	Object *objAt(Math::Vector2d pos);
+	void setActor(Common::SharedPtr<Object> actor, bool userSelected = false);
+	Common::SharedPtr<Object> objAt(Math::Vector2d pos);
 	void flashSelectableActor(int flash);
 	void stopTalking();
 	void walkFast(bool state = true);
 
 	Common::SharedPtr<Room> defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Common::SharedPtr<Room> room);
-	void enterRoom(Common::SharedPtr<Room> room, Object *door = nullptr);
+	void enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object> door = nullptr);
 
 	void cameraAt(Math::Vector2d at);
 	// Returns the camera position: the position of the middle of the screen.
 	Math::Vector2d cameraPos();
-	void follow(Object *actor);
+	void follow(Common::SharedPtr<Object> actor);
 	void fadeTo(FadeEffect effect, float duration, bool fadeToSep = false);
 
 	void execNutEntry(HSQUIRRELVM v, const Common::String &entry);
 	void execBnutEntry(HSQUIRRELVM v, const Common::String &entry);
-	bool callVerb(Object *actor, VerbId verbId, Object *noun1, Object *noun2 = nullptr);
-	bool execSentence(Object *actor, VerbId verbId, Object *noun1, Object *noun2 = nullptr);
+	bool callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2 = nullptr);
+	bool execSentence(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2 = nullptr);
 
 	float getRandom() const;
 	float getRandom(float min, float max) const;
@@ -153,17 +153,17 @@ private:
 	void exitRoom(Common::SharedPtr<Room> nextRoom);
 	void actorEnter();
 	void actorExit();
-	void cancelSentence(Object *actor = nullptr);
+	void cancelSentence(Common::SharedPtr<Object> actor = nullptr);
 	void clickedAt(Math::Vector2d scrPos);
 	bool clickedAtHandled(Math::Vector2d roomPos);
 	void setShaderEffect(RoomEffect effect);
-	bool selectable(Object *actor);
+	bool selectable(Common::SharedPtr<Object> actor);
 	void resetVerb();
 	Common::String cursorText();
 	Verb verb();
-	bool preWalk(Object *actor, VerbId verbId, Object *noun1, Object *noun2);
+	bool preWalk(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2);
 	void updateTriggers();
-	void callTrigger(Object *obj, HSQOBJECT trigger);
+	void callTrigger(Common::SharedPtr<Object> obj, HSQOBJECT trigger);
 	Common::Array<ActorSwitcherSlot> actorSwitcherSlots();
 	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
 	Scaling *getScaling(const Common::String &name);
@@ -177,17 +177,17 @@ public:
 	GGPackSet _pack;
 	ResManager _resManager;
 	Common::Array<Common::SharedPtr<Room> > _rooms;
-	Common::Array<Object *> _actors;
-	Common::Array<Object *> _objects;
+	Common::Array<Common::SharedPtr<Object> > _actors;
+	Common::Array<Common::SharedPtr<Object> > _objects;
 	Common::Array<Common::SharedPtr<ThreadBase> > _threads;
 	Common::Array<Common::SharedPtr<Task> > _tasks;
 	Common::Array<Common::SharedPtr<Callback> > _callbacks;
-	Object *_actor = nullptr;
-	Object *_followActor = nullptr;
+	Common::SharedPtr<Object> _actor = nullptr;
+	Common::SharedPtr<Object> _followActor = nullptr;
 	Common::SharedPtr<Room> _room;
 	float _time = 0.f;
-	Object *_noun1 = nullptr;
-	Object *_noun2 = nullptr;
+	Common::SharedPtr<Object> _noun1 = nullptr;
+	Common::SharedPtr<Object> _noun2 = nullptr;
 	UseFlag _useFlag;
 	HSQOBJECT _defaultObj;
 	bool _walkFastState = false;
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 877b6c8a796..1c146fbb839 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -50,7 +50,7 @@ Facing flip(Facing facing) {
 	}
 }
 
-Facing getFacingToFaceTo(Object *actor, Object *obj) {
+Facing getFacingToFaceTo(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	Math::Vector2d d = obj->_node->getPos() + obj->_node->getOffset() - (actor->_node->getPos() + actor->_node->getOffset());
 	if (abs(d.getY()) > abs(d.getX())) {
 		return d.getY() > 0 ? FACE_BACK : FACE_FRONT;
diff --git a/engines/twp/util.h b/engines/twp/util.h
index fd0f5bac6da..3f21c0ee800 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -78,7 +78,7 @@ T clamp(T x, T a, T b) {
 // game util
 Facing getFacing(int dir, Facing facing);
 Facing flip(Facing facing);
-Facing getFacingToFaceTo(Object *actor, Object *obj);
+Facing getFacingToFaceTo(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
 // parsing util
 bool toBool(const Common::JSONObject &jNode, const Common::String &key);
@@ -97,6 +97,16 @@ size_t find(const Common::Array<T> &array, const T &o) {
 	return (size_t)-1;
 }
 
+template<typename T>
+size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T* o) {
+	for (size_t i = 0; i < array.size(); i++) {
+		if (array[i].get() == o) {
+			return i;
+		}
+	}
+	return (size_t)-1;
+}
+
 // string util
 Common::String join(const Common::Array<Common::String> &array, const Common::String &sep);
 Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by);
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 9d34a9b6f77..d1b0a39e9b9 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -116,7 +116,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	const Color red(1.f, 0.f, 0.f);
 	const Color yellow(1.f, 1.f, 0.f);
 	const Color blue(0.f, 0.f, 1.f);
-	const Object *actor = g_engine->_actor;
+	const Common::SharedPtr<Object> actor = g_engine->_actor;
 
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
@@ -177,7 +177,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
 
-		Object *obj = g_engine->objAt(roomPos);
+		Common::SharedPtr<Object> obj = g_engine->objAt(roomPos);
 		if (obj) {
 			t = Math::Matrix4();
 			pos = g_engine->roomToScreen(obj->getUsePos()) - Math::Vector2d(4.f, 4.f);


Commit: 6cb55e0fec5f275e5b273104f723f06c56987f7f
    https://github.com/scummvm/scummvm/commit/6cb55e0fec5f275e5b273104f723f06c56987f7f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with object nodes

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index a5cce86da43..be56221e514 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -795,9 +795,9 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	actor->_key = key;
 
 	debugC(kDebugActScript, "Create actor %s %d", key.c_str(), actor->getId());
-	actor->_node = new ActorNode(actor);
-	actor->_nodeAnim = new Anim(actor.get());
-	actor->_node->addChild(actor->_nodeAnim);
+	actor->_node = Common::SharedPtr<Node>(new ActorNode(actor));
+	actor->_nodeAnim = Common::SharedPtr<Anim>(new Anim(actor.get()));
+	actor->_node->addChild(actor->_nodeAnim.get());
 	g_engine->_actors.push_back(actor);
 
 	sq_pushobject(v, actor->_table);
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 6cc31b7af65..5d72af1d3ef 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -440,8 +440,8 @@ void Talking::say(const Common::String &text) {
 		_obj->_sayNode->remove();
 	}
 	Text text2("sayline", txt, thCenter, tvCenter, SCREEN_WIDTH * 3.f / 4.f, _color);
-	_obj->_sayNode = new TextNode();
-	((TextNode *)_obj->_sayNode)->setText(text2);
+	_obj->_sayNode = Common::SharedPtr<TextNode>(new TextNode());
+	_obj->_sayNode->setText(text2);
 	_obj->_sayNode->setColor(_color);
 	_node = _obj->_sayNode;
 	Math::Vector2d pos = g_engine->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
@@ -452,7 +452,7 @@ void Talking::say(const Common::String &text) {
 
 	_obj->_sayNode->setPos(pos);
 	_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
-	g_engine->_screenScene.addChild(_obj->_sayNode);
+	g_engine->_screenScene.addChild(_obj->_sayNode.get());
 	_elapsed = 0.f;
 }
 
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 9c44d033e1f..35edd0befd3 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -234,6 +234,7 @@ private:
 	float _wsd;
 };
 
+class TextNode;
 // Creates a talking animation for a specified object.
 class Talking : public Motor {
 public:
@@ -253,8 +254,8 @@ private:
 	int loadActorSpeech(const Common::String &name);
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
-	Node *_node = nullptr;
+	Common::SharedPtr<Object> _obj;
+	Common::SharedPtr<TextNode> _node;
 	Lip _lip;
 	float _elapsed = 0.f;
 	float _duration = 0.f;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 57c95883ed5..6e9f17ea028 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -78,9 +78,9 @@ private:
 
 Object::Object()
 	: _talkOffset(0, 90) {
-	_node = new Node("newObj");
-	_nodeAnim = new Anim(this);
-	_node->addChild(_nodeAnim);
+	_node = Common::SharedPtr<Node>(new Node("newObj"));
+	_nodeAnim = Common::SharedPtr<Anim>(new Anim(this));
+	_node->addChild(_nodeAnim.get());
 	sq_resetobject(&_table);
 }
 
@@ -377,7 +377,7 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 				if (index != -1)
 					layer->_objects.remove_at(index);
 				if (layer)
-					layer->_node->removeChild(object->_node);
+					layer->_node->removeChild(object->_node.get());
 			}
 		}
 		if (room && room->layer(0) && room->layer(0)->_node) {
@@ -387,7 +387,7 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 				int index = find(layer->_objects, object);
 				if (index == -1)
 					layer->_objects.push_back(object);
-				layer->_node->addChild(object->_node);
+				layer->_node->addChild(object->_node.get());
 			}
 		}
 		if (object->_room != room) {
@@ -763,7 +763,7 @@ void Object::turn(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj
 }
 
 void Object::jiggle(float amount) {
-	_jiggleTo = new Jiggle(_node, amount);
+	_jiggleTo = new Jiggle(_node.get(), amount);
 }
 
 void Object::inventoryScrollUp() {
diff --git a/engines/twp/object.h b/engines/twp/object.h
index f8f59d0dad1..8f761b41c52 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -86,6 +86,7 @@ class Motor;
 class Node;
 class Layer;
 class Blink;
+class TextNode;
 
 struct TalkingState {
 	Common::SharedPtr<Object> _obj;
@@ -230,9 +231,9 @@ public:
 	Common::SharedPtr<Room> _room;
 	Common::Array<ObjectAnimation> _anims;
 	bool _temporary = false;
-	Node *_node = nullptr;
-	Node *_sayNode = nullptr;
-	Anim *_nodeAnim = nullptr;
+	Common::SharedPtr<Node> _node;
+	Common::SharedPtr<TextNode> _sayNode;
+	Common::SharedPtr<Anim> _nodeAnim;
 	Common::SharedPtr<Layer> _layer;
 	Common::StringArray _hiddenLayers;
 	Common::String _animName;
@@ -251,7 +252,7 @@ public:
 	Color _talkColor;
 	Common::HashMap<Common::String, Common::String> _animNames;
 	bool _lit = false;
-	Common::SharedPtr<Object> _owner = nullptr;
+	Common::SharedPtr<Object> _owner;
 	Common::Array<Common::SharedPtr<Object> > _inventory;
 	int _inventoryOffset = 0;
 	Common::StringArray _icons;
@@ -260,7 +261,7 @@ public:
 	float _iconElapsed = 0.f;
 	HSQOBJECT _enter, _leave;
 	int _dependentState = 0;
-	Common::SharedPtr<Object> _dependentObj = nullptr;
+	Common::SharedPtr<Object> _dependentObj;
 	float _popElapsed = 0.f;
 	int _popCount = 0;
 	Sentence _exec;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 488248488d4..057305241f9 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -610,7 +610,7 @@ static SQInteger objectParent(HSQUIRRELVM v) {
 	if (!parent)
 		return sq_throwerror(v, "failed to get parent");
 	obj->_parent = parent->_key;
-	parent->_node->addChild(obj->_node);
+	parent->_node->addChild(obj->_node.get());
 	return 0;
 }
 
@@ -700,7 +700,7 @@ static SQInteger objectRotateTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
-		obj->setRotateTo(new RotateTo(duration, obj->_node, rotation, intToInterpolationMethod(interpolation)));
+		obj->setRotateTo(new RotateTo(duration, obj->_node.get(), rotation, intToInterpolationMethod(interpolation)));
 	}
 	return 0;
 }
@@ -729,7 +729,7 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
-		obj->setRotateTo(new ScaleTo(duration, obj->_node, scale, intToInterpolationMethod(interpolation)));
+		obj->setRotateTo(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation)));
 	}
 	return 0;
 }
@@ -740,7 +740,7 @@ static SQInteger objectScreenSpace(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	g_engine->_screenScene.addChild(obj->_node);
+	g_engine->_screenScene.addChild(obj->_node.get());
 	return 0;
 }
 
@@ -1028,7 +1028,7 @@ static SQInteger shakeObject(HSQUIRRELVM v) {
 	float amount;
 	if (SQ_FAILED(sqget(v, 3, amount)))
 		return sq_throwerror(v, "failed to get amount");
-	obj->setShakeTo(new Shake(obj->_node, amount));
+	obj->setShakeTo(new Shake(obj->_node.get(), amount));
 	return 0;
 }
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 3683f072c3a..8335d03c7cf 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -189,7 +189,7 @@ Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const
 
 	obj->_node->setZSort(1);
 	layer(0)->_objects.push_back(obj);
-	layer(0)->_node->addChild(obj->_node);
+	layer(0)->_node->addChild(obj->_node.get());
 	obj->_layer = layer(0);
 	obj->setState(0);
 
@@ -218,7 +218,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
-	TextNode *node = new TextNode();
+	Common::SharedPtr<TextNode> node(new TextNode());
 	node->setText(txt);
 	float y = 0.5f;
 	switch (vAlign) {
@@ -245,7 +245,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 	}
 	obj->_node = node;
 	layer(0)->_objects.push_back(obj);
-	layer(0)->_node->addChild(obj->_node);
+	layer(0)->_node->addChild(obj->_node.get());
 	obj->_layer = layer(0);
 
 	g_engine->_objects.push_back(obj);
@@ -325,12 +325,12 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 			Common::SharedPtr<Object> obj(new Object());
 			Twp::setId(obj->_table, newObjId());
 			obj->_key = jObject["name"]->asString();
-			Node *objNode = new Node(obj->_key);
+			Common::SharedPtr<Node> objNode(new Node(obj->_key));
 			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_node = objNode;
-			obj->_nodeAnim = new Anim(obj.get());
-			obj->_node->addChild(obj->_nodeAnim);
+			obj->_nodeAnim = Common::SharedPtr<Anim>(new Anim(obj.get()));
+			obj->_node->addChild(obj->_nodeAnim.get());
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
 				obj->_useDir = parseUseDir(jObject["usedir"]->asString());
@@ -434,13 +434,13 @@ void Room::objectParallaxLayer(Common::SharedPtr<Object> obj, int zsort) {
 		// removes object from old layer
 		if (obj->_layer) {
 			int i = find(obj->_layer->_objects, obj);
-			obj->_layer->_node->removeChild(obj->_node);
+			obj->_layer->_node->removeChild(obj->_node.get());
 			obj->_layer->_objects.remove_at(i);
 		}
 		// adds object to the new one
 		l->_objects.push_back(obj);
 		// update scenegraph
-		l->_node->addChild(obj->_node);
+		l->_node->addChild(obj->_node.get());
 		obj->_layer = l;
 	}
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cf8e635a18a..3ecad00e303 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1014,9 +1014,9 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 				sqsetf(oTable, "flags", 0);
 			Common::SharedPtr<Object> obj(new Object(oTable, k));
 			setId(obj->_table, newObjId());
-			obj->_node = new Node(k);
-			obj->_nodeAnim = new Anim(obj.get());
-			obj->_node->addChild(obj->_nodeAnim);
+			obj->_node = Common::SharedPtr<Node>(new Node(k));
+			obj->_nodeAnim = Common::SharedPtr<Anim>(new Anim(obj.get()));
+			obj->_node->addChild(obj->_nodeAnim.get());
 			Object::setRoom(obj, params->room);
 			// set room as delegate
 			sqsetdelegate(obj->_table, params->room->_table);
@@ -1116,7 +1116,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 					obj->setTouchable(true);
 				}
 
-				layerNode->addChild(obj->_node);
+				layerNode->addChild(obj->_node.get());
 			}
 		}
 	}
@@ -1131,7 +1131,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 				if (!parent) {
 					warning("parent: '%s' not found", obj->_parent.c_str());
 				} else {
-					parent->_node->addChild(obj->_node);
+					parent->_node->addChild(obj->_node.get());
 				}
 			}
 		}


Commit: d1b5955fb4b058421bf43ad0efd77f822134c0fb
    https://github.com/scummvm/scummvm/commit/d1b5955fb4b058421bf43ad0efd77f822134c0fb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with sounds

Changed paths:
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index c39ce0f97fc..9d8ef9791f0 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -36,7 +36,7 @@
 
 namespace Twp {
 
-void SoundStream::open(SoundDefinition *sndDef) {
+void SoundStream::open(Common::SharedPtr<SoundDefinition> sndDef) {
 	sndDef->load();
 	_stream.open(sndDef->_buffer.data(), sndDef->_buffer.size());
 }
@@ -90,7 +90,7 @@ bool AudioSystem::playing(int id) const {
 	return g_engine->_mixer->isSoundIDActive(id);
 }
 
-bool AudioSystem::playing(SoundDefinition *soundDef) const {
+bool AudioSystem::playing(Common::SharedPtr<SoundDefinition> soundDef) const {
 	for (int i = 0; i < 32; i++) {
 		if (_slots[i].busy && _slots[i].sndDef == soundDef) {
 			return g_engine->_mixer->isSoundHandleActive(_slots[i].handle);
@@ -240,7 +240,7 @@ AudioSlot *AudioSystem::getFreeSlot() {
 	return nullptr;
 }
 
-int AudioSystem::play(SoundDefinition *sndDef, Audio::Mixer::SoundType cat, int loopTimes, float fadeInTimeMs, float volume, int objId) {
+int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::SoundType cat, int loopTimes, float fadeInTimeMs, float volume, int objId) {
 	AudioSlot *slot = getFreeSlot();
 	if (!slot)
 		return 0;
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index c800fa1681a..cf3b62b7427 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -29,7 +29,7 @@
 #include "twp/ggpack.h"
 
 namespace Audio {
-	class SeekableAudioStream;
+class SeekableAudioStream;
 }
 
 namespace Twp {
@@ -38,9 +38,9 @@ class AudioChannel;
 class SoundDefinition;
 
 class SoundDefinition;
-class SoundStream: public Common::SeekableReadStream {
+class SoundStream : public Common::SeekableReadStream {
 public:
-	void open(SoundDefinition* sndDef);
+	void open(Common::SharedPtr<SoundDefinition> sndDef);
 
 	virtual uint32 read(void *dataPtr, uint32 dataSize) override;
 	virtual bool eos() const override;
@@ -58,40 +58,40 @@ public:
 	friend class SoundStream;
 
 public:
-	SoundDefinition(const Common::String& name);
+	SoundDefinition(const Common::String &name);
 
 	void load();
 	int getId() const { return _id; }
 	Common::String getName() const { return _name; }
 
 private:
-    int _id;						// identifier for this sound
-    Common::String _name;		    // name of the sound to load
-    Common::Array<byte> _buffer;	// buffer containing the sound data
-    bool _loaded = false;		    // indicates whether or not the sound buffer has been loaded
+	int _id;                     // identifier for this sound
+	Common::String _name;        // name of the sound to load
+	Common::Array<byte> _buffer; // buffer containing the sound data
+	bool _loaded = false;        // indicates whether or not the sound buffer has been loaded
 };
 
 struct AudioSlot {
-	Audio::SoundHandle handle;					// handle returned when this sound has been played
-	SoundDefinition *sndDef = nullptr;			// sound definition associated to this slot
-	SoundStream stream;							// audio stream
-	bool busy = false;							// is sound active
-	float volume = 1.f;							// actual volume for this slot
-	float fadeInTimeMs = 0.f;					// fade-in time in milliseconds
-	float fadeOutTimeMs = 0.f;					// fade-out time in milliseconds
+	Audio::SoundHandle handle;                           // handle returned when this sound has been played
+	Common::SharedPtr<SoundDefinition> sndDef = nullptr; // sound definition associated to this slot
+	SoundStream stream;                                  // audio stream
+	bool busy = false;                                   // is sound active
+	float volume = 1.f;                                  // actual volume for this slot
+	float fadeInTimeMs = 0.f;                            // fade-in time in milliseconds
+	float fadeOutTimeMs = 0.f;                           // fade-out time in milliseconds
 	int total = 0;
-	int id = 0;									// unique sound ID
-	int objId = 0;								// object ID or 0 if none
-	int loopTimes = 0;							//
-	Audio::Mixer::SoundType soundType;			//
+	int id = 0;                        // unique sound ID
+	int objId = 0;                     // object ID or 0 if none
+	int loopTimes = 0;                 //
+	Audio::Mixer::SoundType soundType; //
 };
 
 class AudioSystem {
 public:
-	int play(SoundDefinition* sndDef, Audio::Mixer::SoundType cat, int loopTimes = 0, float fadeInTimeMs = 0.f, float volume = 1.f, int objId = 0);
+	int play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::SoundType cat, int loopTimes = 0, float fadeInTimeMs = 0.f, float volume = 1.f, int objId = 0);
 
 	bool playing(int id) const;
-	bool playing(SoundDefinition* soundDef) const;
+	bool playing(Common::SharedPtr<SoundDefinition> soundDef) const;
 
 	void fadeOut(int id, float fadeTime);
 	void stop(int id);
@@ -105,13 +105,13 @@ public:
 
 	void update(float elapsed);
 
-	Common::Array<SoundDefinition*> _soundDefs;
+	Common::Array<Common::SharedPtr<SoundDefinition> > _soundDefs;
 	AudioSlot _slots[32];
-	SoundDefinition* _soundHover = nullptr;	// not used yet, should be used in the GUI
+	Common::SharedPtr<SoundDefinition> _soundHover; // not used yet, should be used in the GUI
 
 private:
-	void updateVolume(AudioSlot* slot);
-	AudioSlot* getFreeSlot();
+	void updateVolume(AudioSlot *slot);
+	AudioSlot *getFreeSlot();
 
 private:
 	float _masterVolume = 1.f;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 5d72af1d3ef..deb668b09c4 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -357,7 +357,7 @@ int Talking::loadActorSpeech(const Common::String &name) {
 	filename.toUppercase();
 	filename += ".ogg";
 	if (g_engine->_pack.assetExists(filename.c_str())) {
-		SoundDefinition *soundDefinition = new SoundDefinition(filename);
+		Common::SharedPtr<SoundDefinition> soundDefinition(new SoundDefinition(filename));
 		if (!soundDefinition) {
 			debugC(kDebugGame, "File %s.ogg not found", name.c_str());
 		} else {
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 6e9f17ea028..df2f1f3ad6b 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -225,7 +225,7 @@ void Object::trig(const Common::String &name) {
 	} else {
 		int id = 0;
 		sqgetf(sqrootTbl(g_engine->getVm()), name.substr(1), id);
-		SoundDefinition *sound = sqsounddef(id);
+		Common::SharedPtr<SoundDefinition> sound = sqsounddef(id);
 		if (!sound)
 			warning("Cannot trig sound '%s', sound not found (id=%d, %s)", name.c_str(), id, _key.c_str());
 		else
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 8f761b41c52..a6f0b36c2bf 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -243,7 +243,7 @@ public:
 	Facing _facing = FACE_FRONT;
 	int _facingLockValue = 0;
 	float _fps = 0.f;
-	Common::HashMap<int, Trigger *> _triggers;
+	Common::HashMap<int, Common::SharedPtr<Trigger> > _triggers;
 	Math::Vector2d _talkOffset;
 	Math::Vector2d _walkSpeed;
 	bool _triggerActive = false;
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 498e41bfc61..8cb2e27c2be 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -32,7 +32,7 @@ namespace Twp {
 
 class SoundTrigger : public Trigger {
 public:
-	SoundTrigger(const Common::Array<SoundDefinition *> sounds, int objId) : _sounds(sounds), _objId(objId) {}
+	SoundTrigger(const Common::Array<Common::SharedPtr<SoundDefinition> > sounds, int objId) : _sounds(sounds), _objId(objId) {}
 	virtual ~SoundTrigger() {}
 
 	virtual void trig() override {
@@ -41,7 +41,7 @@ public:
 	}
 
 private:
-	const Common::Array<SoundDefinition *> _sounds;
+	const Common::Array<Common::SharedPtr<SoundDefinition> > _sounds;
 	int _objId;
 };
 
@@ -62,7 +62,7 @@ static SQInteger actorSound(HSQUIRRELVM v) {
 		if ((numSounds == 1) && (SQ_SUCCEEDED(sqget(v, 4, tmp))) && (tmp == 0)) {
 			obj->_triggers.erase(trigNum);
 		} else {
-			Common::Array<SoundDefinition *> sounds;
+			Common::Array<Common::SharedPtr<SoundDefinition> > sounds;
 			if (sq_gettype(v, 4) == OT_ARRAY) {
 				if (SQ_FAILED(sqgetarray(v, 4, sounds)))
 					return sq_throwerror(v, "failed to get sounds");
@@ -73,7 +73,7 @@ static SQInteger actorSound(HSQUIRRELVM v) {
 				}
 			}
 
-			Trigger *trigger = new SoundTrigger(sounds, obj->getId());
+			Common::SharedPtr<Trigger> trigger(new SoundTrigger(sounds, obj->getId()));
 			obj->_triggers[trigNum] = trigger;
 		}
 	}
@@ -89,7 +89,7 @@ static SQInteger defineSound(HSQUIRRELVM v) {
 	Common::String filename;
 	if (SQ_FAILED(sqget(v, 2, filename)))
 		return sq_throwerror(v, "failed to get filename");
-	SoundDefinition *sound = new SoundDefinition(filename);
+	Common::SharedPtr<SoundDefinition> sound(new SoundDefinition(filename));
 	g_engine->_audio._soundDefs.push_back(sound);
 	debugC(kDebugSndScript, "defineSound(%s)-> %d", filename.c_str(), sound->getId());
 	sqpush(v, sound->getId());
@@ -124,7 +124,7 @@ static SQInteger isSoundPlaying(HSQUIRRELVM v) {
 
 static SQInteger playObjectSound(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	SoundDefinition *soundDef = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> soundDef = sqsounddef(v, 2);
 	if (!soundDef)
 		return sq_throwerror(v, "failed to get sound");
 
@@ -156,7 +156,7 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 // objectState(quickiePalFlickerLight, ON)
 // _flourescentSoundID = playSound(soundFlourescentOn)
 static SQInteger playSound(HSQUIRRELVM v) {
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound) {
 		int soundId = 0;
 		sqget(v, 2, soundId);
@@ -180,7 +180,7 @@ static SQInteger playSound(HSQUIRRELVM v) {
 //     }
 // }
 static SQInteger playSoundVolume(HSQUIRRELVM v) {
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get sound");
 	int soundId = g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
@@ -189,7 +189,7 @@ static SQInteger playSoundVolume(HSQUIRRELVM v) {
 }
 
 static SQInteger loadSound(HSQUIRRELVM v) {
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get sound");
 	sound->load();
@@ -212,7 +212,7 @@ static SQInteger loopMusic(HSQUIRRELVM v) {
 	int loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get music");
 	if (numArgs == 3) {
@@ -230,7 +230,7 @@ static SQInteger loopObjectSound(HSQUIRRELVM v) {
 	int loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get music");
 	Common::SharedPtr<Object> obj = sqobj(v, 3);
@@ -273,7 +273,7 @@ static SQInteger loopSound(HSQUIRRELVM v) {
 	int loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
-	SoundDefinition *sound = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get music");
 	if (numArgs == 3) {
@@ -325,7 +325,7 @@ static SQInteger musicMixVolume(HSQUIRRELVM v) {
 }
 
 static SQInteger playMusic(HSQUIRRELVM v) {
-	SoundDefinition *soundDef = sqsounddef(v, 2);
+	Common::SharedPtr<SoundDefinition> soundDef = sqsounddef(v, 2);
 	if (!soundDef)
 		return sq_throwerror(v, "failed to get music");
 	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 73780508e2a..8e61b1058e9 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -346,16 +346,16 @@ Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i) {
 	return nullptr;
 }
 
-SoundDefinition *sqsounddef(int id) {
+Common::SharedPtr<SoundDefinition> sqsounddef(int id) {
 	for (size_t i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
-		SoundDefinition *sound = g_engine->_audio._soundDefs[i];
+		Common::SharedPtr<SoundDefinition> sound = g_engine->_audio._soundDefs[i];
 		if (sound->getId() == id)
 			return sound;
 	}
 	return nullptr;
 }
 
-SoundDefinition *sqsounddef(HSQUIRRELVM v, int i) {
+Common::SharedPtr<SoundDefinition> sqsounddef(HSQUIRRELVM v, int i) {
 	int id;
 	if (SQ_SUCCEEDED(sqget(v, i, id)))
 		return sqsounddef(id);
@@ -462,7 +462,7 @@ Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v) {
 	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), GetThread(v));
 }
 
-static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<SoundDefinition *> &arr) {
+static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::SharedPtr<SoundDefinition> > &arr) {
 	sq_pushobject(v, o);
 	sq_pushnull(v);
 	while (SQ_SUCCEEDED(sq_next(v, -2))) {
@@ -472,7 +472,7 @@ static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<SoundDefinition
 	sq_pop(v, 1);
 }
 
-SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<SoundDefinition *> &arr) {
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundDefinition> > &arr) {
 	HSQOBJECT obj;
 	SQRESULT result = sq_getstackobj(v, i, &obj);
 	sqgetarray(v, obj, arr);
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index 668f1bb1268..af8a8185d00 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -89,7 +89,7 @@ void setId(HSQOBJECT &o, int id);
 
 void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
 SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr);
-SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<SoundDefinition*>&  arr);
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundDefinition> >&  arr);
 
 template<typename TFunc>
 void sqgetitems(HSQOBJECT o, TFunc func) {
@@ -167,8 +167,8 @@ Common::SharedPtr<Object> sqobj(HSQUIRRELVM v, int i);
 Common::SharedPtr<Object> sqobj(int i);
 Common::SharedPtr<Object> sqactor(HSQOBJECT table);
 Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i);
-SoundDefinition* sqsounddef(HSQUIRRELVM v, int i);
-SoundDefinition *sqsounddef(int id);
+Common::SharedPtr<SoundDefinition> sqsounddef(HSQUIRRELVM v, int i);
+Common::SharedPtr<SoundDefinition> sqsounddef(int id);
 Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v);
 Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v, int id);
 Common::SharedPtr<ThreadBase> sqthread(int id);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index d61e47677cc..44c8f391d5d 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -531,7 +531,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		warning("TODO: exCommand EX_CAMERA_TRACKING: not implemented");
 		break;
 	case EX_BUTTON_HOVER_SOUND: {
-		SoundDefinition* sound = sqsounddef(v, 3);
+		Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 3);
 		if (!sound)
 			return sq_throwerror(v, "failed to get sound for EX_BUTTON_HOVER_SOUND");
 		g_engine->_audio._soundHover = sound;


Commit: 5711e7f1f1b4f30855988051f1fc5794c1758c4f
    https://github.com/scummvm/scummvm/commit/5711e7f1f1b4f30855988051f1fc5794c1758c4f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix misc leaks

Changed paths:
    engines/twp/font.h
    engines/twp/graph.cpp
    engines/twp/graph.h
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/scenegraph.cpp
    engines/twp/syslib.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/font.h b/engines/twp/font.h
index da76e9b4e0e..666a438f1ab 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -176,7 +176,7 @@ public:
 	TextVAlignment getVAlign() { return _vAlign; }
 
 	void setFont(const Common::String &fontName);
-	Font *getFont() { return _font; }
+	Common::SharedPtr<Font> getFont() { return _font; }
 	Math::Vector2d getBounds();
 
 	void draw(Gfx &gfx, Math::Matrix4 trsf = Math::Matrix4());
@@ -185,7 +185,7 @@ private:
 	void update();
 
 private:
-	Font *_font = nullptr;
+	Common::SharedPtr<Font> _font;
 	Common::String _fontName;
 	Texture *_texture = nullptr;
 	Common::String _txt;
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 3a544c7aeb3..d2907116f63 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -282,8 +282,8 @@ static uint minIndex(const Common::Array<float> &values) {
 	return index;
 }
 
-Graph *PathFinder::createGraph() {
-	Graph *result = new Graph();
+Common::SharedPtr<Graph> PathFinder::createGraph() {
+	Common::SharedPtr<Graph> result(new Graph());
 	for (uint i = 0; i < _walkboxes.size(); i++) {
 		const Walkbox &walkbox = _walkboxes[i];
 		if (walkbox.getPoints().size() > 2) {
diff --git a/engines/twp/graph.h b/engines/twp/graph.h
index e4bfb8ece5b..a6981c7063c 100644
--- a/engines/twp/graph.h
+++ b/engines/twp/graph.h
@@ -118,12 +118,12 @@ public:
 	const Graph &getGraph() const { return _walkgraph; }
 
 private:
-	Graph *createGraph();
+	Common::SharedPtr<Graph> createGraph();
 	bool inLineOfSight(Vector2i start, Vector2i to);
 
 private:
 	Common::Array<Walkbox> _walkboxes;
-	Graph *_graph = nullptr;
+	Common::SharedPtr<Graph> _graph;
 	Graph _walkgraph;
 	bool _isDirty = true;
 };
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 3f20537e71d..367d4e1b046 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -83,14 +83,16 @@ void ResManager::loadFont(const Common::String &name) {
 	if (name == "sayline") {
 		debugC(kDebugRes, "Load font %s", name.c_str());
 		Common::String resName = ConfMan.getBool("retroFonts") ? "FontRetroSheet": "FontModernSheet";
-		_fontModernSheet.load(resName);
-		_fonts[name] = &_fontModernSheet;
+		Common::SharedPtr<GGFont> fontModernSheet(new GGFont());
+		fontModernSheet->load(resName);
+		_fonts[name] = fontModernSheet;
 	} else if (name == "C64Font") {
 		debugC(kDebugRes, "Load font %s", name.c_str());
-		_fontC64TermSheet.load("FontC64TermSheet");
-		_fonts[name] = &_fontC64TermSheet;
+		Common::SharedPtr<GGFont> fontC64TermSheet(new GGFont());
+		fontC64TermSheet->load("FontC64TermSheet");
+		_fonts[name] = fontC64TermSheet;
 	} else {
-		BmFont* font = new BmFont();
+		Common::SharedPtr<BmFont> font(new BmFont());
 		font->load(name);
 		_fonts[name] = font;
 	}
@@ -104,7 +106,7 @@ SpriteSheet *ResManager::spriteSheet(const Common::String &name) {
 	return &_spriteSheets[key];
 }
 
-Font *ResManager::font(const Common::String &name) {
+Common::SharedPtr<Font> ResManager::font(const Common::String &name) {
 	Common::String key = getKey(name.c_str());
 	if (!_fonts.contains(key)) {
 		loadFont(key.c_str());
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index 92d65a716f9..388da057797 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -36,7 +36,7 @@ public:
 
 	Texture *texture(const Common::String &name);
 	SpriteSheet *spriteSheet(const Common::String& name);
-	Font *font(const Common::String& name);
+	Common::SharedPtr<Font> font(const Common::String& name);
 
 private:
 	void loadTexture(const Common::String &name);
@@ -46,10 +46,7 @@ private:
 public:
 	Common::HashMap<Common::String, Texture> _textures;
 	Common::HashMap<Common::String, SpriteSheet> _spriteSheets;
-	Common::HashMap<Common::String, Font*> _fonts;
-	GGFont _fontModernSheet;
-	GGFont _fontRetroSheet;
-	GGFont _fontC64TermSheet;
+	Common::HashMap<Common::String, Common::SharedPtr<Font> > _fonts;
 };
 } // namespace Twp
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 8335d03c7cf..34851418dd6 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -149,12 +149,11 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
 	setId(_table, newRoomId());
 	_name = name;
-	_scene = new Scene();
+	_scene = Common::SharedPtr<Scene>(new Scene());
 	_scene->addChild(&_overlayNode);
 }
 
 Room::~Room() {
-	delete _scene;
 }
 
 Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const Common::Array<Common::String> &frames) {
diff --git a/engines/twp/room.h b/engines/twp/room.h
index adcc2afd636..0efa660053c 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -60,7 +60,7 @@ public:
 	Common::Array<Common::SharedPtr<Object> > _objects;
 	Math::Vector2d _parallax;
 	int _zsort = 0;
-	Node *_node = nullptr;
+	Common::SharedPtr<Node> _node;
 };
 
 struct ScalingValue {
@@ -147,7 +147,7 @@ public:
 	Common::Array<ScalingTrigger> _scalingTriggers; // Scaling Triggers of the room
 	bool _pseudo = false;
 	Common::Array<Common::SharedPtr<Object> > _objects;
-	Scene *_scene = nullptr;
+	Common::SharedPtr<Scene> _scene;
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
 	Motor* _overlayTo = nullptr;
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 22197058ab2..f5388dd26ef 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -255,7 +255,7 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 
 	// enable debug lighting ?
 	if (_zOrder == 0 && g_engine->_lighting->_debug) {
-		g_engine->getGfx().use(g_engine->_lighting);
+		g_engine->getGfx().use(g_engine->_lighting.get());
 	} else {
 		g_engine->getGfx().use(nullptr);
 	}
@@ -381,7 +381,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			const SpriteSheetFrame &sf = sheet->frameTable[frame];
 			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 			if (_obj->_lit) {
-				g_engine->getGfx().use(g_engine->_lighting);
+				g_engine->getGfx().use(g_engine->_lighting.get());
 				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
 				const float left = flipX ?
 					(-1.f + sf.sourceSize.getX()) / 2.f - sf.spriteSourceSize.left :
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 44c8f391d5d..bfa0053eae0 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -77,13 +77,13 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 		sq_getstring(v, -1, &name);
 
 	Common::String threadName = Common::String::format("%s %s (%lld)", name == nullptr ? "<anonymous>" : name, _stringval(_closure(closureObj)->_function->_sourcename), _closure(closureObj)->_function->_lineinfos->_line);
-	Thread *t = new Thread(threadName, global, threadObj, envObj, closureObj, args);
+	Common::SharedPtr<Thread> t(new Thread(threadName, global, threadObj, envObj, closureObj, args));
 	sq_pop(vm, 1);
 	if (name)
 		sq_pop(v, 1); // pop name
 	sq_pop(v, 1);     // pop closure
 
-	g_engine->_threads.push_back(Common::SharedPtr<ThreadBase>(t));
+	g_engine->_threads.push_back(t);
 
 	debugC(kDebugSysScript, "create thread %s", t->getName().c_str());
 
@@ -150,8 +150,8 @@ static SQInteger addCallback(HSQUIRRELVM v) {
 		args.push_back(arg);
 	}
 
-	Callback *callback = new Callback(newCallbackId(), duration, methodName, args);
-	g_engine->_callbacks.push_back(Common::SharedPtr<Callback>(callback));
+	Common::SharedPtr<Callback> callback(new Callback(newCallbackId(), duration, methodName, args));
+	g_engine->_callbacks.push_back(callback);
 
 	sqpush(v, callback->getId());
 	return 1;
@@ -493,8 +493,8 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 	}
 
 	Common::SharedPtr<ThreadBase> parentThread = sqthread(v);
-	Cutscene *cutscene = new Cutscene(parentThread->getId(), threadObj, closure, closureOverride, envObj);
-	g_engine->_cutscene.reset(cutscene);
+	Common::SharedPtr<Cutscene> cutscene(new Cutscene(parentThread->getId(), threadObj, closure, closureOverride, envObj));
+	g_engine->_cutscene = cutscene;
 
 	// call the closure in the thread
 	cutscene->update(0.f);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 3ecad00e303..1152a697c5b 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -709,7 +709,7 @@ Common::Error TwpEngine::run() {
 	_sepiaShader.init("sepia", vsrc, sepiaShader);
 	_fadeShader.reset(new FadeShader());
 
-	_lighting = new Lighting();
+	_lighting = Common::SharedPtr<Lighting>(new Lighting());
 
 	_pack.init();
 
@@ -1068,11 +1068,11 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 	Common::SharedPtr<Room> result;
 	if (name == "Void") {
 		result.reset(new Room(name, table));
-		result->_scene = new Scene();
+		result->_scene = Common::SharedPtr<Scene>(new Scene());
 		Common::SharedPtr<Layer> layer(new Layer("background", Math::Vector2d(1.f, 1.f), 0));
-		layer->_node = new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray());
+		layer->_node = Common::SharedPtr<Node>(new ParallaxNode(Math::Vector2d(1.f, 1.f), "", Common::StringArray()));
 		result->_layers.push_back(layer);
-		result->_scene->addChild(layer->_node);
+		result->_scene->addChild(layer->_node.get());
 		sqsetf(sqrootTbl(v), name, result->_table);
 	} else {
 		result.reset(new Room(name, table));
@@ -1086,11 +1086,11 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 		for (size_t i = 0; i < result->_layers.size(); i++) {
 			Common::SharedPtr<Layer> layer = result->_layers[i];
 			// create layer node
-			ParallaxNode *layerNode = new ParallaxNode(layer->_parallax, result->_sheet, layer->_names);
+			Common::SharedPtr<ParallaxNode> layerNode(new ParallaxNode(layer->_parallax, result->_sheet, layer->_names));
 			layerNode->setZSort(layer->_zsort);
 			layerNode->setName(Common::String::format("Layer %s(%d)", layer->_names[0].c_str(), layer->_zsort));
 			layer->_node = layerNode;
-			result->_scene->addChild(layerNode);
+			result->_scene->addChild(layerNode.get());
 
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Common::SharedPtr<Object> obj = layer->_objects[j];
@@ -1166,7 +1166,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 		_room->_scene->remove();
 	_room = room;
 	room->_effect = RoomEffect::None;
-	_scene.addChild(_room->_scene);
+	_scene.addChild(_room->_scene.get());
 	_room->_lights._numLights = 0;
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
@@ -1554,10 +1554,10 @@ void TwpEngine::callTrigger(Common::SharedPtr<Object> obj, HSQOBJECT trigger) {
 			args.push_back(_actor->_table);
 		}
 
-		Thread *thread = new Thread("Trigger", false, threadObj, obj->_table, trigger, args);
+		Common::SharedPtr<Thread> thread(new Thread("Trigger", false, threadObj, obj->_table, trigger, args));
 
 		debugC(kDebugGame, "create triggerthread id: %d}", thread->getId());
-		g_engine->_threads.push_back(Common::SharedPtr<ThreadBase>(thread));
+		g_engine->_threads.push_back(thread);
 
 		// call the closure in the thread
 		if (!thread->call()) {
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index ea768d333ec..9e642ca08fb 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -194,7 +194,7 @@ public:
 	bool _holdToMove = false;
 	float _nextHoldToMoveTime = 0.f;
 	int _frameCounter = 0;
-	Lighting *_lighting = nullptr;
+	Common::SharedPtr<Lighting> _lighting;
 	Common::SharedPtr<Cutscene> _cutscene;
 	Scene _scene;
 	Scene _screenScene;


Commit: 6842d6818538cab81ec23be0b05350a1ec504e46
    https://github.com/scummvm/scummvm/commit/6842d6818538cab81ec23be0b05350a1ec504e46
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix yack leaks

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/yack.cpp
    engines/twp/yack.h


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 7d29626cb42..c282bc8eb67 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -198,7 +198,7 @@ void Dialog::choose(DialogSlot *slot) {
 	if (slot && slot->_isValid) {
 		sqcall("onChoiceClick");
 		for (size_t i = 0; i < slot->_stmt->_conds.size(); i++) {
-			YCond *cond = slot->_stmt->_conds[i];
+			Common::SharedPtr<YCond> cond = slot->_stmt->_conds[i];
 			CondStateVisitor v(slot->_dlg, DialogSelMode::Choose);
 			cond->accept(v);
 		}
@@ -321,16 +321,16 @@ bool Dialog::isCond(const Common::String &cond) const {
 	return result;
 }
 
-YLabel *Dialog::label(int line, const Common::String &name) const {
+Common::SharedPtr<YLabel> Dialog::label(int line, const Common::String &name) const {
 	for (size_t i = 0; i < _cu->_labels.size(); i++) {
-		YLabel *label = _cu->_labels[i];
+		Common::SharedPtr<YLabel> label = _cu->_labels[i];
 		if ((label->_name == name) && (label->_line >= line)) {
 			return label;
 		}
 	}
 	line = 0;
 	for (size_t i = 0; i < _cu->_labels.size(); i++) {
-		YLabel *label = _cu->_labels[i];
+		Common::SharedPtr<YLabel> label = _cu->_labels[i];
 		if ((label->_name == name) && (label->_line >= line)) {
 			return label;
 		}
@@ -350,7 +350,7 @@ void Dialog::gotoNextLabel() {
 	if (_lbl) {
         size_t i = Twp::find(_cu->_labels, _lbl);
 		if ((i != (size_t)-1) && (i != _cu->_labels.size() - 1)) {
-			YLabel *label = _cu->_labels[i + 1];
+			Common::SharedPtr<YLabel> label = _cu->_labels[i + 1];
 			selectLabel(label->_line, label->_name);
 		} else {
 			_state = None;
@@ -364,7 +364,7 @@ void Dialog::updateChoiceStates() {
 		DialogSlot *slot = &_slots[i];
 		if (slot->_isValid) {
 			for (size_t j = 0; j < slot->_stmt->_conds.size(); j++) {
-				YCond *cond = slot->_stmt->_conds[j];
+				Common::SharedPtr<YCond> cond = slot->_stmt->_conds[j];
 				CondStateVisitor v(this, DialogSelMode::Show);
 				cond->accept(v);
 			}
@@ -372,7 +372,7 @@ void Dialog::updateChoiceStates() {
 	}
 }
 
-void Dialog::run(YStatement *stmt) {
+void Dialog::run(Common::SharedPtr<YStatement> stmt) {
 	if (acceptConditions(stmt)) {
 		ExpVisitor visitor(this);
 		stmt->_exp->accept(visitor);
@@ -384,10 +384,10 @@ void Dialog::run(YStatement *stmt) {
 	_currentStatement++;
 }
 
-bool Dialog::acceptConditions(YStatement *stmt) {
+bool Dialog::acceptConditions(Common::SharedPtr<YStatement> stmt) {
 	CondVisitor vis(this);
 	for (size_t i = 0; i < stmt->_conds.size(); i++) {
-		YCond *cond = stmt->_conds[i];
+		Common::SharedPtr<YCond> cond = stmt->_conds[i];
 		cond->accept(vis);
 		if (!vis._accepted) {
 			return false;
@@ -405,7 +405,7 @@ void Dialog::running(float dt) {
 	else {
 		_state = Active;
 		while (_lbl && (_currentStatement < _lbl->_stmts.size()) && (_state == Active)) {
-			YStatement *statmt = _lbl->_stmts[_currentStatement];
+			Common::SharedPtr<YStatement> statmt = _lbl->_stmts[_currentStatement];
 			IsChoice isChoice;
 			statmt->_exp->accept(isChoice);
 			if (!acceptConditions(statmt))
@@ -438,7 +438,7 @@ static Common::String text(const Common::String &txt) {
 	return result;
 }
 
-void Dialog::addSlot(YStatement *stmt) {
+void Dialog::addSlot(Common::SharedPtr<YStatement> stmt) {
 	YChoice *choice = (YChoice *)stmt->_exp.get();
 	if ((!_slots[choice->_number - 1]._isValid) && (numSlots() < _context.limit)) {
 		DialogSlot *slot = &_slots[choice->_number - 1];
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 22e7264b2e0..d96bd194d92 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -45,7 +45,7 @@ public:
 public:
 	bool _isValid = false;
 	Text _text;
-	YStatement *_stmt = nullptr;
+	Common::SharedPtr<YStatement> _stmt = nullptr;
 	Dialog *_dlg = nullptr;
 };
 
@@ -194,15 +194,15 @@ public:
 
 private:
 	void choose(DialogSlot* slot);
-	YLabel *label(int line, const Common::String &name) const;
+	Common::SharedPtr<YLabel> label(int line, const Common::String &name) const;
 	void gotoNextLabel();
 	bool choicesReady() const { return numSlots() > 0; }
 	void updateChoiceStates();
-	void run(YStatement *stmt);
-	bool acceptConditions(YStatement *stmt);
+	void run(Common::SharedPtr<YStatement> stmt);
+	bool acceptConditions(Common::SharedPtr<YStatement> stmt);
 	void running(float dt);
 
-	void addSlot(YStatement *stmt);
+	void addSlot(Common::SharedPtr<YStatement> stmt);
 	int numSlots() const;
 	void clearSlots();
 
@@ -218,7 +218,7 @@ private:
 	DialogState _state = DialogState::None;
     size_t _currentStatement = 0;
 	unique_ptr<YCompilationUnit> _cu;
-	YLabel *_lbl = nullptr;
+	Common::SharedPtr<YLabel> _lbl;
 	DialogSlot _slots[MAXDIALOGSLOTS];
 	Math::Vector2d _mousePos;
 };
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 2361b3bdba8..8b37920851c 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -308,19 +308,13 @@ void YCompilationUnit::accept(YackVisitor &v) { v.visit(*this); }
 YLabel::YLabel(int line) { _line = line; }
 
 YLabel::~YLabel() {
-	for (size_t i = 0; i < _stmts.size(); i++) {
-		delete _stmts[i];
-	}
 }
 
 YCompilationUnit::~YCompilationUnit() {
-	for (size_t i = 0; i < _labels.size(); i++) {
-		delete _labels[i];
-	}
 }
 
-YLabel *YackParser::parseLabel() {
-	unique_ptr<YLabel> pLabel;
+Common::SharedPtr<YLabel> YackParser::parseLabel() {
+	Common::SharedPtr<YLabel> pLabel;
 	// :
 	_it++;
 	// label
@@ -330,15 +324,15 @@ YLabel *YackParser::parseLabel() {
 	do {
 		if (match({YackTokenId::Colon}) || match({YackTokenId::End}))
 			break;
-		YStatement *pStatement = parseStatement();
+		Common::SharedPtr<YStatement> pStatement = parseStatement();
 		pLabel->_stmts.push_back(pStatement);
 	} while (true);
 
-	return pLabel.release();
+	return pLabel;
 }
-YStatement *YackParser::parseStatement() {
-	unique_ptr<YStatement> pStatement;
-	pStatement.reset(new YStatement());
+
+Common::SharedPtr<YStatement> YackParser::parseStatement() {
+	Common::SharedPtr<YStatement> pStatement(new YStatement());
 
 	// expression
 	pStatement->_exp.reset(parseExpression());
@@ -346,27 +340,29 @@ YStatement *YackParser::parseStatement() {
 	while (match({YackTokenId::Condition})) {
 		pStatement->_conds.push_back(parseCondition());
 	}
-	return pStatement.release();
+	return pStatement;
 }
-YCond *YackParser::parseCondition() {
+
+Common::SharedPtr<YCond> YackParser::parseCondition() {
 	auto text = _reader.readText(*_it);
 	auto conditionText = text.substr(1, text.size() - 2);
 	auto line = _it->line;
 	_it++;
 	if (conditionText == "once") {
-		return new YOnce(line);
+		return Common::SharedPtr<YOnce>(new YOnce(line));
 	} else if (conditionText == "showonce") {
-		return new YShowOnce(line);
+		return Common::SharedPtr<YShowOnce>(new YShowOnce(line));
 	} else if (conditionText == "onceever") {
-		return new YOnceEver(line);
+		return Common::SharedPtr<YOnceEver>(new YOnceEver(line));
 	} else if (conditionText == "temponce") {
-		return new YTempOnce(line);
+		return Common::SharedPtr<YTempOnce>(new YTempOnce(line));
 	}
-	auto pCondition = new YCodeCond(line);
+	Common::SharedPtr<YCodeCond> pCondition(new YCodeCond(line));
 	pCondition->_code = conditionText;
 	return pCondition;
 }
-YExp *YackParser::parseExpression() {
+
+Common::SharedPtr<YExp> YackParser::parseExpression() {
 	if (match({YackTokenId::Identifier, YackTokenId::Colon, YackTokenId::String}))
 		return parseSayExpression();
 	if (match({YackTokenId::WaitWhile}))
@@ -381,36 +377,39 @@ YExp *YackParser::parseExpression() {
 		return parseCodeExpression();
 	return nullptr;
 }
-YSay *YackParser::parseSayExpression() {
+
+Common::SharedPtr<YSay> YackParser::parseSayExpression() {
 	auto actor = _reader.readText(*_it++);
 	_it++;
 	auto text = _reader.readText(*_it);
 	_it++;
-	auto pExp = new YSay();
+	Common::SharedPtr<YSay> pExp(new YSay());
 	pExp->_actor = actor;
 	pExp->_text = text.substr(1, text.size() - 2);
 	return pExp;
 }
-YExp *YackParser::parseWaitWhileExpression() {
+
+Common::SharedPtr<YExp> YackParser::parseWaitWhileExpression() {
 	auto waitwhile = _reader.readText(*_it++);
 	auto code = waitwhile.substr(10);
-	auto pExp = new YWaitWhile();
+	Common::SharedPtr<YWaitWhile> pExp(new YWaitWhile());
 	pExp->_cond = code;
 	return pExp;
 }
-YExp *YackParser::parseInstructionExpression() {
+
+Common::SharedPtr<YExp> YackParser::parseInstructionExpression() {
 	auto identifier = _reader.readText(*_it++);
 	if (identifier == "shutup") {
-		return new YShutup();
+		return Common::SharedPtr<YShutup>(new YShutup());
 	} else if (identifier == "pause") {
 		// pause number
 		auto time = atof(_reader.readText(*_it++).c_str());
-		auto pExp = new YPause();
+		Common::SharedPtr<YPause> pExp(new YPause());
 		pExp->_time = time;
 		return pExp;
 	} else if (identifier == "waitfor") {
 		// waitfor [actor]
-		auto pExp = new YWaitFor();
+		Common::SharedPtr<YWaitFor> pExp(new YWaitFor());
 		if (_it->id == YackTokenId::Identifier) {
 			auto actor = _reader.readText(*_it++);
 			pExp->_actor = actor;
@@ -418,7 +417,7 @@ YExp *YackParser::parseInstructionExpression() {
 		return pExp;
 	} else if (identifier == "parrot") {
 		// parrot [active]
-		auto pExp = new YParrot();
+		Common::SharedPtr<YParrot> pExp(new YParrot());
 		if (_it->id == YackTokenId::Identifier) {
 			auto active = _reader.readText(*_it++);
 			pExp->_active = active == "yes";
@@ -426,7 +425,7 @@ YExp *YackParser::parseInstructionExpression() {
 		return pExp;
 	} else if (identifier == "dialog") {
 		// dialog [actor]
-		auto pExp = new YDialog();
+		Common::SharedPtr<YDialog> pExp(new YDialog());
 		if (_it->id == YackTokenId::Identifier) {
 			auto actor = _reader.readText(*_it++);
 			pExp->_actor = actor;
@@ -434,7 +433,7 @@ YExp *YackParser::parseInstructionExpression() {
 		return pExp;
 	} else if (identifier == "override") {
 		// override [node]
-		auto pExp = new YOverride();
+		Common::SharedPtr<YOverride> pExp(new YOverride());
 		if (_it->id == YackTokenId::Identifier) {
 			auto node = _reader.readText(*_it++);
 			pExp->_node = node;
@@ -442,7 +441,7 @@ YExp *YackParser::parseInstructionExpression() {
 		return pExp;
 	} else if (identifier == "allowobjects") {
 		// allowobjects [allow]
-		auto pExp = new YAllowObjects();
+		Common::SharedPtr<YAllowObjects> pExp(new YAllowObjects());
 		if (_it->id == YackTokenId::Identifier) {
 			auto node = _reader.readText(*_it++);
 			pExp->_active = node == "YES";
@@ -450,7 +449,7 @@ YExp *YackParser::parseInstructionExpression() {
 		return pExp;
 	} else if (identifier == "limit") {
 		// limit [number]
-		auto pExp = new YLimit();
+		Common::SharedPtr<YLimit> pExp(new YLimit());
 		if (_it->id == YackTokenId::Int) {
 			auto node = _reader.readText(*_it++);
 			pExp->_max = strtol(node.c_str(), nullptr, 10);
@@ -459,21 +458,24 @@ YExp *YackParser::parseInstructionExpression() {
 	}
 	error("Unknown instruction: %s", identifier.c_str());
 }
-YGoto *YackParser::parseGotoExpression() {
+
+Common::SharedPtr<YGoto> YackParser::parseGotoExpression() {
 	_it++;
 	int line = _it->line;
 	auto name = _reader.readText(*_it++);
-	auto pExp = new YGoto(line);
+	Common::SharedPtr<YGoto> pExp(new YGoto(line));
 	pExp->_name = name;
 	return pExp;
 }
-YCodeExp *YackParser::parseCodeExpression() {
+
+Common::SharedPtr<YCodeExp> YackParser::parseCodeExpression() {
 	auto code = _reader.readText(*_it++);
-	auto pExp = new YCodeExp();
+	Common::SharedPtr<YCodeExp> pExp(new YCodeExp());
 	pExp->_code = code.substr(1);
 	return pExp;
 }
-YChoice *YackParser::parseChoiceExpression() {
+
+Common::SharedPtr<YChoice> YackParser::parseChoiceExpression() {
 	auto number = atol(_reader.readText(*_it).c_str());
 	_it++;
 	Common::String text;
@@ -485,10 +487,10 @@ YChoice *YackParser::parseChoiceExpression() {
 	}
 
 	_it++;
-	auto pExp = new YChoice();
+	Common::SharedPtr<YChoice> pExp(new YChoice());
 	pExp->_number = number;
 	pExp->_text = text;
-	pExp->_goto.reset(parseGotoExpression());
+	pExp->_goto = parseGotoExpression();
 	return pExp;
 }
 
diff --git a/engines/twp/yack.h b/engines/twp/yack.h
index d1e47c2003e..ab886013446 100644
--- a/engines/twp/yack.h
+++ b/engines/twp/yack.h
@@ -154,7 +154,7 @@ public:
 public:
 	int _number = 0;
 	Common::String _text;
-	unique_ptr<YGoto> _goto;
+	Common::SharedPtr<YGoto> _goto;
 };
 
 class YSay : public YExp {
@@ -254,8 +254,8 @@ public:
 	virtual void accept(YackVisitor &v) override;
 
 public:
-	unique_ptr<YExp> _exp;
-	Common::Array<YCond *> _conds;
+	Common::SharedPtr<YExp> _exp;
+	Common::Array<Common::SharedPtr<YCond> > _conds;
 };
 
 class YLabel : public YackNode {
@@ -266,7 +266,7 @@ public:
 
 public:
 	Common::String _name;
-	Common::Array<YStatement *> _stmts;
+	Common::Array<Common::SharedPtr<YStatement> > _stmts;
 	int _line = 0;
 };
 
@@ -276,7 +276,7 @@ public:
 	virtual void accept(YackVisitor &v) override;
 
 public:
-	Common::Array<YLabel*> _labels;
+	Common::Array<Common::SharedPtr<YLabel> > _labels;
 };
 
 class YackVisitor {
@@ -388,16 +388,16 @@ public:
 
 private:
 	bool match(const std::initializer_list<YackTokenId> &ids);
-	YLabel* parseLabel();
-	YStatement *parseStatement();
-	YCond *parseCondition();
-	YExp *parseExpression();
-	YSay *parseSayExpression();
-	YExp *parseWaitWhileExpression();
-	YExp *parseInstructionExpression();
-	YGoto *parseGotoExpression();
-	YCodeExp *parseCodeExpression();
-	YChoice *parseChoiceExpression();
+	Common::SharedPtr<YLabel> parseLabel();
+	Common::SharedPtr<YStatement> parseStatement();
+	Common::SharedPtr<YCond> parseCondition();
+	Common::SharedPtr<YExp> parseExpression();
+	Common::SharedPtr<YSay> parseSayExpression();
+	Common::SharedPtr<YExp> parseWaitWhileExpression();
+	Common::SharedPtr<YExp> parseInstructionExpression();
+	Common::SharedPtr<YGoto> parseGotoExpression();
+	Common::SharedPtr<YCodeExp> parseCodeExpression();
+	Common::SharedPtr<YChoice> parseChoiceExpression();
 
 private:
 	YackTokenReader _reader;


Commit: 61f0357120db92817c1c6835c78f2860a8c0f2b7
    https://github.com/scummvm/scummvm/commit/61f0357120db92817c1c6835c78f2860a8c0f2b7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with motors

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/enginedialogtarget.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 4eb7740ba44..829328408aa 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -4,6 +4,7 @@
 #include "twp/thread.h"
 #include "twp/dialog.h"
 #include "twp/lighting.h"
+#include "common/debug-channels.h"
 
 namespace Twp {
 
@@ -172,7 +173,7 @@ static void drawResources() {
 	ImVec2 cursor = ImGui::GetCursorPos();
 	ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 10.f));
 	ImGui::Text("Preview:");
-	ImGui::BeginChild("TexturePreview", ImVec2(0, 0), ImGuiChildFlags_Border|ImGuiChildFlags_ResizeX|ImGuiChildFlags_ResizeY);
+	ImGui::BeginChild("TexturePreview", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY);
 	for (auto &res : g_engine->_resManager._textures) {
 		if (state._textureSelected == res._key) {
 			ImGui::Image((ImTextureID)(intptr_t)res._value.id, ImVec2(res._value.width, res._value.height));
@@ -284,6 +285,30 @@ static void drawGeneral() {
 	ImGui::Checkbox("Verbs", &g_engine->_inputState._inputVerbsActive);
 	ImGui::Separator();
 
+	if (ImGui::CollapsingHeader("Debug")) {
+		bool allEnabled = true;
+		auto channels = DebugMan.getDebugChannels();
+		for (auto &channel : channels) {
+			bool enabled = DebugMan.isDebugChannelEnabled(channel.channel);
+			allEnabled &= enabled;
+			if (ImGui::Checkbox(channel.name.c_str(), &enabled)) {
+				if (enabled) {
+					DebugMan.enableDebugChannel(channel.channel);
+				} else {
+					DebugMan.disableDebugChannel(channel.channel);
+				}
+			}
+		}
+		ImGui::Separator();
+		if (ImGui::Checkbox("All", &allEnabled)) {
+			if (allEnabled) {
+				DebugMan.enableAllDebugChannels();
+			} else {
+				DebugMan.disableAllDebugChannels();
+			}
+		}
+	}
+
 	// Camera
 	if (ImGui::CollapsingHeader("Camera")) {
 		ImGui::TextColored(gray, "follow:");
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index c282bc8eb67..446448d590c 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -29,8 +29,8 @@ namespace Twp {
 
 class SerialMotors : public Motor {
 public:
-	SerialMotors(const std::initializer_list<Motor *> &motors) : _motors(motors) {}
-	SerialMotors(const Common::Array<Motor *> &motors) : _motors(motors) {}
+	SerialMotors(const std::initializer_list<Common::SharedPtr<Motor> > &motors) : _motors(motors) {}
+	SerialMotors(const Common::Array<Common::SharedPtr<Motor> > &motors) : _motors(motors) {}
 
 	virtual void update(float elapsed) override {
 		if (_motors.size() > 0) {
@@ -46,7 +46,7 @@ public:
 	}
 
 private:
-	Common::Array<Motor *> _motors;
+	Common::Array<Common::SharedPtr<Motor> > _motors;
 };
 
 class SelectLabelMotor : public Motor {
@@ -205,9 +205,9 @@ void Dialog::choose(DialogSlot *slot) {
 		YChoice *choice = getChoice(slot);
 		if (slot->_dlg->_context.parrot) {
 			slot->_dlg->_state = DialogState::Active;
-			slot->_dlg->_action = new SerialMotors(
+			slot->_dlg->_action = Common::SharedPtr<SerialMotors>(new SerialMotors(
 				{slot->_dlg->_tgt->say(slot->_dlg->_context.actor, choice->_text),
-				 new SelectLabelMotor(slot->_dlg, choice->_goto->_line, choice->_goto->_name)});
+				 Common::SharedPtr<SelectLabelMotor>(new SelectLabelMotor(slot->_dlg, choice->_goto->_line, choice->_goto->_name))}));
 			slot->_dlg->clearSlots();
 		} else {
 			slot->_dlg->selectLabel(choice->_goto->_line, choice->_goto->_name);
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index d96bd194d92..6266ea07631 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -90,10 +90,10 @@ public:
 
 	virtual Color actorColor(const Common::String &actor) = 0;
 	virtual Color actorColorHover(const Common::String &actor) = 0;
-	virtual Motor *say(const Common::String &actor, const Common::String &text) = 0;
-	virtual Motor *waitWhile(const Common::String &cond) = 0;
+	virtual Common::SharedPtr<Motor> say(const Common::String &actor, const Common::String &text) = 0;
+	virtual Common::SharedPtr<Motor> waitWhile(const Common::String &cond) = 0;
 	virtual void shutup() = 0;
-	virtual Motor *pause(float time) = 0;
+	virtual Common::SharedPtr<Motor> pause(float time) = 0;
 	virtual bool execCond(const Common::String &cond) = 0;
 };
 
@@ -212,7 +212,7 @@ public:
 	Common::Array<DialogConditionState> _states;
 	DialogContext _context;
 	unique_ptr<DialogTarget> _tgt;
-	Motor* _action = nullptr;
+	Common::SharedPtr<Motor> _action;
 
 private:
 	DialogState _state = DialogState::None;
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 4c921592a6c..2d80b1a8228 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -79,23 +79,23 @@ Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
 	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
 }
 
-Motor *EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
+Common::SharedPtr<Motor> EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
 	debugC(kDebugDialog, "say %s: %s", actor.c_str(), text.c_str());
 	Common::SharedPtr<Object> act = actorOrCurrent(actor);
 	Object::say(act, {text}, act->_talkColor);
 	return act->getTalking();
 }
 
-Motor *EngineDialogTarget::waitWhile(const Common::String &cond) {
-	return new WaitWhile(this, cond);
+Common::SharedPtr<Motor> EngineDialogTarget::waitWhile(const Common::String &cond) {
+	return Common::SharedPtr<WaitWhile>(new WaitWhile(this, cond));
 }
 
 void EngineDialogTarget::shutup() {
 	g_engine->stopTalking();
 }
 
-Motor *EngineDialogTarget::pause(float time) {
-	return new Pause(time);
+Common::SharedPtr<Motor> EngineDialogTarget::pause(float time) {
+	return Common::SharedPtr<Pause>(new Pause(time));
 }
 
 bool EngineDialogTarget::execCond(const Common::String &cond) {
diff --git a/engines/twp/enginedialogtarget.h b/engines/twp/enginedialogtarget.h
index 4a04f23773e..4e5048157d5 100644
--- a/engines/twp/enginedialogtarget.h
+++ b/engines/twp/enginedialogtarget.h
@@ -30,10 +30,10 @@ class EngineDialogTarget : public DialogTarget {
 public:
 	virtual Color actorColor(const Common::String &actor) override;
 	virtual Color actorColorHover(const Common::String &actor) override;
-	virtual Motor *say(const Common::String &actor, const Common::String &text) override;
-	virtual Motor *waitWhile(const Common::String &cond) override;
+	virtual Common::SharedPtr<Motor> say(const Common::String &actor, const Common::String &text) override;
+	virtual Common::SharedPtr<Motor> waitWhile(const Common::String &cond) override;
 	virtual void shutup() override;
-	virtual Motor *pause(float time) override;
+	virtual Common::SharedPtr<Motor> pause(float time) override;
 	virtual bool execCond(const Common::String &cond) override;
 };
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index deb668b09c4..f3df09d7341 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -241,14 +241,14 @@ void WalkTo::actorArrived() {
 		}
 
 		if (needsReach)
-			_obj->setReach(new ReachAnim(_obj, noun1));
+			_obj->setReach(Common::SharedPtr<ReachAnim>(new ReachAnim(_obj, noun1)));
 		else
 			Object::execVerb(_obj);
 	}
 }
 
 void WalkTo::update(float elapsed) {
-	if (_path.size() != 0) {
+	if (_state == kWalking && !_path.empty()) {
 		Vector2i dest = _path[0];
 		float d = distance(dest, (Vector2i)_obj->_node->getAbsPos());
 
@@ -256,8 +256,10 @@ void WalkTo::update(float elapsed) {
 		if (d < 1.0) {
 			_obj->_node->setPos((Math::Vector2d)_path[0]);
 			_path.remove_at(0);
-			if (_path.size() == 0) {
+			if (_path.empty()) {
+				_state = kArrived;
 				actorArrived();
+				return;
 			}
 		} else {
 			Math::Vector2d delta = (Math::Vector2d)dest - _obj->_node->getAbsPos();
@@ -274,11 +276,13 @@ void WalkTo::update(float elapsed) {
 		}
 	}
 
-	Motor *reach = _obj->getReach();
-	if (reach && reach->isEnabled()) {
-		reach->update(elapsed);
-		if (!reach->isEnabled())
-			disable();
+	if(_state == kArrived) {
+		Common::SharedPtr<Motor> reach = _obj->getReach();
+		if (reach && reach->isEnabled()) {
+			reach->update(elapsed);
+			if (!reach->isEnabled())
+				disable();
+		}
 	}
 }
 
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 35edd0befd3..96fd9011633 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -216,22 +216,28 @@ private:
 	float _elapsed = 0.f;
 };
 
+enum WalkToState {
+	kWalking,
+	kArrived
+};
+
 class WalkTo : public Motor {
 public:
 	WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing = 0);
-	virtual void disable() override;
+	void disable() override;
 
 	const Common::Array<Vector2i> &getPath() const { return _path; }
 
 private:
 	void actorArrived();
-	virtual void update(float elapsed) override;
+	void update(float elapsed) override;
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _obj;
 	Common::Array<Vector2i> _path;
 	int _facing = 0;
 	float _wsd;
+	WalkToState _state = kWalking;
 };
 
 class TextNode;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index df2f1f3ad6b..7e5ca04d1e2 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -396,7 +396,7 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 	}
 }
 
-static void disableMotor(Motor *motor) {
+static void disableMotor(Common::SharedPtr<Motor> motor) {
 	if (motor)
 		motor->disable();
 }
@@ -407,7 +407,7 @@ void Object::stopObjectMotors() {
 	disableMotor(_moveTo);
 	disableMotor(_walkTo);
 	disableMotor(_talking);
-	disableMotor(_blink.get());
+	disableMotor(_blink);
 	disableMotor(_turnTo);
 	disableMotor(_shakeTo);
 	disableMotor(_jiggleTo);
@@ -529,14 +529,14 @@ void Object::stand() {
 	}                          \
 	_##motorTo = motorTo;
 
-void Object::setAlphaTo(Motor *alphaTo) { SET_MOTOR(alphaTo); }
-void Object::setRotateTo(Motor *rotateTo) { SET_MOTOR(rotateTo); }
-void Object::setMoveTo(Motor *moveTo) { SET_MOTOR(moveTo); }
-void Object::setWalkTo(Motor *walkTo) { SET_MOTOR(walkTo); }
-void Object::setReach(Motor *reach) { SET_MOTOR(reach); }
-void Object::setTalking(Motor *talking) { SET_MOTOR(talking); }
-void Object::setTurnTo(Motor *turnTo) { SET_MOTOR(turnTo); }
-void Object::setShakeTo(Motor *shakeTo) { SET_MOTOR(shakeTo); }
+void Object::setAlphaTo(Common::SharedPtr<Motor> alphaTo) { SET_MOTOR(alphaTo); }
+void Object::setRotateTo(Common::SharedPtr<Motor> rotateTo) { SET_MOTOR(rotateTo); }
+void Object::setMoveTo(Common::SharedPtr<Motor> moveTo) { SET_MOTOR(moveTo); }
+void Object::setWalkTo(Common::SharedPtr<Motor> walkTo) { SET_MOTOR(walkTo); }
+void Object::setReach(Common::SharedPtr<Motor> reach) { SET_MOTOR(reach); }
+void Object::setTalking(Common::SharedPtr<Motor> talking) { SET_MOTOR(talking); }
+void Object::setTurnTo(Common::SharedPtr<Motor> turnTo) { SET_MOTOR(turnTo); }
+void Object::setShakeTo(Common::SharedPtr<Motor> shakeTo) { SET_MOTOR(shakeTo); }
 
 void Object::update(float elapsedSec) {
 	if (_dependentObj)
@@ -743,7 +743,7 @@ void Object::walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing) {
 	if (!obj->_walkTo || (!obj->_walkTo->isEnabled())) {
 		obj->play(obj->getAnimName(WALK_ANIMNAME), true);
 	}
-	obj->_walkTo = new WalkTo(obj, pos, facing);
+	obj->_walkTo = Common::SharedPtr<WalkTo>(new WalkTo(obj, pos, facing));
 }
 
 // Walks an actor to the `obj` and then faces it.
@@ -763,7 +763,7 @@ void Object::turn(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj
 }
 
 void Object::jiggle(float amount) {
-	_jiggleTo = new Jiggle(_node.get(), amount);
+	_jiggleTo = Common::SharedPtr<Jiggle>(new Jiggle(_node.get(), amount));
 }
 
 void Object::inventoryScrollUp() {
@@ -778,9 +778,9 @@ void Object::inventoryScrollDown() {
 }
 
 void TalkingState::say(const Common::StringArray &texts, Common::SharedPtr<Object> obj) {
-	Talking *talking = static_cast<Talking *>(obj->getTalking());
+	Talking *talking = static_cast<Talking *>(obj->getTalking().get());
 	if (!talking) {
-		obj->setTalking(new Talking(obj, texts, _color));
+		obj->setTalking(Common::SharedPtr<Talking>(new Talking(obj, texts, _color)));
 	} else {
 		talking->append(texts);
 	}
diff --git a/engines/twp/object.h b/engines/twp/object.h
index a6f0b36c2bf..a2f68a838cc 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -181,22 +181,22 @@ public:
 
 	void update(float elapsedSec);
 
-	void setAlphaTo(Motor *alphaTo);
-	void setRotateTo(Motor *rotateTo);
-	void setMoveTo(Motor *moveTo);
-	void setWalkTo(Motor *walkTo);
-	void setReach(Motor *reach);
-	Motor *getWalkTo() const { return _walkTo; }
-	Motor *getReach() const { return _reach; }
+	void setAlphaTo(Common::SharedPtr<Motor> alphaTo);
+	void setRotateTo(Common::SharedPtr<Motor> rotateTo);
+	void setMoveTo(Common::SharedPtr<Motor> moveTo);
+	void setWalkTo(Common::SharedPtr<Motor> walkTo);
+	void setReach(Common::SharedPtr<Motor> reach);
+	Common::SharedPtr<Motor> getWalkTo() { return _walkTo; }
+	Common::SharedPtr<Motor> getReach() { return _reach; }
 	static void walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing = 0);
 	static void walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
-	void setTalking(Motor *talking);
-	void setBlink(Motor *blink);
-	void setTurnTo(Motor *turnTo);
-	void setShakeTo(Motor *shakeTo);
+	void setTalking(Common::SharedPtr<Motor> talking);
+	void setBlink(Common::SharedPtr<Motor> blink);
+	void setTurnTo(Common::SharedPtr<Motor> turnTo);
+	void setShakeTo(Common::SharedPtr<Motor> shakeTo);
 
-	Motor *getTalking() { return _talking; }
+	Common::SharedPtr<Motor> getTalking() { return _talking; }
 	void stopTalking();
 	static void say(Common::SharedPtr<Object> obj, const Common::StringArray &texts, Color color);
 
@@ -269,16 +269,16 @@ public:
 	bool _jiggle = false;
 
 private:
-	Motor *_alphaTo = nullptr;
-	Motor *_rotateTo = nullptr;
-	Motor *_moveTo = nullptr;
-	Motor *_walkTo = nullptr;
-	Motor *_reach = nullptr;
-	Motor *_talking = nullptr;
+	Common::SharedPtr<Motor> _alphaTo;
+	Common::SharedPtr<Motor> _rotateTo;
+	Common::SharedPtr<Motor> _moveTo;
+	Common::SharedPtr<Motor> _walkTo;
+	Common::SharedPtr<Motor> _reach;
+	Common::SharedPtr<Motor> _talking;
 	Common::SharedPtr<Blink> _blink;
-	Motor *_turnTo = nullptr;
-	Motor *_shakeTo = nullptr;
-	Motor *_jiggleTo = nullptr;
+	Common::SharedPtr<Motor> _turnTo;
+	Common::SharedPtr<Motor> _shakeTo;
+	Common::SharedPtr<Motor> _jiggleTo;
 	TalkingState _talkingState;
 };
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 057305241f9..6a90f355f9b 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -303,7 +303,7 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && (SQ_FAILED(sqget(v, 5, interpolation))))
 			interpolation = 0;
-		obj->setAlphaTo(new AlphaTo(t, obj, alpha, intToInterpolationMethod(interpolation)));
+		obj->setAlphaTo(Common::SharedPtr<AlphaTo>(new AlphaTo(t, obj, alpha, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }
@@ -526,7 +526,7 @@ static SQInteger objectMoveTo(HSQUIRRELVM v) {
 		if ((sq_gettop(v) >= 6) && SQ_FAILED(sqget(v, 6, interpolation)))
 			interpolation = 0;
 		Math::Vector2d destPos = Math::Vector2d(x, y);
-		obj->setMoveTo(new MoveTo(duration, obj, destPos, intToInterpolationMethod(interpolation)));
+		obj->setMoveTo(Common::SharedPtr<MoveTo>(new MoveTo(duration, obj, destPos, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }
@@ -568,7 +568,7 @@ static SQInteger objectOffsetTo(HSQUIRRELVM v) {
 		if ((sq_gettop(v) >= 6) && (SQ_FAILED(sq_getinteger(v, 6, &interpolation))))
 			interpolation = 0;
 		Math::Vector2d destPos(x, y);
-		obj->setMoveTo(new OffsetTo(duration, obj, destPos, intToInterpolationMethod(interpolation)));
+		obj->setMoveTo(Common::SharedPtr<OffsetTo>(new OffsetTo(duration, obj, destPos, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }
@@ -700,7 +700,7 @@ static SQInteger objectRotateTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
-		obj->setRotateTo(new RotateTo(duration, obj->_node.get(), rotation, intToInterpolationMethod(interpolation)));
+		obj->setRotateTo(Common::SharedPtr<RotateTo>(new RotateTo(duration, obj->_node.get(), rotation, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }
@@ -729,7 +729,7 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
-		obj->setRotateTo(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation)));
+		obj->setRotateTo(Common::SharedPtr<ScaleTo>(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }
@@ -1028,7 +1028,7 @@ static SQInteger shakeObject(HSQUIRRELVM v) {
 	float amount;
 	if (SQ_FAILED(sqget(v, 3, amount)))
 		return sq_throwerror(v, "failed to get amount");
-	obj->setShakeTo(new Shake(obj->_node.get(), amount));
+	obj->setShakeTo(Common::SharedPtr<Shake>(new Shake(obj->_node.get(), amount)));
 	return 0;
 }
 
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index d1b0a39e9b9..e0dfcf22a3e 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -120,7 +120,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
-		const WalkTo *walkTo = (WalkTo *)actor->getWalkTo();
+		const WalkTo *walkTo = (WalkTo *)actor->getWalkTo().get();
 		const Common::Array<Vector2i> &path = walkTo->getPath();
 		if (path.size() > 0) {
 			Common::Array<Vertex> vertices;


Commit: 4f2d9911702faecf2dbf44f015b41cf480472c2d
    https://github.com/scummvm/scummvm/commit/4f2d9911702faecf2dbf44f015b41cf480472c2d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix various leaks

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/audio.h
    engines/twp/camera.h
    engines/twp/dialog.h
    engines/twp/genlib.cpp
    engines/twp/hud.h
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/thread.h
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index be56221e514..6314e923518 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -197,7 +197,7 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	Common::SharedPtr<Object> obj = nullptr;
+	Common::SharedPtr<Object> obj;
 	if (sq_gettop(v) == 3)
 		obj = sqobj(v, 3);
 	if (!obj)
@@ -562,7 +562,7 @@ static SQInteger actorTalkColors(HSQUIRRELVM v) {
 // actorTalking()
 // actorTalking(vo)
 static SQInteger actorTalking(HSQUIRRELVM v) {
-	Common::SharedPtr<Object> actor = nullptr;
+	Common::SharedPtr<Object> actor;
 	if (sq_gettop(v) == 2) {
 		actor = sqobj(v, 2);
 		if (!actor) {
@@ -708,7 +708,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 //}
 static SQInteger actorWalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	Common::SharedPtr<Object> actor = nullptr;
+	Common::SharedPtr<Object> actor;
 	if (nArgs == 1) {
 		actor = g_engine->_actor;
 	} else if (nArgs == 2) {
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index cf3b62b7427..41d5ce9f9d4 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -73,7 +73,7 @@ private:
 
 struct AudioSlot {
 	Audio::SoundHandle handle;                           // handle returned when this sound has been played
-	Common::SharedPtr<SoundDefinition> sndDef = nullptr; // sound definition associated to this slot
+	Common::SharedPtr<SoundDefinition> sndDef; // sound definition associated to this slot
 	SoundStream stream;                                  // audio stream
 	bool busy = false;                                   // is sound active
 	float volume = 1.f;                                  // actual volume for this slot
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
index 09001659490..e6008c64387 100644
--- a/engines/twp/camera.h
+++ b/engines/twp/camera.h
@@ -119,7 +119,7 @@ private:
 	float _elapsed = 0.f;
 	float _time = 0.f;
 	Common::SharedPtr<Room> _room;
-	Common::SharedPtr<Object> _follow = nullptr;
+	Common::SharedPtr<Object> _follow;
 	EasingFunc_t _function = {&linear};
 };
 } // namespace Twp
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 6266ea07631..232219a1e20 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -45,7 +45,7 @@ public:
 public:
 	bool _isValid = false;
 	Text _text;
-	Common::SharedPtr<YStatement> _stmt = nullptr;
+	Common::SharedPtr<YStatement> _stmt;
 	Dialog *_dlg = nullptr;
 };
 
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 60a5b2bed37..002d0c2eca8 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -548,8 +548,8 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 		return 0;
 	}
 
-	Common::SharedPtr<Object> obj1 = nullptr;
-	Common::SharedPtr<Object> obj2 = nullptr;
+	Common::SharedPtr<Object> obj1;
+	Common::SharedPtr<Object> obj2;
 	if (nArgs >= 3) {
 		obj1 = sqobj(v, 3);
 		if (!obj1)
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index a8888a9165a..f099054651b 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -66,7 +66,7 @@ public:
 	VerbUiColors verbUiColors;
 	Verb verbs[MAX_VERBS];
 	bool selectable = false;
-	Common::SharedPtr<Object> actor = nullptr;
+	Common::SharedPtr<Object> actor;
 
 public:
 	ActorSlot();
@@ -120,7 +120,7 @@ private:
 
 public:
 	ActorSlot _actorSlots[NUMACTORS];
-	Common::SharedPtr<Object> _actor = nullptr;
+	Common::SharedPtr<Object> _actor;
 	VerbRect _verbRects[NUMVERBS];
 	Verb _verb;
 	HudShader _shader;
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 96fd9011633..01dd215a3ce 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -101,7 +101,7 @@ private:
 	virtual void update(float elasped) override;
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _obj;
 	Tween<Math::Vector2d> _tween;
 };
 
@@ -114,7 +114,7 @@ private:
 	virtual void update(float elasped) override;
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _obj;
 	Tween<Math::Vector2d> _tween;
 };
 
@@ -127,7 +127,7 @@ private:
 	virtual void update(float elasped) override;
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _obj;
 	Tween<float> _tween;
 };
 
@@ -210,8 +210,8 @@ private:
 	void playReachAnim();
 
 private:
-	Common::SharedPtr<Object> _actor = nullptr;
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _actor;
+	Common::SharedPtr<Object> _obj;
 	int _state = 0;
 	float _elapsed = 0.f;
 };
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 7e5ca04d1e2..31b6d9b837b 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -68,7 +68,7 @@ public:
 	}
 
 private:
-	Common::SharedPtr<Object> _obj = nullptr;
+	Common::SharedPtr<Object> _obj;
 	BlinkState _state = BlinkState::Closed;
 	float _min = 0.f;
 	float _max = 0.f;
diff --git a/engines/twp/object.h b/engines/twp/object.h
index a2f68a838cc..58102b61806 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -75,8 +75,8 @@ struct VerbId {
 class Object;
 struct Sentence {
 	VerbId verb;
-	Common::SharedPtr<Object> noun1 = nullptr;
-	Common::SharedPtr<Object> noun2 = nullptr;
+	Common::SharedPtr<Object> noun1;
+	Common::SharedPtr<Object> noun2;
 	bool enabled = false;
 };
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 6a90f355f9b..ac2e3ccf5d4 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -924,7 +924,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 		sqgetf(o, "name", name);
 		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_engine->_textDb.getText(name).c_str()).c_str());
 	}
-	Common::SharedPtr<Object> actor = nullptr;
+	Common::SharedPtr<Object> actor;
 	if (sq_gettop(v) >= 3) {
 		actor = sqactor(v, 3);
 		if (!actor)
diff --git a/engines/twp/room.h b/engines/twp/room.h
index 0efa660053c..daa287a7a2a 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -97,7 +97,7 @@ struct Lights {
 struct ScalingTrigger {
 	ScalingTrigger(Common::SharedPtr<Object>  obj, Scaling* scaling);
 
-	Common::SharedPtr<Object>  _obj = nullptr;
+	Common::SharedPtr<Object>  _obj;
 	Scaling* _scaling = nullptr;
 };
 
@@ -150,8 +150,8 @@ public:
 	Common::SharedPtr<Scene> _scene;
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
-	Motor* _overlayTo = nullptr;
-	Motor* _rotateTo = nullptr;
+	Common::SharedPtr<Motor> _overlayTo;
+	Common::SharedPtr<Motor> _rotateTo;
 	float _rotation = 0.f;
 	PathFinder _pathFinder;
 };
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index e6f794ee615..674821abb47 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -489,7 +489,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
 		debugC(kDebugRoomScript, "start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
-		g_engine->_room->_overlayTo = new OverlayTo(duration, room, Color::fromRgba(endColor));
+		g_engine->_room->_overlayTo = Common::SharedPtr<OverlayTo>(new OverlayTo(duration, room, Color::fromRgba(endColor)));
 	}
 	return 0;
 }
@@ -498,7 +498,7 @@ static SQInteger roomRotateTo(HSQUIRRELVM v) {
 	float rotation;
 	if (SQ_FAILED(sqget(v, 2, rotation)))
 		return sq_throwerror(v, "failed to get rotation");
-	g_engine->_room->_rotateTo = new RoomRotateTo(g_engine->_room, rotation);
+	g_engine->_room->_rotateTo = Common::SharedPtr<RoomRotateTo>(new RoomRotateTo(g_engine->_room, rotation));
 	return 0;
 }
 
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index f5388dd26ef..966a71e9bbb 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -303,9 +303,10 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 	clear();
 	for (size_t i = 0; i < _anim->layers.size(); i++) {
 		const ObjectAnimation &layer = _anim->layers[i];
-		Anim *node = new Anim(_obj);
+		Common::SharedPtr<Anim> node(new Anim(_obj));
+		_anims.push_back(node);
 		node->setAnim(&layer, fps, loop, instant);
-		addChild(node);
+		addChild(node.get());
 	}
 }
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 9910cd3810d..9661226a7ab 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -182,6 +182,7 @@ private:
 	bool _loop = false;
 	bool _instant = false;
 	Object *_obj = nullptr;
+	Common::Array<Common::SharedPtr<Anim> > _anims;
 };
 
 class ActorNode final : public Node {
@@ -192,7 +193,7 @@ public:
 	Math::Vector2d getScale() const override final;
 
 private:
-	Common::SharedPtr<Object> _object = nullptr;
+	Common::SharedPtr<Object> _object;
 };
 
 class TextNode final : public Node {
@@ -309,10 +310,10 @@ private:
 	void drawSprite(SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
 
 private:
-	Common::SharedPtr<Object>  _actor = nullptr;
+	Common::SharedPtr<Object>  _actor;
     Color _backColor, _verbNormal;
     bool _down = false;
-    Common::SharedPtr<Object>  _obj = nullptr;
+    Common::SharedPtr<Object>  _obj;
 	Common::Rect _itemRects[NUMOBJECTS];
 	Common::Rect _arrowUpRect;
 	Common::Rect _arrowDnRect;
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index d6018a1349a..da1c61a8fa1 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -132,7 +132,7 @@ private:
 	CutsceneState _state;
 	bool _showCursor = false;
 	InputStateFlag _inputState = (InputStateFlag)0;
-	Common::SharedPtr<Object> _actor = nullptr;
+	Common::SharedPtr<Object> _actor;
 };
 
 } // namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 9e642ca08fb..e287b5d15ec 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -182,12 +182,12 @@ public:
 	Common::Array<Common::SharedPtr<ThreadBase> > _threads;
 	Common::Array<Common::SharedPtr<Task> > _tasks;
 	Common::Array<Common::SharedPtr<Callback> > _callbacks;
-	Common::SharedPtr<Object> _actor = nullptr;
-	Common::SharedPtr<Object> _followActor = nullptr;
+	Common::SharedPtr<Object> _actor;
+	Common::SharedPtr<Object> _followActor;
 	Common::SharedPtr<Room> _room;
 	float _time = 0.f;
-	Common::SharedPtr<Object> _noun1 = nullptr;
-	Common::SharedPtr<Object> _noun2 = nullptr;
+	Common::SharedPtr<Object> _noun1;
+	Common::SharedPtr<Object> _noun2;
 	UseFlag _useFlag;
 	HSQOBJECT _defaultObj;
 	bool _walkFastState = false;


Commit: ee6736d7b69ce9d9616bb9b9ac4c78422d48febd
    https://github.com/scummvm/scummvm/commit/ee6736d7b69ce9d9616bb9b9ac4c78422d48febd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix leaks with ggpack and savegames

Changed paths:
    engines/twp/ggpack.cpp
    engines/twp/object.cpp
    engines/twp/room.cpp
    engines/twp/savegame.cpp
    engines/twp/savegame.h
    engines/twp/spritesheet.cpp
    imgui.ini


diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 19da3b38e0a..544fdd248d7 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -644,7 +644,7 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 	MemStream ms;
 	ms.open(buffer.data(), entriesSize);
 	GGHashMapDecoder tblDecoder;
-	Common::JSONValue *value = tblDecoder.open(&ms);
+	Common::ScopedPtr<Common::JSONValue> value(tblDecoder.open(&ms));
 	if (!value)
 		return false;
 
@@ -658,7 +658,6 @@ bool GGPackDecoder::open(Common::SeekableReadStream *s, const XorKey &key) {
 		_entries[filename] = GGPackEntry{offset, size};
 		debug(kDebugGGPack, "filename: %s, off: %d, size: %d", filename.c_str(), offset, size);
 	}
-	delete value;
 
 	return true;
 }
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 31b6d9b837b..623d6450f9c 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -89,15 +89,15 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 }
 
 Object::~Object() {
+	_nodeAnim->remove();
+	_node->remove();
+
 	if (_layer) {
 		size_t i = find(_layer->_objects, this);
 		if (i != (size_t)-1) {
 			_layer->_objects.remove_at(i);
 		}
 	}
-
-	_nodeAnim->remove();
-	_node->remove();
 }
 
 Common::SharedPtr<Object> Object::createActor() {
@@ -499,7 +499,7 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 	entry.open(g_engine->_pack, name + ".json");
 
 	GGHashMapDecoder dec;
-	Common::JSONValue *json = dec.open(&entry);
+	Common::ScopedPtr<Common::JSONValue> json(dec.open(&entry));
 	if (!json) {
 		warning("Costume %s(%s) for actor %s not found", name.c_str(), sheet.c_str(), _key.c_str());
 		return;
@@ -515,8 +515,6 @@ void Object::setCostume(const Common::String &name, const Common::String &sheet)
 		_sheet = sheet;
 	}
 	stand();
-
-	delete json;
 }
 
 void Object::stand() {
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 34851418dd6..fae3351aa8a 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -254,7 +254,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 
 void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	GGHashMapDecoder d;
-	Common::JSONValue *value = d.open(&s);
+	Common::ScopedPtr<Common::JSONValue> value(d.open(&s));
 	// debugC(kDebugGame, "Room: %s", value->stringify().c_str());
 	const Common::JSONObject &jRoom = value->asObject();
 
@@ -375,8 +375,6 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 		width += g_engine->_resManager.spriteSheet(room->_sheet)->frameTable[name].sourceSize.getX();
 	}
 	room->_roomSize.setX(width);
-
-	delete value;
 }
 
 Common::SharedPtr<Layer> Room::layer(int zsort) {
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index e10793d629d..9b30f8e5bff 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -392,7 +392,6 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	long long int version = json["version"]->asIntegerNumber();
 	if (version != 2) {
 		error("Cannot load savegame version %lld", version);
-		delete savegame.jSavegame;
 		return false;
 	}
 
@@ -417,7 +416,6 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 
 	sqcall("postLoad");
 
-	delete savegame.jSavegame;
 	return true;
 }
 
@@ -436,7 +434,7 @@ bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &
 		return false;
 
 	GGHashMapDecoder decoder;
-	savegame.jSavegame = decoder.open(&ms);
+	savegame.jSavegame.reset(decoder.open(&ms));
 	if (!savegame.jSavegame)
 		return false;
 
diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
index 816c5014dbb..e1c76b7e130 100644
--- a/engines/twp/savegame.h
+++ b/engines/twp/savegame.h
@@ -32,7 +32,7 @@ struct SaveGame {
 	int64_t time = 0;
 	int64_t gameTime = 0;
 	bool easyMode = false;
-	Common::JSONValue *jSavegame = nullptr;
+	Common::ScopedPtr<Common::JSONValue> jSavegame;
 };
 
 class SaveGameManager {
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index 9f217027079..804602e37ac 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -45,7 +45,7 @@ static void parseFrame(const Common::String &key, const Common::JSONObject &valu
 }
 
 void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
-	Common::JSONValue *json = Common::JSON::parse(contents.c_str());
+	Common::ScopedPtr<Common::JSONValue> json(Common::JSON::parse(contents.c_str()));
 	const Common::JSONObject &obj = json->asObject()["frames"]->asObject();
 	for (auto it = obj.begin(); it != obj.end(); it++) {
 		parseFrame(it->_key, it->_value->asObject(), frameTable[it->_key]);
@@ -53,8 +53,6 @@ void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
 
 	const Common::JSONObject& jMeta = json->asObject()["meta"]->asObject();
 	meta.image = jMeta["image"]->asString();
-
-	delete json;
 }
 
 } // namespace Twp
diff --git a/imgui.ini b/imgui.ini
index f886929d913..46cf3e5bb95 100644
--- a/imgui.ini
+++ b/imgui.ini
@@ -3,15 +3,16 @@ Pos=60,60
 Size=400,400
 
 [Window][General]
-Pos=79,25
-Size=363,483
+Pos=81,33
+Size=419,482
+Collapsed=1
 
 [Window][Objects]
 Pos=60,60
 Size=520,600
 
 [Window][Sounds]
-Pos=60,60
+Pos=114,67
 Size=520,600
 
 [Table][0xBFA6B9D2,7]


Commit: a093828a1ad46d389fb3776c82a4ef75a8950ced
    https://github.com/scummvm/scummvm/commit/a093828a1ad46d389fb3776c82a4ef75a8950ced
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add missing int function

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 002d0c2eca8..d5a4f29be5c 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -930,7 +930,7 @@ void sqgame_register_genlib(HSQUIRRELVM v) {
 	regFunc(v, getPrivatePref, _SC("getPrivatePref"));
 	regFunc(v, incutscene, _SC("incutscene"));
 	regFunc(v, indialog, _SC("indialog"));
-	regFunc(v, integer, _SC("integer"));
+	regFunc(v, integer, _SC("int"));
 	regFunc(v, in_array, _SC("in_array"));
 	regFunc(v, is_array, _SC("is_array"));
 	regFunc(v, is_function, _SC("is_function"));


Commit: c8d7559169c17b1f98d677e8f82d782a53869402
    https://github.com/scummvm/scummvm/commit/c8d7559169c17b1f98d677e8f82d782a53869402
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix several issues

Changed paths:
    engines/twp/callback.cpp
    engines/twp/callback.h
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/vm.cpp


diff --git a/engines/twp/callback.cpp b/engines/twp/callback.cpp
index cd3a229189c..db57698c6ed 100644
--- a/engines/twp/callback.cpp
+++ b/engines/twp/callback.cpp
@@ -28,11 +28,17 @@ Callback::Callback(int id, float duration, const Common::String &name, const Com
 	: _id(id), _duration(duration), _name(name), _args(args) {
 }
 
+void Callback::remove() {
+	_dead = true;
+}
+
 void Callback::call() {
 	sqcall(_name.c_str(), _args);
 }
 
 bool Callback::update(float elapsed) {
+	if (_dead)
+		return true;
 	_elapsed += elapsed;
 	bool result = _elapsed > _duration;
 	if (result)
diff --git a/engines/twp/callback.h b/engines/twp/callback.h
index 682fc820fdd..a51ea0a1da4 100644
--- a/engines/twp/callback.h
+++ b/engines/twp/callback.h
@@ -31,7 +31,9 @@ namespace Twp {
 class Callback {
 public:
 	Callback(int id, float duration, const Common::String& name, const Common::Array<HSQOBJECT>& args);
+
 	bool update(float elapsed);
+	void remove();
 
 	Common::String getName() const { return _name; }
 	int getId() const { return _id; }
@@ -45,6 +47,7 @@ private:
 	Common::Array<HSQOBJECT> _args;
 	float _duration = 0.f;
 	float _elapsed = 0.f;
+	bool _dead = false;
 
 public:
 	void call();
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 623d6450f9c..627c8daa22e 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -89,7 +89,7 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 }
 
 Object::~Object() {
-	_nodeAnim->remove();
+	if(_nodeAnim) _nodeAnim->remove();
 	_node->remove();
 
 	if (_layer) {
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index ac2e3ccf5d4..84dd84f8158 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -153,8 +153,13 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 static SQInteger deleteObject(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		size_t i = find(obj->_layer->_objects, obj);
-		obj->_layer->_objects.remove_at(i);
+		if (obj->_layer) {
+			obj->_node->remove();
+			size_t i = find(obj->_layer->_objects, obj);
+			if (i != (size_t)-1) {
+				obj->_layer->_objects.remove_at(i);
+			}
+		}
 	}
 	return 0;
 }
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index fae3351aa8a..c2a2bc1b3a5 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -192,8 +192,6 @@ Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const
 	obj->_layer = layer(0);
 	obj->setState(0);
 
-	g_engine->_objects.push_back(obj);
-
 	return obj;
 }
 
@@ -213,11 +211,11 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 	setId(obj->_table, newObjId());
 	debugC(kDebugGame, "Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
-	obj->_node->setName(obj->_key);
 
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
 	Common::SharedPtr<TextNode> node(new TextNode());
+	node->setName(obj->_key);
 	node->setText(txt);
 	float y = 0.5f;
 	switch (vAlign) {
@@ -242,13 +240,12 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 		node->setAnchorNorm(Math::Vector2d(1.f, y));
 		break;
 	}
+	obj->_nodeAnim = nullptr;
 	obj->_node = node;
 	layer(0)->_objects.push_back(obj);
 	layer(0)->_node->addChild(obj->_node.get());
 	obj->_layer = layer(0);
 
-	g_engine->_objects.push_back(obj);
-
 	return obj;
 }
 
@@ -328,7 +325,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_node = objNode;
-			obj->_nodeAnim = Common::SharedPtr<Anim>(new Anim(obj.get()));
+			obj->_nodeAnim.reset(new Anim(obj.get()));
 			obj->_node->addChild(obj->_nodeAnim.get());
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
diff --git a/engines/twp/room.h b/engines/twp/room.h
index daa287a7a2a..a502987f8e8 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -146,7 +146,6 @@ public:
 	Common::Array<Common::SharedPtr<Object> > _triggers; // Triggers currently enabled in the room
 	Common::Array<ScalingTrigger> _scalingTriggers; // Scaling Triggers of the room
 	bool _pseudo = false;
-	Common::Array<Common::SharedPtr<Object> > _objects;
 	Common::SharedPtr<Scene> _scene;
 	OverlayNode _overlayNode;	// Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 674821abb47..6b64ee4687c 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -264,7 +264,7 @@ static SQInteger definePseudoRoom(HSQUIRRELVM v) {
 	if (SQ_FAILED(sq_getstackobj(v, -1, &table)))
 		return sq_throwerror(v, "failed to get room table");
 
-	Common::SharedPtr<Room> room = g_engine->defineRoom(name, table, true);
+	Common::SharedPtr<Room> room(g_engine->defineRoom(name, table, true));
 	debugC(kDebugRoomScript, "Define pseudo room: %s", name);
 	g_engine->_rooms.push_back(room);
 	sqpush(v, room->_table);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 9b30f8e5bff..e0e0d4fc2df 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -333,7 +333,7 @@ static void loadObject(Common::SharedPtr<Object> obj, const Common::JSONObject &
 
 static void loadPseudoObjects(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
-		Common::SharedPtr<Object> o = object(room, it->_key);
+		Common::SharedPtr<Object> o(object(room, it->_key));
 		if (!o)
 			warning("load: room '%s' object '%s' not loaded because it has not been found", room->_name.c_str(), it->_key.c_str());
 		else
@@ -347,7 +347,7 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 			loadPseudoObjects(room, it->_value->asObject());
 		} else {
 			if (!it->_key.hasPrefix("_")) {
-				Common::SharedPtr<Object> o = object(room, it->_key);
+				Common::SharedPtr<Object> o(object(room, it->_key));
 				if (!o) {
 					HSQOBJECT tmp;
 					toSquirrel(it->_value, tmp);
@@ -395,6 +395,8 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 		return false;
 	}
 
+	debug("%s", savegame.jSavegame->stringify().c_str());
+
 	sqcall("preLoad");
 	loadGameScene(json["gameScene"]->asObject());
 	loadDialog(json["gameScene"]->asObject());
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 966a71e9bbb..94411b3878e 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -56,7 +56,9 @@ Node::Node(const Common::String &name, Math::Vector2d scale, Color color)
 	  _scale(scale) {
 }
 
-Node::~Node() {}
+Node::~Node() {
+	remove();
+}
 
 void Node::addChild(Node *child) {
 	if (child->_parent == this)
@@ -90,7 +92,9 @@ int Node::find(Node *other) {
 
 void Node::removeChild(Node *child) {
 	int i = find(child);
-	_children.remove_at(i);
+	if(i != -1) {
+		_children.remove_at(i);
+	}
 	child->_parent = nullptr;
 }
 
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 8e61b1058e9..535458ce4f4 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -459,7 +459,10 @@ Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v) {
 		}
 	}
 
-	return *Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), GetThread(v));
+	auto it = Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), GetThread(v));
+	if(it != g_engine->_threads.end())
+		return *it;
+	return nullptr;
 }
 
 static void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::SharedPtr<SoundDefinition> > &arr) {
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index bfa0053eae0..6a105709aeb 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -725,7 +725,7 @@ static SQInteger removeCallback(HSQUIRRELVM v) {
 	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
 		Common::SharedPtr<Callback> cb = g_engine->_callbacks[i];
 		if (cb->getId() == id) {
-			g_engine->_callbacks.remove_at(i);
+			cb->remove();
 			return 0;
 		}
 	}
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 6e19c09723f..2711d6c9c9b 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -185,7 +185,15 @@ void Cutscene::stop() {
 	Common::SharedPtr<ThreadBase> t = sqthread(_parentThreadId);
 	if (t && t->getId())
 		t->unpause();
-	sq_suspendvm(getThread());
+	HSQUIRRELVM thread = getThread();
+	if (thread)
+		sq_suspendvm(thread);
+}
+
+HSQUIRRELVM Cutscene::getThread() {
+	if (_threadObj._type != OT_THREAD)
+		return nullptr;
+	return _threadObj._unVal.pThread;
 }
 
 void Cutscene::checkEndCutsceneOverride() {
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index da1c61a8fa1..dbb70e72f0a 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -106,13 +106,13 @@ class Object;
 class Cutscene final : public ThreadBase {
 public:
 	Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
-	virtual ~Cutscene() override final;
+	~Cutscene() override final;
 
 	void start();
-	virtual bool isGlobal() override final { return false; }
-	virtual HSQUIRRELVM getThread() override final { return _threadObj._unVal.pThread; }
-	virtual bool update(float elapsed) override final;
-	virtual void stop() override final;
+	bool isGlobal() override final { return false; }
+	HSQUIRRELVM getThread() override final;
+	bool update(float elapsed) override final;
+	void stop() override final;
 
 	bool hasOverride() const;
 	inline void cutsceneOverride() { _state = csOverride; }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1152a697c5b..2fff9dfc07d 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -502,7 +502,7 @@ void TwpEngine::update(float elapsed) {
 	Common::Array<Common::SharedPtr<ThreadBase> > threadsToRemove;
 
 	for (auto it = threads.begin(); it != threads.end(); it++) {
-		Common::SharedPtr<ThreadBase> thread = *it;
+		Common::SharedPtr<ThreadBase> thread(*it);
 		if (thread->update(elapsed)) {
 			threadsToRemove.push_back(thread);
 		}
@@ -510,7 +510,7 @@ void TwpEngine::update(float elapsed) {
 
 	// remove threads that are terminated
 	for (auto it = threadsToRemove.begin(); it != threadsToRemove.end(); it++) {
-		Common::SharedPtr<ThreadBase> thread = *it;
+		Common::SharedPtr<ThreadBase> thread(*it);
 		int i = find(_threads, *it);
 		if (i != -1) {
 			_threads.remove_at(i);
@@ -519,8 +519,8 @@ void TwpEngine::update(float elapsed) {
 
 	// update callbacks
 	for (auto it = _callbacks.begin(); it != _callbacks.end();) {
-		Common::SharedPtr<Callback> cb = *it;
-		if (cb->update(elapsed)) {
+		Common::SharedPtr<Callback> cb(*it);
+		if (!cb || cb->update(elapsed)) {
 			it = _callbacks.erase(it);
 			continue;
 		}
@@ -529,7 +529,7 @@ void TwpEngine::update(float elapsed) {
 
 	// update tasks
 	for (auto it = _tasks.begin(); it != _tasks.end();) {
-		Common::SharedPtr<Task> task = *it;
+		Common::SharedPtr<Task> task(*it);
 		if (task->update(elapsed)) {
 			it = _tasks.erase(it);
 			continue;
@@ -989,18 +989,7 @@ struct DefineObjectParams {
 
 static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 	DefineObjectParams *params = static_cast<DefineObjectParams *>(data);
-	HSQUIRRELVM v = params->v;
 	if (oTable._type == OT_TABLE) {
-		if (params->pseudo) {
-			// if it's a pseudo room we need to clone each object
-			sq_pushobject(v, oTable);
-			sq_clone(v, -1);
-			sq_getstackobj(v, -1, &oTable);
-			sq_addref(v, &oTable);
-			sq_pop(v, 2);
-			sqsetf(params->room->_table, k, oTable);
-		}
-
 		if (sqrawexists(oTable, "icon")) {
 			// Add inventory object to root table
 			debugC(kDebugGame, "Add %s to inventory", k.c_str());
@@ -1094,7 +1083,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Common::SharedPtr<Object> obj = layer->_objects[j];
-				if (!sqrawexists(table, obj->_key)) {
+				if (!sqrawexists(result->_table, obj->_key)) {
 					// this object does not exist, so create it
 					sq_newtable(v);
 					sq_getstackobj(v, -1, &obj->_table);
@@ -1113,6 +1102,16 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 					if (obj->_objType == otNone)
 						obj->setTouchable(false);
 				} else if (obj->_objType == otNone) {
+					if (pseudo) {
+						// if it's a pseudo room we need to clone each object
+						sqgetf(result->_table, obj->_key, obj->_table);
+						sq_pushobject(v, obj->_table);
+						sq_clone(v, -1);
+						sq_getstackobj(v, -1, &obj->_table);
+						sq_addref(v, &obj->_table);
+						sq_remove(v, -2);
+						sqsetf(result->_table, obj->_key, obj->_table);
+					}
 					obj->setTouchable(true);
 				}
 
@@ -1259,9 +1258,9 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 		for (size_t i = 0; i < _room->_layers.size(); i++) {
 			Common::SharedPtr<Layer> layer = _room->_layers[i];
 			for (auto it = layer->_objects.begin(); it != layer->_objects.end();) {
-				Common::SharedPtr<Object> obj = *it;
+				Common::SharedPtr<Object> obj(*it);
 				if (obj->_temporary) {
-					it = it = layer->_objects.erase(it);
+					it = layer->_objects.erase(it);
 					continue;
 				} else if (isActor(obj->getId()) && _actor != obj) {
 					obj->stopObjectMotors();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index e287b5d15ec..969565aae28 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -178,7 +178,6 @@ public:
 	ResManager _resManager;
 	Common::Array<Common::SharedPtr<Room> > _rooms;
 	Common::Array<Common::SharedPtr<Object> > _actors;
-	Common::Array<Common::SharedPtr<Object> > _objects;
 	Common::Array<Common::SharedPtr<ThreadBase> > _threads;
 	Common::Array<Common::SharedPtr<Task> > _tasks;
 	Common::Array<Common::SharedPtr<Callback> > _callbacks;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index 9612091a2ca..e969442e144 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -80,7 +80,7 @@ static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) {
 }
 
 Vm::Vm() {
-	gVm = v = sq_open(1024);
+	gVm = v = sq_open(1024 * 2);
 	sq_setcompilererrorhandler(v, errorHandler);
 	sq_newclosure(v, aux_printerror, 0);
 	sq_seterrorhandler(v);


Commit: ddcf22df7af057f985d2fffb11236e1ad04080f7
    https://github.com/scummvm/scummvm/commit/ddcf22df7af057f985d2fffb11236e1ad04080f7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix room FADE_WOBBLE

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/shaders.h
    engines/twp/twp.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 1e221e6d905..3b5c151c63f 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -298,10 +298,6 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, ui
 
 		_shader->_shader.use();
 
-		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-
-		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
-
 		GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 		GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * v_size, vertices, GL_STREAM_DRAW));
 		GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index cc08564dc98..6977cbc7984 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -61,12 +61,12 @@ public:
 	FadeShader();
 	virtual ~FadeShader() override;
 
-	virtual int getNumTextures() override;
-	virtual int getTexture(int index) override;
-	virtual int getTextureLoc(int index) override;
+	int getNumTextures() override;
+	int getTexture(int index) override;
+	int getTextureLoc(int index) override;
 
 private:
-	virtual void applyUniforms() final;
+	void applyUniforms() override;
 
 public:
 	FadeEffect _effect = FadeEffect::None;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 2fff9dfc07d..7bffa010513 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -607,21 +607,19 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	_shaderParams.updateShader();
 
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
-	bool flipY = _fadeShader->_effect == FadeEffect::Wobble;
 	Math::Vector2d camPos = _gfx.cameraPos();
-	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, flipY);
+	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect == FadeEffect::Wobble);
 
 	Texture *screenTexture = &renderTexture2;
 	if (_fadeShader->_effect != FadeEffect::None) {
 		// draw second room if any
 		_gfx.setRenderTarget(&renderTexture);
 		_gfx.use(nullptr);
-		_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 		_gfx.cameraPos(_fadeShader->_cameraPos);
-		_gfx.clear(Color(0, 0, 0));
+		_gfx.clear(Color(1, 0, 0));
 		if (_fadeShader->_room && _fadeShader->_effect == FadeEffect::Wobble) {
-			Math::Vector2d camSize = _fadeShader->_room->getScreenSize();
-			_gfx.camera(camSize);
+			Math::Vector2d screenSize = _fadeShader->_room->getScreenSize();
+			_gfx.camera(screenSize);
 			_fadeShader->_room->_scene->draw();
 		}
 
@@ -634,7 +632,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 		case FadeEffect::Wobble:
 			texture1 = &renderTexture;
 			texture2 = &renderTexture2;
-			screenTexture = &renderTexture;
+			screenTexture = &renderTexture3;
 			break;
 		case FadeEffect::In:
 			texture1 = &renderTexture,
@@ -670,7 +668,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	m2.setPosition(Math::Vector3d(SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f));
 	Math::Angle angle;
 	m.buildAroundZ(Math::Angle(-_room->_rotation));
-	_gfx.drawSprite(*screenTexture, Color(), m2 * m * m1, false, _fadeShader->_effect != FadeEffect::None);
+	_gfx.drawSprite(*screenTexture, Color(), m2 * m * m1, false, false);
 
 	// draw UI
 	_gfx.cameraPos(camPos);
@@ -1156,7 +1154,11 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 
 	// exit current room
 	exitRoom(_room);
-	_fadeShader->_effect = FadeEffect::None;
+
+	// reset fade effect if we change the room except for wobble effect
+	if(_fadeShader->_effect != FadeEffect::Wobble) {
+		_fadeShader->_effect = FadeEffect::None;
+	}
 
 	// sets the current room for scripts
 	sqsetf(sqrootTbl(v), "currentRoom", room->_table);
@@ -1362,7 +1364,7 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 	_fadeShader->_fadeToSepia = fadeToSep;
 	_fadeShader->_effect = effect;
 	_fadeShader->_room = _room;
-	_fadeShader->_cameraPos = cameraPos();
+	_fadeShader->_cameraPos = _gfx.cameraPos();
 	_fadeShader->_duration = duration;
 	_fadeShader->_movement = effect == FadeEffect::Wobble ? 0.005f : 0.f;
 	_fadeShader->_elapsed = 0.f;


Commit: 9eb8fe627ef8698a64b14127a71f532cbc522e6a
    https://github.com/scummvm/scummvm/commit/9eb8fe627ef8698a64b14127a71f532cbc522e6a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix scaleTo

Changed paths:
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 627c8daa22e..2112497d6bd 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -411,6 +411,7 @@ void Object::stopObjectMotors() {
 	disableMotor(_turnTo);
 	disableMotor(_shakeTo);
 	disableMotor(_jiggleTo);
+	disableMotor(_scaleTo);
 }
 
 void Object::setFacing(Facing facing) {
@@ -535,6 +536,7 @@ void Object::setReach(Common::SharedPtr<Motor> reach) { SET_MOTOR(reach); }
 void Object::setTalking(Common::SharedPtr<Motor> talking) { SET_MOTOR(talking); }
 void Object::setTurnTo(Common::SharedPtr<Motor> turnTo) { SET_MOTOR(turnTo); }
 void Object::setShakeTo(Common::SharedPtr<Motor> shakeTo) { SET_MOTOR(shakeTo); }
+void Object::setScaleTo(Common::SharedPtr<Motor> scaleTo) { SET_MOTOR(scaleTo); }
 
 void Object::update(float elapsedSec) {
 	if (_dependentObj)
@@ -557,6 +559,8 @@ void Object::update(float elapsedSec) {
 		_shakeTo->update(elapsedSec);
 	if (_jiggleTo)
 		_jiggleTo->update(elapsedSec);
+	if (_scaleTo)
+		_scaleTo->update(elapsedSec);
 
 	if (_nodeAnim)
 		_nodeAnim->update(elapsedSec);
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 58102b61806..a575fd65192 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -195,6 +195,7 @@ public:
 	void setBlink(Common::SharedPtr<Motor> blink);
 	void setTurnTo(Common::SharedPtr<Motor> turnTo);
 	void setShakeTo(Common::SharedPtr<Motor> shakeTo);
+	void setScaleTo(Common::SharedPtr<Motor> scaleTo);
 
 	Common::SharedPtr<Motor> getTalking() { return _talking; }
 	void stopTalking();
@@ -279,6 +280,7 @@ private:
 	Common::SharedPtr<Motor> _turnTo;
 	Common::SharedPtr<Motor> _shakeTo;
 	Common::SharedPtr<Motor> _jiggleTo;
+	Common::SharedPtr<Motor> _scaleTo;
 	TalkingState _talkingState;
 };
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 84dd84f8158..62964a51884 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -734,7 +734,7 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 		int interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
-		obj->setRotateTo(Common::SharedPtr<ScaleTo>(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation))));
+		obj->setScaleTo(Common::SharedPtr<ScaleTo>(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation))));
 	}
 	return 0;
 }


Commit: 2c38aa8ab7e673cfbc906a5ccea4e45f6bc68719
    https://github.com/scummvm/scummvm/commit/2c38aa8ab7e673cfbc906a5ccea4e45f6bc68719
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix screen upside down after fading

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7bffa010513..0cc2b8ecc14 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -608,7 +608,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	Math::Vector2d camPos = _gfx.cameraPos();
-	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect == FadeEffect::Wobble);
+	_gfx.drawSprite(renderTexture, Color(), Math::Matrix4(), false, _fadeShader->_effect != FadeEffect::None);
 
 	Texture *screenTexture = &renderTexture2;
 	if (_fadeShader->_effect != FadeEffect::None) {
@@ -616,7 +616,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 		_gfx.setRenderTarget(&renderTexture);
 		_gfx.use(nullptr);
 		_gfx.cameraPos(_fadeShader->_cameraPos);
-		_gfx.clear(Color(1, 0, 0));
+		_gfx.clear(Color(0, 0, 0));
 		if (_fadeShader->_room && _fadeShader->_effect == FadeEffect::Wobble) {
 			Math::Vector2d screenSize = _fadeShader->_room->getScreenSize();
 			_gfx.camera(screenSize);


Commit: 8f7b3034263d90ba9ea3e134688e6f5309860831
    https://github.com/scummvm/scummvm/commit/8f7b3034263d90ba9ea3e134688e6f5309860831
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Debug savegame only if channel activated

Changed paths:
    engines/twp/savegame.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index e0e0d4fc2df..03108dac774 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -395,7 +395,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 		return false;
 	}
 
-	debug("%s", savegame.jSavegame->stringify().c_str());
+	debugC(kDebugGame, "%s", savegame.jSavegame->stringify().c_str());
 
 	sqcall("preLoad");
 	loadGameScene(json["gameScene"]->asObject());


Commit: 4ed379403a8dc6da6593ca26612f6ca8f818a682
    https://github.com/scummvm/scummvm/commit/4ed379403a8dc6da6593ca26612f6ca8f818a682
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Input should be disabled if requested

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0cc2b8ecc14..9eff935a543 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -211,7 +211,7 @@ void TwpEngine::walkFast(bool state) {
 }
 
 void TwpEngine::clickedAt(Math::Vector2d scrPos) {
-	if (_room && _inputState.getInputActive() && !_actorSwitcher.isMouseOver()) {
+	if (_room && !_actorSwitcher.isMouseOver()) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		Common::SharedPtr<Object> obj = objAt(roomPos);
 
@@ -452,7 +452,7 @@ void TwpEngine::update(float elapsed) {
 			_sentence.setText(cursorText());
 
 			// call clickedAt if any button down
-			if ((_dialog.getState() == DialogState::None) && !_hud.isOver()) {
+			if ((_inputState.getInputActive() && _dialog.getState() == DialogState::None) && !_hud.isOver()) {
 				if (_cursor.isLeftDown() || _cursor.isRightDown()) {
 					clickedAt(scrPos);
 				} else if (_cursor.leftDown || _cursor.rightDown) {
@@ -1156,7 +1156,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 	exitRoom(_room);
 
 	// reset fade effect if we change the room except for wobble effect
-	if(_fadeShader->_effect != FadeEffect::Wobble) {
+	if (_fadeShader->_effect != FadeEffect::Wobble) {
 		_fadeShader->_effect = FadeEffect::None;
 	}
 


Commit: 866a7db57f34e37e612f0b2e3d9f8f5a18a240e3
    https://github.com/scummvm/scummvm/commit/866a7db57f34e37e612f0b2e3d9f8f5a18a240e3
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix impossible to pickup some objects

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 9eff935a543..2c430205432 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1374,7 +1374,7 @@ struct GetByZorder {
 	GetByZorder(Common::SharedPtr<Object> &result) : _result(result) { result = nullptr; }
 
 	bool operator()(Common::SharedPtr<Object> obj) {
-		if (obj->_node->getZSort() < _zOrder) {
+		if (obj->_node->getZSort() <= _zOrder) {
 			_result = obj;
 			_zOrder = obj->_node->getZSort();
 		}


Commit: 48171056b39e918ff554896bca2eb4793aa4b50f
    https://github.com/scummvm/scummvm/commit/48171056b39e918ff554896bca2eb4793aa4b50f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash in tasks

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 2c430205432..a2d3dbcccd8 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -511,8 +511,8 @@ void TwpEngine::update(float elapsed) {
 	// remove threads that are terminated
 	for (auto it = threadsToRemove.begin(); it != threadsToRemove.end(); it++) {
 		Common::SharedPtr<ThreadBase> thread(*it);
-		int i = find(_threads, *it);
-		if (i != -1) {
+		size_t i = find(_threads, *it);
+		if (i != (size_t)-1) {
 			_threads.remove_at(i);
 		}
 	}
@@ -528,15 +528,25 @@ void TwpEngine::update(float elapsed) {
 	}
 
 	// update tasks
+	Common::Array<Common::SharedPtr<Task> > tasks(_tasks);
+	Common::Array<Common::SharedPtr<Task> > tasksToRemove;
 	for (auto it = _tasks.begin(); it != _tasks.end();) {
 		Common::SharedPtr<Task> task(*it);
 		if (task->update(elapsed)) {
-			it = _tasks.erase(it);
-			continue;
+			tasksToRemove.push_back(task);
 		}
 		it++;
 	}
 
+	// remove tasks that are terminated
+	for (auto it = tasksToRemove.begin(); it != tasksToRemove.end(); it++) {
+		Common::SharedPtr<Task> task(*it);
+		size_t i = find(_tasks, *it);
+		if (i != (size_t)-1) {
+			_tasks.remove_at(i);
+		}
+	}
+
 	// update objects
 	if (_room) {
 		_room->update(elapsed);


Commit: 29f6c91491304a7315b9058963e412161b77de73
    https://github.com/scummvm/scummvm/commit/29f6c91491304a7315b9058963e412161b77de73
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix cursor text visible when input off

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index a2d3dbcccd8..fc6f5637ab8 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -260,7 +260,7 @@ Verb TwpEngine::verb() {
 
 Common::String TwpEngine::cursorText() {
 	Common::String result;
-	if (_dialog.getState() == DialogState::None) {
+	if (_dialog.getState() == DialogState::None && _inputState.getInputActive()) {
 		if (_hud.isVisible() && _hud._over) {
 			return _hud._verb.id.id > 1 ? _textDb.getText(verb().text) : "";
 		}


Commit: 45227fa0d72061c1d8b4193749f0dbf6e73ea22a
    https://github.com/scummvm/scummvm/commit/45227fa0d72061c1d8b4193749f0dbf6e73ea22a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Cursort text missing verb the first time

Changed paths:
    engines/twp/object.h
    engines/twp/twp.cpp


diff --git a/engines/twp/object.h b/engines/twp/object.h
index a575fd65192..ccbfb975c74 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -69,7 +69,7 @@ public:
 };
 
 struct VerbId {
-	int id = 0;
+	int id = VERB_WALKTO;
 };
 
 class Object;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index fc6f5637ab8..0340029062a 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -255,6 +255,9 @@ Verb TwpEngine::verb() {
 	Verb result = _hud._verb;
 	if (result.id.id == VERB_WALKTO && _noun1 && _noun1->inInventory())
 		result = *_hud.actorSlot(_actor)->getVerb(_noun1->defaultVerbId());
+	else if(_actor) {
+		result = *_hud.actorSlot(_actor)->getVerb(_hud._verb.id.id);
+	}
 	return result;
 }
 


Commit: 9b4390be9915f5c09591e1479a8dbda400bebb3c
    https://github.com/scummvm/scummvm/commit/9b4390be9915f5c09591e1479a8dbda400bebb3c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix we can use object on same itself

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 0340029062a..8d3c1f9959f 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -368,11 +368,30 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 	return result;
 }
 
-struct GetNoun2 {
-	GetNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {}
+struct GetUseNoun2 {
+	GetUseNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
+		_noun2 = nullptr;
+	}
+
+	bool operator()(Common::SharedPtr<Object> obj) {
+		if ((obj != g_engine->_actor) && (g_engine->_noun1 != obj)) {
+			_noun2 = obj;
+			return true;
+		}
+		return false;
+	}
+
+public:
+	Common::SharedPtr<Object> &_noun2;
+};
+
+struct GetGiveableNoun2 {
+	GetGiveableNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
+		_noun2 = nullptr;
+	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
-		if (obj != g_engine->_actor && obj->getFlags() & GIVEABLE) {
+		if ((obj != g_engine->_actor) && (obj->getFlags() & GIVEABLE) && (g_engine->_noun2 != obj)) {
 			_noun2 = obj;
 			return true;
 		}
@@ -402,14 +421,14 @@ void TwpEngine::update(float elapsed) {
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		if (_room->_fullscreen == FULLSCREENROOM) {
 			if ((_hud._verb.id.id == VERB_USE) && (_useFlag != ufNone)) {
-				_noun2 = objAt(roomPos);
+				objsAt(roomPos, GetUseNoun2(_noun2));
 			} else if (_hud._verb.id.id == VERB_GIVE) {
 				if (_useFlag != ufGiveTo) {
 					_noun1 = inventoryAt(roomPos);
 					_useFlag = ufNone;
 					_noun2 = nullptr;
 				} else {
-					objsAt(roomPos, GetNoun2(_noun2));
+					objsAt(roomPos, GetGiveableNoun2(_noun2));
 					if (_noun2)
 						debugC(kDebugGame, "Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
 				}


Commit: 98dfa1c025f353eb6aff6edf566abf8d2d7ffe53
    https://github.com/scummvm/scummvm/commit/98dfa1c025f353eb6aff6edf566abf8d2d7ffe53
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix we can switch actor even if disabled

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/debugtools.cpp
    engines/twp/ids.h
    engines/twp/syslib.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 6314e923518..f697b8eab55 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -386,16 +386,16 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 2, selectable)))
 			return sq_throwerror(v, "failed to get selectable");
 		switch (selectable) {
-		case 0:
+		case OFF:
 			g_engine->_actorSwitcher._mode &= (~asOn);
 			break;
-		case 1:
+		case ON:
 			g_engine->_actorSwitcher._mode |= asOn;
 			break;
-		case 2:
+		case TEMP_UNSELECTABLE:
 			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
 			break;
-		case 3:
+		case TEMP_SELECTABLE:
 			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
 			break;
 		default:
@@ -565,10 +565,6 @@ static SQInteger actorTalking(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor;
 	if (sq_gettop(v) == 2) {
 		actor = sqobj(v, 2);
-		if (!actor) {
-			sqpush(v, false);
-			return 1;
-		}
 	} else {
 		actor = g_engine->_actor;
 	}
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index b6f701b3a03..71d69d3c606 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -153,7 +153,7 @@ void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float
 			_down = true;
 			// check if we allow to select an actor
             size_t iconIdx = iconIndex(scrPos);
-			if ((_mode & asOn) || (iconIdx == (_slots.size() - 1))) {
+			if ((_mode == asOn) || (iconIdx == (_slots.size() - 1))) {
 				if (_slots[iconIdx].selectFunc != nullptr)
 					_slots[iconIdx].select();
 			}
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 829328408aa..4c0ba3f8af4 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -283,6 +283,24 @@ static void drawGeneral() {
 	ImGui::Checkbox("Cursor", &g_engine->_inputState._showCursor);
 	ImGui::SameLine();
 	ImGui::Checkbox("Verbs", &g_engine->_inputState._inputVerbsActive);
+
+	ImGui::Separator();
+	bool isSwitcherOn = g_engine->_actorSwitcher._mode == asOn;
+	if(ImGui::Checkbox("Switcher ON", &isSwitcherOn)) {
+		if(isSwitcherOn) {
+			g_engine->_actorSwitcher._mode |= asOn;
+		} else {
+			g_engine->_actorSwitcher._mode &= ~asOn;
+		}
+	}
+	bool isTemporaryUnselectable = g_engine->_actorSwitcher._mode & asTemporaryUnselectable;
+	if(ImGui::Checkbox("Switcher Temp. Unselectable", &isTemporaryUnselectable)) {
+		if(isTemporaryUnselectable) {
+			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
+		} else {
+			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
+		}
+	}
 	ImGui::Separator();
 
 	if (ImGui::CollapsingHeader("Debug")) {
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index c3430521993..9565eb79de5 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -126,8 +126,6 @@
 #define VERBFLAG_INSTANT  1
 #define TWP_NO  0
 #define TWP_YES  1
-#define UNSELECTABLE  0
-#define SELECTABLE  1
 #define TEMP_UNSELECTABLE  2
 #define TEMP_SELECTABLE  3
 #define MAC  1
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 6a105709aeb..007e79b839f 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -944,8 +944,6 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "VERBFLAG_INSTANT", VERBFLAG_INSTANT);
 	regConst(v, "NO", TWP_NO);
 	regConst(v, "YES", TWP_YES);
-	regConst(v, "UNSELECTABLE", UNSELECTABLE);
-	regConst(v, "SELECTABLE", SELECTABLE);
 	regConst(v, "TEMP_UNSELECTABLE", TEMP_UNSELECTABLE);
 	regConst(v, "TEMP_SELECTABLE", TEMP_SELECTABLE);
 	regConst(v, "MAC", MAC);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 8d3c1f9959f..7aaae028b6f 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -327,10 +327,12 @@ Common::SharedPtr<Object> inventoryAt(Math::Vector2d pos) {
 }
 
 static void selectSlotActor(int id) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		if (g_engine->_actors[i]->getId() == id) {
-			g_engine->setActor(g_engine->_actors[i]);
-			break;
+	if(g_engine->_actorSwitcher._mode == asOn) {
+		for (size_t i = 0; i < g_engine->_actors.size(); i++) {
+			if (g_engine->_actors[i]->getId() == id) {
+				g_engine->setActor(g_engine->_actors[i]);
+				break;
+			}
 		}
 	}
 }
@@ -792,7 +794,7 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectActor4:
 				case TwpAction::kSelectActor5:
 				case TwpAction::kSelectActor6:
-					if (_dialog.getState() == DialogState::None) {
+					if(g_engine->_actorSwitcher._mode == asOn) {
 						int index = (TwpAction)e.customType - kSelectActor1;
 						ActorSlot *slot = &_hud._actorSlots[index];
 						if (slot->selectable && slot->actor && (slot->actor->_room->_name != "Void")) {
@@ -801,7 +803,7 @@ Common::Error TwpEngine::run() {
 					}
 					break;
 				case TwpAction::kSelectPreviousActor:
-					if (_actor) {
+					if ((g_engine->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];
@@ -816,7 +818,7 @@ Common::Error TwpEngine::run() {
 					}
 					break;
 				case TwpAction::kSelectNextActor:
-					if (_actor) {
+					if ((g_engine->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];


Commit: 4b3f6d25e5e15fddb9fab95483f19cb83111a5bb
    https://github.com/scummvm/scummvm/commit/4b3f6d25e5e15fddb9fab95483f19cb83111a5bb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Disable savegame when necessary

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/twp.h


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 4c0ba3f8af4..9ec20f7c389 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -283,6 +283,8 @@ static void drawGeneral() {
 	ImGui::Checkbox("Cursor", &g_engine->_inputState._showCursor);
 	ImGui::SameLine();
 	ImGui::Checkbox("Verbs", &g_engine->_inputState._inputVerbsActive);
+	ImGui::SameLine();
+	ImGui::Checkbox("Allow SaveGame", &g_engine->_saveGameManager._allowSaveGame);
 
 	ImGui::Separator();
 	bool isSwitcherOn = g_engine->_actorSwitcher._mode == asOn;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 969565aae28..26f62001e0e 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -108,7 +108,7 @@ public:
 		return true;
 	}
 	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
-		return true;
+		return _saveGameManager._allowSaveGame;
 	}
 
 	virtual Common::String getSaveStateName(int slot) const override;


Commit: ca9867b6c6b85547b14b5bfb2fcabf550026f543
    https://github.com/scummvm/scummvm/commit/ca9867b6c6b85547b14b5bfb2fcabf550026f543
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix node draw order

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/scenegraph.cpp


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 9ec20f7c389..7a85857752c 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -14,6 +14,8 @@ static struct {
 	bool _showStack = false;
 	bool _showAudio = false;
 	bool _showResources = false;
+	bool _showScenegraph = false;
+	Node* _node = nullptr;
 	ImGuiTextFilter _objFilter;
 	int _fadeEffect = 0;
 	float _fadeDuration = 0.f;
@@ -396,6 +398,7 @@ static void drawGeneral() {
 		ImGui::Checkbox("Stack", &state._showStack);
 		ImGui::Checkbox("Audio", &state._showAudio);
 		ImGui::Checkbox("Resources", &state._showResources);
+		ImGui::Checkbox("Scenegraph", &state._showScenegraph);
 	}
 	ImGui::Separator();
 
@@ -431,6 +434,67 @@ static void drawGeneral() {
 	ImGui::End();
 }
 
+static void drawNode(Node* node) {
+	auto children = node->getChildren();
+	bool selected = state._node == node;
+	if(children.empty()) {
+		if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+			state._node = node;
+		}
+	} else {
+		ImGui::PushID(node->getName().c_str());
+		if(ImGui::TreeNode("")) {
+			ImGui::SameLine();
+			if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+				state._node = node;
+			}
+			for(auto& child : children) {
+				drawNode(child);
+			}
+			ImGui::TreePop();
+		} else {
+			ImGui::SameLine();
+			if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+				state._node = node;
+			}
+		}
+		ImGui::PopID();
+	}
+}
+
+static void drawScenegraph() {
+	if (!state._showScenegraph)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Scenegraph", &state._showScenegraph);
+	drawNode(&g_engine->_scene);
+	ImGui::End();
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	if(state._node != nullptr) {
+		ImGui::Begin("Node");
+		state._node->isVisible();
+		bool visible = state._node->isVisible();
+		if(ImGui::Checkbox(state._node->getName().c_str(), &visible)) {
+			state._node->setVisible(visible);
+		}
+		int zsort = state._node->getZSort();
+		if(ImGui::DragInt("Z-Sort", &zsort)) {
+			state._node->setZSort(zsort);
+		}
+		Math::Vector2d pos = state._node->getPos();
+		if(ImGui::DragFloat2("Pos", pos.getData())) {
+			state._node->setPos(pos);
+		}
+		Math::Vector2d offset = state._node->getOffset();
+		if(ImGui::DragFloat2("Offset", offset.getData())) {
+			state._node->setOffset(offset);
+		}
+		ImGui::End();
+	}
+}
+
 void onImGuiRender() {
 	drawGeneral();
 	drawThreads();
@@ -438,6 +502,7 @@ void onImGuiRender() {
 	drawStack();
 	drawAudio();
 	drawResources();
+	drawScenegraph();
 }
 
 } // namespace Twp
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 94411b3878e..f7f584a1068 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -92,7 +92,7 @@ int Node::find(Node *other) {
 
 void Node::removeChild(Node *child) {
 	int i = find(child);
-	if(i != -1) {
+	if (i != -1) {
 		_children.remove_at(i);
 	}
 	child->_parent = nullptr;
@@ -180,15 +180,32 @@ void Node::setSize(Math::Vector2d size) {
 	}
 }
 
-static int cmpNodes(const Node *x, const Node *y) {
-	return y->getZSort() < x->getZSort();
+// this structure is used to have a stable sort
+typedef struct {
+	size_t index;
+	Node *node;
+} NodeSort;
+
+static int cmpNodes(const NodeSort &x, const NodeSort &y) {
+	if (y.node->getZSort() == x.node->getZSort()) {
+		return x.index < y.index;
+	}
+	return x.node->getZSort() > y.node->getZSort();
 }
 
 void Node::onDrawChildren(Math::Matrix4 trsf) {
-	Common::Array<Node *> children(_children);
+	// use this "stable sort" until there is something better available
+	Common::Array<NodeSort> children;
+	for (size_t i = 0; i < _children.size(); i++) {
+		children.push_back({i, _children[i]});
+	}
 	Common::sort(children.begin(), children.end(), cmpNodes);
+	_children.clear();
+	_children.reserve(children.size());
 	for (size_t i = 0; i < children.size(); i++) {
-		Node *child = children[i];
+		_children.push_back(children[i].node);
+	}
+	for (auto& child : _children) {
 		child->draw(trsf);
 	}
 }
@@ -367,7 +384,8 @@ void Anim::update(float elapsed) {
 void Anim::drawCore(Math::Matrix4 trsf) {
 	if (_frameIndex < _frames.size()) {
 		const Common::String &frame = _frames[_frameIndex];
-		if(frame == "null") return;
+		if (frame == "null")
+			return;
 
 		bool flipX = _obj->getFacing() == FACE_LEFT;
 		if (_sheet.size() == 0) {
@@ -388,9 +406,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			if (_obj->_lit) {
 				g_engine->getGfx().use(g_engine->_lighting.get());
 				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
-				const float left = flipX ?
-					(-1.f + sf.sourceSize.getX()) / 2.f - sf.spriteSourceSize.left :
-					sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f;
+				const float left = flipX ? (-1.f + sf.sourceSize.getX()) / 2.f - sf.spriteSourceSize.left : sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f;
 				const float top = -sf.sourceSize.getY() / 2.f + sf.spriteSourceSize.top;
 
 				g_engine->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});


Commit: ae5cd629e1ba01dcbb753dbc5fd8235317dd9411
    https://github.com/scummvm/scummvm/commit/ae5cd629e1ba01dcbb753dbc5fd8235317dd9411
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix bug with pseudo objects

Changed paths:
    engines/twp/savegame.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 03108dac774..3d00fcc237d 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -222,14 +222,9 @@ static void setAnimations(Common::SharedPtr<Object> actor, const Common::JSONArr
 }
 
 static void loadActor(Common::SharedPtr<Object> actor, const Common::JSONObject &json) {
-	bool touchable = true;
-	if (json.contains("_untouchable"))
-		touchable = json["_untouchable"]->asIntegerNumber() == 0;
-	actor->setTouchable(touchable);
-	bool hidden = false;
-	if (json.contains("_hidden"))
-		hidden = json["_hidden"]->asIntegerNumber() == 1;
-	actor->_node->setVisible(!hidden);
+	actor->setTouchable(json.contains("_untouchable") ? json["_untouchable"]->asIntegerNumber() != 1 : true);
+	actor->_node->setVisible(json.contains("_hidden") ? json["_hidden"]->asIntegerNumber() != 1 : true);
+	actor->_volume = json.contains("_volume") ? json["_volume"]->asNumber() : 1.0f;
 	for (auto it = json.begin(); it != json.end(); it++) {
 		if (it->_key == "_animations") {
 			setAnimations(actor, it->_value->asArray());
@@ -258,9 +253,7 @@ static void loadActor(Common::SharedPtr<Object> actor, const Common::JSONObject
 			actor->_node->setRenderOffset(parseVec2(it->_value->asString()));
 		} else if (it->_key == "_roomKey") {
 			Object::setRoom(actor, room(it->_value->asString()));
-		} else if ((it->_key == "_hidden") || (it->_key == "_untouchable")) {
-		} else if (it->_key == "_volume") {
-			actor->_volume = it->_value->asNumber();
+		} else if ((it->_key == "_hidden") || (it->_key == "_untouchable") || (it->_key == "_volume")) {
 		} else {
 			if (!it->_key.hasPrefix("_")) {
 				HSQOBJECT tmp;
@@ -388,6 +381,12 @@ static int32_t computeHash(byte *data, size_t n) {
 }
 
 bool SaveGameManager::loadGame(const SaveGame &savegame) {
+	// dump savegame as json
+	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("load.json", false);
+	// Common::String s = savegame.jSavegame->stringify(true);
+	// saveFile->write(s.c_str(), s.size());
+	// saveFile->finalize();
+
 	const Common::JSONObject &json = savegame.jSavegame->asObject();
 	long long int version = json["version"]->asIntegerNumber();
 	if (version != 2) {
@@ -589,14 +588,14 @@ void SaveGameManager::loadObjects(const Common::JSONObject &json) {
 struct JsonCallback {
 	bool skipObj;
 	bool pseudo;
-	HSQOBJECT* rootTable;
-	Common::JSONObject* jObj;
+	HSQOBJECT *rootTable;
+	Common::JSONObject *jObj;
 };
 
 static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false);
 
 static void fillMissingProperties(const Common::String &k, HSQOBJECT &oTable, void *data) {
-	JsonCallback* params = static_cast<JsonCallback*>(data);
+	JsonCallback *params = static_cast<JsonCallback *>(data);
 	if ((k.size() > 0) && (!k.hasPrefix("_"))) {
 		if (!(params->skipObj && isObject(getId(oTable)) && (params->pseudo || sqrawexists(*params->rootTable, k)))) {
 			Common::JSONValue *json = tojson(oTable, true);
@@ -639,11 +638,11 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 			int id = 0;
 			sqgetf(obj, "_id", id);
 			if (isActor(id)) {
-				Common::SharedPtr<Object> a = actor(id);
+				Common::SharedPtr<Object> a(actor(id));
 				jObj["_actorKey"] = new Common::JSONValue(a->_key);
 				return new Common::JSONValue(jObj);
 			} else if (isObject(id)) {
-				Common::SharedPtr<Object> o = sqobj(id);
+				Common::SharedPtr<Object> o(sqobj(id));
 				if (!o)
 					return new Common::JSONValue();
 				jObj["_objectKey"] = new Common::JSONValue(o->_key);
@@ -651,7 +650,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 					jObj["_roomKey"] = new Common::JSONValue(o->_room->_name);
 				return new Common::JSONValue(jObj);
 			} else if (isRoom(id)) {
-				Common::SharedPtr<Room> r = getRoom(id);
+				Common::SharedPtr<Room> r(getRoom(id));
 				jObj["_roomKey"] = new Common::JSONValue(r->_name);
 				return new Common::JSONValue(jObj);
 			}
@@ -712,7 +711,7 @@ static Common::JSONValue *createJActor(Common::SharedPtr<Object> actor) {
 		jActor["_untouchable"] = new Common::JSONValue(1LL);
 	if (!actor->_node->isVisible())
 		jActor["_hidden"] = new Common::JSONValue(1LL);
-	if (actor->_volume != 0.f)
+	if (actor->_volume != 1.f)
 		jActor["_volume"] = new Common::JSONValue(actor->_volume);
 	//   result.fields.sort(cmpKey);
 	return new Common::JSONValue(jActor);
@@ -899,7 +898,7 @@ static Common::JSONValue *createJObject(HSQOBJECT &table, Common::SharedPtr<Obje
 }
 
 static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
-	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
+	Common::JSONObject *jObj = static_cast<Common::JSONObject *>(data);
 	if (isObject(getId(v))) {
 		Common::SharedPtr<Object> obj = sqobj(v);
 		if (!obj || (obj->_objType == otNone)) {
@@ -916,13 +915,13 @@ static Common::JSONValue *createJObjects() {
 	return new Common::JSONValue(json);
 }
 
-static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void* data) {
-	Common::JSONObject* jObj = static_cast<Common::JSONObject*>(data);
+static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void *data) {
+	Common::JSONObject *jObj = static_cast<Common::JSONObject *>(data);
 	if (isObject(getId(v))) {
-			Common::SharedPtr<Object> obj = sqobj(v);
-			// info fmt"pseudoObj: createJObject({k})"
-			(*jObj)[k] = createJObject(v, obj);
-		}
+		Common::SharedPtr<Object> obj(sqobj(v));
+		// info fmt"pseudoObj: createJObject({k})"
+		(*jObj)[k] = createJObject(v, obj);
+	}
 }
 
 static Common::JSONValue *createJPseudoObjects(Common::SharedPtr<Room> room) {
@@ -981,7 +980,7 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	// dump savegame as json
 	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("save.json", false);
 	// Common::String s = data->stringify(true);
-	// saveFile->write(s.c_str(),s.size());
+	// saveFile->write(s.c_str(), s.size());
 	// saveFile->finalize();
 
 	const uint32 fullSize = 500000;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7aaae028b6f..eaa8ae94855 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1133,7 +1133,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 
 					if (obj->_objType == otNone)
 						obj->setTouchable(false);
-				} else if (obj->_objType == otNone) {
+				} else {
 					if (pseudo) {
 						// if it's a pseudo room we need to clone each object
 						sqgetf(result->_table, obj->_key, obj->_table);
@@ -1144,7 +1144,9 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 						sq_remove(v, -2);
 						sqsetf(result->_table, obj->_key, obj->_table);
 					}
-					obj->setTouchable(true);
+					if (obj->_objType == otNone) {
+						obj->setTouchable(true);
+					}
 				}
 
 				layerNode->addChild(obj->_node.get());
@@ -1484,7 +1486,7 @@ bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common:
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
 	ActorSlot *slot = _hud.actorSlot(actor);
 	Verb *verb = slot->getVerb(verbId.id);
-	Common::String verbFuncName = verb ? verb->fun : slot->getVerb(0)->fun;
+	Common::String verbFuncName = verb ? verb->fun : slot->verbs[0].fun;
 	debugC(kDebugGame, "callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
 
 	// test if object became untouchable


Commit: 18c96a499d429a9b02feaacade00b446873f8950
    https://github.com/scummvm/scummvm/commit/18c96a499d429a9b02feaacade00b446873f8950
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash with tasks

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index eaa8ae94855..21de5f14409 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -554,7 +554,7 @@ void TwpEngine::update(float elapsed) {
 	// update tasks
 	Common::Array<Common::SharedPtr<Task> > tasks(_tasks);
 	Common::Array<Common::SharedPtr<Task> > tasksToRemove;
-	for (auto it = _tasks.begin(); it != _tasks.end();) {
+	for (auto it = tasks.begin(); it != tasks.end();) {
 		Common::SharedPtr<Task> task(*it);
 		if (task->update(elapsed)) {
 			tasksToRemove.push_back(task);


Commit: 1547e4654b2f3e73ca1454136245675ed1ae5d40
    https://github.com/scummvm/scummvm/commit/1547e4654b2f3e73ca1454136245675ed1ae5d40
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix actorDistanceTo with 1 parameter

Changed paths:
    engines/twp/actorlib.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index f697b8eab55..9a4533361eb 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -198,12 +198,13 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	Common::SharedPtr<Object> obj;
-	if (sq_gettop(v) == 3)
+	if (sq_gettop(v) == 3) {
 		obj = sqobj(v, 3);
-	if (!obj)
-		return sq_throwerror(v, "failed to get object");
-	else
+		if (!obj)
+			return sq_throwerror(v, "failed to get object");
+	} else {
 		obj = g_engine->_actor;
+	}
 	sqpush(v, distance((Vector2i)actor->_node->getPos(), (Vector2i)obj->getUsePos()));
 	return 1;
 }


Commit: 79b3ace7ca9f168575d2fcc009b80c379607a0fb
    https://github.com/scummvm/scummvm/commit/79b3ace7ca9f168575d2fcc009b80c379607a0fb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash with text in parenthesis

Changed paths:
    engines/twp/motor.cpp


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index f3df09d7341..fcdcb5366fe 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -425,7 +425,7 @@ void Talking::say(const Common::String &text) {
 
 	// modify state ?
 	Common::String state;
-	if (txt[0] == '{') {
+	if (!txt.empty() && txt[0] == '{') {
 		int i = txt.find('}');
 		if (i != -1) {
 			state = txt.substr(1, txt.size() - 2);


Commit: cc3ce37994eeac0e56bfad507de968a6e12d51d9
    https://github.com/scummvm/scummvm/commit/cc3ce37994eeac0e56bfad507de968a6e12d51d9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix impossible to open Mansion gate

Changed paths:
    engines/twp/roomlib.cpp


diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 6b64ee4687c..18270397043 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -43,7 +43,6 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, obj->_leave)))
 			return sq_throwerror(v, "failed to get leave");
 	sq_addref(g_engine->getVm(), &obj->_leave);
-	obj->_triggerActive = true;
 	g_engine->_room->_triggers.push_back(obj);
 	return 0;
 }


Commit: 49273670d52264c0f1ee4d14821db247cf43afa7
    https://github.com/scummvm/scummvm/commit/49273670d52264c0f1ee4d14821db247cf43afa7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix Franklin icon should be a ghost

Changed paths:
    engines/twp/object.cpp
    engines/twp/object.h


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 2112497d6bd..359b7267224 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -299,8 +299,18 @@ void Object::setTouchable(bool value) {
 }
 
 void Object::setIcon(int fps, const Common::StringArray &icons) {
-	_icons = icons;
-	_iconFps = fps;
+	HSQUIRRELVM v = g_engine->getVm();
+	sq_newarray(v, 0);
+	sqpush(v, fps);
+	for (size_t i = 0; i < icons.size(); i++) {
+		sqpush(v, icons[i]);
+		sq_arrayappend(v, -2);
+	}
+	HSQOBJECT array;
+	sq_resetobject(&array);
+	sq_getstackobj(v, -1, &array);
+	sqsetf(_table, "icon", array);
+
 	_iconIndex = 0;
 	_iconElapsed = 0.f;
 }
@@ -332,28 +342,32 @@ private:
 	int _index = 0;
 };
 
-Common::String Object::getIcon() {
-	if (_icons.size() > 0)
-		return _icons[_iconIndex];
+ObjectIcons Object::getIcons() const {
+	ObjectIcons result;
 	HSQOBJECT iconTable;
 	sq_resetobject(&iconTable);
 	sqgetf(_table, "icon", iconTable);
 	if (iconTable._type == OT_NULL) {
-		return "";
+		return result;
 	}
 	if (iconTable._type == OT_STRING) {
-		Common::String result = sq_objtostring(&iconTable);
-		setIcon(result);
+		Common::String icons(sq_objtostring(&iconTable));
+		result.icons.push_back(icons);
 		return result;
 	}
 	if (iconTable._type == OT_ARRAY) {
-		int fps;
-		Common::StringArray icons;
-		sqgetitems(iconTable, GetIcons(fps, icons));
-		setIcon(fps, icons);
-		return getIcon();
+		sqgetitems(iconTable, GetIcons(result.fps, result.icons));
+		return result;
 	}
-	return "";
+	return result;
+}
+
+Common::String Object::getIcon() {
+	ObjectIcons result = getIcons();
+	if(result.icons.empty())
+		return "";
+	_iconIndex = _iconIndex % result.icons.size();
+	return result.icons[_iconIndex];
 }
 
 int Object::getFlags() {
@@ -565,11 +579,12 @@ void Object::update(float elapsedSec) {
 	if (_nodeAnim)
 		_nodeAnim->update(elapsedSec);
 
-	if ((_icons.size() > 1) && (_iconFps > 0)) {
+	ObjectIcons icons = getIcons();
+	if ((icons.icons.size() > 1) && (icons.fps > 0)) {
 		_iconElapsed += elapsedSec;
-		if (_iconElapsed > (1.f / _iconFps)) {
+		if (_iconElapsed > (1.f / icons.fps)) {
 			_iconElapsed = 0.f;
-			_iconIndex = (_iconIndex + 1) % _icons.size();
+			_iconIndex = (_iconIndex + 1) % icons.icons.size();
 		}
 	}
 
diff --git a/engines/twp/object.h b/engines/twp/object.h
index ccbfb975c74..2aacaa9589e 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -100,6 +100,11 @@ struct LockFacing {
 	Facing value;
 };
 
+struct ObjectIcons {
+	int fps = 0;
+	Common::StringArray icons;
+};
+
 class Object {
 public:
 	Object();
@@ -156,6 +161,7 @@ public:
 	void setIcon(int fps, const Common::StringArray &icons);
 	void setIcon(const Common::String &icon);
 	Common::String getIcon();
+	ObjectIcons getIcons() const;
 	bool inInventory();
 	void removeInventory(Common::SharedPtr<Object> obj);
 
@@ -256,8 +262,6 @@ public:
 	Common::SharedPtr<Object> _owner;
 	Common::Array<Common::SharedPtr<Object> > _inventory;
 	int _inventoryOffset = 0;
-	Common::StringArray _icons;
-	int _iconFps = 0;
 	int _iconIndex = 0;
 	float _iconElapsed = 0.f;
 	HSQOBJECT _enter, _leave;


Commit: 3545c138bd30d36e011e541522c919436fac4bee
    https://github.com/scummvm/scummvm/commit/3545c138bd30d36e011e541522c919436fac4bee
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use inventory_slot

Changed paths:
    engines/twp/object.cpp
    engines/twp/scenegraph.h


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 359b7267224..23b49a62edd 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -599,7 +599,19 @@ void Object::update(float elapsedSec) {
 
 void Object::pickupObject(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	obj->_owner.reset(actor);
-	actor->_inventory.push_back(obj);
+	int inventory_slot = -1;
+	if(sqrawexists(obj->_table, "inventory_slot")) {
+		sqgetf(obj->_table, "inventory_slot", inventory_slot);
+		inventory_slot--;
+		if(inventory_slot >= actor->_inventory.size()) {
+			inventory_slot = -1;
+		}
+	}
+	if(inventory_slot == -1) {
+		actor->_inventory.push_back(obj);
+	} else {
+		actor->_inventory[inventory_slot] = obj;
+	}
 
 	sqcall("onPickup", obj->_table, actor->_table);
 
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 9661226a7ab..4cba5118bf4 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -158,7 +158,7 @@ struct ObjectAnimation;
 class Object;
 class Anim : public Node {
 public:
-	Anim(Object* obj);
+	Anim(Object *obj);
 
 	void clearFrames();
 	void setAnim(const ObjectAnimation *anim, float fps = 0.f, bool loop = false, bool instant = false);
@@ -176,7 +176,7 @@ public:
 private:
 	Common::String _sheet;
 	Common::Array<Common::String> _frames;
-    size_t _frameIndex = 0;
+	size_t _frameIndex = 0;
 	float _elapsed = 0.f;
 	float _frameDuration = 0.f;
 	bool _loop = false;
@@ -220,15 +220,14 @@ public:
 };
 
 enum class CursorShape {
-    Normal,
-    Front,
-    Back,
-    Left,
-    Right,
-    Pause
+	Normal,
+	Front,
+	Back,
+	Left,
+	Right,
+	Pause
 };
 
-
 enum InputStateFlag {
 	II_FLAGS_UI_INPUT_ON = 1,
 	II_FLAGS_UI_INPUT_OFF = 2,
@@ -271,11 +270,11 @@ private:
 
 public:
 	bool _inputHUD = false;
-    bool _inputActive = false;
-    bool _showCursor = false;
-    bool _inputVerbsActive = false;
-    CursorShape _cursorShape = CursorShape::Normal;
-    bool _hotspot = false;
+	bool _inputActive = false;
+	bool _showCursor = false;
+	bool _inputVerbsActive = false;
+	CursorShape _cursorShape = CursorShape::Normal;
+	bool _hotspot = false;
 };
 
 class OverlayNode final : public Node {
@@ -292,13 +291,13 @@ private:
 	Color _ovlColor;
 };
 
-class Inventory: public Node {
+class Inventory : public Node {
 public:
 	Inventory();
-	void update(float elapsed, Common::SharedPtr<Object>  actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
+	void update(float elapsed, Common::SharedPtr<Object> actor = nullptr, Color backColor = Color(0, 0, 0), Color verbNormal = Color(0, 0, 0));
 
-	Common::SharedPtr<Object>  getObject() const { return _obj; }
-	Math::Vector2d getPos(Common::SharedPtr<Object>  inv) const;
+	Common::SharedPtr<Object> getObject() const { return _obj; }
+	Math::Vector2d getPos(Common::SharedPtr<Object> inv) const;
 
 	void setVisible(bool visible) override;
 
@@ -307,13 +306,13 @@ private:
 	void drawArrows(Math::Matrix4 trsf);
 	void drawBack(Math::Matrix4 trsf);
 	void drawItems(Math::Matrix4 trsf);
-	void drawSprite(SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
+	void drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 
 private:
-	Common::SharedPtr<Object>  _actor;
-    Color _backColor, _verbNormal;
-    bool _down = false;
-    Common::SharedPtr<Object>  _obj;
+	Common::SharedPtr<Object> _actor;
+	Color _backColor, _verbNormal;
+	bool _down = false;
+	Common::SharedPtr<Object> _obj;
 	Common::Rect _itemRects[NUMOBJECTS];
 	Common::Rect _arrowUpRect;
 	Common::Rect _arrowDnRect;
@@ -322,12 +321,12 @@ private:
 	bool _fadeIn = false;
 };
 
-class SentenceNode: public Node {
+class SentenceNode : public Node {
 public:
 	SentenceNode();
 	virtual ~SentenceNode();
 
-	void setText(const Common::String& text);
+	void setText(const Common::String &text);
 
 private:
 	void drawCore(Math::Matrix4 trsf) override final;
@@ -336,12 +335,12 @@ private:
 	Common::String _text;
 };
 
-class SpriteNode: public Node {
+class SpriteNode : public Node {
 public:
 	SpriteNode();
 	virtual ~SpriteNode();
 
-	void setSprite(const Common::String& sheet, const Common::String& frame);
+	void setSprite(const Common::String &sheet, const Common::String &frame);
 
 private:
 	void drawCore(Math::Matrix4 trsf) override final;
@@ -351,7 +350,7 @@ private:
 	Common::String _frame;
 };
 
-class NoOverrideNode: public Node {
+class NoOverrideNode : public Node {
 public:
 	NoOverrideNode();
 	virtual ~NoOverrideNode();
@@ -364,13 +363,13 @@ private:
 	float _elapsed = 0.f;
 };
 
-class HotspotMarkerNode: public Node {
+class HotspotMarkerNode : public Node {
 public:
 	HotspotMarkerNode();
 	virtual ~HotspotMarkerNode();
 
 private:
-	void drawSprite(const SpriteSheetFrame& sf, Texture* texture, Color color, Math::Matrix4 trsf);
+	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 	void drawCore(Math::Matrix4 trsf) override final;
 };
 


Commit: 2852c14f0b5af8f98f026e31737fce3dc34753b6
    https://github.com/scummvm/scummvm/commit/2852c14f0b5af8f98f026e31737fce3dc34753b6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Clean code

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/audio.cpp
    engines/twp/camera.cpp
    engines/twp/console.cpp
    engines/twp/debugtools.cpp
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/enginedialogtarget.h
    engines/twp/font.cpp
    engines/twp/font.h
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/graph.cpp
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/lighting.cpp
    engines/twp/lip.cpp
    engines/twp/lip.h
    engines/twp/metaengine.cpp
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/shaders.cpp
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp
    engines/twp/vm.cpp
    engines/twp/walkboxnode.cpp
    engines/twp/yack.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 9a4533361eb..92466263828 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -21,12 +21,7 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
-#include "twp/room.h"
-#include "twp/object.h"
 #include "twp/squtil.h"
-#include "twp/util.h"
-#include "twp/scenegraph.h"
-#include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
@@ -86,7 +81,7 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get actor");
 		Common::SharedPtr<Object> spot = sqobj(v, 3);
 		if (spot) {
-			Math::Vector2d pos = spot->_node->getPos() + spot->_usePos;
+			Math::Vector2d pos(spot->_node->getPos() + spot->_usePos);
 			Object::setRoom(actor, spot->_room);
 			actor->stopWalking();
 			debugC(kDebugActScript, "actorAt %s at %s (%d, %d), room '%s'", actor->_key.c_str(), spot->_key.c_str(), (int)pos.getX(), (int)pos.getY(), spot->_room->_name.c_str());
@@ -334,9 +329,8 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 3, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (size_t i = 0; i < g_engine->_room->_walkboxes.size(); i++) {
-		const Walkbox &walkbox = g_engine->_room->_walkboxes[i];
-		if (walkbox._name == name) {
+	for (const auto & walkbox : g_engine->_room->_walkboxes) {
+			if (walkbox._name == name) {
 			if (walkbox.contains((Vector2i)actor->_node->getAbsPos())) {
 				sqpush(v, true);
 				return 1;
@@ -750,14 +744,12 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
-		Facing *facing = nullptr;
-		int dir;
+		int facing = 0;
 		if (nArgs == 5) {
-			if (SQ_FAILED(sqget(v, 5, dir)))
+			if (SQ_FAILED(sqget(v, 5, facing)))
 				return sq_throwerror(v, "failed to get dir");
-			facing = (Facing *)&dir;
 		}
-		Object::walk(actor, Vector2i(x, y), facing ? *facing : 0);
+		Object::walk(actor, Vector2i(x, y), facing);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
@@ -810,7 +802,7 @@ static SQInteger flashSelectableActor(HSQUIRRELVM v) {
 }
 
 struct GetStrings {
-	GetStrings(Common::StringArray &texts) : _texts(texts) {}
+	explicit GetStrings(Common::StringArray &texts) : _texts(texts) {}
 
 	void operator()(HSQOBJECT item) {
 		_texts.push_back(sq_objtostring(&item));
@@ -925,7 +917,7 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	ActorSlot *slot = g_engine->_hud.actorSlot(actor);
-	bool selectable = !slot ? false : slot->selectable;
+	bool selectable = slot && slot->selectable;
 	sqpush(v, selectable);
 	return 1;
 }
@@ -941,8 +933,7 @@ static SQInteger is_actor(HSQUIRRELVM v) {
 // See also masterRoomArray.
 static SQInteger masterActorArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
+	for (auto actor : g_engine->_actors) {
 		sqpush(v, actor->_table);
 		sq_arrayappend(v, -2);
 	}
@@ -998,8 +989,7 @@ static SQInteger triggerActors(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sq_newarray(v, 0);
-	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
-		Common::SharedPtr<Object> actor = *it;
+	for (auto actor : g_engine->_actors) {
 		if (obj->contains(actor->_node->getPos())) {
 			sq_pushobject(v, actor->_table);
 			sq_arrayappend(v, -2);
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index 71d69d3c606..bf49d9bcb13 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -21,7 +21,6 @@
 
 #include "twp/actorswitcher.h"
 #include "twp/twp.h"
-#include "twp/util.h"
 
 #define DISABLE_ALPHA 0.f
 #define ENABLE_ALPHA 1.f
@@ -89,7 +88,7 @@ void ActorSwitcher::drawCore(Math::Matrix4 trsf) {
 			ActorSwitcherSlot &slot = _slots[i];
 			drawIcon(slot.icon, slot.back, slot.frame, trsf, i);
 		}
-	} else if (_slots.size() > 0) {
+	} else if (!_slots.empty()) {
 		ActorSwitcherSlot &slot = _slots[0];
 		drawIcon(slot.icon, slot.back, slot.frame, trsf, 0);
 	}
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 9d8ef9791f0..53c0221939b 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -27,7 +27,6 @@
 #include "audio/decoders/wave.h"
 #include "twp/audio.h"
 #include "twp/twp.h"
-#include "twp/ids.h"
 #include "twp/squtil.h"
 
 #ifndef USE_VORBIS
@@ -81,9 +80,9 @@ bool AudioSystem::playing(int id) const {
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && _slots[i].sndDef->getId() == id) {
-			return g_engine->_mixer->isSoundHandleActive(_slots[i].handle);
+	for (const auto & _slot : _slots) {
+		if (_slot.busy && _slot.sndDef->getId() == id) {
+			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
 		}
 	}
 	// sound ID ?
@@ -91,9 +90,9 @@ bool AudioSystem::playing(int id) const {
 }
 
 bool AudioSystem::playing(Common::SharedPtr<SoundDefinition> soundDef) const {
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && _slots[i].sndDef == soundDef) {
-			return g_engine->_mixer->isSoundHandleActive(_slots[i].handle);
+	for (const auto & _slot : _slots) {
+		if (_slot.busy && _slot.sndDef == soundDef) {
+			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
 		}
 	}
 	return false;
@@ -119,9 +118,9 @@ void AudioSystem::stop(int id) {
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && _slots[i].sndDef->getId() == id) {
-			g_engine->_mixer->stopHandle(_slots[i].handle);
+	for (auto & _slot : _slots) {
+		if (_slot.busy && _slot.sndDef->getId() == id) {
+			g_engine->_mixer->stopHandle(_slot.handle);
 		}
 	}
 	// sound ID ?
@@ -132,9 +131,9 @@ void AudioSystem::setMasterVolume(float vol) {
 	_masterVolume = Twp::clamp(vol, 0.f, 1.f);
 
 	// update sounds
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && g_engine->_mixer->isSoundHandleActive(_slots[i].handle)) {
-			g_engine->_mixer->setChannelVolume(_slots[i].handle, _slots[i].volume * _masterVolume);
+	for (auto & _slot : _slots) {
+		if (_slot.busy && g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
+			g_engine->_mixer->setChannelVolume(_slot.handle, _slot.volume * _masterVolume);
 		}
 	}
 }
@@ -192,46 +191,46 @@ void AudioSystem::setVolume(int id, float vol) {
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID or sound ID ?
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && ((_slots[i].sndDef->getId() == id) || (g_engine->_mixer->getSoundID(_slots[i].handle) == id))) {
-			_slots[i].volume = vol;
-			updateVolume(&_slots[i]);
+	for (auto & _slot : _slots) {
+		if (_slot.busy && ((_slot.sndDef->getId() == id) || (g_engine->_mixer->getSoundID(_slot.handle) == id))) {
+			_slot.volume = vol;
+			updateVolume(&_slot);
 		}
 	}
 }
 
-void AudioSystem::update(float elapsed) {
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy && !g_engine->_mixer->isSoundHandleActive(_slots[i].handle)) {
-			if(_slots[i].loopTimes > 0) {
-				_slots[i].loopTimes--;
+void AudioSystem::update(float) {
+	for (auto & _slot : _slots) {
+		if (_slot.busy && !g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
+			if(_slot.loopTimes > 0) {
+				_slot.loopTimes--;
 				Audio::SeekableAudioStream *audioStream;
-				Common::String name = _slots[i].sndDef->getName();
-				_slots[i].stream.seek(0);
+				Common::String name = _slot.sndDef->getName();
+				_slot.stream.seek(0);
 				if (name.hasSuffixIgnoreCase(".ogg")) {
-					audioStream = Audio::makeVorbisStream(&_slots[i].stream, DisposeAfterUse::NO);
+					audioStream = Audio::makeVorbisStream(&_slot.stream, DisposeAfterUse::NO);
 				} else if (name.hasSuffixIgnoreCase(".wav")) {
-					audioStream = Audio::makeWAVStream(&_slots[i].stream, DisposeAfterUse::NO);
+					audioStream = Audio::makeWAVStream(&_slot.stream, DisposeAfterUse::NO);
 				} else {
 					error("Unexpected audio format: %s", name.c_str());
 				}
-				g_engine->_mixer->playStream(_slots[i].soundType, &_slots[i].handle, audioStream, _slots[i].id, _slots[i].volume);
+				g_engine->_mixer->playStream(_slot.soundType, &_slot.handle, audioStream, _slot.id, _slot.volume);
 			} else {
-				_slots[i].busy = false;
+				_slot.busy = false;
 			}
 		}
 	}
 	// sound definition ID or sound ID ?
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].busy) {
-			updateVolume(&_slots[i]);
+	for (auto & _slot : _slots) {
+		if (_slot.busy) {
+			updateVolume(&_slot);
 		}
 	}
 }
 
 AudioSlot *AudioSystem::getFreeSlot() {
-	for (int i = 0; i < 32; i++) {
-		AudioSlot *slot = &_slots[i];
+	for (auto & _slot : _slots) {
+		AudioSlot *slot = &_slot;
 		if (!slot->busy || !g_engine->_mixer->isSoundHandleActive(slot->handle)) {
 			slot->busy = false;
 			return slot;
@@ -278,9 +277,9 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 }
 
 int AudioSystem::getElapsed(int id) const {
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].id == id) {
-			Audio::Timestamp t = g_engine->_mixer->getElapsedTime(_slots[i].handle);
+	for (const auto & _slot : _slots) {
+		if (_slot.id == id) {
+			Audio::Timestamp t = g_engine->_mixer->getElapsedTime(_slot.handle);
 			return t.msecs();
 		}
 	}
@@ -288,9 +287,9 @@ int AudioSystem::getElapsed(int id) const {
 }
 
 int AudioSystem::getDuration(int id) const {
-	for (int i = 0; i < 32; i++) {
-		if (_slots[i].id == id) {
-			return _slots[i].total;
+	for (const auto & _slot : _slots) {
+		if (_slot.id == id) {
+			return _slot.total;
 		}
 	}
 	return 0;
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 80666dae7c9..99d603e7b40 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -20,11 +20,6 @@
  */
 
 #include "twp/twp.h"
-#include "twp/camera.h"
-#include "twp/room.h"
-#include "twp/object.h"
-#include "twp/util.h"
-#include "twp/scenegraph.h"
 
 namespace Twp {
 
diff --git a/engines/twp/console.cpp b/engines/twp/console.cpp
index b7ec67f8e74..38351da10cc 100644
--- a/engines/twp/console.cpp
+++ b/engines/twp/console.cpp
@@ -29,8 +29,7 @@ Console::Console() : GUI::Debugger() {
 	registerCmd("!",   WRAP_METHOD(Console, Cmd_exec));
 }
 
-Console::~Console() {
-}
+Console::~Console() = default;
 
 bool Console::Cmd_exec(int argc, const char **argv) {
 	Common::String s;
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 7a85857752c..4826edb7ca5 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -1,8 +1,28 @@
+/* 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 "twp/debugtools.h"
 #include "imgui/imgui.h"
 #include "twp/twp.h"
 #include "twp/thread.h"
-#include "twp/dialog.h"
 #include "twp/lighting.h"
 #include "common/debug-channels.h"
 
@@ -86,7 +106,7 @@ static void drawObjects() {
 					obj->_node->setVisible(visible);
 				}
 				ImGui::SameLine();
-				Common::String name = obj->_key == "" ? obj->getName() : Common::String::format("%s(%s) %d", obj->getName().c_str(), obj->_key.c_str(), obj->getId());
+				Common::String name = obj->_key.empty() ? obj->getName() : Common::String::format("%s(%s) %d", obj->getName().c_str(), obj->_key.c_str(), obj->getId());
 				bool selected = false;
 				if (ImGui::Selectable(name.c_str(), &selected)) {
 					// gObject = obj;
diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index f69d761414f..cdcc8c1db37 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -20,12 +20,8 @@
  */
 
 #include "base/plugins.h"
-#include "common/config-manager.h"
 #include "common/file.h"
-#include "common/md5.h"
-#include "common/str-array.h"
 #include "common/translation.h"
-#include "common/util.h"
 #include "twp/detection.h"
 #include "twp/detection_tables.h"
 
@@ -48,8 +44,8 @@ TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(T
 }
 
 ADDetectedGame TwpMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
-	for (auto it = allFiles.begin(); it != allFiles.end(); it++) {
-		if (it->_key.toString().hasSuffixIgnoreCase(".ggpack1")) {
+	for (const auto & allFile : allFiles) {
+		if (allFile._key.toString().hasSuffixIgnoreCase(".ggpack1")) {
 			return ADDetectedGame(Twp::gameDescriptions);
 		}
 	}
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index 4fb6d06d884..b33a6b49813 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -53,7 +53,7 @@ class TwpMetaEngineDetection : public AdvancedMetaEngineDetection {
 
 public:
 	TwpMetaEngineDetection();
-	~TwpMetaEngineDetection() override {}
+	~TwpMetaEngineDetection() override = default;
 
 	const char *getName() const override {
 		return "twp";
@@ -72,7 +72,7 @@ public:
 	}
 
 private:
-	virtual ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra = nullptr) const override;
+	ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const override;
 };
 
 #endif // TWP_DETECTION_H
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 446448d590c..487f3391f2b 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -20,20 +20,17 @@
  */
 
 #include "twp/twp.h"
-#include "twp/util.h"
 #include "twp/squtil.h"
-#include "twp/dialog.h"
-#include "common/debug.h"
 
 namespace Twp {
 
 class SerialMotors : public Motor {
 public:
 	SerialMotors(const std::initializer_list<Common::SharedPtr<Motor> > &motors) : _motors(motors) {}
-	SerialMotors(const Common::Array<Common::SharedPtr<Motor> > &motors) : _motors(motors) {}
+	explicit SerialMotors(const Common::Array<Common::SharedPtr<Motor> > &motors) : _motors(motors) {}
 
-	virtual void update(float elapsed) override {
-		if (_motors.size() > 0) {
+	void update(float elapsed) override {
+		if (!_motors.empty()) {
 			_motors[0]->update(elapsed);
 			if (!_motors[0]->isEnabled()) {
 				debugC(kDebugDialog, "SerialMotors next");
@@ -55,7 +52,7 @@ public:
 		: _dlg(dlg), _line(line), _name(name) {
 	}
 
-	virtual void update(float elapsed) override {
+	void update(float elapsed) override {
 		_dlg->selectLabel(_line, _name);
 		disable();
 	}
@@ -73,7 +70,7 @@ DialogConditionState CondStateVisitor::createState(int line, DialogConditionMode
 	return DialogConditionState(mode, _dlg->_context.actor, _dlg->_context.dialogName, line);
 }
 
-DialogConditionState::DialogConditionState() {}
+DialogConditionState::DialogConditionState() = default;
 
 DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String& k, const Common::String& dlg, int ln)
 : mode(m), actorKey(k), dialog(dlg), line(ln) {
@@ -100,7 +97,7 @@ void CondStateVisitor::visit(const YTempOnce &node) {
 }
 
 ExpVisitor::ExpVisitor(Dialog *dialog) : _dialog(dialog) {}
-ExpVisitor::~ExpVisitor() {}
+ExpVisitor::~ExpVisitor() = default;
 
 void ExpVisitor::visit(const YCodeExp &node) {
 	debugC(kDebugDialog, "execute code %s", node._code.c_str());
@@ -157,7 +154,7 @@ void ExpVisitor::visit(const YSay &node) {
 }
 
 CondVisitor::CondVisitor(Dialog *dialog) : _dialog(dialog) {}
-CondVisitor::~CondVisitor() {}
+CondVisitor::~CondVisitor() = default;
 
 void CondVisitor::visit(const YCodeCond &node) {
 	_accepted = _dialog->isCond(node._code);
@@ -182,7 +179,7 @@ void CondVisitor::visit(const YTempOnce &node) {
 DialogSlot::DialogSlot() : Node("DialogSlot") {}
 
 Dialog::Dialog() : Node("Dialog") {}
-Dialog::~Dialog() {}
+Dialog::~Dialog() = default;
 
 static YChoice *getChoice(DialogSlot *slot) {
 	return (YChoice *)(slot->_stmt->_exp.get());
@@ -268,8 +265,7 @@ void Dialog::update(float dt) {
 }
 
 bool Dialog::isOnce(int line) const {
-	for (size_t i = 0; i < _states.size(); i++) {
-		const DialogConditionState &state = _states[i];
+	for (const auto & state : _states) {
 		if (state.mode == Once && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isOnce %d: false", line);
 			return false;
@@ -280,8 +276,7 @@ bool Dialog::isOnce(int line) const {
 }
 
 bool Dialog::isShowOnce(int line) const {
-	for (size_t i = 0; i < _states.size(); i++) {
-		const DialogConditionState &state = _states[i];
+	for (const auto & state : _states) {
 		if (state.mode == ShowOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isShowOnce %d: false", line);
 			return false;
@@ -292,8 +287,7 @@ bool Dialog::isShowOnce(int line) const {
 }
 
 bool Dialog::isOnceEver(int line) const {
-	for (size_t i = 0; i < _states.size(); i++) {
-		const DialogConditionState &state = _states[i];
+	for (const auto & state : _states) {
 		if (state.mode == OnceEver && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isOnceEver %d: false", line);
 			return false;
@@ -304,8 +298,7 @@ bool Dialog::isOnceEver(int line) const {
 }
 
 bool Dialog::isTempOnce(int line) const {
-	for (size_t i = 0; i < _states.size(); i++) {
-		const DialogConditionState &state = _states[i];
+	for (const auto & state : _states) {
 		if (state.mode == TempOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isTempOnce %d: false", line);
 			return false;
@@ -322,15 +315,13 @@ bool Dialog::isCond(const Common::String &cond) const {
 }
 
 Common::SharedPtr<YLabel> Dialog::label(int line, const Common::String &name) const {
-	for (size_t i = 0; i < _cu->_labels.size(); i++) {
-		Common::SharedPtr<YLabel> label = _cu->_labels[i];
+	for (auto label : _cu->_labels) {
 		if ((label->_name == name) && (label->_line >= line)) {
 			return label;
 		}
 	}
 	line = 0;
-	for (size_t i = 0; i < _cu->_labels.size(); i++) {
-		Common::SharedPtr<YLabel> label = _cu->_labels[i];
+	for (auto label : _cu->_labels) {
 		if ((label->_name == name) && (label->_line >= line)) {
 			return label;
 		}
@@ -360,11 +351,10 @@ void Dialog::gotoNextLabel() {
 
 void Dialog::updateChoiceStates() {
 	_state = WaitingForChoice;
-	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
-		DialogSlot *slot = &_slots[i];
+	for (auto & _slot : _slots) {
+		DialogSlot *slot = &_slot;
 		if (slot->_isValid) {
-			for (size_t j = 0; j < slot->_stmt->_conds.size(); j++) {
-				Common::SharedPtr<YCond> cond = slot->_stmt->_conds[j];
+			for (auto cond : slot->_stmt->_conds) {
 				CondStateVisitor v(this, DialogSelMode::Show);
 				cond->accept(v);
 			}
@@ -386,8 +376,7 @@ void Dialog::run(Common::SharedPtr<YStatement> stmt) {
 
 bool Dialog::acceptConditions(Common::SharedPtr<YStatement> stmt) {
 	CondVisitor vis(this);
-	for (size_t i = 0; i < stmt->_conds.size(); i++) {
-		Common::SharedPtr<YCond> cond = stmt->_conds[i];
+	for (auto cond : stmt->_conds) {
 		cond->accept(vis);
 		if (!vis._accepted) {
 			return false;
@@ -453,22 +442,22 @@ void Dialog::addSlot(Common::SharedPtr<YStatement> stmt) {
 
 int Dialog::numSlots() const {
 	int num = 0;
-	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
-		if (_slots[i]._isValid)
+	for (const auto & _slot : _slots) {
+		if (_slot._isValid)
 			num++;
 	}
 	return num;
 }
 
 void Dialog::clearSlots() {
-	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
-		_slots[i]._isValid = false;
+	for (auto & _slot : _slots) {
+		_slot._isValid = false;
 	}
 }
 
 void Dialog::drawCore(Math::Matrix4 trsf) {
-	for (size_t i = 0; i < MAXDIALOGSLOTS; i++) {
-		DialogSlot *slot = &_slots[i];
+	for (auto & _slot : _slots) {
+		DialogSlot *slot = &_slot;
 		if (slot->_isValid) {
 			Math::Matrix4 t(trsf);
 			t.translate(Math::Vector3d(slot->getPos().getX(), slot->getPos().getY(), 0.f));
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 232219a1e20..cb99d4e600b 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -78,7 +78,7 @@ enum DialogSelMode {
 struct DialogConditionState {
 	DialogConditionMode mode;
 	Common::String actorKey, dialog;
-	int line;
+	int line{};
 
     DialogConditionState();
 	DialogConditionState(DialogConditionMode mode, const Common::String& actorKey, const Common::String& dialog, int line);
diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index fb22a7681d8..e4b2a5da881 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -22,7 +22,6 @@
 #include "common/translation.h"
 
 #include "gui/gui-manager.h"
-#include "gui/widget.h"
 #include "gui/widgets/edittext.h"
 #include "gui/widgets/popup.h"
 #include "gui/ThemeEval.h"
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index 434cccf720e..f73cd752d18 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -30,10 +30,10 @@ namespace Twp {
 class TwpOptionsContainerWidget : public GUI::OptionsContainerWidget {
 public:
 	TwpOptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &domain);
-	~TwpOptionsContainerWidget() override {}
+	~TwpOptionsContainerWidget() override = default;
 
-	virtual void load() override;
-	virtual bool save() override;
+	void load() override;
+	bool save() override;
 
 private:
 	void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 2d80b1a8228..e55a5c9bf48 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -26,9 +26,9 @@ namespace Twp {
 
 class Pause : public Motor {
 public:
-	Pause(float time) : _time(time) {}
+	explicit Pause(float time) : _time(time) {}
 
-	virtual void update(float elapsed) override {
+	void update(float elapsed) override {
 		_time -= elapsed;
 		if (_time <= 0)
 			disable();
@@ -42,7 +42,7 @@ class WaitWhile : public Motor {
 public:
 	WaitWhile(EngineDialogTarget* target, const Common::String& cond) : _target(target), _cond(cond) {}
 
-	virtual void update(float elapsed) override {
+	void update(float elapsed) override {
 		if (!_target->execCond(_cond))
     		disable();
 	}
@@ -53,11 +53,9 @@ private:
 };
 
 static Common::SharedPtr<Object> actor(const Common::String &name) {
-	// for (actor in gEngine.actors) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
-		if (actor->_key == name)
-			return actor;
+	for (auto a : g_engine->_actors) {
+		if (a->_key == name)
+			return a;
 	}
 	return nullptr;
 }
diff --git a/engines/twp/enginedialogtarget.h b/engines/twp/enginedialogtarget.h
index 4e5048157d5..205f46934f5 100644
--- a/engines/twp/enginedialogtarget.h
+++ b/engines/twp/enginedialogtarget.h
@@ -28,13 +28,13 @@ namespace Twp {
 
 class EngineDialogTarget : public DialogTarget {
 public:
-	virtual Color actorColor(const Common::String &actor) override;
-	virtual Color actorColorHover(const Common::String &actor) override;
-	virtual Common::SharedPtr<Motor> say(const Common::String &actor, const Common::String &text) override;
-	virtual Common::SharedPtr<Motor> waitWhile(const Common::String &cond) override;
-	virtual void shutup() override;
-	virtual Common::SharedPtr<Motor> pause(float time) override;
-	virtual bool execCond(const Common::String &cond) override;
+	Color actorColor(const Common::String &actor) override;
+	Color actorColorHover(const Common::String &actor) override;
+	Common::SharedPtr<Motor> say(const Common::String &actor, const Common::String &text) override;
+	Common::SharedPtr<Motor> waitWhile(const Common::String &cond) override;
+	void shutup() override;
+	Common::SharedPtr<Motor> pause(float time) override;
+	bool execCond(const Common::String &cond) override;
 };
 
 } // namespace Twp
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 3432b0d5710..1946b0e7c1b 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -21,9 +21,6 @@
 
 #include "twp/font.h"
 #include "twp/twp.h"
-#include "twp/ggpack.h"
-#include "common/str.h"
-#include "graphics/opengl/system_headers.h"
 
 namespace Twp {
 
@@ -54,7 +51,7 @@ typedef struct Line {
 
 class TokenReader {
 public:
-	TokenReader(const Common::U32String &text);
+	explicit TokenReader(const Common::U32String &text);
 	Common::U32String substr(Token tok);
 	bool readToken(Token &token);
 
diff --git a/engines/twp/font.h b/engines/twp/font.h
index 666a438f1ab..6e701195008 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -65,7 +65,7 @@ struct Glyph {
 
 class Font {
 public:
-	virtual ~Font() {}
+	virtual ~Font() = default;
 
 	virtual int getLineHeight() = 0;
 	virtual float getKerning(CodePoint prev, CodePoint next) = 0;
@@ -76,13 +76,13 @@ public:
 // Represents a bitmap font from a spritesheet.
 class GGFont : public Font {
 public:
-	virtual ~GGFont();
+	~GGFont() override;
 	void load(const Common::String &path);
 
-	virtual int getLineHeight() override final { return _lineHeight; }
-	virtual float getKerning(CodePoint prev, CodePoint next) override final { return 0.f; }
-	virtual Glyph getGlyph(CodePoint chr) override final;
-	virtual Common::String getName() override final { return _name; }
+	int getLineHeight() final { return _lineHeight; }
+	float getKerning(CodePoint prev, CodePoint next) final { return 0.f; }
+	Glyph getGlyph(CodePoint chr) final;
+	Common::String getName() final { return _name; }
 
 private:
 	Common::HashMap<CodePoint, Glyph> _glyphs;
@@ -106,13 +106,13 @@ struct Char {
 
 class BmFont : public Font {
 public:
-	virtual ~BmFont();
+	~BmFont() override;
 	void load(const Common::String &path);
 
-	virtual int getLineHeight() override final { return _lnHeight; }
-	virtual float getKerning(CodePoint prev, CodePoint next) override final;
-	virtual Glyph getGlyph(CodePoint chr) override final;
-	virtual Common::String getName() override final { return _name; }
+	int getLineHeight() final { return _lnHeight; }
+	float getKerning(CodePoint prev, CodePoint next) final;
+	Glyph getGlyph(CodePoint chr) final;
+	Common::String getName() final { return _name; }
 
 private:
 	Common::HashMap<CodePoint, Glyph> _glyphs;
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index d5a4f29be5c..ab69434e920 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -21,29 +21,19 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
-#include "twp/room.h"
-#include "twp/object.h"
-#include "twp/scenegraph.h"
 #include "twp/squtil.h"
-#include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
-#include "twp/squirrel/sqobject.h"
 #include "twp/squirrel/sqstring.h"
 #include "twp/squirrel/sqstate.h"
 #include "twp/squirrel/sqtable.h"
-#include "twp/squirrel/sqstdstring.h"
-#include "twp/squirrel/sqstdmath.h"
-#include "twp/squirrel/sqstdio.h"
-#include "twp/squirrel/sqstdaux.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
 #include "common/crc.h"
-#include "common/stream.h"
 
 namespace Twp {
 
 struct GetArray {
-	GetArray(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
+	explicit GetArray(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
 
 	void operator()(HSQOBJECT &o) {
 		_objs.push_back(o);
@@ -59,7 +49,7 @@ static void shuffle(Common::Array<T> &array) {
 		Common::RandomSource &rnd = g_engine->getRandomSource();
 		for (size_t i = 0; i < array.size() - 1; i++) {
 			size_t j = i + rnd.getRandomNumber(RAND_MAX) / (RAND_MAX / (array.size() - i) + 1);
-			T &t = array[j];
+			const T &t = array[j];
 			array[j] = array[i];
 			array[i] = t;
 		}
@@ -87,8 +77,8 @@ static SQInteger arrayShuffle(HSQUIRRELVM v) {
 	shuffle(arr);
 
 	sq_newarray(v, 0);
-	for (auto it = arr.begin(); it != arr.end(); it++) {
-		sqpush(v, *it);
+	for (auto & it : arr) {
+		sqpush(v, it);
 		sq_arrayappend(v, -2);
 	}
 	return 1;
@@ -423,9 +413,9 @@ static SQInteger in_array(HSQUIRRELVM v) {
 	}
 	sq_pop(v, 1); // pops the null iterator
 
-	for (auto it = objs.begin(); it != objs.end(); it++) {
+	for (auto & it : objs) {
 		sq_pushobject(v, obj);
-		sq_pushobject(v, *it);
+		sq_pushobject(v, it);
 		if (sq_cmp(v) == 0) {
 			sq_pop(v, 2);
 			sqpush(v, 1);
@@ -633,11 +623,13 @@ static SQInteger randomseed(HSQUIRRELVM v) {
 	}
 	case 2: {
 		int seed = 0;
-		if (sq_gettype(v, 2) == OT_NULL)
+		if (sq_gettype(v, 2) == OT_NULL) {
 			g_engine->getRandomSource().setSeed(g_engine->getRandomSource().generateNewSeed());
-		return 0;
-		if (SQ_FAILED(sqget(v, 2, seed)))
+			return 0;
+		}
+		if (SQ_FAILED(sqget(v, 2, seed))) {
 			return sq_throwerror(v, "failed to get seed");
+		}
 		g_engine->getRandomSource().setSeed(seed);
 		return 0;
 	}
@@ -695,7 +687,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get verb definitionTable");
 	if (!sq_istable(table))
 		return sq_throwerror(v, "verb definitionTable is not a table");
-	int id;
+	int id = 0;
 	Common::String image;
 	Common::String text;
 	Common::String fun;
@@ -739,10 +731,8 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	switch (nArgs) {
 	case 1: {
-		for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-			Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
-			for (size_t j = 0; j < layer->_objects.size(); j++) {
-				Common::SharedPtr<Object> obj = layer->_objects[j];
+		for (auto layer : g_engine->_room->_layers) {
+			for (auto obj : layer->_objects) {
 				obj->_exec.enabled = false;
 			}
 		}
@@ -894,7 +884,7 @@ static SQInteger strsplit(HSQUIRRELVM v) {
 	while (tok) {
 		sq_pushstring(v, tok, -1);
 		sq_arrayappend(v, -2);
-		tok = strtok(NULL, delimiter);
+		tok = strtok(nullptr, delimiter);
 	}
 	return 1;
 }
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 3b5c151c63f..31c45c6f53d 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -21,10 +21,6 @@
 
 #include "twp/gfx.h"
 #include "twp/twp.h"
-#include "common/debug.h"
-#include "graphics/opengl/debug.h"
-#include "graphics/opengl/context.h"
-#include "graphics/opengl/system_headers.h"
 
 namespace Twp {
 
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index d2907116f63..1a4429096f0 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -22,8 +22,6 @@
 #define FORBIDDEN_SYMBOL_ALLOW_ALL
 
 #include "twp/graph.h"
-#include "twp/util.h"
-#include "twp/clipper/clipper.hpp"
 
 namespace Twp {
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 8f6b87026aa..a3211de532f 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -21,29 +21,25 @@
 
 #include "twp/hud.h"
 #include "twp/twp.h"
-#include "math/vector2d.h"
-#include "graphics/opengl/debug.h"
-#include "graphics/opengl/system_headers.h"
 #include "common/config-manager.h"
 
 namespace Twp {
 
-Verb::Verb() {}
+Verb::Verb() = default;
 
 Verb::Verb(VerbId verbId, const Common::String& img, const Common::String& f, const Common::String& t, const Common::String& k, int fl)
 : id(verbId), image(img), fun(f), text(t), key(k), flags(fl) {
 }
 
-VerbUiColors::VerbUiColors() {}
+VerbUiColors::VerbUiColors() = default;
 
 VerbUiColors::VerbUiColors(Color s, Color vbNormal, Color vbNormalTint, Color vbHiglight, Color vbHiglightTint, Color dlgNormal, Color dlgHighlt, Color invFrame, Color inventoryBack, Color retroNml, Color retroHighlt)
 : sentence(s), verbNormal(vbNormal), verbNormalTint(vbNormalTint), verbHighlight(vbHiglight), verbHighlightTint(vbHiglightTint), dialogNormal(dlgNormal), dialogHighlight(dlgHighlt), inventoryFrame(invFrame), inventoryBackground(inventoryBack), retroNormal(retroNml), retroHighlight(retroHighlt) {
 }
 
-ActorSlot::ActorSlot() {}
+ActorSlot::ActorSlot() = default;
 
-HudShader::HudShader() {
-}
+HudShader::HudShader() = default;
 
 void HudShader::init() {
 	const char *vsrc = R"(#version 110
@@ -101,7 +97,7 @@ void HudShader::init() {
 	Shader::init("hud", v_source, f_source);
 }
 
-HudShader::~HudShader() {}
+HudShader::~HudShader() = default;
 
 void HudShader::applyUniforms() {
 	setUniform("u_ranges", Math::Vector2d(0.8f, 0.8f));
@@ -112,8 +108,8 @@ void HudShader::applyUniforms() {
 
 Hud::Hud() : Node("hud") {
 	_zOrder = 100;
-	for (int i = 0; i < NUMACTORS; i++) {
-		ActorSlot *slot = &_actorSlots[i];
+	for (auto & _actorSlot : _actorSlots) {
+		ActorSlot *slot = &_actorSlot;
 		slot->actor = nullptr;
 	}
 }
@@ -123,8 +119,8 @@ void Hud::init() {
 }
 
 ActorSlot *Hud::actorSlot(Common::SharedPtr<Object> actor) {
-	for (int i = 0; i < NUMACTORS; i++) {
-		ActorSlot *slot = &_actorSlots[i];
+	for (auto & _actorSlot : _actorSlots) {
+		ActorSlot *slot = &_actorSlot;
 		if (slot->actor == actor) {
 			return slot;
 		}
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index f099054651b..1fd2a9c7b78 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -55,7 +55,7 @@ struct Verb {
 	Common::String fun;
 	Common::String text;
 	Common::String key;
-	int flags;
+	int flags{};
 
 	Verb();
 	Verb(VerbId id, const Common::String& image, const Common::String& fun, const Common::String& text, const Common::String& key, int flags = 0);
@@ -72,9 +72,9 @@ public:
 	ActorSlot();
 
 	Verb *getVerb(int id) {
-		for (int i = 0; i < MAX_VERBS; i++) {
-			if (verbs[i].id.id == id) {
-				return &verbs[i];
+		for (auto & verb : verbs) {
+			if (verb.id.id == id) {
+				return &verb;
 			}
 		}
 		return nullptr;
@@ -90,12 +90,12 @@ struct VerbRect {
 class HudShader : public Shader {
 public:
 	HudShader();
-	virtual ~HudShader() override;
+	~HudShader() override;
 
 	void init();
 
 private:
-	virtual void applyUniforms() final;
+	void applyUniforms() final;
 
 public:
 	Color _shadowColor;
@@ -115,13 +115,12 @@ public:
 	void setVisible(bool visible) override;
 
 private:
-	virtual void drawCore(Math::Matrix4 trsf) override final;
+	void drawCore(Math::Matrix4 trsf) final;
 	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 
 public:
 	ActorSlot _actorSlots[NUMACTORS];
 	Common::SharedPtr<Object> _actor;
-	VerbRect _verbRects[NUMVERBS];
 	Verb _verb;
 	HudShader _shader;
 	Math::Vector2d _mousePos;
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 496bbfb8c4a..aee21d0e892 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -1,8 +1,6 @@
 #include "twp/lighting.h"
 #include "twp/room.h"
 #include "twp/twp.h"
-#include "graphics/opengl/debug.h"
-#include "graphics/opengl/system_headers.h"
 #include "common/math.h"
 
 namespace Twp {
@@ -160,7 +158,7 @@ Lighting::Lighting() {
 	init("lighting", vshader, fshader);
 }
 
-Lighting::~Lighting() {}
+Lighting::~Lighting() = default;
 
 void Lighting::setSpriteOffset(const Math::Vector2d &offset) {
 	_spriteOffset = offset;
diff --git a/engines/twp/lip.cpp b/engines/twp/lip.cpp
index a2dd2c0184f..9b1e4c86564 100644
--- a/engines/twp/lip.cpp
+++ b/engines/twp/lip.cpp
@@ -35,7 +35,7 @@ void Lip::load(Common::SeekableReadStream *stream) {
 }
 
 char Lip::letter(float time) {
-	if (_items.size() == 0)
+	if (_items.empty())
 		return 'A';
 	for (size_t i = 0; i < _items.size() - 1; i++) {
 		if (time < _items[i + 1].time) {
diff --git a/engines/twp/lip.h b/engines/twp/lip.h
index 51955b5978f..401eb054464 100644
--- a/engines/twp/lip.h
+++ b/engines/twp/lip.h
@@ -29,8 +29,8 @@
 namespace Twp {
 
 struct LipItem {
-	float time;
-	char letter;
+	float time = 0.f;
+	char letter = '\0';
 };
 
 // This contains the lip animation for a specific text.
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 94b2502e010..7c2d7a24067 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -20,7 +20,6 @@
  */
 
 #include "gui/gui-manager.h"
-#include "gui/widget.h"
 #include "gui/widgets/edittext.h"
 #include "gui/widgets/popup.h"
 #include "gui/ThemeEval.h"
@@ -34,7 +33,6 @@
 #include "twp/metaengine.h"
 #include "twp/detection.h"
 #include "twp/twp.h"
-#include "twp/savegame.h"
 #include "twp/time.h"
 #include "twp/actions.h"
 #include "twp/dialogs.h"
@@ -171,8 +169,6 @@ GUI::OptionsContainerWidget *TwpMetaEngine::buildEngineOptionsWidget(GUI::GuiObj
 Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) const {
 	Common::Keymap *engineKeyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, target, "Thimbleweed Park keymap");
 
-	Common::Action *act;
-
 	struct {
 		Common::String name;
 		const Common::U32String desc;
@@ -198,6 +194,7 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 		{"SHOWHOTSPOTS", _("Show hotspots"), Twp::kShowHotspots, "TAB"},
 	};
 
+	Common::Action *act;
 	for (int i = 0; i < ARRAYSIZE(actions); i++) {
 		act = new Common::Action(actions[i].name.c_str(), actions[i].desc);
 		act->setCustomEngineActionEvent(actions[i].action);
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index fcdcb5366fe..e96d7f5ed7e 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -23,14 +23,11 @@
 #include "common/config-manager.h"
 
 #include "twp/twp.h"
-#include "twp/motor.h"
-#include "twp/object.h"
-#include "twp/scenegraph.h"
 #include "twp/squtil.h"
 
 namespace Twp {
 
-OffsetTo::~OffsetTo() {}
+OffsetTo::~OffsetTo() = default;
 
 OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
 	: _obj(obj),
@@ -44,7 +41,7 @@ void OffsetTo::update(float elapsed) {
 		disable();
 }
 
-MoveTo::~MoveTo() {}
+MoveTo::~MoveTo() = default;
 
 MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
 	: _obj(obj),
@@ -58,7 +55,7 @@ void MoveTo::update(float elapsed) {
 		disable();
 }
 
-AlphaTo::~AlphaTo() {}
+AlphaTo::~AlphaTo() = default;
 
 AlphaTo::AlphaTo(float duration, Common::SharedPtr<Object> obj, float to, InterpolationMethod im)
 	: _obj(obj),
@@ -73,7 +70,7 @@ void AlphaTo::update(float elapsed) {
 		disable();
 }
 
-RotateTo::~RotateTo() {}
+RotateTo::~RotateTo() = default;
 
 RotateTo::RotateTo(float duration, Node *node, float to, InterpolationMethod im)
 	: _node(node),
@@ -87,7 +84,7 @@ void RotateTo::update(float elapsed) {
 		disable();
 }
 
-RoomRotateTo::~RoomRotateTo() {}
+RoomRotateTo::~RoomRotateTo() = default;
 
 RoomRotateTo::RoomRotateTo(Common::SharedPtr<Room> room, float to)
 	: _room(room),
@@ -101,7 +98,7 @@ void RoomRotateTo::update(float elapsed) {
 		disable();
 }
 
-ScaleTo::~ScaleTo() {}
+ScaleTo::~ScaleTo() = default;
 
 ScaleTo::ScaleTo(float duration, Node *node, float to, InterpolationMethod im)
 	: _node(node),
@@ -116,7 +113,7 @@ void ScaleTo::update(float elapsed) {
 		disable();
 }
 
-Shake::~Shake() {}
+Shake::~Shake() = default;
 
 Shake::Shake(Node *node, float amount)
 	: _node(node),
@@ -135,7 +132,7 @@ OverlayTo::OverlayTo(float duration, Common::SharedPtr<Room> room, Color to)
 	  _tween(g_engine->_room->getOverlay(), to, duration, InterpolationMethod()) {
 }
 
-OverlayTo::~OverlayTo() {}
+OverlayTo::~OverlayTo() = default;
 
 void OverlayTo::update(float elapsed) {
 	_tween.update(elapsed);
@@ -148,8 +145,7 @@ ReachAnim::ReachAnim(Common::SharedPtr<Object> actor, Common::SharedPtr<Object>
 	: _actor(actor), _obj(obj) {
 }
 
-ReachAnim::~ReachAnim() {
-}
+ReachAnim::~ReachAnim() = default;
 
 void ReachAnim::playReachAnim() {
 	Common::String anim = _actor->getAnimName(REACH_ANIMNAME + _obj->getReachAnim());
@@ -193,7 +189,7 @@ WalkTo::WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing)
 
 void WalkTo::disable() {
 	Motor::disable();
-	if (_path.size() != 0) {
+	if (!_path.empty()) {
 		debugC(kDebugGame, "actor walk cancelled");
 	}
 	if (_obj->isWalking())
@@ -409,7 +405,7 @@ void Talking::say(const Common::String &text) {
 		txt = text.substr(1);
 	}
 
-	// remove text in parenthesis
+	// remove text in parentheses
 	if (txt[0] == '(') {
 		int i = txt.find(')');
 		if (i != -1)
@@ -501,7 +497,7 @@ Common::String Talking::talkieKey() {
 Jiggle::Jiggle(Node *node, float amount) : _amount(amount), _node(node) {
 }
 
-Jiggle::~Jiggle() {}
+Jiggle::~Jiggle() = default;
 
 void Jiggle::update(float elapsed) {
 	_jiggleTime += 20.f * elapsed;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 23b49a62edd..657296d7d33 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -20,13 +20,7 @@
  */
 
 #include "twp/twp.h"
-#include "twp/ids.h"
-#include "twp/object.h"
-#include "twp/scenegraph.h"
 #include "twp/squtil.h"
-#include "twp/util.h"
-#include "twp/ggpack.h"
-#include "twp/motor.h"
 
 #define MIN_TALK_DIST 60
 #define MIN_USE_DIST 15
@@ -404,9 +398,7 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 				layer->_node->addChild(object->_node.get());
 			}
 		}
-		if (object->_room != room) {
-			object->_room = room;
-		}
+		object->_room = room;
 	}
 }
 
@@ -714,7 +706,7 @@ static void cantReach(Common::SharedPtr<Object> self, Common::SharedPtr<Object>
 	} else {
 		HSQOBJECT nilTbl;
 		sq_resetobject(&nilTbl);
-		sqcall(g_engine->_defaultObj, "verbCantReach", self->_table, !noun2 ? nilTbl : noun2->_table);
+		sqcall(g_engine->_defaultObj, "verbCantReach", self->_table, nilTbl);
 	}
 }
 
@@ -753,7 +745,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 			}
 			float dist = distance((Vector2i)obj->getUsePos(), (Vector2i)noun2->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
-			debugC(kDebugGame, "actorArrived: noun2 min_dist: {dist} > {min_dist} ?");
+			debugC(kDebugGame, "actorArrived: noun2 min_dist: %f > %f ?", dist, min_dist);
 			if (dist > min_dist) {
 				cantReach(noun1, noun2);
 				return;
@@ -807,7 +799,7 @@ void Object::inventoryScrollDown() {
 }
 
 void TalkingState::say(const Common::StringArray &texts, Common::SharedPtr<Object> obj) {
-	Talking *talking = static_cast<Talking *>(obj->getTalking().get());
+	Talking *talking = dynamic_cast<Talking *>(obj->getTalking().get());
 	if (!talking) {
 		obj->setTalking(Common::SharedPtr<Talking>(new Talking(obj, texts, _color)));
 	} else {
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 62964a51884..649c35c3843 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -22,28 +22,16 @@
 #include "twp/twp.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
-#include "twp/object.h"
-#include "twp/camera.h"
-#include "twp/motor.h"
-#include "twp/room.h"
-#include "twp/scenegraph.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
-#include "squirrel/sqobject.h"
 #include "squirrel/sqstring.h"
 #include "squirrel/sqstate.h"
 #include "squirrel/sqtable.h"
-#include "squirrel/sqstdstring.h"
-#include "squirrel/sqstdmath.h"
-#include "squirrel/sqstdio.h"
-#include "squirrel/sqstdaux.h"
 #include "squirrel/sqfuncproto.h"
 #include "squirrel/sqclosure.h"
 
 namespace Twp {
 
-extern TwpEngine *g_engine;
-
 // Creates a new, room local object using sheet as the sprite sheet and image as the image name.
 // This object is deleted when the room exits.
 // If sheet parameter not provided, use room's sprite sheet instead.
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 367d4e1b046..a594944733a 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -20,12 +20,9 @@
  */
 
 #include "common/config-manager.h"
-#include "common/str.h"
 #include "image/png.h"
 #include "twp/resmanager.h"
-#include "twp/gfx.h"
 #include "twp/twp.h"
-#include "twp/ggpack.h"
 
 namespace Twp {
 
@@ -54,6 +51,7 @@ void ResManager::loadTexture(const Common::String &name) {
 	const Graphics::Surface *surface = d.getSurface();
 	if(!surface) {
 		error("PNG %s not loaded, please check USE_PNG flag", name.c_str());
+		return;
 	}
 
 	_textures[name].load(*surface);
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index c2a2bc1b3a5..dcae804cb54 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -22,16 +22,8 @@
 #define FORBIDDEN_SYMBOL_ALLOW_ALL
 
 #include "twp/twp.h"
-#include "twp/room.h"
-#include "twp/ggpack.h"
 #include "twp/squtil.h"
-#include "twp/font.h"
-#include "twp/scenegraph.h"
-#include "twp/ids.h"
-#include "twp/object.h"
-#include "twp/util.h"
 #include "twp/clipper/clipper.hpp"
-#include "common/algorithm.h"
 
 namespace Twp {
 
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 18270397043..fda27f8e961 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -21,11 +21,7 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
-#include "twp/room.h"
-#include "twp/object.h"
 #include "twp/squtil.h"
-#include "twp/scenegraph.h"
-#include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
@@ -210,7 +206,7 @@ static SQInteger lightTurnOn(HSQUIRRELVM v) {
 }
 
 static SQInteger lightZRange(HSQUIRRELVM v) {
-	Light *light = sqlight(v, 2);
+	const Light *light = sqlight(v, 2);
 	if (light) {
 		int nearY, farY;
 		if (SQ_FAILED(sqget(v, 3, nearY)))
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 3d00fcc237d..6ad8eef5c65 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -24,7 +24,6 @@
 #include "twp/squtil.h"
 #include "twp/btea.h"
 #include "twp/time.h"
-#include "common/file.h"
 #include "common/savefile.h"
 
 namespace Twp {
@@ -96,9 +95,9 @@ static DialogConditionState parseState(Common::String &dialog) {
 
 static Common::SharedPtr<Room> room(const Common::String &name) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
-		if (room->_name == name) {
-			return room;
+		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+		if (r->_name == name) {
+			return r;
 		}
 	}
 	return nullptr;
@@ -118,11 +117,11 @@ static Common::SharedPtr<Object> object(Common::SharedPtr<Room> room, const Comm
 
 static Common::SharedPtr<Object> object(const Common::String &key) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
-		for (size_t j = 0; j < room->_layers.size(); j++) {
-			Common::SharedPtr<Layer> layer = room->_layers[j];
+		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+		for (size_t j = 0; j < r->_layers.size(); j++) {
+			Common::SharedPtr<Layer> layer(r->_layers[j]);
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
-				Common::SharedPtr<Object> o = layer->_objects[k];
+				Common::SharedPtr<Object> o(layer->_objects[k]);
 				if (o->_key == key)
 					return o;
 			}
@@ -222,8 +221,8 @@ static void setAnimations(Common::SharedPtr<Object> actor, const Common::JSONArr
 }
 
 static void loadActor(Common::SharedPtr<Object> actor, const Common::JSONObject &json) {
-	actor->setTouchable(json.contains("_untouchable") ? json["_untouchable"]->asIntegerNumber() != 1 : true);
-	actor->_node->setVisible(json.contains("_hidden") ? json["_hidden"]->asIntegerNumber() != 1 : true);
+	actor->setTouchable(!json.contains("_untouchable") || json["_untouchable"]->asIntegerNumber() != 1);
+	actor->_node->setVisible(!json.contains("_hidden") || json["_hidden"]->asIntegerNumber() != 1);
 	actor->_volume = json.contains("_volume") ? json["_volume"]->asNumber() : 1.0f;
 	for (auto it = json.begin(); it != json.end(); it++) {
 		if (it->_key == "_animations") {
@@ -484,7 +483,7 @@ void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 }
 
 struct GetHObjects {
-	GetHObjects(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
+	explicit GetHObjects(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
 
 	void operator()(HSQOBJECT item) {
 		_objs.push_back(item);
@@ -543,13 +542,13 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 		const Common::JSONObject &jInventory = json->asObject();
 		const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
 		for (int i = 0; i < NUMACTORS; i++) {
-			Common::SharedPtr<Object> actor = g_engine->_hud._actorSlots[i].actor;
-			if (actor) {
-				int jiggleCount = 0;
-				actor->_inventory.clear();
+			Common::SharedPtr<Object> a(g_engine->_hud._actorSlots[i].actor);
+			if (a) {
+				a->_inventory.clear();
 				const Common::JSONObject &jSlot = jSlots[i]->asObject();
 				if (jSlot.contains("objects")) {
 					if (jSlot["objects"]->isArray()) {
+						int jiggleCount = 0;
 						const Common::JSONArray &jSlotObjects = jSlot["objects"]->asArray();
 						for (size_t j = 0; j < jSlotObjects.size(); j++) {
 							const Common::JSONValue *jObj = jSlotObjects[j];
@@ -557,13 +556,13 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 							if (!obj)
 								warning("inventory obj '%s' not found", jObj->asString().c_str());
 							else {
-								Object::pickupObject(actor, obj);
+								Object::pickupObject(a, obj);
 								obj->_jiggle = jSlot.contains("jiggle") && jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
 							}
 						}
 					}
 				}
-				actor->_inventoryOffset = jSlot["scroll"]->asIntegerNumber();
+				a->_inventoryOffset = jSlot["scroll"]->asIntegerNumber();
 			}
 		}
 	}
@@ -607,7 +606,7 @@ static void fillMissingProperties(const Common::String &k, HSQOBJECT &oTable, vo
 }
 
 struct GetJArray {
-	GetJArray(Common::JSONArray &arr) : _arr(arr) {}
+	explicit GetJArray(Common::JSONArray &arr) : _arr(arr) {}
 
 	void operator()(HSQOBJECT item) {
 		_arr.push_back(tojson(item, true));
@@ -720,9 +719,9 @@ static Common::JSONValue *createJActor(Common::SharedPtr<Object> actor) {
 static Common::JSONValue *createJActors() {
 	Common::JSONObject jActors;
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
-		if (actor->_key.size() > 0) {
-			jActors[actor->_key] = createJActor(actor);
+		Common::SharedPtr<Object> a(g_engine->_actors[i]);
+		if (a->_key.size() > 0) {
+			jActors[a->_key] = createJActor(a);
 		}
 	}
 	// result.fields.sort(cmpKey);
@@ -943,9 +942,9 @@ static Common::JSONValue *createJRoom(Common::SharedPtr<Room> room) {
 static Common::JSONValue *createJRooms() {
 	Common::JSONObject json;
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
-		if (room)
-			json[room->_name] = createJRoom(room);
+		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+		if (r)
+			json[r->_name] = createJRoom(r);
 	}
 	//   result.fields.sort(cmpKey);
 	return new Common::JSONValue(json);
@@ -997,7 +996,7 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	byte marker = (8 - ((fullSize + 9) % 8));
 
 	// write at the end 16 bytes: hashdata (4 bytes) + savetime (4 bytes) + marker (8 bytes)
-	int32 *p = (int32 *)(buffer.data() + fullSize);
+	int32 *p = (int32*)(buffer.data() + fullSize);
 	p[0] = hash;
 	p[1] = savetime;
 	memset(&p[2], marker, 8);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index f7f584a1068..a372a5f84e0 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -20,14 +20,10 @@
  */
 
 #include "math/matrix3.h"
-#include "math/vector3d.h"
 #include "common/algorithm.h"
 #include "common/config-manager.h"
 #include "twp/scenegraph.h"
 #include "twp/twp.h"
-#include "twp/gfx.h"
-#include "twp/object.h"
-#include "twp/util.h"
 #include "twp/lighting.h"
 
 namespace Twp {
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 946e47e9a4e..62e8217e53f 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -21,8 +21,6 @@
 
 #include "twp/shaders.h"
 #include "twp/twp.h"
-#include "graphics/opengl/debug.h"
-#include "graphics/opengl/system_headers.h"
 
 namespace Twp {
 
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 8cb2e27c2be..b20e19c23ee 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -21,12 +21,7 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
-#include "twp/room.h"
-#include "twp/object.h"
 #include "twp/squtil.h"
-#include "twp/audio.h"
-#include "twp/squirrel/squirrel.h"
-#include "audio/mixer.h"
 
 namespace Twp {
 
@@ -357,7 +352,7 @@ static SQInteger soundVolume(HSQUIRRELVM v) {
 	return 0;
 }
 
-static SQInteger stopAllSounds(HSQUIRRELVM v) {
+static SQInteger stopAllSounds(HSQUIRRELVM) {
 	g_engine->_mixer->stopAll();
 	return 0;
 }
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 535458ce4f4..9d71cec0e83 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -21,18 +21,12 @@
 
 #include "twp/lighting.h"
 #include "twp/squtil.h"
-#include "twp/room.h"
-#include "twp/object.h"
 #include "twp/thread.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
-#include "squirrel/sqobject.h"
 #include "squirrel/sqstring.h"
 #include "squirrel/sqstate.h"
 #include "squirrel/sqtable.h"
-#include "squirrel/sqstdstring.h"
-#include "squirrel/sqstdmath.h"
-#include "squirrel/sqstdio.h"
 #include "squirrel/sqstdaux.h"
 #include "squirrel/sqfuncproto.h"
 #include "squirrel/sqclosure.h"
@@ -443,7 +437,7 @@ Light *sqlight(HSQUIRRELVM v, int i) {
 }
 
 struct GetThread {
-	GetThread(HSQUIRRELVM v) : _v(v) {}
+	explicit GetThread(HSQUIRRELVM v) : _v(v) {}
 	bool operator()(Common::SharedPtr<ThreadBase> t) {
 		return t->getThread() == _v;
 	}
@@ -482,7 +476,7 @@ SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundD
 	return result;
 }
 
-void sqgetpairs(HSQOBJECT obj, void func(const Common::String &key, HSQOBJECT &obj, void *data), void *data) {
+void sqgetpairs(HSQOBJECT obj, void func(const Common::String &k, HSQOBJECT &obj, void *data), void *data) {
 	HSQUIRRELVM v = g_engine->getVm();
 	sq_pushobject(v, obj);
 	sq_pushnull(v);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 007e79b839f..20f5c7bb263 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -21,25 +21,15 @@
 
 #include "twp/sqgame.h"
 #include "twp/twp.h"
-#include "twp/ids.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/task.h"
-#include "twp/callback.h"
-#include "twp/scenegraph.h"
-#include "twp/squirrel/squirrel.h"
 #include "twp/squirrel/sqvm.h"
-#include "twp/squirrel/sqobject.h"
 #include "twp/squirrel/sqstring.h"
 #include "twp/squirrel/sqstate.h"
 #include "twp/squirrel/sqtable.h"
-#include "twp/squirrel/sqstdstring.h"
-#include "twp/squirrel/sqstdmath.h"
-#include "twp/squirrel/sqstdio.h"
-#include "twp/squirrel/sqstdaux.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
-#include "common/debug.h"
 
 namespace Twp {
 
@@ -96,11 +86,11 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 }
 
 static SQInteger breakfunc(HSQUIRRELVM v, void func(Common::SharedPtr<ThreadBase> t, void *data), void *data) {
-	Common::SharedPtr<ThreadBase> t = sqthread(v);
-	if (!t)
+	Common::SharedPtr<ThreadBase> thread(sqthread(v));
+	if (!thread)
 		return sq_throwerror(v, "failed to get thread");
-	t->suspend();
-	func(t, data);
+	thread->suspend();
+	func(thread, data);
 	return -666;
 }
 
@@ -240,7 +230,7 @@ static bool isAnimating(Common::SharedPtr<Object> obj) {
 }
 
 struct ObjAnimating {
-	ObjAnimating(Common::SharedPtr<Object> obj) : _obj(obj) {}
+	explicit ObjAnimating(Common::SharedPtr<Object> obj) : _obj(obj) {}
 	bool operator()() {
 		return isAnimating(_obj);
 	}
@@ -377,7 +367,7 @@ struct SomeoneTalking {
 };
 
 struct ActorTalking {
-	ActorTalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
+	explicit ActorTalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
 
 	bool operator()() {
 		return _obj->getTalking() && _obj->getTalking()->isEnabled();
@@ -415,7 +405,7 @@ static SQInteger breakwhiletalking(HSQUIRRELVM v) {
 }
 
 struct ActorWalking {
-	ActorWalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
+	explicit ActorWalking(Common::SharedPtr<Object> obj) : _obj(obj) {}
 
 	bool operator()() {
 		return _obj->getWalkTo() && _obj->getWalkTo()->isEnabled();
@@ -443,7 +433,7 @@ static SQInteger breakwhilewalking(HSQUIRRELVM v) {
 }
 
 struct SoundPlaying {
-	SoundPlaying(int soundId) : _soundId(soundId) {}
+	explicit SoundPlaying(int soundId) : _soundId(soundId) {}
 
 	bool operator()() {
 		return g_engine->_audio.playing(_soundId);
@@ -608,8 +598,8 @@ static SQInteger inputOff(HSQUIRRELVM v) {
 }
 
 static SQInteger inputOn(HSQUIRRELVM v) {
-	Common::SharedPtr<Cutscene> cutscene = g_engine->_cutscene;
-	if (!cutscene || cutscene->isStopped()) {
+	Common::SharedPtr<Cutscene> scene(g_engine->_cutscene);
+	if (!scene || scene->isStopped()) {
 		g_engine->_inputState.setInputActive(true);
 		g_engine->_inputState.setShowCursor(true);
 	} else {
@@ -618,8 +608,8 @@ static SQInteger inputOn(HSQUIRRELVM v) {
 		state &= (~UI_INPUT_OFF);
 		state |= UI_CURSOR_ON;
 		state &= (~UI_CURSOR_OFF);
-		cutscene->setInputState((InputStateFlag)state);
-		cutscene->setShowCursor(true);
+		scene->setInputState((InputStateFlag)state);
+		scene->setShowCursor(true);
 	}
 	return 0;
 }
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 2711d6c9c9b..f77f514e764 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -59,8 +59,8 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 	_pauseable = true;
 
 	HSQUIRRELVM v = g_engine->getVm();
-	for (size_t i = 0; i < _args.size(); i++) {
-		sq_addref(v, &_args[i]);
+	for (auto & _arg : _args) {
+		sq_addref(v, &_arg);
 	}
 	sq_addref(v, &_threadObj);
 	sq_addref(v, &_envObj);
@@ -70,8 +70,8 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 Thread::~Thread() {
 	debugC(kDebugGame, "delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
 	HSQUIRRELVM v = g_engine->getVm();
-	for (size_t i = 0; i < _args.size(); i++) {
-		sq_release(v, &_args[i]);
+	for (auto & _arg : _args) {
+		sq_release(v, &_arg);
 	}
 	sq_release(v, &_threadObj);
 	sq_release(v, &_envObj);
@@ -84,8 +84,8 @@ bool Thread::call() {
 	SQInteger top = sq_gettop(v);
 	sq_pushobject(v, _closureObj);
 	sq_pushobject(v, _envObj);
-	for (size_t i = 0; i < _args.size(); i++) {
-		sq_pushobject(v, _args[i]);
+	for (auto _arg : _args) {
+		sq_pushobject(v, _arg);
 	}
 	if (SQ_FAILED(sq_call(v, 1 + _args.size(), SQFalse, SQTrue))) {
 		sq_settop(v, top);
@@ -131,9 +131,8 @@ Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, H
 	debugC(kDebugGame, "Create cutscene %d with input: 0x%X from parent thread: %d", _id, _inputState, _parentThreadId);
 	g_engine->_inputState.setInputActive(false);
 	g_engine->_inputState.setShowCursor(false);
-	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
-		Common::SharedPtr<ThreadBase> thread = g_engine->_threads[i];
-		if (thread->isGlobal())
+	for (auto thread : g_engine->_threads) {
+			if (thread->isGlobal())
 			thread->pause();
 	}
 	HSQUIRRELVM vm = g_engine->getVm();
@@ -175,8 +174,7 @@ void Cutscene::stop() {
 	debugC(kDebugGame, "Restore cutscene input: %X", _inputState);
 	g_engine->follow(g_engine->_actor);
 	Common::Array<Common::SharedPtr<ThreadBase> > threads(g_engine->_threads);
-	for (size_t i = 0; i < threads.size(); i++) {
-		Common::SharedPtr<ThreadBase> thread = threads[i];
+	for (auto thread : threads) {
 		if (thread->isGlobal())
 			thread->unpause();
 	}
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index ff74ef0279e..56d6f3ff595 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -20,10 +20,7 @@
  */
 
 #include "twp/twp.h"
-#include "twp/tsv.h"
 #include "twp/squtil.h"
-#include "twp/squirrel/squirrel.h"
-#include "twp/detection.h"
 
 namespace Twp {
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 21de5f14409..953ba25f086 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -23,34 +23,16 @@
 #include "imgui_impl_sdl2_scummvm.h"
 #include "imgui_impl_opengl3_scummvm.h"
 #include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
-#include "common/debug.h"
-#include "common/scummsys.h"
-#include "common/system.h"
-#include "common/stream.h"
-#include "common/file.h"
-#include "common/config-manager.h"
-#include "common/events.h"
 #include "common/savefile.h"
 #include "image/png.h"
 #include "engines/util.h"
-#include "graphics/palette.h"
 #include "graphics/opengl/system_headers.h"
 #include "twp/twp.h"
-#include "twp/detection.h"
 #include "twp/console.h"
-#include "twp/vm.h"
-#include "twp/ggpack.h"
-#include "twp/gfx.h"
 #include "twp/lighting.h"
-#include "twp/font.h"
 #include "twp/thread.h"
-#include "twp/scenegraph.h"
 #include "twp/squtil.h"
-#include "twp/object.h"
-#include "twp/ids.h"
 #include "twp/task.h"
-#include "twp/squirrel/squirrel.h"
-#include "twp/yack.h"
 #include "twp/enginedialogtarget.h"
 #include "twp/actions.h"
 #include "twp/debugtools.h"
@@ -307,7 +289,7 @@ void objsAt(Math::Vector2d pos, TFunc func) {
 }
 
 struct InInventory {
-	InInventory(Common::SharedPtr<Object> &obj) : _obj(obj) { _obj = nullptr; }
+	explicit InInventory(Common::SharedPtr<Object> &obj) : _obj(obj) { _obj = nullptr; }
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->inInventory()) {
 			_obj = obj;
@@ -371,7 +353,7 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 }
 
 struct GetUseNoun2 {
-	GetUseNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
+	explicit GetUseNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
 		_noun2 = nullptr;
 	}
 
@@ -388,7 +370,7 @@ public:
 };
 
 struct GetGiveableNoun2 {
-	GetGiveableNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
+	explicit GetGiveableNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
 		_noun2 = nullptr;
 	}
 
@@ -1407,7 +1389,7 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 }
 
 struct GetByZorder {
-	GetByZorder(Common::SharedPtr<Object> &result) : _result(result) { result = nullptr; }
+	explicit GetByZorder(Common::SharedPtr<Object> &result) : _result(result) { result = nullptr; }
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 1c146fbb839..62a68addadd 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -20,7 +20,6 @@
  */
 
 #include "twp/object.h"
-#include "twp/util.h"
 #include "twp/scenegraph.h"
 
 namespace Twp {
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index e969442e144..d908afa5495 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -20,18 +20,10 @@
  */
 
 #include "twp/twp.h"
-#include "twp/vm.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
-#include "twp/thread.h"
-#include "common/array.h"
-#include "common/algorithm.h"
-#include "common/debug.h"
-#include "common/random.h"
-#include "image/png.h"
 #include "squirrel/squirrel.h"
 #include "squirrel/sqvm.h"
-#include "squirrel/sqobject.h"
 #include "squirrel/sqstring.h"
 #include "squirrel/sqstate.h"
 #include "squirrel/sqtable.h"
@@ -46,7 +38,7 @@ namespace Twp {
 
 static HSQUIRRELVM gVm = nullptr;
 
-static void errorHandler(HSQUIRRELVM v, const SQChar *desc, const SQChar *source, SQInteger line,
+static void errorHandler(HSQUIRRELVM, const SQChar *desc, const SQChar *source, SQInteger line,
 						 SQInteger column) {
 	debugN("TWP: desc %s, source: %s (%lld,%lld)", desc, source, line, column);
 }
@@ -69,7 +61,7 @@ static SQInteger aux_printerror(HSQUIRRELVM v) {
 	return 0;
 }
 
-static void printfunc(HSQUIRRELVM v, const SQChar *s, ...) {
+static void printfunc(HSQUIRRELVM, const SQChar *s, ...) {
 	char buf[1024 * 1024];
 	va_list vl;
 	va_start(vl, s);
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index e0dfcf22a3e..1b172ce09e3 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -20,7 +20,6 @@
  */
 
 #include "twp/twp.h"
-#include "twp/walkboxnode.h"
 #include "twp/lighting.h"
 
 namespace Twp {
@@ -214,7 +213,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 }
 
 LightingNode::LightingNode() : Node("Lighting") {
-	setVisible(false);
+	_visible = false;
 }
 
 void LightingNode::drawCore(Math::Matrix4 trsf) {
diff --git a/engines/twp/yack.cpp b/engines/twp/yack.cpp
index 8b37920851c..a39cb7e6f31 100644
--- a/engines/twp/yack.cpp
+++ b/engines/twp/yack.cpp
@@ -21,7 +21,6 @@
 
 #include "twp/yack.h"
 #include "twp/detection.h"
-#include "common/util.h"
 
 namespace Twp {
 


Commit: 12b83a75a334f5110911ef16c5f852dd02e53c02
    https://github.com/scummvm/scummvm/commit/12b83a75a334f5110911ef16c5f852dd02e53c02
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix image not translated

Changed paths:
    engines/twp/actorswitcher.cpp
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/hud.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/spritesheet.cpp
    engines/twp/spritesheet.h


diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index bf49d9bcb13..45e0a97f1e7 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -71,9 +71,9 @@ float ActorSwitcher::getAlpha(size_t index) const {
 void ActorSwitcher::drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index) {
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	const SpriteSheetFrame &iconBackFrame = gameSheet->frameTable["icon_background"];
-	const SpriteSheetFrame &iconActorFrame = gameSheet->frameTable[icon];
-	const SpriteSheetFrame &iconFrame = gameSheet->frameTable["icon_frame"];
+	const SpriteSheetFrame &iconBackFrame = gameSheet->getFrame("icon_background");
+	const SpriteSheetFrame &iconActorFrame = gameSheet->getFrame(icon);
+	const SpriteSheetFrame &iconFrame = gameSheet->getFrame("icon_frame");
 	Math::Matrix4 t = transform(trsf, index);
 	float alpha = getAlpha(index);
 
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 1946b0e7c1b..05843d849c3 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -170,7 +170,7 @@ GGFont::~GGFont() {}
 void GGFont::load(const Common::String &path) {
 	SpriteSheet *spritesheet = g_engine->_resManager.spriteSheet(path);
 	int lineHeight = 0;
-	for (auto it = spritesheet->frameTable.begin(); it != spritesheet->frameTable.end(); it++) {
+	for (auto it = spritesheet->_frameTable.begin(); it != spritesheet->_frameTable.end(); it++) {
 		const SpriteSheetFrame &frame = it->_value;
 		Glyph glyph;
 		glyph.advance = MAX(frame.sourceSize.getX() - frame.spriteSourceSize.left - 4, 0.f);
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index ab69434e920..fed102138f3 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -311,7 +311,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
 				SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
-				SpriteSheetFrame *verbFrame = &verbSheet->frameTable[Common::String::format("%s_en", vb.image.c_str())];
+				const SpriteSheetFrame *verbFrame = &verbSheet->getFrame(Common::String::format("%s_en", vb.image.c_str()));
 				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
 				debugC(kDebugGenScript, "findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
 				sqpush(v, pos);
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index a3211de532f..f24050d3cc1 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -142,7 +142,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	// draw HUD background
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	bool classic = ConfMan.getBool("hudSentence");
-	const SpriteSheetFrame &backingFrame = gameSheet->frameTable[classic ? "ui_backing_tall" : "ui_backing"];
+	const SpriteSheetFrame &backingFrame = gameSheet->getFrame(classic ? "ui_backing_tall" : "ui_backing");
 	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
 	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha*getAlpha()), trsf);
@@ -168,7 +168,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	for (int i = 1; i < 22; i++) {
 		const Verb &verb = slot->verbs[i];
 		if (verb.image.size() > 0) {
-			const SpriteSheetFrame &verbFrame = verbSheet->frameTable[Common::String::format("%s%s_%s", verb.image.c_str(), verbSuffix.c_str(), lang.c_str())];
+			const SpriteSheetFrame &verbFrame = verbSheet->getFrame(Common::String::format("%s%s_%s", verb.image.c_str(), verbSuffix.c_str(), lang.c_str()));
 			bool over = verbFrame.spriteSourceSize.contains(_mousePos.getX(), _mousePos.getY());
 			if (over)
 				isOver = true;
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index a594944733a..17d5336284f 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -26,16 +26,16 @@
 
 namespace Twp {
 
-Common::String getKey(const char *path) {
+static Common::String getKey(const char *path) {
 	int len = strlen(path);
 	Common::String p(path);
 	size_t i = p.findLastOf(".");
 	p = p.substr(0, i);
-	if ((len > 4) && scumm_strnicmp(p.c_str() + 4, "_en", 3) == 0) {
-		Common::String lang = ConfMan.get("language");
-		Common::String filename(path, len - 3);
+	if ((len > 4) && p.hasSuffixIgnoreCase("_en")) {
+		Common::String lang(ConfMan.get("language"));
+		Common::String filename(p.substr(0, p.size()-2));
 		const char *ext = path + i;
-		return Common::String::format("%s_%s%s", filename.c_str(), lang.c_str(), ext);
+		return Common::String::format("%s%s%s", filename.c_str(), lang.c_str(), ext);
 	}
 	return path;
 }
@@ -58,7 +58,7 @@ void ResManager::loadTexture(const Common::String &name) {
 }
 
 Texture *ResManager::texture(const Common::String &name) {
-	Common::String key = getKey(name.c_str());
+	Common::String key(getKey(name.c_str()));
 	if (!_textures.contains(key)) {
 		loadTexture(key.c_str());
 	}
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index dcae804cb54..2d5f7fb6974 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -361,7 +361,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	int width = 0;
 	for (size_t i = 0; i < backNames.size(); i++) {
 		Common::String name = backNames[i];
-		width += g_engine->_resManager.spriteSheet(room->_sheet)->frameTable[name].sourceSize.getX();
+		width += g_engine->_resManager.spriteSheet(room->_sheet)->getFrame(name).sourceSize.getX();
 	}
 	room->_roomSize.setX(width);
 }
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index a372a5f84e0..44afef44c35 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -280,7 +280,7 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	Math::Matrix4 t = trsf;
 	float x = 0.f;
 	for (size_t i = 0; i < _frames.size(); i++) {
-		const SpriteSheetFrame &frame = sheet->frameTable[_frames[i]];
+		const SpriteSheetFrame &frame = sheet->getFrame(_frames[i]);
 		g_engine->_lighting->setSpriteOffset({0.f, static_cast<float>(-frame.frame.height())});
 		g_engine->_lighting->setSpriteSheetFrame(frame, *texture, false);
 
@@ -397,7 +397,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			g_engine->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
 		} else {
 			const SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
-			const SpriteSheetFrame &sf = sheet->frameTable[frame];
+			const SpriteSheetFrame &sf = sheet->getFrame(frame);
 			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 			if (_obj->_lit) {
 				g_engine->getGfx().use(g_engine->_lighting.get());
@@ -495,7 +495,7 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	if (ConfMan.getBool("hudSentence") && _hotspot) {
 		cursorName = "hotspot_" + cursorName;
 	}
-	const SpriteSheetFrame &sf = gameSheet->frameTable[cursorName];
+	const SpriteSheetFrame &sf = gameSheet->getFrame(cursorName);
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos * 2.f);
 	scale(trsf, Math::Vector2d(2.f, 2.f));
@@ -574,7 +574,7 @@ Math::Vector2d Inventory::getPos(Common::SharedPtr<Object> inv) const {
 	return {};
 }
 
-void Inventory::drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
+void Inventory::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
 	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
@@ -584,8 +584,8 @@ void Inventory::drawArrows(Math::Matrix4 trsf) {
 	bool isRetro = ConfMan.getBool("retroVerbs");
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	SpriteSheetFrame *arrowUp = &gameSheet->frameTable[isRetro ? "scroll_up_retro" : "scroll_up"];
-	SpriteSheetFrame *arrowDn = &gameSheet->frameTable[isRetro ? "scroll_down_retro" : "scroll_down"];
+	const SpriteSheetFrame *arrowUp = &gameSheet->getFrame(isRetro ? "scroll_up_retro" : "scroll_up");
+	const SpriteSheetFrame *arrowDn = &gameSheet->getFrame(isRetro ? "scroll_down_retro" : "scroll_down");
 	float alphaUp = hasUpArrow(_actor) ? 1.f : 0.f;
 	float alphaDn = hasDownArrow(_actor) ? 1.f : 0.f;
 	Math::Matrix4 tUp(trsf);
@@ -600,7 +600,7 @@ void Inventory::drawArrows(Math::Matrix4 trsf) {
 void Inventory::drawBack(Math::Matrix4 trsf) {
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	SpriteSheetFrame *back = &gameSheet->frameTable["inventory_background"];
+	const SpriteSheetFrame *back = &gameSheet->getFrame("inventory_background");
 
 	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + back->sourceSize.getX() / 2.f;
 	float offsetX = startOffsetX;
@@ -633,8 +633,8 @@ void Inventory::drawItems(Math::Matrix4 trsf) {
 	for (int i = 0; i < count; i++) {
 		Common::SharedPtr<Object> obj = _actor->_inventory[_actor->_inventoryOffset * NUMOBJECTSBYROW + i];
 		Common::String icon = obj->getIcon();
-		if (itemsSheet->frameTable.contains(icon)) {
-			SpriteSheetFrame *itemFrame = &itemsSheet->frameTable[icon];
+		if (itemsSheet->_frameTable.contains(icon)) {
+			SpriteSheetFrame *itemFrame = &itemsSheet->_frameTable[icon];
 			Math::Vector2d pos(startOffsetX + ((float)(i % NUMOBJECTSBYROW) * (BACKWIDTH + BACKOFFSET)), startOffsetY - ((float)(i / NUMOBJECTSBYROW) * (BACKHEIGHT + BACKOFFSET)));
 			Math::Matrix4 t(trsf);
 			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
@@ -765,7 +765,7 @@ void SpriteNode::setSprite(const Common::String &sheet, const Common::String &fr
 
 void SpriteNode::drawCore(Math::Matrix4 trsf) {
 	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
-	SpriteSheetFrame *frame = &sheet->frameTable[_frame];
+	const SpriteSheetFrame *frame = &sheet->getFrame(_frame);
 
 	Common::Rect rect = frame->frame;
 	setSize(Math::Vector2d(frame->frame.width(), frame->frame.height()));
@@ -823,7 +823,7 @@ void HotspotMarkerNode::drawSprite(const SpriteSheetFrame &sf, Texture *texture,
 void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
 	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
 	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
-	SpriteSheetFrame *frame = &gameSheet->frameTable["hotspot_marker"];
+	const SpriteSheetFrame *frame = &gameSheet->getFrame("hotspot_marker");
 	Color color = Color::create(255, 165, 0);
 	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 4cba5118bf4..879fe1b026f 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -306,7 +306,7 @@ private:
 	void drawArrows(Math::Matrix4 trsf);
 	void drawBack(Math::Matrix4 trsf);
 	void drawItems(Math::Matrix4 trsf);
-	void drawSprite(SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
+	void drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf);
 
 private:
 	Common::SharedPtr<Object> _actor;
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index 804602e37ac..82dce0b4118 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -21,6 +21,8 @@
  */
 
 #include "common/formats/json.h"
+#include "common/config-manager.h"
+#include "twp/twp.h"
 #include "twp/spritesheet.h"
 
 namespace Twp {
@@ -48,11 +50,29 @@ void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
 	Common::ScopedPtr<Common::JSONValue> json(Common::JSON::parse(contents.c_str()));
 	const Common::JSONObject &obj = json->asObject()["frames"]->asObject();
 	for (auto it = obj.begin(); it != obj.end(); it++) {
-		parseFrame(it->_key, it->_value->asObject(), frameTable[it->_key]);
+		parseFrame(it->_key, it->_value->asObject(), _frameTable[it->_key]);
 	}
 
 	const Common::JSONObject& jMeta = json->asObject()["meta"]->asObject();
 	meta.image = jMeta["image"]->asString();
 }
 
+Common::String SpriteSheet::getKey(const Common::String& key) {
+	if(key.hasSuffixIgnoreCase("_en")) {
+		Common::String lang(ConfMan.get("language"));
+		Common::String newKey(Common::String::format("%s%s", key.substr(0, key.size()-2).c_str(), lang.c_str()));
+		return newKey;
+	}
+	return key;
+}
+
+const SpriteSheetFrame& SpriteSheet::getFrame(const Common::String& key) const {
+	if(key.hasSuffixIgnoreCase("_en")) {
+		Common::String newKey(getKey(key));
+		if(_frameTable.contains(newKey))
+			return _frameTable[newKey];
+	}
+	return _frameTable[key];
+}
+
 } // namespace Twp
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
index 8ea0ce8e943..d888a726cc9 100644
--- a/engines/twp/spritesheet.h
+++ b/engines/twp/spritesheet.h
@@ -42,10 +42,13 @@ struct SpriteSheetFrame {
 };
 
 struct SpriteSheet {
-	Common::HashMap<Common::String, SpriteSheetFrame> frameTable;
-	SpriteSheetMetadata meta;
-
 	void parseSpriteSheet(const Common::String &contents);
+	const SpriteSheetFrame& getFrame(const Common::String& key) const;
+
+	static Common::String getKey(const Common::String& key);
+
+	SpriteSheetMetadata meta;
+	Common::HashMap<Common::String, SpriteSheetFrame> _frameTable;
 };
 
 } // namespace Twp


Commit: ab9de54d425c2dcd30385b34a2e3aa95afbc278c
    https://github.com/scummvm/scummvm/commit/ab9de54d425c2dcd30385b34a2e3aa95afbc278c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Clean motor code

Changed paths:
    engines/twp/motor.cpp
    engines/twp/motor.h


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index e96d7f5ed7e..95b1a874ae0 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -29,7 +29,7 @@ namespace Twp {
 
 OffsetTo::~OffsetTo() = default;
 
-OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
+OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getOffset(), pos, duration, im) {
 }
@@ -43,7 +43,7 @@ void OffsetTo::update(float elapsed) {
 
 MoveTo::~MoveTo() = default;
 
-MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im)
+MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getPos(), pos, duration, im) {
 }
@@ -276,8 +276,19 @@ void WalkTo::update(float elapsed) {
 		Common::SharedPtr<Motor> reach = _obj->getReach();
 		if (reach && reach->isEnabled()) {
 			reach->update(elapsed);
-			if (!reach->isEnabled())
+			_state =  kReach;
+			return;
+		}
+	}
+
+	if(_state == kReach) {
+		Common::SharedPtr<Motor> reach = _obj->getReach();
+		if (reach) {
+			if (reach->isEnabled()) {
+				reach->update(elapsed);
+			} else {
 				disable();
+			}
 		}
 	}
 }
@@ -329,7 +340,7 @@ void Talking::update(float elapsed) {
 			debugC(kDebugGame, "talking %s audio stopped", _obj->_key.c_str());
 			_obj->_sound = 0;
 		} else {
-			float e = g_engine->_audio.getElapsed(_obj->_sound) / 1000.f;
+			float e = static_cast<float>(g_engine->_audio.getElapsed(_obj->_sound)) / 1000.f;
 			char letter = _lip.letter(e);
 			_obj->setHeadIndex(letterToIndex(letter));
 		}
@@ -366,7 +377,7 @@ int Talking::loadActorSpeech(const Common::String &name) {
 			int duration = g_engine->_audio.getDuration(id);
 			debugC(kDebugGame, "talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
 			if (duration)
-				_duration = duration / 1000.f;
+				_duration = static_cast<float>(duration) / 1000.f;
 			return id;
 		}
 	}
@@ -407,8 +418,8 @@ void Talking::say(const Common::String &text) {
 
 	// remove text in parentheses
 	if (txt[0] == '(') {
-		int i = txt.find(')');
-		if (i != -1)
+		uint32 i = txt.find(')');
+		if (i != Common::String::npos)
 			txt = txt.substr(i + 1);
 	}
 
@@ -422,8 +433,8 @@ void Talking::say(const Common::String &text) {
 	// modify state ?
 	Common::String state;
 	if (!txt.empty() && txt[0] == '{') {
-		int i = txt.find('}');
-		if (i != -1) {
+		uint32 i = txt.find('}');
+		if (i != Common::String::npos) {
 			state = txt.substr(1, txt.size() - 2);
 			debugC(kDebugGame, "Set state from anim '%s'", state.c_str());
 			if (state != "notalk") {
@@ -481,7 +492,7 @@ void Talking::setDuration(const Common::String &text) {
 	float sayLineMinTime = 0.2f;
 	//   let sayLineSpeed = prefs(SayLineSpeed);
 	float sayLineSpeed = 0.5f;
-	float duration = (sayLineBaseTime + sayLineCharTime * text.size()) / (0.2f + sayLineSpeed);
+	float duration = (sayLineBaseTime + sayLineCharTime * static_cast<float>(text.size())) / (0.2f + sayLineSpeed);
 	_duration = MAX(duration, sayLineMinTime);
 }
 
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 01dd215a3ce..0caaa00ef22 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -38,10 +38,10 @@ public:
 	}
 
 	bool running() {
-		if (swing || loop)
+		if (swing || loop) {
 			return true;
-		else
-			return elapsed < duration;
+		}
+		return elapsed < duration;
 	}
 
 	void update(float el) {
@@ -49,7 +49,7 @@ public:
 			elapsed += el;
 			float f = clamp(elapsed / duration, 0.0f, 1.0f);
 			if (!dir_forward)
-				f = 1.0 - f;
+				f = 1.0f - f;
 			if ((elapsed > duration) && (swing || loop)) {
 				elapsed = elapsed - duration;
 				if (swing)
@@ -95,7 +95,7 @@ class Object;
 class OffsetTo : public Motor {
 public:
 	virtual ~OffsetTo();
-	OffsetTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im);
+	OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
@@ -108,7 +108,7 @@ private:
 class MoveTo : public Motor {
 public:
 	virtual ~MoveTo();
-	MoveTo(float duration, Common::SharedPtr<Object> obj, Math::Vector2d pos, InterpolationMethod im);
+	MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
@@ -218,7 +218,8 @@ private:
 
 enum WalkToState {
 	kWalking,
-	kArrived
+	kArrived,
+	kReach
 };
 
 class WalkTo : public Motor {


Commit: 6e9f002106e59b9e04ff3ffc8b1187b1c58659d9
    https://github.com/scummvm/scummvm/commit/6e9f002106e59b9e04ff3ffc8b1187b1c58659d9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Missing names in phone book

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index fed102138f3..44d236d3a84 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -879,13 +879,22 @@ static SQInteger strsplit(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, delimiter)))
 		return sq_throwerror(v, "Failed to get delimiter");
 	sq_newarray(v, 0);
+	size_t delLen = strlen(delimiter);
+	if(delLen == 0)
+		return 1;
+
+	size_t len = str.size();
 	char *s = str.begin();
-	char *tok = strtok(s, delimiter);
-	while (tok) {
-		sq_pushstring(v, tok, -1);
+	size_t result = strcspn(s, delimiter);
+	while (result != len) {
+		sq_pushstring(v, s, result);
 		sq_arrayappend(v, -2);
-		tok = strtok(nullptr, delimiter);
+		s += (result + delLen);
+		len -= (result + delLen);
+		result = strcspn(s, delimiter);
 	}
+	sq_pushstring(v, s, result);
+	sq_arrayappend(v, -2);
 	return 1;
 }
 


Commit: 59f09a8736fedaedda6220f5321ba371a743a9ef
    https://github.com/scummvm/scummvm/commit/59f09a8736fedaedda6220f5321ba371a743a9ef
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: When calling kscumm the text contains $

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/enginedialogtarget.cpp
    engines/twp/tsv.cpp


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 487f3391f2b..17172da2cd6 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -150,7 +150,8 @@ void ExpVisitor::visit(const YLimit &node) {
 }
 
 void ExpVisitor::visit(const YSay &node) {
-	_dialog->_action = _dialog->_tgt->say(node._actor, node._text);
+	Common::String text(g_engine->getTextDb().getText(node._text));
+	_dialog->_action = _dialog->_tgt->say(node._actor, text);
 }
 
 CondVisitor::CondVisitor(Dialog *dialog) : _dialog(dialog) {}
@@ -421,7 +422,7 @@ void Dialog::running(float dt) {
 }
 
 static Common::String text(const Common::String &txt) {
-	Common::String result = g_engine->getTextDb().getText(txt);
+	Common::String result(g_engine->getTextDb().getText(txt));
 	result = remove(result, '(', ')');
 	result = remove(result, '{', '}');
 	return result;
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index e55a5c9bf48..d6ccf1d99b3 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -118,7 +118,7 @@ bool EngineDialogTarget::execCond(const Common::String &cond) {
 	sq_push(v, -2);
 	// call
 	if (SQ_FAILED(sq_call(v, 1, SQTrue, SQTrue))) {
-		debugC(kDebugDialog, "Error calling code {code}");
+		debugC(kDebugDialog, "Error calling code %s", code.c_str());
 		return false;
 	}
 
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 56d6f3ff595..8b0e210ba79 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -63,7 +63,7 @@ Common::String TextDb::getText(const Common::String &text) {
 			Common::String txt;
 			SQInteger top = sq_gettop(v);
 			sq_pushroottable(v);
-			Common::String code = Common::String::format("return %s", text.substr(1, text.size() - 2).c_str());
+			Common::String code(Common::String::format("return %s", text.substr(1, text.size() - 1).c_str()));
 			if (SQ_FAILED(sq_compilebuffer(v, code.c_str(), code.size(), "execCode", SQTrue))) {
 				error("Error executing code %s", code.c_str());
 			} else {


Commit: 3eb969ce8509f827b73d31f239aed7c642ebbb63
    https://github.com/scummvm/scummvm/commit/3eb969ce8509f827b73d31f239aed7c642ebbb63
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix issue with walkbox

Changed paths:
    engines/twp/room.cpp


diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 2d5f7fb6974..704f744f3a1 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -115,8 +115,6 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 			const Walkbox &wb = walkboxes[i];
 			if (wb.isVisible()) {
 				subjects.push_back(toPolygon(wb));
-			} else {
-				clips.push_back(toPolygon(wb));
 			}
 		}
 
@@ -458,7 +456,7 @@ void Room::walkboxHidden(const Common::String &name, bool hidden) {
 		Walkbox &wb = _walkboxes[i];
 		if (wb._name == name) {
 			wb.setVisible(!hidden);
-			// 1 walkbox has change so update merged polygon
+			// 1 walkbox has changed so update merged polygon
 			_pathFinder.setDirty(true);
 			return;
 		}


Commit: 3ec7b6d28fe19d5aefa0aa8e5f2f32b3fd7cda07
    https://github.com/scummvm/scummvm/commit/3ec7b6d28fe19d5aefa0aa8e5f2f32b3fd7cda07
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash when changing actor

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 953ba25f086..371b8c84121 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1415,6 +1415,7 @@ Common::SharedPtr<Object> TwpEngine::objAt(Math::Vector2d pos) {
 void TwpEngine::setActor(Common::SharedPtr<Object> actor, bool userSelected) {
 	_actor = actor;
 	_hud._actor = actor;
+	resetVerb();
 	if (!_hud.getParent() && actor) {
 		_screenScene.addChild(&_hud);
 	} else if (_hud.getParent() && !actor) {


Commit: 4eabd859f2a190dc0b98a873aeb3389e85809119
    https://github.com/scummvm/scummvm/commit/4eabd859f2a190dc0b98a873aeb3389e85809119
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash when loading game

Changed paths:
    engines/twp/savegame.cpp


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 6ad8eef5c65..8a1a590c031 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -407,8 +407,8 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	g_engine->setTotalPlayTime(savegame.gameTime * 1000);
 	g_engine->_inputState.setState((InputStateFlag)json["inputState"]->asIntegerNumber());
 	loadObjects(json["objects"]->asObject());
-	setActor(json["selectedActor"]->asString());
 	g_engine->setRoom(room(json["currentRoom"]->asString()));
+	setActor(json["selectedActor"]->asString());
 	g_engine->cameraAt(g_engine->_actor->_node->getPos());
 
 	HSQUIRRELVM v = g_engine->getVm();


Commit: 69cef8c94250e87b204f22b65c607a2bf7f8e8fb
    https://github.com/scummvm/scummvm/commit/69cef8c94250e87b204f22b65c607a2bf7f8e8fb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash with invalid key anim

Changed paths:
    engines/twp/scenegraph.cpp
    engines/twp/spritesheet.cpp
    engines/twp/spritesheet.h


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 44afef44c35..ff0ac1fb08f 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -328,7 +328,7 @@ void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool insta
 }
 
 void Anim::trigSound() {
-	if ((_anim->triggers.size() > 0) && _frameIndex < _anim->triggers.size()) {
+	if (_anim && (_anim->triggers.size() > 0) && (_frameIndex < _anim->triggers.size())) {
 		const Common::String &trigger = _anim->triggers[_frameIndex];
 		if ((trigger.size() > 0) && trigger != "null") {
 			_obj->trig(trigger);
@@ -355,7 +355,7 @@ void Anim::update(float elapsed) {
 				disable();
 			}
 		}
-		if (_anim->offsets.size() > 0) {
+		if (_anim && _anim->offsets.size() > 0) {
 			Math::Vector2d off = _frameIndex < _anim->offsets.size() ? _anim->offsets[_frameIndex] : Math::Vector2d();
 			if (_obj->getFacing() == FACE_LEFT) {
 				off.setX(-off.getX());
@@ -397,24 +397,25 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			g_engine->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
 		} else {
 			const SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
-			const SpriteSheetFrame &sf = sheet->getFrame(frame);
+			const SpriteSheetFrame *sf = sheet->frame(frame);
+			if(!sf) return;
 			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
 			if (_obj->_lit) {
 				g_engine->getGfx().use(g_engine->_lighting.get());
 				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
-				const float left = flipX ? (-1.f + sf.sourceSize.getX()) / 2.f - sf.spriteSourceSize.left : sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f;
-				const float top = -sf.sourceSize.getY() / 2.f + sf.spriteSourceSize.top;
+				const float left = flipX ? (-1.f + sf->sourceSize.getX()) / 2.f - sf->spriteSourceSize.left : sf->spriteSourceSize.left - sf->sourceSize.getX() / 2.f;
+				const float top = -sf->sourceSize.getY() / 2.f + sf->spriteSourceSize.top;
 
 				g_engine->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});
-				g_engine->_lighting->setSpriteSheetFrame(sf, *texture, flipX);
+				g_engine->_lighting->setSpriteSheetFrame(*sf, *texture, flipX);
 			} else {
 				g_engine->getGfx().use(nullptr);
 			}
-			const float x = flipX ? (1.f - sf.sourceSize.getX()) / 2.f + sf.frame.width() + sf.spriteSourceSize.left : sf.sourceSize.getX() / 2.f - sf.spriteSourceSize.left;
-			const float y = sf.sourceSize.getY() / 2.f - sf.spriteSourceSize.height() - sf.spriteSourceSize.top;
+			const float x = flipX ? (1.f - sf->sourceSize.getX()) / 2.f + sf->frame.width() + sf->spriteSourceSize.left : sf->sourceSize.getX() / 2.f - sf->spriteSourceSize.left;
+			const float y = sf->sourceSize.getY() / 2.f - sf->spriteSourceSize.height() - sf->spriteSourceSize.top;
 			Math::Vector3d pos(int(-x), int(y), 0.f);
 			trsf.translate(pos);
-			g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf, flipX);
+			g_engine->getGfx().drawSprite(sf->frame, *texture, getComputedColor(), trsf, flipX);
 			g_engine->getGfx().use(nullptr);
 		}
 	}
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index 82dce0b4118..0ca01b4be16 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -75,4 +75,15 @@ const SpriteSheetFrame& SpriteSheet::getFrame(const Common::String& key) const {
 	return _frameTable[key];
 }
 
+const SpriteSheetFrame* SpriteSheet::frame(const Common::String& key) const {
+	if(key.hasSuffixIgnoreCase("_en")) {
+		Common::String newKey(getKey(key));
+		if(_frameTable.contains(newKey))
+			return &_frameTable[newKey];
+	}
+	if(_frameTable.contains(key))
+		return &_frameTable[key];
+	return nullptr;
+}
+
 } // namespace Twp
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
index d888a726cc9..048eed79e3f 100644
--- a/engines/twp/spritesheet.h
+++ b/engines/twp/spritesheet.h
@@ -43,6 +43,7 @@ struct SpriteSheetFrame {
 
 struct SpriteSheet {
 	void parseSpriteSheet(const Common::String &contents);
+	const SpriteSheetFrame* frame(const Common::String& key) const;
 	const SpriteSheetFrame& getFrame(const Common::String& key) const;
 
 	static Common::String getKey(const Common::String& key);


Commit: 50895370f40a2826f352621aacc22f9c8c73628e
    https://github.com/scummvm/scummvm/commit/50895370f40a2826f352621aacc22f9c8c73628e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add autosave

Changed paths:
    engines/twp/savegame.h
    engines/twp/syslib.cpp


diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
index e1c76b7e130..45a020585aa 100644
--- a/engines/twp/savegame.h
+++ b/engines/twp/savegame.h
@@ -53,6 +53,7 @@ private:
 
 public:
 	bool _allowSaveGame = true;
+	bool _autoSave = false;
 };
 
 } // namespace Twp
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 20f5c7bb263..55df513bfe6 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -507,6 +507,17 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, cmd)))
 		return sq_throwerror(v, "Failed to get command");
 	switch (cmd) {
+	case EX_AUTOSAVE_STATE: {
+		int enabled;
+		if (SQ_FAILED(sqget(v, 3, enabled)))
+			return sq_throwerror(v, "Failed to get enabled");
+		g_engine->_saveGameManager._autoSave = enabled != 0;
+	} break;
+	case EX_AUTOSAVE: {
+		if (g_engine->_saveGameManager._autoSave) {
+			g_engine->saveGameState(0, "", true);
+		}
+	} break;
 	case EX_ALLOW_SAVEGAMES: {
 		int enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
@@ -548,7 +559,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		warning("exCommand EX_FORCE_TALKIE_TEXT: not implemented");
 		break;
 	default:
-		warning("exCommand(%dd) not implemented", cmd);
+		warning("exCommand(%d) not implemented", cmd);
 		break;
 	}
 	return 0;


Commit: bc1be1cfb653c0454484487914d482036e5decda
    https://github.com/scummvm/scummvm/commit/bc1be1cfb653c0454484487914d482036e5decda
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix can't use strange device on pillowTronSlot

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 371b8c84121..7ac2285c38c 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -358,15 +358,17 @@ struct GetUseNoun2 {
 	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
-		if ((obj != g_engine->_actor) && (g_engine->_noun1 != obj)) {
-			_noun2 = obj;
-			return true;
+		if (obj->_node->getZSort() <= _zOrder) {
+			if ((obj != g_engine->_actor) && (g_engine->_noun2 != obj)) {
+				_noun2 = obj;
+			}
 		}
 		return false;
 	}
 
 public:
 	Common::SharedPtr<Object> &_noun2;
+	int _zOrder = INT_MAX;
 };
 
 struct GetGiveableNoun2 {


Commit: 2efa0d60b580738ce32eff055f95eb863d52d2ea
    https://github.com/scummvm/scummvm/commit/2efa0d60b580738ce32eff055f95eb863d52d2ea
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix audio loop

Changed paths:
    engines/twp/audio.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 53c0221939b..b1672b6bfd9 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -202,8 +202,10 @@ void AudioSystem::setVolume(int id, float vol) {
 void AudioSystem::update(float) {
 	for (auto & _slot : _slots) {
 		if (_slot.busy && !g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
-			if(_slot.loopTimes > 0) {
-				_slot.loopTimes--;
+			if((_slot.loopTimes == -1) || _slot.loopTimes > 0) {
+				if(_slot.loopTimes != -1) {
+					_slot.loopTimes--;
+				}
 				Audio::SeekableAudioStream *audioStream;
 				Common::String name = _slot.sndDef->getName();
 				_slot.stream.seek(0);


Commit: bce1313ad644ad08f8afa5983afd595165b4e962
    https://github.com/scummvm/scummvm/commit/bce1313ad644ad08f8afa5983afd595165b4e962
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix several bugs

- actorDistanceWithin if actors is not in the same room
- add STOP_LOOPING const
- call actorEnter and actorExit
- fix loadDialog
- add name to cutscene
- annoying kid should not stop music when talking with him

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/camera.cpp
    engines/twp/debugtools.cpp
    engines/twp/gfx.h
    engines/twp/ids.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/savegame.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 92466263828..c9816cfa81d 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -207,31 +207,36 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 3) {
-		Common::SharedPtr<Object> actor1 = g_engine->_actor;
-		Common::SharedPtr<Object> actor2 = sqactor(v, 2);
+		Common::SharedPtr<Object> actor1(g_engine->_actor);
+		Common::SharedPtr<Object> actor2(sqactor(v, 2));
 		if (!actor2)
 			return sq_throwerror(v, "failed to get actor");
 		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot");
+		if(actor1->_room != actor2->_room)
+			return false;
 		// not sure about this, needs to be check one day ;)
 		sqpush(v, distance((Vector2i)actor1->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < distance((Vector2i)actor2->_node->getAbsPos(), (Vector2i)obj->getUsePos()));
 		return 1;
-	} else if (nArgs == 4) {
-		Common::SharedPtr<Object> actor = sqactor(v, 2);
+	}
+
+	if (nArgs == 4) {
+		Common::SharedPtr<Object> actor(sqactor(v, 2));
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		Common::SharedPtr<Object> obj = sqobj(v, 3);
+		Common::SharedPtr<Object> obj(sqobj(v, 3));
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 		int dist;
 		if (SQ_FAILED(sqget(v, 4, dist)))
 			return sq_throwerror(v, "failed to get distance");
+		if(actor->_room != obj->_room)
+			return false;
 		sqpush(v, distance((Vector2i)actor->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < dist);
 		return 1;
-	} else {
-		return sq_throwerror(v, "actorDistanceWithin not implemented");
 	}
+	return sq_throwerror(v, "actorDistanceWithin not implemented");
 }
 
 // Makes the actor face a given direction.
@@ -439,7 +444,10 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 		int facing = 0;
 		if (SQ_FAILED(sqget(v, 3, facing)))
 			return sq_throwerror(v, "failed to get facing");
-		actor->lockFacing(facing);
+		if (facing == 0)
+			actor->resetLockFacing();
+		else
+			actor->lockFacing(facing);
 	} break;
 	case OT_TABLE: {
 		HSQOBJECT obj;
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 99d603e7b40..f1f6a7e433d 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -111,10 +111,11 @@ void Camera::update(Common::SharedPtr<Room> room, Common::SharedPtr<Object> foll
 InterpolationMethod intToInterpolationMethod(int value) {
 	bool loop = (value & 0x10);
 	bool swing = (value & 0x20);
+	bool stopLooping = (value & 0x40);
 	InterpolationKind kind = (InterpolationKind)(value & 0x0F);
 	InterpolationMethod im;
 	im.kind = kind;
-	im.loop = loop;
+	im.loop = loop && !stopLooping;
 	im.swing = swing;
 	return im;
 }
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 4826edb7ca5..b58a305b984 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -24,6 +24,7 @@
 #include "twp/twp.h"
 #include "twp/thread.h"
 #include "twp/lighting.h"
+#include "twp/squtil.h"
 #include "common/debug-channels.h"
 
 namespace Twp {
@@ -35,12 +36,15 @@ static struct {
 	bool _showAudio = false;
 	bool _showResources = false;
 	bool _showScenegraph = false;
+	bool _showActor = false;
 	Node* _node = nullptr;
 	ImGuiTextFilter _objFilter;
+	ImGuiTextFilter _actorFilter;
 	int _fadeEffect = 0;
 	float _fadeDuration = 0.f;
 	bool _fadeToSepia = false;
 	Common::String _textureSelected;
+	int _selectedActor = 0;
 } state;
 
 ImVec4 gray(0.6f, 0.6f, 0.6f, 1.f);
@@ -64,6 +68,26 @@ static void drawThreads() {
 			ImGui::TableSetupColumn("Line");
 			ImGui::TableHeadersRow();
 
+			if(g_engine->_cutscene) {
+				Common::SharedPtr<ThreadBase> thread(g_engine->_cutscene);
+				SQStackInfos infos;
+				sq_stackinfos(thread->getThread(), 0, &infos);
+
+				ImGui::TableNextRow();
+				ImGui::TableNextColumn();
+				ImGui::Text("%5d", thread->getId());
+				ImGui::TableNextColumn();
+				ImGui::Text("%-56s", thread->getName().c_str());
+				ImGui::TableNextColumn();
+				ImGui::Text("%-6s", "cutscene");
+				ImGui::TableNextColumn();
+				ImGui::Text("%-9s", infos.funcname);
+				ImGui::TableNextColumn();
+				ImGui::Text("%-9s", infos.source);
+				ImGui::TableNextColumn();
+				ImGui::Text("%5lld", infos.line);
+			}
+
 			for (const auto &thread : threads) {
 				SQStackInfos infos;
 				sq_stackinfos(thread->getThread(), 0, &infos);
@@ -147,6 +171,52 @@ static ImVec4 getCategoryColor(Audio::Mixer::SoundType type) {
 	return ImVec4(1.f, 1.f, 1.f, 1.f);
 }
 
+static void drawActors() {
+	if (!state._showActor)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Actors", &state._showStack);
+	state._actorFilter.Draw();
+	ImGui::BeginChild("Actor_List");
+	for(auto& actor : g_engine->_actors) {
+		bool selected = actor->getId() == state._selectedActor;
+		Common::String key(actor->_key);
+		if (state._actorFilter.PassFilter(actor->_key.c_str())) {
+			if (key.empty()) {
+				key = "??";
+			}
+			if (ImGui::Selectable(key.c_str(), &selected)) {
+				state._selectedActor = actor->getId();
+			}
+		}
+	}
+	ImGui::EndChild();
+	ImGui::End();
+}
+
+static void drawActor() {
+	if (!state._showActor)
+		return;
+
+	Common::SharedPtr<Object> actor(sqobj(state._selectedActor));
+	if (!actor)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Actor", &state._showStack);
+	ImGui::Text("Name: %s", actor->_key.c_str());
+	ImGui::Text("Costume: %s (%s)", actor->_costumeName.c_str(), actor->_costumeSheet.c_str());
+	ImGui::Text("Animation: %s", actor->_animName.c_str());
+	Common::String hiddenLayers(Twp::join(actor->_hiddenLayers, ", "));
+	ImGui::Text("Hidden Layers: %s", hiddenLayers.c_str());
+	ImGui::Text("Facing: %d", actor->_facing);
+	ImGui::Text("Facing Lock: %d", actor->_facingLockValue);
+	ImGui::ColorEdit3("Talk color", actor->_talkColor.v);
+	ImGui::DragFloat2("Talk offset", actor->_talkOffset.getData());
+	ImGui::End();
+}
+
 static void drawStack() {
 	if (!state._showStack)
 		return;
@@ -275,9 +345,9 @@ static void drawGeneral() {
 	ImGui::TextColored(gray, "Stack:");
 	ImGui::SameLine();
 	ImGui::Text("%lld", size);
-	ImGui::TextColored(gray, "In cutscene:");
+	ImGui::TextColored(gray, "Cutscene:");
 	ImGui::SameLine();
-	ImGui::Text("%s", g_engine->_cutscene ? "yes" : "no");
+	ImGui::Text("%s", g_engine->_cutscene ? g_engine->_cutscene->getName().c_str() : "no");
 	DialogState dialogState = g_engine->_dialog.getState();
 	ImGui::TextColored(gray, "In dialog:");
 	ImGui::SameLine();
@@ -415,10 +485,11 @@ static void drawGeneral() {
 	if (ImGui::CollapsingHeader("Windows")) {
 		ImGui::Checkbox("Threads", &state._showThreads);
 		ImGui::Checkbox("Objects", &state._showObjects);
+		ImGui::Checkbox("Actor", &state._showActor);
 		ImGui::Checkbox("Stack", &state._showStack);
 		ImGui::Checkbox("Audio", &state._showAudio);
 		ImGui::Checkbox("Resources", &state._showResources);
-		ImGui::Checkbox("Scenegraph", &state._showScenegraph);
+		ImGui::Checkbox("Scene graph", &state._showScenegraph);
 	}
 	ImGui::Separator();
 
@@ -523,6 +594,8 @@ void onImGuiRender() {
 	drawAudio();
 	drawResources();
 	drawScenegraph();
+	drawActors();
+	drawActor();
 }
 
 } // namespace Twp
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 07c6e2b8e06..269295ba421 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -140,9 +140,7 @@ public:
 	void setUniform2(const char * name, float* value, size_t count);
 	void setUniform3(const char * name, float* value, size_t count);
 
-	void setUniform(const char *name, Math::Matrix4 value);
 	void setUniform(const char * name, Math::Vector2d value);
-	void setUniform(const char * name, Math::Vector3d value);
 	void setUniform3(const char * name, Color value);
 	void setUniform4(const char * name, Color value);
 
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index 9565eb79de5..d59ce87cf4a 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -74,6 +74,7 @@
 #define SLOW_EASE_OUT  5
 #define LOOPING  0x10
 #define SWING  0X20
+#define STOP_LOOPING  0X40
 #define ALIGN_LEFT    0x0000000010000000
 #define ALIGN_CENTER  0x0000000020000000
 #define ALIGN_RIGHT   0x0000000040000000
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 657296d7d33..c4f7821cbd8 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -372,8 +372,9 @@ int Object::getFlags() {
 }
 
 void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> room) {
-	if ((object->_room != room) || !object->_node->getParent()) {
-		if (object->_room != room) {
+	bool roomChanged = object->_room != room;
+	if (roomChanged || !object->_node->getParent()) {
+		if (roomChanged) {
 			object->stopObjectMotors();
 		}
 		Common::SharedPtr<Room> oldRoom = object->_room;
@@ -399,6 +400,14 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 			}
 		}
 		object->_room = room;
+
+		if(roomChanged && isActor(object->getId())) {
+			if(room == g_engine->_room) {
+				g_engine->actorEnter(object);
+			} else if(oldRoom == g_engine->_room) {
+				g_engine->actorExit(object);
+			}
+		}
 	}
 }
 
@@ -418,6 +427,12 @@ void Object::stopObjectMotors() {
 	disableMotor(_shakeTo);
 	disableMotor(_jiggleTo);
 	disableMotor(_scaleTo);
+	_node->setRotation(0);
+	_node->setRotationOffset(0);
+	_node->setOffset({0.f, 0.f});
+	_node->setShakeOffset({0.f, 0.f});
+	_node->setAlpha(1);
+	_node->setScale({1.f,1.f});
 }
 
 void Object::setFacing(Facing facing) {
@@ -537,10 +552,8 @@ void Object::stand() {
 void Object::setAlphaTo(Common::SharedPtr<Motor> alphaTo) { SET_MOTOR(alphaTo); }
 void Object::setRotateTo(Common::SharedPtr<Motor> rotateTo) { SET_MOTOR(rotateTo); }
 void Object::setMoveTo(Common::SharedPtr<Motor> moveTo) { SET_MOTOR(moveTo); }
-void Object::setWalkTo(Common::SharedPtr<Motor> walkTo) { SET_MOTOR(walkTo); }
 void Object::setReach(Common::SharedPtr<Motor> reach) { SET_MOTOR(reach); }
 void Object::setTalking(Common::SharedPtr<Motor> talking) { SET_MOTOR(talking); }
-void Object::setTurnTo(Common::SharedPtr<Motor> turnTo) { SET_MOTOR(turnTo); }
 void Object::setShakeTo(Common::SharedPtr<Motor> shakeTo) { SET_MOTOR(shakeTo); }
 void Object::setScaleTo(Common::SharedPtr<Motor> scaleTo) { SET_MOTOR(scaleTo); }
 
@@ -629,6 +642,7 @@ void Object::say(Common::SharedPtr<Object> obj, const Common::StringArray &texts
 
 void Object::resetLockFacing() {
 	_facingMap.clear();
+	_facingLockValue = 0;
 }
 
 void Object::lockFacing(int facing) {
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 2aacaa9589e..3ac94082041 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -170,7 +170,6 @@ public:
 
 	bool contains(Math::Vector2d pos);
 	static void setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> room);
-	void delObject();
 	void stopObjectMotors();
 	void dependentOn(Common::SharedPtr<Object> dependentObj, int state);
 
@@ -190,7 +189,6 @@ public:
 	void setAlphaTo(Common::SharedPtr<Motor> alphaTo);
 	void setRotateTo(Common::SharedPtr<Motor> rotateTo);
 	void setMoveTo(Common::SharedPtr<Motor> moveTo);
-	void setWalkTo(Common::SharedPtr<Motor> walkTo);
 	void setReach(Common::SharedPtr<Motor> reach);
 	Common::SharedPtr<Motor> getWalkTo() { return _walkTo; }
 	Common::SharedPtr<Motor> getReach() { return _reach; }
@@ -198,8 +196,6 @@ public:
 	static void walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
 	void setTalking(Common::SharedPtr<Motor> talking);
-	void setBlink(Common::SharedPtr<Motor> blink);
-	void setTurnTo(Common::SharedPtr<Motor> turnTo);
 	void setShakeTo(Common::SharedPtr<Motor> shakeTo);
 	void setScaleTo(Common::SharedPtr<Motor> scaleTo);
 
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 8a1a590c031..bed6a40024f 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -397,7 +397,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 
 	sqcall("preLoad");
 	loadGameScene(json["gameScene"]->asObject());
-	loadDialog(json["gameScene"]->asObject());
+	loadDialog(json["dialog"]->asObject());
 	loadCallbacks(json["callbacks"]->asObject());
 	loadGlobals(json["globals"]->asObject());
 	loadActors(json["actors"]->asObject());
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 55df513bfe6..51bc1a5837f 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -483,7 +483,8 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 	}
 
 	Common::SharedPtr<ThreadBase> parentThread = sqthread(v);
-	Common::SharedPtr<Cutscene> cutscene(new Cutscene(parentThread->getId(), threadObj, closure, closureOverride, envObj));
+	Common::String cutsceneName = Common::String::format("%s (%lld)", _stringval(_closure(closure)->_function->_sourcename), _closure(closure)->_function->_lineinfos->_line);
+	Common::SharedPtr<Cutscene> cutscene(new Cutscene(cutsceneName, parentThread->getId(), threadObj, closure, closureOverride, envObj));
 	g_engine->_cutscene = cutscene;
 
 	// call the closure in the thread
@@ -892,6 +893,7 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "SLOW_EASE_OUT", SLOW_EASE_OUT);
 	regConst(v, "LOOPING", LOOPING);
 	regConst(v, "SWING", SWING);
+	regConst(v, "STOP_LOOPING", STOP_LOOPING);
 	regConst(v, "ALIGN_LEFT", ALIGN_LEFT);
 	regConst(v, "ALIGN_CENTER", ALIGN_CENTER);
 	regConst(v, "ALIGN_RIGHT", ALIGN_RIGHT);
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index f77f514e764..6cdd302af3d 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -115,14 +115,14 @@ void Thread::stop() {
 	suspend();
 }
 
-Cutscene::Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
+Cutscene::Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
 	: _parentThreadId(parentThreadId),
 	  _threadObj(threadObj),
 	  _closure(closure),
 	  _closureOverride(closureOverride),
 	  _envObj(envObj) {
 
-	_name = "cutscene";
+	_name = name;
 	_id = newThreadId();
 	_inputState = g_engine->_inputState.getState();
 	_actor = g_engine->_followActor;
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index dbb70e72f0a..e02bdbdb52d 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -105,7 +105,7 @@ enum CutsceneState {
 class Object;
 class Cutscene final : public ThreadBase {
 public:
-	Cutscene(int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
+	Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
 	~Cutscene() override final;
 
 	void start();
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 7ac2285c38c..88ef245c9b8 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -509,9 +509,10 @@ void TwpEngine::update(float elapsed) {
 	Common::Array<Common::SharedPtr<ThreadBase> > threads(_threads);
 	Common::Array<Common::SharedPtr<ThreadBase> > threadsToRemove;
 
+	bool isNotInDialog = _dialog.getState() == DialogState::None;
 	for (auto it = threads.begin(); it != threads.end(); it++) {
 		Common::SharedPtr<ThreadBase> thread(*it);
-		if (thread->update(elapsed)) {
+		if ((isNotInDialog || !thread->isGlobal()) && thread->update(elapsed)) {
 			threadsToRemove.push_back(thread);
 		}
 	}
@@ -1212,8 +1213,9 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 	_camera.setRoom(room);
 	_camera.setAt(camPos);
 
+	stopTalking();
+
 	// call actor enter function and objects enter function
-	actorEnter();
 	for (size_t i = 0; i < room->_layers.size(); i++) {
 		Common::SharedPtr<Layer> layer = room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
@@ -1225,7 +1227,9 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 					_room->_scalingTriggers.push_back(ScalingTrigger(obj, scaling));
 				}
 			}
-			if (sqrawexists(obj->_table, "enter"))
+			if(isActor(obj->getId())) {
+				actorEnter(_actor);
+			} else if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
 		}
 	}
@@ -1248,14 +1252,12 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 	sqcall("enteredRoom", room->_table);
 }
 
-void TwpEngine::actorEnter() {
-	if (_actor) {
-		sqcall(_actor->_table, "actorEnter");
-		if (_room) {
-			if (sqrawexists(_room->_table, "actorEnter")) {
-				sqcall(_room->_table, "actorEnter", _actor->_table);
-			}
-		}
+void TwpEngine::actorEnter(Common::SharedPtr<Object> actor) {
+	if(!actor) return;
+	if(sqrawexists(g_engine->_room->_table, "actorEnter")) {
+		sqcall(g_engine->_room->_table, "actorEnter", actor->_table);
+	} else {
+		sqcall("actorEnter", actor->_table);
 	}
 }
 
@@ -1266,7 +1268,16 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 		_room->_triggers.clear();
 		_room->_scalingTriggers.clear();
 
-		actorExit();
+		for (size_t i = 0; i < _room->_layers.size(); i++) {
+			Common::SharedPtr<Layer> layer(_room->_layers[i]);
+			for (size_t j = 0; j < layer->_objects.size(); j++) {
+				Common::SharedPtr<Object> obj = layer->_objects[j];
+				obj->stopObjectMotors();
+				if(isActor(obj->getId())) {
+					actorExit(obj);
+				}
+			}
+		}
 
 		// call room exit function with the next room as a parameter if requested
 		int nparams = sqparamCount(v, _room->_table, "exit");
@@ -1312,8 +1323,8 @@ void TwpEngine::setRoom(Common::SharedPtr<Room> room) {
 		enterRoom(room);
 }
 
-void TwpEngine::actorExit() {
-	if (_actor && _room) {
+void TwpEngine::actorExit(Common::SharedPtr<Object> actor) {
+	if (actor && _room) {
 		if (sqrawexists(_room->_table, "actorExit")) {
 			sqcall(_room->_table, "actorExit", _actor->_table);
 		}
@@ -1625,7 +1636,8 @@ void TwpEngine::updateTriggers() {
 }
 
 void TwpEngine::stopTalking() {
-	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
+	if(!_room) return;
+	for (auto it = _room->_layers.begin(); it != _room->_layers.end(); it++) {
 		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
 			(*it2)->stopTalking();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 26f62001e0e..38211f59e56 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -129,6 +129,8 @@ public:
 	void stopTalking();
 	void walkFast(bool state = true);
 
+	void actorEnter(Common::SharedPtr<Object> actor);
+	void actorExit(Common::SharedPtr<Object> actor);
 	Common::SharedPtr<Room> defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo = false);
 	void setRoom(Common::SharedPtr<Room> room);
 	void enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object> door = nullptr);
@@ -151,8 +153,6 @@ private:
 	void update(float elapsedMs);
 	void draw(RenderTexture *texture = nullptr);
 	void exitRoom(Common::SharedPtr<Room> nextRoom);
-	void actorEnter();
-	void actorExit();
 	void cancelSentence(Common::SharedPtr<Object> actor = nullptr);
 	void clickedAt(Math::Vector2d scrPos);
 	bool clickedAtHandled(Math::Vector2d roomPos);


Commit: c89c419bd57692c14a1c76939f8d0365abc1631e
    https://github.com/scummvm/scummvm/commit/c89c419bd57692c14a1c76939f8d0365abc1631e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove hardcoded shader version

Changed paths:
    engines/twp/gfx.cpp
    engines/twp/hud.cpp
    engines/twp/lighting.cpp
    engines/twp/shaders.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 31c45c6f53d..69cd6f325d4 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -137,7 +137,7 @@ Shader::~Shader() {
 
 void Shader::init(const char *name, const char *vertex, const char *fragment) {
 	const char *attributes[] = {"a_position", "a_color", "a_texCoords", nullptr};
-	_shader.loadFromStrings(name, vertex, fragment, attributes);
+	_shader.loadFromStrings(name, vertex, fragment, attributes, 110);
 
 	uint32 vbo = g_engine->getGfx()._vbo;
 	_shader.enableVertexAttribute("a_position", vbo, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)0);
@@ -199,7 +199,7 @@ void Gfx::init() {
 	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, _vbo));
 	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
 
-	const char *fragmentSrc = R"(#version 110
+	const char *fragmentSrc = R"(
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	uniform sampler2D u_texture;
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index f24050d3cc1..6ffad4c86be 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -42,7 +42,7 @@ ActorSlot::ActorSlot() = default;
 HudShader::HudShader() = default;
 
 void HudShader::init() {
-	const char *vsrc = R"(#version 110
+	const char *v_source = R"(
 	attribute vec2 a_position;
 	attribute vec4 a_color;
 	attribute vec2 a_texCoords;
@@ -71,7 +71,7 @@ void HudShader::init() {
 		v_ranges = u_ranges;
 	})";
 
-	const char* fsrc = R"(#version 110
+	const char* f_source = R"(
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	varying vec4 v_shadowColor;
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index aee21d0e892..3529edb5e5f 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -5,7 +5,7 @@
 
 namespace Twp {
 
-static const char *fshader = R"(#version 110
+static const char *fshader = R"(
 #ifdef GL_ES
 	precision highp float;
 #endif
@@ -75,7 +75,7 @@ void main(void) {
 	gl_FragColor = vec4(finalCol.rgb * finalLight, finalCol.a);
 })";
 
-static const char *debug_fshader = R"(#version 110
+static const char *debug_fshader = R"(
 varying vec2 v_texCoords;
 varying vec4 v_color;
 
@@ -141,7 +141,7 @@ void main(void) {
 	gl_FragColor = vec4(finalCol.rgb + diffuse, finalCol.a);
 })";
 
-static const char *vshader = R"(#version 110
+static const char *vshader = R"(
 uniform mat4 u_transform;
 attribute vec2 a_position;
 attribute vec4 a_color;
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index 62e8217e53f..e31439ae3fe 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -24,7 +24,7 @@
 
 namespace Twp {
 
-const char *vsrc = R"(#version 110
+const char *vsrc = R"(
 	uniform mat4 u_transform;
 attribute vec2 a_position;
 attribute vec4 a_color;
@@ -37,7 +37,7 @@ void main() {
 	v_texCoords = a_texCoords;
 })";
 
-	const char* bwShader = R"(#version 110
+	const char* bwShader = R"(
 varying vec2 v_texCoords;
 varying vec4 v_color;
 uniform sampler2D u_texture;
@@ -48,7 +48,7 @@ void main() {
 	gl_FragColor = vec4(gray, gray, gray, col.a);
 })";
 
-const char* ghostShader = R"(#version 110
+const char* ghostShader = R"(
 // Work in progress ghost shader.. Too over the top at the moment, it'll make you sick.
 
 varying vec4 v_color;
@@ -221,7 +221,7 @@ void main(void) {
 	gl_FragColor = v_color * vec4(col, texture2D(u_texture, c1).a);
 })";
 
-const char* sepiaShader = R"(#version 110
+const char* sepiaShader = R"(
 
 varying vec4 v_color;
 varying vec2 v_texCoords;
@@ -315,7 +315,7 @@ void main(void) {
 })";
 
 FadeShader::FadeShader() {
-	const char *fadeShader = R"(#version 110
+	const char *fadeShader = R"(
 #ifdef GL_ES
 		precision highp float;
 #endif


Commit: 4cb6a24c06ccb566da239b890c46addedd27862c
    https://github.com/scummvm/scummvm/commit/4cb6a24c06ccb566da239b890c46addedd27862c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix regression, actors should not have their alpha reset

Changed paths:
    engines/twp/object.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index c4f7821cbd8..360bab99c16 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -400,14 +400,6 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 			}
 		}
 		object->_room = room;
-
-		if(roomChanged && isActor(object->getId())) {
-			if(room == g_engine->_room) {
-				g_engine->actorEnter(object);
-			} else if(oldRoom == g_engine->_room) {
-				g_engine->actorExit(object);
-			}
-		}
 	}
 }
 
@@ -431,7 +423,6 @@ void Object::stopObjectMotors() {
 	_node->setRotationOffset(0);
 	_node->setOffset({0.f, 0.f});
 	_node->setShakeOffset({0.f, 0.f});
-	_node->setAlpha(1);
 	_node->setScale({1.f,1.f});
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 88ef245c9b8..4afc2a7d251 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1326,7 +1326,7 @@ void TwpEngine::setRoom(Common::SharedPtr<Room> room) {
 void TwpEngine::actorExit(Common::SharedPtr<Object> actor) {
 	if (actor && _room) {
 		if (sqrawexists(_room->_table, "actorExit")) {
-			sqcall(_room->_table, "actorExit", _actor->_table);
+			sqcall(_room->_table, "actorExit", actor->_table);
 		}
 	}
 }


Commit: c7ac09c116601dca28b1f799992c11ce575bf3df
    https://github.com/scummvm/scummvm/commit/c7ac09c116601dca28b1f799992c11ce575bf3df
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Actor should stop walking when exiting room

Changed paths:
    engines/twp/graph.cpp
    engines/twp/motor.cpp
    engines/twp/object.cpp


diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 1a4429096f0..9b5a237cb0c 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -127,7 +127,7 @@ void Graph::addEdge(GraphEdge e) {
 	}
 	if (!edge(e.to, e.start)) {
 		GraphEdge e2(e.to, e.start, e.cost);
-		_edges[e.to].push_back(e);
+		_edges[e.to].push_back(e2);
 	}
 }
 
@@ -313,11 +313,12 @@ Common::SharedPtr<Graph> PathFinder::createGraph() {
 
 Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
 	Common::Array<Vector2i> result;
-	if (_walkboxes.size() > 0) {
+	if (!_walkboxes.empty()) {
 		// find the walkbox where the actor is and put it first
 		for (uint i = 0; i < _walkboxes.size(); i++) {
 			const Walkbox &wb = _walkboxes[i];
 			if (wb.contains(start) && (i != 0)) {
+				_graph.reset();
 				SWAP(_walkboxes[0], _walkboxes[i]);
 				break;
 			}
@@ -332,8 +333,10 @@ Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
 			}
 
 			const uint index = minIndex(dists);
-			if (index != 0)
+			if (index != 0) {
+				_graph.reset();
 				SWAP(_walkboxes[0], _walkboxes[index]);
+			}
 		}
 
 		if (!_graph)
@@ -352,7 +355,7 @@ Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
 			to = wb.getClosestPointOnEdge(to);
 		}
 		// we don't want the actor to walk in a different walkbox
-		// then check if endpoint is inside one of the other walkboxes and find closest point on edge
+		// then check if endpoint is inside one of the other walkboxes and find the closest point on edge
 		for (uint i = 1; i < _walkboxes.size(); i++) {
 			if (_walkboxes[i].contains(to)) {
 				to = _walkboxes[i].getClosestPointOnEdge(to);
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 95b1a874ae0..4ad5b6efa94 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -244,6 +244,7 @@ void WalkTo::actorArrived() {
 }
 
 void WalkTo::update(float elapsed) {
+	if(!_enabled) return;
 	if (_state == kWalking && !_path.empty()) {
 		Vector2i dest = _path[0];
 		float d = distance(dest, (Vector2i)_obj->_node->getAbsPos());
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 360bab99c16..d5e875e19fd 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -424,6 +424,8 @@ void Object::stopObjectMotors() {
 	_node->setOffset({0.f, 0.f});
 	_node->setShakeOffset({0.f, 0.f});
 	_node->setScale({1.f,1.f});
+	if(isActor(getId()))
+		stand();
 }
 
 void Object::setFacing(Facing facing) {


Commit: df1f1a1d1ca19ccf5e86f10ae00c05dded78b43a
    https://github.com/scummvm/scummvm/commit/df1f1a1d1ca19ccf5e86f10ae00c05dded78b43a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix regression actorEnter and actorExit should be called

Changed paths:
    engines/twp/object.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index d5e875e19fd..313d9ce794b 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -400,6 +400,14 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 			}
 		}
 		object->_room = room;
+
+		if(roomChanged && isActor(object->getId())) {
+			if(room == g_engine->_room) {
+				g_engine->actorEnter(object);
+			} else if(oldRoom == g_engine->_room) {
+				g_engine->actorExit(object);
+			}
+		}
 	}
 }
 
@@ -597,19 +605,7 @@ void Object::update(float elapsedSec) {
 
 void Object::pickupObject(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	obj->_owner.reset(actor);
-	int inventory_slot = -1;
-	if(sqrawexists(obj->_table, "inventory_slot")) {
-		sqgetf(obj->_table, "inventory_slot", inventory_slot);
-		inventory_slot--;
-		if(inventory_slot >= actor->_inventory.size()) {
-			inventory_slot = -1;
-		}
-	}
-	if(inventory_slot == -1) {
-		actor->_inventory.push_back(obj);
-	} else {
-		actor->_inventory[inventory_slot] = obj;
-	}
+	actor->_inventory.push_back(obj);
 
 	sqcall("onPickup", obj->_table, actor->_table);
 


Commit: d0e7ac4628cfd7b900d81d5f8f9e9a5ee16e2fdf
    https://github.com/scummvm/scummvm/commit/d0e7ac4628cfd7b900d81d5f8f9e9a5ee16e2fdf
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Clear inventory offset when no more inventory

Changed paths:
    engines/twp/objlib.cpp


diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 649c35c3843..8c9d641a7e7 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -995,8 +995,12 @@ static SQInteger removeInventory(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	if (obj->_owner)
+	if(isActor(obj->getId())) {
+		obj->_inventory.clear();
+		obj->_inventoryOffset = 0;
+	} else if (obj->_owner) {
 		obj->_owner->removeInventory(obj);
+	}
 	return 0;
 }
 


Commit: f12099e667b7edfe5c9fa4de33535ecf0fec464b
    https://github.com/scummvm/scummvm/commit/f12099e667b7edfe5c9fa4de33535ecf0fec464b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add find_if

Changed paths:
    engines/twp/util.h


diff --git a/engines/twp/util.h b/engines/twp/util.h
index 3f21c0ee800..caa2020bf2b 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -97,6 +97,16 @@ size_t find(const Common::Array<T> &array, const T &o) {
 	return (size_t)-1;
 }
 
+template<typename T, typename Pred>
+size_t find_if(const Common::Array<T> &array, Pred p) {
+	for (size_t i = 0; i < array.size(); i++) {
+		if (p(array[i])) {
+			return i;
+		}
+	}
+	return (size_t)-1;
+}
+
 template<typename T>
 size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T* o) {
 	for (size_t i = 0; i < array.size(); i++) {


Commit: c3b7b5b397fb3dd6f83ff05c57c15acb1f65adce
    https://github.com/scummvm/scummvm/commit/c3b7b5b397fb3dd6f83ff05c57c15acb1f65adce
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Don't use verb on actor if not talk or give

If wwe don't do that the computer is not usable in Delores room

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 4afc2a7d251..fd912284e16 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1228,7 +1228,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 				}
 			}
 			if(isActor(obj->getId())) {
-				actorEnter(_actor);
+				actorEnter(obj);
 			} else if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
 		}
@@ -1401,19 +1401,27 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 	_fadeShader->_elapsed = 0.f;
 }
 
-struct GetByZorder {
-	explicit GetByZorder(Common::SharedPtr<Object> &result) : _result(result) { result = nullptr; }
+struct GetByZOrder {
+	explicit GetByZOrder(Common::SharedPtr<Object> &result) : _result(result) {
+		result = nullptr;
+		_isTalkVerb = g_engine->_hud._verb.id.id == VERB_TALKTO;
+		_isGiveVerb = g_engine->_hud._verb.id.id == VERB_GIVE;
+	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
-			_result = obj;
-			_zOrder = obj->_node->getZSort();
+			if(!isActor(obj->getId()) || (_isTalkVerb && (obj->getFlags() & TALKABLE)) || (_isGiveVerb && (obj->getFlags() & GIVEABLE))) {
+				_result = obj;
+				_zOrder = obj->_node->getZSort();
+			}
 		}
 		return false;
 	}
 
 public:
 	Common::SharedPtr<Object> &_result;
+	bool _isTalkVerb;
+	bool _isGiveVerb;
 
 private:
 	int _zOrder = INT_MAX;
@@ -1421,7 +1429,7 @@ private:
 
 Common::SharedPtr<Object> TwpEngine::objAt(Math::Vector2d pos) {
 	Common::SharedPtr<Object> result;
-	objsAt(pos, GetByZorder(result));
+	objsAt(pos, GetByZOrder(result));
 	return result;
 }
 


Commit: dc065124bf8ac2793dbf8d7d9da4ba5968ca7fdd
    https://github.com/scummvm/scummvm/commit/dc065124bf8ac2793dbf8d7d9da4ba5968ca7fdd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Do some fixes

Changed paths:
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/squtil.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index fda27f8e961..a9cb4ac4ca4 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -345,7 +345,7 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 // local spotters = roomActors(currentRoom)
 // foreach(actor in spotters) { ...}
 static SQInteger roomActors(HSQUIRRELVM v) {
-	Common::SharedPtr<Room> room = sqroom(v, 2);
+	Common::SharedPtr<Room> room(sqroom(v, 2));
 	if (!room)
 		return sq_throwerror(v, "failed to get room");
 
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index bed6a40024f..4b3b964495e 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -362,6 +362,7 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 }
 
 static void setActor(const Common::String &key) {
+	if(key.empty()) return;
 	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
 		Common::SharedPtr<Object> a = g_engine->_actors[i];
 		if (a->_key == key) {
@@ -393,7 +394,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 		return false;
 	}
 
-	debugC(kDebugGame, "%s", savegame.jSavegame->stringify().c_str());
+	debugC(kDebugGame, "load game: %s", savegame.jSavegame->stringify().c_str());
 
 	sqcall("preLoad");
 	loadGameScene(json["gameScene"]->asObject());
@@ -409,7 +410,8 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	loadObjects(json["objects"]->asObject());
 	g_engine->setRoom(room(json["currentRoom"]->asString()));
 	setActor(json["selectedActor"]->asString());
-	g_engine->cameraAt(g_engine->_actor->_node->getPos());
+	if(g_engine->_actor)
+		g_engine->cameraAt(g_engine->_actor->_node->getPos());
 
 	HSQUIRRELVM v = g_engine->getVm();
 	sqsetf(sqrootTbl(v), "SAVEBUILD", json["savebuild"]->asIntegerNumber());
@@ -576,7 +578,7 @@ void SaveGameManager::loadRooms(const Common::JSONObject &json) {
 
 void SaveGameManager::loadObjects(const Common::JSONObject &json) {
 	for (auto it = json.begin(); it != json.end(); it++) {
-		Common::SharedPtr<Object> o = object(it->_key);
+		Common::SharedPtr<Object> o(object(it->_key));
 		if (o)
 			loadObject(o, it->_value->asObject());
 		else
@@ -899,7 +901,7 @@ static Common::JSONValue *createJObject(HSQOBJECT &table, Common::SharedPtr<Obje
 static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
 	Common::JSONObject *jObj = static_cast<Common::JSONObject *>(data);
 	if (isObject(getId(v))) {
-		Common::SharedPtr<Object> obj = sqobj(v);
+		Common::SharedPtr<Object> obj(sqobj(v));
 		if (!obj || (obj->_objType == otNone)) {
 			// info fmt"obj: createJObject({k})"
 			(*jObj)[k] = createJObject(v, obj);
@@ -976,6 +978,8 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	sqcall("preSave");
 	Common::JSONValue *data = createSaveGame();
 
+	debugC(kDebugGame, "save game: %s", data->stringify().c_str());
+
 	// dump savegame as json
 	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("save.json", false);
 	// Common::String s = data->stringify(true);
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 9d71cec0e83..73ce98e37fe 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -276,7 +276,7 @@ Common::SharedPtr<Room> sqroom(HSQOBJECT table) {
 
 Common::SharedPtr<Room> getRoom(int id) {
 	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
+		Common::SharedPtr<Room> room(g_engine->_rooms[i]);
 		if (getId(room->_table) == id)
 			return room;
 	}
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index fd912284e16..1da88f68450 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1192,7 +1192,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 	_room->_lights._numLights = 0;
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
 	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
-	if (_actor)
+	if (_actor && _hud.actorSlot(_actor))
 		_hud._verb = _hud.actorSlot(_actor)->verbs[0];
 
 	// move current actor to the new room
@@ -1404,13 +1404,11 @@ void TwpEngine::fadeTo(FadeEffect effect, float duration, bool fadeToSep) {
 struct GetByZOrder {
 	explicit GetByZOrder(Common::SharedPtr<Object> &result) : _result(result) {
 		result = nullptr;
-		_isTalkVerb = g_engine->_hud._verb.id.id == VERB_TALKTO;
-		_isGiveVerb = g_engine->_hud._verb.id.id == VERB_GIVE;
 	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
-			if(!isActor(obj->getId()) || (_isTalkVerb && (obj->getFlags() & TALKABLE)) || (_isGiveVerb && (obj->getFlags() & GIVEABLE))) {
+			if(!isActor(obj->getId()) || !obj->_key.empty()) {
 				_result = obj;
 				_zOrder = obj->_node->getZSort();
 			}
@@ -1420,8 +1418,6 @@ struct GetByZOrder {
 
 public:
 	Common::SharedPtr<Object> &_result;
-	bool _isTalkVerb;
-	bool _isGiveVerb;
 
 private:
 	int _zOrder = INT_MAX;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 38211f59e56..085139035ff 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -105,10 +105,10 @@ public:
 	}
 
 	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
-		return true;
+		return !_cutscene;
 	}
 	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
-		return _saveGameManager._allowSaveGame;
+		return _saveGameManager._allowSaveGame && !_cutscene;
 	}
 
 	virtual Common::String getSaveStateName(int slot) const override;


Commit: f2b8a5ec2975ddbd9e5db3c8599ed4f47b83d544
    https://github.com/scummvm/scummvm/commit/f2b8a5ec2975ddbd9e5db3c8599ed4f47b83d544
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix shuffle method

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 44d236d3a84..7422064a4e9 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -46,12 +46,9 @@ private:
 template<typename T>
 static void shuffle(Common::Array<T> &array) {
 	if (array.size() > 1) {
-		Common::RandomSource &rnd = g_engine->getRandomSource();
-		for (size_t i = 0; i < array.size() - 1; i++) {
-			size_t j = i + rnd.getRandomNumber(RAND_MAX) / (RAND_MAX / (array.size() - i) + 1);
-			const T &t = array[j];
-			array[j] = array[i];
-			array[i] = t;
+		for (size_t i = 0; i < array.size(); i++) {
+			size_t j = g_engine->getRandomSource().getRandomNumberRng(0, array.size() - 1);
+			SWAP(array[j], array[i]);
 		}
 	}
 }


Commit: e4dfbdbaa02088812394f98e295b543f77800adf
    https://github.com/scummvm/scummvm/commit/e4dfbdbaa02088812394f98e295b543f77800adf
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix module.mk for create_project

Changed paths:
    engines/twp/module.mk


diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 5bae8a2b646..75a16aba15b 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -1,38 +1,6 @@
 MODULE := engines/twp
 
-SQUIRREL_OBJS = \
-	squirrel/sqapi.o \
-	squirrel/sqbaselib.o \
-	squirrel/sqfuncstate.o \
-	squirrel/sqdebug.o \
-	squirrel/sqlexer.o \
-	squirrel/sqobject.o \
-	squirrel/sqcompiler.o \
-	squirrel/sqstate.o \
-	squirrel/sqtable.o \
-	squirrel/sqmem.o \
-	squirrel/sqvm.o \
-	squirrel/sqclass.o \
-	squirrel/sqstdio.o \
-	squirrel/sqstdmath.o \
-	squirrel/sqstdstring.o \
-	squirrel/sqstdstream.o \
-	squirrel/sqstdblob.o \
-	squirrel/sqstdrex.o \
-	squirrel/sqstdaux.o
-
-CLIPPER_OBJS = clipper/clipper.o
-IMGUI_OBJS = imgui/imgui.o \
-	imgui/imgui_draw.o \
-	imgui/imgui_widgets.o \
-	imgui/imgui_tables.o \
-	imgui_impl_opengl3_scummvm.o \
-	imgui_impl_sdl2_scummvm.o
-
 MODULE_OBJS = \
-	$(SQUIRREL_OBJS) \
-	$(CLIPPER_OBJS) \
-	$(IMGUI_OBJS) \
 	twp.o \
 	console.o \
 	metaengine.o \
@@ -78,6 +46,32 @@ MODULE_OBJS = \
 	time.o \
 	dialogs.o \
 	debugtools.o \
+	squirrel/sqapi.o \
+	squirrel/sqbaselib.o \
+	squirrel/sqfuncstate.o \
+	squirrel/sqdebug.o \
+	squirrel/sqlexer.o \
+	squirrel/sqobject.o \
+	squirrel/sqcompiler.o \
+	squirrel/sqstate.o \
+	squirrel/sqtable.o \
+	squirrel/sqmem.o \
+	squirrel/sqvm.o \
+	squirrel/sqclass.o \
+	squirrel/sqstdio.o \
+	squirrel/sqstdmath.o \
+	squirrel/sqstdstring.o \
+	squirrel/sqstdstream.o \
+	squirrel/sqstdblob.o \
+	squirrel/sqstdrex.o \
+	squirrel/sqstdaux.o \
+	clipper/clipper.o \
+	imgui/imgui.o \
+	imgui/imgui_draw.o \
+	imgui/imgui_widgets.o \
+	imgui/imgui_tables.o \
+	imgui_impl_opengl3_scummvm.o \
+	imgui_impl_sdl2_scummvm.o
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)


Commit: f2b016d53830f751dde5bc9865d4c967d5af029e
    https://github.com/scummvm/scummvm/commit/f2b016d53830f751dde5bc9865d4c967d5af029e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add USE_IMGUI flag

Changed paths:
    configure
    engines/twp/module.mk
    engines/twp/twp.cpp


diff --git a/configure b/configure
index d14b4a81149..001345619c5 100755
--- a/configure
+++ b/configure
@@ -277,6 +277,7 @@ _endian=unknown
 _need_memalign=yes
 _have_x86=no
 _have_amd64=no
+_imgui=no
 
 # Add (virtual) features
 add_feature 16bit "16bit color" "_16bit"
@@ -301,6 +302,7 @@ add_feature zlib "zlib" "_zlib"
 add_feature lua "lua" "_lua"
 add_feature fribidi "FriBidi" "_fribidi"
 add_feature test_cxx11 "Test C++11" "_test_cxx11"
+add_feature imgui "imgui" "_imgui"
 
 # Directories for installing ScummVM.
 # This list is closely based on what GNU autoconf does,
@@ -1282,6 +1284,8 @@ for ac_option in $@; do
 	--disable-tts)                _tts=no                ;;
 	--enable-gtk)                 _gtk=yes               ;;
 	--disable-gtk)                _gtk=no                ;;
+	--disable-imgui)              _imgui=no              ;;
+	--enable-imgui)               _imgui=yes             ;;
 	--opengl-mode=*)
 		_opengl_mode=`echo $ac_option | cut -d '=' -f 2`
 		;;
@@ -6765,6 +6769,13 @@ fi
 define_in_config_if_yes "$_discord" 'USE_DISCORD'
 echo "$_discord"
 
+
+#
+# Check for Imgui
+#
+define_in_config_if_yes "$_imgui" 'USE_IMGUI'
+echo "$_imgui"
+
 #
 # Enable vkeybd / event recorder
 #
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 75a16aba15b..d93f97f080d 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -45,7 +45,6 @@ MODULE_OBJS = \
 	btea.o \
 	time.o \
 	dialogs.o \
-	debugtools.o \
 	squirrel/sqapi.o \
 	squirrel/sqbaselib.o \
 	squirrel/sqfuncstate.o \
@@ -66,12 +65,17 @@ MODULE_OBJS = \
 	squirrel/sqstdrex.o \
 	squirrel/sqstdaux.o \
 	clipper/clipper.o \
+
+ifdef USE_IMGUI
+MODULE_OBJS += \
+	debugtools.o \
 	imgui/imgui.o \
 	imgui/imgui_draw.o \
 	imgui/imgui_widgets.o \
 	imgui/imgui_tables.o \
 	imgui_impl_opengl3_scummvm.o \
 	imgui_impl_sdl2_scummvm.o
+endif
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_TWP), DYNAMIC_PLUGIN)
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1da88f68450..cba6c2cdfd7 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,14 +19,22 @@
  *
  */
 
-#include "imgui/imgui.h"
-#include "imgui_impl_sdl2_scummvm.h"
-#include "imgui_impl_opengl3_scummvm.h"
-#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+#include "config.h"
+
+#ifdef USE_IMGUI
+	#include "imgui/imgui.h"
+	#include "imgui_impl_sdl2_scummvm.h"
+	#include "imgui_impl_opengl3_scummvm.h"
+	#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+#endif
+
+#include "common/config-manager.h"
+#include "common/events.h"
 #include "common/savefile.h"
 #include "image/png.h"
 #include "engines/util.h"
 #include "graphics/opengl/system_headers.h"
+
 #include "twp/twp.h"
 #include "twp/console.h"
 #include "twp/lighting.h"
@@ -40,7 +48,10 @@
 namespace Twp {
 
 TwpEngine *g_engine;
-SDL_Window *g_window = nullptr;
+
+#ifdef USE_IMGUI
+	SDL_Window *g_window = nullptr;
+#endif
 
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
@@ -695,8 +706,11 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 
 	// imgui render
 	_gfx.use(nullptr);
+
+#ifdef USE_IMGUI
 	ImGui::Render();
 	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+#endif
 
 	g_system->updateScreen();
 }
@@ -705,6 +719,7 @@ Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
 
+#ifdef USE_IMGUI
 	// Setup Dear ImGui
 	OpenGLSdlGraphics3dManager *manager = dynamic_cast<OpenGLSdlGraphics3dManager *>(g_system->getPaletteManager());
 	IMGUI_CHECKVERSION();
@@ -714,6 +729,7 @@ Common::Error TwpEngine::run() {
 	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
 	ImGui_ImplOpenGL3_Init("#version 110");
 	ImGui::StyleColorsDark();
+#endif
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -762,10 +778,12 @@ Common::Error TwpEngine::run() {
 	while (!shouldQuit()) {
 		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
+#ifdef USE_IMGUI
 			ImGui_ImplSDL2_ProcessEvent(&e);
 			ImGuiIO &io = ImGui::GetIO();
 			if (io.WantTextInput || io.WantCaptureMouse)
 				continue;
+#endif
 
 			switch (e.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START: {
@@ -921,11 +939,13 @@ Common::Error TwpEngine::run() {
 		time = newTime;
 		update(speed * delta / 1000.f);
 
+#ifdef USE_IMGUI
 		ImGui_ImplOpenGL3_NewFrame();
 		ImGui_ImplSDL2_NewFrame(g_window);
 		ImGui::NewFrame();
 
 		onImGuiRender();
+#endif
 
 		draw();
 		_cursor.update();
@@ -938,9 +958,11 @@ Common::Error TwpEngine::run() {
 	}
 
 	// Cleanup
+#ifdef USE_IMGUI
 	ImGui_ImplOpenGL3_Shutdown();
 	ImGui_ImplSDL2_Shutdown();
 	ImGui::DestroyContext();
+#endif
 
 	return Common::kNoError;
 }


Commit: 77f379014fb2a338b8e0572d22e0919a4c7afccd
    https://github.com/scummvm/scummvm/commit/77f379014fb2a338b8e0572d22e0919a4c7afccd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix phone keeps ringing

Changed paths:
    engines/twp/audio.cpp
    engines/twp/soundlib.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index b1672b6bfd9..625d2d61e1d 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -120,6 +120,7 @@ void AudioSystem::stop(int id) {
 	// sound definition ID ?
 	for (auto & _slot : _slots) {
 		if (_slot.busy && _slot.sndDef->getId() == id) {
+			_slot.loopTimes = 0;
 			g_engine->_mixer->stopHandle(_slot.handle);
 		}
 	}
@@ -154,6 +155,7 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 			vol *= (1.f - progress);
 		}
 		if (progress > 1.0f) {
+			slot->loopTimes = 0;
 			g_engine->_mixer->stopHandle(slot->handle);
 			return;
 		}
@@ -260,12 +262,11 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 	if(!audioStream)
 		error("Failed to load audio: %s", name.c_str());
 
-	byte vol = (byte)(volume * 255);
 	int id = newSoundId();
 	if (fadeInTimeMs > 0.f) {
-		vol = 0;
+		volume = 0;
 	}
-	g_engine->_mixer->playStream(cat, &slot->handle, audioStream, id, vol * _masterVolume);
+	g_engine->_mixer->playStream(cat, &slot->handle, audioStream, id, volume * _masterVolume);
 	slot->id = id;
 	slot->objId = objId;
 	slot->sndDef = sndDef;
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index b20e19c23ee..53121eee342 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -282,6 +282,7 @@ static SQInteger loopSound(HSQUIRRELVM v) {
 		}
 	}
 	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
+	debugC(kDebugSndScript, "loopSound %s: %d", sound->getName().c_str(), soundId);
 	sqpush(v, soundId);
 	return 1;
 }


Commit: c27cbbaf63765915e8e58acca9b1ad7b3817c342
    https://github.com/scummvm/scummvm/commit/c27cbbaf63765915e8e58acca9b1ad7b3817c342
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix some complation issues on Windows

Changed paths:
    engines/twp/graph.cpp
    engines/twp/objlib.cpp
    engines/twp/squirrel/squirrel.h
    engines/twp/twp.cpp


diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 9b5a237cb0c..8501f980dbe 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -171,7 +171,7 @@ void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
 }
 
 static Vector2i toVector2i(float x, float y) {
-	return Vector2i(round(x), round(y));
+	return Vector2i(roundf(x), roundf(y));
 }
 
 Vector2i Walkbox::getClosestPointOnEdge(Vector2i p) const {
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 8c9d641a7e7..ceaaa4a11c0 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -457,7 +457,7 @@ static SQInteger objectIcon(HSQUIRRELVM v) {
 	case OT_ARRAY: {
 		Common::String icon;
 		Common::StringArray icons;
-		int fps;
+		int fps = 10;
 		sq_push(v, 3);
 		sq_pushnull(v); // null iterator
 		if (SQ_SUCCEEDED(sq_next(v, -2)))
diff --git a/engines/twp/squirrel/squirrel.h b/engines/twp/squirrel/squirrel.h
index 57147b42210..111cc48ecbc 100755
--- a/engines/twp/squirrel/squirrel.h
+++ b/engines/twp/squirrel/squirrel.h
@@ -60,7 +60,7 @@ struct SQDelegable;
 struct SQOuter;
 
 #ifdef _UNICODE
-#define SQUNICODE
+//#define SQUNICODE
 #endif
 
 #include "sqconfig.h"
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cba6c2cdfd7..f5302991687 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,7 +19,9 @@
  *
  */
 
+#if defined(HAVE_CONFIG_H)
 #include "config.h"
+#endif
 
 #ifdef USE_IMGUI
 	#include "imgui/imgui.h"


Commit: f6073f1d48cc6f4dfddec395cdfea31c7444054d
    https://github.com/scummvm/scummvm/commit/f6073f1d48cc6f4dfddec395cdfea31c7444054d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Indicate dependencies in configure.engine

Changed paths:
    engines/twp/audio.cpp
    engines/twp/configure.engine
    engines/twp/resmanager.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 625d2d61e1d..e4486c7c18e 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -29,10 +29,6 @@
 #include "twp/twp.h"
 #include "twp/squtil.h"
 
-#ifndef USE_VORBIS
-	#error TWP engine requires USE_VORBIS flag
-#endif
-
 namespace Twp {
 
 void SoundStream::open(Common::SharedPtr<SoundDefinition> sndDef) {
diff --git a/engines/twp/configure.engine b/engines/twp/configure.engine
index 84ec54804ef..b23be5a4c60 100644
--- a/engines/twp/configure.engine
+++ b/engines/twp/configure.engine
@@ -1,3 +1,3 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine twp "Thimbleweed Park" yes "" "" ""
+add_engine twp "Thimbleweed Park" yes "" "" "16bit highres vorbis png imgui opengl_game_shaders"
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 17d5336284f..339c72a8b43 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -50,7 +50,7 @@ void ResManager::loadTexture(const Common::String &name) {
 	d.loadStream(r);
 	const Graphics::Surface *surface = d.getSurface();
 	if(!surface) {
-		error("PNG %s not loaded, please check USE_PNG flag", name.c_str());
+		error("PNG %s not loaded", name.c_str());
 		return;
 	}
 


Commit: 16e9235d423939ce8abf4ba6c4913fed45285244
    https://github.com/scummvm/scummvm/commit/16e9235d423939ce8abf4ba6c4913fed45285244
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Add games explicitly in detection tables

Changed paths:
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/detection_tables.h


diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index cdcc8c1db37..800bd854c2c 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -43,13 +43,4 @@ TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(T
 																			   sizeof(ADGameDescription), Twp::twpGames) {
 }
 
-ADDetectedGame TwpMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
-	for (const auto & allFile : allFiles) {
-		if (allFile._key.toString().hasSuffixIgnoreCase(".ggpack1")) {
-			return ADDetectedGame(Twp::gameDescriptions);
-		}
-	}
-	return ADDetectedGame();
-}
-
 REGISTER_PLUGIN_STATIC(TWP_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TwpMetaEngineDetection);
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index b33a6b49813..6ddf43d41d9 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -70,9 +70,6 @@ public:
 	const DebugChannelDef *getDebugChannels() const override {
 		return debugFlagList;
 	}
-
-private:
-	ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const override;
 };
 
 #endif // TWP_DETECTION_H
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index 1d4578e0d05..436409e8ed3 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -27,14 +27,25 @@ const PlainGameDescriptor twpGames[] = {
 };
 
 const ADGameDescription gameDescriptions[] = {
+	// Thimbleweed Park - GOG version
 	{
 		"twp",
-		nullptr,
-		AD_ENTRY1("ThimbleweedPark.ggpack1", nullptr),
+		"",
+		AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
 		Common::UNK_LANG,
 		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
-		GUIO1(GUIO_NONE)
+		GUIO1(GUIO_NOMIDI)
+	},
+	// Thimbleweed Park - EPIC Games version
+	{
+		"twp",
+		"",
+		AD_ENTRY1s("ThimbleweedPark.ggpack1", "a97546ee2d9e19aab59714a267009a31", 502540584),
+		Common::UNK_LANG,
+		Common::kPlatformUnknown,
+		ADGF_UNSTABLE,
+		GUIO1(GUIO_NOMIDI)
 	},
 
 	AD_TABLE_END_MARKER


Commit: b8e0a3703708ba5043361ae1e22f88d409f7a738
    https://github.com/scummvm/scummvm/commit/b8e0a3703708ba5043361ae1e22f88d409f7a738
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix several code review comments

Changed paths:
    configure
    engines/twp/POTFILES
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/actorswitcher.h
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/configure.engine
    engines/twp/credits.pl
    engines/twp/debugtools.cpp
    engines/twp/detection.h
    engines/twp/graph.cpp
    engines/twp/prefs.cpp
    engines/twp/sqgame.h
    engines/twp/tsv.cpp
    engines/twp/util.cpp


diff --git a/configure b/configure
index 001345619c5..f9dc9c74658 100755
--- a/configure
+++ b/configure
@@ -6773,8 +6773,11 @@ echo "$_discord"
 #
 # Check for Imgui
 #
-define_in_config_if_yes "$_imgui" 'USE_IMGUI'
-echo "$_imgui"
+if test "$_opengl" = yes ; then
+	echocheck "Imgui"
+	define_in_config_if_yes "$_imgui" 'USE_IMGUI'
+	echo "$_imgui"
+fi
 
 #
 # Enable vkeybd / event recorder
diff --git a/engines/twp/POTFILES b/engines/twp/POTFILES
index 388ceab2140..523f89adf63 100644
--- a/engines/twp/POTFILES
+++ b/engines/twp/POTFILES
@@ -1 +1,2 @@
+engines/twp/dialogs.cpp
 engines/twp/metaengine.cpp
diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index c9816cfa81d..aa6aa33def6 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -214,7 +214,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> obj = sqobj(v, 3);
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot");
-		if(actor1->_room != actor2->_room)
+		if (actor1->_room != actor2->_room)
 			return false;
 		// not sure about this, needs to be check one day ;)
 		sqpush(v, distance((Vector2i)actor1->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < distance((Vector2i)actor2->_node->getAbsPos(), (Vector2i)obj->getUsePos()));
@@ -231,7 +231,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		int dist;
 		if (SQ_FAILED(sqget(v, 4, dist)))
 			return sq_throwerror(v, "failed to get distance");
-		if(actor->_room != obj->_room)
+		if (actor->_room != obj->_room)
 			return false;
 		sqpush(v, distance((Vector2i)actor->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < dist);
 		return 1;
@@ -334,8 +334,8 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 3, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (const auto & walkbox : g_engine->_room->_walkboxes) {
-			if (walkbox._name == name) {
+	for (const auto &walkbox : g_engine->_room->_walkboxes) {
+		if (walkbox._name == name) {
 			if (walkbox.contains((Vector2i)actor->_node->getAbsPos())) {
 				sqpush(v, true);
 				return 1;
@@ -932,7 +932,7 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 
 // If an actor is specified, returns true otherwise returns false.
 static SQInteger is_actor(HSQUIRRELVM v) {
-	Common::SharedPtr<Object> actor = sqactor(v, 2);
+	Common::SharedPtr<Object> actor(sqactor(v, 2));
 	sqpush(v, actor != nullptr);
 	return 1;
 }
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index 45e0a97f1e7..f985aa9bbc9 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -31,16 +31,16 @@
 
 namespace Twp {
 
-ActorSwitcherSlot::ActorSwitcherSlot(const Common::String &icon, Color back, Color frame, SelectFunc *selectFunc, int id) {
-	this->icon = icon;
-	this->back = back;
-	this->frame = frame;
-	this->selectFunc = selectFunc;
-	this->id = id;
+ActorSwitcherSlot::ActorSwitcherSlot(const Common::String &icon_, Color back_, Color frame_, SelectFunc *selectFunc_, int id_) {
+	icon = icon_;
+	this->back = back_;
+	this->frame = frame_;
+	this->selectFunc = selectFunc_;
+	this->id = id_;
 }
 
 void ActorSwitcherSlot::select() {
-	if(selectFunc) {
+	if (selectFunc) {
 		selectFunc(id);
 	}
 }
@@ -101,7 +101,7 @@ void ActorSwitcher::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Col
 }
 
 float ActorSwitcher::height() const {
-	float n = _mouseOver ? _slots.size() : 1;
+	float n = _mouseOver ? _slots.size() : 1.0f;
 	return n * ACTOR_SEP;
 }
 
@@ -116,50 +116,51 @@ Common::Rect ActorSwitcher::rect() const {
 }
 
 void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float elapsed) {
-	if (_visible) {
-		_slots = slots;
-
-		// update flash icon
-		if ((_flash != 0) && ((_flash == -1) || (_flashElapsed < _flash))) {
-			_flashElapsed = _flashElapsed + elapsed;
-			_alpha = 0.6f + 0.4f * sin(M_PI * 2.f * _flashElapsed);
-		} else {
-			_flash = 0;
-			_flashElapsed = 0.f;
-			_alpha = INACTIVE_ALPHA;
-		}
+	if (!_visible)
+		return;
+
+	_slots = slots;
+
+	// update flash icon
+	if ((_flash != 0) && ((_flash == -1) || (_flashElapsed < _flash))) {
+		_flashElapsed = _flashElapsed + elapsed;
+		_alpha = 0.6f + 0.4f * sin(M_PI * 2.f * _flashElapsed);
+	} else {
+		_flash = 0;
+		_flashElapsed = 0.f;
+		_alpha = INACTIVE_ALPHA;
+	}
 
-		// check if mouse is over actor icons or gear icon
-		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
-		bool oldMouseOver = _mouseOver;
-		_mouseOver = !_down && rect().contains(scrPos.getX(), scrPos.getY());
+	// check if mouse is over actor icons or gear icon
+	Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
+	bool oldMouseOver = _mouseOver;
+	_mouseOver = !_down && rect().contains(scrPos.getX(), scrPos.getY());
 
-		// update anim
-		_animElapsed = _animElapsed + elapsed;
+	// update anim
+	_animElapsed = _animElapsed + elapsed;
 
-		// stop anim or flash if necessary
-		if (oldMouseOver != _mouseOver) {
-			_animElapsed = 0.f;
-			if (_mouseOver)
-				_flash = 0;
-		}
+	// stop anim or flash if necessary
+	if (oldMouseOver != _mouseOver) {
+		_animElapsed = 0.f;
+		if (_mouseOver)
+			_flash = 0;
+	}
 
-		// update anim pos
-		_animPos = MIN(1.f, _animElapsed / ANIM_DURATION);
-
-		// check if we select an actor or gear icon
-		if (_mouseOver && (g_engine->_cursor.leftDown) && !_down) {
-			_down = true;
-			// check if we allow to select an actor
-            size_t iconIdx = iconIndex(scrPos);
-			if ((_mode == asOn) || (iconIdx == (_slots.size() - 1))) {
-				if (_slots[iconIdx].selectFunc != nullptr)
-					_slots[iconIdx].select();
-			}
+	// update anim pos
+	_animPos = MIN(1.f, _animElapsed / ANIM_DURATION);
+
+	// check if we select an actor or gear icon
+	if (_mouseOver && (g_engine->_cursor.leftDown) && !_down) {
+		_down = true;
+		// check if we allow to select an actor
+		size_t iconIdx = iconIndex(scrPos);
+		if ((_mode == asOn) || (iconIdx == (_slots.size() - 1))) {
+			if (_slots[iconIdx].selectFunc != nullptr)
+				_slots[iconIdx].select();
 		}
-		if (!g_engine->_cursor.leftDown)
-			_down = false;
 	}
+	if (!g_engine->_cursor.leftDown)
+		_down = false;
 }
 
 } // namespace Twp
diff --git a/engines/twp/actorswitcher.h b/engines/twp/actorswitcher.h
index 4b56bd4e9f6..3d6a8a2a441 100644
--- a/engines/twp/actorswitcher.h
+++ b/engines/twp/actorswitcher.h
@@ -22,8 +22,8 @@
 #ifndef TWP_ACTORSWITCHER_H
 #define TWP_ACTORSWITCHER_H
 
-#include "twp/scenegraph.h"
 #include "common/func.h"
+#include "twp/scenegraph.h"
 
 namespace Twp {
 
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index e4486c7c18e..b3214e464fd 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -70,13 +70,13 @@ void SoundDefinition::load() {
 
 bool AudioSystem::playing(int id) const {
 	// channel ID ?
-	if (id >= 1 && id <= 32) {
+	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return false;
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
-	for (const auto & _slot : _slots) {
+	for (const auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef->getId() == id) {
 			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
 		}
@@ -86,7 +86,7 @@ bool AudioSystem::playing(int id) const {
 }
 
 bool AudioSystem::playing(Common::SharedPtr<SoundDefinition> soundDef) const {
-	for (const auto & _slot : _slots) {
+	for (const auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef == soundDef) {
 			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
 		}
@@ -98,7 +98,7 @@ void AudioSystem::fadeOut(int id, float fadeTime) {
 	if (fadeTime < 0.01f) {
 		stop(id);
 	} else {
-		for (int i = 0; i < 32; i++) {
+		for (int i = 0; i < NUM_AUDIO_SLOTS; i++) {
 			if (_slots[i].busy && _slots[i].id == id) {
 				_slots[i].fadeOutTimeMs = fadeTime;
 			}
@@ -108,13 +108,13 @@ void AudioSystem::fadeOut(int id, float fadeTime) {
 
 void AudioSystem::stop(int id) {
 	// channel ID ?
-	if (id >= 1 && id <= 32) {
+	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return;
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef->getId() == id) {
 			_slot.loopTimes = 0;
 			g_engine->_mixer->stopHandle(_slot.handle);
@@ -128,7 +128,7 @@ void AudioSystem::setMasterVolume(float vol) {
 	_masterVolume = Twp::clamp(vol, 0.f, 1.f);
 
 	// update sounds
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		if (_slot.busy && g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
 			g_engine->_mixer->setChannelVolume(_slot.handle, _slot.volume * _masterVolume);
 		}
@@ -183,13 +183,13 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 
 void AudioSystem::setVolume(int id, float vol) {
 	// channel ID ?
-	if (id >= 1 && id <= 32) {
+	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return;
 		id = g_engine->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID or sound ID ?
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		if (_slot.busy && ((_slot.sndDef->getId() == id) || (g_engine->_mixer->getSoundID(_slot.handle) == id))) {
 			_slot.volume = vol;
 			updateVolume(&_slot);
@@ -198,10 +198,10 @@ void AudioSystem::setVolume(int id, float vol) {
 }
 
 void AudioSystem::update(float) {
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		if (_slot.busy && !g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
-			if((_slot.loopTimes == -1) || _slot.loopTimes > 0) {
-				if(_slot.loopTimes != -1) {
+			if ((_slot.loopTimes == -1) || _slot.loopTimes > 0) {
+				if (_slot.loopTimes != -1) {
 					_slot.loopTimes--;
 				}
 				Audio::SeekableAudioStream *audioStream;
@@ -212,7 +212,7 @@ void AudioSystem::update(float) {
 				} else if (name.hasSuffixIgnoreCase(".wav")) {
 					audioStream = Audio::makeWAVStream(&_slot.stream, DisposeAfterUse::NO);
 				} else {
-					error("Unexpected audio format: %s", name.c_str());
+					error("AudioSystem::update(): Unexpected audio format: %s", name.c_str());
 				}
 				g_engine->_mixer->playStream(_slot.soundType, &_slot.handle, audioStream, _slot.id, _slot.volume);
 			} else {
@@ -221,7 +221,7 @@ void AudioSystem::update(float) {
 		}
 	}
 	// sound definition ID or sound ID ?
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		if (_slot.busy) {
 			updateVolume(&_slot);
 		}
@@ -229,7 +229,7 @@ void AudioSystem::update(float) {
 }
 
 AudioSlot *AudioSystem::getFreeSlot() {
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		AudioSlot *slot = &_slot;
 		if (!slot->busy || !g_engine->_mixer->isSoundHandleActive(slot->handle)) {
 			slot->busy = false;
@@ -255,7 +255,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 	} else {
 		error("Unexpected audio format: %s", name.c_str());
 	}
-	if(!audioStream)
+	if (!audioStream)
 		error("Failed to load audio: %s", name.c_str());
 
 	int id = newSoundId();
@@ -276,7 +276,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 }
 
 int AudioSystem::getElapsed(int id) const {
-	for (const auto & _slot : _slots) {
+	for (const auto &_slot : _slots) {
 		if (_slot.id == id) {
 			Audio::Timestamp t = g_engine->_mixer->getElapsedTime(_slot.handle);
 			return t.msecs();
@@ -286,7 +286,7 @@ int AudioSystem::getElapsed(int id) const {
 }
 
 int AudioSystem::getDuration(int id) const {
-	for (const auto & _slot : _slots) {
+	for (const auto &_slot : _slots) {
 		if (_slot.id == id) {
 			return _slot.total;
 		}
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index 41d5ce9f9d4..b1dd4de42eb 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -29,11 +29,16 @@
 #include "twp/ggpack.h"
 
 namespace Audio {
+
 class SeekableAudioStream;
 }
 
 namespace Twp {
 
+enum {
+	NUM_AUDIO_SLOTS = 32
+};
+
 class AudioChannel;
 class SoundDefinition;
 
@@ -106,7 +111,7 @@ public:
 	void update(float elapsed);
 
 	Common::Array<Common::SharedPtr<SoundDefinition> > _soundDefs;
-	AudioSlot _slots[32];
+	AudioSlot _slots[NUM_AUDIO_SLOTS];
 	Common::SharedPtr<SoundDefinition> _soundHover; // not used yet, should be used in the GUI
 
 private:
diff --git a/engines/twp/configure.engine b/engines/twp/configure.engine
index b23be5a4c60..78d5384d468 100644
--- a/engines/twp/configure.engine
+++ b/engines/twp/configure.engine
@@ -1,3 +1,3 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine twp "Thimbleweed Park" yes "" "" "16bit highres vorbis png imgui opengl_game_shaders"
+add_engine twp "Thimbleweed Park" yes "" "" "16bit highres vorbis png opengl_game_shaders"
diff --git a/engines/twp/credits.pl b/engines/twp/credits.pl
index 0ed5006e0c7..f0cd477b9dd 100644
--- a/engines/twp/credits.pl
+++ b/engines/twp/credits.pl
@@ -1,3 +1,3 @@
-begin_section("Twp");
-	add_person("Name 1", "Handle 1", "");
+begin_section("Thimbleweed Park");
+	add_person("Valéry Sablonnière", "Scemino", "");
 end_section();
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index b58a305b984..5b00fc352d4 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -1,23 +1,23 @@
 /* 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/>.
-*
-*/
+ *
+ * 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 "twp/debugtools.h"
 #include "imgui/imgui.h"
@@ -37,7 +37,7 @@ static struct {
 	bool _showResources = false;
 	bool _showScenegraph = false;
 	bool _showActor = false;
-	Node* _node = nullptr;
+	Node *_node = nullptr;
 	ImGuiTextFilter _objFilter;
 	ImGuiTextFilter _actorFilter;
 	int _fadeEffect = 0;
@@ -68,7 +68,7 @@ static void drawThreads() {
 			ImGui::TableSetupColumn("Line");
 			ImGui::TableHeadersRow();
 
-			if(g_engine->_cutscene) {
+			if (g_engine->_cutscene) {
 				Common::SharedPtr<ThreadBase> thread(g_engine->_cutscene);
 				SQStackInfos infos;
 				sq_stackinfos(thread->getThread(), 0, &infos);
@@ -179,7 +179,7 @@ static void drawActors() {
 	ImGui::Begin("Actors", &state._showStack);
 	state._actorFilter.Draw();
 	ImGui::BeginChild("Actor_List");
-	for(auto& actor : g_engine->_actors) {
+	for (auto &actor : g_engine->_actors) {
 		bool selected = actor->getId() == state._selectedActor;
 		Common::String key(actor->_key);
 		if (state._actorFilter.PassFilter(actor->_key.c_str())) {
@@ -290,7 +290,7 @@ static void drawAudio() {
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
 	ImGui::Begin("Sounds", &state._showAudio);
-	ImGui::Text("# sounds: %d/32", count);
+	ImGui::Text("# sounds: %d/%d", count, NUM_AUDIO_SLOTS);
 	ImGui::Separator();
 
 	if (ImGui::BeginTable("Threads", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
@@ -303,7 +303,7 @@ static void drawAudio() {
 		ImGui::TableSetupColumn("Pan");
 		ImGui::TableHeadersRow();
 
-		for (int i = 0; i < 32; i++) {
+		for (int i = 0; i < NUM_AUDIO_SLOTS; i++) {
 			auto &sound = g_engine->_audio._slots[i];
 			ImGui::TableNextRow();
 			ImGui::TableNextColumn();
@@ -380,16 +380,16 @@ static void drawGeneral() {
 
 	ImGui::Separator();
 	bool isSwitcherOn = g_engine->_actorSwitcher._mode == asOn;
-	if(ImGui::Checkbox("Switcher ON", &isSwitcherOn)) {
-		if(isSwitcherOn) {
+	if (ImGui::Checkbox("Switcher ON", &isSwitcherOn)) {
+		if (isSwitcherOn) {
 			g_engine->_actorSwitcher._mode |= asOn;
 		} else {
 			g_engine->_actorSwitcher._mode &= ~asOn;
 		}
 	}
 	bool isTemporaryUnselectable = g_engine->_actorSwitcher._mode & asTemporaryUnselectable;
-	if(ImGui::Checkbox("Switcher Temp. Unselectable", &isTemporaryUnselectable)) {
-		if(isTemporaryUnselectable) {
+	if (ImGui::Checkbox("Switcher Temp. Unselectable", &isTemporaryUnselectable)) {
+		if (isTemporaryUnselectable) {
 			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
 		} else {
 			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
@@ -525,27 +525,27 @@ static void drawGeneral() {
 	ImGui::End();
 }
 
-static void drawNode(Node* node) {
+static void drawNode(Node *node) {
 	auto children = node->getChildren();
 	bool selected = state._node == node;
-	if(children.empty()) {
-		if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+	if (children.empty()) {
+		if (ImGui::Selectable(node->getName().c_str(), &selected)) {
 			state._node = node;
 		}
 	} else {
 		ImGui::PushID(node->getName().c_str());
-		if(ImGui::TreeNode("")) {
+		if (ImGui::TreeNode("")) {
 			ImGui::SameLine();
-			if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+			if (ImGui::Selectable(node->getName().c_str(), &selected)) {
 				state._node = node;
 			}
-			for(auto& child : children) {
+			for (auto &child : children) {
 				drawNode(child);
 			}
 			ImGui::TreePop();
 		} else {
 			ImGui::SameLine();
-			if(ImGui::Selectable(node->getName().c_str(), &selected)) {
+			if (ImGui::Selectable(node->getName().c_str(), &selected)) {
 				state._node = node;
 			}
 		}
@@ -563,23 +563,23 @@ static void drawScenegraph() {
 	ImGui::End();
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
-	if(state._node != nullptr) {
+	if (state._node != nullptr) {
 		ImGui::Begin("Node");
 		state._node->isVisible();
 		bool visible = state._node->isVisible();
-		if(ImGui::Checkbox(state._node->getName().c_str(), &visible)) {
+		if (ImGui::Checkbox(state._node->getName().c_str(), &visible)) {
 			state._node->setVisible(visible);
 		}
 		int zsort = state._node->getZSort();
-		if(ImGui::DragInt("Z-Sort", &zsort)) {
+		if (ImGui::DragInt("Z-Sort", &zsort)) {
 			state._node->setZSort(zsort);
 		}
 		Math::Vector2d pos = state._node->getPos();
-		if(ImGui::DragFloat2("Pos", pos.getData())) {
+		if (ImGui::DragFloat2("Pos", pos.getData())) {
 			state._node->setPos(pos);
 		}
 		Math::Vector2d offset = state._node->getOffset();
-		if(ImGui::DragFloat2("Offset", offset.getData())) {
+		if (ImGui::DragFloat2("Offset", offset.getData())) {
 			state._node->setOffset(offset);
 		}
 		ImGui::End();
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index 6ddf43d41d9..f5bebe1ff12 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -27,17 +27,17 @@
 namespace Twp {
 
 enum TwpDebugChannels {
-	kDebugText	     	= 1 << 0,
-	kDebugGGPack     	= 1 << 1,
-	kDebugRes     		= 1 << 2,
-	kDebugDialog 		= 1 << 3,
-	kDebugGenScript 	= 1 << 4,
-	kDebugObjScript 	= 1 << 5,
-	kDebugSysScript 	= 1 << 6,
-	kDebugRoomScript	= 1 << 7,
-	kDebugActScript		= 1 << 8,
-	kDebugSndScript		= 1 << 9,
-	kDebugGame			= 1 << 10,
+	kDebugText = 1,
+	kDebugGGPack,
+	kDebugRes,
+	kDebugDialog,
+	kDebugGenScript,
+	kDebugObjScript,
+	kDebugSysScript,
+	kDebugRoomScript,
+	kDebugActScript,
+	kDebugSndScript,
+	kDebugGame,
 };
 
 extern const PlainGameDescriptor twpGames[];
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 8501f980dbe..74c723e1807 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -19,8 +19,6 @@
  *
  */
 
-#define FORBIDDEN_SYMBOL_ALLOW_ALL
-
 #include "twp/graph.h"
 
 namespace Twp {
diff --git a/engines/twp/prefs.cpp b/engines/twp/prefs.cpp
index 22590161236..4e4e11368a9 100644
--- a/engines/twp/prefs.cpp
+++ b/engines/twp/prefs.cpp
@@ -94,7 +94,8 @@ void Preferences::savePrefs() {
 }
 
 Common::String Preferences::getKey(const Common::String &path) {
-	Common::String t = Twp::replace(path, "_en", "_" + ConfMan.get("language"));
+	Common::String t(path);
+	replace(t, "_en", "_" + ConfMan.get("language"));
 	return t;
 }
 
diff --git a/engines/twp/sqgame.h b/engines/twp/sqgame.h
index ff6d1105883..231a6c3fd8e 100644
--- a/engines/twp/sqgame.h
+++ b/engines/twp/sqgame.h
@@ -22,12 +22,11 @@
 #ifndef TWP_SQGAME_H
 #define TWP_SQGAME_H
 
-#include <stddef.h>
 #include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
-void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck = 0, const SQChar *typemask = NULL);
+void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger nparamscheck = 0, const SQChar *typemask = nullptr);
 void sqgame_register_constants(HSQUIRRELVM v);
 void sqgame_register_syslib(HSQUIRRELVM v);
 void sqgame_register_objlib(HSQUIRRELVM v);
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 8b0e210ba79..85b7a42f9b5 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -43,7 +43,7 @@ Common::String TextDb::getText(int id) {
 		if (result.hasSuffix("#M") || result.hasSuffix("#F"))
 			result = result.substr(0, result.size() - 2);
 		// replace \" by ";
-		result = Twp::replace(result, "\\\"", "\"");
+		replace(result, "\\\"", "\"");
 	} else {
 		result = Common::String::format("Text %d not found", id);
 		error("Text %d not found", id);
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 62a68addadd..47b75c485ac 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -166,22 +166,6 @@ Common::String join(const Common::Array<Common::String> &array, const Common::St
 	return result;
 }
 
-Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by) {
-	Common::String result;
-	uint i = 0;
-	uint whatSize = what.size();
-	while (true) {
-		uint j = s.find(what, i);
-		if (j == Common::String::npos)
-			break;
-		result += s.substr(i, j - i);
-		result += by;
-		i = j + whatSize;
-	}
-	result += s.substr(i);
-	return result;
-}
-
 Common::String remove(const Common::String &txt, char startC, char endC) {
 	if ((txt.size() > 0) && txt[0] == startC) {
 		uint32 i = txt.find(endC);


Commit: 2423ba3ed6f35d07070835c361700c3f6620bd4c
    https://github.com/scummvm/scummvm/commit/2423ba3ed6f35d07070835c361700c3f6620bd4c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Move btea to common

Changed paths:
  A common/btea.cpp
  A common/btea.h
  R engines/twp/btea.cpp
  R engines/twp/btea.h
    common/module.mk
    engines/twp/module.mk
    engines/twp/savegame.cpp


diff --git a/engines/twp/btea.cpp b/common/btea.cpp
similarity index 95%
rename from engines/twp/btea.cpp
rename to common/btea.cpp
index 3d3937c315d..63ff48996d2 100644
--- a/engines/twp/btea.cpp
+++ b/common/btea.cpp
@@ -19,9 +19,9 @@
  *
  */
 
-#include "twp/btea.h"
+#include "common/btea.h"
 
-namespace Twp {
+namespace Common {
 
 #define DELTA 0x9e3779b9
 #define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
@@ -37,7 +37,7 @@ void BTEACrypto::decrypt(uint32 *v, int n, const uint32 *key) {
 // This method comes from https://en.wikipedia.org/wiki/XXTEA
 void BTEACrypto::btea(uint32 *v, int n, const uint32 *key) {
 	uint32 y, z, sum;
-	unsigned p, rounds, e;
+	unsigned int p, rounds, e;
 	if (n > 1) { /* Coding Part */
 		rounds = 6 + 52 / n;
 		sum = 0;
@@ -69,4 +69,4 @@ void BTEACrypto::btea(uint32 *v, int n, const uint32 *key) {
 		} while (--rounds);
 	}
 }
-} // namespace Twp
+} // namespace Common
diff --git a/engines/twp/btea.h b/common/btea.h
similarity index 50%
rename from engines/twp/btea.h
rename to common/btea.h
index ef9d3018b1f..a5b5d842853 100644
--- a/engines/twp/btea.h
+++ b/common/btea.h
@@ -19,22 +19,40 @@
  *
  */
 
-#ifndef TWP_BTEA_H
-#define TWP_BTEA_H
+#ifndef COMMON_BTEA_H
+#define COMMON_BTEA_H
 
 #include "common/system.h"
 
-namespace Twp {
+namespace Common {
 
+/**
+ * Corrected Block TEA (aka XXTEA) class for ScummVM.
+ *
+ * In cryptography, Corrected Block TEA (often referred to as XXTEA)
+ * is a block cipher designed to correct weaknesses in the original Block TEA.
+ */
 class BTEACrypto {
 public:
-  static void encrypt(uint32 *v, int n, const uint32 *k);
-  static void decrypt(uint32 *v, int n, const uint32 *k);
+	/**
+	 * Encrypt data with a specified key.
+	 * @param[in,out] data    the data to encrypt
+	 * @param[in] n	          the size of the data
+	 * @param[in] key         the key to use to encrypt the data
+	 */
+	static void encrypt(uint32 *data, int n, const uint32 *key);
+	/**
+	 * Decrypt data encrypted before with btea with a specified key.
+	 * @param[in,out] data    the data to decrypt
+	 * @param[in] n	          the size of the data
+	 * @param[in] key         the key to use to decrypt the data
+	 */
+	static void decrypt(uint32 *data, int n, const uint32 *key);
 
 private:
-  static void btea(uint32 *v, int n, const uint32 *k);
+	static void btea(uint32 *v, int n, const uint32 *k);
 };
 
-} // End of namespace Twp
+} // End of namespace Common
 
-#endif // TWP_BTEA_H
+#endif // COMMON_BTEA_H
diff --git a/common/module.mk b/common/module.mk
index 52e1689f58a..90b48297c3f 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -2,6 +2,7 @@ MODULE := common
 
 MODULE_OBJS := \
 	archive.o \
+	btea.o \
 	concatstream.o \
 	config-manager.o \
 	coroutines.o \
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index d93f97f080d..1377fe784c5 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -1,50 +1,49 @@
 MODULE := engines/twp
 
 MODULE_OBJS = \
-	twp.o \
+	actorlib.o \
+	actorswitcher.o \
+	audio.o \
+	callback.o \
+	camera.o \
 	console.o \
-	metaengine.o \
-	vm.o \
-	ggpack.o \
+	dialog.o \
+	dialogs.o \
+	enginedialogtarget.o \
+	font.o \
+	genlib.o \
 	gfx.o \
-	resmanager.o \
-	spritesheet.o \
-	room.o \
+	ggpack.o \
+	graph.o \
+	hud.o \
+	ids.o \
 	lighting.o \
-	font.o \
-	sqgame.o \
-	syslib.o \
+	lip.o \
+	metaengine.o \
+	motor.o \
+	object.o \
 	objlib.o \
-	genlib.o \
-	squtil.o \
-	thread.o \
+	prefs.o \
+	resmanager.o \
 	rectf.o \
-	scenegraph.o \
-	object.o \
-	ids.o \
-	camera.o \
-	actorlib.o \
+	room.o \
 	roomlib.o \
+	scenegraph.o \
+	shaders.o \
 	soundlib.o \
-	prefs.o \
+	savegame.o \
+	spritesheet.o \
+	sqgame.o \
+	squtil.o \
+	syslib.o \
+	thread.o \
+	time.o \
 	tsv.o \
+	twp.o \
 	util.o \
-	motor.o \
-	yack.o \
-	dialog.o \
-	shaders.o \
-	hud.o \
-	lip.o \
-	callback.o \
-	graph.o \
+	vm.o \
 	walkboxnode.o \
-	actorswitcher.o \
-	enginedialogtarget.o \
-	audio.o \
-	savegame.o \
-	btea.o \
-	time.o \
-	dialogs.o \
+	yack.o \
 	squirrel/sqapi.o \
 	squirrel/sqbaselib.o \
 	squirrel/sqfuncstate.o \
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 4b3b964495e..75401f646d2 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -19,12 +19,12 @@
  *
  */
 
+#include "common/btea.h"
+#include "common/savefile.h"
 #include "twp/ggpack.h"
 #include "twp/savegame.h"
 #include "twp/squtil.h"
-#include "twp/btea.h"
 #include "twp/time.h"
-#include "common/savefile.h"
 
 namespace Twp {
 
@@ -424,7 +424,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame) {
 	Common::Array<byte> data(stream->size());
 	stream->read(data.data(), data.size());
-	BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
+	Common::BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
 	savegame.hashData = *(int32_t *)(&data[data.size() - 16]);
 	savegame.time = *(int32_t *)&data[data.size() - 12];
 	int32_t hashCheck = computeHash(data.data(), data.size() - 16);
@@ -1006,7 +1006,7 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	memset(&p[2], marker, 8);
 
 	// then encode data
-	BTEACrypto::encrypt((uint32 *)buffer.data(), buffer.size() / 4, savegameKey);
+	Common::BTEACrypto::encrypt((uint32 *)buffer.data(), buffer.size() / 4, savegameKey);
 
 	// and write data
 	ws->write(buffer.data(), buffer.size());


Commit: 6d3f6555fbc7c2ab5f1c36f4caf663a7c1d2d609
    https://github.com/scummvm/scummvm/commit/6d3f6555fbc7c2ab5f1c36f4caf663a7c1d2d609
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove std from ClipperLib

Changed paths:
  R engines/twp/clipper/CMakeLists.txt
    engines/twp/clipper/clipper.cpp
    engines/twp/clipper/clipper.hpp
    engines/twp/room.cpp


diff --git a/engines/twp/clipper/CMakeLists.txt b/engines/twp/clipper/CMakeLists.txt
deleted file mode 100644
index 3250c9c48e3..00000000000
--- a/engines/twp/clipper/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-cmake_minimum_required(VERSION 3.5)
-
-project(clipper CXX)
-
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-
-set(CLIPPER_SRC clipper.cpp)
-add_library(clipper STATIC ${CLIPPER_SRC})
diff --git a/engines/twp/clipper/clipper.cpp b/engines/twp/clipper/clipper.cpp
index 89fbadba9f9..fffc3bc8644 100644
--- a/engines/twp/clipper/clipper.cpp
+++ b/engines/twp/clipper/clipper.cpp
@@ -1,53 +1,44 @@
 /*******************************************************************************
-*                                                                              *
-* Author    :  Angus Johnson                                                   *
-* Version   :  6.4.2                                                           *
-* Date      :  27 February 2017                                                *
-* Website   :  http://www.angusj.com                                           *
-* Copyright :  Angus Johnson 2010-2017                                         *
-*                                                                              *
-* License:                                                                     *
-* Use, modification & distribution is subject to Boost Software License Ver 1. *
-* http://www.boost.org/LICENSE_1_0.txt                                         *
-*                                                                              *
-* Attributions:                                                                *
-* The code in this library is an extension of Bala Vatti's clipping algorithm: *
-* "A generic solution to polygon clipping"                                     *
-* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
-* http://portal.acm.org/citation.cfm?id=129906                                 *
-*                                                                              *
-* Computer graphics and geometric modeling: implementation and algorithms      *
-* By Max K. Agoston                                                            *
-* Springer; 1 edition (January 4, 2005)                                        *
-* http://books.google.com/books?q=vatti+clipping+agoston                       *
-*                                                                              *
-* See also:                                                                    *
-* "Polygon Offsetting by Computing Winding Numbers"                            *
-* Paper no. DETC2005-85513 pp. 565-575                                         *
-* ASME 2005 International Design Engineering Technical Conferences             *
-* and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
-* September 24-28, 2005 , Long Beach, California, USA                          *
-* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
-*                                                                              *
-*******************************************************************************/
+ *                                                                              *
+ * Author    :  Angus Johnson                                                   *
+ * Version   :  6.4.2                                                           *
+ * Date      :  27 February 2017                                                *
+ * Website   :  http://www.angusj.com                                           *
+ * Copyright :  Angus Johnson 2010-2017                                         *
+ *                                                                              *
+ * License:                                                                     *
+ * Use, modification & distribution is subject to Boost Software License Ver 1. *
+ * http://www.boost.org/LICENSE_1_0.txt                                         *
+ *                                                                              *
+ * Attributions:                                                                *
+ * The code in this library is an extension of Bala Vatti's clipping algorithm: *
+ * "A generic solution to polygon clipping"                                     *
+ * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
+ * http://portal.acm.org/citation.cfm?id=129906                                 *
+ *                                                                              *
+ * Computer graphics and geometric modeling: implementation and algorithms      *
+ * By Max K. Agoston                                                            *
+ * Springer; 1 edition (January 4, 2005)                                        *
+ * http://books.google.com/books?q=vatti+clipping+agoston                       *
+ *                                                                              *
+ * See also:                                                                    *
+ * "Polygon Offsetting by Computing Winding Numbers"                            *
+ * Paper no. DETC2005-85513 pp. 565-575                                         *
+ * ASME 2005 International Design Engineering Technical Conferences             *
+ * and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
+ * September 24-28, 2005 , Long Beach, California, USA                          *
+ * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
+ *                                                                              *
+ *******************************************************************************/
 
 /*******************************************************************************
-*                                                                              *
-* This is a translation of the Delphi Clipper library and the naming style     *
-* used has retained a Delphi flavour.                                          *
-*                                                                              *
-*******************************************************************************/
+ *                                                                              *
+ * This is a translation of the Delphi Clipper library and the naming style     *
+ * used has retained a Delphi flavour.                                          *
+ *                                                                              *
+ *******************************************************************************/
 
 #include "clipper.hpp"
-#include <cmath>
-#include <vector>
-#include <algorithm>
-#include <stdexcept>
-#include <cstring>
-#include <cstdlib>
-#include <ostream>
-#include <functional>
-
 #include "common/debug.h"
 
 namespace ClipperLib {
@@ -56,93 +47,94 @@ static double const pi = 3.141592653589793238;
 static double const two_pi = pi * 2;
 static double const def_arc_tolerance = 0.25;
 
-enum Direction { dRightToLeft, dLeftToRight };
+enum Direction { dRightToLeft,
+				 dLeftToRight };
 
-static int const Unassigned = -1;  //edge not currently 'owning' a solution
-static int const Skip = -2;        //edge that would otherwise close a path
+static int const Unassigned = -1; // edge not currently 'owning' a solution
+static int const Skip = -2;       // edge that would otherwise close a path
 
 #define HORIZONTAL (-1.0E+40)
 #define TOLERANCE (1.0e-20)
 #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
 
 struct TEdge {
-  IntPoint Bot;
-  IntPoint Curr; //current (updated for every new scanbeam)
-  IntPoint Top;
-  double Dx;
-  PolyType PolyTyp;
-  EdgeSide Side; //side only refers to current side of solution poly
-  int WindDelta; //1 or -1 depending on winding direction
-  int WindCnt;
-  int WindCnt2; //winding count of the opposite polytype
-  int OutIdx;
-  TEdge *Next;
-  TEdge *Prev;
-  TEdge *NextInLML;
-  TEdge *NextInAEL;
-  TEdge *PrevInAEL;
-  TEdge *NextInSEL;
-  TEdge *PrevInSEL;
+	IntPoint Bot;
+	IntPoint Curr; // current (updated for every new scanbeam)
+	IntPoint Top;
+	double Dx;
+	PolyType PolyTyp;
+	EdgeSide Side; // side only refers to current side of solution poly
+	int WindDelta; // 1 or -1 depending on winding direction
+	int WindCnt;
+	int WindCnt2; // winding count of the opposite polytype
+	int OutIdx;
+	TEdge *Next;
+	TEdge *Prev;
+	TEdge *NextInLML;
+	TEdge *NextInAEL;
+	TEdge *PrevInAEL;
+	TEdge *NextInSEL;
+	TEdge *PrevInSEL;
 };
 
 struct IntersectNode {
-  TEdge *Edge1;
-  TEdge *Edge2;
-  IntPoint Pt;
+	TEdge *Edge1;
+	TEdge *Edge2;
+	IntPoint Pt;
 };
 
 struct LocalMinimum {
-  cInt Y;
-  TEdge *LeftBound;
-  TEdge *RightBound;
+	cInt Y;
+	TEdge *LeftBound;
+	TEdge *RightBound;
 };
 
 struct OutPt;
 
-//OutRec: contains a path in the clipping solution. Edges in the AEL will
-//carry a pointer to an OutRec when they are part of the clipping solution.
+// OutRec: contains a path in the clipping solution. Edges in the AEL will
+// carry a pointer to an OutRec when they are part of the clipping solution.
 struct OutRec {
-  int Idx;
-  bool IsHole;
-  bool IsOpen;
-  OutRec *FirstLeft;  //see comments in clipper.pas
-  PolyNode *PolyNd;
-  OutPt *Pts;
-  OutPt *BottomPt;
+	int Idx;
+	bool IsHole;
+	bool IsOpen;
+	OutRec *FirstLeft; // see comments in clipper.pas
+	PolyNode *PolyNd;
+	OutPt *Pts;
+	OutPt *BottomPt;
 };
 
 struct OutPt {
-  int Idx;
-  IntPoint Pt;
-  OutPt *Next;
-  OutPt *Prev;
+	int Idx;
+	IntPoint Pt;
+	OutPt *Next;
+	OutPt *Prev;
 };
 
 struct Join {
-  OutPt *OutPt1;
-  OutPt *OutPt2;
-  IntPoint OffPt;
+	OutPt *OutPt1;
+	OutPt *OutPt2;
+	IntPoint OffPt;
 };
 
 struct LocMinSorter {
-  inline bool operator()(const LocalMinimum &locMin1, const LocalMinimum &locMin2) {
-    return locMin2.Y < locMin1.Y;
-  }
+	inline bool operator()(const LocalMinimum &locMin1, const LocalMinimum &locMin2) {
+		return locMin2.Y < locMin1.Y;
+	}
 };
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 inline cInt Round(double val) {
-  if ((val < 0))
-    return static_cast<cInt>(val - 0.5);
-  else
-    return static_cast<cInt>(val + 0.5);
+	if ((val < 0))
+		return static_cast<cInt>(val - 0.5);
+	else
+		return static_cast<cInt>(val + 0.5);
 }
 //------------------------------------------------------------------------------
 
 inline cInt Abs(cInt val) {
-  return val < 0 ? -val : val;
+	return val < 0 ? -val : val;
 }
 
 //------------------------------------------------------------------------------
@@ -150,27 +142,27 @@ inline cInt Abs(cInt val) {
 //------------------------------------------------------------------------------
 
 void PolyTree::Clear() {
-  for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
-    delete AllNodes[i];
-  AllNodes.resize(0);
-  Childs.resize(0);
+	for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
+		delete AllNodes[i];
+	AllNodes.resize(0);
+	Childs.resize(0);
 }
 //------------------------------------------------------------------------------
 
 PolyNode *PolyTree::GetFirst() const {
-  if (!Childs.empty())
-    return Childs[0];
-  else
-    return 0;
+	if (!Childs.empty())
+		return Childs[0];
+	else
+		return 0;
 }
 //------------------------------------------------------------------------------
 
 int PolyTree::Total() const {
-  int result = (int) AllNodes.size();
-  //with negative offsets, ignore the hidden outer polygon ...
-  if (result > 0 && Childs[0] != AllNodes[0])
-    result--;
-  return result;
+	int result = (int)AllNodes.size();
+	// with negative offsets, ignore the hidden outer polygon ...
+	if (result > 0 && Childs[0] != AllNodes[0])
+		result--;
+	return result;
 }
 
 //------------------------------------------------------------------------------
@@ -182,49 +174,49 @@ PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {
 //------------------------------------------------------------------------------
 
 int PolyNode::ChildCount() const {
-  return (int) Childs.size();
+	return (int)Childs.size();
 }
 //------------------------------------------------------------------------------
 
 void PolyNode::AddChild(PolyNode &child) {
-  unsigned cnt = (unsigned) Childs.size();
-  Childs.push_back(&child);
-  child.Parent = this;
-  child.Index = cnt;
+	unsigned cnt = (unsigned)Childs.size();
+	Childs.push_back(&child);
+	child.Parent = this;
+	child.Index = cnt;
 }
 //------------------------------------------------------------------------------
 
 PolyNode *PolyNode::GetNext() const {
-  if (!Childs.empty())
-    return Childs[0];
-  else
-    return GetNextSiblingUp();
+	if (!Childs.empty())
+		return Childs[0];
+	else
+		return GetNextSiblingUp();
 }
 //------------------------------------------------------------------------------
 
 PolyNode *PolyNode::GetNextSiblingUp() const {
-  if (!Parent) //protects against PolyTree.GetNextSiblingUp()
-    return 0;
-  else if (Index == Parent->Childs.size() - 1)
-    return Parent->GetNextSiblingUp();
-  else
-    return Parent->Childs[Index + 1];
+	if (!Parent) // protects against PolyTree.GetNextSiblingUp()
+		return 0;
+	else if (Index == Parent->Childs.size() - 1)
+		return Parent->GetNextSiblingUp();
+	else
+		return Parent->Childs[Index + 1];
 }
 //------------------------------------------------------------------------------
 
 bool PolyNode::IsHole() const {
-  bool result = true;
-  PolyNode *node = Parent;
-  while (node) {
-    result = !result;
-    node = node->Parent;
-  }
-  return result;
+	bool result = true;
+	PolyNode *node = Parent;
+	while (node) {
+		result = !result;
+		node = node->Parent;
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 bool PolyNode::IsOpen() const {
-  return m_IsOpen;
+	return m_IsOpen;
 }
 //------------------------------------------------------------------------------
 
@@ -240,126 +232,125 @@ bool PolyNode::IsOpen() const {
 
 class Int128 {
 public:
-  ulong64 lo;
-  long64 hi;
-
-  Int128(long64 _lo = 0) {
-    lo = (ulong64) _lo;
-    if (_lo < 0)
-      hi = -1;
-    else
-      hi = 0;
-  }
-
-  Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
-
-  Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
-
-  Int128 &operator=(const long64 &val) {
-    lo = (ulong64) val;
-    if (val < 0)
-      hi = -1;
-    else
-      hi = 0;
-    return *this;
-  }
-
-  bool operator==(const Int128 &val) const { return (hi == val.hi && lo == val.lo); }
-
-  bool operator!=(const Int128 &val) const { return !(*this == val); }
-
-  bool operator>(const Int128 &val) const {
-    if (hi != val.hi)
-      return hi > val.hi;
-    else
-      return lo > val.lo;
-  }
-
-  bool operator<(const Int128 &val) const {
-    if (hi != val.hi)
-      return hi < val.hi;
-    else
-      return lo < val.lo;
-  }
-
-  bool operator>=(const Int128 &val) const { return !(*this < val); }
-
-  bool operator<=(const Int128 &val) const { return !(*this > val); }
-
-  Int128 &operator+=(const Int128 &rhs) {
-    hi += rhs.hi;
-    lo += rhs.lo;
-    if (lo < rhs.lo)
-      hi++;
-    return *this;
-  }
-
-  Int128 operator+(const Int128 &rhs) const {
-    Int128 result(*this);
-    result += rhs;
-    return result;
-  }
-
-  Int128 &operator-=(const Int128 &rhs) {
-    *this += -rhs;
-    return *this;
-  }
-
-  Int128 operator-(const Int128 &rhs) const {
-    Int128 result(*this);
-    result -= rhs;
-    return result;
-  }
-
-  Int128 operator-() const //unary negation
-  {
-    if (lo == 0)
-      return Int128(-hi, 0);
-    else
-      return Int128(~hi, ~lo + 1);
-  }
-
-  operator double() const {
-    const double shift64 = 18446744073709551616.0; //2^64
-    if (hi < 0) {
-      if (lo == 0)
-        return (double) hi * shift64;
-      else
-        return -(double) (~lo + ~hi * shift64);
-    } else
-      return (double) (lo + hi * shift64);
-  }
-
+	ulong64 lo;
+	long64 hi;
+
+	Int128(long64 _lo = 0) {
+		lo = (ulong64)_lo;
+		if (_lo < 0)
+			hi = -1;
+		else
+			hi = 0;
+	}
+
+	Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
+
+	Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
+
+	Int128 &operator=(const long64 &val) {
+		lo = (ulong64)val;
+		if (val < 0)
+			hi = -1;
+		else
+			hi = 0;
+		return *this;
+	}
+
+	bool operator==(const Int128 &val) const { return (hi == val.hi && lo == val.lo); }
+
+	bool operator!=(const Int128 &val) const { return !(*this == val); }
+
+	bool operator>(const Int128 &val) const {
+		if (hi != val.hi)
+			return hi > val.hi;
+		else
+			return lo > val.lo;
+	}
+
+	bool operator<(const Int128 &val) const {
+		if (hi != val.hi)
+			return hi < val.hi;
+		else
+			return lo < val.lo;
+	}
+
+	bool operator>=(const Int128 &val) const { return !(*this < val); }
+
+	bool operator<=(const Int128 &val) const { return !(*this > val); }
+
+	Int128 &operator+=(const Int128 &rhs) {
+		hi += rhs.hi;
+		lo += rhs.lo;
+		if (lo < rhs.lo)
+			hi++;
+		return *this;
+	}
+
+	Int128 operator+(const Int128 &rhs) const {
+		Int128 result(*this);
+		result += rhs;
+		return result;
+	}
+
+	Int128 &operator-=(const Int128 &rhs) {
+		*this += -rhs;
+		return *this;
+	}
+
+	Int128 operator-(const Int128 &rhs) const {
+		Int128 result(*this);
+		result -= rhs;
+		return result;
+	}
+
+	Int128 operator-() const // unary negation
+	{
+		if (lo == 0)
+			return Int128(-hi, 0);
+		else
+			return Int128(~hi, ~lo + 1);
+	}
+
+	operator double() const {
+		const double shift64 = 18446744073709551616.0; // 2^64
+		if (hi < 0) {
+			if (lo == 0)
+				return (double)hi * shift64;
+			else
+				return -(double)(~lo + ~hi * shift64);
+		} else
+			return (double)(lo + hi * shift64);
+	}
 };
 //------------------------------------------------------------------------------
 
 Int128 Int128Mul(long64 lhs, long64 rhs) {
-  bool negate = (lhs < 0) != (rhs < 0);
-
-  if (lhs < 0)
-    lhs = -lhs;
-  ulong64 int1Hi = ulong64(lhs) >> 32;
-  ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
-
-  if (rhs < 0)
-    rhs = -rhs;
-  ulong64 int2Hi = ulong64(rhs) >> 32;
-  ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
-
-  //nb: see comments in clipper.pas
-  ulong64 a = int1Hi * int2Hi;
-  ulong64 b = int1Lo * int2Lo;
-  ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
-
-  Int128 tmp;
-  tmp.hi = long64(a + (c >> 32));
-  tmp.lo = long64(c << 32);
-  tmp.lo += long64(b);
-  if (tmp.lo < b)
-    tmp.hi++;
-  if (negate)
-    tmp = -tmp;
-  return tmp;
+	bool negate = (lhs < 0) != (rhs < 0);
+
+	if (lhs < 0)
+		lhs = -lhs;
+	ulong64 int1Hi = ulong64(lhs) >> 32;
+	ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
+
+	if (rhs < 0)
+		rhs = -rhs;
+	ulong64 int2Hi = ulong64(rhs) >> 32;
+	ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
+
+	// nb: see comments in clipper.pas
+	ulong64 a = int1Hi * int2Hi;
+	ulong64 b = int1Lo * int2Lo;
+	ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+	Int128 tmp;
+	tmp.hi = long64(a + (c >> 32));
+	tmp.lo = long64(c << 32);
+	tmp.lo += long64(b);
+	if (tmp.lo < b)
+		tmp.hi++;
+	if (negate)
+		tmp = -tmp;
+	return tmp;
 }
 #endif
 
@@ -368,3162 +359,3193 @@ Int128 Int128Mul(long64 lhs, long64 rhs) {
 //------------------------------------------------------------------------------
 
 bool Orientation(const Path &poly) {
-  return Area(poly) >= 0;
+	return Area(poly) >= 0;
 }
 //------------------------------------------------------------------------------
 
 double Area(const Path &poly) {
-  int size = (int) poly.size();
-  if (size < 3)
-    return 0;
+	int size = (int)poly.size();
+	if (size < 3)
+		return 0;
 
-  double a = 0;
-  for (int i = 0, j = size - 1; i < size; ++i) {
-    a += ((double) poly[j].X + poly[i].X) * ((double) poly[j].Y - poly[i].Y);
-    j = i;
-  }
-  return -a * 0.5;
+	double a = 0;
+	for (int i = 0, j = size - 1; i < size; ++i) {
+		a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
+		j = i;
+	}
+	return -a * 0.5;
 }
 //------------------------------------------------------------------------------
 
 double Area(const OutPt *op) {
-  const OutPt *startOp = op;
-  if (!op)
-    return 0;
-  double a = 0;
-  do {
-    a += (double) (op->Prev->Pt.X + op->Pt.X) * (double) (op->Prev->Pt.Y - op->Pt.Y);
-    op = op->Next;
-  } while (op != startOp);
-  return a * 0.5;
+	const OutPt *startOp = op;
+	if (!op)
+		return 0;
+	double a = 0;
+	do {
+		a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y);
+		op = op->Next;
+	} while (op != startOp);
+	return a * 0.5;
 }
 //------------------------------------------------------------------------------
 
 double Area(const OutRec &outRec) {
-  return Area(outRec.Pts);
+	return Area(outRec.Pts);
 }
 //------------------------------------------------------------------------------
 
 bool PointIsVertex(const IntPoint &Pt, OutPt *pp) {
-  OutPt *pp2 = pp;
-  do {
-    if (pp2->Pt == Pt)
-      return true;
-    pp2 = pp2->Next;
-  } while (pp2 != pp);
-  return false;
+	OutPt *pp2 = pp;
+	do {
+		if (pp2->Pt == Pt)
+			return true;
+		pp2 = pp2->Next;
+	} while (pp2 != pp);
+	return false;
 }
 //------------------------------------------------------------------------------
 
-//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
-//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
 int PointInPolygon(const IntPoint &pt, const Path &path) {
-  //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
-  int result = 0;
-  size_t cnt = path.size();
-  if (cnt < 3)
-    return 0;
-  IntPoint ip = path[0];
-  for (size_t i = 1; i <= cnt; ++i) {
-    IntPoint ipNext = (i == cnt ? path[0] : path[i]);
-    if (ipNext.Y == pt.Y) {
-      if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
-          ((ipNext.X > pt.X) == (ip.X < pt.X))))
-        return -1;
-    }
-    if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
-      if (ip.X >= pt.X) {
-        if (ipNext.X > pt.X)
-          result = 1 - result;
-        else {
-          double d = (double) (ip.X - pt.X) * (ipNext.Y - pt.Y) -
-              (double) (ipNext.X - pt.X) * (ip.Y - pt.Y);
-          if (!d)
-            return -1;
-          if ((d > 0) == (ipNext.Y > ip.Y))
-            result = 1 - result;
-        }
-      } else {
-        if (ipNext.X > pt.X) {
-          double d = (double) (ip.X - pt.X) * (ipNext.Y - pt.Y) -
-              (double) (ipNext.X - pt.X) * (ip.Y - pt.Y);
-          if (!d)
-            return -1;
-          if ((d > 0) == (ipNext.Y > ip.Y))
-            result = 1 - result;
-        }
-      }
-    }
-    ip = ipNext;
-  }
-  return result;
+	// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+	int result = 0;
+	size_t cnt = path.size();
+	if (cnt < 3)
+		return 0;
+	IntPoint ip = path[0];
+	for (size_t i = 1; i <= cnt; ++i) {
+		IntPoint ipNext = (i == cnt ? path[0] : path[i]);
+		if (ipNext.Y == pt.Y) {
+			if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
+									   ((ipNext.X > pt.X) == (ip.X < pt.X))))
+				return -1;
+		}
+		if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
+			if (ip.X >= pt.X) {
+				if (ipNext.X > pt.X)
+					result = 1 - result;
+				else {
+					double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+							   (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+					if (!d)
+						return -1;
+					if ((d > 0) == (ipNext.Y > ip.Y))
+						result = 1 - result;
+				}
+			} else {
+				if (ipNext.X > pt.X) {
+					double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+							   (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+					if (!d)
+						return -1;
+					if ((d > 0) == (ipNext.Y > ip.Y))
+						result = 1 - result;
+				}
+			}
+		}
+		ip = ipNext;
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 int PointInPolygon(const IntPoint &pt, OutPt *op) {
-  //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
-  int result = 0;
-  OutPt *startOp = op;
-  for (;;) {
-    if (op->Next->Pt.Y == pt.Y) {
-      if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y &&
-          ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
-        return -1;
-    }
-    if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
-      if (op->Pt.X >= pt.X) {
-        if (op->Next->Pt.X > pt.X)
-          result = 1 - result;
-        else {
-          double d = (double) (op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
-              (double) (op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
-          if (!d)
-            return -1;
-          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
-            result = 1 - result;
-        }
-      } else {
-        if (op->Next->Pt.X > pt.X) {
-          double d = (double) (op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
-              (double) (op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
-          if (!d)
-            return -1;
-          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
-            result = 1 - result;
-        }
-      }
-    }
-    op = op->Next;
-    if (startOp == op)
-      break;
-  }
-  return result;
+	// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+	int result = 0;
+	OutPt *startOp = op;
+	for (;;) {
+		if (op->Next->Pt.Y == pt.Y) {
+			if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y &&
+											 ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
+				return -1;
+		}
+		if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
+			if (op->Pt.X >= pt.X) {
+				if (op->Next->Pt.X > pt.X)
+					result = 1 - result;
+				else {
+					double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+							   (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+					if (!d)
+						return -1;
+					if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+						result = 1 - result;
+				}
+			} else {
+				if (op->Next->Pt.X > pt.X) {
+					double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+							   (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+					if (!d)
+						return -1;
+					if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+						result = 1 - result;
+				}
+			}
+		}
+		op = op->Next;
+		if (startOp == op)
+			break;
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) {
-  OutPt *op = OutPt1;
-  do {
-    //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
-    int res = PointInPolygon(op->Pt, OutPt2);
-    if (res >= 0)
-      return res > 0;
-    op = op->Next;
-  } while (op != OutPt1);
-  return true;
+	OutPt *op = OutPt1;
+	do {
+		// nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+		int res = PointInPolygon(op->Pt, OutPt2);
+		if (res >= 0)
+			return res > 0;
+		op = op->Next;
+	} while (op != OutPt1);
+	return true;
 }
 //----------------------------------------------------------------------
 
 bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) {
 #ifndef use_int32
-  if (UseFullInt64Range)
-    return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
-        Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
-  else
+	if (UseFullInt64Range)
+		return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
+			   Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
+	else
 #endif
-    return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
-        (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
+		return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
+			   (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
 }
 //------------------------------------------------------------------------------
 
 bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
-                 const IntPoint pt3, bool UseFullInt64Range) {
+				 const IntPoint pt3, bool UseFullInt64Range) {
 #ifndef use_int32
-  if (UseFullInt64Range)
-    return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
-  else
+	if (UseFullInt64Range)
+		return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
+	else
 #endif
-    return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) == (pt1.X - pt2.X) * (pt2.Y - pt3.Y);
+		return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) == (pt1.X - pt2.X) * (pt2.Y - pt3.Y);
 }
 //------------------------------------------------------------------------------
 
 bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
-                 const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) {
+				 const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) {
 #ifndef use_int32
-  if (UseFullInt64Range)
-    return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
-  else
+	if (UseFullInt64Range)
+		return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
+	else
 #endif
-    return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) == (pt1.X - pt2.X) * (pt3.Y - pt4.Y);
+		return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) == (pt1.X - pt2.X) * (pt3.Y - pt4.Y);
 }
 //------------------------------------------------------------------------------
 
 inline bool IsHorizontal(TEdge &e) {
-  return e.Dx == HORIZONTAL;
+	return e.Dx == HORIZONTAL;
 }
 //------------------------------------------------------------------------------
 
 inline double GetDx(const IntPoint pt1, const IntPoint pt2) {
-  return (pt1.Y == pt2.Y) ?
-         HORIZONTAL : (double) (pt2.X - pt1.X) / (pt2.Y - pt1.Y);
+	return (pt1.Y == pt2.Y) ? HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
 }
 //---------------------------------------------------------------------------
 
 inline void SetDx(TEdge &e) {
-  cInt dy = (e.Top.Y - e.Bot.Y);
-  if (dy == 0)
-    e.Dx = HORIZONTAL;
-  else
-    e.Dx = (double) (e.Top.X - e.Bot.X) / dy;
+	cInt dy = (e.Top.Y - e.Bot.Y);
+	if (dy == 0)
+		e.Dx = HORIZONTAL;
+	else
+		e.Dx = (double)(e.Top.X - e.Bot.X) / dy;
 }
 //---------------------------------------------------------------------------
 
 inline void SwapSides(TEdge &Edge1, TEdge &Edge2) {
-  EdgeSide Side = Edge1.Side;
-  Edge1.Side = Edge2.Side;
-  Edge2.Side = Side;
+	EdgeSide Side = Edge1.Side;
+	Edge1.Side = Edge2.Side;
+	Edge2.Side = Side;
 }
 //------------------------------------------------------------------------------
 
 inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) {
-  int OutIdx = Edge1.OutIdx;
-  Edge1.OutIdx = Edge2.OutIdx;
-  Edge2.OutIdx = OutIdx;
+	int OutIdx = Edge1.OutIdx;
+	Edge1.OutIdx = Edge2.OutIdx;
+	Edge2.OutIdx = OutIdx;
 }
 //------------------------------------------------------------------------------
 
 inline cInt TopX(TEdge &edge, const cInt currentY) {
-  return (currentY == edge.Top.Y) ?
-         edge.Top.X : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
+	return (currentY == edge.Top.Y) ? edge.Top.X : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
 }
 //------------------------------------------------------------------------------
 
 void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) {
 #ifdef use_xyz
-  ip.Z = 0;
+	ip.Z = 0;
 #endif
 
-  double b1, b2;
-  if (Edge1.Dx == Edge2.Dx) {
-    ip.Y = Edge1.Curr.Y;
-    ip.X = TopX(Edge1, ip.Y);
-    return;
-  } else if (Edge1.Dx == 0) {
-    ip.X = Edge1.Bot.X;
-    if (IsHorizontal(Edge2))
-      ip.Y = Edge2.Bot.Y;
-    else {
-      b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
-      ip.Y = Round(ip.X / Edge2.Dx + b2);
-    }
-  } else if (Edge2.Dx == 0) {
-    ip.X = Edge2.Bot.X;
-    if (IsHorizontal(Edge1))
-      ip.Y = Edge1.Bot.Y;
-    else {
-      b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
-      ip.Y = Round(ip.X / Edge1.Dx + b1);
-    }
-  } else {
-    b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
-    b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
-    double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
-    ip.Y = Round(q);
-    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
-      ip.X = Round(Edge1.Dx * q + b1);
-    else
-      ip.X = Round(Edge2.Dx * q + b2);
-  }
-
-  if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
-    if (Edge1.Top.Y > Edge2.Top.Y)
-      ip.Y = Edge1.Top.Y;
-    else
-      ip.Y = Edge2.Top.Y;
-    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
-      ip.X = TopX(Edge1, ip.Y);
-    else
-      ip.X = TopX(Edge2, ip.Y);
-  }
-  //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
-  if (ip.Y > Edge1.Curr.Y) {
-    ip.Y = Edge1.Curr.Y;
-    //use the more vertical edge to derive X ...
-    if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
-      ip.X = TopX(Edge2, ip.Y);
-    else
-      ip.X = TopX(Edge1, ip.Y);
-  }
+	double b1, b2;
+	if (Edge1.Dx == Edge2.Dx) {
+		ip.Y = Edge1.Curr.Y;
+		ip.X = TopX(Edge1, ip.Y);
+		return;
+	} else if (Edge1.Dx == 0) {
+		ip.X = Edge1.Bot.X;
+		if (IsHorizontal(Edge2))
+			ip.Y = Edge2.Bot.Y;
+		else {
+			b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
+			ip.Y = Round(ip.X / Edge2.Dx + b2);
+		}
+	} else if (Edge2.Dx == 0) {
+		ip.X = Edge2.Bot.X;
+		if (IsHorizontal(Edge1))
+			ip.Y = Edge1.Bot.Y;
+		else {
+			b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
+			ip.Y = Round(ip.X / Edge1.Dx + b1);
+		}
+	} else {
+		b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
+		b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
+		double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
+		ip.Y = Round(q);
+		if (fabs(Edge1.Dx) < fabs(Edge2.Dx))
+			ip.X = Round(Edge1.Dx * q + b1);
+		else
+			ip.X = Round(Edge2.Dx * q + b2);
+	}
+
+	if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
+		if (Edge1.Top.Y > Edge2.Top.Y)
+			ip.Y = Edge1.Top.Y;
+		else
+			ip.Y = Edge2.Top.Y;
+		if (fabs(Edge1.Dx) < fabs(Edge2.Dx))
+			ip.X = TopX(Edge1, ip.Y);
+		else
+			ip.X = TopX(Edge2, ip.Y);
+	}
+	// finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+	if (ip.Y > Edge1.Curr.Y) {
+		ip.Y = Edge1.Curr.Y;
+		// use the more vertical edge to derive X ...
+		if (fabs(Edge1.Dx) > fabs(Edge2.Dx))
+			ip.X = TopX(Edge2, ip.Y);
+		else
+			ip.X = TopX(Edge1, ip.Y);
+	}
 }
 //------------------------------------------------------------------------------
 
 void ReversePolyPtLinks(OutPt *pp) {
-  if (!pp)
-    return;
-  OutPt *pp1, *pp2;
-  pp1 = pp;
-  do {
-    pp2 = pp1->Next;
-    pp1->Next = pp1->Prev;
-    pp1->Prev = pp2;
-    pp1 = pp2;
-  } while (pp1 != pp);
+	if (!pp)
+		return;
+	OutPt *pp1, *pp2;
+	pp1 = pp;
+	do {
+		pp2 = pp1->Next;
+		pp1->Next = pp1->Prev;
+		pp1->Prev = pp2;
+		pp1 = pp2;
+	} while (pp1 != pp);
 }
 //------------------------------------------------------------------------------
 
 void DisposeOutPts(OutPt *&pp) {
-  if (pp == 0)
-    return;
-  pp->Prev->Next = 0;
-  while (pp) {
-    OutPt *tmpPp = pp;
-    pp = pp->Next;
-    delete tmpPp;
-  }
+	if (pp == 0)
+		return;
+	pp->Prev->Next = 0;
+	while (pp) {
+		OutPt *tmpPp = pp;
+		pp = pp->Next;
+		delete tmpPp;
+	}
 }
 //------------------------------------------------------------------------------
 
 inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) {
-  std::memset(e, 0, sizeof(TEdge));
-  e->Next = eNext;
-  e->Prev = ePrev;
-  e->Curr = Pt;
-  e->OutIdx = Unassigned;
+	memset(e, 0, sizeof(TEdge));
+	e->Next = eNext;
+	e->Prev = ePrev;
+	e->Curr = Pt;
+	e->OutIdx = Unassigned;
 }
 //------------------------------------------------------------------------------
 
 void InitEdge2(TEdge &e, PolyType Pt) {
-  if (e.Curr.Y >= e.Next->Curr.Y) {
-    e.Bot = e.Curr;
-    e.Top = e.Next->Curr;
-  } else {
-    e.Top = e.Curr;
-    e.Bot = e.Next->Curr;
-  }
-  SetDx(e);
-  e.PolyTyp = Pt;
+	if (e.Curr.Y >= e.Next->Curr.Y) {
+		e.Bot = e.Curr;
+		e.Top = e.Next->Curr;
+	} else {
+		e.Top = e.Curr;
+		e.Bot = e.Next->Curr;
+	}
+	SetDx(e);
+	e.PolyTyp = Pt;
 }
 //------------------------------------------------------------------------------
 
 TEdge *RemoveEdge(TEdge *e) {
-  //removes e from double_linked_list (but without removing from memory)
-  e->Prev->Next = e->Next;
-  e->Next->Prev = e->Prev;
-  TEdge *result = e->Next;
-  e->Prev = 0; //flag as removed (see ClipperBase.Clear)
-  return result;
+	// removes e from double_linked_list (but without removing from memory)
+	e->Prev->Next = e->Next;
+	e->Next->Prev = e->Prev;
+	TEdge *result = e->Next;
+	e->Prev = 0; // flag as removed (see ClipperBase.Clear)
+	return result;
 }
 //------------------------------------------------------------------------------
 
 inline void ReverseHorizontal(TEdge &e) {
-  //swap horizontal edges' Top and Bottom x's so they follow the natural
-  //progression of the bounds - ie so their xbots will align with the
-  //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
-  std::swap(e.Top.X, e.Bot.X);
+	// swap horizontal edges' Top and Bottom x's so they follow the natural
+	// progression of the bounds - ie so their xbots will align with the
+	// adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
+	SWAP(e.Top.X, e.Bot.X);
 #ifdef use_xyz
-  std::swap(e.Top.Z, e.Bot.Z);
+	SWAP(e.Top.Z, e.Bot.Z);
 #endif
 }
 //------------------------------------------------------------------------------
 
 void SwapPoints(IntPoint &pt1, IntPoint &pt2) {
-  IntPoint tmp = pt1;
-  pt1 = pt2;
-  pt2 = tmp;
+	IntPoint tmp = pt1;
+	pt1 = pt2;
+	pt2 = tmp;
 }
 //------------------------------------------------------------------------------
 
 bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
-                       IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
-  //precondition: segments are Collinear.
-  if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
-    if (pt1a.X > pt1b.X)
-      SwapPoints(pt1a, pt1b);
-    if (pt2a.X > pt2b.X)
-      SwapPoints(pt2a, pt2b);
-    if (pt1a.X > pt2a.X)
-      pt1 = pt1a;
-    else
-      pt1 = pt2a;
-    if (pt1b.X < pt2b.X)
-      pt2 = pt1b;
-    else
-      pt2 = pt2b;
-    return pt1.X < pt2.X;
-  } else {
-    if (pt1a.Y < pt1b.Y)
-      SwapPoints(pt1a, pt1b);
-    if (pt2a.Y < pt2b.Y)
-      SwapPoints(pt2a, pt2b);
-    if (pt1a.Y < pt2a.Y)
-      pt1 = pt1a;
-    else
-      pt1 = pt2a;
-    if (pt1b.Y > pt2b.Y)
-      pt2 = pt1b;
-    else
-      pt2 = pt2b;
-    return pt1.Y > pt2.Y;
-  }
+					   IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
+	// precondition: segments are Collinear.
+	if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
+		if (pt1a.X > pt1b.X)
+			SwapPoints(pt1a, pt1b);
+		if (pt2a.X > pt2b.X)
+			SwapPoints(pt2a, pt2b);
+		if (pt1a.X > pt2a.X)
+			pt1 = pt1a;
+		else
+			pt1 = pt2a;
+		if (pt1b.X < pt2b.X)
+			pt2 = pt1b;
+		else
+			pt2 = pt2b;
+		return pt1.X < pt2.X;
+	} else {
+		if (pt1a.Y < pt1b.Y)
+			SwapPoints(pt1a, pt1b);
+		if (pt2a.Y < pt2b.Y)
+			SwapPoints(pt2a, pt2b);
+		if (pt1a.Y < pt2a.Y)
+			pt1 = pt1a;
+		else
+			pt1 = pt2a;
+		if (pt1b.Y > pt2b.Y)
+			pt2 = pt1b;
+		else
+			pt2 = pt2b;
+		return pt1.Y > pt2.Y;
+	}
 }
 //------------------------------------------------------------------------------
 
 bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) {
-  OutPt *p = btmPt1->Prev;
-  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
-    p = p->Prev;
-  double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
-  p = btmPt1->Next;
-  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
-    p = p->Next;
-  double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
-
-  p = btmPt2->Prev;
-  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
-    p = p->Prev;
-  double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
-  p = btmPt2->Next;
-  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
-    p = p->Next;
-  double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
-
-  if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
-      std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
-    return Area(btmPt1) > 0; //if otherwise identical use orientation
-  else
-    return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
+	OutPt *p = btmPt1->Prev;
+	while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+		p = p->Prev;
+	double dx1p = fabs(GetDx(btmPt1->Pt, p->Pt));
+	p = btmPt1->Next;
+	while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+		p = p->Next;
+	double dx1n = fabs(GetDx(btmPt1->Pt, p->Pt));
+
+	p = btmPt2->Prev;
+	while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+		p = p->Prev;
+	double dx2p = fabs(GetDx(btmPt2->Pt, p->Pt));
+	p = btmPt2->Next;
+	while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+		p = p->Next;
+	double dx2n = fabs(GetDx(btmPt2->Pt, p->Pt));
+
+	if (MAX(dx1p, dx1n) == MAX(dx2p, dx2n) &&
+		MIN(dx1p, dx1n) == MIN(dx2p, dx2n))
+		return Area(btmPt1) > 0; // if otherwise identical use orientation
+	else
+		return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
 }
 //------------------------------------------------------------------------------
 
 OutPt *GetBottomPt(OutPt *pp) {
-  OutPt *dups = 0;
-  OutPt *p = pp->Next;
-  while (p != pp) {
-    if (p->Pt.Y > pp->Pt.Y) {
-      pp = p;
-      dups = 0;
-    } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
-      if (p->Pt.X < pp->Pt.X) {
-        dups = 0;
-        pp = p;
-      } else {
-        if (p->Next != pp && p->Prev != pp)
-          dups = p;
-      }
-    }
-    p = p->Next;
-  }
-  if (dups) {
-    //there appears to be at least 2 vertices at BottomPt so ...
-    while (dups != p) {
-      if (!FirstIsBottomPt(p, dups))
-        pp = dups;
-      dups = dups->Next;
-      while (dups->Pt != pp->Pt)
-        dups = dups->Next;
-    }
-  }
-  return pp;
+	OutPt *dups = 0;
+	OutPt *p = pp->Next;
+	while (p != pp) {
+		if (p->Pt.Y > pp->Pt.Y) {
+			pp = p;
+			dups = 0;
+		} else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
+			if (p->Pt.X < pp->Pt.X) {
+				dups = 0;
+				pp = p;
+			} else {
+				if (p->Next != pp && p->Prev != pp)
+					dups = p;
+			}
+		}
+		p = p->Next;
+	}
+	if (dups) {
+		// there appears to be at least 2 vertices at BottomPt so ...
+		while (dups != p) {
+			if (!FirstIsBottomPt(p, dups))
+				pp = dups;
+			dups = dups->Next;
+			while (dups->Pt != pp->Pt)
+				dups = dups->Next;
+		}
+	}
+	return pp;
 }
 //------------------------------------------------------------------------------
 
 bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
-                           const IntPoint pt2, const IntPoint pt3) {
-  if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
-    return false;
-  else if (pt1.X != pt3.X)
-    return (pt2.X > pt1.X) == (pt2.X < pt3.X);
-  else
-    return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
+						   const IntPoint pt2, const IntPoint pt3) {
+	if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
+		return false;
+	else if (pt1.X != pt3.X)
+		return (pt2.X > pt1.X) == (pt2.X < pt3.X);
+	else
+		return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
 }
 //------------------------------------------------------------------------------
 
 bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) {
-  if (seg1a > seg1b)
-    std::swap(seg1a, seg1b);
-  if (seg2a > seg2b)
-    std::swap(seg2a, seg2b);
-  return (seg1a < seg2b) && (seg2a < seg1b);
+	if (seg1a > seg1b)
+		SWAP(seg1a, seg1b);
+	if (seg2a > seg2b)
+		SWAP(seg2a, seg2b);
+	return (seg1a < seg2b) && (seg2a < seg1b);
 }
 
 //------------------------------------------------------------------------------
 // ClipperBase class methods ...
 //------------------------------------------------------------------------------
 
-ClipperBase::ClipperBase() //constructor
+ClipperBase::ClipperBase() // constructor
 {
-  m_CurrentLM = m_MinimaList.begin(); //begin() == end() here
-  m_UseFullRange = false;
+	m_CurrentLM = m_MinimaList.begin(); // begin() == end() here
+	m_UseFullRange = false;
 }
 //------------------------------------------------------------------------------
 
-ClipperBase::~ClipperBase() //destructor
+ClipperBase::~ClipperBase() // destructor
 {
-  Clear();
+	Clear();
 }
 //------------------------------------------------------------------------------
 
 void RangeTest(const IntPoint &Pt, bool &useFullRange) {
-  if (useFullRange) {
-    if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
-      error("Coordinate outside allowed range");
-  } else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) {
-    useFullRange = true;
-    RangeTest(Pt, useFullRange);
-  }
+	if (useFullRange) {
+		if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
+			error("Coordinate outside allowed range");
+	} else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) {
+		useFullRange = true;
+		RangeTest(Pt, useFullRange);
+	}
 }
 //------------------------------------------------------------------------------
 
 TEdge *FindNextLocMin(TEdge *E) {
-  for (;;) {
-    while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
-      E = E->Next;
-    if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
-      break;
-    while (IsHorizontal(*E->Prev))
-      E = E->Prev;
-    TEdge *E2 = E;
-    while (IsHorizontal(*E))
-      E = E->Next;
-    if (E->Top.Y == E->Prev->Bot.Y)
-      continue; //ie just an intermediate horz.
-    if (E2->Prev->Bot.X < E->Bot.X)
-      E = E2;
-    break;
-  }
-  return E;
+	for (;;) {
+		while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
+			E = E->Next;
+		if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
+			break;
+		while (IsHorizontal(*E->Prev))
+			E = E->Prev;
+		TEdge *E2 = E;
+		while (IsHorizontal(*E))
+			E = E->Next;
+		if (E->Top.Y == E->Prev->Bot.Y)
+			continue; // ie just an intermediate horz.
+		if (E2->Prev->Bot.X < E->Bot.X)
+			E = E2;
+		break;
+	}
+	return E;
 }
 //------------------------------------------------------------------------------
 
 TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) {
-  TEdge *Result = E;
-  TEdge *Horz = 0;
-
-  if (E->OutIdx == Skip) {
-    //if edges still remain in the current bound beyond the skip edge then
-    //create another LocMin and call ProcessBound once more
-    if (NextIsForward) {
-      while (E->Top.Y == E->Next->Bot.Y)
-        E = E->Next;
-      //don't include top horizontals when parsing a bound a second time,
-      //they will be contained in the opposite bound ...
-      while (E != Result && IsHorizontal(*E))
-        E = E->Prev;
-    } else {
-      while (E->Top.Y == E->Prev->Bot.Y)
-        E = E->Prev;
-      while (E != Result && IsHorizontal(*E))
-        E = E->Next;
-    }
-
-    if (E == Result) {
-      if (NextIsForward)
-        Result = E->Next;
-      else
-        Result = E->Prev;
-    } else {
-      //there are more edges in the bound beyond result starting with E
-      if (NextIsForward)
-        E = Result->Next;
-      else
-        E = Result->Prev;
-      MinimaList::value_type locMin;
-      locMin.Y = E->Bot.Y;
-      locMin.LeftBound = 0;
-      locMin.RightBound = E;
-      E->WindDelta = 0;
-      Result = ProcessBound(E, NextIsForward);
-      m_MinimaList.push_back(locMin);
-    }
-    return Result;
-  }
-
-  TEdge *EStart;
-
-  if (IsHorizontal(*E)) {
-    //We need to be careful with open paths because this may not be a
-    //true local minima (ie E may be following a skip edge).
-    //Also, consecutive horz. edges may start heading left before going right.
-    if (NextIsForward)
-      EStart = E->Prev;
-    else
-      EStart = E->Next;
-    if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
-    {
-      if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
-        ReverseHorizontal(*E);
-    } else if (EStart->Bot.X != E->Bot.X)
-      ReverseHorizontal(*E);
-  }
-
-  EStart = E;
-  if (NextIsForward) {
-    while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
-      Result = Result->Next;
-    if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
-      //nb: at the top of a bound, horizontals are added to the bound
-      //only when the preceding edge attaches to the horizontal's left vertex
-      //unless a Skip edge is encountered when that becomes the top divide
-      Horz = Result;
-      while (IsHorizontal(*Horz->Prev))
-        Horz = Horz->Prev;
-      if (Horz->Prev->Top.X > Result->Next->Top.X)
-        Result = Horz->Prev;
-    }
-    while (E != Result) {
-      E->NextInLML = E->Next;
-      if (IsHorizontal(*E) && E != EStart &&
-          E->Bot.X != E->Prev->Top.X)
-        ReverseHorizontal(*E);
-      E = E->Next;
-    }
-    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
-      ReverseHorizontal(*E);
-    Result = Result->Next; //move to the edge just beyond current bound
-  } else {
-    while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
-      Result = Result->Prev;
-    if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
-      Horz = Result;
-      while (IsHorizontal(*Horz->Next))
-        Horz = Horz->Next;
-      if (Horz->Next->Top.X == Result->Prev->Top.X ||
-          Horz->Next->Top.X > Result->Prev->Top.X)
-        Result = Horz->Next;
-    }
-
-    while (E != Result) {
-      E->NextInLML = E->Prev;
-      if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
-        ReverseHorizontal(*E);
-      E = E->Prev;
-    }
-    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
-      ReverseHorizontal(*E);
-    Result = Result->Prev; //move to the edge just beyond current bound
-  }
-
-  return Result;
+	TEdge *Result = E;
+	TEdge *Horz = 0;
+
+	if (E->OutIdx == Skip) {
+		// if edges still remain in the current bound beyond the skip edge then
+		// create another LocMin and call ProcessBound once more
+		if (NextIsForward) {
+			while (E->Top.Y == E->Next->Bot.Y)
+				E = E->Next;
+			// don't include top horizontals when parsing a bound a second time,
+			// they will be contained in the opposite bound ...
+			while (E != Result && IsHorizontal(*E))
+				E = E->Prev;
+		} else {
+			while (E->Top.Y == E->Prev->Bot.Y)
+				E = E->Prev;
+			while (E != Result && IsHorizontal(*E))
+				E = E->Next;
+		}
+
+		if (E == Result) {
+			if (NextIsForward)
+				Result = E->Next;
+			else
+				Result = E->Prev;
+		} else {
+			// there are more edges in the bound beyond result starting with E
+			if (NextIsForward)
+				E = Result->Next;
+			else
+				E = Result->Prev;
+			MinimaList::value_type locMin;
+			locMin.Y = E->Bot.Y;
+			locMin.LeftBound = 0;
+			locMin.RightBound = E;
+			E->WindDelta = 0;
+			Result = ProcessBound(E, NextIsForward);
+			m_MinimaList.push_back(locMin);
+		}
+		return Result;
+	}
+
+	TEdge *EStart;
+
+	if (IsHorizontal(*E)) {
+		// We need to be careful with open paths because this may not be a
+		// true local minima (ie E may be following a skip edge).
+		// Also, consecutive horz. edges may start heading left before going right.
+		if (NextIsForward)
+			EStart = E->Prev;
+		else
+			EStart = E->Next;
+		if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge
+		{
+			if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
+				ReverseHorizontal(*E);
+		} else if (EStart->Bot.X != E->Bot.X)
+			ReverseHorizontal(*E);
+	}
+
+	EStart = E;
+	if (NextIsForward) {
+		while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
+			Result = Result->Next;
+		if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
+			// nb: at the top of a bound, horizontals are added to the bound
+			// only when the preceding edge attaches to the horizontal's left vertex
+			// unless a Skip edge is encountered when that becomes the top divide
+			Horz = Result;
+			while (IsHorizontal(*Horz->Prev))
+				Horz = Horz->Prev;
+			if (Horz->Prev->Top.X > Result->Next->Top.X)
+				Result = Horz->Prev;
+		}
+		while (E != Result) {
+			E->NextInLML = E->Next;
+			if (IsHorizontal(*E) && E != EStart &&
+				E->Bot.X != E->Prev->Top.X)
+				ReverseHorizontal(*E);
+			E = E->Next;
+		}
+		if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
+			ReverseHorizontal(*E);
+		Result = Result->Next; // move to the edge just beyond current bound
+	} else {
+		while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
+			Result = Result->Prev;
+		if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
+			Horz = Result;
+			while (IsHorizontal(*Horz->Next))
+				Horz = Horz->Next;
+			if (Horz->Next->Top.X == Result->Prev->Top.X ||
+				Horz->Next->Top.X > Result->Prev->Top.X)
+				Result = Horz->Next;
+		}
+
+		while (E != Result) {
+			E->NextInLML = E->Prev;
+			if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+				ReverseHorizontal(*E);
+			E = E->Prev;
+		}
+		if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+			ReverseHorizontal(*E);
+		Result = Result->Prev; // move to the edge just beyond current bound
+	}
+
+	return Result;
 }
 //------------------------------------------------------------------------------
 
 bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) {
 #ifdef use_lines
-  if (!Closed && PolyTyp == ptClip)
-    error("AddPath: Open paths must be subject.");
+	if (!Closed && PolyTyp == ptClip)
+		error("AddPath: Open paths must be subject.");
 #else
-  if (!Closed)
-    error("AddPath: Open paths have been disabled.");
+	if (!Closed)
+		error("AddPath: Open paths have been disabled.");
 #endif
 
-  int highI = (int) pg.size() - 1;
-  if (Closed)
-    while (highI > 0 && (pg[highI] == pg[0]))
-      --highI;
-  while (highI > 0 && (pg[highI] == pg[highI - 1]))
-    --highI;
-  if ((Closed && highI < 2) || (!Closed && highI < 1))
-    return false;
-
-  //create a new edge array ...
-  TEdge *edges = new TEdge[highI + 1];
-
-  bool IsFlat = true;
-  //1. Basic (first) edge initialization ...
-  edges[1].Curr = pg[1];
-  RangeTest(pg[0], m_UseFullRange);
-  RangeTest(pg[highI], m_UseFullRange);
-  InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
-  InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
-  for (int i = highI - 1; i >= 1; --i) {
-    RangeTest(pg[i], m_UseFullRange);
-    InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
-  }
-
-  TEdge *eStart = &edges[0];
-
-  //2. Remove duplicate vertices, and (when closed) collinear edges ...
-  TEdge *E = eStart, *eLoopStop = eStart;
-  for (;;) {
-    //nb: allows matching start and end points when not Closed ...
-    if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
-      if (E == E->Next)
-        break;
-      if (E == eStart)
-        eStart = E->Next;
-      E = RemoveEdge(E);
-      eLoopStop = E;
-      continue;
-    }
-    if (E->Prev == E->Next)
-      break; //only two vertices
-    else if (Closed &&
-        SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) &&
-        (!m_PreserveCollinear ||
-            !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
-      //Collinear edges are allowed for open paths but in closed paths
-      //the default is to merge adjacent collinear edges into a single edge.
-      //However, if the PreserveCollinear property is enabled, only overlapping
-      //collinear edges (ie spikes) will be removed from closed paths.
-      if (E == eStart)
-        eStart = E->Next;
-      E = RemoveEdge(E);
-      E = E->Prev;
-      eLoopStop = E;
-      continue;
-    }
-    E = E->Next;
-    if ((E == eLoopStop) || (!Closed && E->Next == eStart))
-      break;
-  }
-
-  if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
-    delete[] edges;
-    return false;
-  }
-
-  if (!Closed) {
-    m_HasOpenPaths = true;
-    eStart->Prev->OutIdx = Skip;
-  }
-
-  //3. Do second stage of edge initialization ...
-  E = eStart;
-  do {
-    InitEdge2(*E, PolyTyp);
-    E = E->Next;
-    if (IsFlat && E->Curr.Y != eStart->Curr.Y)
-      IsFlat = false;
-  } while (E != eStart);
-
-  //4. Finally, add edge bounds to LocalMinima list ...
-
-  //Totally flat paths must be handled differently when adding them
-  //to LocalMinima list to avoid endless loops etc ...
-  if (IsFlat) {
-    if (Closed) {
-      delete[] edges;
-      return false;
-    }
-    E->Prev->OutIdx = Skip;
-    MinimaList::value_type locMin;
-    locMin.Y = E->Bot.Y;
-    locMin.LeftBound = 0;
-    locMin.RightBound = E;
-    locMin.RightBound->Side = esRight;
-    locMin.RightBound->WindDelta = 0;
-    for (;;) {
-      if (E->Bot.X != E->Prev->Top.X)
-        ReverseHorizontal(*E);
-      if (E->Next->OutIdx == Skip)
-        break;
-      E->NextInLML = E->Next;
-      E = E->Next;
-    }
-    m_MinimaList.push_back(locMin);
-    m_edges.push_back(edges);
-    return true;
-  }
-
-  m_edges.push_back(edges);
-  bool leftBoundIsForward;
-  TEdge *EMin = 0;
-
-  //workaround to avoid an endless loop in the while loop below when
-  //open paths have matching start and end points ...
-  if (E->Prev->Bot == E->Prev->Top)
-    E = E->Next;
-
-  for (;;) {
-    E = FindNextLocMin(E);
-    if (E == EMin)
-      break;
-    else if (!EMin)
-      EMin = E;
-
-    //E and E.Prev now share a local minima (left aligned if horizontal).
-    //Compare their slopes to find which starts which bound ...
-    MinimaList::value_type locMin;
-    locMin.Y = E->Bot.Y;
-    if (E->Dx < E->Prev->Dx) {
-      locMin.LeftBound = E->Prev;
-      locMin.RightBound = E;
-      leftBoundIsForward = false; //Q.nextInLML = Q.prev
-    } else {
-      locMin.LeftBound = E;
-      locMin.RightBound = E->Prev;
-      leftBoundIsForward = true; //Q.nextInLML = Q.next
-    }
-
-    if (!Closed)
-      locMin.LeftBound->WindDelta = 0;
-    else if (locMin.LeftBound->Next == locMin.RightBound)
-      locMin.LeftBound->WindDelta = -1;
-    else
-      locMin.LeftBound->WindDelta = 1;
-    locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
-
-    E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
-    if (E->OutIdx == Skip)
-      E = ProcessBound(E, leftBoundIsForward);
-
-    TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
-    if (E2->OutIdx == Skip)
-      E2 = ProcessBound(E2, !leftBoundIsForward);
-
-    if (locMin.LeftBound->OutIdx == Skip)
-      locMin.LeftBound = 0;
-    else if (locMin.RightBound->OutIdx == Skip)
-      locMin.RightBound = 0;
-    m_MinimaList.push_back(locMin);
-    if (!leftBoundIsForward)
-      E = E2;
-  }
-  return true;
+	int highI = (int)pg.size() - 1;
+	if (Closed)
+		while (highI > 0 && (pg[highI] == pg[0]))
+			--highI;
+	while (highI > 0 && (pg[highI] == pg[highI - 1]))
+		--highI;
+	if ((Closed && highI < 2) || (!Closed && highI < 1))
+		return false;
+
+	// create a new edge array ...
+	TEdge *edges = new TEdge[highI + 1];
+
+	bool IsFlat = true;
+	// 1. Basic (first) edge initialization ...
+	edges[1].Curr = pg[1];
+	RangeTest(pg[0], m_UseFullRange);
+	RangeTest(pg[highI], m_UseFullRange);
+	InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
+	InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
+	for (int i = highI - 1; i >= 1; --i) {
+		RangeTest(pg[i], m_UseFullRange);
+		InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
+	}
+
+	TEdge *eStart = &edges[0];
+
+	// 2. Remove duplicate vertices, and (when closed) collinear edges ...
+	TEdge *E = eStart, *eLoopStop = eStart;
+	for (;;) {
+		// nb: allows matching start and end points when not Closed ...
+		if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
+			if (E == E->Next)
+				break;
+			if (E == eStart)
+				eStart = E->Next;
+			E = RemoveEdge(E);
+			eLoopStop = E;
+			continue;
+		}
+		if (E->Prev == E->Next)
+			break; // only two vertices
+		else if (Closed &&
+				 SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) &&
+				 (!m_PreserveCollinear ||
+				  !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
+			// Collinear edges are allowed for open paths but in closed paths
+			// the default is to merge adjacent collinear edges into a single edge.
+			// However, if the PreserveCollinear property is enabled, only overlapping
+			// collinear edges (ie spikes) will be removed from closed paths.
+			if (E == eStart)
+				eStart = E->Next;
+			E = RemoveEdge(E);
+			E = E->Prev;
+			eLoopStop = E;
+			continue;
+		}
+		E = E->Next;
+		if ((E == eLoopStop) || (!Closed && E->Next == eStart))
+			break;
+	}
+
+	if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
+		delete[] edges;
+		return false;
+	}
+
+	if (!Closed) {
+		m_HasOpenPaths = true;
+		eStart->Prev->OutIdx = Skip;
+	}
+
+	// 3. Do second stage of edge initialization ...
+	E = eStart;
+	do {
+		InitEdge2(*E, PolyTyp);
+		E = E->Next;
+		if (IsFlat && E->Curr.Y != eStart->Curr.Y)
+			IsFlat = false;
+	} while (E != eStart);
+
+	// 4. Finally, add edge bounds to LocalMinima list ...
+
+	// Totally flat paths must be handled differently when adding them
+	// to LocalMinima list to avoid endless loops etc ...
+	if (IsFlat) {
+		if (Closed) {
+			delete[] edges;
+			return false;
+		}
+		E->Prev->OutIdx = Skip;
+		MinimaList::value_type locMin;
+		locMin.Y = E->Bot.Y;
+		locMin.LeftBound = 0;
+		locMin.RightBound = E;
+		locMin.RightBound->Side = esRight;
+		locMin.RightBound->WindDelta = 0;
+		for (;;) {
+			if (E->Bot.X != E->Prev->Top.X)
+				ReverseHorizontal(*E);
+			if (E->Next->OutIdx == Skip)
+				break;
+			E->NextInLML = E->Next;
+			E = E->Next;
+		}
+		m_MinimaList.push_back(locMin);
+		m_edges.push_back(edges);
+		return true;
+	}
+
+	m_edges.push_back(edges);
+	bool leftBoundIsForward;
+	TEdge *EMin = 0;
+
+	// workaround to avoid an endless loop in the while loop below when
+	// open paths have matching start and end points ...
+	if (E->Prev->Bot == E->Prev->Top)
+		E = E->Next;
+
+	for (;;) {
+		E = FindNextLocMin(E);
+		if (E == EMin)
+			break;
+		else if (!EMin)
+			EMin = E;
+
+		// E and E.Prev now share a local minima (left aligned if horizontal).
+		// Compare their slopes to find which starts which bound ...
+		MinimaList::value_type locMin;
+		locMin.Y = E->Bot.Y;
+		if (E->Dx < E->Prev->Dx) {
+			locMin.LeftBound = E->Prev;
+			locMin.RightBound = E;
+			leftBoundIsForward = false; // Q.nextInLML = Q.prev
+		} else {
+			locMin.LeftBound = E;
+			locMin.RightBound = E->Prev;
+			leftBoundIsForward = true; // Q.nextInLML = Q.next
+		}
+
+		if (!Closed)
+			locMin.LeftBound->WindDelta = 0;
+		else if (locMin.LeftBound->Next == locMin.RightBound)
+			locMin.LeftBound->WindDelta = -1;
+		else
+			locMin.LeftBound->WindDelta = 1;
+		locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
+
+		E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
+		if (E->OutIdx == Skip)
+			E = ProcessBound(E, leftBoundIsForward);
+
+		TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
+		if (E2->OutIdx == Skip)
+			E2 = ProcessBound(E2, !leftBoundIsForward);
+
+		if (locMin.LeftBound->OutIdx == Skip)
+			locMin.LeftBound = 0;
+		else if (locMin.RightBound->OutIdx == Skip)
+			locMin.RightBound = 0;
+		m_MinimaList.push_back(locMin);
+		if (!leftBoundIsForward)
+			E = E2;
+	}
+	return true;
 }
 //------------------------------------------------------------------------------
 
 bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) {
-  bool result = false;
-  for (Paths::size_type i = 0; i < ppg.size(); ++i)
-    if (AddPath(ppg[i], PolyTyp, Closed))
-      result = true;
-  return result;
+	bool result = false;
+	for (Paths::size_type i = 0; i < ppg.size(); ++i)
+		if (AddPath(ppg[i], PolyTyp, Closed))
+			result = true;
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::Clear() {
-  DisposeLocalMinimaList();
-  for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
-    TEdge *edges = m_edges[i];
-    delete[] edges;
-  }
-  m_edges.clear();
-  m_UseFullRange = false;
-  m_HasOpenPaths = false;
+	DisposeLocalMinimaList();
+	for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
+		TEdge *edges = m_edges[i];
+		delete[] edges;
+	}
+	m_edges.clear();
+	m_UseFullRange = false;
+	m_HasOpenPaths = false;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::Reset() {
-  m_CurrentLM = m_MinimaList.begin();
-  if (m_CurrentLM == m_MinimaList.end())
-    return; //ie nothing to process
-  std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
-
-  m_Scanbeam = ScanbeamList(); //clears/resets priority_queue
-  //reset all edges ...
-  for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) {
-    InsertScanbeam(lm->Y);
-    TEdge *e = lm->LeftBound;
-    if (e) {
-      e->Curr = e->Bot;
-      e->Side = esLeft;
-      e->OutIdx = Unassigned;
-    }
-
-    e = lm->RightBound;
-    if (e) {
-      e->Curr = e->Bot;
-      e->Side = esRight;
-      e->OutIdx = Unassigned;
-    }
-  }
-  m_ActiveEdges = 0;
-  m_CurrentLM = m_MinimaList.begin();
+	m_CurrentLM = m_MinimaList.begin();
+	if (m_CurrentLM == m_MinimaList.end())
+		return; // ie nothing to process
+	Common::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
+
+	m_Scanbeam = ScanbeamList(); // clears/resets priority_queue
+	// reset all edges ...
+	for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) {
+		InsertScanbeam(lm->Y);
+		TEdge *e = lm->LeftBound;
+		if (e) {
+			e->Curr = e->Bot;
+			e->Side = esLeft;
+			e->OutIdx = Unassigned;
+		}
+
+		e = lm->RightBound;
+		if (e) {
+			e->Curr = e->Bot;
+			e->Side = esRight;
+			e->OutIdx = Unassigned;
+		}
+	}
+	m_ActiveEdges = 0;
+	m_CurrentLM = m_MinimaList.begin();
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::DisposeLocalMinimaList() {
-  m_MinimaList.clear();
-  m_CurrentLM = m_MinimaList.begin();
+	m_MinimaList.clear();
+	m_CurrentLM = m_MinimaList.begin();
 }
 //------------------------------------------------------------------------------
 
 bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) {
-  if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
-    return false;
-  locMin = &(*m_CurrentLM);
-  ++m_CurrentLM;
-  return true;
+	if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
+		return false;
+	locMin = &(*m_CurrentLM);
+	++m_CurrentLM;
+	return true;
 }
 //------------------------------------------------------------------------------
 
 IntRect ClipperBase::GetBounds() {
-  IntRect result;
-  MinimaList::iterator lm = m_MinimaList.begin();
-  if (lm == m_MinimaList.end()) {
-    result.left = result.top = result.right = result.bottom = 0;
-    return result;
-  }
-  result.left = lm->LeftBound->Bot.X;
-  result.top = lm->LeftBound->Bot.Y;
-  result.right = lm->LeftBound->Bot.X;
-  result.bottom = lm->LeftBound->Bot.Y;
-  while (lm != m_MinimaList.end()) {
-    //todo - needs fixing for open paths
-    result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
-    TEdge *e = lm->LeftBound;
-    for (;;) {
-      TEdge *bottomE = e;
-      while (e->NextInLML) {
-        if (e->Bot.X < result.left)
-          result.left = e->Bot.X;
-        if (e->Bot.X > result.right)
-          result.right = e->Bot.X;
-        e = e->NextInLML;
-      }
-      result.left = std::min(result.left, e->Bot.X);
-      result.right = std::max(result.right, e->Bot.X);
-      result.left = std::min(result.left, e->Top.X);
-      result.right = std::max(result.right, e->Top.X);
-      result.top = std::min(result.top, e->Top.Y);
-      if (bottomE == lm->LeftBound)
-        e = lm->RightBound;
-      else
-        break;
-    }
-    ++lm;
-  }
-  return result;
+	IntRect result;
+	MinimaList::iterator lm = m_MinimaList.begin();
+	if (lm == m_MinimaList.end()) {
+		result.left = result.top = result.right = result.bottom = 0;
+		return result;
+	}
+	result.left = lm->LeftBound->Bot.X;
+	result.top = lm->LeftBound->Bot.Y;
+	result.right = lm->LeftBound->Bot.X;
+	result.bottom = lm->LeftBound->Bot.Y;
+	while (lm != m_MinimaList.end()) {
+		// todo - needs fixing for open paths
+		result.bottom = MAX(result.bottom, lm->LeftBound->Bot.Y);
+		TEdge *e = lm->LeftBound;
+		for (;;) {
+			TEdge *bottomE = e;
+			while (e->NextInLML) {
+				if (e->Bot.X < result.left)
+					result.left = e->Bot.X;
+				if (e->Bot.X > result.right)
+					result.right = e->Bot.X;
+				e = e->NextInLML;
+			}
+			result.left = MIN(result.left, e->Bot.X);
+			result.right = MAX(result.right, e->Bot.X);
+			result.left = MIN(result.left, e->Top.X);
+			result.right = MAX(result.right, e->Top.X);
+			result.top = MIN(result.top, e->Top.Y);
+			if (bottomE == lm->LeftBound)
+				e = lm->RightBound;
+			else
+				break;
+		}
+		++lm;
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::InsertScanbeam(const cInt Y) {
-  m_Scanbeam.push(Y);
+	m_Scanbeam.push(Y);
 }
 //------------------------------------------------------------------------------
 
 bool ClipperBase::PopScanbeam(cInt &Y) {
-  if (m_Scanbeam.empty())
-    return false;
-  Y = m_Scanbeam.top();
-  m_Scanbeam.pop();
-  while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
-    m_Scanbeam.pop();
-  } // Pop duplicates.
-  return true;
+	if (m_Scanbeam.empty())
+		return false;
+	Y = m_Scanbeam.top();
+	m_Scanbeam.pop();
+	while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
+		m_Scanbeam.pop();
+	} // Pop duplicates.
+	return true;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::DisposeAllOutRecs() {
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
-    DisposeOutRec(i);
-  m_PolyOuts.clear();
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+		DisposeOutRec(i);
+	m_PolyOuts.clear();
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::DisposeOutRec(PolyOutList::size_type index) {
-  OutRec *outRec = m_PolyOuts[index];
-  if (outRec->Pts)
-    DisposeOutPts(outRec->Pts);
-  delete outRec;
-  m_PolyOuts[index] = 0;
+	OutRec *outRec = m_PolyOuts[index];
+	if (outRec->Pts)
+		DisposeOutPts(outRec->Pts);
+	delete outRec;
+	m_PolyOuts[index] = 0;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::DeleteFromAEL(TEdge *e) {
-  TEdge *AelPrev = e->PrevInAEL;
-  TEdge *AelNext = e->NextInAEL;
-  if (!AelPrev && !AelNext && (e != m_ActiveEdges))
-    return; //already deleted
-  if (AelPrev)
-    AelPrev->NextInAEL = AelNext;
-  else
-    m_ActiveEdges = AelNext;
-  if (AelNext)
-    AelNext->PrevInAEL = AelPrev;
-  e->NextInAEL = 0;
-  e->PrevInAEL = 0;
+	TEdge *AelPrev = e->PrevInAEL;
+	TEdge *AelNext = e->NextInAEL;
+	if (!AelPrev && !AelNext && (e != m_ActiveEdges))
+		return; // already deleted
+	if (AelPrev)
+		AelPrev->NextInAEL = AelNext;
+	else
+		m_ActiveEdges = AelNext;
+	if (AelNext)
+		AelNext->PrevInAEL = AelPrev;
+	e->NextInAEL = 0;
+	e->PrevInAEL = 0;
 }
 //------------------------------------------------------------------------------
 
 OutRec *ClipperBase::CreateOutRec() {
-  OutRec *result = new OutRec;
-  result->IsHole = false;
-  result->IsOpen = false;
-  result->FirstLeft = 0;
-  result->Pts = 0;
-  result->BottomPt = 0;
-  result->PolyNd = 0;
-  m_PolyOuts.push_back(result);
-  result->Idx = (int) m_PolyOuts.size() - 1;
-  return result;
+	OutRec *result = new OutRec;
+	result->IsHole = false;
+	result->IsOpen = false;
+	result->FirstLeft = 0;
+	result->Pts = 0;
+	result->BottomPt = 0;
+	result->PolyNd = 0;
+	m_PolyOuts.push_back(result);
+	result->Idx = (int)m_PolyOuts.size() - 1;
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) {
-  //check that one or other edge hasn't already been removed from AEL ...
-  if (Edge1->NextInAEL == Edge1->PrevInAEL ||
-      Edge2->NextInAEL == Edge2->PrevInAEL)
-    return;
-
-  if (Edge1->NextInAEL == Edge2) {
-    TEdge *Next = Edge2->NextInAEL;
-    if (Next)
-      Next->PrevInAEL = Edge1;
-    TEdge *Prev = Edge1->PrevInAEL;
-    if (Prev)
-      Prev->NextInAEL = Edge2;
-    Edge2->PrevInAEL = Prev;
-    Edge2->NextInAEL = Edge1;
-    Edge1->PrevInAEL = Edge2;
-    Edge1->NextInAEL = Next;
-  } else if (Edge2->NextInAEL == Edge1) {
-    TEdge *Next = Edge1->NextInAEL;
-    if (Next)
-      Next->PrevInAEL = Edge2;
-    TEdge *Prev = Edge2->PrevInAEL;
-    if (Prev)
-      Prev->NextInAEL = Edge1;
-    Edge1->PrevInAEL = Prev;
-    Edge1->NextInAEL = Edge2;
-    Edge2->PrevInAEL = Edge1;
-    Edge2->NextInAEL = Next;
-  } else {
-    TEdge *Next = Edge1->NextInAEL;
-    TEdge *Prev = Edge1->PrevInAEL;
-    Edge1->NextInAEL = Edge2->NextInAEL;
-    if (Edge1->NextInAEL)
-      Edge1->NextInAEL->PrevInAEL = Edge1;
-    Edge1->PrevInAEL = Edge2->PrevInAEL;
-    if (Edge1->PrevInAEL)
-      Edge1->PrevInAEL->NextInAEL = Edge1;
-    Edge2->NextInAEL = Next;
-    if (Edge2->NextInAEL)
-      Edge2->NextInAEL->PrevInAEL = Edge2;
-    Edge2->PrevInAEL = Prev;
-    if (Edge2->PrevInAEL)
-      Edge2->PrevInAEL->NextInAEL = Edge2;
-  }
-
-  if (!Edge1->PrevInAEL)
-    m_ActiveEdges = Edge1;
-  else if (!Edge2->PrevInAEL)
-    m_ActiveEdges = Edge2;
+	// check that one or other edge hasn't already been removed from AEL ...
+	if (Edge1->NextInAEL == Edge1->PrevInAEL ||
+		Edge2->NextInAEL == Edge2->PrevInAEL)
+		return;
+
+	if (Edge1->NextInAEL == Edge2) {
+		TEdge *Next = Edge2->NextInAEL;
+		if (Next)
+			Next->PrevInAEL = Edge1;
+		TEdge *Prev = Edge1->PrevInAEL;
+		if (Prev)
+			Prev->NextInAEL = Edge2;
+		Edge2->PrevInAEL = Prev;
+		Edge2->NextInAEL = Edge1;
+		Edge1->PrevInAEL = Edge2;
+		Edge1->NextInAEL = Next;
+	} else if (Edge2->NextInAEL == Edge1) {
+		TEdge *Next = Edge1->NextInAEL;
+		if (Next)
+			Next->PrevInAEL = Edge2;
+		TEdge *Prev = Edge2->PrevInAEL;
+		if (Prev)
+			Prev->NextInAEL = Edge1;
+		Edge1->PrevInAEL = Prev;
+		Edge1->NextInAEL = Edge2;
+		Edge2->PrevInAEL = Edge1;
+		Edge2->NextInAEL = Next;
+	} else {
+		TEdge *Next = Edge1->NextInAEL;
+		TEdge *Prev = Edge1->PrevInAEL;
+		Edge1->NextInAEL = Edge2->NextInAEL;
+		if (Edge1->NextInAEL)
+			Edge1->NextInAEL->PrevInAEL = Edge1;
+		Edge1->PrevInAEL = Edge2->PrevInAEL;
+		if (Edge1->PrevInAEL)
+			Edge1->PrevInAEL->NextInAEL = Edge1;
+		Edge2->NextInAEL = Next;
+		if (Edge2->NextInAEL)
+			Edge2->NextInAEL->PrevInAEL = Edge2;
+		Edge2->PrevInAEL = Prev;
+		if (Edge2->PrevInAEL)
+			Edge2->PrevInAEL->NextInAEL = Edge2;
+	}
+
+	if (!Edge1->PrevInAEL)
+		m_ActiveEdges = Edge1;
+	else if (!Edge2->PrevInAEL)
+		m_ActiveEdges = Edge2;
 }
 //------------------------------------------------------------------------------
 
 void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) {
-  if (!e->NextInLML)
-    error("UpdateEdgeIntoAEL: invalid call");
-
-  e->NextInLML->OutIdx = e->OutIdx;
-  TEdge *AelPrev = e->PrevInAEL;
-  TEdge *AelNext = e->NextInAEL;
-  if (AelPrev)
-    AelPrev->NextInAEL = e->NextInLML;
-  else
-    m_ActiveEdges = e->NextInLML;
-  if (AelNext)
-    AelNext->PrevInAEL = e->NextInLML;
-  e->NextInLML->Side = e->Side;
-  e->NextInLML->WindDelta = e->WindDelta;
-  e->NextInLML->WindCnt = e->WindCnt;
-  e->NextInLML->WindCnt2 = e->WindCnt2;
-  e = e->NextInLML;
-  e->Curr = e->Bot;
-  e->PrevInAEL = AelPrev;
-  e->NextInAEL = AelNext;
-  if (!IsHorizontal(*e))
-    InsertScanbeam(e->Top.Y);
+	if (!e->NextInLML)
+		error("UpdateEdgeIntoAEL: invalid call");
+
+	e->NextInLML->OutIdx = e->OutIdx;
+	TEdge *AelPrev = e->PrevInAEL;
+	TEdge *AelNext = e->NextInAEL;
+	if (AelPrev)
+		AelPrev->NextInAEL = e->NextInLML;
+	else
+		m_ActiveEdges = e->NextInLML;
+	if (AelNext)
+		AelNext->PrevInAEL = e->NextInLML;
+	e->NextInLML->Side = e->Side;
+	e->NextInLML->WindDelta = e->WindDelta;
+	e->NextInLML->WindCnt = e->WindCnt;
+	e->NextInLML->WindCnt2 = e->WindCnt2;
+	e = e->NextInLML;
+	e->Curr = e->Bot;
+	e->PrevInAEL = AelPrev;
+	e->NextInAEL = AelNext;
+	if (!IsHorizontal(*e))
+		InsertScanbeam(e->Top.Y);
 }
 //------------------------------------------------------------------------------
 
 bool ClipperBase::LocalMinimaPending() {
-  return (m_CurrentLM != m_MinimaList.end());
+	return (m_CurrentLM != m_MinimaList.end());
 }
 
 //------------------------------------------------------------------------------
 // TClipper methods ...
 //------------------------------------------------------------------------------
 
-Clipper::Clipper(int initOptions) : ClipperBase() //constructor
+Clipper::Clipper(int initOptions) : ClipperBase() // constructor
 {
-  m_ExecuteLocked = false;
-  m_UseFullRange = false;
-  m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
-  m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
-  m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
-  m_HasOpenPaths = false;
+	m_ExecuteLocked = false;
+	m_UseFullRange = false;
+	m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
+	m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
+	m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
+	m_HasOpenPaths = false;
 #ifdef use_xyz
-  m_ZFill = 0;
+	m_ZFill = 0;
 #endif
 }
 //------------------------------------------------------------------------------
 
 #ifdef use_xyz
-void Clipper::ZFillFunction(ZFillCallback zFillFunc)
-{
-  m_ZFill = zFillFunc;
+void Clipper::ZFillFunction(ZFillCallback zFillFunc) {
+	m_ZFill = zFillFunc;
 }
 //------------------------------------------------------------------------------
 #endif
 
 bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) {
-  return Execute(clipType, solution, fillType, fillType);
+	return Execute(clipType, solution, fillType, fillType);
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) {
-  return Execute(clipType, polytree, fillType, fillType);
+	return Execute(clipType, polytree, fillType, fillType);
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::Execute(ClipType clipType, Paths &solution,
-                      PolyFillType subjFillType, PolyFillType clipFillType) {
-  if (m_ExecuteLocked)
-    return false;
-  if (m_HasOpenPaths)
-    error("Error: PolyTree struct is needed for open path clipping.");
-  m_ExecuteLocked = true;
-  solution.resize(0);
-  m_SubjFillType = subjFillType;
-  m_ClipFillType = clipFillType;
-  m_ClipType = clipType;
-  m_UsingPolyTree = false;
-  bool succeeded = ExecuteInternal();
-  if (succeeded)
-    BuildResult(solution);
-  DisposeAllOutRecs();
-  m_ExecuteLocked = false;
-  return succeeded;
+					  PolyFillType subjFillType, PolyFillType clipFillType) {
+	if (m_ExecuteLocked)
+		return false;
+	if (m_HasOpenPaths)
+		error("Error: PolyTree struct is needed for open path clipping.");
+	m_ExecuteLocked = true;
+	solution.resize(0);
+	m_SubjFillType = subjFillType;
+	m_ClipFillType = clipFillType;
+	m_ClipType = clipType;
+	m_UsingPolyTree = false;
+	bool succeeded = ExecuteInternal();
+	if (succeeded)
+		BuildResult(solution);
+	DisposeAllOutRecs();
+	m_ExecuteLocked = false;
+	return succeeded;
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
-                      PolyFillType subjFillType, PolyFillType clipFillType) {
-  if (m_ExecuteLocked)
-    return false;
-  m_ExecuteLocked = true;
-  m_SubjFillType = subjFillType;
-  m_ClipFillType = clipFillType;
-  m_ClipType = clipType;
-  m_UsingPolyTree = true;
-  bool succeeded = ExecuteInternal();
-  if (succeeded)
-    BuildResult2(polytree);
-  DisposeAllOutRecs();
-  m_ExecuteLocked = false;
-  return succeeded;
+					  PolyFillType subjFillType, PolyFillType clipFillType) {
+	if (m_ExecuteLocked)
+		return false;
+	m_ExecuteLocked = true;
+	m_SubjFillType = subjFillType;
+	m_ClipFillType = clipFillType;
+	m_ClipType = clipType;
+	m_UsingPolyTree = true;
+	bool succeeded = ExecuteInternal();
+	if (succeeded)
+		BuildResult2(polytree);
+	DisposeAllOutRecs();
+	m_ExecuteLocked = false;
+	return succeeded;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::FixHoleLinkage(OutRec &outrec) {
-  //skip OutRecs that (a) contain outermost polygons or
-  //(b) already have the correct owner/child linkage ...
-  if (!outrec.FirstLeft ||
-      (outrec.IsHole != outrec.FirstLeft->IsHole &&
-          outrec.FirstLeft->Pts))
-    return;
+	// skip OutRecs that (a) contain outermost polygons or
+	//(b) already have the correct owner/child linkage ...
+	if (!outrec.FirstLeft ||
+		(outrec.IsHole != outrec.FirstLeft->IsHole &&
+		 outrec.FirstLeft->Pts))
+		return;
 
-  OutRec *orfl = outrec.FirstLeft;
-  while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
-    orfl = orfl->FirstLeft;
-  outrec.FirstLeft = orfl;
+	OutRec *orfl = outrec.FirstLeft;
+	while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
+		orfl = orfl->FirstLeft;
+	outrec.FirstLeft = orfl;
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::ExecuteInternal() {
-  bool succeeded = true;
-  {
-    Reset();
-    m_Maxima = MaximaList();
-    m_SortedEdges = 0;
-
-    succeeded = true;
-    cInt botY, topY;
-    if (!PopScanbeam(botY))
-      return false;
-    InsertLocalMinimaIntoAEL(botY);
-    while (PopScanbeam(topY) || LocalMinimaPending()) {
-      ProcessHorizontals();
-      ClearGhostJoins();
-      if (!ProcessIntersections(topY)) {
-        succeeded = false;
-        break;
-      }
-      ProcessEdgesAtTopOfScanbeam(topY);
-      botY = topY;
-      InsertLocalMinimaIntoAEL(botY);
-    }
-  }
-
-  if (succeeded) {
-    //fix orientations ...
-    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-      OutRec *outRec = m_PolyOuts[i];
-      if (!outRec->Pts || outRec->IsOpen)
-        continue;
-      if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
-        ReversePolyPtLinks(outRec->Pts);
-    }
-
-    if (!m_Joins.empty())
-      JoinCommonEdges();
-
-    //unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
-    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-      OutRec *outRec = m_PolyOuts[i];
-      if (!outRec->Pts)
-        continue;
-      if (outRec->IsOpen)
-        FixupOutPolyline(*outRec);
-      else
-        FixupOutPolygon(*outRec);
-    }
-
-    if (m_StrictSimple)
-      DoSimplePolygons();
-  }
-
-  ClearJoins();
-  ClearGhostJoins();
-  return succeeded;
+	bool succeeded = true;
+	{
+		Reset();
+		m_Maxima = MaximaList();
+		m_SortedEdges = 0;
+
+		succeeded = true;
+		cInt botY, topY;
+		if (!PopScanbeam(botY))
+			return false;
+		InsertLocalMinimaIntoAEL(botY);
+		while (PopScanbeam(topY) || LocalMinimaPending()) {
+			ProcessHorizontals();
+			ClearGhostJoins();
+			if (!ProcessIntersections(topY)) {
+				succeeded = false;
+				break;
+			}
+			ProcessEdgesAtTopOfScanbeam(topY);
+			botY = topY;
+			InsertLocalMinimaIntoAEL(botY);
+		}
+	}
+
+	if (succeeded) {
+		// fix orientations ...
+		for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+			OutRec *outRec = m_PolyOuts[i];
+			if (!outRec->Pts || outRec->IsOpen)
+				continue;
+			if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
+				ReversePolyPtLinks(outRec->Pts);
+		}
+
+		if (!m_Joins.empty())
+			JoinCommonEdges();
+
+		// unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
+		for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+			OutRec *outRec = m_PolyOuts[i];
+			if (!outRec->Pts)
+				continue;
+			if (outRec->IsOpen)
+				FixupOutPolyline(*outRec);
+			else
+				FixupOutPolygon(*outRec);
+		}
+
+		if (m_StrictSimple)
+			DoSimplePolygons();
+	}
+
+	ClearJoins();
+	ClearGhostJoins();
+	return succeeded;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::SetWindingCount(TEdge &edge) {
-  TEdge *e = edge.PrevInAEL;
-  //find the edge of the same polytype that immediately preceeds 'edge' in AEL
-  while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
-    e = e->PrevInAEL;
-  if (!e) {
-    if (edge.WindDelta == 0) {
-      PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
-      edge.WindCnt = (pft == pftNegative ? -1 : 1);
-    } else
-      edge.WindCnt = edge.WindDelta;
-    edge.WindCnt2 = 0;
-    e = m_ActiveEdges; //ie get ready to calc WindCnt2
-  } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
-    edge.WindCnt = 1;
-    edge.WindCnt2 = e->WindCnt2;
-    e = e->NextInAEL; //ie get ready to calc WindCnt2
-  } else if (IsEvenOddFillType(edge)) {
-    //EvenOdd filling ...
-    if (edge.WindDelta == 0) {
-      //are we inside a subj polygon ...
-      bool Inside = true;
-      TEdge *e2 = e->PrevInAEL;
-      while (e2) {
-        if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
-          Inside = !Inside;
-        e2 = e2->PrevInAEL;
-      }
-      edge.WindCnt = (Inside ? 0 : 1);
-    } else {
-      edge.WindCnt = edge.WindDelta;
-    }
-    edge.WindCnt2 = e->WindCnt2;
-    e = e->NextInAEL; //ie get ready to calc WindCnt2
-  } else {
-    //nonZero, Positive or Negative filling ...
-    if (e->WindCnt * e->WindDelta < 0) {
-      //prev edge is 'decreasing' WindCount (WC) toward zero
-      //so we're outside the previous polygon ...
-      if (Abs(e->WindCnt) > 1) {
-        //outside prev poly but still inside another.
-        //when reversing direction of prev poly use the same WC
-        if (e->WindDelta * edge.WindDelta < 0)
-          edge.WindCnt = e->WindCnt;
-          //otherwise continue to 'decrease' WC ...
-        else
-          edge.WindCnt = e->WindCnt + edge.WindDelta;
-      } else
-        //now outside all polys of same polytype so set own WC ...
-        edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
-    } else {
-      //prev edge is 'increasing' WindCount (WC) away from zero
-      //so we're inside the previous polygon ...
-      if (edge.WindDelta == 0)
-        edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
-        //if wind direction is reversing prev then use same WC
-      else if (e->WindDelta * edge.WindDelta < 0)
-        edge.WindCnt = e->WindCnt;
-        //otherwise add to WC ...
-      else
-        edge.WindCnt = e->WindCnt + edge.WindDelta;
-    }
-    edge.WindCnt2 = e->WindCnt2;
-    e = e->NextInAEL; //ie get ready to calc WindCnt2
-  }
-
-  //update WindCnt2 ...
-  if (IsEvenOddAltFillType(edge)) {
-    //EvenOdd filling ...
-    while (e != &edge) {
-      if (e->WindDelta != 0)
-        edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
-      e = e->NextInAEL;
-    }
-  } else {
-    //nonZero, Positive or Negative filling ...
-    while (e != &edge) {
-      edge.WindCnt2 += e->WindDelta;
-      e = e->NextInAEL;
-    }
-  }
+	TEdge *e = edge.PrevInAEL;
+	// find the edge of the same polytype that immediately preceeds 'edge' in AEL
+	while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
+		e = e->PrevInAEL;
+	if (!e) {
+		if (edge.WindDelta == 0) {
+			PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
+			edge.WindCnt = (pft == pftNegative ? -1 : 1);
+		} else
+			edge.WindCnt = edge.WindDelta;
+		edge.WindCnt2 = 0;
+		e = m_ActiveEdges; // ie get ready to calc WindCnt2
+	} else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
+		edge.WindCnt = 1;
+		edge.WindCnt2 = e->WindCnt2;
+		e = e->NextInAEL; // ie get ready to calc WindCnt2
+	} else if (IsEvenOddFillType(edge)) {
+		// EvenOdd filling ...
+		if (edge.WindDelta == 0) {
+			// are we inside a subj polygon ...
+			bool Inside = true;
+			TEdge *e2 = e->PrevInAEL;
+			while (e2) {
+				if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
+					Inside = !Inside;
+				e2 = e2->PrevInAEL;
+			}
+			edge.WindCnt = (Inside ? 0 : 1);
+		} else {
+			edge.WindCnt = edge.WindDelta;
+		}
+		edge.WindCnt2 = e->WindCnt2;
+		e = e->NextInAEL; // ie get ready to calc WindCnt2
+	} else {
+		// nonZero, Positive or Negative filling ...
+		if (e->WindCnt * e->WindDelta < 0) {
+			// prev edge is 'decreasing' WindCount (WC) toward zero
+			// so we're outside the previous polygon ...
+			if (Abs(e->WindCnt) > 1) {
+				// outside prev poly but still inside another.
+				// when reversing direction of prev poly use the same WC
+				if (e->WindDelta * edge.WindDelta < 0)
+					edge.WindCnt = e->WindCnt;
+				// otherwise continue to 'decrease' WC ...
+				else
+					edge.WindCnt = e->WindCnt + edge.WindDelta;
+			} else
+				// now outside all polys of same polytype so set own WC ...
+				edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
+		} else {
+			// prev edge is 'increasing' WindCount (WC) away from zero
+			// so we're inside the previous polygon ...
+			if (edge.WindDelta == 0)
+				edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
+			// if wind direction is reversing prev then use same WC
+			else if (e->WindDelta * edge.WindDelta < 0)
+				edge.WindCnt = e->WindCnt;
+			// otherwise add to WC ...
+			else
+				edge.WindCnt = e->WindCnt + edge.WindDelta;
+		}
+		edge.WindCnt2 = e->WindCnt2;
+		e = e->NextInAEL; // ie get ready to calc WindCnt2
+	}
+
+	// update WindCnt2 ...
+	if (IsEvenOddAltFillType(edge)) {
+		// EvenOdd filling ...
+		while (e != &edge) {
+			if (e->WindDelta != 0)
+				edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
+			e = e->NextInAEL;
+		}
+	} else {
+		// nonZero, Positive or Negative filling ...
+		while (e != &edge) {
+			edge.WindCnt2 += e->WindDelta;
+			e = e->NextInAEL;
+		}
+	}
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::IsEvenOddFillType(const TEdge &edge) const {
-  if (edge.PolyTyp == ptSubject)
-    return m_SubjFillType == pftEvenOdd;
-  else
-    return m_ClipFillType == pftEvenOdd;
+	if (edge.PolyTyp == ptSubject)
+		return m_SubjFillType == pftEvenOdd;
+	else
+		return m_ClipFillType == pftEvenOdd;
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const {
-  if (edge.PolyTyp == ptSubject)
-    return m_ClipFillType == pftEvenOdd;
-  else
-    return m_SubjFillType == pftEvenOdd;
+	if (edge.PolyTyp == ptSubject)
+		return m_ClipFillType == pftEvenOdd;
+	else
+		return m_SubjFillType == pftEvenOdd;
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::IsContributing(const TEdge &edge) const {
-  PolyFillType pft, pft2;
-  if (edge.PolyTyp == ptSubject) {
-    pft = m_SubjFillType;
-    pft2 = m_ClipFillType;
-  } else {
-    pft = m_ClipFillType;
-    pft2 = m_SubjFillType;
-  }
-
-  switch (pft) {
-  case pftEvenOdd:
-    //return false if a subj line has been flagged as inside a subj polygon
-    if (edge.WindDelta == 0 && edge.WindCnt != 1)
-      return false;
-    break;
-  case pftNonZero:
-    if (Abs(edge.WindCnt) != 1)
-      return false;
-    break;
-  case pftPositive:
-    if (edge.WindCnt != 1)
-      return false;
-    break;
-  default: //pftNegative
-    if (edge.WindCnt != -1)
-      return false;
-  }
-
-  switch (m_ClipType) {
-  case ctIntersection:
-    switch (pft2) {
-    case pftEvenOdd:
-    case pftNonZero:return (edge.WindCnt2 != 0);
-    case pftPositive:return (edge.WindCnt2 > 0);
-    default:return (edge.WindCnt2 < 0);
-    }
-    break;
-  case ctUnion:
-    switch (pft2) {
-    case pftEvenOdd:
-    case pftNonZero:return (edge.WindCnt2 == 0);
-    case pftPositive:return (edge.WindCnt2 <= 0);
-    default:return (edge.WindCnt2 >= 0);
-    }
-    break;
-  case ctDifference:
-    if (edge.PolyTyp == ptSubject)
-      switch (pft2) {
-      case pftEvenOdd:
-      case pftNonZero:return (edge.WindCnt2 == 0);
-      case pftPositive:return (edge.WindCnt2 <= 0);
-      default:return (edge.WindCnt2 >= 0);
-      }
-    else
-      switch (pft2) {
-      case pftEvenOdd:
-      case pftNonZero:return (edge.WindCnt2 != 0);
-      case pftPositive:return (edge.WindCnt2 > 0);
-      default:return (edge.WindCnt2 < 0);
-      }
-    break;
-  case ctXor:
-    if (edge.WindDelta == 0) //XOr always contributing unless open
-      switch (pft2) {
-      case pftEvenOdd:
-      case pftNonZero:return (edge.WindCnt2 == 0);
-      case pftPositive:return (edge.WindCnt2 <= 0);
-      default:return (edge.WindCnt2 >= 0);
-      }
-    else
-      return true;
-    break;
-  default:return true;
-  }
+	PolyFillType pft, pft2;
+	if (edge.PolyTyp == ptSubject) {
+		pft = m_SubjFillType;
+		pft2 = m_ClipFillType;
+	} else {
+		pft = m_ClipFillType;
+		pft2 = m_SubjFillType;
+	}
+
+	switch (pft) {
+	case pftEvenOdd:
+		// return false if a subj line has been flagged as inside a subj polygon
+		if (edge.WindDelta == 0 && edge.WindCnt != 1)
+			return false;
+		break;
+	case pftNonZero:
+		if (Abs(edge.WindCnt) != 1)
+			return false;
+		break;
+	case pftPositive:
+		if (edge.WindCnt != 1)
+			return false;
+		break;
+	default: // pftNegative
+		if (edge.WindCnt != -1)
+			return false;
+	}
+
+	switch (m_ClipType) {
+	case ctIntersection:
+		switch (pft2) {
+		case pftEvenOdd:
+		case pftNonZero:
+			return (edge.WindCnt2 != 0);
+		case pftPositive:
+			return (edge.WindCnt2 > 0);
+		default:
+			return (edge.WindCnt2 < 0);
+		}
+		break;
+	case ctUnion:
+		switch (pft2) {
+		case pftEvenOdd:
+		case pftNonZero:
+			return (edge.WindCnt2 == 0);
+		case pftPositive:
+			return (edge.WindCnt2 <= 0);
+		default:
+			return (edge.WindCnt2 >= 0);
+		}
+		break;
+	case ctDifference:
+		if (edge.PolyTyp == ptSubject)
+			switch (pft2) {
+			case pftEvenOdd:
+			case pftNonZero:
+				return (edge.WindCnt2 == 0);
+			case pftPositive:
+				return (edge.WindCnt2 <= 0);
+			default:
+				return (edge.WindCnt2 >= 0);
+			}
+		else
+			switch (pft2) {
+			case pftEvenOdd:
+			case pftNonZero:
+				return (edge.WindCnt2 != 0);
+			case pftPositive:
+				return (edge.WindCnt2 > 0);
+			default:
+				return (edge.WindCnt2 < 0);
+			}
+		break;
+	case ctXor:
+		if (edge.WindDelta == 0) // XOr always contributing unless open
+			switch (pft2) {
+			case pftEvenOdd:
+			case pftNonZero:
+				return (edge.WindCnt2 == 0);
+			case pftPositive:
+				return (edge.WindCnt2 <= 0);
+			default:
+				return (edge.WindCnt2 >= 0);
+			}
+		else
+			return true;
+		break;
+	default:
+		return true;
+	}
 }
 //------------------------------------------------------------------------------
 
 OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
-  OutPt *result;
-  TEdge *e, *prevE;
-  if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
-    result = AddOutPt(e1, Pt);
-    e2->OutIdx = e1->OutIdx;
-    e1->Side = esLeft;
-    e2->Side = esRight;
-    e = e1;
-    if (e->PrevInAEL == e2)
-      prevE = e2->PrevInAEL;
-    else
-      prevE = e->PrevInAEL;
-  } else {
-    result = AddOutPt(e2, Pt);
-    e1->OutIdx = e2->OutIdx;
-    e1->Side = esRight;
-    e2->Side = esLeft;
-    e = e2;
-    if (e->PrevInAEL == e1)
-      prevE = e1->PrevInAEL;
-    else
-      prevE = e->PrevInAEL;
-  }
-
-  if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
-    cInt xPrev = TopX(*prevE, Pt.Y);
-    cInt xE = TopX(*e, Pt.Y);
-    if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
-        SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) {
-      OutPt *outPt = AddOutPt(prevE, Pt);
-      AddJoin(result, outPt, e->Top);
-    }
-  }
-  return result;
+	OutPt *result;
+	TEdge *e, *prevE;
+	if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
+		result = AddOutPt(e1, Pt);
+		e2->OutIdx = e1->OutIdx;
+		e1->Side = esLeft;
+		e2->Side = esRight;
+		e = e1;
+		if (e->PrevInAEL == e2)
+			prevE = e2->PrevInAEL;
+		else
+			prevE = e->PrevInAEL;
+	} else {
+		result = AddOutPt(e2, Pt);
+		e1->OutIdx = e2->OutIdx;
+		e1->Side = esRight;
+		e2->Side = esLeft;
+		e = e2;
+		if (e->PrevInAEL == e1)
+			prevE = e1->PrevInAEL;
+		else
+			prevE = e->PrevInAEL;
+	}
+
+	if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
+		cInt xPrev = TopX(*prevE, Pt.Y);
+		cInt xE = TopX(*e, Pt.Y);
+		if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
+			SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) {
+			OutPt *outPt = AddOutPt(prevE, Pt);
+			AddJoin(result, outPt, e->Top);
+		}
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
-  AddOutPt(e1, Pt);
-  if (e2->WindDelta == 0)
-    AddOutPt(e2, Pt);
-  if (e1->OutIdx == e2->OutIdx) {
-    e1->OutIdx = Unassigned;
-    e2->OutIdx = Unassigned;
-  } else if (e1->OutIdx < e2->OutIdx)
-    AppendPolygon(e1, e2);
-  else
-    AppendPolygon(e2, e1);
+	AddOutPt(e1, Pt);
+	if (e2->WindDelta == 0)
+		AddOutPt(e2, Pt);
+	if (e1->OutIdx == e2->OutIdx) {
+		e1->OutIdx = Unassigned;
+		e2->OutIdx = Unassigned;
+	} else if (e1->OutIdx < e2->OutIdx)
+		AppendPolygon(e1, e2);
+	else
+		AppendPolygon(e2, e1);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::AddEdgeToSEL(TEdge *edge) {
-  //SEL pointers in PEdge are reused to build a list of horizontal edges.
-  //However, we don't need to worry about order with horizontal edge processing.
-  if (!m_SortedEdges) {
-    m_SortedEdges = edge;
-    edge->PrevInSEL = 0;
-    edge->NextInSEL = 0;
-  } else {
-    edge->NextInSEL = m_SortedEdges;
-    edge->PrevInSEL = 0;
-    m_SortedEdges->PrevInSEL = edge;
-    m_SortedEdges = edge;
-  }
+	// SEL pointers in PEdge are reused to build a list of horizontal edges.
+	// However, we don't need to worry about order with horizontal edge processing.
+	if (!m_SortedEdges) {
+		m_SortedEdges = edge;
+		edge->PrevInSEL = 0;
+		edge->NextInSEL = 0;
+	} else {
+		edge->NextInSEL = m_SortedEdges;
+		edge->PrevInSEL = 0;
+		m_SortedEdges->PrevInSEL = edge;
+		m_SortedEdges = edge;
+	}
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::PopEdgeFromSEL(TEdge *&edge) {
-  if (!m_SortedEdges)
-    return false;
-  edge = m_SortedEdges;
-  DeleteFromSEL(m_SortedEdges);
-  return true;
+	if (!m_SortedEdges)
+		return false;
+	edge = m_SortedEdges;
+	DeleteFromSEL(m_SortedEdges);
+	return true;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::CopyAELToSEL() {
-  TEdge *e = m_ActiveEdges;
-  m_SortedEdges = e;
-  while (e) {
-    e->PrevInSEL = e->PrevInAEL;
-    e->NextInSEL = e->NextInAEL;
-    e = e->NextInAEL;
-  }
+	TEdge *e = m_ActiveEdges;
+	m_SortedEdges = e;
+	while (e) {
+		e->PrevInSEL = e->PrevInAEL;
+		e->NextInSEL = e->NextInAEL;
+		e = e->NextInAEL;
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) {
-  Join *j = new Join;
-  j->OutPt1 = op1;
-  j->OutPt2 = op2;
-  j->OffPt = OffPt;
-  m_Joins.push_back(j);
+	Join *j = new Join;
+	j->OutPt1 = op1;
+	j->OutPt2 = op2;
+	j->OffPt = OffPt;
+	m_Joins.push_back(j);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::ClearJoins() {
-  for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
-    delete m_Joins[i];
-  m_Joins.resize(0);
+	for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+		delete m_Joins[i];
+	m_Joins.resize(0);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::ClearGhostJoins() {
-  for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
-    delete m_GhostJoins[i];
-  m_GhostJoins.resize(0);
+	for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
+		delete m_GhostJoins[i];
+	m_GhostJoins.resize(0);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) {
-  Join *j = new Join;
-  j->OutPt1 = op;
-  j->OutPt2 = 0;
-  j->OffPt = OffPt;
-  m_GhostJoins.push_back(j);
+	Join *j = new Join;
+	j->OutPt1 = op;
+	j->OutPt2 = 0;
+	j->OffPt = OffPt;
+	m_GhostJoins.push_back(j);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) {
-  const LocalMinimum *lm;
-  while (PopLocalMinima(botY, lm)) {
-    TEdge *lb = lm->LeftBound;
-    TEdge *rb = lm->RightBound;
-
-    OutPt *Op1 = 0;
-    if (!lb) {
-      //nb: don't insert LB into either AEL or SEL
-      InsertEdgeIntoAEL(rb, 0);
-      SetWindingCount(*rb);
-      if (IsContributing(*rb))
-        Op1 = AddOutPt(rb, rb->Bot);
-    } else if (!rb) {
-      InsertEdgeIntoAEL(lb, 0);
-      SetWindingCount(*lb);
-      if (IsContributing(*lb))
-        Op1 = AddOutPt(lb, lb->Bot);
-      InsertScanbeam(lb->Top.Y);
-    } else {
-      InsertEdgeIntoAEL(lb, 0);
-      InsertEdgeIntoAEL(rb, lb);
-      SetWindingCount(*lb);
-      rb->WindCnt = lb->WindCnt;
-      rb->WindCnt2 = lb->WindCnt2;
-      if (IsContributing(*lb))
-        Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
-      InsertScanbeam(lb->Top.Y);
-    }
-
-    if (rb) {
-      if (IsHorizontal(*rb)) {
-        AddEdgeToSEL(rb);
-        if (rb->NextInLML)
-          InsertScanbeam(rb->NextInLML->Top.Y);
-      } else
-        InsertScanbeam(rb->Top.Y);
-    }
-
-    if (!lb || !rb)
-      continue;
-
-    //if any output polygons share an edge, they'll need joining later ...
-    if (Op1 && IsHorizontal(*rb) &&
-        m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) {
-      for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
-        Join *jr = m_GhostJoins[i];
-        //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
-        //the 'ghost' join to a real join ready for later ...
-        if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X))
-          AddJoin(jr->OutPt1, Op1, jr->OffPt);
-      }
-    }
-
-    if (lb->OutIdx >= 0 && lb->PrevInAEL &&
-        lb->PrevInAEL->Curr.X == lb->Bot.X &&
-        lb->PrevInAEL->OutIdx >= 0 &&
-        SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) &&
-        (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
-      OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
-      AddJoin(Op1, Op2, lb->Top);
-    }
-
-    if (lb->NextInAEL != rb) {
-
-      if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
-          SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) &&
-          (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
-        OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
-        AddJoin(Op1, Op2, rb->Top);
-      }
-
-      TEdge *e = lb->NextInAEL;
-      if (e) {
-        while (e != rb) {
-          //nb: For calculating winding counts etc, IntersectEdges() assumes
-          //that param1 will be to the Right of param2 ABOVE the intersection ...
-          IntersectEdges(rb, e, lb->Curr); //order important here
-          e = e->NextInAEL;
-        }
-      }
-    }
-
-  }
+	const LocalMinimum *lm;
+	while (PopLocalMinima(botY, lm)) {
+		TEdge *lb = lm->LeftBound;
+		TEdge *rb = lm->RightBound;
+
+		OutPt *Op1 = 0;
+		if (!lb) {
+			// nb: don't insert LB into either AEL or SEL
+			InsertEdgeIntoAEL(rb, 0);
+			SetWindingCount(*rb);
+			if (IsContributing(*rb))
+				Op1 = AddOutPt(rb, rb->Bot);
+		} else if (!rb) {
+			InsertEdgeIntoAEL(lb, 0);
+			SetWindingCount(*lb);
+			if (IsContributing(*lb))
+				Op1 = AddOutPt(lb, lb->Bot);
+			InsertScanbeam(lb->Top.Y);
+		} else {
+			InsertEdgeIntoAEL(lb, 0);
+			InsertEdgeIntoAEL(rb, lb);
+			SetWindingCount(*lb);
+			rb->WindCnt = lb->WindCnt;
+			rb->WindCnt2 = lb->WindCnt2;
+			if (IsContributing(*lb))
+				Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
+			InsertScanbeam(lb->Top.Y);
+		}
+
+		if (rb) {
+			if (IsHorizontal(*rb)) {
+				AddEdgeToSEL(rb);
+				if (rb->NextInLML)
+					InsertScanbeam(rb->NextInLML->Top.Y);
+			} else
+				InsertScanbeam(rb->Top.Y);
+		}
+
+		if (!lb || !rb)
+			continue;
+
+		// if any output polygons share an edge, they'll need joining later ...
+		if (Op1 && IsHorizontal(*rb) &&
+			m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) {
+			for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
+				Join *jr = m_GhostJoins[i];
+				// if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+				// the 'ghost' join to a real join ready for later ...
+				if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X))
+					AddJoin(jr->OutPt1, Op1, jr->OffPt);
+			}
+		}
+
+		if (lb->OutIdx >= 0 && lb->PrevInAEL &&
+			lb->PrevInAEL->Curr.X == lb->Bot.X &&
+			lb->PrevInAEL->OutIdx >= 0 &&
+			SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) &&
+			(lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
+			OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
+			AddJoin(Op1, Op2, lb->Top);
+		}
+
+		if (lb->NextInAEL != rb) {
+
+			if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
+				SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) &&
+				(rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
+				OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
+				AddJoin(Op1, Op2, rb->Top);
+			}
+
+			TEdge *e = lb->NextInAEL;
+			if (e) {
+				while (e != rb) {
+					// nb: For calculating winding counts etc, IntersectEdges() assumes
+					// that param1 will be to the Right of param2 ABOVE the intersection ...
+					IntersectEdges(rb, e, lb->Curr); // order important here
+					e = e->NextInAEL;
+				}
+			}
+		}
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::DeleteFromSEL(TEdge *e) {
-  TEdge *SelPrev = e->PrevInSEL;
-  TEdge *SelNext = e->NextInSEL;
-  if (!SelPrev && !SelNext && (e != m_SortedEdges))
-    return; //already deleted
-  if (SelPrev)
-    SelPrev->NextInSEL = SelNext;
-  else
-    m_SortedEdges = SelNext;
-  if (SelNext)
-    SelNext->PrevInSEL = SelPrev;
-  e->NextInSEL = 0;
-  e->PrevInSEL = 0;
+	TEdge *SelPrev = e->PrevInSEL;
+	TEdge *SelNext = e->NextInSEL;
+	if (!SelPrev && !SelNext && (e != m_SortedEdges))
+		return; // already deleted
+	if (SelPrev)
+		SelPrev->NextInSEL = SelNext;
+	else
+		m_SortedEdges = SelNext;
+	if (SelNext)
+		SelNext->PrevInSEL = SelPrev;
+	e->NextInSEL = 0;
+	e->PrevInSEL = 0;
 }
 //------------------------------------------------------------------------------
 
 #ifdef use_xyz
-void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
-{
-  if (pt.Z != 0 || !m_ZFill) return;
-  else if (pt == e1.Bot) pt.Z = e1.Bot.Z;
-  else if (pt == e1.Top) pt.Z = e1.Top.Z;
-  else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
-  else if (pt == e2.Top) pt.Z = e2.Top.Z;
-  else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
+void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) {
+	if (pt.Z != 0 || !m_ZFill)
+		return;
+	else if (pt == e1.Bot)
+		pt.Z = e1.Bot.Z;
+	else if (pt == e1.Top)
+		pt.Z = e1.Top.Z;
+	else if (pt == e2.Bot)
+		pt.Z = e2.Bot.Z;
+	else if (pt == e2.Top)
+		pt.Z = e2.Top.Z;
+	else
+		(*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
 }
 //------------------------------------------------------------------------------
 #endif
 
 void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) {
-  bool e1Contributing = (e1->OutIdx >= 0);
-  bool e2Contributing = (e2->OutIdx >= 0);
+	bool e1Contributing = (e1->OutIdx >= 0);
+	bool e2Contributing = (e2->OutIdx >= 0);
 
 #ifdef use_xyz
-  SetZ(Pt, *e1, *e2);
+	SetZ(Pt, *e1, *e2);
 #endif
 
 #ifdef use_lines
-  //if either edge is on an OPEN path ...
-  if (e1->WindDelta == 0 || e2->WindDelta == 0) {
-    //ignore subject-subject open path intersections UNLESS they
-    //are both open paths, AND they are both 'contributing maximas' ...
-    if (e1->WindDelta == 0 && e2->WindDelta == 0)
-      return;
-
-      //if intersecting a subj line with a subj poly ...
-    else if (e1->PolyTyp == e2->PolyTyp &&
-        e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) {
-      if (e1->WindDelta == 0) {
-        if (e2Contributing) {
-          AddOutPt(e1, Pt);
-          if (e1Contributing)
-            e1->OutIdx = Unassigned;
-        }
-      } else {
-        if (e1Contributing) {
-          AddOutPt(e2, Pt);
-          if (e2Contributing)
-            e2->OutIdx = Unassigned;
-        }
-      }
-    } else if (e1->PolyTyp != e2->PolyTyp) {
-      //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
-      if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
-          (m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
-        AddOutPt(e1, Pt);
-        if (e1Contributing)
-          e1->OutIdx = Unassigned;
-      } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
-          (m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
-        AddOutPt(e2, Pt);
-        if (e2Contributing)
-          e2->OutIdx = Unassigned;
-      }
-    }
-    return;
-  }
+	// if either edge is on an OPEN path ...
+	if (e1->WindDelta == 0 || e2->WindDelta == 0) {
+		// ignore subject-subject open path intersections UNLESS they
+		// are both open paths, AND they are both 'contributing maximas' ...
+		if (e1->WindDelta == 0 && e2->WindDelta == 0)
+			return;
+
+		// if intersecting a subj line with a subj poly ...
+		else if (e1->PolyTyp == e2->PolyTyp &&
+				 e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) {
+			if (e1->WindDelta == 0) {
+				if (e2Contributing) {
+					AddOutPt(e1, Pt);
+					if (e1Contributing)
+						e1->OutIdx = Unassigned;
+				}
+			} else {
+				if (e1Contributing) {
+					AddOutPt(e2, Pt);
+					if (e2Contributing)
+						e2->OutIdx = Unassigned;
+				}
+			}
+		} else if (e1->PolyTyp != e2->PolyTyp) {
+			// toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
+			if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
+				(m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
+				AddOutPt(e1, Pt);
+				if (e1Contributing)
+					e1->OutIdx = Unassigned;
+			} else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
+					   (m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
+				AddOutPt(e2, Pt);
+				if (e2Contributing)
+					e2->OutIdx = Unassigned;
+			}
+		}
+		return;
+	}
 #endif
 
-  //update winding counts...
-  //assumes that e1 will be to the Right of e2 ABOVE the intersection
-  if (e1->PolyTyp == e2->PolyTyp) {
-    if (IsEvenOddFillType(*e1)) {
-      int oldE1WindCnt = e1->WindCnt;
-      e1->WindCnt = e2->WindCnt;
-      e2->WindCnt = oldE1WindCnt;
-    } else {
-      if (e1->WindCnt + e2->WindDelta == 0)
-        e1->WindCnt = -e1->WindCnt;
-      else
-        e1->WindCnt += e2->WindDelta;
-      if (e2->WindCnt - e1->WindDelta == 0)
-        e2->WindCnt = -e2->WindCnt;
-      else
-        e2->WindCnt -= e1->WindDelta;
-    }
-  } else {
-    if (!IsEvenOddFillType(*e2))
-      e1->WindCnt2 += e2->WindDelta;
-    else
-      e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
-    if (!IsEvenOddFillType(*e1))
-      e2->WindCnt2 -= e1->WindDelta;
-    else
-      e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
-  }
-
-  PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
-  if (e1->PolyTyp == ptSubject) {
-    e1FillType = m_SubjFillType;
-    e1FillType2 = m_ClipFillType;
-  } else {
-    e1FillType = m_ClipFillType;
-    e1FillType2 = m_SubjFillType;
-  }
-  if (e2->PolyTyp == ptSubject) {
-    e2FillType = m_SubjFillType;
-    e2FillType2 = m_ClipFillType;
-  } else {
-    e2FillType = m_ClipFillType;
-    e2FillType2 = m_SubjFillType;
-  }
-
-  cInt e1Wc, e2Wc;
-  switch (e1FillType) {
-  case pftPositive: e1Wc = e1->WindCnt;
-    break;
-  case pftNegative: e1Wc = -e1->WindCnt;
-    break;
-  default: e1Wc = Abs(e1->WindCnt);
-  }
-  switch (e2FillType) {
-  case pftPositive: e2Wc = e2->WindCnt;
-    break;
-  case pftNegative: e2Wc = -e2->WindCnt;
-    break;
-  default: e2Wc = Abs(e2->WindCnt);
-  }
-
-  if (e1Contributing && e2Contributing) {
-    if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
-        (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
-      AddLocalMaxPoly(e1, e2, Pt);
-    } else {
-      AddOutPt(e1, Pt);
-      AddOutPt(e2, Pt);
-      SwapSides(*e1, *e2);
-      SwapPolyIndexes(*e1, *e2);
-    }
-  } else if (e1Contributing) {
-    if (e2Wc == 0 || e2Wc == 1) {
-      AddOutPt(e1, Pt);
-      SwapSides(*e1, *e2);
-      SwapPolyIndexes(*e1, *e2);
-    }
-  } else if (e2Contributing) {
-    if (e1Wc == 0 || e1Wc == 1) {
-      AddOutPt(e2, Pt);
-      SwapSides(*e1, *e2);
-      SwapPolyIndexes(*e1, *e2);
-    }
-  } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
-    //neither edge is currently contributing ...
-
-    cInt e1Wc2, e2Wc2;
-    switch (e1FillType2) {
-    case pftPositive: e1Wc2 = e1->WindCnt2;
-      break;
-    case pftNegative : e1Wc2 = -e1->WindCnt2;
-      break;
-    default: e1Wc2 = Abs(e1->WindCnt2);
-    }
-    switch (e2FillType2) {
-    case pftPositive: e2Wc2 = e2->WindCnt2;
-      break;
-    case pftNegative: e2Wc2 = -e2->WindCnt2;
-      break;
-    default: e2Wc2 = Abs(e2->WindCnt2);
-    }
-
-    if (e1->PolyTyp != e2->PolyTyp) {
-      AddLocalMinPoly(e1, e2, Pt);
-    } else if (e1Wc == 1 && e2Wc == 1)
-      switch (m_ClipType) {
-      case ctIntersection:
-        if (e1Wc2 > 0 && e2Wc2 > 0)
-          AddLocalMinPoly(e1, e2, Pt);
-        break;
-      case ctUnion:
-        if (e1Wc2 <= 0 && e2Wc2 <= 0)
-          AddLocalMinPoly(e1, e2, Pt);
-        break;
-      case ctDifference:
-        if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
-            ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
-          AddLocalMinPoly(e1, e2, Pt);
-        break;
-      case ctXor:AddLocalMinPoly(e1, e2, Pt);
-      }
-    else
-      SwapSides(*e1, *e2);
-  }
+	// update winding counts...
+	// assumes that e1 will be to the Right of e2 ABOVE the intersection
+	if (e1->PolyTyp == e2->PolyTyp) {
+		if (IsEvenOddFillType(*e1)) {
+			int oldE1WindCnt = e1->WindCnt;
+			e1->WindCnt = e2->WindCnt;
+			e2->WindCnt = oldE1WindCnt;
+		} else {
+			if (e1->WindCnt + e2->WindDelta == 0)
+				e1->WindCnt = -e1->WindCnt;
+			else
+				e1->WindCnt += e2->WindDelta;
+			if (e2->WindCnt - e1->WindDelta == 0)
+				e2->WindCnt = -e2->WindCnt;
+			else
+				e2->WindCnt -= e1->WindDelta;
+		}
+	} else {
+		if (!IsEvenOddFillType(*e2))
+			e1->WindCnt2 += e2->WindDelta;
+		else
+			e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
+		if (!IsEvenOddFillType(*e1))
+			e2->WindCnt2 -= e1->WindDelta;
+		else
+			e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
+	}
+
+	PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
+	if (e1->PolyTyp == ptSubject) {
+		e1FillType = m_SubjFillType;
+		e1FillType2 = m_ClipFillType;
+	} else {
+		e1FillType = m_ClipFillType;
+		e1FillType2 = m_SubjFillType;
+	}
+	if (e2->PolyTyp == ptSubject) {
+		e2FillType = m_SubjFillType;
+		e2FillType2 = m_ClipFillType;
+	} else {
+		e2FillType = m_ClipFillType;
+		e2FillType2 = m_SubjFillType;
+	}
+
+	cInt e1Wc, e2Wc;
+	switch (e1FillType) {
+	case pftPositive:
+		e1Wc = e1->WindCnt;
+		break;
+	case pftNegative:
+		e1Wc = -e1->WindCnt;
+		break;
+	default:
+		e1Wc = Abs(e1->WindCnt);
+	}
+	switch (e2FillType) {
+	case pftPositive:
+		e2Wc = e2->WindCnt;
+		break;
+	case pftNegative:
+		e2Wc = -e2->WindCnt;
+		break;
+	default:
+		e2Wc = Abs(e2->WindCnt);
+	}
+
+	if (e1Contributing && e2Contributing) {
+		if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
+			(e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
+			AddLocalMaxPoly(e1, e2, Pt);
+		} else {
+			AddOutPt(e1, Pt);
+			AddOutPt(e2, Pt);
+			SwapSides(*e1, *e2);
+			SwapPolyIndexes(*e1, *e2);
+		}
+	} else if (e1Contributing) {
+		if (e2Wc == 0 || e2Wc == 1) {
+			AddOutPt(e1, Pt);
+			SwapSides(*e1, *e2);
+			SwapPolyIndexes(*e1, *e2);
+		}
+	} else if (e2Contributing) {
+		if (e1Wc == 0 || e1Wc == 1) {
+			AddOutPt(e2, Pt);
+			SwapSides(*e1, *e2);
+			SwapPolyIndexes(*e1, *e2);
+		}
+	} else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
+		// neither edge is currently contributing ...
+
+		cInt e1Wc2, e2Wc2;
+		switch (e1FillType2) {
+		case pftPositive:
+			e1Wc2 = e1->WindCnt2;
+			break;
+		case pftNegative:
+			e1Wc2 = -e1->WindCnt2;
+			break;
+		default:
+			e1Wc2 = Abs(e1->WindCnt2);
+		}
+		switch (e2FillType2) {
+		case pftPositive:
+			e2Wc2 = e2->WindCnt2;
+			break;
+		case pftNegative:
+			e2Wc2 = -e2->WindCnt2;
+			break;
+		default:
+			e2Wc2 = Abs(e2->WindCnt2);
+		}
+
+		if (e1->PolyTyp != e2->PolyTyp) {
+			AddLocalMinPoly(e1, e2, Pt);
+		} else if (e1Wc == 1 && e2Wc == 1)
+			switch (m_ClipType) {
+			case ctIntersection:
+				if (e1Wc2 > 0 && e2Wc2 > 0)
+					AddLocalMinPoly(e1, e2, Pt);
+				break;
+			case ctUnion:
+				if (e1Wc2 <= 0 && e2Wc2 <= 0)
+					AddLocalMinPoly(e1, e2, Pt);
+				break;
+			case ctDifference:
+				if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+					((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+					AddLocalMinPoly(e1, e2, Pt);
+				break;
+			case ctXor:
+				AddLocalMinPoly(e1, e2, Pt);
+			}
+		else
+			SwapSides(*e1, *e2);
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::SetHoleState(TEdge *e, OutRec *outrec) {
-  TEdge *e2 = e->PrevInAEL;
-  TEdge *eTmp = 0;
-  while (e2) {
-    if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
-      if (!eTmp)
-        eTmp = e2;
-      else if (eTmp->OutIdx == e2->OutIdx)
-        eTmp = 0;
-    }
-    e2 = e2->PrevInAEL;
-  }
-  if (!eTmp) {
-    outrec->FirstLeft = 0;
-    outrec->IsHole = false;
-  } else {
-    outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
-    outrec->IsHole = !outrec->FirstLeft->IsHole;
-  }
+	TEdge *e2 = e->PrevInAEL;
+	TEdge *eTmp = 0;
+	while (e2) {
+		if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
+			if (!eTmp)
+				eTmp = e2;
+			else if (eTmp->OutIdx == e2->OutIdx)
+				eTmp = 0;
+		}
+		e2 = e2->PrevInAEL;
+	}
+	if (!eTmp) {
+		outrec->FirstLeft = 0;
+		outrec->IsHole = false;
+	} else {
+		outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
+		outrec->IsHole = !outrec->FirstLeft->IsHole;
+	}
 }
 //------------------------------------------------------------------------------
 
 OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) {
-  //work out which polygon fragment has the correct hole state ...
-  if (!outRec1->BottomPt)
-    outRec1->BottomPt = GetBottomPt(outRec1->Pts);
-  if (!outRec2->BottomPt)
-    outRec2->BottomPt = GetBottomPt(outRec2->Pts);
-  OutPt *OutPt1 = outRec1->BottomPt;
-  OutPt *OutPt2 = outRec2->BottomPt;
-  if (OutPt1->Pt.Y > OutPt2->Pt.Y)
-    return outRec1;
-  else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
-    return outRec2;
-  else if (OutPt1->Pt.X < OutPt2->Pt.X)
-    return outRec1;
-  else if (OutPt1->Pt.X > OutPt2->Pt.X)
-    return outRec2;
-  else if (OutPt1->Next == OutPt1)
-    return outRec2;
-  else if (OutPt2->Next == OutPt2)
-    return outRec1;
-  else if (FirstIsBottomPt(OutPt1, OutPt2))
-    return outRec1;
-  else
-    return outRec2;
+	// work out which polygon fragment has the correct hole state ...
+	if (!outRec1->BottomPt)
+		outRec1->BottomPt = GetBottomPt(outRec1->Pts);
+	if (!outRec2->BottomPt)
+		outRec2->BottomPt = GetBottomPt(outRec2->Pts);
+	OutPt *OutPt1 = outRec1->BottomPt;
+	OutPt *OutPt2 = outRec2->BottomPt;
+	if (OutPt1->Pt.Y > OutPt2->Pt.Y)
+		return outRec1;
+	else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
+		return outRec2;
+	else if (OutPt1->Pt.X < OutPt2->Pt.X)
+		return outRec1;
+	else if (OutPt1->Pt.X > OutPt2->Pt.X)
+		return outRec2;
+	else if (OutPt1->Next == OutPt1)
+		return outRec2;
+	else if (OutPt2->Next == OutPt2)
+		return outRec1;
+	else if (FirstIsBottomPt(OutPt1, OutPt2))
+		return outRec1;
+	else
+		return outRec2;
 }
 //------------------------------------------------------------------------------
 
 bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) {
-  do {
-    outRec1 = outRec1->FirstLeft;
-    if (outRec1 == outRec2)
-      return true;
-  } while (outRec1);
-  return false;
+	do {
+		outRec1 = outRec1->FirstLeft;
+		if (outRec1 == outRec2)
+			return true;
+	} while (outRec1);
+	return false;
 }
 //------------------------------------------------------------------------------
 
 OutRec *Clipper::GetOutRec(int Idx) {
-  OutRec *outrec = m_PolyOuts[Idx];
-  while (outrec != m_PolyOuts[outrec->Idx])
-    outrec = m_PolyOuts[outrec->Idx];
-  return outrec;
+	OutRec *outrec = m_PolyOuts[Idx];
+	while (outrec != m_PolyOuts[outrec->Idx])
+		outrec = m_PolyOuts[outrec->Idx];
+	return outrec;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) {
-  //get the start and ends of both output polygons ...
-  OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
-  OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
-
-  OutRec *holeStateRec;
-  if (OutRec1RightOfOutRec2(outRec1, outRec2))
-    holeStateRec = outRec2;
-  else if (OutRec1RightOfOutRec2(outRec2, outRec1))
-    holeStateRec = outRec1;
-  else
-    holeStateRec = GetLowermostRec(outRec1, outRec2);
-
-  //get the start and ends of both output polygons and
-  //join e2 poly onto e1 poly and delete pointers to e2 ...
-
-  OutPt *p1_lft = outRec1->Pts;
-  OutPt *p1_rt = p1_lft->Prev;
-  OutPt *p2_lft = outRec2->Pts;
-  OutPt *p2_rt = p2_lft->Prev;
-
-  //join e2 poly onto e1 poly and delete pointers to e2 ...
-  if (e1->Side == esLeft) {
-    if (e2->Side == esLeft) {
-      //z y x a b c
-      ReversePolyPtLinks(p2_lft);
-      p2_lft->Next = p1_lft;
-      p1_lft->Prev = p2_lft;
-      p1_rt->Next = p2_rt;
-      p2_rt->Prev = p1_rt;
-      outRec1->Pts = p2_rt;
-    } else {
-      //x y z a b c
-      p2_rt->Next = p1_lft;
-      p1_lft->Prev = p2_rt;
-      p2_lft->Prev = p1_rt;
-      p1_rt->Next = p2_lft;
-      outRec1->Pts = p2_lft;
-    }
-  } else {
-    if (e2->Side == esRight) {
-      //a b c z y x
-      ReversePolyPtLinks(p2_lft);
-      p1_rt->Next = p2_rt;
-      p2_rt->Prev = p1_rt;
-      p2_lft->Next = p1_lft;
-      p1_lft->Prev = p2_lft;
-    } else {
-      //a b c x y z
-      p1_rt->Next = p2_lft;
-      p2_lft->Prev = p1_rt;
-      p1_lft->Prev = p2_rt;
-      p2_rt->Next = p1_lft;
-    }
-  }
-
-  outRec1->BottomPt = 0;
-  if (holeStateRec == outRec2) {
-    if (outRec2->FirstLeft != outRec1)
-      outRec1->FirstLeft = outRec2->FirstLeft;
-    outRec1->IsHole = outRec2->IsHole;
-  }
-  outRec2->Pts = 0;
-  outRec2->BottomPt = 0;
-  outRec2->FirstLeft = outRec1;
-
-  int OKIdx = e1->OutIdx;
-  int ObsoleteIdx = e2->OutIdx;
-
-  e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly
-  e2->OutIdx = Unassigned;
-
-  TEdge *e = m_ActiveEdges;
-  while (e) {
-    if (e->OutIdx == ObsoleteIdx) {
-      e->OutIdx = OKIdx;
-      e->Side = e1->Side;
-      break;
-    }
-    e = e->NextInAEL;
-  }
-
-  outRec2->Idx = outRec1->Idx;
+	// get the start and ends of both output polygons ...
+	OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
+	OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
+
+	OutRec *holeStateRec;
+	if (OutRec1RightOfOutRec2(outRec1, outRec2))
+		holeStateRec = outRec2;
+	else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+		holeStateRec = outRec1;
+	else
+		holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+	// get the start and ends of both output polygons and
+	// join e2 poly onto e1 poly and delete pointers to e2 ...
+
+	OutPt *p1_lft = outRec1->Pts;
+	OutPt *p1_rt = p1_lft->Prev;
+	OutPt *p2_lft = outRec2->Pts;
+	OutPt *p2_rt = p2_lft->Prev;
+
+	// join e2 poly onto e1 poly and delete pointers to e2 ...
+	if (e1->Side == esLeft) {
+		if (e2->Side == esLeft) {
+			// z y x a b c
+			ReversePolyPtLinks(p2_lft);
+			p2_lft->Next = p1_lft;
+			p1_lft->Prev = p2_lft;
+			p1_rt->Next = p2_rt;
+			p2_rt->Prev = p1_rt;
+			outRec1->Pts = p2_rt;
+		} else {
+			// x y z a b c
+			p2_rt->Next = p1_lft;
+			p1_lft->Prev = p2_rt;
+			p2_lft->Prev = p1_rt;
+			p1_rt->Next = p2_lft;
+			outRec1->Pts = p2_lft;
+		}
+	} else {
+		if (e2->Side == esRight) {
+			// a b c z y x
+			ReversePolyPtLinks(p2_lft);
+			p1_rt->Next = p2_rt;
+			p2_rt->Prev = p1_rt;
+			p2_lft->Next = p1_lft;
+			p1_lft->Prev = p2_lft;
+		} else {
+			// a b c x y z
+			p1_rt->Next = p2_lft;
+			p2_lft->Prev = p1_rt;
+			p1_lft->Prev = p2_rt;
+			p2_rt->Next = p1_lft;
+		}
+	}
+
+	outRec1->BottomPt = 0;
+	if (holeStateRec == outRec2) {
+		if (outRec2->FirstLeft != outRec1)
+			outRec1->FirstLeft = outRec2->FirstLeft;
+		outRec1->IsHole = outRec2->IsHole;
+	}
+	outRec2->Pts = 0;
+	outRec2->BottomPt = 0;
+	outRec2->FirstLeft = outRec1;
+
+	int OKIdx = e1->OutIdx;
+	int ObsoleteIdx = e2->OutIdx;
+
+	e1->OutIdx = Unassigned; // nb: safe because we only get here via AddLocalMaxPoly
+	e2->OutIdx = Unassigned;
+
+	TEdge *e = m_ActiveEdges;
+	while (e) {
+		if (e->OutIdx == ObsoleteIdx) {
+			e->OutIdx = OKIdx;
+			e->Side = e1->Side;
+			break;
+		}
+		e = e->NextInAEL;
+	}
+
+	outRec2->Idx = outRec1->Idx;
 }
 //------------------------------------------------------------------------------
 
 OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) {
-  if (e->OutIdx < 0) {
-    OutRec *outRec = CreateOutRec();
-    outRec->IsOpen = (e->WindDelta == 0);
-    OutPt *newOp = new OutPt;
-    outRec->Pts = newOp;
-    newOp->Idx = outRec->Idx;
-    newOp->Pt = pt;
-    newOp->Next = newOp;
-    newOp->Prev = newOp;
-    if (!outRec->IsOpen)
-      SetHoleState(e, outRec);
-    e->OutIdx = outRec->Idx;
-    return newOp;
-  } else {
-    OutRec *outRec = m_PolyOuts[e->OutIdx];
-    //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
-    OutPt *op = outRec->Pts;
-
-    bool ToFront = (e->Side == esLeft);
-    if (ToFront && (pt == op->Pt))
-      return op;
-    else if (!ToFront && (pt == op->Prev->Pt))
-      return op->Prev;
-
-    OutPt *newOp = new OutPt;
-    newOp->Idx = outRec->Idx;
-    newOp->Pt = pt;
-    newOp->Next = op;
-    newOp->Prev = op->Prev;
-    newOp->Prev->Next = newOp;
-    op->Prev = newOp;
-    if (ToFront)
-      outRec->Pts = newOp;
-    return newOp;
-  }
+	if (e->OutIdx < 0) {
+		OutRec *outRec = CreateOutRec();
+		outRec->IsOpen = (e->WindDelta == 0);
+		OutPt *newOp = new OutPt;
+		outRec->Pts = newOp;
+		newOp->Idx = outRec->Idx;
+		newOp->Pt = pt;
+		newOp->Next = newOp;
+		newOp->Prev = newOp;
+		if (!outRec->IsOpen)
+			SetHoleState(e, outRec);
+		e->OutIdx = outRec->Idx;
+		return newOp;
+	} else {
+		OutRec *outRec = m_PolyOuts[e->OutIdx];
+		// OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
+		OutPt *op = outRec->Pts;
+
+		bool ToFront = (e->Side == esLeft);
+		if (ToFront && (pt == op->Pt))
+			return op;
+		else if (!ToFront && (pt == op->Prev->Pt))
+			return op->Prev;
+
+		OutPt *newOp = new OutPt;
+		newOp->Idx = outRec->Idx;
+		newOp->Pt = pt;
+		newOp->Next = op;
+		newOp->Prev = op->Prev;
+		newOp->Prev->Next = newOp;
+		op->Prev = newOp;
+		if (ToFront)
+			outRec->Pts = newOp;
+		return newOp;
+	}
 }
 //------------------------------------------------------------------------------
 
 OutPt *Clipper::GetLastOutPt(TEdge *e) {
-  OutRec *outRec = m_PolyOuts[e->OutIdx];
-  if (e->Side == esLeft)
-    return outRec->Pts;
-  else
-    return outRec->Pts->Prev;
+	OutRec *outRec = m_PolyOuts[e->OutIdx];
+	if (e->Side == esLeft)
+		return outRec->Pts;
+	else
+		return outRec->Pts->Prev;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::ProcessHorizontals() {
-  TEdge *horzEdge;
-  while (PopEdgeFromSEL(horzEdge))
-    ProcessHorizontal(horzEdge);
+	TEdge *horzEdge;
+	while (PopEdgeFromSEL(horzEdge))
+		ProcessHorizontal(horzEdge);
 }
 //------------------------------------------------------------------------------
 
 inline bool IsMinima(TEdge *e) {
-  return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
+	return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
 }
 //------------------------------------------------------------------------------
 
 inline bool IsMaxima(TEdge *e, const cInt Y) {
-  return e && e->Top.Y == Y && !e->NextInLML;
+	return e && e->Top.Y == Y && !e->NextInLML;
 }
 //------------------------------------------------------------------------------
 
 inline bool IsIntermediate(TEdge *e, const cInt Y) {
-  return e->Top.Y == Y && e->NextInLML;
+	return e->Top.Y == Y && e->NextInLML;
 }
 //------------------------------------------------------------------------------
 
 TEdge *GetMaximaPair(TEdge *e) {
-  if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
-    return e->Next;
-  else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
-    return e->Prev;
-  else
-    return 0;
+	if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
+		return e->Next;
+	else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
+		return e->Prev;
+	else
+		return 0;
 }
 //------------------------------------------------------------------------------
 
 TEdge *GetMaximaPairEx(TEdge *e) {
-  //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal)
-  TEdge *result = GetMaximaPair(e);
-  if (result && (result->OutIdx == Skip ||
-      (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
-    return 0;
-  return result;
+	// as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal)
+	TEdge *result = GetMaximaPair(e);
+	if (result && (result->OutIdx == Skip ||
+				   (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
+		return 0;
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) {
-  if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
-    return;
-  if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
-    return;
-
-  if (Edge1->NextInSEL == Edge2) {
-    TEdge *Next = Edge2->NextInSEL;
-    if (Next)
-      Next->PrevInSEL = Edge1;
-    TEdge *Prev = Edge1->PrevInSEL;
-    if (Prev)
-      Prev->NextInSEL = Edge2;
-    Edge2->PrevInSEL = Prev;
-    Edge2->NextInSEL = Edge1;
-    Edge1->PrevInSEL = Edge2;
-    Edge1->NextInSEL = Next;
-  } else if (Edge2->NextInSEL == Edge1) {
-    TEdge *Next = Edge1->NextInSEL;
-    if (Next)
-      Next->PrevInSEL = Edge2;
-    TEdge *Prev = Edge2->PrevInSEL;
-    if (Prev)
-      Prev->NextInSEL = Edge1;
-    Edge1->PrevInSEL = Prev;
-    Edge1->NextInSEL = Edge2;
-    Edge2->PrevInSEL = Edge1;
-    Edge2->NextInSEL = Next;
-  } else {
-    TEdge *Next = Edge1->NextInSEL;
-    TEdge *Prev = Edge1->PrevInSEL;
-    Edge1->NextInSEL = Edge2->NextInSEL;
-    if (Edge1->NextInSEL)
-      Edge1->NextInSEL->PrevInSEL = Edge1;
-    Edge1->PrevInSEL = Edge2->PrevInSEL;
-    if (Edge1->PrevInSEL)
-      Edge1->PrevInSEL->NextInSEL = Edge1;
-    Edge2->NextInSEL = Next;
-    if (Edge2->NextInSEL)
-      Edge2->NextInSEL->PrevInSEL = Edge2;
-    Edge2->PrevInSEL = Prev;
-    if (Edge2->PrevInSEL)
-      Edge2->PrevInSEL->NextInSEL = Edge2;
-  }
-
-  if (!Edge1->PrevInSEL)
-    m_SortedEdges = Edge1;
-  else if (!Edge2->PrevInSEL)
-    m_SortedEdges = Edge2;
+	if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
+		return;
+	if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
+		return;
+
+	if (Edge1->NextInSEL == Edge2) {
+		TEdge *Next = Edge2->NextInSEL;
+		if (Next)
+			Next->PrevInSEL = Edge1;
+		TEdge *Prev = Edge1->PrevInSEL;
+		if (Prev)
+			Prev->NextInSEL = Edge2;
+		Edge2->PrevInSEL = Prev;
+		Edge2->NextInSEL = Edge1;
+		Edge1->PrevInSEL = Edge2;
+		Edge1->NextInSEL = Next;
+	} else if (Edge2->NextInSEL == Edge1) {
+		TEdge *Next = Edge1->NextInSEL;
+		if (Next)
+			Next->PrevInSEL = Edge2;
+		TEdge *Prev = Edge2->PrevInSEL;
+		if (Prev)
+			Prev->NextInSEL = Edge1;
+		Edge1->PrevInSEL = Prev;
+		Edge1->NextInSEL = Edge2;
+		Edge2->PrevInSEL = Edge1;
+		Edge2->NextInSEL = Next;
+	} else {
+		TEdge *Next = Edge1->NextInSEL;
+		TEdge *Prev = Edge1->PrevInSEL;
+		Edge1->NextInSEL = Edge2->NextInSEL;
+		if (Edge1->NextInSEL)
+			Edge1->NextInSEL->PrevInSEL = Edge1;
+		Edge1->PrevInSEL = Edge2->PrevInSEL;
+		if (Edge1->PrevInSEL)
+			Edge1->PrevInSEL->NextInSEL = Edge1;
+		Edge2->NextInSEL = Next;
+		if (Edge2->NextInSEL)
+			Edge2->NextInSEL->PrevInSEL = Edge2;
+		Edge2->PrevInSEL = Prev;
+		if (Edge2->PrevInSEL)
+			Edge2->PrevInSEL->NextInSEL = Edge2;
+	}
+
+	if (!Edge1->PrevInSEL)
+		m_SortedEdges = Edge1;
+	else if (!Edge2->PrevInSEL)
+		m_SortedEdges = Edge2;
 }
 //------------------------------------------------------------------------------
 
 TEdge *GetNextInAEL(TEdge *e, Direction dir) {
-  return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
+	return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
 }
 //------------------------------------------------------------------------------
 
 void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left, cInt &Right) {
-  if (HorzEdge.Bot.X < HorzEdge.Top.X) {
-    Left = HorzEdge.Bot.X;
-    Right = HorzEdge.Top.X;
-    Dir = dLeftToRight;
-  } else {
-    Left = HorzEdge.Top.X;
-    Right = HorzEdge.Bot.X;
-    Dir = dRightToLeft;
-  }
+	if (HorzEdge.Bot.X < HorzEdge.Top.X) {
+		Left = HorzEdge.Bot.X;
+		Right = HorzEdge.Top.X;
+		Dir = dLeftToRight;
+	} else {
+		Left = HorzEdge.Top.X;
+		Right = HorzEdge.Bot.X;
+		Dir = dRightToLeft;
+	}
 }
 //------------------------------------------------------------------------
 
 /*******************************************************************************
-* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or    *
-* Bottom of a scanbeam) are processed as if layered. The order in which HEs    *
-* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#]    *
-* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs),      *
-* and with other non-horizontal edges [*]. Once these intersections are        *
-* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into   *
-* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs.    *
-*******************************************************************************/
+ * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or    *
+ * Bottom of a scanbeam) are processed as if layered. The order in which HEs    *
+ * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#]    *
+ * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs),      *
+ * and with other non-horizontal edges [*]. Once these intersections are        *
+ * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into   *
+ * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs.    *
+ *******************************************************************************/
 
 void Clipper::ProcessHorizontal(TEdge *horzEdge) {
-  Direction dir;
-  cInt horzLeft, horzRight;
-  bool IsOpen = (horzEdge->WindDelta == 0);
-
-  GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
-
-  TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
-  while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
-    eLastHorz = eLastHorz->NextInLML;
-  if (!eLastHorz->NextInLML)
-    eMaxPair = GetMaximaPair(eLastHorz);
-
-  MaximaList::const_iterator maxIt;
-  MaximaList::const_reverse_iterator maxRit;
-  if (m_Maxima.size() > 0) {
-    //get the first maxima in range (X) ...
-    if (dir == dLeftToRight) {
-      maxIt = m_Maxima.begin();
-      while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
-        maxIt++;
-      if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
-        maxIt = m_Maxima.end();
-    } else {
-      maxRit = m_Maxima.rbegin();
-      while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X)
-        maxRit++;
-      if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
-        maxRit = m_Maxima.rend();
-    }
-  }
-
-  OutPt *op1 = 0;
-
-  for (;;) //loop through consec. horizontal edges
-  {
-
-    bool IsLastHorz = (horzEdge == eLastHorz);
-    TEdge *e = GetNextInAEL(horzEdge, dir);
-    while (e) {
-
-      //this code block inserts extra coords into horizontal edges (in output
-      //polygons) whereever maxima touch these horizontal edges. This helps
-      //'simplifying' polygons (ie if the Simplify property is set).
-      if (m_Maxima.size() > 0) {
-        if (dir == dLeftToRight) {
-          while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
-            if (horzEdge->OutIdx >= 0 && !IsOpen)
-              AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
-            maxIt++;
-          }
-        } else {
-          while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) {
-            if (horzEdge->OutIdx >= 0 && !IsOpen)
-              AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
-            maxRit++;
-          }
-        }
-      }
-
-      if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
-          (dir == dRightToLeft && e->Curr.X < horzLeft))
-        break;
-
-      //Also break if we've got to the end of an intermediate horizontal edge ...
-      //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
-      if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
-          e->Dx < horzEdge->NextInLML->Dx)
-        break;
-
-      if (horzEdge->OutIdx >= 0 && !IsOpen)  //note: may be done multiple times
-      {
+	Direction dir;
+	cInt horzLeft, horzRight;
+	bool IsOpen = (horzEdge->WindDelta == 0);
+
+	GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+	TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
+	while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
+		eLastHorz = eLastHorz->NextInLML;
+	if (!eLastHorz->NextInLML)
+		eMaxPair = GetMaximaPair(eLastHorz);
+
+	MaximaList::const_iterator maxIt;
+	MaximaList::const_iterator maxRit;
+	if (m_Maxima.size() > 0) {
+		// get the first maxima in range (X) ...
+		if (dir == dLeftToRight) {
+			maxIt = m_Maxima.begin();
+			while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
+				maxIt++;
+			if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
+				maxIt = m_Maxima.end();
+		} else {
+			maxRit = m_Maxima.end();
+			while (maxRit != m_Maxima.begin() && *maxRit > horzEdge->Bot.X)
+				maxRit++;
+			if (maxRit != m_Maxima.begin() && *maxRit <= eLastHorz->Top.X)
+				maxRit = m_Maxima.begin();
+		}
+	}
+
+	OutPt *op1 = 0;
+
+	for (;;) // loop through consec. horizontal edges
+	{
+
+		bool IsLastHorz = (horzEdge == eLastHorz);
+		TEdge *e = GetNextInAEL(horzEdge, dir);
+		while (e) {
+
+			// this code block inserts extra coords into horizontal edges (in output
+			// polygons) whereever maxima touch these horizontal edges. This helps
+			//'simplifying' polygons (ie if the Simplify property is set).
+			if (m_Maxima.size() > 0) {
+				if (dir == dLeftToRight) {
+					while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
+						if (horzEdge->OutIdx >= 0 && !IsOpen)
+							AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
+						maxIt++;
+					}
+				} else {
+					while (maxRit != m_Maxima.begin() && *maxRit > e->Curr.X) {
+						if (horzEdge->OutIdx >= 0 && !IsOpen)
+							AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
+						maxRit++;
+					}
+				}
+			}
+
+			if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
+				(dir == dRightToLeft && e->Curr.X < horzLeft))
+				break;
+
+			// Also break if we've got to the end of an intermediate horizontal edge ...
+			// nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+			if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
+				e->Dx < horzEdge->NextInLML->Dx)
+				break;
+
+			if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times
+			{
 #ifdef use_xyz
-        if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e);
-        else SetZ(e->Curr, *e, *horzEdge);
+				if (dir == dLeftToRight)
+					SetZ(e->Curr, *horzEdge, *e);
+				else
+					SetZ(e->Curr, *e, *horzEdge);
 #endif
-        op1 = AddOutPt(horzEdge, e->Curr);
-        TEdge *eNextHorz = m_SortedEdges;
-        while (eNextHorz) {
-          if (eNextHorz->OutIdx >= 0 &&
-              HorzSegmentsOverlap(horzEdge->Bot.X,
-                                  horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
-            OutPt *op2 = GetLastOutPt(eNextHorz);
-            AddJoin(op2, op1, eNextHorz->Top);
-          }
-          eNextHorz = eNextHorz->NextInSEL;
-        }
-        AddGhostJoin(op1, horzEdge->Bot);
-      }
-
-      //OK, so far we're still in range of the horizontal Edge  but make sure
-      //we're at the last of consec. horizontals when matching with eMaxPair
-      if (e == eMaxPair && IsLastHorz) {
-        if (horzEdge->OutIdx >= 0)
-          AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
-        DeleteFromAEL(horzEdge);
-        DeleteFromAEL(eMaxPair);
-        return;
-      }
-
-      if (dir == dLeftToRight) {
-        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
-        IntersectEdges(horzEdge, e, Pt);
-      } else {
-        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
-        IntersectEdges(e, horzEdge, Pt);
-      }
-      TEdge *eNext = GetNextInAEL(e, dir);
-      SwapPositionsInAEL(horzEdge, e);
-      e = eNext;
-    } //end while(e)
-
-    //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
-    if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
-      break;
-
-    UpdateEdgeIntoAEL(horzEdge);
-    if (horzEdge->OutIdx >= 0)
-      AddOutPt(horzEdge, horzEdge->Bot);
-    GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
-
-  } //end for (;;)
-
-  if (horzEdge->OutIdx >= 0 && !op1) {
-    op1 = GetLastOutPt(horzEdge);
-    TEdge *eNextHorz = m_SortedEdges;
-    while (eNextHorz) {
-      if (eNextHorz->OutIdx >= 0 &&
-          HorzSegmentsOverlap(horzEdge->Bot.X,
-                              horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
-        OutPt *op2 = GetLastOutPt(eNextHorz);
-        AddJoin(op2, op1, eNextHorz->Top);
-      }
-      eNextHorz = eNextHorz->NextInSEL;
-    }
-    AddGhostJoin(op1, horzEdge->Top);
-  }
-
-  if (horzEdge->NextInLML) {
-    if (horzEdge->OutIdx >= 0) {
-      op1 = AddOutPt(horzEdge, horzEdge->Top);
-      UpdateEdgeIntoAEL(horzEdge);
-      if (horzEdge->WindDelta == 0)
-        return;
-      //nb: HorzEdge is no longer horizontal here
-      TEdge *ePrev = horzEdge->PrevInAEL;
-      TEdge *eNext = horzEdge->NextInAEL;
-      if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
-          ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
-          (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
-              SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
-        OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
-        AddJoin(op1, op2, horzEdge->Top);
-      } else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
-          eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
-          eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
-          SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
-        OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
-        AddJoin(op1, op2, horzEdge->Top);
-      }
-    } else
-      UpdateEdgeIntoAEL(horzEdge);
-  } else {
-    if (horzEdge->OutIdx >= 0)
-      AddOutPt(horzEdge, horzEdge->Top);
-    DeleteFromAEL(horzEdge);
-  }
+				op1 = AddOutPt(horzEdge, e->Curr);
+				TEdge *eNextHorz = m_SortedEdges;
+				while (eNextHorz) {
+					if (eNextHorz->OutIdx >= 0 &&
+						HorzSegmentsOverlap(horzEdge->Bot.X,
+											horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
+						OutPt *op2 = GetLastOutPt(eNextHorz);
+						AddJoin(op2, op1, eNextHorz->Top);
+					}
+					eNextHorz = eNextHorz->NextInSEL;
+				}
+				AddGhostJoin(op1, horzEdge->Bot);
+			}
+
+			// OK, so far we're still in range of the horizontal Edge  but make sure
+			// we're at the last of consec. horizontals when matching with eMaxPair
+			if (e == eMaxPair && IsLastHorz) {
+				if (horzEdge->OutIdx >= 0)
+					AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
+				DeleteFromAEL(horzEdge);
+				DeleteFromAEL(eMaxPair);
+				return;
+			}
+
+			if (dir == dLeftToRight) {
+				IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+				IntersectEdges(horzEdge, e, Pt);
+			} else {
+				IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+				IntersectEdges(e, horzEdge, Pt);
+			}
+			TEdge *eNext = GetNextInAEL(e, dir);
+			SwapPositionsInAEL(horzEdge, e);
+			e = eNext;
+		} // end while(e)
+
+		// Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+		if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
+			break;
+
+		UpdateEdgeIntoAEL(horzEdge);
+		if (horzEdge->OutIdx >= 0)
+			AddOutPt(horzEdge, horzEdge->Bot);
+		GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+	} // end for (;;)
+
+	if (horzEdge->OutIdx >= 0 && !op1) {
+		op1 = GetLastOutPt(horzEdge);
+		TEdge *eNextHorz = m_SortedEdges;
+		while (eNextHorz) {
+			if (eNextHorz->OutIdx >= 0 &&
+				HorzSegmentsOverlap(horzEdge->Bot.X,
+									horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) {
+				OutPt *op2 = GetLastOutPt(eNextHorz);
+				AddJoin(op2, op1, eNextHorz->Top);
+			}
+			eNextHorz = eNextHorz->NextInSEL;
+		}
+		AddGhostJoin(op1, horzEdge->Top);
+	}
+
+	if (horzEdge->NextInLML) {
+		if (horzEdge->OutIdx >= 0) {
+			op1 = AddOutPt(horzEdge, horzEdge->Top);
+			UpdateEdgeIntoAEL(horzEdge);
+			if (horzEdge->WindDelta == 0)
+				return;
+			// nb: HorzEdge is no longer horizontal here
+			TEdge *ePrev = horzEdge->PrevInAEL;
+			TEdge *eNext = horzEdge->NextInAEL;
+			if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
+				ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
+				(ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+				 SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
+				OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
+				AddJoin(op1, op2, horzEdge->Top);
+			} else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
+					   eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
+					   eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
+					   SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
+				OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
+				AddJoin(op1, op2, horzEdge->Top);
+			}
+		} else
+			UpdateEdgeIntoAEL(horzEdge);
+	} else {
+		if (horzEdge->OutIdx >= 0)
+			AddOutPt(horzEdge, horzEdge->Top);
+		DeleteFromAEL(horzEdge);
+	}
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::ProcessIntersections(const cInt topY) {
-  if (!m_ActiveEdges)
-    return true;
-  {
-    BuildIntersectList(topY);
-    size_t IlSize = m_IntersectList.size();
-    if (IlSize == 0)
-      return true;
-    if (IlSize == 1 || FixupIntersectionOrder())
-      ProcessIntersectList();
-    else
-      return false;
-  }
+	if (!m_ActiveEdges)
+		return true;
+	{
+		BuildIntersectList(topY);
+		size_t IlSize = m_IntersectList.size();
+		if (IlSize == 0)
+			return true;
+		if (IlSize == 1 || FixupIntersectionOrder())
+			ProcessIntersectList();
+		else
+			return false;
+	}
 
-  m_SortedEdges = 0;
-  return true;
+	m_SortedEdges = 0;
+	return true;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::DisposeIntersectNodes() {
-  for (size_t i = 0; i < m_IntersectList.size(); ++i)
-    delete m_IntersectList[i];
-  m_IntersectList.clear();
+	for (size_t i = 0; i < m_IntersectList.size(); ++i)
+		delete m_IntersectList[i];
+	m_IntersectList.clear();
 }
 //------------------------------------------------------------------------------
 
 void Clipper::BuildIntersectList(const cInt topY) {
-  if (!m_ActiveEdges)
-    return;
-
-  //prepare for sorting ...
-  TEdge *e = m_ActiveEdges;
-  m_SortedEdges = e;
-  while (e) {
-    e->PrevInSEL = e->PrevInAEL;
-    e->NextInSEL = e->NextInAEL;
-    e->Curr.X = TopX(*e, topY);
-    e = e->NextInAEL;
-  }
-
-  //bubblesort ...
-  bool isModified;
-  do {
-    isModified = false;
-    e = m_SortedEdges;
-    while (e->NextInSEL) {
-      TEdge *eNext = e->NextInSEL;
-      IntPoint Pt;
-      if (e->Curr.X > eNext->Curr.X) {
-        IntersectPoint(*e, *eNext, Pt);
-        if (Pt.Y < topY)
-          Pt = IntPoint(TopX(*e, topY), topY);
-        IntersectNode *newNode = new IntersectNode;
-        newNode->Edge1 = e;
-        newNode->Edge2 = eNext;
-        newNode->Pt = Pt;
-        m_IntersectList.push_back(newNode);
-
-        SwapPositionsInSEL(e, eNext);
-        isModified = true;
-      } else
-        e = eNext;
-    }
-    if (e->PrevInSEL)
-      e->PrevInSEL->NextInSEL = 0;
-    else
-      break;
-  } while (isModified);
-  m_SortedEdges = 0; //important
+	if (!m_ActiveEdges)
+		return;
+
+	// prepare for sorting ...
+	TEdge *e = m_ActiveEdges;
+	m_SortedEdges = e;
+	while (e) {
+		e->PrevInSEL = e->PrevInAEL;
+		e->NextInSEL = e->NextInAEL;
+		e->Curr.X = TopX(*e, topY);
+		e = e->NextInAEL;
+	}
+
+	// bubblesort ...
+	bool isModified;
+	do {
+		isModified = false;
+		e = m_SortedEdges;
+		while (e->NextInSEL) {
+			TEdge *eNext = e->NextInSEL;
+			IntPoint Pt;
+			if (e->Curr.X > eNext->Curr.X) {
+				IntersectPoint(*e, *eNext, Pt);
+				if (Pt.Y < topY)
+					Pt = IntPoint(TopX(*e, topY), topY);
+				IntersectNode *newNode = new IntersectNode;
+				newNode->Edge1 = e;
+				newNode->Edge2 = eNext;
+				newNode->Pt = Pt;
+				m_IntersectList.push_back(newNode);
+
+				SwapPositionsInSEL(e, eNext);
+				isModified = true;
+			} else
+				e = eNext;
+		}
+		if (e->PrevInSEL)
+			e->PrevInSEL->NextInSEL = 0;
+		else
+			break;
+	} while (isModified);
+	m_SortedEdges = 0; // important
 }
 //------------------------------------------------------------------------------
 
-
 void Clipper::ProcessIntersectList() {
-  for (size_t i = 0; i < m_IntersectList.size(); ++i) {
-    IntersectNode *iNode = m_IntersectList[i];
-    {
-      IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
-      SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
-    }
-    delete iNode;
-  }
-  m_IntersectList.clear();
+	for (size_t i = 0; i < m_IntersectList.size(); ++i) {
+		IntersectNode *iNode = m_IntersectList[i];
+		{
+			IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
+			SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
+		}
+		delete iNode;
+	}
+	m_IntersectList.clear();
 }
 //------------------------------------------------------------------------------
 
 bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) {
-  return node2->Pt.Y < node1->Pt.Y;
+	return node2->Pt.Y < node1->Pt.Y;
 }
 //------------------------------------------------------------------------------
 
 inline bool EdgesAdjacent(const IntersectNode &inode) {
-  return (inode.Edge1->NextInSEL == inode.Edge2) ||
-      (inode.Edge1->PrevInSEL == inode.Edge2);
+	return (inode.Edge1->NextInSEL == inode.Edge2) ||
+		   (inode.Edge1->PrevInSEL == inode.Edge2);
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::FixupIntersectionOrder() {
-  //pre-condition: intersections are sorted Bottom-most first.
-  //Now it's crucial that intersections are made only between adjacent edges,
-  //so to ensure this the order of intersections may need adjusting ...
-  CopyAELToSEL();
-  std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
-  size_t cnt = m_IntersectList.size();
-  for (size_t i = 0; i < cnt; ++i) {
-    if (!EdgesAdjacent(*m_IntersectList[i])) {
-      size_t j = i + 1;
-      while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
-        j++;
-      if (j == cnt)
-        return false;
-      std::swap(m_IntersectList[i], m_IntersectList[j]);
-    }
-    SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
-  }
-  return true;
+	// pre-condition: intersections are sorted Bottom-most first.
+	// Now it's crucial that intersections are made only between adjacent edges,
+	// so to ensure this the order of intersections may need adjusting ...
+	CopyAELToSEL();
+	Common::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
+	size_t cnt = m_IntersectList.size();
+	for (size_t i = 0; i < cnt; ++i) {
+		if (!EdgesAdjacent(*m_IntersectList[i])) {
+			size_t j = i + 1;
+			while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
+				j++;
+			if (j == cnt)
+				return false;
+			SWAP(m_IntersectList[i], m_IntersectList[j]);
+		}
+		SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
+	}
+	return true;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::DoMaxima(TEdge *e) {
-  TEdge *eMaxPair = GetMaximaPairEx(e);
-  if (!eMaxPair) {
-    if (e->OutIdx >= 0)
-      AddOutPt(e, e->Top);
-    DeleteFromAEL(e);
-    return;
-  }
-
-  TEdge *eNext = e->NextInAEL;
-  while (eNext && eNext != eMaxPair) {
-    IntersectEdges(e, eNext, e->Top);
-    SwapPositionsInAEL(e, eNext);
-    eNext = e->NextInAEL;
-  }
-
-  if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
-    DeleteFromAEL(e);
-    DeleteFromAEL(eMaxPair);
-  } else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
-    if (e->OutIdx >= 0)
-      AddLocalMaxPoly(e, eMaxPair, e->Top);
-    DeleteFromAEL(e);
-    DeleteFromAEL(eMaxPair);
-  }
+	TEdge *eMaxPair = GetMaximaPairEx(e);
+	if (!eMaxPair) {
+		if (e->OutIdx >= 0)
+			AddOutPt(e, e->Top);
+		DeleteFromAEL(e);
+		return;
+	}
+
+	TEdge *eNext = e->NextInAEL;
+	while (eNext && eNext != eMaxPair) {
+		IntersectEdges(e, eNext, e->Top);
+		SwapPositionsInAEL(e, eNext);
+		eNext = e->NextInAEL;
+	}
+
+	if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
+		DeleteFromAEL(e);
+		DeleteFromAEL(eMaxPair);
+	} else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
+		if (e->OutIdx >= 0)
+			AddLocalMaxPoly(e, eMaxPair, e->Top);
+		DeleteFromAEL(e);
+		DeleteFromAEL(eMaxPair);
+	}
 #ifdef use_lines
-  else if (e->WindDelta == 0) {
-    if (e->OutIdx >= 0) {
-      AddOutPt(e, e->Top);
-      e->OutIdx = Unassigned;
-    }
-    DeleteFromAEL(e);
-
-    if (eMaxPair->OutIdx >= 0) {
-      AddOutPt(eMaxPair, e->Top);
-      eMaxPair->OutIdx = Unassigned;
-    }
-    DeleteFromAEL(eMaxPair);
-  }
+	else if (e->WindDelta == 0) {
+		if (e->OutIdx >= 0) {
+			AddOutPt(e, e->Top);
+			e->OutIdx = Unassigned;
+		}
+		DeleteFromAEL(e);
+
+		if (eMaxPair->OutIdx >= 0) {
+			AddOutPt(eMaxPair, e->Top);
+			eMaxPair->OutIdx = Unassigned;
+		}
+		DeleteFromAEL(eMaxPair);
+	}
 #endif
-  else
-    error("DoMaxima error");
+	else
+		error("DoMaxima error");
 }
 //------------------------------------------------------------------------------
 
 void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) {
-  TEdge *e = m_ActiveEdges;
-  while (e) {
-    //1. process maxima, treating them as if they're 'bent' horizontal edges,
-    //   but exclude maxima with horizontal edges. nb: e can't be a horizontal.
-    bool IsMaximaEdge = IsMaxima(e, topY);
-
-    if (IsMaximaEdge) {
-      TEdge *eMaxPair = GetMaximaPairEx(e);
-      IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
-    }
-
-    if (IsMaximaEdge) {
-      if (m_StrictSimple)
-        m_Maxima.push_back(e->Top.X);
-      TEdge *ePrev = e->PrevInAEL;
-      DoMaxima(e);
-      if (!ePrev)
-        e = m_ActiveEdges;
-      else
-        e = ePrev->NextInAEL;
-    } else {
-      //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
-      if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
-        UpdateEdgeIntoAEL(e);
-        if (e->OutIdx >= 0)
-          AddOutPt(e, e->Bot);
-        AddEdgeToSEL(e);
-      } else {
-        e->Curr.X = TopX(*e, topY);
-        e->Curr.Y = topY;
+	TEdge *e = m_ActiveEdges;
+	while (e) {
+		// 1. process maxima, treating them as if they're 'bent' horizontal edges,
+		//    but exclude maxima with horizontal edges. nb: e can't be a horizontal.
+		bool IsMaximaEdge = IsMaxima(e, topY);
+
+		if (IsMaximaEdge) {
+			TEdge *eMaxPair = GetMaximaPairEx(e);
+			IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
+		}
+
+		if (IsMaximaEdge) {
+			if (m_StrictSimple)
+				m_Maxima.push_back(e->Top.X);
+			TEdge *ePrev = e->PrevInAEL;
+			DoMaxima(e);
+			if (!ePrev)
+				e = m_ActiveEdges;
+			else
+				e = ePrev->NextInAEL;
+		} else {
+			// 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
+			if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
+				UpdateEdgeIntoAEL(e);
+				if (e->OutIdx >= 0)
+					AddOutPt(e, e->Bot);
+				AddEdgeToSEL(e);
+			} else {
+				e->Curr.X = TopX(*e, topY);
+				e->Curr.Y = topY;
 #ifdef use_xyz
-        e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
+				e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
 #endif
-      }
-
-      //When StrictlySimple and 'e' is being touched by another edge, then
-      //make sure both edges have a vertex here ...
-      if (m_StrictSimple) {
-        TEdge *ePrev = e->PrevInAEL;
-        if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) &&
-            (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) {
-          IntPoint pt = e->Curr;
+			}
+
+			// When StrictlySimple and 'e' is being touched by another edge, then
+			// make sure both edges have a vertex here ...
+			if (m_StrictSimple) {
+				TEdge *ePrev = e->PrevInAEL;
+				if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) &&
+					(ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) {
+					IntPoint pt = e->Curr;
 #ifdef use_xyz
-          SetZ(pt, *ePrev, *e);
+					SetZ(pt, *ePrev, *e);
 #endif
-          OutPt *op = AddOutPt(ePrev, pt);
-          OutPt *op2 = AddOutPt(e, pt);
-          AddJoin(op, op2, pt); //StrictlySimple (type-3) join
-        }
-      }
-
-      e = e->NextInAEL;
-    }
-  }
-
-  //3. Process horizontals at the Top of the scanbeam ...
-  m_Maxima.sort();
-  ProcessHorizontals();
-  m_Maxima.clear();
-
-  //4. Promote intermediate vertices ...
-  e = m_ActiveEdges;
-  while (e) {
-    if (IsIntermediate(e, topY)) {
-      OutPt *op = 0;
-      if (e->OutIdx >= 0)
-        op = AddOutPt(e, e->Top);
-      UpdateEdgeIntoAEL(e);
-
-      //if output polygons share an edge, they'll need joining later ...
-      TEdge *ePrev = e->PrevInAEL;
-      TEdge *eNext = e->NextInAEL;
-      if (ePrev && ePrev->Curr.X == e->Bot.X &&
-          ePrev->Curr.Y == e->Bot.Y && op &&
-          ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
-          SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) &&
-          (e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
-        OutPt *op2 = AddOutPt(ePrev, e->Bot);
-        AddJoin(op, op2, e->Top);
-      } else if (eNext && eNext->Curr.X == e->Bot.X &&
-          eNext->Curr.Y == e->Bot.Y && op &&
-          eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
-          SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) &&
-          (e->WindDelta != 0) && (eNext->WindDelta != 0)) {
-        OutPt *op2 = AddOutPt(eNext, e->Bot);
-        AddJoin(op, op2, e->Top);
-      }
-    }
-    e = e->NextInAEL;
-  }
+					OutPt *op = AddOutPt(ePrev, pt);
+					OutPt *op2 = AddOutPt(e, pt);
+					AddJoin(op, op2, pt); // StrictlySimple (type-3) join
+				}
+			}
+
+			e = e->NextInAEL;
+		}
+	}
+
+	// 3. Process horizontals at the Top of the scanbeam ...
+	Common::sort(m_Maxima.begin(), m_Maxima.end());
+	ProcessHorizontals();
+	m_Maxima.clear();
+
+	// 4. Promote intermediate vertices ...
+	e = m_ActiveEdges;
+	while (e) {
+		if (IsIntermediate(e, topY)) {
+			OutPt *op = 0;
+			if (e->OutIdx >= 0)
+				op = AddOutPt(e, e->Top);
+			UpdateEdgeIntoAEL(e);
+
+			// if output polygons share an edge, they'll need joining later ...
+			TEdge *ePrev = e->PrevInAEL;
+			TEdge *eNext = e->NextInAEL;
+			if (ePrev && ePrev->Curr.X == e->Bot.X &&
+				ePrev->Curr.Y == e->Bot.Y && op &&
+				ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+				SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) &&
+				(e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
+				OutPt *op2 = AddOutPt(ePrev, e->Bot);
+				AddJoin(op, op2, e->Top);
+			} else if (eNext && eNext->Curr.X == e->Bot.X &&
+					   eNext->Curr.Y == e->Bot.Y && op &&
+					   eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
+					   SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) &&
+					   (e->WindDelta != 0) && (eNext->WindDelta != 0)) {
+				OutPt *op2 = AddOutPt(eNext, e->Bot);
+				AddJoin(op, op2, e->Top);
+			}
+		}
+		e = e->NextInAEL;
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::FixupOutPolyline(OutRec &outrec) {
-  OutPt *pp = outrec.Pts;
-  OutPt *lastPP = pp->Prev;
-  while (pp != lastPP) {
-    pp = pp->Next;
-    if (pp->Pt == pp->Prev->Pt) {
-      if (pp == lastPP)
-        lastPP = pp->Prev;
-      OutPt *tmpPP = pp->Prev;
-      tmpPP->Next = pp->Next;
-      pp->Next->Prev = tmpPP;
-      delete pp;
-      pp = tmpPP;
-    }
-  }
-
-  if (pp == pp->Prev) {
-    DisposeOutPts(pp);
-    outrec.Pts = 0;
-    return;
-  }
+	OutPt *pp = outrec.Pts;
+	OutPt *lastPP = pp->Prev;
+	while (pp != lastPP) {
+		pp = pp->Next;
+		if (pp->Pt == pp->Prev->Pt) {
+			if (pp == lastPP)
+				lastPP = pp->Prev;
+			OutPt *tmpPP = pp->Prev;
+			tmpPP->Next = pp->Next;
+			pp->Next->Prev = tmpPP;
+			delete pp;
+			pp = tmpPP;
+		}
+	}
+
+	if (pp == pp->Prev) {
+		DisposeOutPts(pp);
+		outrec.Pts = 0;
+		return;
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::FixupOutPolygon(OutRec &outrec) {
-  //FixupOutPolygon() - removes duplicate points and simplifies consecutive
-  //parallel edges by removing the middle vertex.
-  OutPt *lastOK = 0;
-  outrec.BottomPt = 0;
-  OutPt *pp = outrec.Pts;
-  bool preserveCol = m_PreserveCollinear || m_StrictSimple;
-
-  for (;;) {
-    if (pp->Prev == pp || pp->Prev == pp->Next) {
-      DisposeOutPts(pp);
-      outrec.Pts = 0;
-      return;
-    }
-
-    //test for duplicate points and collinear edges ...
-    if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
-        (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
-            (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
-      lastOK = 0;
-      OutPt *tmp = pp;
-      pp->Prev->Next = pp->Next;
-      pp->Next->Prev = pp->Prev;
-      pp = pp->Prev;
-      delete tmp;
-    } else if (pp == lastOK)
-      break;
-    else {
-      if (!lastOK)
-        lastOK = pp;
-      pp = pp->Next;
-    }
-  }
-  outrec.Pts = pp;
+	// FixupOutPolygon() - removes duplicate points and simplifies consecutive
+	// parallel edges by removing the middle vertex.
+	OutPt *lastOK = 0;
+	outrec.BottomPt = 0;
+	OutPt *pp = outrec.Pts;
+	bool preserveCol = m_PreserveCollinear || m_StrictSimple;
+
+	for (;;) {
+		if (pp->Prev == pp || pp->Prev == pp->Next) {
+			DisposeOutPts(pp);
+			outrec.Pts = 0;
+			return;
+		}
+
+		// test for duplicate points and collinear edges ...
+		if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
+			(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
+			 (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
+			lastOK = 0;
+			OutPt *tmp = pp;
+			pp->Prev->Next = pp->Next;
+			pp->Next->Prev = pp->Prev;
+			pp = pp->Prev;
+			delete tmp;
+		} else if (pp == lastOK)
+			break;
+		else {
+			if (!lastOK)
+				lastOK = pp;
+			pp = pp->Next;
+		}
+	}
+	outrec.Pts = pp;
 }
 //------------------------------------------------------------------------------
 
 int PointCount(OutPt *Pts) {
-  if (!Pts)
-    return 0;
-  int result = 0;
-  OutPt *p = Pts;
-  do {
-    result++;
-    p = p->Next;
-  } while (p != Pts);
-  return result;
+	if (!Pts)
+		return 0;
+	int result = 0;
+	OutPt *p = Pts;
+	do {
+		result++;
+		p = p->Next;
+	} while (p != Pts);
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::BuildResult(Paths &polys) {
-  polys.reserve(m_PolyOuts.size());
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-    if (!m_PolyOuts[i]->Pts)
-      continue;
-    Path pg;
-    OutPt *p = m_PolyOuts[i]->Pts->Prev;
-    int cnt = PointCount(p);
-    if (cnt < 2)
-      continue;
-    pg.reserve(cnt);
-    for (int i = 0; i < cnt; ++i) {
-      pg.push_back(p->Pt);
-      p = p->Prev;
-    }
-    polys.push_back(pg);
-  }
+	polys.reserve(m_PolyOuts.size());
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+		if (!m_PolyOuts[i]->Pts)
+			continue;
+		Path pg;
+		OutPt *p = m_PolyOuts[i]->Pts->Prev;
+		int cnt = PointCount(p);
+		if (cnt < 2)
+			continue;
+		pg.reserve(cnt);
+		for (int j = 0; j < cnt; ++j) {
+			pg.push_back(p->Pt);
+			p = p->Prev;
+		}
+		polys.push_back(pg);
+	}
 }
 //------------------------------------------------------------------------------
 
 void Clipper::BuildResult2(PolyTree &polytree) {
-  polytree.Clear();
-  polytree.AllNodes.reserve(m_PolyOuts.size());
-  //add each output polygon/contour to polytree ...
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
-    OutRec *outRec = m_PolyOuts[i];
-    int cnt = PointCount(outRec->Pts);
-    if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
-      continue;
-    FixHoleLinkage(*outRec);
-    PolyNode *pn = new PolyNode();
-    //nb: polytree takes ownership of all the PolyNodes
-    polytree.AllNodes.push_back(pn);
-    outRec->PolyNd = pn;
-    pn->Parent = 0;
-    pn->Index = 0;
-    pn->Contour.reserve(cnt);
-    OutPt *op = outRec->Pts->Prev;
-    for (int j = 0; j < cnt; j++) {
-      pn->Contour.push_back(op->Pt);
-      op = op->Prev;
-    }
-  }
-
-  //fixup PolyNode links etc ...
-  polytree.Childs.reserve(m_PolyOuts.size());
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
-    OutRec *outRec = m_PolyOuts[i];
-    if (!outRec->PolyNd)
-      continue;
-    if (outRec->IsOpen) {
-      outRec->PolyNd->m_IsOpen = true;
-      polytree.AddChild(*outRec->PolyNd);
-    } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
-      outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
-    else
-      polytree.AddChild(*outRec->PolyNd);
-  }
+	polytree.Clear();
+	polytree.AllNodes.reserve(m_PolyOuts.size());
+	// add each output polygon/contour to polytree ...
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+		OutRec *outRec = m_PolyOuts[i];
+		int cnt = PointCount(outRec->Pts);
+		if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
+			continue;
+		FixHoleLinkage(*outRec);
+		PolyNode *pn = new PolyNode();
+		// nb: polytree takes ownership of all the PolyNodes
+		polytree.AllNodes.push_back(pn);
+		outRec->PolyNd = pn;
+		pn->Parent = 0;
+		pn->Index = 0;
+		pn->Contour.reserve(cnt);
+		OutPt *op = outRec->Pts->Prev;
+		for (int j = 0; j < cnt; j++) {
+			pn->Contour.push_back(op->Pt);
+			op = op->Prev;
+		}
+	}
+
+	// fixup PolyNode links etc ...
+	polytree.Childs.reserve(m_PolyOuts.size());
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+		OutRec *outRec = m_PolyOuts[i];
+		if (!outRec->PolyNd)
+			continue;
+		if (outRec->IsOpen) {
+			outRec->PolyNd->m_IsOpen = true;
+			polytree.AddChild(*outRec->PolyNd);
+		} else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
+			outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
+		else
+			polytree.AddChild(*outRec->PolyNd);
+	}
 }
 //------------------------------------------------------------------------------
 
 void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) {
-  //just swap the contents (because fIntersectNodes is a single-linked-list)
-  IntersectNode inode = int1; //gets a copy of Int1
-  int1.Edge1 = int2.Edge1;
-  int1.Edge2 = int2.Edge2;
-  int1.Pt = int2.Pt;
-  int2.Edge1 = inode.Edge1;
-  int2.Edge2 = inode.Edge2;
-  int2.Pt = inode.Pt;
+	// just swap the contents (because fIntersectNodes is a single-linked-list)
+	IntersectNode inode = int1; // gets a copy of Int1
+	int1.Edge1 = int2.Edge1;
+	int1.Edge2 = int2.Edge2;
+	int1.Pt = int2.Pt;
+	int2.Edge1 = inode.Edge1;
+	int2.Edge2 = inode.Edge2;
+	int2.Pt = inode.Pt;
 }
 //------------------------------------------------------------------------------
 
 inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) {
-  if (e2.Curr.X == e1.Curr.X) {
-    if (e2.Top.Y > e1.Top.Y)
-      return e2.Top.X < TopX(e1, e2.Top.Y);
-    else
-      return e1.Top.X > TopX(e2, e1.Top.Y);
-  } else
-    return e2.Curr.X < e1.Curr.X;
+	if (e2.Curr.X == e1.Curr.X) {
+		if (e2.Top.Y > e1.Top.Y)
+			return e2.Top.X < TopX(e1, e2.Top.Y);
+		else
+			return e1.Top.X > TopX(e2, e1.Top.Y);
+	} else
+		return e2.Curr.X < e1.Curr.X;
 }
 //------------------------------------------------------------------------------
 
 bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
-                cInt &Left, cInt &Right) {
-  if (a1 < a2) {
-    if (b1 < b2) {
-      Left = std::max(a1, b1);
-      Right = std::min(a2, b2);
-    } else {
-      Left = std::max(a1, b2);
-      Right = std::min(a2, b1);
-    }
-  } else {
-    if (b1 < b2) {
-      Left = std::max(a2, b1);
-      Right = std::min(a1, b2);
-    } else {
-      Left = std::max(a2, b2);
-      Right = std::min(a1, b1);
-    }
-  }
-  return Left < Right;
+				cInt &Left, cInt &Right) {
+	if (a1 < a2) {
+		if (b1 < b2) {
+			Left = MAX(a1, b1);
+			Right = MIN(a2, b2);
+		} else {
+			Left = MAX(a1, b2);
+			Right = MIN(a2, b1);
+		}
+	} else {
+		if (b1 < b2) {
+			Left = MAX(a2, b1);
+			Right = MIN(a1, b2);
+		} else {
+			Left = MAX(a2, b2);
+			Right = MIN(a1, b1);
+		}
+	}
+	return Left < Right;
 }
 //------------------------------------------------------------------------------
 
 inline void UpdateOutPtIdxs(OutRec &outrec) {
-  OutPt *op = outrec.Pts;
-  do {
-    op->Idx = outrec.Idx;
-    op = op->Prev;
-  } while (op != outrec.Pts);
+	OutPt *op = outrec.Pts;
+	do {
+		op->Idx = outrec.Idx;
+		op = op->Prev;
+	} while (op != outrec.Pts);
 }
 //------------------------------------------------------------------------------
 
 void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) {
-  if (!m_ActiveEdges) {
-    edge->PrevInAEL = 0;
-    edge->NextInAEL = 0;
-    m_ActiveEdges = edge;
-  } else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
-    edge->PrevInAEL = 0;
-    edge->NextInAEL = m_ActiveEdges;
-    m_ActiveEdges->PrevInAEL = edge;
-    m_ActiveEdges = edge;
-  } else {
-    if (!startEdge)
-      startEdge = m_ActiveEdges;
-    while (startEdge->NextInAEL &&
-        !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
-      startEdge = startEdge->NextInAEL;
-    edge->NextInAEL = startEdge->NextInAEL;
-    if (startEdge->NextInAEL)
-      startEdge->NextInAEL->PrevInAEL = edge;
-    edge->PrevInAEL = startEdge;
-    startEdge->NextInAEL = edge;
-  }
+	if (!m_ActiveEdges) {
+		edge->PrevInAEL = 0;
+		edge->NextInAEL = 0;
+		m_ActiveEdges = edge;
+	} else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
+		edge->PrevInAEL = 0;
+		edge->NextInAEL = m_ActiveEdges;
+		m_ActiveEdges->PrevInAEL = edge;
+		m_ActiveEdges = edge;
+	} else {
+		if (!startEdge)
+			startEdge = m_ActiveEdges;
+		while (startEdge->NextInAEL &&
+			   !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
+			startEdge = startEdge->NextInAEL;
+		edge->NextInAEL = startEdge->NextInAEL;
+		if (startEdge->NextInAEL)
+			startEdge->NextInAEL->PrevInAEL = edge;
+		edge->PrevInAEL = startEdge;
+		startEdge->NextInAEL = edge;
+	}
 }
 //----------------------------------------------------------------------
 
 OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) {
-  OutPt *result = new OutPt;
-  result->Pt = outPt->Pt;
-  result->Idx = outPt->Idx;
-  if (InsertAfter) {
-    result->Next = outPt->Next;
-    result->Prev = outPt;
-    outPt->Next->Prev = result;
-    outPt->Next = result;
-  } else {
-    result->Prev = outPt->Prev;
-    result->Next = outPt;
-    outPt->Prev->Next = result;
-    outPt->Prev = result;
-  }
-  return result;
+	OutPt *result = new OutPt;
+	result->Pt = outPt->Pt;
+	result->Idx = outPt->Idx;
+	if (InsertAfter) {
+		result->Next = outPt->Next;
+		result->Prev = outPt;
+		outPt->Next->Prev = result;
+		outPt->Next = result;
+	} else {
+		result->Prev = outPt->Prev;
+		result->Next = outPt;
+		outPt->Prev->Next = result;
+		outPt->Prev = result;
+	}
+	return result;
 }
 //------------------------------------------------------------------------------
 
 bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b,
-              const IntPoint Pt, bool DiscardLeft) {
-  Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
-  Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
-  if (Dir1 == Dir2)
-    return false;
-
-  //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
-  //want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
-  //So, to facilitate this while inserting Op1b and Op2b ...
-  //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
-  //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
-  if (Dir1 == dLeftToRight) {
-    while (op1->Next->Pt.X <= Pt.X &&
-        op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
-      op1 = op1->Next;
-    if (DiscardLeft && (op1->Pt.X != Pt.X))
-      op1 = op1->Next;
-    op1b = DupOutPt(op1, !DiscardLeft);
-    if (op1b->Pt != Pt) {
-      op1 = op1b;
-      op1->Pt = Pt;
-      op1b = DupOutPt(op1, !DiscardLeft);
-    }
-  } else {
-    while (op1->Next->Pt.X >= Pt.X &&
-        op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
-      op1 = op1->Next;
-    if (!DiscardLeft && (op1->Pt.X != Pt.X))
-      op1 = op1->Next;
-    op1b = DupOutPt(op1, DiscardLeft);
-    if (op1b->Pt != Pt) {
-      op1 = op1b;
-      op1->Pt = Pt;
-      op1b = DupOutPt(op1, DiscardLeft);
-    }
-  }
-
-  if (Dir2 == dLeftToRight) {
-    while (op2->Next->Pt.X <= Pt.X &&
-        op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
-      op2 = op2->Next;
-    if (DiscardLeft && (op2->Pt.X != Pt.X))
-      op2 = op2->Next;
-    op2b = DupOutPt(op2, !DiscardLeft);
-    if (op2b->Pt != Pt) {
-      op2 = op2b;
-      op2->Pt = Pt;
-      op2b = DupOutPt(op2, !DiscardLeft);
-    }
-  } else {
-    while (op2->Next->Pt.X >= Pt.X &&
-        op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
-      op2 = op2->Next;
-    if (!DiscardLeft && (op2->Pt.X != Pt.X))
-      op2 = op2->Next;
-    op2b = DupOutPt(op2, DiscardLeft);
-    if (op2b->Pt != Pt) {
-      op2 = op2b;
-      op2->Pt = Pt;
-      op2b = DupOutPt(op2, DiscardLeft);
-    }
-  }
-
-  if ((Dir1 == dLeftToRight) == DiscardLeft) {
-    op1->Prev = op2;
-    op2->Next = op1;
-    op1b->Next = op2b;
-    op2b->Prev = op1b;
-  } else {
-    op1->Next = op2;
-    op2->Prev = op1;
-    op1b->Prev = op2b;
-    op2b->Next = op1b;
-  }
-  return true;
+			  const IntPoint Pt, bool DiscardLeft) {
+	Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
+	Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
+	if (Dir1 == Dir2)
+		return false;
+
+	// When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
+	// want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
+	// So, to facilitate this while inserting Op1b and Op2b ...
+	// when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
+	// otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
+	if (Dir1 == dLeftToRight) {
+		while (op1->Next->Pt.X <= Pt.X &&
+			   op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
+			op1 = op1->Next;
+		if (DiscardLeft && (op1->Pt.X != Pt.X))
+			op1 = op1->Next;
+		op1b = DupOutPt(op1, !DiscardLeft);
+		if (op1b->Pt != Pt) {
+			op1 = op1b;
+			op1->Pt = Pt;
+			op1b = DupOutPt(op1, !DiscardLeft);
+		}
+	} else {
+		while (op1->Next->Pt.X >= Pt.X &&
+			   op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
+			op1 = op1->Next;
+		if (!DiscardLeft && (op1->Pt.X != Pt.X))
+			op1 = op1->Next;
+		op1b = DupOutPt(op1, DiscardLeft);
+		if (op1b->Pt != Pt) {
+			op1 = op1b;
+			op1->Pt = Pt;
+			op1b = DupOutPt(op1, DiscardLeft);
+		}
+	}
+
+	if (Dir2 == dLeftToRight) {
+		while (op2->Next->Pt.X <= Pt.X &&
+			   op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
+			op2 = op2->Next;
+		if (DiscardLeft && (op2->Pt.X != Pt.X))
+			op2 = op2->Next;
+		op2b = DupOutPt(op2, !DiscardLeft);
+		if (op2b->Pt != Pt) {
+			op2 = op2b;
+			op2->Pt = Pt;
+			op2b = DupOutPt(op2, !DiscardLeft);
+		}
+	} else {
+		while (op2->Next->Pt.X >= Pt.X &&
+			   op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
+			op2 = op2->Next;
+		if (!DiscardLeft && (op2->Pt.X != Pt.X))
+			op2 = op2->Next;
+		op2b = DupOutPt(op2, DiscardLeft);
+		if (op2b->Pt != Pt) {
+			op2 = op2b;
+			op2->Pt = Pt;
+			op2b = DupOutPt(op2, DiscardLeft);
+		}
+	}
+
+	if ((Dir1 == dLeftToRight) == DiscardLeft) {
+		op1->Prev = op2;
+		op2->Next = op1;
+		op1b->Next = op2b;
+		op2b->Prev = op1b;
+	} else {
+		op1->Next = op2;
+		op2->Prev = op1;
+		op1b->Prev = op2b;
+		op2b->Next = op1b;
+	}
+	return true;
 }
 //------------------------------------------------------------------------------
 
 bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) {
-  OutPt *op1 = j->OutPt1, *op1b;
-  OutPt *op2 = j->OutPt2, *op2b;
-
-  //There are 3 kinds of joins for output polygons ...
-  //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
-  //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
-  //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
-  //location at the Bottom of the overlapping segment (& Join.OffPt is above).
-  //3. StrictSimple joins where edges touch but are not collinear and where
-  //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
-  bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
-
-  if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
-      (j->OffPt == j->OutPt2->Pt)) {
-    //Strictly Simple join ...
-    if (outRec1 != outRec2)
-      return false;
-    op1b = j->OutPt1->Next;
-    while (op1b != op1 && (op1b->Pt == j->OffPt))
-      op1b = op1b->Next;
-    bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
-    op2b = j->OutPt2->Next;
-    while (op2b != op2 && (op2b->Pt == j->OffPt))
-      op2b = op2b->Next;
-    bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
-    if (reverse1 == reverse2)
-      return false;
-    if (reverse1) {
-      op1b = DupOutPt(op1, false);
-      op2b = DupOutPt(op2, true);
-      op1->Prev = op2;
-      op2->Next = op1;
-      op1b->Next = op2b;
-      op2b->Prev = op1b;
-      j->OutPt1 = op1;
-      j->OutPt2 = op1b;
-      return true;
-    } else {
-      op1b = DupOutPt(op1, true);
-      op2b = DupOutPt(op2, false);
-      op1->Next = op2;
-      op2->Prev = op1;
-      op1b->Prev = op2b;
-      op2b->Next = op1b;
-      j->OutPt1 = op1;
-      j->OutPt2 = op1b;
-      return true;
-    }
-  } else if (isHorizontal) {
-    //treat horizontal joins differently to non-horizontal joins since with
-    //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
-    //may be anywhere along the horizontal edge.
-    op1b = op1;
-    while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2)
-      op1 = op1->Prev;
-    while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2)
-      op1b = op1b->Next;
-    if (op1b->Next == op1 || op1b->Next == op2)
-      return false; //a flat 'polygon'
-
-    op2b = op2;
-    while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b)
-      op2 = op2->Prev;
-    while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1)
-      op2b = op2b->Next;
-    if (op2b->Next == op2 || op2b->Next == op1)
-      return false; //a flat 'polygon'
-
-    cInt Left, Right;
-    //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
-    if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
-      return false;
-
-    //DiscardLeftSide: when overlapping edges are joined, a spike will created
-    //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
-    //on the discard Side as either may still be needed for other joins ...
-    IntPoint Pt;
-    bool DiscardLeftSide;
-    if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
-      Pt = op1->Pt;
-      DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
-    } else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
-      Pt = op2->Pt;
-      DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
-    } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
-      Pt = op1b->Pt;
-      DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
-    } else {
-      Pt = op2b->Pt;
-      DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
-    }
-    j->OutPt1 = op1;
-    j->OutPt2 = op2;
-    return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
-  } else {
-    //nb: For non-horizontal joins ...
-    //    1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
-    //    2. Jr.OutPt1.Pt > Jr.OffPt.Y
-
-    //make sure the polygons are correctly oriented ...
-    op1b = op1->Next;
-    while ((op1b->Pt == op1->Pt) && (op1b != op1))
-      op1b = op1b->Next;
-    bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
-        !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
-    if (Reverse1) {
-      op1b = op1->Prev;
-      while ((op1b->Pt == op1->Pt) && (op1b != op1))
-        op1b = op1b->Prev;
-      if ((op1b->Pt.Y > op1->Pt.Y) ||
-          !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
-        return false;
-    }
-    op2b = op2->Next;
-    while ((op2b->Pt == op2->Pt) && (op2b != op2))
-      op2b = op2b->Next;
-    bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
-        !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
-    if (Reverse2) {
-      op2b = op2->Prev;
-      while ((op2b->Pt == op2->Pt) && (op2b != op2))
-        op2b = op2b->Prev;
-      if ((op2b->Pt.Y > op2->Pt.Y) ||
-          !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
-        return false;
-    }
-
-    if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
-        ((outRec1 == outRec2) && (Reverse1 == Reverse2)))
-      return false;
-
-    if (Reverse1) {
-      op1b = DupOutPt(op1, false);
-      op2b = DupOutPt(op2, true);
-      op1->Prev = op2;
-      op2->Next = op1;
-      op1b->Next = op2b;
-      op2b->Prev = op1b;
-      j->OutPt1 = op1;
-      j->OutPt2 = op1b;
-      return true;
-    } else {
-      op1b = DupOutPt(op1, true);
-      op2b = DupOutPt(op2, false);
-      op1->Next = op2;
-      op2->Prev = op1;
-      op1b->Prev = op2b;
-      op2b->Next = op1b;
-      j->OutPt1 = op1;
-      j->OutPt2 = op1b;
-      return true;
-    }
-  }
+	OutPt *op1 = j->OutPt1, *op1b;
+	OutPt *op2 = j->OutPt2, *op2b;
+
+	// There are 3 kinds of joins for output polygons ...
+	// 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
+	// along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
+	// 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
+	// location at the Bottom of the overlapping segment (& Join.OffPt is above).
+	// 3. StrictSimple joins where edges touch but are not collinear and where
+	// Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
+	bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
+
+	if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
+		(j->OffPt == j->OutPt2->Pt)) {
+		// Strictly Simple join ...
+		if (outRec1 != outRec2)
+			return false;
+		op1b = j->OutPt1->Next;
+		while (op1b != op1 && (op1b->Pt == j->OffPt))
+			op1b = op1b->Next;
+		bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
+		op2b = j->OutPt2->Next;
+		while (op2b != op2 && (op2b->Pt == j->OffPt))
+			op2b = op2b->Next;
+		bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
+		if (reverse1 == reverse2)
+			return false;
+		if (reverse1) {
+			op1b = DupOutPt(op1, false);
+			op2b = DupOutPt(op2, true);
+			op1->Prev = op2;
+			op2->Next = op1;
+			op1b->Next = op2b;
+			op2b->Prev = op1b;
+			j->OutPt1 = op1;
+			j->OutPt2 = op1b;
+			return true;
+		} else {
+			op1b = DupOutPt(op1, true);
+			op2b = DupOutPt(op2, false);
+			op1->Next = op2;
+			op2->Prev = op1;
+			op1b->Prev = op2b;
+			op2b->Next = op1b;
+			j->OutPt1 = op1;
+			j->OutPt2 = op1b;
+			return true;
+		}
+	} else if (isHorizontal) {
+		// treat horizontal joins differently to non-horizontal joins since with
+		// them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
+		// may be anywhere along the horizontal edge.
+		op1b = op1;
+		while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2)
+			op1 = op1->Prev;
+		while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2)
+			op1b = op1b->Next;
+		if (op1b->Next == op1 || op1b->Next == op2)
+			return false; // a flat 'polygon'
+
+		op2b = op2;
+		while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b)
+			op2 = op2->Prev;
+		while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1)
+			op2b = op2b->Next;
+		if (op2b->Next == op2 || op2b->Next == op1)
+			return false; // a flat 'polygon'
+
+		cInt Left, Right;
+		// Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
+		if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
+			return false;
+
+		// DiscardLeftSide: when overlapping edges are joined, a spike will created
+		// which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
+		// on the discard Side as either may still be needed for other joins ...
+		IntPoint Pt;
+		bool DiscardLeftSide;
+		if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
+			Pt = op1->Pt;
+			DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
+		} else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
+			Pt = op2->Pt;
+			DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
+		} else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
+			Pt = op1b->Pt;
+			DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
+		} else {
+			Pt = op2b->Pt;
+			DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
+		}
+		j->OutPt1 = op1;
+		j->OutPt2 = op2;
+		return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
+	} else {
+		// nb: For non-horizontal joins ...
+		//     1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
+		//     2. Jr.OutPt1.Pt > Jr.OffPt.Y
+
+		// make sure the polygons are correctly oriented ...
+		op1b = op1->Next;
+		while ((op1b->Pt == op1->Pt) && (op1b != op1))
+			op1b = op1b->Next;
+		bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
+						 !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
+		if (Reverse1) {
+			op1b = op1->Prev;
+			while ((op1b->Pt == op1->Pt) && (op1b != op1))
+				op1b = op1b->Prev;
+			if ((op1b->Pt.Y > op1->Pt.Y) ||
+				!SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
+				return false;
+		}
+		op2b = op2->Next;
+		while ((op2b->Pt == op2->Pt) && (op2b != op2))
+			op2b = op2b->Next;
+		bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
+						 !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
+		if (Reverse2) {
+			op2b = op2->Prev;
+			while ((op2b->Pt == op2->Pt) && (op2b != op2))
+				op2b = op2b->Prev;
+			if ((op2b->Pt.Y > op2->Pt.Y) ||
+				!SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
+				return false;
+		}
+
+		if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
+			((outRec1 == outRec2) && (Reverse1 == Reverse2)))
+			return false;
+
+		if (Reverse1) {
+			op1b = DupOutPt(op1, false);
+			op2b = DupOutPt(op2, true);
+			op1->Prev = op2;
+			op2->Next = op1;
+			op1b->Next = op2b;
+			op2b->Prev = op1b;
+			j->OutPt1 = op1;
+			j->OutPt2 = op1b;
+			return true;
+		} else {
+			op1b = DupOutPt(op1, true);
+			op2b = DupOutPt(op2, false);
+			op1->Next = op2;
+			op2->Prev = op1;
+			op1b->Prev = op2b;
+			op2b->Next = op1b;
+			j->OutPt1 = op1;
+			j->OutPt2 = op1b;
+			return true;
+		}
+	}
 }
 //----------------------------------------------------------------------
 
 static OutRec *ParseFirstLeft(OutRec *FirstLeft) {
-  while (FirstLeft && !FirstLeft->Pts)
-    FirstLeft = FirstLeft->FirstLeft;
-  return FirstLeft;
+	while (FirstLeft && !FirstLeft->Pts)
+		FirstLeft = FirstLeft->FirstLeft;
+	return FirstLeft;
 }
 //------------------------------------------------------------------------------
 
 void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) {
-  //tests if NewOutRec contains the polygon before reassigning FirstLeft
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-    OutRec *outRec = m_PolyOuts[i];
-    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
-    if (outRec->Pts && firstLeft == OldOutRec) {
-      if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
-        outRec->FirstLeft = NewOutRec;
-    }
-  }
+	// tests if NewOutRec contains the polygon before reassigning FirstLeft
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+		OutRec *outRec = m_PolyOuts[i];
+		OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+		if (outRec->Pts && firstLeft == OldOutRec) {
+			if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
+				outRec->FirstLeft = NewOutRec;
+		}
+	}
 }
 //----------------------------------------------------------------------
 
 void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) {
-  //A polygon has split into two such that one is now the inner of the other.
-  //It's possible that these polygons now wrap around other polygons, so check
-  //every polygon that's also contained by OuterOutRec's FirstLeft container
-  //(including 0) to see if they've become inner to the new inner polygon ...
-  OutRec *orfl = OuterOutRec->FirstLeft;
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-    OutRec *outRec = m_PolyOuts[i];
-
-    if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
-      continue;
-    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
-    if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec)
-      continue;
-    if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
-      outRec->FirstLeft = InnerOutRec;
-    else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
-      outRec->FirstLeft = OuterOutRec;
-    else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec)
-      outRec->FirstLeft = orfl;
-  }
+	// A polygon has split into two such that one is now the inner of the other.
+	// It's possible that these polygons now wrap around other polygons, so check
+	// every polygon that's also contained by OuterOutRec's FirstLeft container
+	//(including 0) to see if they've become inner to the new inner polygon ...
+	OutRec *orfl = OuterOutRec->FirstLeft;
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+		OutRec *outRec = m_PolyOuts[i];
+
+		if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
+			continue;
+		OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+		if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec)
+			continue;
+		if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
+			outRec->FirstLeft = InnerOutRec;
+		else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
+			outRec->FirstLeft = OuterOutRec;
+		else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec)
+			outRec->FirstLeft = orfl;
+	}
 }
 //----------------------------------------------------------------------
 void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) {
-  //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
-  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
-    OutRec *outRec = m_PolyOuts[i];
-    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
-    if (outRec->Pts && firstLeft == OldOutRec)
-      outRec->FirstLeft = NewOutRec;
-  }
+	// reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
+	for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+		OutRec *outRec = m_PolyOuts[i];
+		OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+		if (outRec->Pts && firstLeft == OldOutRec)
+			outRec->FirstLeft = NewOutRec;
+	}
 }
 //----------------------------------------------------------------------
 
 void Clipper::JoinCommonEdges() {
-  for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
-    Join *join = m_Joins[i];
-
-    OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
-    OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
-
-    if (!outRec1->Pts || !outRec2->Pts)
-      continue;
-    if (outRec1->IsOpen || outRec2->IsOpen)
-      continue;
-
-    //get the polygon fragment with the correct hole state (FirstLeft)
-    //before calling JoinPoints() ...
-    OutRec *holeStateRec;
-    if (outRec1 == outRec2)
-      holeStateRec = outRec1;
-    else if (OutRec1RightOfOutRec2(outRec1, outRec2))
-      holeStateRec = outRec2;
-    else if (OutRec1RightOfOutRec2(outRec2, outRec1))
-      holeStateRec = outRec1;
-    else
-      holeStateRec = GetLowermostRec(outRec1, outRec2);
-
-    if (!JoinPoints(join, outRec1, outRec2))
-      continue;
-
-    if (outRec1 == outRec2) {
-      //instead of joining two polygons, we've just created a new one by
-      //splitting one polygon into two.
-      outRec1->Pts = join->OutPt1;
-      outRec1->BottomPt = 0;
-      outRec2 = CreateOutRec();
-      outRec2->Pts = join->OutPt2;
-
-      //update all OutRec2.Pts Idx's ...
-      UpdateOutPtIdxs(*outRec2);
-
-      if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
-        //outRec1 contains outRec2 ...
-        outRec2->IsHole = !outRec1->IsHole;
-        outRec2->FirstLeft = outRec1;
-
-        if (m_UsingPolyTree)
-          FixupFirstLefts2(outRec2, outRec1);
-
-        if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
-          ReversePolyPtLinks(outRec2->Pts);
-
-      } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
-        //outRec2 contains outRec1 ...
-        outRec2->IsHole = outRec1->IsHole;
-        outRec1->IsHole = !outRec2->IsHole;
-        outRec2->FirstLeft = outRec1->FirstLeft;
-        outRec1->FirstLeft = outRec2;
-
-        if (m_UsingPolyTree)
-          FixupFirstLefts2(outRec1, outRec2);
-
-        if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
-          ReversePolyPtLinks(outRec1->Pts);
-      } else {
-        //the 2 polygons are completely separate ...
-        outRec2->IsHole = outRec1->IsHole;
-        outRec2->FirstLeft = outRec1->FirstLeft;
-
-        //fixup FirstLeft pointers that may need reassigning to OutRec2
-        if (m_UsingPolyTree)
-          FixupFirstLefts1(outRec1, outRec2);
-      }
-
-    } else {
-      //joined 2 polygons together ...
-
-      outRec2->Pts = 0;
-      outRec2->BottomPt = 0;
-      outRec2->Idx = outRec1->Idx;
-
-      outRec1->IsHole = holeStateRec->IsHole;
-      if (holeStateRec == outRec2)
-        outRec1->FirstLeft = outRec2->FirstLeft;
-      outRec2->FirstLeft = outRec1;
-
-      if (m_UsingPolyTree)
-        FixupFirstLefts3(outRec2, outRec1);
-    }
-  }
+	for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
+		Join *join = m_Joins[i];
+
+		OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
+		OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
+
+		if (!outRec1->Pts || !outRec2->Pts)
+			continue;
+		if (outRec1->IsOpen || outRec2->IsOpen)
+			continue;
+
+		// get the polygon fragment with the correct hole state (FirstLeft)
+		// before calling JoinPoints() ...
+		OutRec *holeStateRec;
+		if (outRec1 == outRec2)
+			holeStateRec = outRec1;
+		else if (OutRec1RightOfOutRec2(outRec1, outRec2))
+			holeStateRec = outRec2;
+		else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+			holeStateRec = outRec1;
+		else
+			holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+		if (!JoinPoints(join, outRec1, outRec2))
+			continue;
+
+		if (outRec1 == outRec2) {
+			// instead of joining two polygons, we've just created a new one by
+			// splitting one polygon into two.
+			outRec1->Pts = join->OutPt1;
+			outRec1->BottomPt = 0;
+			outRec2 = CreateOutRec();
+			outRec2->Pts = join->OutPt2;
+
+			// update all OutRec2.Pts Idx's ...
+			UpdateOutPtIdxs(*outRec2);
+
+			if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
+				// outRec1 contains outRec2 ...
+				outRec2->IsHole = !outRec1->IsHole;
+				outRec2->FirstLeft = outRec1;
+
+				if (m_UsingPolyTree)
+					FixupFirstLefts2(outRec2, outRec1);
+
+				if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
+					ReversePolyPtLinks(outRec2->Pts);
+
+			} else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
+				// outRec2 contains outRec1 ...
+				outRec2->IsHole = outRec1->IsHole;
+				outRec1->IsHole = !outRec2->IsHole;
+				outRec2->FirstLeft = outRec1->FirstLeft;
+				outRec1->FirstLeft = outRec2;
+
+				if (m_UsingPolyTree)
+					FixupFirstLefts2(outRec1, outRec2);
+
+				if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
+					ReversePolyPtLinks(outRec1->Pts);
+			} else {
+				// the 2 polygons are completely separate ...
+				outRec2->IsHole = outRec1->IsHole;
+				outRec2->FirstLeft = outRec1->FirstLeft;
+
+				// fixup FirstLeft pointers that may need reassigning to OutRec2
+				if (m_UsingPolyTree)
+					FixupFirstLefts1(outRec1, outRec2);
+			}
+
+		} else {
+			// joined 2 polygons together ...
+
+			outRec2->Pts = 0;
+			outRec2->BottomPt = 0;
+			outRec2->Idx = outRec1->Idx;
+
+			outRec1->IsHole = holeStateRec->IsHole;
+			if (holeStateRec == outRec2)
+				outRec1->FirstLeft = outRec2->FirstLeft;
+			outRec2->FirstLeft = outRec1;
+
+			if (m_UsingPolyTree)
+				FixupFirstLefts3(outRec2, outRec1);
+		}
+	}
 }
 
 //------------------------------------------------------------------------------
@@ -3531,15 +3553,15 @@ void Clipper::JoinCommonEdges() {
 //------------------------------------------------------------------------------
 
 DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
-  if (pt2.X == pt1.X && pt2.Y == pt1.Y)
-    return DoublePoint(0, 0);
+	if (pt2.X == pt1.X && pt2.Y == pt1.Y)
+		return DoublePoint(0, 0);
 
-  double Dx = (double) (pt2.X - pt1.X);
-  double dy = (double) (pt2.Y - pt1.Y);
-  double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy);
-  Dx *= f;
-  dy *= f;
-  return DoublePoint(dy, -Dx);
+	double Dx = (double)(pt2.X - pt1.X);
+	double dy = (double)(pt2.Y - pt1.Y);
+	double f = 1 * 1.0 / sqrt(Dx * Dx + dy * dy);
+	Dx *= f;
+	dy *= f;
+	return DoublePoint(dy, -Dx);
 }
 
 //------------------------------------------------------------------------------
@@ -3547,400 +3569,405 @@ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
 //------------------------------------------------------------------------------
 
 ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
-  this->MiterLimit = miterLimit;
-  this->ArcTolerance = arcTolerance;
-  m_lowest.X = -1;
+	this->MiterLimit = miterLimit;
+	this->ArcTolerance = arcTolerance;
+	m_lowest.X = -1;
 }
 //------------------------------------------------------------------------------
 
 ClipperOffset::~ClipperOffset() {
-  Clear();
+	Clear();
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::Clear() {
-  for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
-    delete m_polyNodes.Childs[i];
-  m_polyNodes.Childs.clear();
-  m_lowest.X = -1;
+	for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+		delete m_polyNodes.Childs[i];
+	m_polyNodes.Childs.clear();
+	m_lowest.X = -1;
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::AddPath(const Path &path, JoinType joinType, EndType endType) {
-  int highI = (int) path.size() - 1;
-  if (highI < 0)
-    return;
-  PolyNode *newNode = new PolyNode();
-  newNode->m_jointype = joinType;
-  newNode->m_endtype = endType;
-
-  //strip duplicate points from path and also get index to the lowest point ...
-  if (endType == etClosedLine || endType == etClosedPolygon)
-    while (highI > 0 && path[0] == path[highI])
-      highI--;
-  newNode->Contour.reserve(highI + 1);
-  newNode->Contour.push_back(path[0]);
-  int j = 0, k = 0;
-  for (int i = 1; i <= highI; i++)
-    if (newNode->Contour[j] != path[i]) {
-      j++;
-      newNode->Contour.push_back(path[i]);
-      if (path[i].Y > newNode->Contour[k].Y ||
-          (path[i].Y == newNode->Contour[k].Y &&
-              path[i].X < newNode->Contour[k].X))
-        k = j;
-    }
-  if (endType == etClosedPolygon && j < 2) {
-    delete newNode;
-    return;
-  }
-  m_polyNodes.AddChild(*newNode);
-
-  //if this path's lowest pt is lower than all the others then update m_lowest
-  if (endType != etClosedPolygon)
-    return;
-  if (m_lowest.X < 0)
-    m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
-  else {
-    IntPoint ip = m_polyNodes.Childs[(int) m_lowest.X]->Contour[(int) m_lowest.Y];
-    if (newNode->Contour[k].Y > ip.Y ||
-        (newNode->Contour[k].Y == ip.Y &&
-            newNode->Contour[k].X < ip.X))
-      m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
-  }
+	int highI = (int)path.size() - 1;
+	if (highI < 0)
+		return;
+	PolyNode *newNode = new PolyNode();
+	newNode->m_jointype = joinType;
+	newNode->m_endtype = endType;
+
+	// strip duplicate points from path and also get index to the lowest point ...
+	if (endType == etClosedLine || endType == etClosedPolygon)
+		while (highI > 0 && path[0] == path[highI])
+			highI--;
+	newNode->Contour.reserve(highI + 1);
+	newNode->Contour.push_back(path[0]);
+	int j = 0, k = 0;
+	for (int i = 1; i <= highI; i++)
+		if (newNode->Contour[j] != path[i]) {
+			j++;
+			newNode->Contour.push_back(path[i]);
+			if (path[i].Y > newNode->Contour[k].Y ||
+				(path[i].Y == newNode->Contour[k].Y &&
+				 path[i].X < newNode->Contour[k].X))
+				k = j;
+		}
+	if (endType == etClosedPolygon && j < 2) {
+		delete newNode;
+		return;
+	}
+	m_polyNodes.AddChild(*newNode);
+
+	// if this path's lowest pt is lower than all the others then update m_lowest
+	if (endType != etClosedPolygon)
+		return;
+	if (m_lowest.X < 0)
+		m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+	else {
+		IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
+		if (newNode->Contour[k].Y > ip.Y ||
+			(newNode->Contour[k].Y == ip.Y &&
+			 newNode->Contour[k].X < ip.X))
+			m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+	}
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType, EndType endType) {
-  for (Paths::size_type i = 0; i < paths.size(); ++i)
-    AddPath(paths[i], joinType, endType);
+	for (Paths::size_type i = 0; i < paths.size(); ++i)
+		AddPath(paths[i], joinType, endType);
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::FixOrientations() {
-  //fixup orientations of all closed paths if the orientation of the
-  //closed path with the lowermost vertex is wrong ...
-  if (m_lowest.X >= 0 &&
-      !Orientation(m_polyNodes.Childs[(int) m_lowest.X]->Contour)) {
-    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
-      PolyNode &node = *m_polyNodes.Childs[i];
-      if (node.m_endtype == etClosedPolygon ||
-          (node.m_endtype == etClosedLine && Orientation(node.Contour)))
-        ReversePath(node.Contour);
-    }
-  } else {
-    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
-      PolyNode &node = *m_polyNodes.Childs[i];
-      if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
-        ReversePath(node.Contour);
-    }
-  }
+	// fixup orientations of all closed paths if the orientation of the
+	// closed path with the lowermost vertex is wrong ...
+	if (m_lowest.X >= 0 &&
+		!Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) {
+		for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+			PolyNode &node = *m_polyNodes.Childs[i];
+			if (node.m_endtype == etClosedPolygon ||
+				(node.m_endtype == etClosedLine && Orientation(node.Contour)))
+				ReversePath(node.Contour);
+		}
+	} else {
+		for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+			PolyNode &node = *m_polyNodes.Childs[i];
+			if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
+				ReversePath(node.Contour);
+		}
+	}
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::Execute(Paths &solution, double delta) {
-  solution.clear();
-  FixOrientations();
-  DoOffset(delta);
-
-  //now clean up 'corners' ...
-  Clipper clpr;
-  clpr.AddPaths(m_destPolys, ptSubject, true);
-  if (delta > 0) {
-    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
-  } else {
-    IntRect r = clpr.GetBounds();
-    Path outer(4);
-    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
-    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
-    outer[2] = IntPoint(r.right + 10, r.top - 10);
-    outer[3] = IntPoint(r.left - 10, r.top - 10);
-
-    clpr.AddPath(outer, ptSubject, true);
-    clpr.ReverseSolution(true);
-    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
-    if (solution.size() > 0)
-      solution.erase(solution.begin());
-  }
+	solution.clear();
+	FixOrientations();
+	DoOffset(delta);
+
+	// now clean up 'corners' ...
+	Clipper clpr;
+	clpr.AddPaths(m_destPolys, ptSubject, true);
+	if (delta > 0) {
+		clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+	} else {
+		IntRect r = clpr.GetBounds();
+		Path outer(4);
+		outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+		outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+		outer[2] = IntPoint(r.right + 10, r.top - 10);
+		outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+		clpr.AddPath(outer, ptSubject, true);
+		clpr.ReverseSolution(true);
+		clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+		if (solution.size() > 0)
+			solution.erase(solution.begin());
+	}
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::Execute(PolyTree &solution, double delta) {
-  solution.Clear();
-  FixOrientations();
-  DoOffset(delta);
-
-  //now clean up 'corners' ...
-  Clipper clpr;
-  clpr.AddPaths(m_destPolys, ptSubject, true);
-  if (delta > 0) {
-    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
-  } else {
-    IntRect r = clpr.GetBounds();
-    Path outer(4);
-    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
-    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
-    outer[2] = IntPoint(r.right + 10, r.top - 10);
-    outer[3] = IntPoint(r.left - 10, r.top - 10);
-
-    clpr.AddPath(outer, ptSubject, true);
-    clpr.ReverseSolution(true);
-    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
-    //remove the outer PolyNode rectangle ...
-    if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
-      PolyNode *outerNode = solution.Childs[0];
-      solution.Childs.reserve(outerNode->ChildCount());
-      solution.Childs[0] = outerNode->Childs[0];
-      solution.Childs[0]->Parent = outerNode->Parent;
-      for (int i = 1; i < outerNode->ChildCount(); ++i)
-        solution.AddChild(*outerNode->Childs[i]);
-    } else
-      solution.Clear();
-  }
+	solution.Clear();
+	FixOrientations();
+	DoOffset(delta);
+
+	// now clean up 'corners' ...
+	Clipper clpr;
+	clpr.AddPaths(m_destPolys, ptSubject, true);
+	if (delta > 0) {
+		clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+	} else {
+		IntRect r = clpr.GetBounds();
+		Path outer(4);
+		outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+		outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+		outer[2] = IntPoint(r.right + 10, r.top - 10);
+		outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+		clpr.AddPath(outer, ptSubject, true);
+		clpr.ReverseSolution(true);
+		clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+		// remove the outer PolyNode rectangle ...
+		if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
+			PolyNode *outerNode = solution.Childs[0];
+			solution.Childs.reserve(outerNode->ChildCount());
+			solution.Childs[0] = outerNode->Childs[0];
+			solution.Childs[0]->Parent = outerNode->Parent;
+			for (int i = 1; i < outerNode->ChildCount(); ++i)
+				solution.AddChild(*outerNode->Childs[i]);
+		} else
+			solution.Clear();
+	}
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::DoOffset(double delta) {
-  m_destPolys.clear();
-  m_delta = delta;
-
-  //if Zero offset, just copy any CLOSED polygons to m_p and return ...
-  if (NEAR_ZERO(delta)) {
-    m_destPolys.reserve(m_polyNodes.ChildCount());
-    for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
-      PolyNode &node = *m_polyNodes.Childs[i];
-      if (node.m_endtype == etClosedPolygon)
-        m_destPolys.push_back(node.Contour);
-    }
-    return;
-  }
-
-  //see offset_triginometry3.svg in the documentation folder ...
-  if (MiterLimit > 2)
-    m_miterLim = 2 / (MiterLimit * MiterLimit);
-  else
-    m_miterLim = 0.5;
-
-  double y;
-  if (ArcTolerance <= 0.0)
-    y = def_arc_tolerance;
-  else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
-    y = std::fabs(delta) * def_arc_tolerance;
-  else
-    y = ArcTolerance;
-  //see offset_triginometry2.svg in the documentation folder ...
-  double steps = pi / std::acos(1 - y / std::fabs(delta));
-  if (steps > std::fabs(delta) * pi)
-    steps = std::fabs(delta) * pi;  //ie excessive precision check
-  m_sin = std::sin(two_pi / steps);
-  m_cos = std::cos(two_pi / steps);
-  m_StepsPerRad = steps / two_pi;
-  if (delta < 0.0)
-    m_sin = -m_sin;
-
-  m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
-  for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
-    PolyNode &node = *m_polyNodes.Childs[i];
-    m_srcPoly = node.Contour;
-
-    int len = (int) m_srcPoly.size();
-    if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
-      continue;
-
-    m_destPoly.clear();
-    if (len == 1) {
-      if (node.m_jointype == jtRound) {
-        double X = 1.0, Y = 0.0;
-        for (cInt j = 1; j <= steps; j++) {
-          m_destPoly.push_back(IntPoint(
-              Round(m_srcPoly[0].X + X * delta),
-              Round(m_srcPoly[0].Y + Y * delta)));
-          double X2 = X;
-          X = X * m_cos - m_sin * Y;
-          Y = X2 * m_sin + Y * m_cos;
-        }
-      } else {
-        double X = -1.0, Y = -1.0;
-        for (int j = 0; j < 4; ++j) {
-          m_destPoly.push_back(IntPoint(
-              Round(m_srcPoly[0].X + X * delta),
-              Round(m_srcPoly[0].Y + Y * delta)));
-          if (X < 0)
-            X = 1;
-          else if (Y < 0)
-            Y = 1;
-          else
-            X = -1;
-        }
-      }
-      m_destPolys.push_back(m_destPoly);
-      continue;
-    }
-    //build m_normals ...
-    m_normals.clear();
-    m_normals.reserve(len);
-    for (int j = 0; j < len - 1; ++j)
-      m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
-    if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
-      m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
-    else
-      m_normals.push_back(DoublePoint(m_normals[len - 2]));
-
-    if (node.m_endtype == etClosedPolygon) {
-      int k = len - 1;
-      for (int j = 0; j < len; ++j)
-        OffsetPoint(j, k, node.m_jointype);
-      m_destPolys.push_back(m_destPoly);
-    } else if (node.m_endtype == etClosedLine) {
-      int k = len - 1;
-      for (int j = 0; j < len; ++j)
-        OffsetPoint(j, k, node.m_jointype);
-      m_destPolys.push_back(m_destPoly);
-      m_destPoly.clear();
-      //re-build m_normals ...
-      DoublePoint n = m_normals[len - 1];
-      for (int j = len - 1; j > 0; j--)
-        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
-      m_normals[0] = DoublePoint(-n.X, -n.Y);
-      k = 0;
-      for (int j = len - 1; j >= 0; j--)
-        OffsetPoint(j, k, node.m_jointype);
-      m_destPolys.push_back(m_destPoly);
-    } else {
-      int k = 0;
-      for (int j = 1; j < len - 1; ++j)
-        OffsetPoint(j, k, node.m_jointype);
-
-      IntPoint pt1;
-      if (node.m_endtype == etOpenButt) {
-        int j = len - 1;
-        pt1 = IntPoint((cInt) Round(m_srcPoly[j].X + m_normals[j].X *
-            delta), (cInt) Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
-        m_destPoly.push_back(pt1);
-        pt1 = IntPoint((cInt) Round(m_srcPoly[j].X - m_normals[j].X *
-            delta), (cInt) Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
-        m_destPoly.push_back(pt1);
-      } else {
-        int j = len - 1;
-        k = len - 2;
-        m_sinA = 0;
-        m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
-        if (node.m_endtype == etOpenSquare)
-          DoSquare(j, k);
-        else
-          DoRound(j, k);
-      }
-
-      //re-build m_normals ...
-      for (int j = len - 1; j > 0; j--)
-        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
-      m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
-
-      k = len - 1;
-      for (int j = k - 1; j > 0; --j)
-        OffsetPoint(j, k, node.m_jointype);
-
-      if (node.m_endtype == etOpenButt) {
-        pt1 = IntPoint((cInt) Round(m_srcPoly[0].X - m_normals[0].X * delta),
-                       (cInt) Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
-        m_destPoly.push_back(pt1);
-        pt1 = IntPoint((cInt) Round(m_srcPoly[0].X + m_normals[0].X * delta),
-                       (cInt) Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
-        m_destPoly.push_back(pt1);
-      } else {
-        k = 1;
-        m_sinA = 0;
-        if (node.m_endtype == etOpenSquare)
-          DoSquare(0, 1);
-        else
-          DoRound(0, 1);
-      }
-      m_destPolys.push_back(m_destPoly);
-    }
-  }
+	m_destPolys.clear();
+	m_delta = delta;
+
+	// if Zero offset, just copy any CLOSED polygons to m_p and return ...
+	if (NEAR_ZERO(delta)) {
+		m_destPolys.reserve(m_polyNodes.ChildCount());
+		for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+			PolyNode &node = *m_polyNodes.Childs[i];
+			if (node.m_endtype == etClosedPolygon)
+				m_destPolys.push_back(node.Contour);
+		}
+		return;
+	}
+
+	// see offset_triginometry3.svg in the documentation folder ...
+	if (MiterLimit > 2)
+		m_miterLim = 2 / (MiterLimit * MiterLimit);
+	else
+		m_miterLim = 0.5;
+
+	double y;
+	if (ArcTolerance <= 0.0)
+		y = def_arc_tolerance;
+	else if (ArcTolerance > fabs(delta) * def_arc_tolerance)
+		y = fabs(delta) * def_arc_tolerance;
+	else
+		y = ArcTolerance;
+	// see offset_triginometry2.svg in the documentation folder ...
+	double steps = pi / acos(1 - y / fabs(delta));
+	if (steps > fabs(delta) * pi)
+		steps = fabs(delta) * pi; // ie excessive precision check
+	m_sin = sin(two_pi / steps);
+	m_cos = cos(two_pi / steps);
+	m_StepsPerRad = steps / two_pi;
+	if (delta < 0.0)
+		m_sin = -m_sin;
+
+	m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
+	for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+		PolyNode &node = *m_polyNodes.Childs[i];
+		m_srcPoly = node.Contour;
+
+		int len = (int)m_srcPoly.size();
+		if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
+			continue;
+
+		m_destPoly.clear();
+		if (len == 1) {
+			if (node.m_jointype == jtRound) {
+				double X = 1.0, Y = 0.0;
+				for (cInt j = 1; j <= steps; j++) {
+					m_destPoly.push_back(IntPoint(
+						Round(m_srcPoly[0].X + X * delta),
+						Round(m_srcPoly[0].Y + Y * delta)));
+					double X2 = X;
+					X = X * m_cos - m_sin * Y;
+					Y = X2 * m_sin + Y * m_cos;
+				}
+			} else {
+				double X = -1.0, Y = -1.0;
+				for (int j = 0; j < 4; ++j) {
+					m_destPoly.push_back(IntPoint(
+						Round(m_srcPoly[0].X + X * delta),
+						Round(m_srcPoly[0].Y + Y * delta)));
+					if (X < 0)
+						X = 1;
+					else if (Y < 0)
+						Y = 1;
+					else
+						X = -1;
+				}
+			}
+			m_destPolys.push_back(m_destPoly);
+			continue;
+		}
+		// build m_normals ...
+		m_normals.clear();
+		m_normals.reserve(len);
+		for (int j = 0; j < len - 1; ++j)
+			m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
+		if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
+			m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
+		else
+			m_normals.push_back(DoublePoint(m_normals[len - 2]));
+
+		if (node.m_endtype == etClosedPolygon) {
+			int k = len - 1;
+			for (int j = 0; j < len; ++j)
+				OffsetPoint(j, k, node.m_jointype);
+			m_destPolys.push_back(m_destPoly);
+		} else if (node.m_endtype == etClosedLine) {
+			int k = len - 1;
+			for (int j = 0; j < len; ++j)
+				OffsetPoint(j, k, node.m_jointype);
+			m_destPolys.push_back(m_destPoly);
+			m_destPoly.clear();
+			// re-build m_normals ...
+			DoublePoint n = m_normals[len - 1];
+			for (int j = len - 1; j > 0; j--)
+				m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+			m_normals[0] = DoublePoint(-n.X, -n.Y);
+			k = 0;
+			for (int j = len - 1; j >= 0; j--)
+				OffsetPoint(j, k, node.m_jointype);
+			m_destPolys.push_back(m_destPoly);
+		} else {
+			int k = 0;
+			for (int j = 1; j < len - 1; ++j)
+				OffsetPoint(j, k, node.m_jointype);
+
+			IntPoint pt1;
+			if (node.m_endtype == etOpenButt) {
+				int j = len - 1;
+				pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X *
+																delta),
+							   (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
+				m_destPoly.push_back(pt1);
+				pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X *
+																delta),
+							   (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
+				m_destPoly.push_back(pt1);
+			} else {
+				int j = len - 1;
+				k = len - 2;
+				m_sinA = 0;
+				m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
+				if (node.m_endtype == etOpenSquare)
+					DoSquare(j, k);
+				else
+					DoRound(j, k);
+			}
+
+			// re-build m_normals ...
+			for (int j = len - 1; j > 0; j--)
+				m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+			m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
+
+			k = len - 1;
+			for (int j = k - 1; j > 0; --j)
+				OffsetPoint(j, k, node.m_jointype);
+
+			if (node.m_endtype == etOpenButt) {
+				pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta),
+							   (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
+				m_destPoly.push_back(pt1);
+				pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta),
+							   (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
+				m_destPoly.push_back(pt1);
+			} else {
+				k = 1;
+				m_sinA = 0;
+				if (node.m_endtype == etOpenSquare)
+					DoSquare(0, 1);
+				else
+					DoRound(0, 1);
+			}
+			m_destPolys.push_back(m_destPoly);
+		}
+	}
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) {
-  //cross product ...
-  m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
-  if (std::fabs(m_sinA * m_delta) < 1.0) {
-    //dot product ...
-    double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
-    if (cosA > 0) // angle => 0 degrees
-    {
-      m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
-                                    Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
-      return;
-    }
-    //else angle => 180 degrees
-  } else if (m_sinA > 1.0)
-    m_sinA = 1.0;
-  else if (m_sinA < -1.0)
-    m_sinA = -1.0;
-
-  if (m_sinA * m_delta < 0) {
-    m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
-                                  Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
-    m_destPoly.push_back(m_srcPoly[j]);
-    m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
-                                  Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
-  } else
-    switch (jointype) {
-    case jtMiter: {
-      double r = 1 + (m_normals[j].X * m_normals[k].X +
-          m_normals[j].Y * m_normals[k].Y);
-      if (r >= m_miterLim)
-        DoMiter(j, k, r);
-      else
-        DoSquare(j, k);
-      break;
-    }
-    case jtSquare: DoSquare(j, k);
-      break;
-    case jtRound: DoRound(j, k);
-      break;
-    }
-  k = j;
+	// cross product ...
+	m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
+	if (fabs(m_sinA * m_delta) < 1.0) {
+		// dot product ...
+		double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
+		if (cosA > 0) // angle => 0 degrees
+		{
+			m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+										  Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+			return;
+		}
+		// else angle => 180 degrees
+	} else if (m_sinA > 1.0)
+		m_sinA = 1.0;
+	else if (m_sinA < -1.0)
+		m_sinA = -1.0;
+
+	if (m_sinA * m_delta < 0) {
+		m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+									  Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+		m_destPoly.push_back(m_srcPoly[j]);
+		m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+									  Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+	} else
+		switch (jointype) {
+		case jtMiter: {
+			double r = 1 + (m_normals[j].X * m_normals[k].X +
+							m_normals[j].Y * m_normals[k].Y);
+			if (r >= m_miterLim)
+				DoMiter(j, k, r);
+			else
+				DoSquare(j, k);
+			break;
+		}
+		case jtSquare:
+			DoSquare(j, k);
+			break;
+		case jtRound:
+			DoRound(j, k);
+			break;
+		}
+	k = j;
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::DoSquare(int j, int k) {
-  double dx = std::tan(std::atan2(m_sinA,
-                                  m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4);
-  m_destPoly.push_back(IntPoint(
-      Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
-      Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
-  m_destPoly.push_back(IntPoint(
-      Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
-      Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
+	double dx = tan(atan2(m_sinA,
+						  m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) /
+					4);
+	m_destPoly.push_back(IntPoint(
+		Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
+		Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
+	m_destPoly.push_back(IntPoint(
+		Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
+		Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::DoMiter(int j, int k, double r) {
-  double q = m_delta / r;
-  m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
-                                Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
+	double q = m_delta / r;
+	m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
+								  Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
 }
 //------------------------------------------------------------------------------
 
 void ClipperOffset::DoRound(int j, int k) {
-  double a = std::atan2(m_sinA,
-                        m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y);
-  int steps = std::max((int) Round(m_StepsPerRad * std::fabs(a)), 1);
+	double a = atan2(m_sinA,
+					 m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y);
+	int steps = MAX((int)Round(m_StepsPerRad * fabs(a)), 1);
 
-  double X = m_normals[k].X, Y = m_normals[k].Y, X2;
-  for (int i = 0; i < steps; ++i) {
-    m_destPoly.push_back(IntPoint(
-        Round(m_srcPoly[j].X + X * m_delta),
-        Round(m_srcPoly[j].Y + Y * m_delta)));
-    X2 = X;
-    X = X * m_cos - m_sin * Y;
-    Y = X2 * m_sin + Y * m_cos;
-  }
-  m_destPoly.push_back(IntPoint(
-      Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
-      Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+	double X = m_normals[k].X, Y = m_normals[k].Y, X2;
+	for (int i = 0; i < steps; ++i) {
+		m_destPoly.push_back(IntPoint(
+			Round(m_srcPoly[j].X + X * m_delta),
+			Round(m_srcPoly[j].Y + Y * m_delta)));
+		X2 = X;
+		X = X * m_cos - m_sin * Y;
+		Y = X2 * m_sin + Y * m_cos;
+	}
+	m_destPoly.push_back(IntPoint(
+		Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+		Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
 }
 
 //------------------------------------------------------------------------------
@@ -3948,364 +3975,345 @@ void ClipperOffset::DoRound(int j, int k) {
 //------------------------------------------------------------------------------
 
 void Clipper::DoSimplePolygons() {
-  PolyOutList::size_type i = 0;
-  while (i < m_PolyOuts.size()) {
-    OutRec *outrec = m_PolyOuts[i++];
-    OutPt *op = outrec->Pts;
-    if (!op || outrec->IsOpen)
-      continue;
-    do //for each Pt in Polygon until duplicate found do ...
-    {
-      OutPt *op2 = op->Next;
-      while (op2 != outrec->Pts) {
-        if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
-          //split the polygon into two ...
-          OutPt *op3 = op->Prev;
-          OutPt *op4 = op2->Prev;
-          op->Prev = op4;
-          op4->Next = op;
-          op2->Prev = op3;
-          op3->Next = op2;
-
-          outrec->Pts = op;
-          OutRec *outrec2 = CreateOutRec();
-          outrec2->Pts = op2;
-          UpdateOutPtIdxs(*outrec2);
-          if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
-            //OutRec2 is contained by OutRec1 ...
-            outrec2->IsHole = !outrec->IsHole;
-            outrec2->FirstLeft = outrec;
-            if (m_UsingPolyTree)
-              FixupFirstLefts2(outrec2, outrec);
-          } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
-            //OutRec1 is contained by OutRec2 ...
-            outrec2->IsHole = outrec->IsHole;
-            outrec->IsHole = !outrec2->IsHole;
-            outrec2->FirstLeft = outrec->FirstLeft;
-            outrec->FirstLeft = outrec2;
-            if (m_UsingPolyTree)
-              FixupFirstLefts2(outrec, outrec2);
-          } else {
-            //the 2 polygons are separate ...
-            outrec2->IsHole = outrec->IsHole;
-            outrec2->FirstLeft = outrec->FirstLeft;
-            if (m_UsingPolyTree)
-              FixupFirstLefts1(outrec, outrec2);
-          }
-          op2 = op; //ie get ready for the Next iteration
-        }
-        op2 = op2->Next;
-      }
-      op = op->Next;
-    } while (op != outrec->Pts);
-  }
+	PolyOutList::size_type i = 0;
+	while (i < m_PolyOuts.size()) {
+		OutRec *outrec = m_PolyOuts[i++];
+		OutPt *op = outrec->Pts;
+		if (!op || outrec->IsOpen)
+			continue;
+		do // for each Pt in Polygon until duplicate found do ...
+		{
+			OutPt *op2 = op->Next;
+			while (op2 != outrec->Pts) {
+				if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
+					// split the polygon into two ...
+					OutPt *op3 = op->Prev;
+					OutPt *op4 = op2->Prev;
+					op->Prev = op4;
+					op4->Next = op;
+					op2->Prev = op3;
+					op3->Next = op2;
+
+					outrec->Pts = op;
+					OutRec *outrec2 = CreateOutRec();
+					outrec2->Pts = op2;
+					UpdateOutPtIdxs(*outrec2);
+					if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
+						// OutRec2 is contained by OutRec1 ...
+						outrec2->IsHole = !outrec->IsHole;
+						outrec2->FirstLeft = outrec;
+						if (m_UsingPolyTree)
+							FixupFirstLefts2(outrec2, outrec);
+					} else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
+						// OutRec1 is contained by OutRec2 ...
+						outrec2->IsHole = outrec->IsHole;
+						outrec->IsHole = !outrec2->IsHole;
+						outrec2->FirstLeft = outrec->FirstLeft;
+						outrec->FirstLeft = outrec2;
+						if (m_UsingPolyTree)
+							FixupFirstLefts2(outrec, outrec2);
+					} else {
+						// the 2 polygons are separate ...
+						outrec2->IsHole = outrec->IsHole;
+						outrec2->FirstLeft = outrec->FirstLeft;
+						if (m_UsingPolyTree)
+							FixupFirstLefts1(outrec, outrec2);
+					}
+					op2 = op; // ie get ready for the Next iteration
+				}
+				op2 = op2->Next;
+			}
+			op = op->Next;
+		} while (op != outrec->Pts);
+	}
 }
 //------------------------------------------------------------------------------
 
 void ReversePath(Path &p) {
-  std::reverse(p.begin(), p.end());
+	Path reversed(p.size());
+	for (uint i = 0; i < p.size(); i++) {
+		reversed[p.size() - 1 - i] = p[i];
+	}
+	p = reversed;
 }
 //------------------------------------------------------------------------------
 
 void ReversePaths(Paths &p) {
-  for (Paths::size_type i = 0; i < p.size(); ++i)
-    ReversePath(p[i]);
+	for (Paths::size_type i = 0; i < p.size(); ++i)
+		ReversePath(p[i]);
 }
 //------------------------------------------------------------------------------
 
 void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) {
-  Clipper c;
-  c.StrictlySimple(true);
-  c.AddPath(in_poly, ptSubject, true);
-  c.Execute(ctUnion, out_polys, fillType, fillType);
+	Clipper c;
+	c.StrictlySimple(true);
+	c.AddPath(in_poly, ptSubject, true);
+	c.Execute(ctUnion, out_polys, fillType, fillType);
 }
 //------------------------------------------------------------------------------
 
 void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) {
-  Clipper c;
-  c.StrictlySimple(true);
-  c.AddPaths(in_polys, ptSubject, true);
-  c.Execute(ctUnion, out_polys, fillType, fillType);
+	Clipper c;
+	c.StrictlySimple(true);
+	c.AddPaths(in_polys, ptSubject, true);
+	c.Execute(ctUnion, out_polys, fillType, fillType);
 }
 //------------------------------------------------------------------------------
 
 void SimplifyPolygons(Paths &polys, PolyFillType fillType) {
-  SimplifyPolygons(polys, polys, fillType);
+	SimplifyPolygons(polys, polys, fillType);
 }
 //------------------------------------------------------------------------------
 
 inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) {
-  double Dx = ((double) pt1.X - pt2.X);
-  double dy = ((double) pt1.Y - pt2.Y);
-  return (Dx * Dx + dy * dy);
+	double Dx = ((double)pt1.X - pt2.X);
+	double dy = ((double)pt1.Y - pt2.Y);
+	return (Dx * Dx + dy * dy);
 }
 //------------------------------------------------------------------------------
 
 double DistanceFromLineSqrd(
-    const IntPoint &pt, const IntPoint &ln1, const IntPoint &ln2) {
-  //The equation of a line in general form (Ax + By + C = 0)
-  //given 2 points (x�,y�) & (x�,y�) is ...
-  //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
-  //A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
-  //perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
-  //see http://en.wikipedia.org/wiki/Perpendicular_distance
-  double A = double(ln1.Y - ln2.Y);
-  double B = double(ln2.X - ln1.X);
-  double C = A * ln1.X + B * ln1.Y;
-  C = A * pt.X + B * pt.Y - C;
-  return (C * C) / (A * A + B * B);
+	const IntPoint &pt, const IntPoint &ln1, const IntPoint &ln2) {
+	// The equation of a line in general form (Ax + By + C = 0)
+	// given 2 points (x�,y�) & (x�,y�) is ...
+	//(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
+	// A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
+	// perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
+	// see http://en.wikipedia.org/wiki/Perpendicular_distance
+	double A = double(ln1.Y - ln2.Y);
+	double B = double(ln2.X - ln1.X);
+	double C = A * ln1.X + B * ln1.Y;
+	C = A * pt.X + B * pt.Y - C;
+	return (C * C) / (A * A + B * B);
 }
 //---------------------------------------------------------------------------
 
 bool SlopesNearCollinear(const IntPoint &pt1,
-                         const IntPoint &pt2, const IntPoint &pt3, double distSqrd) {
-  //this function is more accurate when the point that's geometrically
-  //between the other 2 points is the one that's tested for distance.
-  //ie makes it more likely to pick up 'spikes' ...
-  if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
-    if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
-      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
-    else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
-      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
-    else
-      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
-  } else {
-    if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
-      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
-    else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
-      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
-    else
-      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
-  }
+						 const IntPoint &pt2, const IntPoint &pt3, double distSqrd) {
+	// this function is more accurate when the point that's geometrically
+	// between the other 2 points is the one that's tested for distance.
+	// ie makes it more likely to pick up 'spikes' ...
+	if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
+		if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
+			return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+		else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
+			return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+		else
+			return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+	} else {
+		if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
+			return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+		else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
+			return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+		else
+			return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+	}
 }
 //------------------------------------------------------------------------------
 
 bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) {
-  double Dx = (double) pt1.X - pt2.X;
-  double dy = (double) pt1.Y - pt2.Y;
-  return ((Dx * Dx) + (dy * dy) <= distSqrd);
+	double Dx = (double)pt1.X - pt2.X;
+	double dy = (double)pt1.Y - pt2.Y;
+	return ((Dx * Dx) + (dy * dy) <= distSqrd);
 }
 //------------------------------------------------------------------------------
 
 OutPt *ExcludeOp(OutPt *op) {
-  OutPt *result = op->Prev;
-  result->Next = op->Next;
-  op->Next->Prev = result;
-  result->Idx = 0;
-  return result;
+	OutPt *result = op->Prev;
+	result->Next = op->Next;
+	op->Next->Prev = result;
+	result->Idx = 0;
+	return result;
 }
 //------------------------------------------------------------------------------
 
 void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) {
-  //distance = proximity in units/pixels below which vertices
-  //will be stripped. Default ~= sqrt(2).
-
-  size_t size = in_poly.size();
-
-  if (size == 0) {
-    out_poly.clear();
-    return;
-  }
-
-  OutPt *outPts = new OutPt[size];
-  for (size_t i = 0; i < size; ++i) {
-    outPts[i].Pt = in_poly[i];
-    outPts[i].Next = &outPts[(i + 1) % size];
-    outPts[i].Next->Prev = &outPts[i];
-    outPts[i].Idx = 0;
-  }
-
-  double distSqrd = distance * distance;
-  OutPt *op = &outPts[0];
-  while (op->Idx == 0 && op->Next != op->Prev) {
-    if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
-      op = ExcludeOp(op);
-      size--;
-    } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
-      ExcludeOp(op->Next);
-      op = ExcludeOp(op);
-      size -= 2;
-    } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) {
-      op = ExcludeOp(op);
-      size--;
-    } else {
-      op->Idx = 1;
-      op = op->Next;
-    }
-  }
-
-  if (size < 3)
-    size = 0;
-  out_poly.resize(size);
-  for (size_t i = 0; i < size; ++i) {
-    out_poly[i] = op->Pt;
-    op = op->Next;
-  }
-  delete[] outPts;
+	// distance = proximity in units/pixels below which vertices
+	// will be stripped. Default ~= sqrt(2).
+
+	size_t size = in_poly.size();
+
+	if (size == 0) {
+		out_poly.clear();
+		return;
+	}
+
+	OutPt *outPts = new OutPt[size];
+	for (size_t i = 0; i < size; ++i) {
+		outPts[i].Pt = in_poly[i];
+		outPts[i].Next = &outPts[(i + 1) % size];
+		outPts[i].Next->Prev = &outPts[i];
+		outPts[i].Idx = 0;
+	}
+
+	double distSqrd = distance * distance;
+	OutPt *op = &outPts[0];
+	while (op->Idx == 0 && op->Next != op->Prev) {
+		if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
+			op = ExcludeOp(op);
+			size--;
+		} else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
+			ExcludeOp(op->Next);
+			op = ExcludeOp(op);
+			size -= 2;
+		} else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) {
+			op = ExcludeOp(op);
+			size--;
+		} else {
+			op->Idx = 1;
+			op = op->Next;
+		}
+	}
+
+	if (size < 3)
+		size = 0;
+	out_poly.resize(size);
+	for (size_t i = 0; i < size; ++i) {
+		out_poly[i] = op->Pt;
+		op = op->Next;
+	}
+	delete[] outPts;
 }
 //------------------------------------------------------------------------------
 
 void CleanPolygon(Path &poly, double distance) {
-  CleanPolygon(poly, poly, distance);
+	CleanPolygon(poly, poly, distance);
 }
 //------------------------------------------------------------------------------
 
 void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) {
-  out_polys.resize(in_polys.size());
-  for (Paths::size_type i = 0; i < in_polys.size(); ++i)
-    CleanPolygon(in_polys[i], out_polys[i], distance);
+	out_polys.resize(in_polys.size());
+	for (Paths::size_type i = 0; i < in_polys.size(); ++i)
+		CleanPolygon(in_polys[i], out_polys[i], distance);
 }
 //------------------------------------------------------------------------------
 
 void CleanPolygons(Paths &polys, double distance) {
-  CleanPolygons(polys, polys, distance);
+	CleanPolygons(polys, polys, distance);
 }
 //------------------------------------------------------------------------------
 
 void Minkowski(const Path &poly, const Path &path,
-               Paths &solution, bool isSum, bool isClosed) {
-  int delta = (isClosed ? 1 : 0);
-  size_t polyCnt = poly.size();
-  size_t pathCnt = path.size();
-  Paths pp;
-  pp.reserve(pathCnt);
-  if (isSum)
-    for (size_t i = 0; i < pathCnt; ++i) {
-      Path p;
-      p.reserve(polyCnt);
-      for (size_t j = 0; j < poly.size(); ++j)
-        p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
-      pp.push_back(p);
-    }
-  else
-    for (size_t i = 0; i < pathCnt; ++i) {
-      Path p;
-      p.reserve(polyCnt);
-      for (size_t j = 0; j < poly.size(); ++j)
-        p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
-      pp.push_back(p);
-    }
-
-  solution.clear();
-  solution.reserve((pathCnt + delta) * (polyCnt + 1));
-  for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
-    for (size_t j = 0; j < polyCnt; ++j) {
-      Path quad;
-      quad.reserve(4);
-      quad.push_back(pp[i % pathCnt][j % polyCnt]);
-      quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
-      quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
-      quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
-      if (!Orientation(quad))
-        ReversePath(quad);
-      solution.push_back(quad);
-    }
+			   Paths &solution, bool isSum, bool isClosed) {
+	int delta = (isClosed ? 1 : 0);
+	size_t polyCnt = poly.size();
+	size_t pathCnt = path.size();
+	Paths pp;
+	pp.reserve(pathCnt);
+	if (isSum)
+		for (size_t i = 0; i < pathCnt; ++i) {
+			Path p;
+			p.reserve(polyCnt);
+			for (size_t j = 0; j < poly.size(); ++j)
+				p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
+			pp.push_back(p);
+		}
+	else
+		for (size_t i = 0; i < pathCnt; ++i) {
+			Path p;
+			p.reserve(polyCnt);
+			for (size_t j = 0; j < poly.size(); ++j)
+				p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
+			pp.push_back(p);
+		}
+
+	solution.clear();
+	solution.reserve((pathCnt + delta) * (polyCnt + 1));
+	for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
+		for (size_t j = 0; j < polyCnt; ++j) {
+			Path quad;
+			quad.reserve(4);
+			quad.push_back(pp[i % pathCnt][j % polyCnt]);
+			quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
+			quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
+			quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
+			if (!Orientation(quad))
+				ReversePath(quad);
+			solution.push_back(quad);
+		}
 }
 //------------------------------------------------------------------------------
 
 void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, bool pathIsClosed) {
-  Minkowski(pattern, path, solution, true, pathIsClosed);
-  Clipper c;
-  c.AddPaths(solution, ptSubject, true);
-  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+	Minkowski(pattern, path, solution, true, pathIsClosed);
+	Clipper c;
+	c.AddPaths(solution, ptSubject, true);
+	c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
 }
 //------------------------------------------------------------------------------
 
 void TranslatePath(const Path &input, Path &output, const IntPoint delta) {
-  //precondition: input != output
-  output.resize(input.size());
-  for (size_t i = 0; i < input.size(); ++i)
-    output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
+	// precondition: input != output
+	output.resize(input.size());
+	for (size_t i = 0; i < input.size(); ++i)
+		output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
 }
 //------------------------------------------------------------------------------
 
 void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, bool pathIsClosed) {
-  Clipper c;
-  for (size_t i = 0; i < paths.size(); ++i) {
-    Paths tmp;
-    Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
-    c.AddPaths(tmp, ptSubject, true);
-    if (pathIsClosed) {
-      Path tmp2;
-      TranslatePath(paths[i], tmp2, pattern[0]);
-      c.AddPath(tmp2, ptClip, true);
-    }
-  }
-  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+	Clipper c;
+	for (size_t i = 0; i < paths.size(); ++i) {
+		Paths tmp;
+		Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
+		c.AddPaths(tmp, ptSubject, true);
+		if (pathIsClosed) {
+			Path tmp2;
+			TranslatePath(paths[i], tmp2, pattern[0]);
+			c.AddPath(tmp2, ptClip, true);
+		}
+	}
+	c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
 }
 //------------------------------------------------------------------------------
 
 void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) {
-  Minkowski(poly1, poly2, solution, false, true);
-  Clipper c;
-  c.AddPaths(solution, ptSubject, true);
-  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+	Minkowski(poly1, poly2, solution, false, true);
+	Clipper c;
+	c.AddPaths(solution, ptSubject, true);
+	c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
 }
 //------------------------------------------------------------------------------
 
-enum NodeType { ntAny, ntOpen, ntClosed };
+enum NodeType { ntAny,
+				ntOpen,
+				ntClosed };
 
 void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype, Paths &paths) {
-  bool match = true;
-  if (nodetype == ntClosed)
-    match = !polynode.IsOpen();
-  else if (nodetype == ntOpen)
-    return;
+	bool match = true;
+	if (nodetype == ntClosed)
+		match = !polynode.IsOpen();
+	else if (nodetype == ntOpen)
+		return;
 
-  if (!polynode.Contour.empty() && match)
-    paths.push_back(polynode.Contour);
-  for (int i = 0; i < polynode.ChildCount(); ++i)
-    AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
+	if (!polynode.Contour.empty() && match)
+		paths.push_back(polynode.Contour);
+	for (int i = 0; i < polynode.ChildCount(); ++i)
+		AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
 }
 //------------------------------------------------------------------------------
 
 void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) {
-  paths.resize(0);
-  paths.reserve(polytree.Total());
-  AddPolyNodeToPaths(polytree, ntAny, paths);
+	paths.resize(0);
+	paths.reserve(polytree.Total());
+	AddPolyNodeToPaths(polytree, ntAny, paths);
 }
 //------------------------------------------------------------------------------
 
 void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) {
-  paths.resize(0);
-  paths.reserve(polytree.Total());
-  AddPolyNodeToPaths(polytree, ntClosed, paths);
+	paths.resize(0);
+	paths.reserve(polytree.Total());
+	AddPolyNodeToPaths(polytree, ntClosed, paths);
 }
 //------------------------------------------------------------------------------
 
 void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) {
-  paths.resize(0);
-  paths.reserve(polytree.Total());
-  //Open paths are top level only, so ...
-  for (int i = 0; i < polytree.ChildCount(); ++i)
-    if (polytree.Childs[i]->IsOpen())
-      paths.push_back(polytree.Childs[i]->Contour);
-}
-//------------------------------------------------------------------------------
-
-std::ostream &operator<<(std::ostream &s, const IntPoint &p) {
-  s << "(" << p.X << "," << p.Y << ")";
-  return s;
-}
-//------------------------------------------------------------------------------
-
-std::ostream &operator<<(std::ostream &s, const Path &p) {
-  if (p.empty())
-    return s;
-  Path::size_type last = p.size() - 1;
-  for (Path::size_type i = 0; i < last; i++)
-    s << "(" << p[i].X << "," << p[i].Y << "), ";
-  s << "(" << p[last].X << "," << p[last].Y << ")\n";
-  return s;
-}
-//------------------------------------------------------------------------------
-
-std::ostream &operator<<(std::ostream &s, const Paths &p) {
-  for (Paths::size_type i = 0; i < p.size(); i++)
-    s << p[i];
-  s << "\n";
-  return s;
+	paths.resize(0);
+	paths.reserve(polytree.Total());
+	// Open paths are top level only, so ...
+	for (int i = 0; i < polytree.ChildCount(); ++i)
+		if (polytree.Childs[i]->IsOpen())
+			paths.push_back(polytree.Childs[i]->Contour);
 }
 //------------------------------------------------------------------------------
 
-} //ClipperLib namespace
+} // namespace ClipperLib
diff --git a/engines/twp/clipper/clipper.hpp b/engines/twp/clipper/clipper.hpp
index 1193a33b335..8598a9eccc4 100644
--- a/engines/twp/clipper/clipper.hpp
+++ b/engines/twp/clipper/clipper.hpp
@@ -49,15 +49,7 @@
 //use_deprecated: Enables temporary support for the obsolete functions
 //#define use_deprecated
 
-#include <vector>
-#include <list>
-#include <set>
-#include <stdexcept>
-#include <cstring>
-#include <cstdlib>
-#include <ostream>
-#include <functional>
-#include <queue>
+#include "common/array.h"
 
 namespace ClipperLib {
 
@@ -70,18 +62,84 @@ enum PolyType { ptSubject, ptClip };
 enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
 
 #ifdef use_int32
-typedef int cInt;
+typedef int32 cInt;
 static cInt const loRange = 0x7FFF;
 static cInt const hiRange = 0x7FFF;
 #else
-typedef signed long long cInt;
+typedef int64 cInt;
 static cInt const loRange = 0x3FFFFFFF;
 static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
-typedef signed long long long64;     //used by Int128 class
-typedef unsigned long long ulong64;
+typedef int64 long64;     //used by Int128 class
+typedef uint64 ulong64;
 
 #endif
 
+/**
+ * Queue ordered by a provided priority function
+ * NOTE: Unlike in the C std library, we have to provde a comparitor that sorts
+ * the array so that the smallest priority comes last
+ */
+template <class _Ty, class _Container = Common::Array<_Ty>, class _Pr = Common::Less<_Ty>>
+class priority_queue {
+public:
+	typedef const _Ty& const_reference;
+
+public:
+	priority_queue() : c(), comp() {}
+
+	explicit priority_queue(const _Pr &_Pred) : c(), comp(_Pred) {}
+
+	priority_queue(const _Pr &_Pred, const _Container &_Cont) : c(_Cont), comp(_Pred) {
+		make_heap(c.begin(), c.end(), comp);
+	}
+
+	template <class _InIt>
+	priority_queue(_InIt _First, _InIt _Last, const _Pr &_Pred, const _Container &_Cont) : c(_Cont), comp(_Pred) {
+		c.insert(c.end(), _First, _Last);
+		make_heap(c.begin(), c.end(), comp);
+	}
+
+	template <class _InIt>
+	priority_queue(_InIt _First, _InIt _Last) : c(_First, _Last), comp() {
+		make_heap(c.begin(), c.end(), comp);
+	}
+
+	template <class _InIt>
+	priority_queue(_InIt _First, _InIt _Last, const _Pr &_Pred) : c(_First, _Last), comp(_Pred) {
+		make_heap(c.begin(), c.end(), comp);
+	}
+
+	bool empty() const {
+		return c.empty();
+	}
+
+	size_t size() const {
+		return c.size();
+	}
+
+	const_reference top() const {
+		return c.back();
+	}
+
+	void push(const typename _Container::value_type &_Val) {
+		c.push_back(_Val);
+		Common::sort(c.begin(), c.end(), comp);
+	}
+
+	void pop() {
+		c.pop_back();
+	}
+
+	void swap(priority_queue &_Right) {
+		SWAP(c, _Right.c);
+		SWAP(comp, _Right.comp);
+	}
+
+protected:
+	_Container c;
+	_Pr comp;
+};
+
 struct IntPoint {
   cInt X;
   cInt Y;
@@ -101,8 +159,8 @@ struct IntPoint {
 };
 //------------------------------------------------------------------------------
 
-typedef std::vector<IntPoint> Path;
-typedef std::vector<Path> Paths;
+typedef Common::Array<IntPoint> Path;
+typedef Common::Array<Path> Paths;
 
 inline Path &operator<<(Path &poly, const IntPoint &p) {
   poly.push_back(p);
@@ -113,10 +171,6 @@ inline Paths &operator<<(Paths &polys, const Path &p) {
   return polys;
 }
 
-std::ostream &operator<<(std::ostream &s, const IntPoint &p);
-std::ostream &operator<<(std::ostream &s, const Path &p);
-std::ostream &operator<<(std::ostream &s, const Paths &p);
-
 struct DoublePoint {
   double X;
   double Y;
@@ -134,7 +188,7 @@ enum JoinType { jtSquare, jtRound, jtMiter };
 enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound };
 
 class PolyNode;
-typedef std::vector<PolyNode *> PolyNodes;
+typedef Common::Array<PolyNode *> PolyNodes;
 
 class PolyNode {
 public:
@@ -208,10 +262,10 @@ struct OutPt;
 struct OutRec;
 struct Join;
 
-typedef std::vector<OutRec *> PolyOutList;
-typedef std::vector<TEdge *> EdgeList;
-typedef std::vector<Join *> JoinList;
-typedef std::vector<IntersectNode *> IntersectList;
+typedef Common::Array<OutRec *> PolyOutList;
+typedef Common::Array<TEdge *> EdgeList;
+typedef Common::Array<Join *> JoinList;
+typedef Common::Array<IntersectNode *> IntersectList;
 
 //------------------------------------------------------------------------------
 
@@ -244,7 +298,7 @@ protected:
   void DeleteFromAEL(TEdge *e);
   void UpdateEdgeIntoAEL(TEdge *&e);
 
-  typedef std::vector<LocalMinimum> MinimaList;
+  typedef Common::Array<LocalMinimum> MinimaList;
   MinimaList::iterator m_CurrentLM;
   MinimaList m_MinimaList;
 
@@ -255,7 +309,7 @@ protected:
   PolyOutList m_PolyOuts;
   TEdge *m_ActiveEdges;
 
-  typedef std::priority_queue<cInt> ScanbeamList;
+  typedef priority_queue<cInt> ScanbeamList;
   ScanbeamList m_Scanbeam;
 };
 //------------------------------------------------------------------------------
@@ -292,7 +346,7 @@ private:
   JoinList m_GhostJoins;
   IntersectList m_IntersectList;
   ClipType m_ClipType;
-  typedef std::list<cInt> MaximaList;
+  typedef Common::Array<cInt> MaximaList;
   MaximaList m_Maxima;
   TEdge *m_SortedEdges;
   bool m_ExecuteLocked;
@@ -371,7 +425,7 @@ private:
   Paths m_destPolys;
   Path m_srcPoly;
   Path m_destPoly;
-  std::vector<DoublePoint> m_normals;
+  Common::Array<DoublePoint> m_normals;
   double m_delta, m_sinA, m_sin, m_cos;
   double m_miterLim, m_StepsPerRad;
   IntPoint m_lowest;
@@ -386,16 +440,6 @@ private:
 };
 //------------------------------------------------------------------------------
 
-class clipperException : public std::exception {
-public:
-  clipperException(const char *description) : m_descr(description) {}
-  virtual ~clipperException() throw() {}
-  virtual const char *what() const throw() { return m_descr.c_str(); }
-private:
-  std::string m_descr;
-};
-//------------------------------------------------------------------------------
-
 } //ClipperLib namespace
 
 #endif //clipper_hpp
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 704f744f3a1..ed8ef871c36 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -19,8 +19,6 @@
  *
  */
 
-#define FORBIDDEN_SYMBOL_ALLOW_ALL
-
 #include "twp/twp.h"
 #include "twp/squtil.h"
 #include "twp/clipper/clipper.hpp"


Commit: e98219abde64445d8f7a905507761f90b3c2b712
    https://github.com/scummvm/scummvm/commit/e98219abde64445d8f7a905507761f90b3c2b712
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Rename g_engine by g_twp

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/audio.cpp
    engines/twp/camera.cpp
    engines/twp/console.cpp
    engines/twp/debugtools.cpp
    engines/twp/dialog.cpp
    engines/twp/enginedialogtarget.cpp
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/hud.cpp
    engines/twp/lighting.cpp
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/shaders.cpp
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index aa6aa33def6..872c736f3c2 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -198,7 +198,7 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
 	} else {
-		obj = g_engine->_actor;
+		obj = g_twp->_actor;
 	}
 	sqpush(v, distance((Vector2i)actor->_node->getPos(), (Vector2i)obj->getUsePos()));
 	return 1;
@@ -207,7 +207,7 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 3) {
-		Common::SharedPtr<Object> actor1(g_engine->_actor);
+		Common::SharedPtr<Object> actor1(g_twp->_actor);
 		Common::SharedPtr<Object> actor2(sqactor(v, 2));
 		if (!actor2)
 			return sq_throwerror(v, "failed to get actor");
@@ -281,8 +281,8 @@ static SQInteger actorHidden(HSQUIRRELVM v) {
 	int hidden = 0;
 	if (SQ_FAILED(sqget(v, 3, hidden)))
 		return sq_throwerror(v, "failed to get hidden");
-	if (hidden && (g_engine->_actor == actor)) {
-		g_engine->follow(nullptr);
+	if (hidden && (g_twp->_actor == actor)) {
+		g_twp->follow(nullptr);
 	}
 	actor->_node->setVisible(hidden == 0);
 	return 0;
@@ -334,7 +334,7 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 3, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (const auto &walkbox : g_engine->_room->_walkboxes) {
+	for (const auto &walkbox : g_twp->_room->_walkboxes) {
 		if (walkbox._name == name) {
 			if (walkbox.contains((Vector2i)actor->_node->getAbsPos())) {
 				sqpush(v, true);
@@ -387,16 +387,16 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get selectable");
 		switch (selectable) {
 		case OFF:
-			g_engine->_actorSwitcher._mode &= (~asOn);
+			g_twp->_actorSwitcher._mode &= (~asOn);
 			break;
 		case ON:
-			g_engine->_actorSwitcher._mode |= asOn;
+			g_twp->_actorSwitcher._mode |= asOn;
 			break;
 		case TEMP_UNSELECTABLE:
-			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
+			g_twp->_actorSwitcher._mode |= asTemporaryUnselectable;
 			break;
 		case TEMP_SELECTABLE:
-			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
+			g_twp->_actorSwitcher._mode &= ~asTemporaryUnselectable;
 			break;
 		default:
 			return sq_throwerror(v, "invalid selectable value");
@@ -411,7 +411,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 			int slot;
 			if (SQ_FAILED(sqget(v, 2, slot)))
 				return sq_throwerror(v, "failed to get slot");
-			g_engine->_hud._actorSlots[slot - 1].selectable = selectable;
+			g_twp->_hud._actorSlots[slot - 1].selectable = selectable;
 		} else {
 			Common::SharedPtr<Object> actor = sqactor(v, 2);
 			if (!actor)
@@ -419,7 +419,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 			Common::String key;
 			sqgetf(actor->_table, "_key", key);
 			debugC(kDebugActScript, "actorSlotSelectable(%s, %s)", key.c_str(), selectable ? "yes" : "no");
-			ActorSlot *slot = g_engine->_hud.actorSlot(actor);
+			ActorSlot *slot = g_twp->_hud.actorSlot(actor);
 			if (!slot)
 				warning("slot for actor %s not found", key.c_str());
 			else
@@ -569,7 +569,7 @@ static SQInteger actorTalking(HSQUIRRELVM v) {
 	if (sq_gettop(v) == 2) {
 		actor = sqobj(v, 2);
 	} else {
-		actor = g_engine->_actor;
+		actor = g_twp->_actor;
 	}
 	bool isTalking = actor && actor->getTalking() && actor->getTalking()->isEnabled();
 	sqpush(v, isTalking);
@@ -709,7 +709,7 @@ static SQInteger actorWalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	Common::SharedPtr<Object> actor;
 	if (nArgs == 1) {
-		actor = g_engine->_actor;
+		actor = g_twp->_actor;
 	} else if (nArgs == 2) {
 		actor = sqactor(v, 2);
 	}
@@ -769,7 +769,7 @@ static SQInteger addSelectableActor(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, slot)))
 		return sq_throwerror(v, "failed to get slot");
 	Common::SharedPtr<Object> actor = sqactor(v, 3);
-	g_engine->_hud._actorSlots[slot - 1].actor = actor;
+	g_twp->_hud._actorSlots[slot - 1].actor = actor;
 	return 0;
 }
 
@@ -780,7 +780,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	if (sq_gettype(v, 2) != OT_TABLE)
 		return sq_throwerror(v, "failed to get a table");
 
-	HSQUIRRELVM vm = g_engine->getVm();
+	HSQUIRRELVM vm = g_twp->getVm();
 	Common::SharedPtr<Object> actor = Object::createActor();
 	sq_resetobject(&actor->_table);
 	sq_getstackobj(v, 2, &actor->_table);
@@ -795,7 +795,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	actor->_node = Common::SharedPtr<Node>(new ActorNode(actor));
 	actor->_nodeAnim = Common::SharedPtr<Anim>(new Anim(actor.get()));
 	actor->_node->addChild(actor->_nodeAnim.get());
-	g_engine->_actors.push_back(actor);
+	g_twp->_actors.push_back(actor);
 
 	sq_pushobject(v, actor->_table);
 	return 1;
@@ -805,7 +805,7 @@ static SQInteger flashSelectableActor(HSQUIRRELVM v) {
 	int time = 0;
 	if (SQ_FAILED(sqget(v, 2, time)))
 		return sq_throwerror(v, "failed to get time");
-	g_engine->flashSelectableActor(time);
+	g_twp->flashSelectableActor(time);
 	return 0;
 }
 
@@ -829,7 +829,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 		index = 3;
 	} else {
 		index = 2;
-		obj = g_engine->_actor;
+		obj = g_twp->_actor;
 	}
 
 	if (sq_gettype(v, index) == OT_ARRAY) {
@@ -859,7 +859,7 @@ static SQInteger sayOrMumbleLine(HSQUIRRELVM v) {
 // See also:
 // - `mumbleLine method`
 static SQInteger sayLine(HSQUIRRELVM v) {
-	g_engine->stopTalking();
+	g_twp->stopTalking();
 	return sayOrMumbleLine(v);
 }
 
@@ -891,7 +891,7 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> actor = sqactor(v, 4);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getAbsPos());
+		Math::Vector2d pos = g_twp->roomToScreen(actor->_node->getAbsPos());
 		x = pos.getX();
 		y = pos.getY();
 		color = actor->_talkColor;
@@ -909,11 +909,11 @@ static SQInteger isActorOnScreen(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor/object");
 
-	if (obj->_room != g_engine->_room) {
+	if (obj->_room != g_twp->_room) {
 		sqpush(v, false);
 	} else {
-		Math::Vector2d pos = obj->_node->getPos() - g_engine->getGfx().cameraPos();
-		Math::Vector2d size = g_engine->getGfx().camera();
+		Math::Vector2d pos = obj->_node->getPos() - g_twp->getGfx().cameraPos();
+		Math::Vector2d size = g_twp->getGfx().camera();
 		bool isOnScreen = Common::Rect(0.0f, 0.0f, size.getX(), size.getY()).contains(pos.getX(), pos.getY());
 		sqpush(v, isOnScreen);
 	}
@@ -924,7 +924,7 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	ActorSlot *slot = g_engine->_hud.actorSlot(actor);
+	ActorSlot *slot = g_twp->_hud.actorSlot(actor);
 	bool selectable = slot && slot->selectable;
 	sqpush(v, selectable);
 	return 1;
@@ -941,7 +941,7 @@ static SQInteger is_actor(HSQUIRRELVM v) {
 // See also masterRoomArray.
 static SQInteger masterActorArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
-	for (auto actor : g_engine->_actors) {
+	for (auto actor : g_twp->_actors) {
 		sqpush(v, actor->_table);
 		sq_arrayappend(v, -2);
 	}
@@ -964,7 +964,7 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (nArgs == 2) {
 		if (sq_gettype(v, 2) == OT_INTEGER) {
-			g_engine->stopTalking();
+			g_twp->stopTalking();
 		} else {
 			Common::SharedPtr<Object> actor = sqobj(v, 2);
 			if (!actor)
@@ -972,7 +972,7 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 			actor->stopTalking();
 		}
 	} else if (nArgs == 1) {
-		g_engine->_actor->stopTalking();
+		g_twp->_actor->stopTalking();
 	}
 	return 0;
 }
@@ -982,7 +982,7 @@ static SQInteger stopTalking(HSQUIRRELVM v) {
 // If they are in a different room, the camera will cut to the new room.
 // The UI will change to reflect the new actor and their inventory.
 static SQInteger selectActor(HSQUIRRELVM v) {
-	g_engine->setActor(sqobj(v, 2));
+	g_twp->setActor(sqobj(v, 2));
 	return 0;
 }
 
@@ -997,7 +997,7 @@ static SQInteger triggerActors(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	sq_newarray(v, 0);
-	for (auto actor : g_engine->_actors) {
+	for (auto actor : g_twp->_actors) {
 		if (obj->contains(actor->_node->getPos())) {
 			sq_pushobject(v, actor->_table);
 			sq_arrayappend(v, -2);
@@ -1043,7 +1043,7 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	sqgetf(table, "dialogNormal", dialogNormal);
 	sqgetf(table, "dialogHighlight", dialogHighlight);
 
-	g_engine->_hud._actorSlots[actorSlot - 1].verbUiColors =
+	g_twp->_hud._actorSlots[actorSlot - 1].verbUiColors =
 		VerbUiColors(
 			Color::rgb(sentence),
 			Color::rgb(verbNormal),
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index f985aa9bbc9..93df01a8ca6 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -69,8 +69,8 @@ float ActorSwitcher::getAlpha(size_t index) const {
 }
 
 void ActorSwitcher::drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index) {
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
 	const SpriteSheetFrame &iconBackFrame = gameSheet->getFrame("icon_background");
 	const SpriteSheetFrame &iconActorFrame = gameSheet->getFrame(icon);
 	const SpriteSheetFrame &iconFrame = gameSheet->getFrame("icon_frame");
@@ -97,7 +97,7 @@ void ActorSwitcher::drawCore(Math::Matrix4 trsf) {
 void ActorSwitcher::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
-	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+	g_twp->getGfx().drawSprite(sf.frame, *texture, color, trsf);
 }
 
 float ActorSwitcher::height() const {
@@ -132,7 +132,7 @@ void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float
 	}
 
 	// check if mouse is over actor icons or gear icon
-	Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
+	Math::Vector2d scrPos = g_twp->winToScreen(g_twp->_cursor.pos);
 	bool oldMouseOver = _mouseOver;
 	_mouseOver = !_down && rect().contains(scrPos.getX(), scrPos.getY());
 
@@ -150,7 +150,7 @@ void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float
 	_animPos = MIN(1.f, _animElapsed / ANIM_DURATION);
 
 	// check if we select an actor or gear icon
-	if (_mouseOver && (g_engine->_cursor.leftDown) && !_down) {
+	if (_mouseOver && (g_twp->_cursor.leftDown) && !_down) {
 		_down = true;
 		// check if we allow to select an actor
 		size_t iconIdx = iconIndex(scrPos);
@@ -159,7 +159,7 @@ void ActorSwitcher::update(const Common::Array<ActorSwitcherSlot> &slots, float
 				_slots[iconIdx].select();
 		}
 	}
-	if (!g_engine->_cursor.leftDown)
+	if (!g_twp->_cursor.leftDown)
 		_down = false;
 }
 
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index b3214e464fd..f2753cb34e6 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -62,7 +62,7 @@ SoundDefinition::SoundDefinition(const Common::String &name) : _name(name), _id(
 void SoundDefinition::load() {
 	if (!_loaded) {
 		GGPackEntryReader entry;
-		entry.open(g_engine->_pack, _name);
+		entry.open(g_twp->_pack, _name);
 		_buffer.resize(entry.size());
 		entry.read(_buffer.data(), entry.size());
 	}
@@ -73,22 +73,22 @@ bool AudioSystem::playing(int id) const {
 	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return false;
-		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+		id = g_twp->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
 	for (const auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef->getId() == id) {
-			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
+			return g_twp->_mixer->isSoundHandleActive(_slot.handle);
 		}
 	}
 	// sound ID ?
-	return g_engine->_mixer->isSoundIDActive(id);
+	return g_twp->_mixer->isSoundIDActive(id);
 }
 
 bool AudioSystem::playing(Common::SharedPtr<SoundDefinition> soundDef) const {
 	for (const auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef == soundDef) {
-			return g_engine->_mixer->isSoundHandleActive(_slot.handle);
+			return g_twp->_mixer->isSoundHandleActive(_slot.handle);
 		}
 	}
 	return false;
@@ -111,17 +111,17 @@ void AudioSystem::stop(int id) {
 	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return;
-		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+		id = g_twp->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID ?
 	for (auto &_slot : _slots) {
 		if (_slot.busy && _slot.sndDef->getId() == id) {
 			_slot.loopTimes = 0;
-			g_engine->_mixer->stopHandle(_slot.handle);
+			g_twp->_mixer->stopHandle(_slot.handle);
 		}
 	}
 	// sound ID ?
-	g_engine->_mixer->stopID(id);
+	g_twp->_mixer->stopID(id);
 }
 
 void AudioSystem::setMasterVolume(float vol) {
@@ -129,8 +129,8 @@ void AudioSystem::setMasterVolume(float vol) {
 
 	// update sounds
 	for (auto &_slot : _slots) {
-		if (_slot.busy && g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
-			g_engine->_mixer->setChannelVolume(_slot.handle, _slot.volume * _masterVolume);
+		if (_slot.busy && g_twp->_mixer->isSoundHandleActive(_slot.handle)) {
+			g_twp->_mixer->setChannelVolume(_slot.handle, _slot.volume * _masterVolume);
 		}
 	}
 }
@@ -142,17 +142,17 @@ float AudioSystem::getMasterVolume() const {
 void AudioSystem::updateVolume(AudioSlot *slot) {
 	float vol = _masterVolume * slot->volume;
 	if (slot->fadeInTimeMs) {
-		vol *= (g_engine->_mixer->getElapsedTime(slot->handle).msecs() / slot->total);
+		vol *= (g_twp->_mixer->getElapsedTime(slot->handle).msecs() / slot->total);
 	}
 	if (slot->fadeOutTimeMs) {
 		float startFade = slot->total - slot->fadeOutTimeMs;
-		float progress = (g_engine->_mixer->getElapsedTime(slot->handle).msecs() - startFade) / slot->fadeOutTimeMs;
+		float progress = (g_twp->_mixer->getElapsedTime(slot->handle).msecs() - startFade) / slot->fadeOutTimeMs;
 		if ((progress >= 0) && (progress <= 1.f)) {
 			vol *= (1.f - progress);
 		}
 		if (progress > 1.0f) {
 			slot->loopTimes = 0;
-			g_engine->_mixer->stopHandle(slot->handle);
+			g_twp->_mixer->stopHandle(slot->handle);
 			return;
 		}
 	}
@@ -160,9 +160,9 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 		Common::SharedPtr<Object> obj = sqobj(slot->objId);
 		if (obj) {
 			float volObj = 0.f;
-			if (obj->_room == g_engine->_room) {
-				float width = g_engine->_room->getScreenSize().getX();
-				float x = g_engine->cameraPos().getX();
+			if (obj->_room == g_twp->_room) {
+				float width = g_twp->_room->getScreenSize().getX();
+				float x = g_twp->cameraPos().getX();
 				float diff = abs(x - obj->_node->getAbsPos().getX());
 				if (diff > (1.5f * width)) {
 					volObj = 0.f;
@@ -173,12 +173,12 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 				}
 
 				float pan = clamp((obj->_node->getAbsPos().getX() - x) / (width / 2), -1.0f, 1.0f);
-				g_engine->_mixer->setChannelBalance(slot->handle, (int8)(pan * 127));
+				g_twp->_mixer->setChannelBalance(slot->handle, (int8)(pan * 127));
 			}
 			vol *= volObj;
 		}
 	}
-	g_engine->_mixer->setChannelVolume(slot->handle, vol * Audio::Mixer::kMaxChannelVolume);
+	g_twp->_mixer->setChannelVolume(slot->handle, vol * Audio::Mixer::kMaxChannelVolume);
 }
 
 void AudioSystem::setVolume(int id, float vol) {
@@ -186,11 +186,11 @@ void AudioSystem::setVolume(int id, float vol) {
 	if (id >= 1 && id <= NUM_AUDIO_SLOTS) {
 		if (!_slots[id].busy)
 			return;
-		id = g_engine->_mixer->getSoundID(_slots[id].handle);
+		id = g_twp->_mixer->getSoundID(_slots[id].handle);
 	}
 	// sound definition ID or sound ID ?
 	for (auto &_slot : _slots) {
-		if (_slot.busy && ((_slot.sndDef->getId() == id) || (g_engine->_mixer->getSoundID(_slot.handle) == id))) {
+		if (_slot.busy && ((_slot.sndDef->getId() == id) || (g_twp->_mixer->getSoundID(_slot.handle) == id))) {
 			_slot.volume = vol;
 			updateVolume(&_slot);
 		}
@@ -199,7 +199,7 @@ void AudioSystem::setVolume(int id, float vol) {
 
 void AudioSystem::update(float) {
 	for (auto &_slot : _slots) {
-		if (_slot.busy && !g_engine->_mixer->isSoundHandleActive(_slot.handle)) {
+		if (_slot.busy && !g_twp->_mixer->isSoundHandleActive(_slot.handle)) {
 			if ((_slot.loopTimes == -1) || _slot.loopTimes > 0) {
 				if (_slot.loopTimes != -1) {
 					_slot.loopTimes--;
@@ -214,7 +214,7 @@ void AudioSystem::update(float) {
 				} else {
 					error("AudioSystem::update(): Unexpected audio format: %s", name.c_str());
 				}
-				g_engine->_mixer->playStream(_slot.soundType, &_slot.handle, audioStream, _slot.id, _slot.volume);
+				g_twp->_mixer->playStream(_slot.soundType, &_slot.handle, audioStream, _slot.id, _slot.volume);
 			} else {
 				_slot.busy = false;
 			}
@@ -231,7 +231,7 @@ void AudioSystem::update(float) {
 AudioSlot *AudioSystem::getFreeSlot() {
 	for (auto &_slot : _slots) {
 		AudioSlot *slot = &_slot;
-		if (!slot->busy || !g_engine->_mixer->isSoundHandleActive(slot->handle)) {
+		if (!slot->busy || !g_twp->_mixer->isSoundHandleActive(slot->handle)) {
 			slot->busy = false;
 			return slot;
 		}
@@ -262,7 +262,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 	if (fadeInTimeMs > 0.f) {
 		volume = 0;
 	}
-	g_engine->_mixer->playStream(cat, &slot->handle, audioStream, id, volume * _masterVolume);
+	g_twp->_mixer->playStream(cat, &slot->handle, audioStream, id, volume * _masterVolume);
 	slot->id = id;
 	slot->objId = objId;
 	slot->sndDef = sndDef;
@@ -278,7 +278,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 int AudioSystem::getElapsed(int id) const {
 	for (const auto &_slot : _slots) {
 		if (_slot.id == id) {
-			Audio::Timestamp t = g_engine->_mixer->getElapsedTime(_slot.handle);
+			Audio::Timestamp t = g_twp->_mixer->getElapsedTime(_slot.handle);
 			return t.msecs();
 		}
 	}
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index f1f6a7e433d..1a55fec67f7 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -39,7 +39,7 @@ void Camera::setAtCore(Math::Vector2d at) {
 	Math::Vector2d screenSize = _room->getScreenSize();
 	_pos = at;
 	clamp(_pos);
-	g_engine->getGfx().cameraPos(_pos - screenSize / 2.f);
+	g_twp->getGfx().cameraPos(_pos - screenSize / 2.f);
 }
 
 void Camera::setAt(Math::Vector2d at) {
diff --git a/engines/twp/console.cpp b/engines/twp/console.cpp
index 38351da10cc..c713dee2ac5 100644
--- a/engines/twp/console.cpp
+++ b/engines/twp/console.cpp
@@ -40,7 +40,7 @@ bool Console::Cmd_exec(int argc, const char **argv) {
 			s += argv[i];
 		}
 	}
-	sqexec(g_engine->getVm(), s.c_str(), "console");
+	sqexec(g_twp->getVm(), s.c_str(), "console");
 	return true;
 }
 
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 5b00fc352d4..2e965e158e0 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -54,7 +54,7 @@ static void drawThreads() {
 		return;
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
-	const auto &threads = g_engine->_threads;
+	const auto &threads = g_twp->_threads;
 	if (ImGui::Begin("Threads", &state._showThreads)) {
 		ImGui::Text("# threads: %u", threads.size());
 		ImGui::Separator();
@@ -68,8 +68,8 @@ static void drawThreads() {
 			ImGui::TableSetupColumn("Line");
 			ImGui::TableHeadersRow();
 
-			if (g_engine->_cutscene) {
-				Common::SharedPtr<ThreadBase> thread(g_engine->_cutscene);
+			if (g_twp->_cutscene) {
+				Common::SharedPtr<ThreadBase> thread(g_twp->_cutscene);
 				SQStackInfos infos;
 				sq_stackinfos(thread->getThread(), 0, &infos);
 
@@ -121,7 +121,7 @@ static void drawObjects() {
 	state._objFilter.Draw();
 
 	// show object list
-	for (auto &layer : g_engine->_room->_layers) {
+	for (auto &layer : g_twp->_room->_layers) {
 		for (auto &obj : layer->_objects) {
 			if (state._objFilter.PassFilter(obj->_key.c_str())) {
 				ImGui::PushID(obj->getId());
@@ -179,7 +179,7 @@ static void drawActors() {
 	ImGui::Begin("Actors", &state._showStack);
 	state._actorFilter.Draw();
 	ImGui::BeginChild("Actor_List");
-	for (auto &actor : g_engine->_actors) {
+	for (auto &actor : g_twp->_actors) {
 		bool selected = actor->getId() == state._selectedActor;
 		Common::String key(actor->_key);
 		if (state._actorFilter.PassFilter(actor->_key.c_str())) {
@@ -224,11 +224,11 @@ static void drawStack() {
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
 	ImGui::Begin("Stack", &state._showStack);
 	ImGui::BeginChild("ScrollingRegion");
-	SQInteger size = sq_gettop(g_engine->getVm());
+	SQInteger size = sq_gettop(g_twp->getVm());
 	ImGui::Text("size: %lld", size);
 	HSQOBJECT obj;
 	for (SQInteger i = 1; i < size; i++) {
-		sq_getstackobj(g_engine->getVm(), -i, &obj);
+		sq_getstackobj(g_twp->getVm(), -i, &obj);
 		ImGui::Text("obj type: 0x%X", obj._type);
 	}
 	ImGui::EndChild();
@@ -247,7 +247,7 @@ static void drawResources() {
 		ImGui::TableSetupColumn("Resolution");
 		ImGui::TableHeadersRow();
 
-		for (auto &res : g_engine->_resManager._textures) {
+		for (auto &res : g_twp->_resManager._textures) {
 			ImGui::TableNextRow();
 			ImGui::TableNextColumn();
 			bool selected = state._textureSelected == res._key;
@@ -266,7 +266,7 @@ static void drawResources() {
 	ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 10.f));
 	ImGui::Text("Preview:");
 	ImGui::BeginChild("TexturePreview", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY);
-	for (auto &res : g_engine->_resManager._textures) {
+	for (auto &res : g_twp->_resManager._textures) {
 		if (state._textureSelected == res._key) {
 			ImGui::Image((ImTextureID)(intptr_t)res._value.id, ImVec2(res._value.width, res._value.height));
 			break;
@@ -283,7 +283,7 @@ static void drawAudio() {
 
 	// count the number of active sounds
 	int count = 0;
-	for (auto &s : g_engine->_audio._slots) {
+	for (auto &s : g_twp->_audio._slots) {
 		if (s.busy)
 			count++;
 	}
@@ -304,13 +304,13 @@ static void drawAudio() {
 		ImGui::TableHeadersRow();
 
 		for (int i = 0; i < NUM_AUDIO_SLOTS; i++) {
-			auto &sound = g_engine->_audio._slots[i];
+			auto &sound = g_twp->_audio._slots[i];
 			ImGui::TableNextRow();
 			ImGui::TableNextColumn();
 			ImGui::Text("#%d", i);
 			if (sound.busy) {
-				float pan = g_engine->_mixer->getChannelBalance(sound.handle) / 128.f;
-				float vol = g_engine->_mixer->getChannelVolume(sound.handle) / 255.f;
+				float pan = g_twp->_mixer->getChannelBalance(sound.handle) / 128.f;
+				float vol = g_twp->_mixer->getChannelVolume(sound.handle) / 255.f;
 				ImGui::TableNextColumn();
 				ImGui::Text("%d", sound.id);
 				ImGui::TableNextColumn();
@@ -325,7 +325,7 @@ static void drawAudio() {
 				ImGui::Text("%0.1f", pan);
 				ImGui::SameLine();
 				if (ImGui::SmallButton("STOP")) {
-					g_engine->_audio.stop(sound.id);
+					g_twp->_audio.stop(sound.id);
 				}
 			}
 		}
@@ -341,58 +341,58 @@ static void drawGeneral() {
 
 	ImGui::Begin("General");
 
-	SQInteger size = sq_gettop(g_engine->getVm());
+	SQInteger size = sq_gettop(g_twp->getVm());
 	ImGui::TextColored(gray, "Stack:");
 	ImGui::SameLine();
 	ImGui::Text("%lld", size);
 	ImGui::TextColored(gray, "Cutscene:");
 	ImGui::SameLine();
-	ImGui::Text("%s", g_engine->_cutscene ? g_engine->_cutscene->getName().c_str() : "no");
-	DialogState dialogState = g_engine->_dialog.getState();
+	ImGui::Text("%s", g_twp->_cutscene ? g_twp->_cutscene->getName().c_str() : "no");
+	DialogState dialogState = g_twp->_dialog.getState();
 	ImGui::TextColored(gray, "In dialog:");
 	ImGui::SameLine();
 	ImGui::Text("%s", ((dialogState == Active) ? "yes" : (dialogState == WaitingForChoice ? "waiting for choice" : "no")));
 	ImGui::TextColored(gray, "Verb:");
 	ImGui::SameLine();
-	Common::String verb = g_engine->getTextDb().getText(g_engine->_hud._verb.text);
-	ImGui::Text("%s %d", verb.c_str(), g_engine->_hud._verb.id.id);
+	Common::String verb = g_twp->getTextDb().getText(g_twp->_hud._verb.text);
+	ImGui::Text("%s %d", verb.c_str(), g_twp->_hud._verb.id.id);
 
-	auto mousePos = g_engine->_cursor.pos;
+	auto mousePos = g_twp->_cursor.pos;
 	ImGui::TextColored(gray, "Pos (screen):");
 	ImGui::SameLine();
 	ImGui::Text("(%.0f, %0.f)", mousePos.getX(), mousePos.getY());
-	if (g_engine->_room) {
-		auto pos = g_engine->screenToRoom(mousePos);
+	if (g_twp->_room) {
+		auto pos = g_twp->screenToRoom(mousePos);
 		ImGui::TextColored(gray, "Pos (room):");
 		ImGui::SameLine();
 		ImGui::Text("(%.0f, %0.f)", pos.getX(), pos.getY());
 	}
 	ImGui::Separator();
-	ImGui::Checkbox("HUD", &g_engine->_inputState._inputHUD);
+	ImGui::Checkbox("HUD", &g_twp->_inputState._inputHUD);
 	ImGui::SameLine();
-	ImGui::Checkbox("Input", &g_engine->_inputState._inputActive);
+	ImGui::Checkbox("Input", &g_twp->_inputState._inputActive);
 	ImGui::SameLine();
-	ImGui::Checkbox("Cursor", &g_engine->_inputState._showCursor);
+	ImGui::Checkbox("Cursor", &g_twp->_inputState._showCursor);
 	ImGui::SameLine();
-	ImGui::Checkbox("Verbs", &g_engine->_inputState._inputVerbsActive);
+	ImGui::Checkbox("Verbs", &g_twp->_inputState._inputVerbsActive);
 	ImGui::SameLine();
-	ImGui::Checkbox("Allow SaveGame", &g_engine->_saveGameManager._allowSaveGame);
+	ImGui::Checkbox("Allow SaveGame", &g_twp->_saveGameManager._allowSaveGame);
 
 	ImGui::Separator();
-	bool isSwitcherOn = g_engine->_actorSwitcher._mode == asOn;
+	bool isSwitcherOn = g_twp->_actorSwitcher._mode == asOn;
 	if (ImGui::Checkbox("Switcher ON", &isSwitcherOn)) {
 		if (isSwitcherOn) {
-			g_engine->_actorSwitcher._mode |= asOn;
+			g_twp->_actorSwitcher._mode |= asOn;
 		} else {
-			g_engine->_actorSwitcher._mode &= ~asOn;
+			g_twp->_actorSwitcher._mode &= ~asOn;
 		}
 	}
-	bool isTemporaryUnselectable = g_engine->_actorSwitcher._mode & asTemporaryUnselectable;
+	bool isTemporaryUnselectable = g_twp->_actorSwitcher._mode & asTemporaryUnselectable;
 	if (ImGui::Checkbox("Switcher Temp. Unselectable", &isTemporaryUnselectable)) {
 		if (isTemporaryUnselectable) {
-			g_engine->_actorSwitcher._mode |= asTemporaryUnselectable;
+			g_twp->_actorSwitcher._mode |= asTemporaryUnselectable;
 		} else {
-			g_engine->_actorSwitcher._mode &= ~asTemporaryUnselectable;
+			g_twp->_actorSwitcher._mode &= ~asTemporaryUnselectable;
 		}
 	}
 	ImGui::Separator();
@@ -425,24 +425,24 @@ static void drawGeneral() {
 	if (ImGui::CollapsingHeader("Camera")) {
 		ImGui::TextColored(gray, "follow:");
 		ImGui::SameLine();
-		ImGui::Text("%s", !g_engine->_followActor ? "(none)" : g_engine->_followActor->_key.c_str());
+		ImGui::Text("%s", !g_twp->_followActor ? "(none)" : g_twp->_followActor->_key.c_str());
 		ImGui::TextColored(gray, "moving:");
 		ImGui::SameLine();
-		ImGui::Text("%s", g_engine->_camera.isMoving() ? "yes" : "no");
-		auto halfScreenSize = g_engine->_room->getScreenSize() / 2.0f;
-		auto camPos = g_engine->cameraPos() - halfScreenSize;
+		ImGui::Text("%s", g_twp->_camera.isMoving() ? "yes" : "no");
+		auto halfScreenSize = g_twp->_room->getScreenSize() / 2.0f;
+		auto camPos = g_twp->cameraPos() - halfScreenSize;
 		if (ImGui::DragFloat2("Camera pos", camPos.getData())) {
-			g_engine->follow(nullptr);
-			g_engine->cameraAt(camPos);
+			g_twp->follow(nullptr);
+			g_twp->cameraAt(camPos);
 		}
-		auto bounds = g_engine->_camera.getBounds();
+		auto bounds = g_twp->_camera.getBounds();
 		if (ImGui::DragFloat4("Bounds", bounds.v)) {
-			g_engine->_camera.setBounds(bounds);
+			g_twp->_camera.setBounds(bounds);
 		}
 	}
 
 	// Room
-	Common::SharedPtr<Room> room = g_engine->_room;
+	Common::SharedPtr<Room> room = g_twp->_room;
 	if (room) {
 		if (ImGui::CollapsingHeader("Room")) {
 			ImGui::TextColored(gray, "Sheet:");
@@ -460,7 +460,7 @@ static void drawGeneral() {
 			Color overlay = room->_overlayNode.getOverlayColor();
 			if (ImGui::ColorEdit4("Overlay", overlay.v))
 				room->_overlayNode.setOverlayColor(overlay);
-			ImGui::Checkbox("Debug Lights", &g_engine->_lighting->_debug);
+			ImGui::Checkbox("Debug Lights", &g_twp->_lighting->_debug);
 			ImGui::ColorEdit4("Ambient Light", room->_lights._ambientLight.v);
 			for (int i = 0; i < room->_lights._numLights; ++i) {
 				Common::String ss = Common::String::format("Light %d", i + 1);
@@ -499,11 +499,11 @@ static void drawGeneral() {
 		int effect = static_cast<int>(room->_effect);
 		if (ImGui::Combo("effect", &effect, RoomEffects))
 			room->_effect = (RoomEffect)effect;
-		ImGui::DragFloat("iFade", &g_engine->_shaderParams.iFade, 0.01f, 0.f, 1.f);
-		ImGui::DragFloat("wobbleIntensity", &g_engine->_shaderParams.wobbleIntensity, 0.01f, 0.f, 1.f);
-		ImGui::DragFloat3("shadows", g_engine->_shaderParams.shadows.v, 0.01f, -1.f, 1.f);
-		ImGui::DragFloat3("midtones", g_engine->_shaderParams.midtones.v, 0.01f, -1.f, 1.f);
-		ImGui::DragFloat3("highlights", g_engine->_shaderParams.highlights.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat("iFade", &g_twp->_shaderParams.iFade, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat("wobbleIntensity", &g_twp->_shaderParams.wobbleIntensity, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat3("shadows", g_twp->_shaderParams.shadows.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("midtones", g_twp->_shaderParams.midtones.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("highlights", g_twp->_shaderParams.highlights.v, 0.01f, -1.f, 1.f);
 	}
 
 	// Fade Effects
@@ -513,10 +513,10 @@ static void drawGeneral() {
 		ImGui::Combo("Fade effect", &state._fadeEffect, FadeEffects);
 		ImGui::DragFloat("Duration", &state._fadeDuration, 0.1f, 0.f, 10.f);
 		ImGui::Checkbox("Fade to sepia", &state._fadeToSepia);
-		ImGui::Text("Elapsed %f", g_engine->_fadeShader->_elapsed);
-		ImGui::Text("Fade %f", g_engine->_fadeShader->_fade);
+		ImGui::Text("Elapsed %f", g_twp->_fadeShader->_elapsed);
+		ImGui::Text("Fade %f", g_twp->_fadeShader->_fade);
 		if (ImGui::Button("GO")) {
-			g_engine->fadeTo((FadeEffect)state._fadeEffect, state._fadeDuration, state._fadeToSepia);
+			g_twp->fadeTo((FadeEffect)state._fadeEffect, state._fadeDuration, state._fadeToSepia);
 		}
 	}
 	ImGui::Separator();
@@ -559,7 +559,7 @@ static void drawScenegraph() {
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
 	ImGui::Begin("Scenegraph", &state._showScenegraph);
-	drawNode(&g_engine->_scene);
+	drawNode(&g_twp->_scene);
 	ImGui::End();
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 17172da2cd6..7730bbfc3e7 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -101,7 +101,7 @@ ExpVisitor::~ExpVisitor() = default;
 
 void ExpVisitor::visit(const YCodeExp &node) {
 	debugC(kDebugDialog, "execute code %s", node._code.c_str());
-	sqexec(g_engine->getVm(), node._code.c_str(), "dialog");
+	sqexec(g_twp->getVm(), node._code.c_str(), "dialog");
 }
 
 void ExpVisitor::visit(const YGoto &node) {
@@ -150,7 +150,7 @@ void ExpVisitor::visit(const YLimit &node) {
 }
 
 void ExpVisitor::visit(const YSay &node) {
-	Common::String text(g_engine->getTextDb().getText(node._text));
+	Common::String text(g_twp->getTextDb().getText(node._text));
 	_dialog->_action = _dialog->_tgt->say(node._actor, text);
 }
 
@@ -219,7 +219,7 @@ void Dialog::start(const Common::String &actor, const Common::String &name, cons
 	Common::String path = name + ".byack";
 	debugC(kDebugDialog, "start dialog %s", path.c_str());
 	GGPackEntryReader reader;
-	reader.open(g_engine->_pack, path);
+	reader.open(g_twp->_pack, path);
 	YackParser parser;
 	_cu.reset(parser.parse(&reader));
 	selectLabel(0, node);
@@ -257,7 +257,7 @@ void Dialog::update(float dt) {
 					}
 				}
 				slot->_text.setColor(over ? colorHover : color);
-				if (over && g_engine->_cursor.isLeftDown())
+				if (over && g_twp->_cursor.isLeftDown())
 					choose(i);
 			}
 		}
@@ -422,7 +422,7 @@ void Dialog::running(float dt) {
 }
 
 static Common::String text(const Common::String &txt) {
-	Common::String result(g_engine->getTextDb().getText(txt));
+	Common::String result(g_twp->getTextDb().getText(txt));
 	result = remove(result, '(', ')');
 	result = remove(result, '{', '}');
 	return result;
@@ -462,7 +462,7 @@ void Dialog::drawCore(Math::Matrix4 trsf) {
 		if (slot->_isValid) {
 			Math::Matrix4 t(trsf);
 			t.translate(Math::Vector3d(slot->getPos().getX(), slot->getPos().getY(), 0.f));
-			slot->_text.draw(g_engine->getGfx(), t);
+			slot->_text.draw(g_twp->getGfx(), t);
 		}
 	}
 }
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index d6ccf1d99b3..c152997ced2 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -53,7 +53,7 @@ private:
 };
 
 static Common::SharedPtr<Object> actor(const Common::String &name) {
-	for (auto a : g_engine->_actors) {
+	for (auto a : g_twp->_actors) {
 		if (a->_key == name)
 			return a;
 	}
@@ -63,18 +63,18 @@ static Common::SharedPtr<Object> actor(const Common::String &name) {
 static Common::SharedPtr<Object> actorOrCurrent(const Common::String &name) {
 	Common::SharedPtr<Object> result = actor(name);
 	if (!result)
-		result = g_engine->_actor;
+		result = g_twp->_actor;
 	return result;
 }
 
 Color EngineDialogTarget::actorColor(const Common::String &actor) {
 	Common::SharedPtr<Object> act = actorOrCurrent(actor);
-	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogNormal;
+	return g_twp->_hud.actorSlot(act)->verbUiColors.dialogNormal;
 }
 
 Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
 	Common::SharedPtr<Object> act = actorOrCurrent(actor);
-	return g_engine->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
+	return g_twp->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
 }
 
 Common::SharedPtr<Motor> EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
@@ -89,7 +89,7 @@ Common::SharedPtr<Motor> EngineDialogTarget::waitWhile(const Common::String &con
 }
 
 void EngineDialogTarget::shutup() {
-	g_engine->stopTalking();
+	g_twp->stopTalking();
 }
 
 Common::SharedPtr<Motor> EngineDialogTarget::pause(float time) {
@@ -101,11 +101,11 @@ bool EngineDialogTarget::execCond(const Common::String &cond) {
 	Common::SharedPtr<Object> act = actor(cond);
 	if (act) {
 		// yes, so we check if the current actor is the given actor name
-		Common::SharedPtr<Object> curActor = g_engine->_actor;
+		Common::SharedPtr<Object> curActor = g_twp->_actor;
 		return curActor && curActor->_key == act->_key;
 	}
 
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	// compile
 	sq_pushroottable(v);
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 05843d849c3..22ac40ac3c8 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -168,7 +168,7 @@ bool TokenReader::readToken(Token &token) {
 GGFont::~GGFont() {}
 
 void GGFont::load(const Common::String &path) {
-	SpriteSheet *spritesheet = g_engine->_resManager.spriteSheet(path);
+	SpriteSheet *spritesheet = g_twp->_resManager.spriteSheet(path);
 	int lineHeight = 0;
 	for (auto it = spritesheet->_frameTable.begin(); it != spritesheet->_frameTable.end(); it++) {
 		const SpriteSheetFrame &frame = it->_value;
@@ -195,12 +195,12 @@ BmFont::~BmFont() {}
 
 void BmFont::load(const Common::String &name) {
 	Common::String path = name + ".fnt";
-	if (!g_engine->_pack.assetExists(path.c_str())) {
+	if (!g_twp->_pack.assetExists(path.c_str())) {
 		path = name + "Font.fnt";
 	}
 	debugC(kDebugRes, "Load font %s", path.c_str());
 	GGPackEntryReader entry;
-	if (!entry.open(g_engine->_pack, path)) {
+	if (!entry.open(g_twp->_pack, path)) {
 		error("error loading font %s", path.c_str());
 	}
 	char tmp[80];
@@ -261,8 +261,8 @@ Math::Vector2d Text::getBounds() {
 void Text::update() {
 	if (_dirty) {
 		_dirty = false;
-		_font = g_engine->_resManager.font(_fontName);
-		_texture = g_engine->_resManager.texture(_font->getName() + ".png");
+		_font = g_twp->_resManager.font(_fontName);
+		_texture = g_twp->_resManager.texture(_font->getName() + ".png");
 
 		// Reset
 		_vertices.clear();
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 7422064a4e9..48a505a93b6 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -47,14 +47,14 @@ template<typename T>
 static void shuffle(Common::Array<T> &array) {
 	if (array.size() > 1) {
 		for (size_t i = 0; i < array.size(); i++) {
-			size_t j = g_engine->getRandomSource().getRandomNumberRng(0, array.size() - 1);
+			size_t j = g_twp->getRandomSource().getRandomNumberRng(0, array.size() - 1);
 			SWAP(array[j], array[i]);
 		}
 	}
 }
 
 static SQInteger activeVerb(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_hud._verb.id.id);
+	sqpush(v, g_twp->_hud._verb.id.id);
 	return 1;
 }
 
@@ -86,7 +86,7 @@ static SQInteger assetExists(HSQUIRRELVM v) {
 	const SQChar *filename;
 	if (SQ_FAILED(sq_getstring(v, 2, &filename)))
 		return sq_throwerror(v, "failed to get filename");
-	sqpush(v, g_engine->_pack.assetExists(filename));
+	sqpush(v, g_twp->_pack.assetExists(filename));
 	return 1;
 }
 
@@ -113,14 +113,14 @@ static SQInteger cameraAt(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get spot or actor");
-		g_engine->follow(nullptr);
-		g_engine->setRoom(obj->_room);
+		g_twp->follow(nullptr);
+		g_twp->setRoom(obj->_room);
 		pos = obj->getUsePos();
 	} else {
 		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
-	g_engine->follow(nullptr);
-	g_engine->cameraAt(pos);
+	g_twp->follow(nullptr);
+	g_twp->cameraAt(pos);
 	return 0;
 }
 
@@ -135,19 +135,19 @@ static SQInteger cameraBounds(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get yMin");
 	if (SQ_FAILED(sqget(v, 5, yMax)))
 		return sq_throwerror(v, "failed to get yMax");
-	g_engine->_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(xMin, yMin), Math::Vector2d(xMax, yMax)));
+	g_twp->_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(xMin, yMin), Math::Vector2d(xMax, yMax)));
 	return 0;
 }
 
 static SQInteger cameraFollow(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
-	g_engine->follow(actor);
+	g_twp->follow(actor);
 	Math::Vector2d pos = actor->_node->getPos();
-	Common::SharedPtr<Room> oldRoom = g_engine->_room;
+	Common::SharedPtr<Room> oldRoom = g_twp->_room;
 	if (actor->_room)
-		g_engine->setRoom(actor->_room);
+		g_twp->setRoom(actor->_room);
 	if (oldRoom != actor->_room)
-		g_engine->cameraAt(pos);
+		g_twp->cameraAt(pos);
 	return 0;
 }
 
@@ -167,13 +167,13 @@ static SQInteger cameraFollow(HSQUIRRELVM v) {
 static SQInteger cameraInRoom(HSQUIRRELVM v) {
 	Common::SharedPtr<Room> room = sqroom(v, 2);
 	if (room) {
-		g_engine->setRoom(room);
+		g_twp->setRoom(room);
 	} else {
 		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj || !obj->_room) {
 			return sq_throwerror(v, "failed to get room");
 		}
-		g_engine->setRoom(obj->_room);
+		g_twp->setRoom(obj->_room);
 	}
 	return 0;
 }
@@ -209,7 +209,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 			int im;
 			if (SQ_FAILED(sqget(v, 4, im)))
 				return sq_throwerror(v, "failed to get interpolation method");
-			pos = Math::Vector2d(x, g_engine->getGfx().cameraPos().getY());
+			pos = Math::Vector2d(x, g_twp->getGfx().cameraPos().getY());
 			interpolation = (InterpolationKind)im;
 		} else {
 			Common::SharedPtr<Object> obj = sqobj(v, 2);
@@ -237,16 +237,16 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 	} else {
 		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
-	Math::Vector2d halfScreen(g_engine->_room->getScreenSize() / 2.f);
+	Math::Vector2d halfScreen(g_twp->_room->getScreenSize() / 2.f);
 	debugC(kDebugGenScript, "cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
-	g_engine->follow(nullptr);
-	g_engine->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
+	g_twp->follow(nullptr);
+	g_twp->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
 	return 0;
 }
 
 // Returns the current camera position x, y.
 static SQInteger cameraPos(HSQUIRRELVM v) {
-	sqpush(v, g_engine->cameraPos());
+	sqpush(v, g_twp->cameraPos());
 	return 1;
 }
 
@@ -262,13 +262,13 @@ static SQInteger sqChr(HSQUIRRELVM v) {
 
 // Returns x coordinates of the mouse in screen coordinates.
 static SQInteger cursorPosX(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_cursor.pos.getX());
+	sqpush(v, g_twp->_cursor.pos.getX());
 	return 1;
 }
 
 // Returns y coordinates of the mouse in screen coordinates.
 static SQInteger cursorPosY(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_cursor.pos.getY());
+	sqpush(v, g_twp->_cursor.pos.getY());
 	return 1;
 }
 
@@ -301,13 +301,13 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		int verb;
 		if (SQ_FAILED(sqget(v, 2, verb)))
 			return sq_throwerror(v, "failed to get verb");
-		ActorSlot *actorSlot = g_engine->_hud.actorSlot(g_engine->_actor);
+		ActorSlot *actorSlot = g_twp->_hud.actorSlot(g_twp->_actor);
 		if (!actorSlot)
 			return 0;
 		for (int i = 1; i < MAX_VERBS; i++) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
-				SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
+				SpriteSheet *verbSheet = g_twp->_resManager.spriteSheet("VerbSheet");
 				const SpriteSheetFrame *verbFrame = &verbSheet->getFrame(Common::String::format("%s_en", vb.image.c_str()));
 				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
 				debugC(kDebugGenScript, "findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
@@ -321,11 +321,11 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
 	if (obj->inInventory()) {
-		sqpush(v, g_engine->_uiInv.getPos(obj));
+		sqpush(v, g_twp->_uiInv.getPos(obj));
 		return 1;
 	}
 
-	Math::Vector2d rPos = g_engine->roomToScreen(obj->_node->getAbsPos());
+	Math::Vector2d rPos = g_twp->roomToScreen(obj->_node->getAbsPos());
 	Math::Vector2d pos(rPos.getX() + obj->_node->getSize().getX() / 2.f, rPos.getY() + obj->_node->getSize().getY() / 2.f);
 	debugC(kDebugGenScript, "findScreenPosition(%s) => (%f,%f)", obj->_name.c_str(), pos.getX(), pos.getY());
 	sqpush(v, pos);
@@ -333,7 +333,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 }
 
 static SQInteger frameCounter(HSQUIRRELVM v) {
-	return sqpush(v, g_engine->_frameCounter);
+	return sqpush(v, g_twp->_frameCounter);
 }
 
 static SQInteger getUserPref(HSQUIRRELVM v) {
@@ -341,8 +341,8 @@ static SQInteger getUserPref(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, key))) {
 		return sq_throwerror(v, "failed to get key");
 	}
-	if (g_engine->getPrefs().hasPrefs(key)) {
-		sqpush(v, g_engine->getPrefs().prefsAsJson(key));
+	if (g_twp->getPrefs().hasPrefs(key)) {
+		sqpush(v, g_twp->getPrefs().prefsAsJson(key));
 		return 1;
 	}
 	int numArgs = sq_gettop(v);
@@ -359,8 +359,8 @@ static SQInteger getPrivatePref(HSQUIRRELVM v) {
 	Common::String key;
 	if (SQ_FAILED(sqget(v, 2, key))) {
 		return sq_throwerror(v, "failed to get key");
-	// } else if (g_engine->getPrefs().hasPrivPref(key)) {
-	// 	return sqpush(v, g_engine->getPrefs().privPrefAsJson(key));
+	// } else if (g_twp->getPrefs().hasPrivPref(key)) {
+	// 	return sqpush(v, g_twp->getPrefs().privPrefAsJson(key));
 	} else if (sq_gettop(v) == 3) {
 		HSQOBJECT obj;
 		sq_getstackobj(v, 3, &obj);
@@ -372,12 +372,12 @@ static SQInteger getPrivatePref(HSQUIRRELVM v) {
 }
 
 static SQInteger incutscene(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_cutscene != nullptr);
+	sqpush(v, g_twp->_cutscene != nullptr);
 	return 1;
 }
 
 static SQInteger indialog(HSQUIRRELVM v) {
-	sqpush(v, (int)g_engine->_dialog.getState());
+	sqpush(v, (int)g_twp->_dialog.getState());
 	return 1;
 }
 
@@ -454,7 +454,7 @@ static SQInteger sqrandom(HSQUIRRELVM v) {
 		sq_getfloat(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		SQFloat value = g_engine->getRandom(min, max);
+		SQFloat value = g_twp->getRandom(min, max);
 		sq_pushfloat(v, value);
 	} else {
 		SQInteger min, max;
@@ -462,7 +462,7 @@ static SQInteger sqrandom(HSQUIRRELVM v) {
 		sq_getinteger(v, 3, &max);
 		if (min > max)
 			SWAP(min, max);
-		SQInteger value = g_engine->getRandomSource().getRandomNumberRngSigned(min, max);
+		SQInteger value = g_twp->getRandomSource().getRandomNumberRngSigned(min, max);
 		sq_pushinteger(v, value);
 	}
 	return 1;
@@ -474,9 +474,9 @@ static SQInteger loadArray(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, orgFilename)))
 		return sq_throwerror(v, "failed to get filename");
 	debugC(kDebugGenScript, "loadArray: %s", orgFilename);
-	Common::String filename = g_engine->getPrefs().getKey(orgFilename);
+	Common::String filename = g_twp->getPrefs().getKey(orgFilename);
 	GGPackEntryReader entry;
-	entry.open(g_engine->_pack, g_engine->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
+	entry.open(g_twp->_pack, g_twp->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
 	sq_newarray(v, 0);
 	while (!entry.eos()) {
 		Common::String line = entry.readLine();
@@ -531,7 +531,7 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 3, choice)))
 			return sq_throwerror(v, "Failed to get choice");
 		// use pushSentence with VERB_DIALOG
-		g_engine->_dialog.choose(choice);
+		g_twp->_dialog.choose(choice);
 		return 0;
 	}
 
@@ -549,7 +549,7 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 	}
 	VerbId verb;
 	verb.id = id;
-	g_engine->execSentence(nullptr, verb, obj1, obj2);
+	g_twp->execSentence(nullptr, verb, obj1, obj2);
 	return 0;
 }
 
@@ -567,7 +567,7 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		HSQOBJECT obj;
 		sq_resetobject(&obj);
 		SQInteger size = sq_getsize(v, 2);
-		int index = g_engine->getRandomSource().getRandomNumber(size - 1);
+		int index = g_twp->getRandomSource().getRandomNumber(size - 1);
 		int i = 0;
 		sq_push(v, 2);  // array
 		sq_pushnull(v); // null iterator
@@ -585,7 +585,7 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		sq_pushobject(v, obj);
 	} else {
 		SQInteger size = sq_gettop(v);
-		int index = g_engine->getRandomSource().getRandomNumber(size - 3);
+		int index = g_twp->getRandomSource().getRandomNumber(size - 3);
 		sq_push(v, 2 + index);
 	}
 	return 1;
@@ -602,7 +602,7 @@ static SQInteger randomOdds(HSQUIRRELVM v) {
 	float value = 0.0f;
 	if (SQ_FAILED(sqget(v, 2, value)))
 		return sq_throwerror(v, "failed to get value");
-	float rnd = g_engine->getRandom();
+	float rnd = g_twp->getRandom();
 	bool res = rnd <= value;
 	sq_pushbool(v, res);
 	return 1;
@@ -615,19 +615,19 @@ static SQInteger randomseed(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	switch (nArgs) {
 	case 1: {
-		sqpush(v, (int)g_engine->getRandomSource().getSeed());
+		sqpush(v, (int)g_twp->getRandomSource().getSeed());
 		return 1;
 	}
 	case 2: {
 		int seed = 0;
 		if (sq_gettype(v, 2) == OT_NULL) {
-			g_engine->getRandomSource().setSeed(g_engine->getRandomSource().generateNewSeed());
+			g_twp->getRandomSource().setSeed(g_twp->getRandomSource().generateNewSeed());
 			return 0;
 		}
 		if (SQ_FAILED(sqget(v, 2, seed))) {
 			return sq_throwerror(v, "failed to get seed");
 		}
-		g_engine->getRandomSource().setSeed(seed);
+		g_twp->getRandomSource().setSeed(seed);
 		return 0;
 	}
 	}
@@ -649,7 +649,7 @@ static SQInteger refreshUI(HSQUIRRELVM v) {
 //     if (y > exitButtonB) { ... }
 // }
 static SQInteger screenSize(HSQUIRRELVM v) {
-	Math::Vector2d screen = g_engine->_room->getScreenSize();
+	Math::Vector2d screen = g_twp->_room->getScreenSize();
 	sqpush(v, screen);
 	return 1;
 }
@@ -703,7 +703,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 	debugC(kDebugGenScript, "setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
 	VerbId verbId;
 	verbId.id = id;
-	g_engine->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
+	g_twp->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
 	return 0;
 }
 
@@ -719,8 +719,8 @@ static SQInteger startDialog(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get node");
 		}
 	}
-	Common::String actor = g_engine->_actor ? g_engine->_actor->_key : "";
-	g_engine->_dialog.start(actor, dialog, node);
+	Common::String actor = g_twp->_actor ? g_twp->_actor->_key : "";
+	g_twp->_dialog.start(actor, dialog, node);
 	return 0;
 }
 
@@ -728,7 +728,7 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	switch (nArgs) {
 	case 1: {
-		for (auto layer : g_engine->_room->_layers) {
+		for (auto layer : g_twp->_room->_layers) {
 			for (auto obj : layer->_objects) {
 				obj->_exec.enabled = false;
 			}
@@ -899,7 +899,7 @@ static SQInteger translate(HSQUIRRELVM v) {
 	const SQChar *text;
 	if (SQ_FAILED(sqget(v, 2, text)))
 		return sq_throwerror(v, "Failed to get text");
-	Common::String newText = g_engine->getTextDb().getText(text);
+	Common::String newText = g_twp->getTextDb().getText(text);
 	debugC(kDebugGenScript, "translate(%s): %s", text, newText.c_str());
 	sqpush(v, newText);
 	return 1;
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 69cd6f325d4..a2f31df25dc 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -139,7 +139,7 @@ void Shader::init(const char *name, const char *vertex, const char *fragment) {
 	const char *attributes[] = {"a_position", "a_color", "a_texCoords", nullptr};
 	_shader.loadFromStrings(name, vertex, fragment, attributes, 110);
 
-	uint32 vbo = g_engine->getGfx()._vbo;
+	uint32 vbo = g_twp->getGfx()._vbo;
 	_shader.enableVertexAttribute("a_position", vbo, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)0);
 	_shader.enableVertexAttribute("a_color", vbo, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)(2 * sizeof(float)));
 	_shader.enableVertexAttribute("a_texCoords", vbo, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (uint32)(6 * sizeof(float)));
@@ -384,8 +384,8 @@ void Gfx::use(Shader *shader) {
 void Gfx::setRenderTarget(RenderTexture *target) {
 	if (!target) {
 		glBindFramebuffer(GL_FRAMEBUFFER, _oldFbo);
-		int w = g_engine->_system->getWidth();
-		int h = g_engine->_system->getHeight();
+		int w = g_twp->_system->getWidth();
+		int h = g_twp->_system->getHeight();
 		glViewport(0, 0, w, h);
 	} else {
 		glBindFramebuffer(GL_FRAMEBUFFER, target->fbo);
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 6ffad4c86be..d70331f3ef3 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -131,7 +131,7 @@ ActorSlot *Hud::actorSlot(Common::SharedPtr<Object> actor) {
 void Hud::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY(), 0.f);
 	trsf.translate(pos);
-	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+	g_twp->getGfx().drawSprite(sf.frame, *texture, color, trsf);
 }
 
 void Hud::drawCore(Math::Matrix4 trsf) {
@@ -140,26 +140,26 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 		return;
 
 	// draw HUD background
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
 	bool classic = ConfMan.getBool("hudSentence");
 	const SpriteSheetFrame &backingFrame = gameSheet->getFrame(classic ? "ui_backing_tall" : "ui_backing");
-	Texture *gameTexture = g_engine->_resManager.texture(gameSheet->meta.image);
+	Texture *gameTexture = g_twp->_resManager.texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
-	g_engine->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha*getAlpha()), trsf);
+	g_twp->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha*getAlpha()), trsf);
 
 	bool verbHlt = ConfMan.getBool("invertVerbHighlight");
 	Color verbHighlight = verbHlt ? Color() : slot->verbUiColors.verbHighlight;
 	Color verbColor = verbHlt ? slot->verbUiColors.verbHighlight : Color();
 
 	// draw actor's verbs
-	SpriteSheet *verbSheet = g_engine->_resManager.spriteSheet("VerbSheet");
-	Texture *verbTexture = g_engine->_resManager.texture(verbSheet->meta.image);
+	SpriteSheet *verbSheet = g_twp->_resManager.spriteSheet("VerbSheet");
+	Texture *verbTexture = g_twp->_resManager.texture(verbSheet->meta.image);
 	Common::String lang = ConfMan.get("language");
 	bool retroVerbs = ConfMan.getBool("retroVerbs");
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
 
-	Shader *saveShader = g_engine->getGfx().getShader();
-	g_engine->getGfx().use(&_shader);
+	Shader *saveShader = g_twp->getGfx().getShader();
+	g_twp->getGfx().use(&_shader);
 	_shader._shadowColor = slot->verbUiColors.verbNormalTint;
 	_shader._normalColor = slot->verbUiColors.verbHighlight;
 	_shader._highlightColor = slot->verbUiColors.verbHighlightTint;
@@ -179,7 +179,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 			drawSprite(verbFrame, verbTexture, Color::withAlpha(color, getAlpha()), trsf);
 		}
 	}
-	g_engine->getGfx().use(saveShader);
+	g_twp->getGfx().use(saveShader);
 	_over = isOver;
 }
 
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 3529edb5e5f..a1e79faac7e 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -174,7 +174,7 @@ void Lighting::update(const Lights &lights) {
 	if (_currentDebug != _debug) {
 		init("lighting", vshader, _debug ? debug_fshader : fshader);
 		_currentDebug = _debug;
-		g_engine->_lightingNode.setVisible(_debug);
+		g_twp->_lightingNode.setVisible(_debug);
 	}
 	_ambientLight = lights._ambientLight;
 	u_numberLights = 0;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 4ad5b6efa94..bd5b86f4de6 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -129,7 +129,7 @@ void Shake::update(float elapsed) {
 OverlayTo::OverlayTo(float duration, Common::SharedPtr<Room> room, Color to)
 	: _room(room),
 	  _to(to),
-	  _tween(g_engine->_room->getOverlay(), to, duration, InterpolationMethod()) {
+	  _tween(g_twp->_room->getOverlay(), to, duration, InterpolationMethod()) {
 }
 
 OverlayTo::~OverlayTo() = default;
@@ -337,11 +337,11 @@ void Talking::update(float elapsed) {
 
 	_elapsed += elapsed;
 	if (_obj->_sound) {
-		if (!g_engine->_audio.playing(_obj->_sound)) {
+		if (!g_twp->_audio.playing(_obj->_sound)) {
 			debugC(kDebugGame, "talking %s audio stopped", _obj->_key.c_str());
 			_obj->_sound = 0;
 		} else {
-			float e = static_cast<float>(g_engine->_audio.getElapsed(_obj->_sound)) / 1000.f;
+			float e = static_cast<float>(g_twp->_audio.getElapsed(_obj->_sound)) / 1000.f;
 			char letter = _lip.letter(e);
 			_obj->setHeadIndex(letterToIndex(letter));
 		}
@@ -368,14 +368,14 @@ int Talking::loadActorSpeech(const Common::String &name) {
 	Common::String filename(name);
 	filename.toUppercase();
 	filename += ".ogg";
-	if (g_engine->_pack.assetExists(filename.c_str())) {
+	if (g_twp->_pack.assetExists(filename.c_str())) {
 		Common::SharedPtr<SoundDefinition> soundDefinition(new SoundDefinition(filename));
 		if (!soundDefinition) {
 			debugC(kDebugGame, "File %s.ogg not found", name.c_str());
 		} else {
-			g_engine->_audio._soundDefs.push_back(soundDefinition);
-			int id = g_engine->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
-			int duration = g_engine->_audio.getDuration(id);
+			g_twp->_audio._soundDefs.push_back(soundDefinition);
+			int id = g_twp->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
+			int duration = g_twp->_audio.getDuration(id);
 			debugC(kDebugGame, "talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
 			if (duration)
 				_duration = static_cast<float>(duration) / 1000.f;
@@ -392,7 +392,7 @@ void Talking::say(const Common::String &text) {
 	Common::String txt(text);
 	if (text[0] == '@') {
 		int id = atoi(text.c_str() + 1);
-		txt = g_engine->_textDb.getText(id);
+		txt = g_twp->_textDb.getText(id);
 
 		id = onTalkieId(id);
 		Common::String key = talkieKey();
@@ -401,15 +401,15 @@ void Talking::say(const Common::String &text) {
 		Common::String path = name + ".lip";
 
 		debugC(kDebugGame, "Load lip %s", path.c_str());
-		if (g_engine->_pack.assetExists(path.c_str())) {
+		if (g_twp->_pack.assetExists(path.c_str())) {
 			GGPackEntryReader entry;
-			entry.open(g_engine->_pack, path);
+			entry.open(g_twp->_pack, path);
 			_lip.load(&entry);
 			debugC(kDebugGame, "Lip %s loaded", path.c_str());
 		}
 
 		if (_obj->_sound) {
-			g_engine->_audio.stop(_obj->_sound);
+			g_twp->_audio.stop(_obj->_sound);
 		}
 
 		_obj->_sound = loadActorSpeech(name);
@@ -456,7 +456,7 @@ void Talking::say(const Common::String &text) {
 	_obj->_sayNode->setText(text2);
 	_obj->_sayNode->setColor(_color);
 	_node = _obj->_sayNode;
-	Math::Vector2d pos = g_engine->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
+	Math::Vector2d pos = g_twp->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
 
 	// clamp position to keep it on screen
 	pos.setX(Twp::clamp(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
@@ -464,7 +464,7 @@ void Talking::say(const Common::String &text) {
 
 	_obj->_sayNode->setPos(pos);
 	_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
-	g_engine->_screenScene.addChild(_obj->_sayNode.get());
+	g_twp->_screenScene.addChild(_obj->_sayNode.get());
 	_elapsed = 0.f;
 }
 
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 313d9ce794b..3d4cf72a228 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -37,7 +37,7 @@ public:
 	Blink(Common::SharedPtr<Object> obj, float min, float max) : _obj(obj), _min(min), _max(max) {
 		_obj->showLayer("blink", false);
 		_state = BlinkState::Closed;
-		_duration = g_engine->getRandom(min, max);
+		_duration = g_twp->getRandom(min, max);
 	}
 
 	virtual void update(float elapsed) override {
@@ -54,7 +54,7 @@ public:
 			_elapsed += elapsed;
 			if (_elapsed > 0.25) {
 				_obj->showLayer("blink", false);
-				_duration = g_engine->getRandom(_min, _max);
+				_duration = g_twp->getRandom(_min, _max);
 				_elapsed = 0;
 				_state = BlinkState::Closed;
 			}
@@ -218,12 +218,12 @@ void Object::trig(const Common::String &name) {
 		}
 	} else {
 		int id = 0;
-		sqgetf(sqrootTbl(g_engine->getVm()), name.substr(1), id);
+		sqgetf(sqrootTbl(g_twp->getVm()), name.substr(1), id);
 		Common::SharedPtr<SoundDefinition> sound = sqsounddef(id);
 		if (!sound)
 			warning("Cannot trig sound '%s', sound not found (id=%d, %s)", name.c_str(), id, _key.c_str());
 		else
-			g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+			g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	}
 }
 
@@ -293,7 +293,7 @@ void Object::setTouchable(bool value) {
 }
 
 void Object::setIcon(int fps, const Common::StringArray &icons) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sq_newarray(v, 0);
 	sqpush(v, fps);
 	for (size_t i = 0; i < icons.size(); i++) {
@@ -402,10 +402,10 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 		object->_room = room;
 
 		if(roomChanged && isActor(object->getId())) {
-			if(room == g_engine->_room) {
-				g_engine->actorEnter(object);
-			} else if(oldRoom == g_engine->_room) {
-				g_engine->actorExit(object);
+			if(room == g_twp->_room) {
+				g_twp->actorEnter(object);
+			} else if(oldRoom == g_twp->_room) {
+				g_twp->actorExit(object);
 			}
 		}
 	}
@@ -519,7 +519,7 @@ void Object::blinkRate(Common::SharedPtr<Object> obj, float min, float max) {
 
 void Object::setCostume(const Common::String &name, const Common::String &sheet) {
 	GGPackEntryReader entry;
-	entry.open(g_engine->_pack, name + ".json");
+	entry.open(g_twp->_pack, name + ".json");
 
 	GGHashMapDecoder dec;
 	Common::ScopedPtr<Common::JSONValue> json(dec.open(&entry));
@@ -693,7 +693,7 @@ static bool verbNotClose(VerbId id) {
 
 static void cantReach(Common::SharedPtr<Object> self, Common::SharedPtr<Object> noun2) {
 	if (sqrawexists(self->_table, "verbCantReach")) {
-		int nParams = sqparamCount(g_engine->getVm(), self->_table, "verbCantReach");
+		int nParams = sqparamCount(g_twp->getVm(), self->_table, "verbCantReach");
 		debugC(kDebugGame, "verbCantReach found in obj '%s' with %d params", self->_key.c_str(), nParams);
 		if (nParams == 1) {
 			sqcall(self->_table, "verbCantReach");
@@ -709,7 +709,7 @@ static void cantReach(Common::SharedPtr<Object> self, Common::SharedPtr<Object>
 	} else {
 		HSQOBJECT nilTbl;
 		sq_resetobject(&nilTbl);
-		sqcall(g_engine->_defaultObj, "verbCantReach", self->_table, nilTbl);
+		sqcall(g_twp->_defaultObj, "verbCantReach", self->_table, nilTbl);
 	}
 }
 
@@ -757,7 +757,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 
 		debugC(kDebugGame, "actorArrived: callVerb");
 		obj->_exec.enabled = false;
-		g_engine->callVerb(obj, verb, noun1, noun2);
+		g_twp->callVerb(obj, verb, noun1, noun2);
 	}
 }
 
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index ceaaa4a11c0..b54f7377528 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -68,8 +68,8 @@ static SQInteger createObject(HSQUIRRELVM v) {
 	}
 
 	debugC(kDebugObjScript, "Create object: %s, %u", sheet.c_str(), frames.size());
-	Common::SharedPtr<Object> obj = g_engine->_room->createObject(sheet, frames);
-	obj->_room = g_engine->_room;
+	Common::SharedPtr<Object> obj = g_twp->_room->createObject(sheet, frames);
+	obj->_room = g_twp->_room;
 	sq_pushobject(v, obj->_table);
 
 	return 1;
@@ -123,7 +123,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 		}
 	}
 	debugC(kDebugObjScript, "Create text %d, %d, max=%f, text=%s", thAlign, tvAlign, maxWidth, text);
-	Common::SharedPtr<Object> obj = g_engine->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
+	Common::SharedPtr<Object> obj = g_twp->_room->createTextObject(fontName, text, thAlign, tvAlign, maxWidth);
 	sqpush(v, obj->_table);
 	return 1;
 }
@@ -169,7 +169,7 @@ static SQInteger findObjectAt(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 3, y)))
 		return sq_throwerror(v, "failed to get y");
-	Common::SharedPtr<Object> obj = g_engine->objAt(Math::Vector2d(x, y));
+	Common::SharedPtr<Object> obj = g_twp->objAt(Math::Vector2d(x, y));
 	if (!obj)
 		sq_pushnull(v);
 	else
@@ -181,7 +181,7 @@ static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	if (!obj->_owner || (obj->_owner != g_engine->_actor)) {
+	if (!obj->_owner || (obj->_owner != g_twp->_actor)) {
 		debugC(kDebugObjScript, "Is '%s(%s)' in inventory: no", obj->_name.c_str(), obj->_key.c_str());
 		sqpush(v, false);
 		return 1;
@@ -591,7 +591,7 @@ static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
 	int layer = 0;
 	if (SQ_FAILED(sqget(v, 3, layer)))
 		return sq_throwerror(v, "failed to get parallax layer");
-	g_engine->_room->objectParallaxLayer(obj, layer);
+	g_twp->_room->objectParallaxLayer(obj, layer);
 	return 0;
 }
 
@@ -733,7 +733,7 @@ static SQInteger objectScreenSpace(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	g_engine->_screenScene.addChild(obj->_node.get());
+	g_twp->_screenScene.addChild(obj->_node.get());
 	return 0;
 }
 
@@ -887,8 +887,8 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, verb)))
 		return sq_throwerror(v, "failed to get verb");
 
-	if (g_engine->_actor) {
-		ActorSlot *slot = g_engine->_hud.actorSlot(g_engine->_actor);
+	if (g_twp->_actor) {
+		ActorSlot *slot = g_twp->_hud.actorSlot(g_twp->_actor);
 		for (int i = 0; i < MAX_VERBS; i++) {
 			Verb *vb = &slot->verbs[i];
 			if (vb->id.id == verb) {
@@ -915,7 +915,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 		sq_getstackobj(v, 2, &o);
 		Common::String name;
 		sqgetf(o, "name", name);
-		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_engine->_textDb.getText(name).c_str()).c_str());
+		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_twp->_textDb.getText(name).c_str()).c_str());
 	}
 	Common::SharedPtr<Object> actor;
 	if (sq_gettop(v) >= 3) {
@@ -924,7 +924,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get actor");
 	}
 	if (!actor)
-		actor = g_engine->_actor;
+		actor = g_twp->_actor;
 	Object::pickupObject(actor, obj);
 	return 0;
 }
@@ -1009,12 +1009,12 @@ static SQInteger removeInventory(HSQUIRRELVM v) {
 // it will call the default object's verbOpen as a fallback, allowing for common failure phrase like "I can't open that.".
 // The default object can be changed at anytime, so different selectable characters can have different default responses.
 static SQInteger setDefaultObject(HSQUIRRELVM v) {
-	HSQUIRRELVM vm = g_engine->getVm();
-	if (g_engine->_defaultObj._type != OT_NULL)
-		sq_release(vm, &g_engine->_defaultObj);
-	if (SQ_FAILED(sq_getstackobj(v, 2, &g_engine->_defaultObj)))
+	HSQUIRRELVM vm = g_twp->getVm();
+	if (g_twp->_defaultObj._type != OT_NULL)
+		sq_release(vm, &g_twp->_defaultObj);
+	if (SQ_FAILED(sq_getstackobj(v, 2, &g_twp->_defaultObj)))
 		return sq_throwerror(v, "failed to get default object");
-	sq_addref(vm, &g_engine->_defaultObj);
+	sq_addref(vm, &g_twp->_defaultObj);
 	return 0;
 }
 
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 339c72a8b43..96efb3eefba 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -43,7 +43,7 @@ static Common::String getKey(const char *path) {
 void ResManager::loadTexture(const Common::String &name) {
 	debugC(kDebugRes, "Load texture %s", name.c_str());
 	GGPackEntryReader r;
-	if(!r.open(g_engine->_pack, name)) {
+	if(!r.open(g_twp->_pack, name)) {
 		error("Texture %s not found", name.c_str());
 	}
 	Image::PNGDecoder d;
@@ -67,7 +67,7 @@ Texture *ResManager::texture(const Common::String &name) {
 
 void ResManager::loadSpriteSheet(const Common::String &name) {
 	GGPackEntryReader r;
-	r.open(g_engine->_pack, name + ".json");
+	r.open(g_twp->_pack, name + ".json");
 
 	// read all contents
 	Common::Array<char> data(r.size());
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index ed8ef871c36..3157d665d84 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -148,7 +148,7 @@ Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const
 	Common::SharedPtr<Object> obj(new Object());
 	obj->_temporary = true;
 
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 
 	// create a table for this object
 	sq_newtable(v);
@@ -187,7 +187,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 	Common::SharedPtr<Object> obj(new Object());
 	obj->_temporary = true;
 
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 
 	// create a table for this object
 	sq_newtable(v);
@@ -357,7 +357,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	int width = 0;
 	for (size_t i = 0; i < backNames.size(); i++) {
 		Common::String name = backNames[i];
-		width += g_engine->_resManager.spriteSheet(room->_sheet)->getFrame(name).sourceSize.getX();
+		width += g_twp->_resManager.spriteSheet(room->_sheet)->getFrame(name).sourceSize.getX();
 	}
 	room->_roomSize.setX(width);
 }
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index a9cb4ac4ca4..7e048d6cc43 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -34,12 +34,12 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 	sq_resetobject(&obj->_leave);
 	if (SQ_FAILED(sqget(v, 3, obj->_enter)))
 		return sq_throwerror(v, "failed to get enter");
-	sq_addref(g_engine->getVm(), &obj->_enter);
+	sq_addref(g_twp->getVm(), &obj->_enter);
 	if (nArgs == 4)
 		if (SQ_FAILED(sqget(v, 4, obj->_leave)))
 			return sq_throwerror(v, "failed to get leave");
-	sq_addref(g_engine->getVm(), &obj->_leave);
-	g_engine->_room->_triggers.push_back(obj);
+	sq_addref(g_twp->getVm(), &obj->_leave);
+	g_twp->_room->_triggers.push_back(obj);
 	return 0;
 }
 
@@ -73,7 +73,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 	} else {
 		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
 	}
-	const Common::Array<Walkbox> &walkboxes = g_engine->_room->_walkboxes;
+	const Common::Array<Walkbox> &walkboxes = g_twp->_room->_walkboxes;
 	for (size_t i = 0; i < walkboxes.size(); i++) {
 		const Walkbox &walkbox = walkboxes[i];
 		if (walkbox.contains(pos1)) {
@@ -96,7 +96,7 @@ static SQInteger createLight(HSQUIRRELVM v) {
 	int y;
 	if (SQ_FAILED(sqget(v, 4, y)))
 		return sq_throwerror(v, "failed to get y");
-	Light *light = g_engine->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
+	Light *light = g_twp->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
 	debugC(kDebugRoomScript, "createLight(%d) -> %d", color, light->id);
 	sqpush(v, light->id);
 	return 1;
@@ -110,11 +110,11 @@ static SQInteger enableTrigger(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 3, enabled)))
 		return sq_throwerror(v, "failed to get enabled");
 	if (enabled) {
-		g_engine->_room->_triggers.push_back(obj);
+		g_twp->_room->_triggers.push_back(obj);
 	} else {
-		int index = find(g_engine->_room->_triggers, obj);
+		int index = find(g_twp->_room->_triggers, obj);
 		if (index != -1)
-			g_engine->_room->_triggers.remove_at(index);
+			g_twp->_room->_triggers.remove_at(index);
 	}
 	return 0;
 }
@@ -123,7 +123,7 @@ static SQInteger enterRoomFromDoor(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	g_engine->enterRoom(obj->_room, obj);
+	g_twp->enterRoom(obj->_room, obj);
 	return 0;
 }
 
@@ -231,9 +231,9 @@ static SQInteger defineRoom(HSQUIRRELVM v) {
 	sqgetf(v, table, "name", name);
 	if (name.size() == 0)
 		sqgetf(v, table, "background", name);
-	Common::SharedPtr<Room> room = g_engine->defineRoom(name, table);
+	Common::SharedPtr<Room> room = g_twp->defineRoom(name, table);
 	debugC(kDebugRoomScript, "Define room: %s", name.c_str());
-	g_engine->_rooms.push_back(room);
+	g_twp->_rooms.push_back(room);
 	sqpush(v, room->_table);
 	return 1;
 }
@@ -259,9 +259,9 @@ static SQInteger definePseudoRoom(HSQUIRRELVM v) {
 	if (SQ_FAILED(sq_getstackobj(v, -1, &table)))
 		return sq_throwerror(v, "failed to get room table");
 
-	Common::SharedPtr<Room> room(g_engine->defineRoom(name, table, true));
+	Common::SharedPtr<Room> room(g_twp->defineRoom(name, table, true));
 	debugC(kDebugRoomScript, "Define pseudo room: %s", name);
-	g_engine->_rooms.push_back(room);
+	g_twp->_rooms.push_back(room);
 	sqpush(v, room->_table);
 	return 1;
 }
@@ -275,8 +275,8 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 	Common::String name;
 	if (SQ_FAILED(sqget(v, 2, name)))
 		return sq_throwerror(v, "failed to get name");
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> room = g_twp->_rooms[i];
 		if (room->_name == name) {
 			sqpush(v, room->_table);
 			return 1;
@@ -299,8 +299,8 @@ static SQInteger findRoom(HSQUIRRELVM v) {
 // }
 static SQInteger masterRoomArray(HSQUIRRELVM v) {
 	sq_newarray(v, 0);
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> room = g_twp->_rooms[i];
 		sq_pushobject(v, room->_table);
 		sq_arrayappend(v, -2);
 	}
@@ -308,17 +308,17 @@ static SQInteger masterRoomArray(HSQUIRRELVM v) {
 }
 
 static SQInteger removeTrigger(HSQUIRRELVM v) {
-	if (!g_engine->_room)
+	if (!g_twp->_room)
 		return 0;
 	if (sq_gettype(v, 2) == OT_CLOSURE) {
 		HSQOBJECT closure;
 		sq_resetobject(&closure);
 		if (SQ_FAILED(sqget(v, 3, closure)))
 			return sq_throwerror(v, "failed to get closure");
-		for (size_t i = 0; i < g_engine->_room->_triggers.size(); i++) {
-			Common::SharedPtr<Object> trigger = g_engine->_room->_triggers[i];
+		for (size_t i = 0; i < g_twp->_room->_triggers.size(); i++) {
+			Common::SharedPtr<Object> trigger = g_twp->_room->_triggers[i];
 			if ((trigger->_enter._unVal.pClosure == closure._unVal.pClosure) || (trigger->_leave._unVal.pClosure == closure._unVal.pClosure)) {
-				g_engine->_room->_triggers.remove_at(i);
+				g_twp->_room->_triggers.remove_at(i);
 				return 0;
 			}
 		}
@@ -326,10 +326,10 @@ static SQInteger removeTrigger(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> obj = sqobj(v, 2);
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
-		size_t i = find(g_engine->_room->_triggers, obj);
+		size_t i = find(g_twp->_room->_triggers, obj);
 		if (i != (size_t)-1) {
 			debugC(kDebugRoomScript, "Remove room trigger: %s(%s)", obj->_name.c_str(), obj->_key.c_str());
-			g_engine->_room->_triggers.remove_at(find(g_engine->_room->_triggers, obj));
+			g_twp->_room->_triggers.remove_at(find(g_twp->_room->_triggers, obj));
 		}
 		return 0;
 	}
@@ -350,8 +350,8 @@ static SQInteger roomActors(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get room");
 
 	sq_newarray(v, 0);
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> actor = g_twp->_actors[i];
 		if (actor->_room == room) {
 			sqpush(v, actor->_table);
 			sq_arrayappend(v, -2);
@@ -368,26 +368,26 @@ static SQInteger roomEffect(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (roomEffect == RoomEffect::Ghost) {
 		if (nArgs == 14) {
-			sqget(v, 3, g_engine->_shaderParams.iFade);
-			sqget(v, 4, g_engine->_shaderParams.wobbleIntensity);
-			sqget(v, 6, g_engine->_shaderParams.shadows.rgba.r);
-			sqget(v, 7, g_engine->_shaderParams.shadows.rgba.g);
-			sqget(v, 8, g_engine->_shaderParams.shadows.rgba.b);
-			sqget(v, 9, g_engine->_shaderParams.midtones.rgba.r);
-			sqget(v, 10, g_engine->_shaderParams.midtones.rgba.g);
-			sqget(v, 11, g_engine->_shaderParams.midtones.rgba.b);
-			sqget(v, 12, g_engine->_shaderParams.highlights.rgba.r);
-			sqget(v, 13, g_engine->_shaderParams.highlights.rgba.g);
-			sqget(v, 14, g_engine->_shaderParams.highlights.rgba.b);
+			sqget(v, 3, g_twp->_shaderParams.iFade);
+			sqget(v, 4, g_twp->_shaderParams.wobbleIntensity);
+			sqget(v, 6, g_twp->_shaderParams.shadows.rgba.r);
+			sqget(v, 7, g_twp->_shaderParams.shadows.rgba.g);
+			sqget(v, 8, g_twp->_shaderParams.shadows.rgba.b);
+			sqget(v, 9, g_twp->_shaderParams.midtones.rgba.r);
+			sqget(v, 10, g_twp->_shaderParams.midtones.rgba.g);
+			sqget(v, 11, g_twp->_shaderParams.midtones.rgba.b);
+			sqget(v, 12, g_twp->_shaderParams.highlights.rgba.r);
+			sqget(v, 13, g_twp->_shaderParams.highlights.rgba.g);
+			sqget(v, 14, g_twp->_shaderParams.highlights.rgba.b);
 		} else {
-			g_engine->_shaderParams.iFade = 1.f;
-			g_engine->_shaderParams.wobbleIntensity = 1.f;
-			g_engine->_shaderParams.shadows = Color(-0.3f, 0.f, 0.f);
-			g_engine->_shaderParams.midtones = Color(-0.2f, 0.f, 0.1f);
-			g_engine->_shaderParams.highlights = Color(0.f, 0.f, 0.2f);
+			g_twp->_shaderParams.iFade = 1.f;
+			g_twp->_shaderParams.wobbleIntensity = 1.f;
+			g_twp->_shaderParams.shadows = Color(-0.3f, 0.f, 0.f);
+			g_twp->_shaderParams.midtones = Color(-0.2f, 0.f, 0.1f);
+			g_twp->_shaderParams.highlights = Color(0.f, 0.f, 0.2f);
 		}
 	}
-	g_engine->_room->_effect = (RoomEffect)effect;
+	g_twp->_room->_effect = (RoomEffect)effect;
 	return 0;
 }
 
@@ -427,7 +427,7 @@ static SQInteger roomFade(HSQUIRRELVM v) {
 	default:
 		break;
 	}
-	g_engine->fadeTo(effect, t, sepia);
+	g_twp->fadeTo(effect, t, sepia);
 	return 0;
 }
 
@@ -472,7 +472,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	if (SQ_FAILED(sqget(v, 2, startColor)))
 		return sq_throwerror(v, "failed to get startColor");
-	Common::SharedPtr<Room> room = g_engine->_room;
+	Common::SharedPtr<Room> room = g_twp->_room;
 	if (room->_overlayTo)
 		room->_overlayTo->disable();
 	room->setOverlay(Color::fromRgba(startColor));
@@ -484,7 +484,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
 		debugC(kDebugRoomScript, "start overlay from {rgba(startColor)} to {rgba(endColor)} in {duration}s");
-		g_engine->_room->_overlayTo = Common::SharedPtr<OverlayTo>(new OverlayTo(duration, room, Color::fromRgba(endColor)));
+		g_twp->_room->_overlayTo = Common::SharedPtr<OverlayTo>(new OverlayTo(duration, room, Color::fromRgba(endColor)));
 	}
 	return 0;
 }
@@ -493,7 +493,7 @@ static SQInteger roomRotateTo(HSQUIRRELVM v) {
 	float rotation;
 	if (SQ_FAILED(sqget(v, 2, rotation)))
 		return sq_throwerror(v, "failed to get rotation");
-	g_engine->_room->_rotateTo = Common::SharedPtr<RoomRotateTo>(new RoomRotateTo(g_engine->_room, rotation));
+	g_twp->_room->_rotateTo = Common::SharedPtr<RoomRotateTo>(new RoomRotateTo(g_twp->_room, rotation));
 	return 0;
 }
 
@@ -509,7 +509,7 @@ static SQInteger setAmbientLight(HSQUIRRELVM v) {
 	int c = 0;
 	if (SQ_FAILED(sqget(v, 2, c)))
 		return sq_throwerror(v, "failed to get color");
-	g_engine->_room->_lights._ambientLight = Color::rgb(c);
+	g_twp->_room->_lights._ambientLight = Color::rgb(c);
 	return 0;
 }
 
@@ -523,7 +523,7 @@ static SQInteger walkboxHidden(HSQUIRRELVM v) {
 	int hidden = 0;
 	if (SQ_FAILED(sqget(v, 3, hidden)))
 		return sq_throwerror(v, "failed to get object or hidden");
-	g_engine->_room->walkboxHidden(walkbox, hidden != 0);
+	g_twp->_room->walkboxHidden(walkbox, hidden != 0);
 	return 0;
 }
 
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 75401f646d2..32c266b72be 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -31,8 +31,8 @@ namespace Twp {
 const static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
 
 static Common::SharedPtr<Object> actor(const Common::String &key) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> a = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> a = g_twp->_actors[i];
 		if (a->_key == key)
 			return a;
 	}
@@ -40,8 +40,8 @@ static Common::SharedPtr<Object> actor(const Common::String &key) {
 }
 
 static Common::SharedPtr<Object> actor(int id) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> a = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> a = g_twp->_actors[i];
 		if (a->getId() == id)
 			return a;
 	}
@@ -74,7 +74,7 @@ static DialogConditionState parseState(Common::String &dialog) {
 		i++;
 	}
 
-	while (!g_engine->_pack.assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
+	while (!g_twp->_pack.assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
 		dialogName += dialog[i];
 		i++;
 	}
@@ -94,8 +94,8 @@ static DialogConditionState parseState(Common::String &dialog) {
 }
 
 static Common::SharedPtr<Room> room(const Common::String &name) {
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> r(g_twp->_rooms[i]);
 		if (r->_name == name) {
 			return r;
 		}
@@ -116,8 +116,8 @@ static Common::SharedPtr<Object> object(Common::SharedPtr<Room> room, const Comm
 }
 
 static Common::SharedPtr<Object> object(const Common::String &key) {
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> r(g_twp->_rooms[i]);
 		for (size_t j = 0; j < r->_layers.size(); j++) {
 			Common::SharedPtr<Layer> layer(r->_layers[j]);
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
@@ -131,7 +131,7 @@ static Common::SharedPtr<Object> object(const Common::String &key) {
 }
 
 static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sq_resetobject(&obj);
 	if (json->isString()) {
@@ -363,10 +363,10 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 
 static void setActor(const Common::String &key) {
 	if(key.empty()) return;
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> a = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> a = g_twp->_actors[i];
 		if (a->_key == key) {
-			g_engine->setActor(a, false);
+			g_twp->setActor(a, false);
 			return;
 		}
 	}
@@ -382,7 +382,7 @@ static int32_t computeHash(byte *data, size_t n) {
 
 bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	// dump savegame as json
-	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("load.json", false);
+	// Common::OutSaveFile *saveFile = g_twp->getSaveFileManager()->openForSaving("load.json", false);
 	// Common::String s = savegame.jSavegame->stringify(true);
 	// saveFile->write(s.c_str(), s.size());
 	// saveFile->finalize();
@@ -404,16 +404,16 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	loadActors(json["actors"]->asObject());
 	loadInventory(json["inventory"]);
 	loadRooms(json["rooms"]->asObject());
-	g_engine->_time = json["gameTime"]->asNumber();
-	g_engine->setTotalPlayTime(savegame.gameTime * 1000);
-	g_engine->_inputState.setState((InputStateFlag)json["inputState"]->asIntegerNumber());
+	g_twp->_time = json["gameTime"]->asNumber();
+	g_twp->setTotalPlayTime(savegame.gameTime * 1000);
+	g_twp->_inputState.setState((InputStateFlag)json["inputState"]->asIntegerNumber());
 	loadObjects(json["objects"]->asObject());
-	g_engine->setRoom(room(json["currentRoom"]->asString()));
+	g_twp->setRoom(room(json["currentRoom"]->asString()));
 	setActor(json["selectedActor"]->asString());
-	if(g_engine->_actor)
-		g_engine->cameraAt(g_engine->_actor->_node->getPos());
+	if(g_twp->_actor)
+		g_twp->cameraAt(g_twp->_actor->_node->getPos());
 
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sqsetf(sqrootTbl(v), "SAVEBUILD", json["savebuild"]->asIntegerNumber());
 
 	sqcall("postLoad");
@@ -453,20 +453,20 @@ void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
 		mode |= asOn;
 	if (json["actorsTempUnselectable"]->asIntegerNumber())
 		mode |= asTemporaryUnselectable;
-	g_engine->_actorSwitcher._mode = mode;
+	g_twp->_actorSwitcher._mode = mode;
 	// TODO: tmpPrefs().forceTalkieText = json["forceTalkieText"].getInt() != 0;
 	const Common::JSONArray &jSelectableActors = json["selectableActors"]->asArray();
 	for (size_t i = 0; i < jSelectableActors.size(); i++) {
 		const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
 		Common::SharedPtr<Object> act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
-		g_engine->_hud._actorSlots[i].actor = act;
-		g_engine->_hud._actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
+		g_twp->_hud._actorSlots[i].actor = act;
+		g_twp->_hud._actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
 	}
 }
 
 void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 	debugC(kDebugGame, "loadDialog");
-	g_engine->_dialog._states.clear();
+	g_twp->_dialog._states.clear();
 	for (auto it = json.begin(); it != json.end(); it++) {
 		Common::String dialog(it->_key);
 		// dialog format: mode dialog number actor
@@ -478,7 +478,7 @@ void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 		// $: showonceever
 		// ^: temponce
 		DialogConditionState state = parseState(dialog);
-		g_engine->_dialog._states.push_back(state);
+		g_twp->_dialog._states.push_back(state);
 		// TODO: what to do with this dialog value ?
 		// let value = property.second.getInt()
 	}
@@ -497,7 +497,7 @@ private:
 
 void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 	debugC(kDebugGame, "loadCallbacks");
-	g_engine->_callbacks.clear();
+	g_twp->_callbacks.clear();
 	if (!json["callbacks"]->isNull()) {
 		const Common::JSONArray &jCallbacks = json["callbacks"]->asArray();
 		for (size_t i = 0; i < jCallbacks.size(); i++) {
@@ -511,7 +511,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 				toSquirrel(jCallBackHash["param"], arg);
 				sqgetitems(arg, GetHObjects(args));
 			}
-			g_engine->_callbacks.push_back(Common::SharedPtr<Callback>(new Callback(id, time, name, args)));
+			g_twp->_callbacks.push_back(Common::SharedPtr<Callback>(new Callback(id, time, name, args)));
 		}
 	}
 	setCallbackId(json["nextGuid"]->asIntegerNumber());
@@ -519,7 +519,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 
 void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
 	debugC(kDebugGame, "loadGlobals");
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	HSQOBJECT g;
 	sqgetf("g", g);
 	for (auto it = json.begin(); it != json.end(); it++) {
@@ -531,8 +531,8 @@ void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
 }
 
 void SaveGameManager::loadActors(const Common::JSONObject &json) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> a = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> a = g_twp->_actors[i];
 		if (a->_key.size() > 0) {
 			loadActor(a, json[a->_key]->asObject());
 		}
@@ -544,7 +544,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 		const Common::JSONObject &jInventory = json->asObject();
 		const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
 		for (int i = 0; i < NUMACTORS; i++) {
-			Common::SharedPtr<Object> a(g_engine->_hud._actorSlots[i].actor);
+			Common::SharedPtr<Object> a(g_twp->_hud._actorSlots[i].actor);
 			if (a) {
 				a->_inventory.clear();
 				const Common::JSONObject &jSlot = jSlots[i]->asObject();
@@ -657,7 +657,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 			}
 		}
 
-		HSQUIRRELVM v = g_engine->getVm();
+		HSQUIRRELVM v = g_twp->getVm();
 		HSQOBJECT rootTbl = sqrootTbl(v);
 
 		JsonCallback params;
@@ -720,8 +720,8 @@ static Common::JSONValue *createJActor(Common::SharedPtr<Object> actor) {
 
 static Common::JSONValue *createJActors() {
 	Common::JSONObject jActors;
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> a(g_engine->_actors[i]);
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> a(g_twp->_actors[i]);
 		if (a->_key.size() > 0) {
 			jActors[a->_key] = createJActor(a);
 		}
@@ -747,8 +747,8 @@ static Common::JSONValue *createJCallback(const Callback &callback) {
 
 static Common::JSONValue *createJCallbackArray() {
 	Common::JSONArray result;
-	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
-		result.push_back(createJCallback(*g_engine->_callbacks[i]));
+	for (size_t i = 0; i < g_twp->_callbacks.size(); i++) {
+		result.push_back(createJCallback(*g_twp->_callbacks[i]));
 	}
 	return new Common::JSONValue(result);
 }
@@ -787,8 +787,8 @@ Common::String createJDlgStateKey(const DialogConditionState &state) {
 
 static Common::JSONValue *createJDialog() {
 	Common::JSONObject json;
-	for (size_t i = 0; i < g_engine->_dialog._states.size(); i++) {
-		const DialogConditionState &state = g_engine->_dialog._states[i];
+	for (size_t i = 0; i < g_twp->_dialog._states.size(); i++) {
+		const DialogConditionState &state = g_twp->_dialog._states[i];
 		if (state.mode != TempOnce) {
 			// TODO: value should be 1 or another value ?
 			json[createJDlgStateKey(state)] = new Common::JSONValue(state.mode == ShowOnce ? 2LL : 1LL);
@@ -821,15 +821,15 @@ static Common::JSONValue *createJSelectableActor(const ActorSlot &slot) {
 static Common::JSONValue *createJSelectableActors() {
 	Common::JSONArray json;
 	for (int i = 0; i < NUMACTORS; i++) {
-		const ActorSlot &slot = g_engine->_hud._actorSlots[i];
+		const ActorSlot &slot = g_twp->_hud._actorSlots[i];
 		json.push_back(createJSelectableActor(slot));
 	}
 	return new Common::JSONValue(json);
 }
 
 static Common::JSONValue *createJGameScene() {
-	bool actorsSelectable = asOn & g_engine->_actorSwitcher._mode;
-	bool actorsTempUnselectable = asTemporaryUnselectable & g_engine->_actorSwitcher._mode;
+	bool actorsSelectable = asOn & g_twp->_actorSwitcher._mode;
+	bool actorsTempUnselectable = asTemporaryUnselectable & g_twp->_actorSwitcher._mode;
 	Common::JSONObject json;
 	json["actorsSelectable"] = createJBool(actorsSelectable);
 	json["actorsTempUnselectable"] = createJBool(actorsTempUnselectable);
@@ -874,7 +874,7 @@ static Common::JSONValue *createJInventory(const ActorSlot &slot) {
 static Common::JSONValue *createJInventory() {
 	Common::JSONArray slots;
 	for (int i = 0; i < NUMACTORS; i++) {
-		const ActorSlot &slot = g_engine->_hud._actorSlots[i];
+		const ActorSlot &slot = g_twp->_hud._actorSlots[i];
 		slots.push_back(createJInventory(slot));
 	}
 	Common::JSONObject json;
@@ -911,7 +911,7 @@ static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
 
 static Common::JSONValue *createJObjects() {
 	Common::JSONObject json;
-	sqgetpairs(sqrootTbl(g_engine->getVm()), fillObjects, &json);
+	sqgetpairs(sqrootTbl(g_twp->getVm()), fillObjects, &json);
 	//   result.fields.sort(cmpKey)
 	return new Common::JSONValue(json);
 }
@@ -943,8 +943,8 @@ static Common::JSONValue *createJRoom(Common::SharedPtr<Room> room) {
 
 static Common::JSONValue *createJRooms() {
 	Common::JSONObject json;
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> r(g_engine->_rooms[i]);
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> r(g_twp->_rooms[i]);
 		if (r)
 			json[r->_name] = createJRoom(r);
 	}
@@ -956,20 +956,20 @@ static Common::JSONValue *createSaveGame() {
 	Common::JSONObject json;
 	json["actors"] = createJActors();
 	json["callbacks"] = createJCallbacks();
-	json["currentRoom"] = createJRoomKey(g_engine->_room);
+	json["currentRoom"] = createJRoomKey(g_twp->_room);
 	json["dialog"] = createJDialog();
 	json["easy_mode"] = createJEasyMode();
 	json["gameGUID"] = new Common::JSONValue("");
 	json["gameScene"] = createJGameScene();
-	json["gameTime"] = new Common::JSONValue(g_engine->_time);
+	json["gameTime"] = new Common::JSONValue(g_twp->_time);
 	json["globals"] = createJGlobals();
-	json["inputState"] = new Common::JSONValue((long long int)g_engine->_inputState.getState());
+	json["inputState"] = new Common::JSONValue((long long int)g_twp->_inputState.getState());
 	json["inventory"] = createJInventory();
 	json["objects"] = createJObjects();
 	json["rooms"] = createJRooms();
 	json["savebuild"] = new Common::JSONValue(958LL);
 	json["savetime"] = new Common::JSONValue((long long)getTime());
-	json["selectedActor"] = new Common::JSONValue(g_engine->_actor ? g_engine->_actor->_key : "");
+	json["selectedActor"] = new Common::JSONValue(g_twp->_actor ? g_twp->_actor->_key : "");
 	json["version"] = new Common::JSONValue((long long int)2);
 	return new Common::JSONValue(json);
 }
@@ -981,7 +981,7 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	debugC(kDebugGame, "save game: %s", data->stringify().c_str());
 
 	// dump savegame as json
-	// Common::OutSaveFile *saveFile = g_engine->getSaveFileManager()->openForSaving("save.json", false);
+	// Common::OutSaveFile *saveFile = g_twp->getSaveFileManager()->openForSaving("save.json", false);
 	// Common::String s = data->stringify(true);
 	// saveFile->write(s.c_str(), s.size());
 	// saveFile->finalize();
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index ff0ac1fb08f..6bbb1341999 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -253,7 +253,7 @@ ParallaxNode::ParallaxNode(const Math::Vector2d &parallax, const Common::String
 ParallaxNode::~ParallaxNode() {}
 
 Math::Matrix4 ParallaxNode::getTrsf(Math::Matrix4 parentTrsf) {
-	Gfx &gfx = g_engine->getGfx();
+	Gfx &gfx = g_twp->getGfx();
 	Math::Matrix4 trsf = Node::getTrsf(parentTrsf);
 	Math::Vector2d camPos = gfx.cameraPos();
 	Math::Vector2d p = Math::Vector2d(-camPos.getX() * _parallax.getX(), -camPos.getY() * _parallax.getY());
@@ -266,23 +266,23 @@ void ParallaxNode::onDrawChildren(Math::Matrix4 trsf) {
 }
 
 void ParallaxNode::drawCore(Math::Matrix4 trsf) {
-	Gfx &gfx = g_engine->getGfx();
-	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
-	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+	Gfx &gfx = g_twp->getGfx();
+	SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
+	Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
 
 	// enable debug lighting ?
-	if (_zOrder == 0 && g_engine->_lighting->_debug) {
-		g_engine->getGfx().use(g_engine->_lighting.get());
+	if (_zOrder == 0 && g_twp->_lighting->_debug) {
+		g_twp->getGfx().use(g_twp->_lighting.get());
 	} else {
-		g_engine->getGfx().use(nullptr);
+		g_twp->getGfx().use(nullptr);
 	}
 
 	Math::Matrix4 t = trsf;
 	float x = 0.f;
 	for (size_t i = 0; i < _frames.size(); i++) {
 		const SpriteSheetFrame &frame = sheet->getFrame(_frames[i]);
-		g_engine->_lighting->setSpriteOffset({0.f, static_cast<float>(-frame.frame.height())});
-		g_engine->_lighting->setSpriteSheetFrame(frame, *texture, false);
+		g_twp->_lighting->setSpriteOffset({0.f, static_cast<float>(-frame.frame.height())});
+		g_twp->_lighting->setSpriteSheetFrame(frame, *texture, false);
 
 		Math::Matrix4 myTrsf = t;
 		myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
@@ -291,7 +291,7 @@ void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 		x += frame.frame.width();
 	}
 
-	g_engine->getGfx().use(nullptr);
+	g_twp->getGfx().use(nullptr);
 }
 
 Anim::Anim(Object *obj)
@@ -391,32 +391,32 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			}
 		}
 		if (_sheet == "raw") {
-			Texture *texture = g_engine->_resManager.texture(frame);
+			Texture *texture = g_twp->_resManager.texture(frame);
 			Math::Vector3d pos(-texture->width / 2.f, -texture->height / 2.f, 0.f);
 			trsf.translate(pos);
-			g_engine->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
+			g_twp->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
 		} else {
-			const SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+			const SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
 			const SpriteSheetFrame *sf = sheet->frame(frame);
 			if(!sf) return;
-			Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
+			Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
 			if (_obj->_lit) {
-				g_engine->getGfx().use(g_engine->_lighting.get());
+				g_twp->getGfx().use(g_twp->_lighting.get());
 				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
 				const float left = flipX ? (-1.f + sf->sourceSize.getX()) / 2.f - sf->spriteSourceSize.left : sf->spriteSourceSize.left - sf->sourceSize.getX() / 2.f;
 				const float top = -sf->sourceSize.getY() / 2.f + sf->spriteSourceSize.top;
 
-				g_engine->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});
-				g_engine->_lighting->setSpriteSheetFrame(*sf, *texture, flipX);
+				g_twp->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});
+				g_twp->_lighting->setSpriteSheetFrame(*sf, *texture, flipX);
 			} else {
-				g_engine->getGfx().use(nullptr);
+				g_twp->getGfx().use(nullptr);
 			}
 			const float x = flipX ? (1.f - sf->sourceSize.getX()) / 2.f + sf->frame.width() + sf->spriteSourceSize.left : sf->sourceSize.getX() / 2.f - sf->spriteSourceSize.left;
 			const float y = sf->sourceSize.getY() / 2.f - sf->spriteSourceSize.height() - sf->spriteSourceSize.top;
 			Math::Vector3d pos(int(-x), int(y), 0.f);
 			trsf.translate(pos);
-			g_engine->getGfx().drawSprite(sf->frame, *texture, getComputedColor(), trsf, flipX);
-			g_engine->getGfx().use(nullptr);
+			g_twp->getGfx().drawSprite(sf->frame, *texture, getComputedColor(), trsf, flipX);
+			g_twp->getGfx().use(nullptr);
 		}
 	}
 }
@@ -456,7 +456,7 @@ void TextNode::onColorUpdated(Color color) {
 }
 
 void TextNode::drawCore(Math::Matrix4 trsf) {
-	_text.draw(g_engine->getGfx(), trsf);
+	_text.draw(g_twp->getGfx(), trsf);
 }
 
 Scene::Scene() : Node("Scene") {
@@ -491,8 +491,8 @@ Common::String InputState::getCursorName() const {
 void InputState::drawCore(Math::Matrix4 trsf) {
 	Common::String cursorName = getCursorName();
 	// draw cursor
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
 	if (ConfMan.getBool("hudSentence") && _hotspot) {
 		cursorName = "hotspot_" + cursorName;
 	}
@@ -500,7 +500,7 @@ void InputState::drawCore(Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos * 2.f);
 	scale(trsf, Math::Vector2d(2.f, 2.f));
-	g_engine->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
+	g_twp->getGfx().drawSprite(sf.frame, *texture, getComputedColor(), trsf);
 }
 
 InputStateFlag InputState::getState() const {
@@ -545,8 +545,8 @@ OverlayNode::OverlayNode() : Node("overlay") {
 }
 
 void OverlayNode::drawCore(Math::Matrix4 trsf) {
-	Math::Vector2d size = g_engine->getGfx().camera();
-	g_engine->getGfx().drawQuad(size, _ovlColor);
+	Math::Vector2d size = g_twp->getGfx().camera();
+	g_twp->getGfx().drawQuad(size, _ovlColor);
 }
 
 static bool hasUpArrow(Common::SharedPtr<Object> actor) {
@@ -578,13 +578,13 @@ Math::Vector2d Inventory::getPos(Common::SharedPtr<Object> inv) const {
 void Inventory::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
-	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+	g_twp->getGfx().drawSprite(sf.frame, *texture, color, trsf);
 }
 
 void Inventory::drawArrows(Math::Matrix4 trsf) {
 	bool isRetro = ConfMan.getBool("retroVerbs");
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
 	const SpriteSheetFrame *arrowUp = &gameSheet->getFrame(isRetro ? "scroll_up_retro" : "scroll_up");
 	const SpriteSheetFrame *arrowDn = &gameSheet->getFrame(isRetro ? "scroll_down_retro" : "scroll_down");
 	float alphaUp = hasUpArrow(_actor) ? 1.f : 0.f;
@@ -599,8 +599,8 @@ void Inventory::drawArrows(Math::Matrix4 trsf) {
 }
 
 void Inventory::drawBack(Math::Matrix4 trsf) {
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
 	const SpriteSheetFrame *back = &gameSheet->getFrame("inventory_background");
 
 	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + back->sourceSize.getX() / 2.f;
@@ -627,8 +627,8 @@ void Inventory::drawBack(Math::Matrix4 trsf) {
 void Inventory::drawItems(Math::Matrix4 trsf) {
 	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + BACKWIDTH / 2.f;
 	float startOffsetY = MARGINBOTTOM + 1.5f * BACKHEIGHT + BACKOFFSET;
-	SpriteSheet *itemsSheet = g_engine->_resManager.spriteSheet("InventoryItems");
-	Texture *texture = g_engine->_resManager.texture(itemsSheet->meta.image);
+	SpriteSheet *itemsSheet = g_twp->_resManager.spriteSheet("InventoryItems");
+	Texture *texture = g_twp->_resManager.texture(itemsSheet->meta.image);
 	int count = MIN(NUMOBJECTS, (int)(_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW));
 
 	for (int i = 0; i < count; i++) {
@@ -685,10 +685,10 @@ void Inventory::update(float elapsed, Common::SharedPtr<Object> actor, Color bac
 
 	_obj = nullptr;
 	if (_actor) {
-		Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
+		Math::Vector2d scrPos = g_twp->winToScreen(g_twp->_cursor.pos);
 
 		// update mouse click
-		bool down = g_engine->_cursor.leftDown;
+		bool down = g_twp->_cursor.leftDown;
 		if (!_down && down) {
 			_down = true;
 			if (_arrowUpRect.contains(scrPos.getX(), scrPos.getY())) {
@@ -753,7 +753,7 @@ void SentenceNode::drawCore(Math::Matrix4 trsf) {
 	}
 	Math::Matrix4 t;
 	t.translate(Math::Vector3d(x, y, 0.f));
-	text.draw(g_engine->getGfx(), t);
+	text.draw(g_twp->getGfx(), t);
 }
 
 SpriteNode::SpriteNode() : Node("Sprite") {}
@@ -765,7 +765,7 @@ void SpriteNode::setSprite(const Common::String &sheet, const Common::String &fr
 }
 
 void SpriteNode::drawCore(Math::Matrix4 trsf) {
-	SpriteSheet *sheet = g_engine->_resManager.spriteSheet(_sheet);
+	SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
 	const SpriteSheetFrame *frame = &sheet->getFrame(_frame);
 
 	Common::Rect rect = frame->frame;
@@ -775,8 +775,8 @@ void SpriteNode::drawCore(Math::Matrix4 trsf) {
 	Math::Vector2d anchor((int)(x), (int)(y));
 	setAnchor(anchor);
 
-	Texture *texture = g_engine->_resManager.texture(sheet->meta.image);
-	g_engine->getGfx().drawSprite(rect, *texture, this->getComputedColor(), trsf);
+	Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
+	g_twp->getGfx().drawSprite(rect, *texture, this->getComputedColor(), trsf);
 }
 
 NoOverrideNode::NoOverrideNode() : Node("NoOverride") {
@@ -818,20 +818,20 @@ HotspotMarkerNode::~HotspotMarkerNode() {}
 void HotspotMarkerNode::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color, Math::Matrix4 trsf) {
 	Math::Vector3d pos(sf.spriteSourceSize.left - sf.sourceSize.getX() / 2.f, -sf.spriteSourceSize.height() - sf.spriteSourceSize.top + sf.sourceSize.getY() / 2.f, 0.f);
 	trsf.translate(pos);
-	g_engine->getGfx().drawSprite(sf.frame, *texture, color, trsf);
+	g_twp->getGfx().drawSprite(sf.frame, *texture, color, trsf);
 }
 
 void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
-	SpriteSheet *gameSheet = g_engine->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_engine->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
 	const SpriteSheetFrame *frame = &gameSheet->getFrame("hotspot_marker");
 	Color color = Color::create(255, 165, 0);
-	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
+	for (size_t i = 0; i < g_twp->_room->_layers.size(); i++) {
+		Common::SharedPtr<Layer> layer = g_twp->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Common::SharedPtr<Object> obj = layer->_objects[j];
 			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
-				Math::Vector2d pos = g_engine->roomToScreen(obj->_node->getAbsPos());
+				Math::Vector2d pos = g_twp->roomToScreen(obj->_node->getAbsPos());
 				Math::Matrix4 t;
 				t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 				drawSprite(*frame, texture, color, t);
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index e31439ae3fe..bc620240fe8 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -385,17 +385,17 @@ int FadeShader::getTextureLoc(int index) { return _textureLoc[index]; }
 
 void ShaderParams::updateShader() {
 	if (effect == RoomEffect::Sepia) {
-		Shader *shader = g_engine->getGfx().getShader();
+		Shader *shader = g_twp->getGfx().getShader();
 		shader->setUniform("RandomValue", randomValue, 5);
 		shader->setUniform("TimeLapse", timeLapse);
 	}
 	//   } else if (effect == RoomEffect::Vhs) {
-	//     Shader* shader = g_engine->getGfx().getShader();
+	//     Shader* shader = g_twp->getGfx().getShader();
 	//     shader->setUniform("iGlobalTime", iGlobalTime);
 	//     shader->setUniform("iNoiseThreshold", iNoiseThreshold);
 	//   } else
 	else if (effect == RoomEffect::Ghost) {
-		Shader *shader = g_engine->getGfx().getShader();
+		Shader *shader = g_twp->getGfx().getShader();
 		shader->setUniform("iGlobalTime", iGlobalTime);
 		shader->setUniform("iFade", iFade);
 		shader->setUniform("wobbleIntensity", wobbleIntensity);
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 53121eee342..3bf1963e381 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -31,8 +31,8 @@ public:
 	virtual ~SoundTrigger() {}
 
 	virtual void trig() override {
-		int i = g_engine->getRandomSource().getRandomNumber(_sounds.size() - 1);
-		g_engine->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 1.f, _objId);
+		int i = g_twp->getRandomSource().getRandomNumber(_sounds.size() - 1);
+		g_twp->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 1.f, _objId);
 	}
 
 private:
@@ -85,7 +85,7 @@ static SQInteger defineSound(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, filename)))
 		return sq_throwerror(v, "failed to get filename");
 	Common::SharedPtr<SoundDefinition> sound(new SoundDefinition(filename));
-	g_engine->_audio._soundDefs.push_back(sound);
+	g_twp->_audio._soundDefs.push_back(sound);
 	debugC(kDebugSndScript, "defineSound(%s)-> %d", filename.c_str(), sound->getId());
 	sqpush(v, sound->getId());
 	return 1;
@@ -101,7 +101,7 @@ static SQInteger fadeOutSound(HSQUIRRELVM v) {
 	float t;
 	if (SQ_FAILED(sqget(v, 3, t)))
 		return sq_throwerror(v, "failed to get fadeOut time");
-	g_engine->_audio.fadeOut(sound, t);
+	g_twp->_audio.fadeOut(sound, t);
 	return 0;
 }
 
@@ -113,7 +113,7 @@ static SQInteger isSoundPlaying(HSQUIRRELVM v) {
 	int soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-	sqpush(v, g_engine->_audio.playing(soundId));
+	sqpush(v, g_twp->_audio.playing(soundId));
 	return 1;
 }
 
@@ -134,10 +134,10 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 	}
 
 	if (obj->_sound) {
-		g_engine->_audio.stop(obj->_sound);
+		g_twp->_audio.stop(obj->_sound);
 	}
 
-	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
+	int soundId = g_twp->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
 	obj->_sound = soundId;
 	sqpush(v, soundId);
 	return 1;
@@ -157,7 +157,7 @@ static SQInteger playSound(HSQUIRRELVM v) {
 		sqget(v, 2, soundId);
 		return sq_throwerror(v, Common::String::format("failed to get sound: %d", soundId).c_str());
 	}
-	int soundId = g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	int soundId = g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -178,7 +178,7 @@ static SQInteger playSoundVolume(HSQUIRRELVM v) {
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get sound");
-	int soundId = g_engine->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	int soundId = g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -216,7 +216,7 @@ static SQInteger loopMusic(HSQUIRRELVM v) {
 	if (numArgs == 4) {
 		sqget(v, 4, fadeInTime);
 	}
-	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kMusicSoundType, loopTimes, fadeInTime);
+	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kMusicSoundType, loopTimes, fadeInTime);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -241,7 +241,7 @@ static SQInteger loopObjectSound(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get fadeInTime");
 		}
 	}
-	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
+	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
 	sqpush(v, soundId);
 	return 1;
 }
@@ -281,7 +281,7 @@ static SQInteger loopSound(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get fadeInTime");
 		}
 	}
-	int soundId = g_engine->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
+	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
 	debugC(kDebugSndScript, "loopSound %s: %d", sound->getName().c_str(), soundId);
 	sqpush(v, soundId);
 	return 1;
@@ -294,10 +294,10 @@ static SQInteger soundVolume(HSQUIRRELVM v, Audio::Mixer::SoundType soundType) {
 			return sq_throwerror(v, "failed to get volume");
 		}
 		int vol = volume * Audio::Mixer::kMaxMixerVolume;
-		g_engine->_mixer->setVolumeForSoundType(soundType, vol);
+		g_twp->_mixer->setVolumeForSoundType(soundType, vol);
 		return 0;
 	}
-	volume = (float)g_engine->_mixer->getVolumeForSoundType(soundType) / Audio::Mixer::kMaxMixerVolume;
+	volume = (float)g_twp->_mixer->getVolumeForSoundType(soundType) / Audio::Mixer::kMaxMixerVolume;
 	sqpush(v, volume);
 	return 1;
 }
@@ -308,10 +308,10 @@ static SQInteger masterSoundVolume(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 2, volume))) {
 			return sq_throwerror(v, "failed to get volume");
 		}
-		g_engine->_audio.setMasterVolume(volume);
+		g_twp->_audio.setMasterVolume(volume);
 		return 0;
 	}
-	volume = g_engine->_audio.getMasterVolume();
+	volume = g_twp->_audio.getMasterVolume();
 	sqpush(v, volume);
 	return 1;
 }
@@ -324,7 +324,7 @@ static SQInteger playMusic(HSQUIRRELVM v) {
 	Common::SharedPtr<SoundDefinition> soundDef = sqsounddef(v, 2);
 	if (!soundDef)
 		return sq_throwerror(v, "failed to get music");
-	int soundId = g_engine->_audio.play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
+	int soundId = g_twp->_audio.play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -349,12 +349,12 @@ static SQInteger soundVolume(HSQUIRRELVM v) {
 	float volume = 1.0f;
 	if (SQ_FAILED(sqget(v, 3, volume)))
 		return sq_throwerror(v, "failed to get volume");
-	g_engine->_audio.setVolume(soundId, volume);
+	g_twp->_audio.setVolume(soundId, volume);
 	return 0;
 }
 
 static SQInteger stopAllSounds(HSQUIRRELVM) {
-	g_engine->_mixer->stopAll();
+	g_twp->_mixer->stopAll();
 	return 0;
 }
 
@@ -368,7 +368,7 @@ static SQInteger stopSound(HSQUIRRELVM v) {
 	int soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-	g_engine->_audio.stop(soundId);
+	g_twp->_audio.stop(soundId);
 	return 0;
 }
 
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 73ce98e37fe..6235b2fbe68 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -219,7 +219,7 @@ void setId(HSQOBJECT &o, int id) {
 }
 
 bool sqrawexists(HSQOBJECT obj, const Common::String &name) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sqpush(v, obj);
 	sq_pushstring(v, name.c_str(), -1);
@@ -233,7 +233,7 @@ bool sqrawexists(HSQOBJECT obj, const Common::String &name) {
 }
 
 void sqsetdelegate(HSQOBJECT obj, HSQOBJECT del) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sqpush(v, obj);
 	sqpush(v, del);
 	sq_setdelegate(v, -2);
@@ -250,7 +250,7 @@ HSQOBJECT sqrootTbl(HSQUIRRELVM v) {
 }
 
 void sqcall(const char *name, const Common::Array<HSQOBJECT> &args) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	HSQOBJECT o = sqrootTbl(v);
 	SQInteger top = sq_gettop(v);
 	sqpushfunc(v, o, name);
@@ -275,8 +275,8 @@ Common::SharedPtr<Room> sqroom(HSQOBJECT table) {
 }
 
 Common::SharedPtr<Room> getRoom(int id) {
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room(g_engine->_rooms[i]);
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> room(g_twp->_rooms[i]);
 		if (getId(room->_table) == id)
 			return room;
 	}
@@ -292,14 +292,14 @@ Common::SharedPtr<Room> sqroom(HSQUIRRELVM v, int i) {
 }
 
 Common::SharedPtr<Object> sqobj(int id) {
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> actor = g_twp->_actors[i];
 		if (getId(actor->_table) == id)
 			return actor;
 	}
 
-	for (size_t i = 0; i < g_engine->_rooms.size(); i++) {
-		Common::SharedPtr<Room> room = g_engine->_rooms[i];
+	for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
+		Common::SharedPtr<Room> room = g_twp->_rooms[i];
 		for (size_t j = 0; j < room->_layers.size(); j++) {
 			Common::SharedPtr<Layer> layer = room->_layers[j];
 			for (size_t k = 0; k < layer->_objects.size(); k++) {
@@ -325,8 +325,8 @@ Common::SharedPtr<Object> sqobj(HSQUIRRELVM v, int i) {
 
 Common::SharedPtr<Object> sqactor(HSQOBJECT table) {
 	int id = getId(table);
-	for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-		Common::SharedPtr<Object> actor = g_engine->_actors[i];
+	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+		Common::SharedPtr<Object> actor = g_twp->_actors[i];
 		if (actor->getId() == id)
 			return actor;
 	}
@@ -341,8 +341,8 @@ Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i) {
 }
 
 Common::SharedPtr<SoundDefinition> sqsounddef(int id) {
-	for (size_t i = 0; i < g_engine->_audio._soundDefs.size(); i++) {
-		Common::SharedPtr<SoundDefinition> sound = g_engine->_audio._soundDefs[i];
+	for (size_t i = 0; i < g_twp->_audio._soundDefs.size(); i++) {
+		Common::SharedPtr<SoundDefinition> sound = g_twp->_audio._soundDefs[i];
 		if (sound->getId() == id)
 			return sound;
 	}
@@ -401,14 +401,14 @@ Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v, int i) {
 }
 
 Common::SharedPtr<ThreadBase> sqthread(int id) {
-	if (g_engine->_cutscene) {
-		if (g_engine->_cutscene->getId() == id) {
-			return g_engine->_cutscene;
+	if (g_twp->_cutscene) {
+		if (g_twp->_cutscene->getId() == id) {
+			return g_twp->_cutscene;
 		}
 	}
 
-	for (size_t i = 0; i < g_engine->_threads.size(); i++) {
-		Common::SharedPtr<ThreadBase> t = g_engine->_threads[i];
+	for (size_t i = 0; i < g_twp->_threads.size(); i++) {
+		Common::SharedPtr<ThreadBase> t = g_twp->_threads[i];
 		if (t->getId() == id) {
 			return t;
 		}
@@ -417,11 +417,11 @@ Common::SharedPtr<ThreadBase> sqthread(int id) {
 }
 
 Light *sqlight(int id) {
-	if(!g_engine->_room)
+	if(!g_twp->_room)
 		return nullptr;
 
 	for (size_t i = 0; i < MAX_LIGHTS; i++) {
-		Light *light = &g_engine->_room->_lights._lights[i];
+		Light *light = &g_twp->_room->_lights._lights[i];
 		if (light->id == id) {
 			return light;
 		}
@@ -447,14 +447,14 @@ private:
 };
 
 Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v) {
-	if (g_engine->_cutscene) {
-		if (g_engine->_cutscene->getThread() == v) {
-			return g_engine->_cutscene;
+	if (g_twp->_cutscene) {
+		if (g_twp->_cutscene->getThread() == v) {
+			return g_twp->_cutscene;
 		}
 	}
 
-	auto it = Common::find_if(g_engine->_threads.begin(), g_engine->_threads.end(), GetThread(v));
-	if(it != g_engine->_threads.end())
+	auto it = Common::find_if(g_twp->_threads.begin(), g_twp->_threads.end(), GetThread(v));
+	if(it != g_twp->_threads.end())
 		return *it;
 	return nullptr;
 }
@@ -477,7 +477,7 @@ SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundD
 }
 
 void sqgetpairs(HSQOBJECT obj, void func(const Common::String &k, HSQOBJECT &obj, void *data), void *data) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sq_pushobject(v, obj);
 	sq_pushnull(v);
 	while (SQ_SUCCEEDED(sq_next(v, -2))) {
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index af8a8185d00..f35fe23d88b 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -52,7 +52,7 @@ void sqpush(HSQUIRRELVM v, T first, Args... args) {
 // set field
 template<typename T>
 void sqsetf(HSQOBJECT o, const Common::String &key, T obj) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sq_pushobject(v, o);
 	sq_pushstring(v, key.c_str(), -1);
@@ -75,13 +75,13 @@ void sqgetf(HSQUIRRELVM v, HSQOBJECT o, const Common::String &name, T &value) {
 
 template<typename T>
 void sqgetf(HSQOBJECT o, const Common::String &name, T &value) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sqgetf(v, o, name, value);
 }
 
 template<typename T>
 void sqgetf(const Common::String &name, T &value) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sqgetf(v, sqrootTbl(v), name, value);
 }
 
@@ -93,7 +93,7 @@ SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundD
 
 template<typename TFunc>
 void sqgetitems(HSQOBJECT o, TFunc func) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sq_pushobject(v, o);
 	sq_pushnull(v);
 	while (SQ_SUCCEEDED(sq_next(v, -2))) {
@@ -109,7 +109,7 @@ void sqgetpairs(HSQOBJECT obj, void func(const Common::String& key, HSQOBJECT& o
 
 template<typename TFunc>
 void sqgetpairs(HSQOBJECT obj, TFunc func) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	sq_pushobject(v, obj);
 	sq_pushnull(v);
 	while (SQ_SUCCEEDED(sq_next(v, -2))) {
@@ -125,7 +125,7 @@ void sqgetpairs(HSQOBJECT obj, TFunc func) {
 
 template<typename T>
 void sqnewf(HSQOBJECT o, const Common::String &key, T obj) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sq_pushobject(v, o);
 	sq_pushstring(v, key.c_str(), -1);
@@ -192,7 +192,7 @@ void sqcall(HSQUIRRELVM v, HSQOBJECT o, const char *name, T... args) {
 template<typename... T>
 void sqcall(HSQOBJECT o, const char *name, T... args) {
 	constexpr size_t n = sizeof...(T);
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sqpushfunc(v, o, name);
 
@@ -207,7 +207,7 @@ void sqcall(HSQOBJECT o, const char *name, T... args) {
 template<typename... T>
 void sqcall(const char *name, T... args) {
 	constexpr size_t n = sizeof...(T);
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	HSQOBJECT o = sqrootTbl(v);
 	SQInteger top = sq_gettop(v);
 	sqpushfunc(v, o, name);
@@ -223,7 +223,7 @@ void sqcall(const char *name, T... args) {
 template<typename TResult, typename... T>
 void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
 	constexpr size_t n = sizeof...(T);
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	SQInteger top = sq_gettop(v);
 	sqpush(v, o);
 	sq_pushstring(v, _SC(name), -1);
@@ -249,7 +249,7 @@ void sqcallfunc(TResult &result, HSQOBJECT o, const char *name, T... args) {
 template<typename TResult, typename... T>
 void sqcallfunc(TResult &result, const char *name, T... args) {
 	constexpr size_t n = sizeof...(T);
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	HSQOBJECT o = sqrootTbl(v);
 	SQInteger top = sq_gettop(v);
 	sqpush(v, o);
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 51bc1a5837f..cdb18b152f0 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -34,7 +34,7 @@
 namespace Twp {
 
 static SQInteger _startthread(HSQUIRRELVM v, bool global) {
-	HSQUIRRELVM vm = g_engine->getVm();
+	HSQUIRRELVM vm = g_twp->getVm();
 	SQInteger size = sq_gettop(v);
 	HSQOBJECT envObj, threadObj, closureObj;
 
@@ -73,7 +73,7 @@ static SQInteger _startthread(HSQUIRRELVM v, bool global) {
 		sq_pop(v, 1); // pop name
 	sq_pop(v, 1);     // pop closure
 
-	g_engine->_threads.push_back(t);
+	g_twp->_threads.push_back(t);
 
 	debugC(kDebugSysScript, "create thread %s", t->getName().c_str());
 
@@ -141,7 +141,7 @@ static SQInteger addCallback(HSQUIRRELVM v) {
 	}
 
 	Common::SharedPtr<Callback> callback(new Callback(newCallbackId(), duration, methodName, args));
-	g_engine->_callbacks.push_back(callback);
+	g_twp->_callbacks.push_back(callback);
 
 	sqpush(v, callback->getId());
 	return 1;
@@ -221,7 +221,7 @@ static SQInteger breakwhilecond(HSQUIRRELVM v, Predicate pred, const char *fmt,
 		return sq_throwerror(v, "Current thread should be created with startthread");
 
 	debugC(kDebugSysScript, "add breakwhilecond name=%s pid=%d, %s", name.c_str(), curThread->getId(), curThread->getName().c_str());
-	g_engine->_tasks.push_back(Common::SharedPtr<Task>(new BreakWhileCond<Predicate>(curThread->getId(), name, pred)));
+	g_twp->_tasks.push_back(Common::SharedPtr<Task>(new BreakWhileCond<Predicate>(curThread->getId(), name, pred)));
 	return -666;
 }
 
@@ -258,7 +258,7 @@ static SQInteger breakwhileanimating(HSQUIRRELVM v) {
 
 struct CameraMoving {
 	bool operator()() {
-		return g_engine->_camera.isMoving();
+		return g_twp->_camera.isMoving();
 	}
 };
 
@@ -271,7 +271,7 @@ static SQInteger breakwhilecamera(HSQUIRRELVM v) {
 
 struct CutsceneRunning {
 	bool operator()() {
-		return g_engine->_cutscene != nullptr;
+		return g_twp->_cutscene != nullptr;
 	}
 };
 
@@ -284,7 +284,7 @@ static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 
 struct DialogRunning {
 	bool operator()() {
-		return g_engine->_dialog.getState() != DialogState::None;
+		return g_twp->_dialog.getState() != DialogState::None;
 	}
 };
 
@@ -297,7 +297,7 @@ static SQInteger breakwhiledialog(HSQUIRRELVM v) {
 
 struct InputOff {
 	bool operator()() {
-		return !g_engine->_inputState.getInputActive();
+		return !g_twp->_inputState.getInputActive();
 	}
 };
 
@@ -336,7 +336,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 			return 0;
 		}
 		return breakwhilecond(
-			v, [id] { return g_engine->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
+			v, [id] { return g_twp->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
 	}
 	return breakwhilecond(
 		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
@@ -344,12 +344,12 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 
 // Returns true if at least 1 actor is talking.
 static bool isSomeoneTalking() {
-	for (auto it = g_engine->_actors.begin(); it != g_engine->_actors.end(); it++) {
+	for (auto it = g_twp->_actors.begin(); it != g_twp->_actors.end(); it++) {
 		Common::SharedPtr<Object> obj = *it;
 		if (obj->getTalking() && obj->getTalking()->isEnabled())
 			return true;
 	}
-	for (auto it = g_engine->_room->_layers.begin(); it != g_engine->_room->_layers.end(); it++) {
+	for (auto it = g_twp->_room->_layers.begin(); it != g_twp->_room->_layers.end(); it++) {
 		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
 			Common::SharedPtr<Object> obj = *it2;
@@ -436,7 +436,7 @@ struct SoundPlaying {
 	explicit SoundPlaying(int soundId) : _soundId(soundId) {}
 
 	bool operator()() {
-		return g_engine->_audio.playing(_soundId);
+		return g_twp->_audio.playing(_soundId);
 	}
 
 private:
@@ -453,7 +453,7 @@ static SQInteger breakwhilesound(HSQUIRRELVM v) {
 }
 
 static SQInteger cutscene(HSQUIRRELVM v) {
-	HSQUIRRELVM vm = g_engine->getVm();
+	HSQUIRRELVM vm = g_twp->getVm();
 	SQInteger nArgs = sq_gettop(v);
 
 	HSQOBJECT envObj;
@@ -485,7 +485,7 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 	Common::SharedPtr<ThreadBase> parentThread = sqthread(v);
 	Common::String cutsceneName = Common::String::format("%s (%lld)", _stringval(_closure(closure)->_function->_sourcename), _closure(closure)->_function->_lineinfos->_line);
 	Common::SharedPtr<Cutscene> cutscene(new Cutscene(cutsceneName, parentThread->getId(), threadObj, closure, closureOverride, envObj));
-	g_engine->_cutscene = cutscene;
+	g_twp->_cutscene = cutscene;
 
 	// call the closure in the thread
 	cutscene->update(0.f);
@@ -494,7 +494,7 @@ static SQInteger cutscene(HSQUIRRELVM v) {
 
 static SQInteger cutsceneOverride(HSQUIRRELVM v) {
 	debugC(kDebugSysScript, "cutsceneOverride");
-	g_engine->_cutscene->cutsceneOverride();
+	g_twp->_cutscene->cutsceneOverride();
 	return 0;
 }
 
@@ -512,18 +512,18 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		int enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
-		g_engine->_saveGameManager._autoSave = enabled != 0;
+		g_twp->_saveGameManager._autoSave = enabled != 0;
 	} break;
 	case EX_AUTOSAVE: {
-		if (g_engine->_saveGameManager._autoSave) {
-			g_engine->saveGameState(0, "", true);
+		if (g_twp->_saveGameManager._autoSave) {
+			g_twp->saveGameState(0, "", true);
 		}
 	} break;
 	case EX_ALLOW_SAVEGAMES: {
 		int enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
-		g_engine->_saveGameManager._allowSaveGame = enabled != 0;
+		g_twp->_saveGameManager._allowSaveGame = enabled != 0;
 	} break;
 	case EX_POP_CHARACTER_SELECTION:
 		// seems not to be used
@@ -536,7 +536,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 3);
 		if (!sound)
 			return sq_throwerror(v, "failed to get sound for EX_BUTTON_HOVER_SOUND");
-		g_engine->_audio._soundHover = sound;
+		g_twp->_audio._soundHover = sound;
 	} break;
 	case EX_RESTART:
 		warning("TODO: exCommand EX_RESTART: not implemented");
@@ -550,7 +550,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		warning("exCommand EX_DISABLE_SAVESYSTEM: not implemented");
 		break;
 	case EX_SHOW_OPTIONS:
-    	g_engine->openMainMenuDialog();
+    	g_twp->openMainMenuDialog();
 		break;
 	case EX_OPTIONS_MUSIC:
 		warning("TODO: exCommand EX_OPTIONS_MUSIC: not implemented");
@@ -575,7 +575,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 // if (gameTime() > (time+testerTronTimeOut)) { // Do something
 // }
 static SQInteger gameTime(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_time);
+	sqpush(v, g_twp->_time);
 	return 1;
 }
 
@@ -584,7 +584,7 @@ static SQInteger sysInclude(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, filename))) {
 		return sq_throwerror(v, "failed to get filename");
 	}
-	g_engine->execNutEntry(v, filename);
+	g_twp->execNutEntry(v, filename);
 	return 0;
 }
 
@@ -597,25 +597,25 @@ static SQInteger inputHUD(HSQUIRRELVM v) {
 	bool on;
 	if (SQ_FAILED(sqget(v, 2, on)))
 		return sq_throwerror(v, "failed to get on");
-	g_engine->_inputState.setInputHUD(on);
+	g_twp->_inputState.setInputHUD(on);
 	return 0;
 }
 
 static SQInteger inputOff(HSQUIRRELVM v) {
-	if (!g_engine->_cutscene || g_engine->_cutscene->isStopped()) {
-		g_engine->_inputState.setInputActive(false);
-		g_engine->_inputState.setShowCursor(false);
+	if (!g_twp->_cutscene || g_twp->_cutscene->isStopped()) {
+		g_twp->_inputState.setInputActive(false);
+		g_twp->_inputState.setShowCursor(false);
 	}
 	return 0;
 }
 
 static SQInteger inputOn(HSQUIRRELVM v) {
-	Common::SharedPtr<Cutscene> scene(g_engine->_cutscene);
+	Common::SharedPtr<Cutscene> scene(g_twp->_cutscene);
 	if (!scene || scene->isStopped()) {
-		g_engine->_inputState.setInputActive(true);
-		g_engine->_inputState.setShowCursor(true);
+		g_twp->_inputState.setInputActive(true);
+		g_twp->_inputState.setShowCursor(true);
 	} else {
-		int state = g_engine->_inputState.getState();
+		int state = g_twp->_inputState.getState();
 		state |= UI_INPUT_ON;
 		state &= (~UI_INPUT_OFF);
 		state |= UI_CURSOR_ON;
@@ -627,14 +627,14 @@ static SQInteger inputOn(HSQUIRRELVM v) {
 }
 
 static SQInteger inputSilentOff(HSQUIRRELVM v) {
-	g_engine->_inputState.setInputActive(false);
+	g_twp->_inputState.setInputActive(false);
 	return 0;
 }
 
 static SQInteger sysInputState(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	if (numArgs == 1) {
-		int state = (int)g_engine->_inputState.getState();
+		int state = (int)g_twp->_inputState.getState();
 		sqpush(v, state);
 		return 1;
 	}
@@ -642,7 +642,7 @@ static SQInteger sysInputState(HSQUIRRELVM v) {
 		int state;
 		if (SQ_FAILED(sqget(v, 2, state)))
 			return sq_throwerror(v, "failed to get state");
-		g_engine->_inputState.setState((InputStateFlag)state);
+		g_twp->_inputState.setState((InputStateFlag)state);
 		return 0;
 	}
 	return sq_throwerror(v, Common::String::format("inputState with %lld arguments not implemented", numArgs).c_str());
@@ -653,12 +653,12 @@ static SQInteger inputVerbs(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, on)))
 		return sq_throwerror(v, "failed to get isActive");
 	debugC(kDebugSysScript, "inputVerbs: %s", on ? "yes" : "no");
-	g_engine->_inputState.setInputVerbsActive(on);
+	g_twp->_inputState.setInputVerbsActive(on);
 	return 1;
 }
 
 static SQInteger isInputOn(HSQUIRRELVM v) {
-	bool isActive = g_engine->_inputState.getInputActive();
+	bool isActive = g_twp->_inputState.getInputActive();
 	sqpush(v, isActive);
 	return 1;
 }
@@ -700,7 +700,7 @@ static SQInteger logWarning(HSQUIRRELVM v) {
 // Based on when the machine is booted and runs all the time (not paused or saved).
 // See also gameTime, which is in seconds.
 static SQInteger microTime(HSQUIRRELVM v) {
-	sqpush(v, g_engine->_time * 1000.0f);
+	sqpush(v, g_twp->_time * 1000.0f);
 	return 1;
 }
 
@@ -714,7 +714,7 @@ static SQInteger moveCursorTo(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 4, t)))
 		return sq_throwerror(v, "Failed to get time");
 
-	g_engine->_cursor.pos = Math::Vector2d(x, y);
+	g_twp->_cursor.pos = Math::Vector2d(x, y);
 	// TODO: use time
 	return 0;
 }
@@ -724,8 +724,8 @@ static SQInteger removeCallback(HSQUIRRELVM v) {
 	int id = 0;
 	if (SQ_FAILED(sqget(v, 2, id)))
 		return sq_throwerror(v, "failed to get callback");
-	for (size_t i = 0; i < g_engine->_callbacks.size(); i++) {
-		Common::SharedPtr<Callback> cb = g_engine->_callbacks[i];
+	for (size_t i = 0; i < g_twp->_callbacks.size(); i++) {
+		Common::SharedPtr<Callback> cb = g_twp->_callbacks[i];
 		if (cb->getId() == id) {
 			cb->remove();
 			return 0;
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 6cdd302af3d..5be24b306c7 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -58,7 +58,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 	_args = args;
 	_pauseable = true;
 
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	for (auto & _arg : _args) {
 		sq_addref(v, &_arg);
 	}
@@ -69,7 +69,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 
 Thread::~Thread() {
 	debugC(kDebugGame, "delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	for (auto & _arg : _args) {
 		sq_release(v, &_arg);
 	}
@@ -124,18 +124,18 @@ Cutscene::Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT thr
 
 	_name = name;
 	_id = newThreadId();
-	_inputState = g_engine->_inputState.getState();
-	_actor = g_engine->_followActor;
-	_showCursor = g_engine->_inputState.getShowCursor();
+	_inputState = g_twp->_inputState.getState();
+	_actor = g_twp->_followActor;
+	_showCursor = g_twp->_inputState.getShowCursor();
 	_state = csStart;
 	debugC(kDebugGame, "Create cutscene %d with input: 0x%X from parent thread: %d", _id, _inputState, _parentThreadId);
-	g_engine->_inputState.setInputActive(false);
-	g_engine->_inputState.setShowCursor(false);
-	for (auto thread : g_engine->_threads) {
+	g_twp->_inputState.setInputActive(false);
+	g_twp->_inputState.setShowCursor(false);
+	for (auto thread : g_twp->_threads) {
 			if (thread->isGlobal())
 			thread->pause();
 	}
-	HSQUIRRELVM vm = g_engine->getVm();
+	HSQUIRRELVM vm = g_twp->getVm();
 	sq_addref(vm, &_threadObj);
 	sq_addref(vm, &_closure);
 	sq_addref(vm, &_closureOverride);
@@ -144,7 +144,7 @@ Cutscene::Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT thr
 
 Cutscene::~Cutscene() {
 	debugC(kDebugGame, "destroy cutscene %d", _id);
-	HSQUIRRELVM vm = g_engine->getVm();
+	HSQUIRRELVM vm = g_twp->getVm();
 	sq_release(vm, &_threadObj);
 	sq_release(vm, &_closure);
 	sq_release(vm, &_closureOverride);
@@ -167,13 +167,13 @@ void Cutscene::start() {
 void Cutscene::stop() {
 	_state = csQuit;
 	debugC(kDebugGame, "End cutscene: %d", getId());
-	g_engine->_inputState.setState(_inputState);
-	g_engine->_inputState.setShowCursor(_showCursor);
+	g_twp->_inputState.setState(_inputState);
+	g_twp->_inputState.setShowCursor(_showCursor);
 	if (_showCursor)
-		g_engine->_inputState.setInputActive(true);
+		g_twp->_inputState.setInputActive(true);
 	debugC(kDebugGame, "Restore cutscene input: %X", _inputState);
-	g_engine->follow(g_engine->_actor);
-	Common::Array<Common::SharedPtr<ThreadBase> > threads(g_engine->_threads);
+	g_twp->follow(g_twp->_actor);
+	Common::Array<Common::SharedPtr<ThreadBase> > threads(g_twp->_threads);
 	for (auto thread : threads) {
 		if (thread->isGlobal())
 			thread->unpause();
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 85b7a42f9b5..d7c495b5416 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -52,7 +52,7 @@ Common::String TextDb::getText(int id) {
 }
 
 Common::String TextDb::getText(const Common::String &text) {
-	HSQUIRRELVM v = g_engine->getVm();
+	HSQUIRRELVM v = g_twp->getVm();
 	if (text.size() > 0) {
 		if (text[0] == '@') {
 			int id = atoi(text.c_str() + 1);
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f5302991687..4022a9ffa59 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -49,7 +49,7 @@
 
 namespace Twp {
 
-TwpEngine *g_engine;
+TwpEngine *g_twp;
 
 #ifdef USE_IMGUI
 	SDL_Window *g_window = nullptr;
@@ -59,7 +59,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
 	  _gameDescription(gameDesc),
 	  _randomSource("Twp") {
-	g_engine = this;
+	g_twp = this;
 	_dialog._tgt.reset(new EngineDialogTarget());
 	sq_resetobject(&_defaultObj);
 	_screenScene.setName("Screen");
@@ -154,7 +154,7 @@ bool TwpEngine::execSentence(Common::SharedPtr<Object> actor, VerbId verbId, Com
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
 	debugC(kDebugGame, "exec(%s,%d,%s,%s)", name.c_str(), verbId.id, noun1name.c_str(), noun2name.c_str());
-	actor = !actor ? g_engine->_actor : actor;
+	actor = !actor ? g_twp->_actor : actor;
 	if ((verbId.id <= 0) || (verbId.id > MAX_VERBS) || (!noun1) || (!actor))
 		return false;
 
@@ -288,13 +288,13 @@ Common::String TwpEngine::cursorText() {
 
 template<typename TFunc>
 void objsAt(Math::Vector2d pos, TFunc func) {
-	if (g_engine->_uiInv.getObject() && g_engine->_room->_fullscreen == FULLSCREENROOM)
-		func(g_engine->_uiInv.getObject());
-	for (size_t i = 0; i < g_engine->_room->_layers.size(); i++) {
-		Common::SharedPtr<Layer> layer = g_engine->_room->_layers[i];
+	if (g_twp->_uiInv.getObject() && g_twp->_room->_fullscreen == FULLSCREENROOM)
+		func(g_twp->_uiInv.getObject());
+	for (size_t i = 0; i < g_twp->_room->_layers.size(); i++) {
+		Common::SharedPtr<Layer> layer = g_twp->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Common::SharedPtr<Object> obj = layer->_objects[j];
-			if ((obj != g_engine->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
+			if ((obj != g_twp->_actor) && (obj->isTouchable() || obj->inInventory()) && (obj->_node->isVisible()) && (obj->_objType == otNone) && (obj->contains(pos)))
 				if (func(obj))
 					return;
 		}
@@ -322,10 +322,10 @@ Common::SharedPtr<Object> inventoryAt(Math::Vector2d pos) {
 }
 
 static void selectSlotActor(int id) {
-	if(g_engine->_actorSwitcher._mode == asOn) {
-		for (size_t i = 0; i < g_engine->_actors.size(); i++) {
-			if (g_engine->_actors[i]->getId() == id) {
-				g_engine->setActor(g_engine->_actors[i]);
+	if(g_twp->_actorSwitcher._mode == asOn) {
+		for (size_t i = 0; i < g_twp->_actors.size(); i++) {
+			if (g_twp->_actors[i]->getId() == id) {
+				g_twp->setActor(g_twp->_actors[i]);
 				break;
 			}
 		}
@@ -333,8 +333,8 @@ static void selectSlotActor(int id) {
 }
 
 static void showOptions(int id) {
-	if (g_engine && !g_engine->isPaused())
-		g_engine->openMainMenuDialog();
+	if (g_twp && !g_twp->isPaused())
+		g_twp->openMainMenuDialog();
 }
 
 ActorSwitcherSlot TwpEngine::actorSwitcherSlot(ActorSlot *slot) {
@@ -372,7 +372,7 @@ struct GetUseNoun2 {
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
-			if ((obj != g_engine->_actor) && (g_engine->_noun2 != obj)) {
+			if ((obj != g_twp->_actor) && (g_twp->_noun2 != obj)) {
 				_noun2 = obj;
 			}
 		}
@@ -390,7 +390,7 @@ struct GetGiveableNoun2 {
 	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
-		if ((obj != g_engine->_actor) && (obj->getFlags() & GIVEABLE) && (g_engine->_noun2 != obj)) {
+		if ((obj != g_twp->_actor) && (obj->getFlags() & GIVEABLE) && (g_twp->_noun2 != obj)) {
 			_noun2 = obj;
 			return true;
 		}
@@ -633,7 +633,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 		setShaderEffect(_room->_effect);
 		_lighting->update(_room->_lights);
 	}
-	_shaderParams.randomValue[0] = g_engine->getRandom();
+	_shaderParams.randomValue[0] = g_twp->getRandom();
 	_shaderParams.timeLapse = fmodf(_time, 1000.f);
 	_shaderParams.iGlobalTime = _shaderParams.timeLapse;
 	_shaderParams.updateShader();
@@ -799,7 +799,7 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectActor4:
 				case TwpAction::kSelectActor5:
 				case TwpAction::kSelectActor6:
-					if(g_engine->_actorSwitcher._mode == asOn) {
+					if(g_twp->_actorSwitcher._mode == asOn) {
 						int index = (TwpAction)e.customType - kSelectActor1;
 						ActorSlot *slot = &_hud._actorSlots[index];
 						if (slot->selectable && slot->actor && (slot->actor->_room->_name != "Void")) {
@@ -808,7 +808,7 @@ Common::Error TwpEngine::run() {
 					}
 					break;
 				case TwpAction::kSelectPreviousActor:
-					if ((g_engine->_actorSwitcher._mode == asOn) && _actor) {
+					if ((g_twp->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];
@@ -823,7 +823,7 @@ Common::Error TwpEngine::run() {
 					}
 					break;
 				case TwpAction::kSelectNextActor:
-					if ((g_engine->_actorSwitcher._mode == asOn) && _actor) {
+					if ((g_twp->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
 							ActorSlot *slot = &_hud._actorSlots[i];
@@ -1007,7 +1007,7 @@ Common::Error TwpEngine::saveGameState(int slot, const Common::String &desc, boo
 	if (result.getCode() == Common::kNoError) {
 		name = changeFileExt(name, ".png");
 		Common::OutSaveFile *thumbnail = _saveFileMan->openForSaving(name, false);
-		g_engine->capture(*thumbnail, Math::Vector2d(320, 180));
+		g_twp->capture(*thumbnail, Math::Vector2d(320, 180));
 		thumbnail->finalize();
 
 		saveFile->finalize();
@@ -1278,8 +1278,8 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 
 void TwpEngine::actorEnter(Common::SharedPtr<Object> actor) {
 	if(!actor) return;
-	if(sqrawexists(g_engine->_room->_table, "actorEnter")) {
-		sqcall(g_engine->_room->_table, "actorEnter", actor->_table);
+	if(sqrawexists(g_twp->_room->_table, "actorEnter")) {
+		sqcall(g_twp->_room->_table, "actorEnter", actor->_table);
 	} else {
 		sqcall("actorEnter", actor->_table);
 	}
@@ -1618,7 +1618,7 @@ void TwpEngine::callTrigger(Common::SharedPtr<Object> obj, HSQOBJECT trigger) {
 		Common::SharedPtr<Thread> thread(new Thread("Trigger", false, threadObj, obj->_table, trigger, args));
 
 		debugC(kDebugGame, "create triggerthread id: %d}", thread->getId());
-		g_engine->_threads.push_back(thread);
+		g_twp->_threads.push_back(thread);
 
 		// call the closure in the thread
 		if (!thread->call()) {
@@ -1674,7 +1674,7 @@ void TwpEngine::stopTalking() {
 }
 
 float TwpEngine::getRandom() const {
-	return g_engine->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
+	return g_twp->getRandomSource().getRandomNumber(RAND_MAX) / (float)RAND_MAX;
 }
 
 float TwpEngine::getRandom(float min, float max) const {
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 085139035ff..f3660a9656d 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -239,8 +239,8 @@ private:
 	Shader _sepiaShader;
 };
 
-extern TwpEngine *g_engine;
-#define SHOULD_QUIT ::Twp::g_engine->shouldQuit()
+extern TwpEngine *g_twp;
+#define SHOULD_QUIT ::Twp::g_twp->shouldQuit()
 
 } // End of namespace Twp
 
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 1b172ce09e3..7b5cadaf1b8 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -30,18 +30,18 @@ WalkboxNode::WalkboxNode() : Node("Walkbox") {
 }
 
 void WalkboxNode::drawCore(Math::Matrix4 trsf) {
-	if (g_engine->_room) {
+	if (g_twp->_room) {
 		Color white;
 		Color red(1.f, 0.f, 0.f);
 		Color green(0.f, 1.f, 0.f);
 		Color yellow(1.f, 1.f, 0.f);
-		Common::Array<Walkbox> walkboxes = g_engine->_room ? g_engine->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
+		Common::Array<Walkbox> walkboxes = g_twp->_room ? g_twp->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
 
 		switch (_mode) {
 		case WalkboxMode::All: {
 			Math::Matrix4 transf;
 			// cancel camera pos
-			Math::Vector2d pos = g_engine->getGfx().cameraPos();
+			Math::Vector2d pos = g_twp->getGfx().cameraPos();
 			transf.translate(Math::Vector3d(-pos.getX(), -pos.getY(), 0.f));
 			for (uint i = 0; i < walkboxes.size(); i++) {
 				const Walkbox &wb = walkboxes[i];
@@ -57,12 +57,12 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 					p -= Math::Vector2d(1.f, 1.f);
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 				}
-				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
+				g_twp->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
 		} break;
 		case WalkboxMode::Merged: {
 			Math::Matrix4 transf;
-			Math::Vector2d pos = g_engine->getGfx().cameraPos();
+			Math::Vector2d pos = g_twp->getGfx().cameraPos();
 			// cancel camera pos
 			transf.translate(Math::Vector3d(-pos.getX(), -pos.getY(), 0.f));
 			for (uint i = 0; i < walkboxes.size(); i++) {
@@ -79,9 +79,9 @@ void WalkboxNode::drawCore(Math::Matrix4 trsf) {
 					p -= Math::Vector2d(1.f, 1.f);
 					t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
 					// if (wb.concave(j) == (i == 0))
-					// 	g_engine->getGfx().drawQuad(Math::Vector2d(3.f, 3.f), yellow, t);
+					// 	g_twp->getGfx().drawQuad(Math::Vector2d(3.f, 3.f), yellow, t);
 				}
-				g_engine->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
+				g_twp->getGfx().drawLinesLoop(vertices.data(), vertices.size(), transf);
 			}
 		} break;
 		default:
@@ -95,8 +95,8 @@ PathNode::PathNode() : Node("Path") {
 }
 
 Vector2i PathNode::fixPos(Vector2i pos) {
-	for (size_t i = 0; i < g_engine->_room->_mergedPolygon.size(); i++) {
-		Walkbox &wb = g_engine->_room->_mergedPolygon[i];
+	for (size_t i = 0; i < g_twp->_room->_mergedPolygon.size(); i++) {
+		Walkbox &wb = g_twp->_room->_mergedPolygon[i];
 		if (!wb.isVisible() && wb.contains(pos)) {
 			return wb.getClosestPointOnEdge((Vector2i)pos);
 		}
@@ -108,14 +108,14 @@ Vector2i PathNode::fixPos(Vector2i pos) {
 }
 
 void PathNode::drawCore(Math::Matrix4 trsf) {
-	if (!g_engine->_room)
+	if (!g_twp->_room)
 		return;
 
 	const Color green(0.f, 1.f, 0.f);
 	const Color red(1.f, 0.f, 0.f);
 	const Color yellow(1.f, 1.f, 0.f);
 	const Color blue(0.f, 0.f, 1.f);
-	const Common::SharedPtr<Object> actor = g_engine->_actor;
+	const Common::SharedPtr<Object> actor = g_twp->_actor;
 
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
@@ -123,28 +123,28 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		const Common::Array<Vector2i> &path = walkTo->getPath();
 		if (path.size() > 0) {
 			Common::Array<Vertex> vertices;
-			vertices.push_back(Vertex(g_engine->roomToScreen(actor->_node->getPos()), yellow));
+			vertices.push_back(Vertex(g_twp->roomToScreen(actor->_node->getPos()), yellow));
 			for (uint i = 0; i < path.size(); i++) {
-				Math::Vector2d p = g_engine->roomToScreen((Math::Vector2d)path[i]);
+				Math::Vector2d p = g_twp->roomToScreen((Math::Vector2d)path[i]);
 				vertices.push_back(Vertex(p, yellow));
 
 				Math::Matrix4 t;
 				p -= Math::Vector2d(2.f, 2.f);
 				t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
-				g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
+				g_twp->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 			}
-			g_engine->getGfx().drawLines(vertices.data(), vertices.size());
+			g_twp->getGfx().drawLines(vertices.data(), vertices.size());
 		}
 	}
 
 	// draw graph nodes
-	const Twp::Graph &graph = g_engine->_room->_pathFinder.getGraph();
+	const Twp::Graph &graph = g_twp->_room->_pathFinder.getGraph();
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All))) {
 		for (uint i = 0; i < graph._concaveVertices.size(); i++) {
-			const Math::Vector2d p = g_engine->roomToScreen((Math::Vector2d)graph._concaveVertices[i]) - Math::Vector2d(2.f, 2.f);
+			const Math::Vector2d p = g_twp->roomToScreen((Math::Vector2d)graph._concaveVertices[i]) - Math::Vector2d(2.f, 2.f);
 			Math::Matrix4 t;
 			t.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
-			g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
+			g_twp->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 		}
 
 		if (_mode == PathMode::All) {
@@ -152,10 +152,10 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 				const Common::Array<GraphEdge> &edges = graph._edges[i];
 				for (uint j = 0; j < edges.size(); j++) {
 					const GraphEdge &edge = edges[j];
-					const Math::Vector2d p1 = g_engine->roomToScreen((Math::Vector2d)graph._nodes[edge.start]);
-					const Math::Vector2d p2 = g_engine->roomToScreen((Math::Vector2d)graph._nodes[edge.to]);
+					const Math::Vector2d p1 = g_twp->roomToScreen((Math::Vector2d)graph._nodes[edge.start]);
+					const Math::Vector2d p2 = g_twp->roomToScreen((Math::Vector2d)graph._nodes[edge.to]);
 					Vertex vertices[] = {Vertex(p1), Vertex(p2)};
-					g_engine->getGfx().drawLines(&vertices[0], 2);
+					g_twp->getGfx().drawLines(&vertices[0], 2);
 				}
 			}
 		}
@@ -163,38 +163,38 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 
 	// draw path from actor to mouse position
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor) {
-		Math::Vector2d pos = g_engine->roomToScreen(actor->_node->getPos()) - Math::Vector2d(2.f, 2.f);
+		Math::Vector2d pos = g_twp->roomToScreen(actor->_node->getPos()) - Math::Vector2d(2.f, 2.f);
 		Math::Matrix4 t;
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
-		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
+		g_twp->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), yellow, t);
 
-		const Math::Vector2d scrPos = g_engine->winToScreen(g_engine->_cursor.pos);
-		const Math::Vector2d roomPos = g_engine->screenToRoom(scrPos);
+		const Math::Vector2d scrPos = g_twp->winToScreen(g_twp->_cursor.pos);
+		const Math::Vector2d roomPos = g_twp->screenToRoom(scrPos);
 		Vector2i p = fixPos((Vector2i)roomPos);
 		t = Math::Matrix4();
-		pos = g_engine->roomToScreen((Math::Vector2d)p) - Math::Vector2d(4.f, 4.f);
+		pos = g_twp->roomToScreen((Math::Vector2d)p) - Math::Vector2d(4.f, 4.f);
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
-		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
+		g_twp->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), yellow, t);
 
-		Common::SharedPtr<Object> obj = g_engine->objAt(roomPos);
+		Common::SharedPtr<Object> obj = g_twp->objAt(roomPos);
 		if (obj) {
 			t = Math::Matrix4();
-			pos = g_engine->roomToScreen(obj->getUsePos()) - Math::Vector2d(4.f, 4.f);
+			pos = g_twp->roomToScreen(obj->getUsePos()) - Math::Vector2d(4.f, 4.f);
 			t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
-			g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), red, t);
+			g_twp->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), red, t);
 		}
 
-		const Common::Array<Vector2i> path = g_engine->_room->calculatePath(fixPos((Vector2i)actor->_node->getPos()), p);
+		const Common::Array<Vector2i> path = g_twp->_room->calculatePath(fixPos((Vector2i)actor->_node->getPos()), p);
 		Common::Array<Vertex> vertices;
 		for (uint i = 0; i < path.size(); i++) {
-			vertices.push_back(Vertex(g_engine->roomToScreen((Math::Vector2d)path[i]), yellow));
+			vertices.push_back(Vertex(g_twp->roomToScreen((Math::Vector2d)path[i]), yellow));
 		}
 		if (vertices.size() > 0) {
-			g_engine->getGfx().drawLines(vertices.data(), vertices.size());
+			g_twp->getGfx().drawLines(vertices.data(), vertices.size());
 		}
 
 		// draw a green square if inside walkbox, red if not
-		Common::Array<Walkbox> walkboxes = g_engine->_room ? g_engine->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
+		Common::Array<Walkbox> walkboxes = g_twp->_room ? g_twp->_room->_pathFinder.getWalkboxes() : Common::Array<Walkbox>();
 		if (walkboxes.empty())
 			return;
 
@@ -202,13 +202,13 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		pos = scrPos - Math::Vector2d(4.f, 4.f);
 		t = Math::Matrix4();
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
-		g_engine->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), inside ? green : red, t);
+		g_twp->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), inside ? green : red, t);
 
 		// draw a blue square on the closest point
-		pos = g_engine->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge((Vector2i)roomPos));
+		pos = g_twp->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge((Vector2i)roomPos));
 		t = Math::Matrix4();
 		t.translate(Math::Vector3d(pos.getX() - 2.f, pos.getY() - 2.f, 0.f));
-		g_engine->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), blue, t);
+		g_twp->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), blue, t);
 	}
 }
 
@@ -217,17 +217,17 @@ LightingNode::LightingNode() : Node("Lighting") {
 }
 
 void LightingNode::drawCore(Math::Matrix4 trsf) {
-	if (!g_engine->_room)
+	if (!g_twp->_room)
 		return;
 
 	const float size = 6.0f;
 	for (int i = 0; i < MAX_LIGHTS; i++) {
-		float *pos = &g_engine->_lighting->u_lightPos[i * 3];
-		float *color = &g_engine->_lighting->u_lightColor[i * 3];
-		Math::Vector2d p = g_engine->roomToScreen(Math::Vector2d(pos[0], pos[1]));
+		float *pos = &g_twp->_lighting->u_lightPos[i * 3];
+		float *color = &g_twp->_lighting->u_lightColor[i * 3];
+		Math::Vector2d p = g_twp->roomToScreen(Math::Vector2d(pos[0], pos[1]));
 		Math::Matrix4 t;
 		t.translate(Math::Vector3d(p.getX() - size / 2.f, p.getY() - size / 2.f, 0.f));
-		g_engine->getGfx().drawQuad(Math::Vector2d(size, size), Color(color[0], color[1], color[2]), t);
+		g_twp->getGfx().drawQuad(Math::Vector2d(size, size), Color(color[0], color[1], color[2]), t);
 	}
 }
 


Commit: c13fdb899feef1153768f1e879b7d74812977d49
    https://github.com/scummvm/scummvm/commit/c13fdb899feef1153768f1e879b7d74812977d49
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix initKeymaps

Changed paths:
    engines/twp/metaengine.cpp


diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 7c2d7a24067..9658e29ab78 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -87,7 +87,7 @@ void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
 	ConfMan.registerDefault("language", "en");
 }
 
-static Common::String getDesc(const Twp::SaveGame& savegame) {
+static Common::String getDesc(const Twp::SaveGame &savegame) {
 	Common::String desc = Twp::formatTime(savegame.time, "%b %d at %H:%M");
 	if (savegame.easyMode)
 		desc += " (casual)";
@@ -170,33 +170,34 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 	Common::Keymap *engineKeyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, target, "Thimbleweed Park keymap");
 
 	struct {
-		Common::String name;
-		const Common::U32String desc;
+		const char *name;
+		const char *desc;
 		Twp::TwpAction action;
-		Common::String input;
+		const char *input;
 	} actions[] = {
-		{"SKIPCUTSCENE", _("Skip cutscene"), Twp::kSkipCutscene, "ESCAPE"},
-		{"SELECTACTOR1", _("Select Actor 1"), Twp::kSelectActor1, "1"},
-		{"SELECTACTOR2", _("Select Actor 2"), Twp::kSelectActor2, "2"},
-		{"SELECTACTOR3", _("Select Actor 3"), Twp::kSelectActor3, "3"},
-		{"SELECTACTOR4", _("Select Actor 4"), Twp::kSelectActor4, "4"},
-		{"SELECTACTOR5", _("Select Actor 5"), Twp::kSelectActor5, "5"},
-		{"SELECTACTOR6", _("Select Actor 6"), Twp::kSelectActor6, "6"},
-		{"SELECTCHOICE1", _("Select Choice 1"), Twp::kSelectChoice1, "1"},
-		{"SELECTCHOICE2", _("Select Choice 2"), Twp::kSelectChoice2, "2"},
-		{"SELECTCHOICE3", _("Select Choice 3"), Twp::kSelectChoice3, "3"},
-		{"SELECTCHOICE4", _("Select Choice 4"), Twp::kSelectChoice4, "4"},
-		{"SELECTCHOICE5", _("Select Choice 5"), Twp::kSelectChoice5, "5"},
-		{"SELECTCHOICE6", _("Select Choice 6"), Twp::kSelectChoice6, "6"},
-		{"SELECTNEXTACTOR", _("Select Next Actor"), Twp::kSelectNextActor, "0"},
-		{"SELECTPREVACTOR", _("Select Previous Actor"), Twp::kSelectPreviousActor, "9"},
-		{"SKIPTEXT", _("Skip Text"), Twp::kSkipText, "."},
-		{"SHOWHOTSPOTS", _("Show hotspots"), Twp::kShowHotspots, "TAB"},
+		{"SKIPCUTSCENE", _s("Skip cutscene"), Twp::kSkipCutscene, "ESCAPE"},
+		{"SELECTACTOR1", _s("Select Actor 1"), Twp::kSelectActor1, "1"},
+		{"SELECTACTOR2", _s("Select Actor 2"), Twp::kSelectActor2, "2"},
+		{"SELECTACTOR3", _s("Select Actor 3"), Twp::kSelectActor3, "3"},
+		{"SELECTACTOR4", _s("Select Actor 4"), Twp::kSelectActor4, "4"},
+		{"SELECTACTOR5", _s("Select Actor 5"), Twp::kSelectActor5, "5"},
+		{"SELECTACTOR6", _s("Select Actor 6"), Twp::kSelectActor6, "6"},
+		{"SELECTCHOICE1", _s("Select Choice 1"), Twp::kSelectChoice1, "1"},
+		{"SELECTCHOICE2", _s("Select Choice 2"), Twp::kSelectChoice2, "2"},
+		{"SELECTCHOICE3", _s("Select Choice 3"), Twp::kSelectChoice3, "3"},
+		{"SELECTCHOICE4", _s("Select Choice 4"), Twp::kSelectChoice4, "4"},
+		{"SELECTCHOICE5", _s("Select Choice 5"), Twp::kSelectChoice5, "5"},
+		{"SELECTCHOICE6", _s("Select Choice 6"), Twp::kSelectChoice6, "6"},
+		{"SELECTNEXTACTOR", _s("Select Next Actor"), Twp::kSelectNextActor, "0"},
+		{"SELECTPREVACTOR", _s("Select Previous Actor"), Twp::kSelectPreviousActor, "9"},
+		{"SKIPTEXT", _s("Skip Text"), Twp::kSkipText, "."},
+		{"SHOWHOTSPOTS", _s("Show hotspots"), Twp::kShowHotspots, "TAB"},
+		{0, 0, Twp::kSkipCutscene, 0},
 	};
 
 	Common::Action *act;
-	for (int i = 0; i < ARRAYSIZE(actions); i++) {
-		act = new Common::Action(actions[i].name.c_str(), actions[i].desc);
+	for (int i = 0; actions[i].name; i++) {
+		act = new Common::Action(actions[i].name, _(actions[i].desc));
 		act->setCustomEngineActionEvent(actions[i].action);
 		act->addDefaultInputMapping(actions[i].input);
 		engineKeyMap->addAction(act);


Commit: 3ce8f220d10a72529ea2e5de7477e27f60127917
    https://github.com/scummvm/scummvm/commit/3ce8f220d10a72529ea2e5de7477e27f60127917
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix some warnings in clipper/squirrel and imgui

Changed paths:
    engines/twp/clipper/clipper.cpp
    engines/twp/imgui_impl_sdl2_scummvm.cpp
    engines/twp/squirrel/sqfuncproto.h


diff --git a/engines/twp/clipper/clipper.cpp b/engines/twp/clipper/clipper.cpp
index fffc3bc8644..ff04afa9d3a 100644
--- a/engines/twp/clipper/clipper.cpp
+++ b/engines/twp/clipper/clipper.cpp
@@ -2519,8 +2519,8 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) {
 	if (!eLastHorz->NextInLML)
 		eMaxPair = GetMaximaPair(eLastHorz);
 
-	MaximaList::const_iterator maxIt;
-	MaximaList::const_iterator maxRit;
+	MaximaList::const_iterator maxIt = nullptr;
+	MaximaList::const_iterator maxRit = nullptr;
 	if (m_Maxima.size() > 0) {
 		// get the first maxima in range (X) ...
 		if (dir == dLeftToRight) {
diff --git a/engines/twp/imgui_impl_sdl2_scummvm.cpp b/engines/twp/imgui_impl_sdl2_scummvm.cpp
index fd225eaf8fd..68a2c82a844 100644
--- a/engines/twp/imgui_impl_sdl2_scummvm.cpp
+++ b/engines/twp/imgui_impl_sdl2_scummvm.cpp
@@ -481,6 +481,8 @@ bool ImGui_ImplSDL2_ProcessEvent(const Common::Event *event) {
 	case Common::EVENT_FOCUS_LOST:
 		io.AddFocusEvent(false);
 		return true;
+	default:
+		return false;
 	}
 	return false;
 }
diff --git a/engines/twp/squirrel/sqfuncproto.h b/engines/twp/squirrel/sqfuncproto.h
index 546dbabb552..d5883454469 100755
--- a/engines/twp/squirrel/sqfuncproto.h
+++ b/engines/twp/squirrel/sqfuncproto.h
@@ -19,12 +19,6 @@ struct SQOuterVar
         _src=src;
         _type=t;
     }
-    SQOuterVar(const SQOuterVar &ov)
-    {
-        _type=ov._type;
-        _src=ov._src;
-        _name=ov._name;
-    }
     SQOuterType _type;
     SQObjectPtr _name;
     SQObjectPtr _src;
@@ -33,13 +27,6 @@ struct SQOuterVar
 struct SQLocalVarInfo
 {
     SQLocalVarInfo():_start_op(0),_end_op(0),_pos(0){}
-    SQLocalVarInfo(const SQLocalVarInfo &lvi)
-    {
-        _name=lvi._name;
-        _start_op=lvi._start_op;
-        _end_op=lvi._end_op;
-        _pos=lvi._pos;
-    }
     SQObjectPtr _name;
     SQUnsignedInteger _start_op;
     SQUnsignedInteger _end_op;


Commit: 9fb8431d87ebf1ad19c49ecdb77fbd835fe8bb95
    https://github.com/scummvm/scummvm/commit/9fb8431d87ebf1ad19c49ecdb77fbd835fe8bb95
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove imgui.ini

Changed paths:
  R imgui.ini
    .gitignore


diff --git a/.gitignore b/.gitignore
index 42f087cbea5..9d3518bed4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -299,3 +299,6 @@ dists/emscripten/emsdk-*
 #Ignore Atari/FreeMiNT files
 scummvm.gtp
 scummvm.ttp
+
+#Ignore Dear imgui file
+imgui.ini
diff --git a/imgui.ini b/imgui.ini
deleted file mode 100644
index 46cf3e5bb95..00000000000
--- a/imgui.ini
+++ /dev/null
@@ -1,27 +0,0 @@
-[Window][Debug##Default]
-Pos=60,60
-Size=400,400
-
-[Window][General]
-Pos=81,33
-Size=419,482
-Collapsed=1
-
-[Window][Objects]
-Pos=60,60
-Size=520,600
-
-[Window][Sounds]
-Pos=114,67
-Size=520,600
-
-[Table][0xBFA6B9D2,7]
-RefScale=13
-Column 0  Width=21
-Column 1  Width=42
-Column 2  Width=56
-Column 3  Width=119
-Column 4  Width=35
-Column 5  Width=42
-Column 6  Width=72
-


Commit: 959365ae48ee12783ecc5187581f535b7ac54597
    https://github.com/scummvm/scummvm/commit/959365ae48ee12783ecc5187581f535b7ac54597
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove unused replace method

Changed paths:
    engines/twp/util.h


diff --git a/engines/twp/util.h b/engines/twp/util.h
index caa2020bf2b..9607c5a3cb6 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -119,7 +119,6 @@ size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T* o) {
 
 // string util
 Common::String join(const Common::Array<Common::String> &array, const Common::String &sep);
-Common::String replace(const Common::String &s, const Common::String &what, const Common::String &by);
 Common::String remove(const Common::String &txt, char startC, char endC);
 
 // math util


Commit: de8939ff58ff769c561959990451ffe6d0244e39
    https://github.com/scummvm/scummvm/commit/de8939ff58ff769c561959990451ffe6d0244e39
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix nArgs uninitilized in squirrel

Changed paths:
    engines/twp/squirrel/sqbaselib.cpp


diff --git a/engines/twp/squirrel/sqbaselib.cpp b/engines/twp/squirrel/sqbaselib.cpp
index 1461513c3a9..f9d69b3e90d 100755
--- a/engines/twp/squirrel/sqbaselib.cpp
+++ b/engines/twp/squirrel/sqbaselib.cpp
@@ -609,7 +609,7 @@ static SQInteger __map_array(SQArray *dest,SQArray *src,HSQUIRRELVM v) {
     SQObject &closure = stack_get(v, 2);
     v->Push(closure);
 
-    SQInteger nArgs;
+    SQInteger nArgs = 0;
     if(sq_type(closure) == OT_CLOSURE) {
         nArgs = _closure(closure)->_function->_nparameters;
     }


Commit: d25814db2a9a64a429d7de53cac4bbefd7abd8a4
    https://github.com/scummvm/scummvm/commit/d25814db2a9a64a429d7de53cac4bbefd7abd8a4
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crash with nodes

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/room.cpp
    engines/twp/savegame.cpp
    engines/twp/squtil.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 872c736f3c2..c1319e8bc43 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -792,6 +792,8 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	actor->_key = key;
 
 	debugC(kDebugActScript, "Create actor %s %d", key.c_str(), actor->getId());
+	actor->_nodeAnim->remove();
+	actor->_node->remove();
 	actor->_node = Common::SharedPtr<Node>(new ActorNode(actor));
 	actor->_nodeAnim = Common::SharedPtr<Anim>(new Anim(actor.get()));
 	actor->_node->addChild(actor->_nodeAnim.get());
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 3157d665d84..00c8227ba8f 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -309,12 +309,8 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 			Common::SharedPtr<Object> obj(new Object());
 			Twp::setId(obj->_table, newObjId());
 			obj->_key = jObject["name"]->asString();
-			Common::SharedPtr<Node> objNode(new Node(obj->_key));
-			objNode->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
-			objNode->setZSort(jObject["zsort"]->asIntegerNumber());
-			obj->_node = objNode;
-			obj->_nodeAnim.reset(new Anim(obj.get()));
-			obj->_node->addChild(obj->_nodeAnim.get());
+			obj->_node->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
+			obj->_node->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());
 			if (jObject.contains("usedir")) {
 				obj->_useDir = parseUseDir(jObject["usedir"]->asString());
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 32c266b72be..086b26c7e34 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -138,7 +138,7 @@ static void toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
 		sqpush(v, json->asString());
 		sqget(v, -1, obj);
 	} else if (json->isIntegerNumber()) {
-		sqpush(v, json->asIntegerNumber());
+		sqpush(v, static_cast<int>(json->asIntegerNumber()));
 		sqget(v, -1, obj);
 	} else if (json->isBool()) {
 		sqpush(v, json->asBool());
@@ -414,7 +414,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 		g_twp->cameraAt(g_twp->_actor->_node->getPos());
 
 	HSQUIRRELVM v = g_twp->getVm();
-	sqsetf(sqrootTbl(v), "SAVEBUILD", json["savebuild"]->asIntegerNumber());
+	sqsetf(sqrootTbl(v), "SAVEBUILD", static_cast<int>(json["savebuild"]->asIntegerNumber()));
 
 	sqcall("postLoad");
 
@@ -621,7 +621,7 @@ private:
 static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj, bool pseudo) {
 	switch (obj._type) {
 	case OT_INTEGER:
-		return new Common::JSONValue(sq_objtointeger(&obj));
+		return new Common::JSONValue(static_cast<long long>(sq_objtointeger(&obj)));
 	case OT_FLOAT:
 		return new Common::JSONValue(sq_objtofloat(&obj));
 	case OT_STRING:
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 6235b2fbe68..fa542138d29 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -33,6 +33,10 @@
 
 namespace Twp {
 
+SQInteger sqpush(HSQUIRRELVM v) {
+	return 0;
+}
+
 template<>
 SQInteger sqpush(HSQUIRRELVM v, int value) {
 	sq_pushinteger(v, value);


Commit: fd1cdb9f126f382f04d64a112ba1afbf6c599c3c
    https://github.com/scummvm/scummvm/commit/fd1cdb9f126f382f04d64a112ba1afbf6c599c3c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix crashes with font and thumbnail

Changed paths:
    engines/twp/font.cpp
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/twp.cpp


diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 22ac40ac3c8..b8d88438773 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -300,7 +300,7 @@ void Text::update() {
 		float y = -lineHeight;
 		for (size_t i = 0; i < lines.size(); i++) {
 			Line &line = lines[i];
-			CodePoint prevChar;
+			CodePoint prevChar = 0;
 			x = 0;
 			for (size_t j = 0; j < line.tokens.size(); j++) {
 				tok = line.tokens[j];
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index a2f31df25dc..369e80eb964 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -85,20 +85,18 @@ void Texture::bind(const Texture *texture) {
 	}
 }
 
-void Texture::capture(Graphics::Surface &surface) {
-	Common::Array<byte> pixels(width * height * 4);
+void Texture::capture(Common::Array<byte> &data) {
+	data.resize(width * height * 4);
 	GLint boundFrameBuffer;
 
 	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &boundFrameBuffer);
 	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
 	}
-	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
 	if (boundFrameBuffer != (int)fbo) {
 		glBindFramebuffer(GL_FRAMEBUFFER, boundFrameBuffer);
 	}
-	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
-	surface.init(width, height, 4 * width, pixels.data(), fmt);
 }
 
 RenderTexture::RenderTexture(Math::Vector2d size) {
@@ -200,13 +198,13 @@ void Gfx::init() {
 	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo));
 
 	const char *fragmentSrc = R"(
-	varying vec4 v_color;
-	varying vec2 v_texCoords;
-	uniform sampler2D u_texture;
-	void main() {
-		vec4 tex_color = texture2D(u_texture, v_texCoords);
-		gl_FragColor = v_color * tex_color;
-	})";
+		varying vec4 v_color;
+		varying vec2 v_texCoords;
+		uniform sampler2D u_texture;
+		void main() {
+			vec4 tex_color = texture2D(u_texture, v_texCoords);
+			gl_FragColor = v_color * tex_color;
+		})";
 	_defaultShader.init("default", vsrc, fragmentSrc);
 	_shader = &_defaultShader;
 	_mvp = ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 269295ba421..c2b64202eca 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -72,16 +72,16 @@ struct Color {
 		return Common::String::format("rgba(%f,%f,%f,%f)", rgba.r, rgba.g, rgba.b, rgba.a);
 	}
 
-	Color operator-(const Color& c) {
+	Color operator-(const Color &c) {
 		return Color(rgba.r - c.rgba.r, rgba.g - c.rgba.g, rgba.b - c.rgba.b, rgba.a - c.rgba.a);
 	}
 
-	Color operator+(const Color& c) {
+	Color operator+(const Color &c) {
 		return Color(rgba.r + c.rgba.r, rgba.g + c.rgba.g, rgba.b + c.rgba.b, rgba.a + c.rgba.a);
 	}
 
 	Color operator*(float f) {
-  		return Color(rgba.r * f, rgba.g * f, rgba.b * f, rgba.a * f);
+		return Color(rgba.r * f, rgba.g * f, rgba.b * f, rgba.a * f);
 	}
 
 	int toInt() const;
@@ -102,20 +102,21 @@ class Texture {
 public:
 	Texture() {}
 	virtual ~Texture() {}
+
 	void load(const Graphics::Surface &surface);
 	static void bind(const Texture *texture);
-	void capture(Graphics::Surface &surface);
+	void capture(Common::Array<byte> &data);
 
 public:
-	uint32 id;
-	int width, height;
-	uint fbo;
+	uint32 id = 0;
+	int width = 0, height = 0;
+	uint fbo = 0;
 };
 
 class RenderTexture : public Texture {
 public:
-	RenderTexture(Math::Vector2d size);
-	virtual ~RenderTexture() override;
+	explicit RenderTexture(Math::Vector2d size);
+	~RenderTexture() override;
 };
 
 struct TextureSlot {
@@ -125,7 +126,8 @@ struct TextureSlot {
 
 class Shader {
 public:
-friend class Gfx;
+	friend class Gfx;
+
 public:
 	Shader();
 	virtual ~Shader();
@@ -134,20 +136,20 @@ public:
 
 	int getUniformLocation(const char *name) const;
 
-	void setUniform(const char * name, int value);
-	void setUniform(const char * name, float value);
-	void setUniform(const char * name, float* value, size_t count);
-	void setUniform2(const char * name, float* value, size_t count);
-	void setUniform3(const char * name, float* value, size_t count);
+	void setUniform(const char *name, int value);
+	void setUniform(const char *name, float value);
+	void setUniform(const char *name, float *value, size_t count);
+	void setUniform2(const char *name, float *value, size_t count);
+	void setUniform3(const char *name, float *value, size_t count);
 
-	void setUniform(const char * name, Math::Vector2d value);
-	void setUniform3(const char * name, Color value);
-	void setUniform4(const char * name, Color value);
+	void setUniform(const char *name, Math::Vector2d value);
+	void setUniform3(const char *name, Color value);
+	void setUniform4(const char *name, Color value);
 
 	virtual void applyUniforms() {}
-	virtual int getNumTextures() { return 0;};
-	virtual int getTexture(int index) { return 0;};
-	virtual int getTextureLoc(int index) { return 0;};
+	virtual int getNumTextures() { return 0; };
+	virtual int getTexture(int index) { return 0; };
+	virtual int getTextureLoc(int index) { return 0; };
 
 private:
 	Common::HashMap<int, TextureSlot> _textures;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 4022a9ffa59..5529c7b7e44 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -24,10 +24,10 @@
 #endif
 
 #ifdef USE_IMGUI
-	#include "imgui/imgui.h"
-	#include "imgui_impl_sdl2_scummvm.h"
-	#include "imgui_impl_opengl3_scummvm.h"
-	#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+#include "imgui/imgui.h"
+#include "imgui_impl_sdl2_scummvm.h"
+#include "imgui_impl_opengl3_scummvm.h"
+#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
 #endif
 
 #include "common/config-manager.h"
@@ -52,7 +52,7 @@ namespace Twp {
 TwpEngine *g_twp;
 
 #ifdef USE_IMGUI
-	SDL_Window *g_window = nullptr;
+SDL_Window *g_window = nullptr;
 #endif
 
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
@@ -250,7 +250,7 @@ Verb TwpEngine::verb() {
 	Verb result = _hud._verb;
 	if (result.id.id == VERB_WALKTO && _noun1 && _noun1->inInventory())
 		result = *_hud.actorSlot(_actor)->getVerb(_noun1->defaultVerbId());
-	else if(_actor) {
+	else if (_actor) {
 		result = *_hud.actorSlot(_actor)->getVerb(_hud._verb.id.id);
 	}
 	return result;
@@ -322,7 +322,7 @@ Common::SharedPtr<Object> inventoryAt(Math::Vector2d pos) {
 }
 
 static void selectSlotActor(int id) {
-	if(g_twp->_actorSwitcher._mode == asOn) {
+	if (g_twp->_actorSwitcher._mode == asOn) {
 		for (size_t i = 0; i < g_twp->_actors.size(); i++) {
 			if (g_twp->_actors[i]->getId() == id) {
 				g_twp->setActor(g_twp->_actors[i]);
@@ -799,7 +799,7 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectActor4:
 				case TwpAction::kSelectActor5:
 				case TwpAction::kSelectActor6:
-					if(g_twp->_actorSwitcher._mode == asOn) {
+					if (g_twp->_actorSwitcher._mode == asOn) {
 						int index = (TwpAction)e.customType - kSelectActor1;
 						ActorSlot *slot = &_hud._actorSlots[index];
 						if (slot->selectable && slot->actor && (slot->actor->_room->_name != "Void")) {
@@ -1251,7 +1251,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 					_room->_scalingTriggers.push_back(ScalingTrigger(obj, scaling));
 				}
 			}
-			if(isActor(obj->getId())) {
+			if (isActor(obj->getId())) {
 				actorEnter(obj);
 			} else if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
@@ -1277,8 +1277,9 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 }
 
 void TwpEngine::actorEnter(Common::SharedPtr<Object> actor) {
-	if(!actor) return;
-	if(sqrawexists(g_twp->_room->_table, "actorEnter")) {
+	if (!actor)
+		return;
+	if (sqrawexists(g_twp->_room->_table, "actorEnter")) {
 		sqcall(g_twp->_room->_table, "actorEnter", actor->_table);
 	} else {
 		sqcall("actorEnter", actor->_table);
@@ -1297,7 +1298,7 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Common::SharedPtr<Object> obj = layer->_objects[j];
 				obj->stopObjectMotors();
-				if(isActor(obj->getId())) {
+				if (isActor(obj->getId())) {
 					actorExit(obj);
 				}
 			}
@@ -1432,7 +1433,7 @@ struct GetByZOrder {
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
-			if(!isActor(obj->getId()) || !obj->_key.empty()) {
+			if (!isActor(obj->getId()) || !obj->_key.empty()) {
 				_result = obj;
 				_zOrder = obj->_node->getZSort();
 			}
@@ -1664,7 +1665,8 @@ void TwpEngine::updateTriggers() {
 }
 
 void TwpEngine::stopTalking() {
-	if(!_room) return;
+	if (!_room)
+		return;
 	for (auto it = _room->_layers.begin(); it != _room->_layers.end(); it++) {
 		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
@@ -1703,14 +1705,22 @@ Scaling *TwpEngine::getScaling(const Common::String &name) {
 }
 
 void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
+	// render scene into texture
+	Common::Array<byte> data;
 	RenderTexture rt(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	draw(&rt);
 
+	// get the data from it
+	rt.capture(data);
+
+	// flip it (due to opengl) and scale it to the desired size
+	Graphics::PixelFormat fmt(4, 8, 8, 8, 8, 0, 8, 16, 24);
 	Graphics::Surface s;
-	rt.capture(s);
+	s.init(SCREEN_WIDTH, SCREEN_HEIGHT, 4 * SCREEN_WIDTH, data.data(), fmt);
 	s.flipVertical(Common::Rect(s.w, s.h));
 	s.scale(size.getX(), size.getY());
 
+	// and save to stream
 	Image::writePNG(stream, s);
 }
 


Commit: a943ad0a2782431c3a0864005679ca43fc731efb
    https://github.com/scummvm/scummvm/commit/a943ad0a2782431c3a0864005679ca43fc731efb
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix formatting, includes order and this

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/actorswitcher.h
    engines/twp/audio.cpp
    engines/twp/audio.h
    engines/twp/callback.h
    engines/twp/camera.h
    engines/twp/clipper/clipper.cpp
    engines/twp/console.cpp
    engines/twp/console.h
    engines/twp/debugtools.cpp
    engines/twp/debugtools.h
    engines/twp/detection_tables.h
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/font.cpp
    engines/twp/font.h
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/gfx.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/ids.cpp
    engines/twp/ids.h
    engines/twp/imgui_impl_opengl3_scummvm.cpp
    engines/twp/imgui_impl_opengl3_scummvm.h
    engines/twp/imgui_impl_sdl2_scummvm.cpp
    engines/twp/imgui_impl_sdl2_scummvm.h
    engines/twp/lighting.cpp
    engines/twp/lighting.h
    engines/twp/lip.h
    engines/twp/metaengine.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/objectanimation.h
    engines/twp/objlib.cpp
    engines/twp/prefs.cpp
    engines/twp/prefs.h
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/scenegraph.h
    engines/twp/shaders.cpp
    engines/twp/shaders.h
    engines/twp/soundlib.cpp
    engines/twp/spritesheet.cpp
    engines/twp/spritesheet.h
    engines/twp/sqgame.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/thread.h
    engines/twp/tsv.h
    engines/twp/twp.cpp
    engines/twp/util.h
    engines/twp/vm.cpp
    engines/twp/yack.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index c1319e8bc43..c17bc6344d9 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/sqgame.h"
 #include "twp/squtil.h"
 
 namespace Twp {
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index 93df01a8ca6..4e8dfc66d96 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/actorswitcher.h"
 #include "twp/twp.h"
+#include "twp/actorswitcher.h"
 
 #define DISABLE_ALPHA 0.f
 #define ENABLE_ALPHA 1.f
@@ -33,10 +33,10 @@ namespace Twp {
 
 ActorSwitcherSlot::ActorSwitcherSlot(const Common::String &icon_, Color back_, Color frame_, SelectFunc *selectFunc_, int id_) {
 	icon = icon_;
-	this->back = back_;
-	this->frame = frame_;
-	this->selectFunc = selectFunc_;
-	this->id = id_;
+	back = back_;
+	frame = frame_;
+	selectFunc = selectFunc_;
+	id = id_;
 }
 
 void ActorSwitcherSlot::select() {
diff --git a/engines/twp/actorswitcher.h b/engines/twp/actorswitcher.h
index 3d6a8a2a441..cf5f28cd1c4 100644
--- a/engines/twp/actorswitcher.h
+++ b/engines/twp/actorswitcher.h
@@ -35,13 +35,13 @@ typedef void SelectFunc(int id);
 
 // This is where all the information about the actor icon stands
 struct ActorSwitcherSlot {
-	ActorSwitcherSlot(const Common::String& icon, Color back, Color frame, SelectFunc* selectFunc, int id = 0);
+	ActorSwitcherSlot(const Common::String &icon_, Color back_, Color frame_, SelectFunc *selectFunc_, int id_ = 0);
 
 	void select();
 
 	Common::String icon;
 	Color back, frame;
-	SelectFunc* selectFunc;
+	SelectFunc *selectFunc;
 	int id;
 };
 
@@ -50,7 +50,7 @@ class ActorSwitcher : public Node {
 public:
 	ActorSwitcher();
 
-	void update(const Common::Array<ActorSwitcherSlot>& slots, float elapsed);
+	void update(const Common::Array<ActorSwitcherSlot> &slots, float elapsed);
 	bool isMouseOver() const { return _mouseOver; }
 	void setFlash(int flash) { _flash = flash; }
 
@@ -65,7 +65,7 @@ protected:
 	Common::Rect rect() const;
 
 public:
-	int _mode = asNone;        // current mode
+	int _mode = asNone; // current mode
 
 private:
 	bool _mouseOver = false;                  // true when mouse is over the icons
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index f2753cb34e6..dce9ee6afa6 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -25,8 +25,8 @@
 #include "audio/audiostream.h"
 #include "audio/decoders/vorbis.h"
 #include "audio/decoders/wave.h"
-#include "twp/audio.h"
 #include "twp/twp.h"
+#include "twp/audio.h"
 #include "twp/squtil.h"
 
 namespace Twp {
diff --git a/engines/twp/audio.h b/engines/twp/audio.h
index b1dd4de42eb..a9bc14f00be 100644
--- a/engines/twp/audio.h
+++ b/engines/twp/audio.h
@@ -77,13 +77,13 @@ private:
 };
 
 struct AudioSlot {
-	Audio::SoundHandle handle;                           // handle returned when this sound has been played
+	Audio::SoundHandle handle;                 // handle returned when this sound has been played
 	Common::SharedPtr<SoundDefinition> sndDef; // sound definition associated to this slot
-	SoundStream stream;                                  // audio stream
-	bool busy = false;                                   // is sound active
-	float volume = 1.f;                                  // actual volume for this slot
-	float fadeInTimeMs = 0.f;                            // fade-in time in milliseconds
-	float fadeOutTimeMs = 0.f;                           // fade-out time in milliseconds
+	SoundStream stream;                        // audio stream
+	bool busy = false;                         // is sound active
+	float volume = 1.f;                        // actual volume for this slot
+	float fadeInTimeMs = 0.f;                  // fade-in time in milliseconds
+	float fadeOutTimeMs = 0.f;                 // fade-out time in milliseconds
 	int total = 0;
 	int id = 0;                        // unique sound ID
 	int objId = 0;                     // object ID or 0 if none
diff --git a/engines/twp/callback.h b/engines/twp/callback.h
index a51ea0a1da4..e2869da31e0 100644
--- a/engines/twp/callback.h
+++ b/engines/twp/callback.h
@@ -30,7 +30,7 @@ namespace Twp {
 
 class Callback {
 public:
-	Callback(int id, float duration, const Common::String& name, const Common::Array<HSQOBJECT>& args);
+	Callback(int id, float duration, const Common::String &name, const Common::Array<HSQOBJECT> &args);
 
 	bool update(float elapsed);
 	void remove();
@@ -39,7 +39,7 @@ public:
 	int getId() const { return _id; }
 	float getDuration() const { return _duration; }
 	float getElapsed() const { return _elapsed; }
-	const Common::Array<HSQOBJECT>& getArgs() const { return _args; }
+	const Common::Array<HSQOBJECT> &getArgs() const { return _args; }
 
 private:
 	int _id = 0;
diff --git a/engines/twp/camera.h b/engines/twp/camera.h
index e6008c64387..bb80bd44053 100644
--- a/engines/twp/camera.h
+++ b/engines/twp/camera.h
@@ -113,7 +113,7 @@ private:
 
 private:
 	Math::Vector2d _pos;
-	Rectf _bounds = Rectf::fromMinMax(Math::Vector2d(-10000,-10000), Math::Vector2d(10000,10000));
+	Rectf _bounds = Rectf::fromMinMax(Math::Vector2d(-10000, -10000), Math::Vector2d(10000, 10000));
 	bool _moving = false;
 	Math::Vector2d _init, _target;
 	float _elapsed = 0.f;
diff --git a/engines/twp/clipper/clipper.cpp b/engines/twp/clipper/clipper.cpp
index ff04afa9d3a..92a10efd33d 100644
--- a/engines/twp/clipper/clipper.cpp
+++ b/engines/twp/clipper/clipper.cpp
@@ -3569,8 +3569,8 @@ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
 //------------------------------------------------------------------------------
 
 ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
-	this->MiterLimit = miterLimit;
-	this->ArcTolerance = arcTolerance;
+	MiterLimit = miterLimit;
+	ArcTolerance = arcTolerance;
 	m_lowest.X = -1;
 }
 //------------------------------------------------------------------------------
diff --git a/engines/twp/console.cpp b/engines/twp/console.cpp
index c713dee2ac5..3faa503989c 100644
--- a/engines/twp/console.cpp
+++ b/engines/twp/console.cpp
@@ -19,14 +19,14 @@
  *
  */
 
-#include "twp/console.h"
 #include "twp/twp.h"
+#include "twp/console.h"
 #include "twp/squtil.h"
 
 namespace Twp {
 
 Console::Console() : GUI::Debugger() {
-	registerCmd("!",   WRAP_METHOD(Console, Cmd_exec));
+	registerCmd("!", WRAP_METHOD(Console, Cmd_exec));
 }
 
 Console::~Console() = default;
diff --git a/engines/twp/console.h b/engines/twp/console.h
index 9d28696f0a5..9b6d2bda4ae 100644
--- a/engines/twp/console.h
+++ b/engines/twp/console.h
@@ -30,6 +30,7 @@ namespace Twp {
 class Console : public GUI::Debugger {
 private:
 	bool Cmd_exec(int argc, const char **argv);
+
 public:
 	Console();
 	~Console() override;
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 2e965e158e0..d6797e2a7e2 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -19,13 +19,13 @@
  *
  */
 
-#include "twp/debugtools.h"
+#include "common/debug-channels.h"
 #include "imgui/imgui.h"
 #include "twp/twp.h"
+#include "twp/debugtools.h"
 #include "twp/thread.h"
 #include "twp/lighting.h"
 #include "twp/squtil.h"
-#include "common/debug-channels.h"
 
 namespace Twp {
 
diff --git a/engines/twp/debugtools.h b/engines/twp/debugtools.h
index 1a79a77e3d6..2cb5cdc647f 100644
--- a/engines/twp/debugtools.h
+++ b/engines/twp/debugtools.h
@@ -23,7 +23,7 @@
 #define TWP_DEBUGTOOLS_H
 
 namespace Twp {
-	void onImGuiRender();
+void onImGuiRender();
 }
 
 #endif
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index 436409e8ed3..974c924da76 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -22,9 +22,8 @@
 namespace Twp {
 
 const PlainGameDescriptor twpGames[] = {
-	{ "twp", "Thimbleweed Park" },
-	{ 0, 0 }
-};
+	{"twp", "Thimbleweed Park"},
+	{0, 0}};
 
 const ADGameDescription gameDescriptions[] = {
 	// Thimbleweed Park - GOG version
@@ -35,8 +34,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::UNK_LANG,
 		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOMIDI)
-	},
+		GUIO1(GUIO_NOMIDI)},
 	// Thimbleweed Park - EPIC Games version
 	{
 		"twp",
@@ -45,10 +43,8 @@ const ADGameDescription gameDescriptions[] = {
 		Common::UNK_LANG,
 		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOMIDI)
-	},
+		GUIO1(GUIO_NOMIDI)},
 
-	AD_TABLE_END_MARKER
-};
+	AD_TABLE_END_MARKER};
 
 } // End of namespace Twp
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 7730bbfc3e7..bf2c47b07fd 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -72,8 +72,8 @@ DialogConditionState CondStateVisitor::createState(int line, DialogConditionMode
 
 DialogConditionState::DialogConditionState() = default;
 
-DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String& k, const Common::String& dlg, int ln)
-: mode(m), actorKey(k), dialog(dlg), line(ln) {
+DialogConditionState::DialogConditionState(DialogConditionMode m, const Common::String &k, const Common::String &dlg, int ln)
+	: mode(m), actorKey(k), dialog(dlg), line(ln) {
 }
 
 void CondStateVisitor::visit(const YOnce &node) {
@@ -266,7 +266,7 @@ void Dialog::update(float dt) {
 }
 
 bool Dialog::isOnce(int line) const {
-	for (const auto & state : _states) {
+	for (const auto &state : _states) {
 		if (state.mode == Once && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isOnce %d: false", line);
 			return false;
@@ -277,7 +277,7 @@ bool Dialog::isOnce(int line) const {
 }
 
 bool Dialog::isShowOnce(int line) const {
-	for (const auto & state : _states) {
+	for (const auto &state : _states) {
 		if (state.mode == ShowOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isShowOnce %d: false", line);
 			return false;
@@ -288,7 +288,7 @@ bool Dialog::isShowOnce(int line) const {
 }
 
 bool Dialog::isOnceEver(int line) const {
-	for (const auto & state : _states) {
+	for (const auto &state : _states) {
 		if (state.mode == OnceEver && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isOnceEver %d: false", line);
 			return false;
@@ -299,7 +299,7 @@ bool Dialog::isOnceEver(int line) const {
 }
 
 bool Dialog::isTempOnce(int line) const {
-	for (const auto & state : _states) {
+	for (const auto &state : _states) {
 		if (state.mode == TempOnce && state.actorKey == _context.actor && state.dialog == _context.dialogName && state.line == line) {
 			debugC(kDebugDialog, "isTempOnce %d: false", line);
 			return false;
@@ -340,7 +340,7 @@ void Dialog::selectLabel(int line, const Common::String &name) {
 
 void Dialog::gotoNextLabel() {
 	if (_lbl) {
-        size_t i = Twp::find(_cu->_labels, _lbl);
+		size_t i = Twp::find(_cu->_labels, _lbl);
 		if ((i != (size_t)-1) && (i != _cu->_labels.size() - 1)) {
 			Common::SharedPtr<YLabel> label = _cu->_labels[i + 1];
 			selectLabel(label->_line, label->_name);
@@ -352,7 +352,7 @@ void Dialog::gotoNextLabel() {
 
 void Dialog::updateChoiceStates() {
 	_state = WaitingForChoice;
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		DialogSlot *slot = &_slot;
 		if (slot->_isValid) {
 			for (auto cond : slot->_stmt->_conds) {
@@ -443,7 +443,7 @@ void Dialog::addSlot(Common::SharedPtr<YStatement> stmt) {
 
 int Dialog::numSlots() const {
 	int num = 0;
-	for (const auto & _slot : _slots) {
+	for (const auto &_slot : _slots) {
 		if (_slot._isValid)
 			num++;
 	}
@@ -451,13 +451,13 @@ int Dialog::numSlots() const {
 }
 
 void Dialog::clearSlots() {
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		_slot._isValid = false;
 	}
 }
 
 void Dialog::drawCore(Math::Matrix4 trsf) {
-	for (auto & _slot : _slots) {
+	for (auto &_slot : _slots) {
 		DialogSlot *slot = &_slot;
 		if (slot->_isValid) {
 			Math::Matrix4 t(trsf);
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index cb99d4e600b..1dd8f841963 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -80,8 +80,8 @@ struct DialogConditionState {
 	Common::String actorKey, dialog;
 	int line{};
 
-    DialogConditionState();
-	DialogConditionState(DialogConditionMode mode, const Common::String& actorKey, const Common::String& dialog, int line);
+	DialogConditionState();
+	DialogConditionState(DialogConditionMode mode, const Common::String &actorKey, const Common::String &dialog, int line);
 };
 
 class DialogTarget {
@@ -193,7 +193,7 @@ public:
 	bool isCond(const Common::String &cond) const;
 
 private:
-	void choose(DialogSlot* slot);
+	void choose(DialogSlot *slot);
 	Common::SharedPtr<YLabel> label(int line, const Common::String &name) const;
 	void gotoNextLabel();
 	bool choicesReady() const { return numSlots() > 0; }
@@ -216,7 +216,7 @@ public:
 
 private:
 	DialogState _state = DialogState::None;
-    size_t _currentStatement = 0;
+	size_t _currentStatement = 0;
 	unique_ptr<YCompilationUnit> _cu;
 	Common::SharedPtr<YLabel> _lbl;
 	DialogSlot _slots[MAXDIALOGSLOTS];
diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index e4b2a5da881..9c2ed7c46f4 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -20,12 +20,10 @@
  */
 
 #include "common/translation.h"
-
 #include "gui/gui-manager.h"
 #include "gui/widgets/edittext.h"
 #include "gui/widgets/popup.h"
 #include "gui/ThemeEval.h"
-
 #include "twp/dialogs.h"
 
 namespace Twp {
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index f73cd752d18..ae9c3867c46 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -51,6 +51,6 @@ private:
 	GUI::PopUpWidget *_langGUIDropdown = nullptr;
 };
 
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index c152997ced2..ebe574cbfc0 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/enginedialogtarget.h"
 #include "twp/twp.h"
+#include "twp/enginedialogtarget.h"
 
 namespace Twp {
 
@@ -40,15 +40,15 @@ private:
 
 class WaitWhile : public Motor {
 public:
-	WaitWhile(EngineDialogTarget* target, const Common::String& cond) : _target(target), _cond(cond) {}
+	WaitWhile(EngineDialogTarget *target, const Common::String &cond) : _target(target), _cond(cond) {}
 
 	void update(float elapsed) override {
 		if (!_target->execCond(_cond))
-    		disable();
+			disable();
 	}
 
 private:
-	EngineDialogTarget* _target = nullptr;
+	EngineDialogTarget *_target = nullptr;
 	Common::String _cond;
 };
 
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index b8d88438773..741b5c20bf5 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/font.h"
 #include "twp/twp.h"
+#include "twp/font.h"
 
 namespace Twp {
 
@@ -61,7 +61,7 @@ private:
 
 private:
 	Common::U32String _text;
-    size_t _off;
+	size_t _off;
 };
 
 static Math::Vector2d normalize(Texture *texture, Math::Vector2d v) {
@@ -90,7 +90,7 @@ static void addGlyphQuad(Texture *texture, Common::Array<Vertex> &vertices, Char
 
 // Skips all characters while one char from the set `token` is found.
 // Returns number of characters skipped.
-static int skipWhile(const Common::U32String& s, const char *toSkip, int start = 0) {
+static int skipWhile(const Common::U32String &s, const char *toSkip, int start = 0) {
 	int result = 0;
 	int len = s.size();
 	while ((start + result < len) && strchr(toSkip, s[result + start]))
@@ -98,7 +98,7 @@ static int skipWhile(const Common::U32String& s, const char *toSkip, int start =
 	return result;
 }
 
-static int skipUntil(const Common::U32String& s, const char *until, int start = 0) {
+static int skipUntil(const Common::U32String &s, const char *until, int start = 0) {
 	int result = 0;
 	int len = s.size();
 	while ((start + result < len) && !strchr(until, s[result + start]))
diff --git a/engines/twp/font.h b/engines/twp/font.h
index 6e701195008..71a3a82eee2 100644
--- a/engines/twp/font.h
+++ b/engines/twp/font.h
@@ -22,17 +22,18 @@
 #ifndef TWP_FONT_H
 #define TWP_FONT_H
 
-#include "twp/spritesheet.h"
-#include "twp/gfx.h"
 #include "common/rect.h"
 #include "common/hashmap.h"
 #include "common/str.h"
+#include "twp/spritesheet.h"
+#include "twp/gfx.h"
 
 namespace Common {
-template<> struct Hash<Common::u32char_type_t> : public UnaryFunction<Common::u32char_type_t, uint> {
+template<>
+struct Hash<Common::u32char_type_t> : public UnaryFunction<Common::u32char_type_t, uint> {
 	uint operator()(Common::u32char_type_t val) const { return (uint)val; }
 };
-}
+} // namespace Common
 
 namespace Twp {
 
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 48a505a93b6..b8259ff6baf 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -19,8 +19,9 @@
  *
  */
 
-#include "twp/sqgame.h"
+#include "common/crc.h"
 #include "twp/twp.h"
+#include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/sqvm.h"
 #include "twp/squirrel/sqstring.h"
@@ -28,7 +29,6 @@
 #include "twp/squirrel/sqtable.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
-#include "common/crc.h"
 
 namespace Twp {
 
@@ -74,7 +74,7 @@ static SQInteger arrayShuffle(HSQUIRRELVM v) {
 	shuffle(arr);
 
 	sq_newarray(v, 0);
-	for (auto & it : arr) {
+	for (auto &it : arr) {
 		sqpush(v, it);
 		sq_arrayappend(v, -2);
 	}
@@ -359,8 +359,8 @@ static SQInteger getPrivatePref(HSQUIRRELVM v) {
 	Common::String key;
 	if (SQ_FAILED(sqget(v, 2, key))) {
 		return sq_throwerror(v, "failed to get key");
-	// } else if (g_twp->getPrefs().hasPrivPref(key)) {
-	// 	return sqpush(v, g_twp->getPrefs().privPrefAsJson(key));
+		// } else if (g_twp->getPrefs().hasPrivPref(key)) {
+		// 	return sqpush(v, g_twp->getPrefs().privPrefAsJson(key));
 	} else if (sq_gettop(v) == 3) {
 		HSQOBJECT obj;
 		sq_getstackobj(v, 3, &obj);
@@ -410,7 +410,7 @@ static SQInteger in_array(HSQUIRRELVM v) {
 	}
 	sq_pop(v, 1); // pops the null iterator
 
-	for (auto & it : objs) {
+	for (auto &it : objs) {
 		sq_pushobject(v, obj);
 		sq_pushobject(v, it);
 		if (sq_cmp(v) == 0) {
@@ -749,17 +749,17 @@ static SQInteger stopSentence(HSQUIRRELVM v) {
 
 // Counts the occurrences of a substring sub in the string `str`.
 static SQInteger strcount(HSQUIRRELVM v) {
-	const char* str;
-	const char* sub;
+	const char *str;
+	const char *sub;
 	if (SQ_FAILED(sqget(v, 2, str)))
 		return sq_throwerror(v, "Failed to get str");
 	if (SQ_FAILED(sqget(v, 3, sub)))
 		return sq_throwerror(v, "Failed to get sub");
 	int count = 0;
-    while ((str = strstr(str, sub))) {
-      str += strlen(sub);
-      ++count;
-    }
+	while ((str = strstr(str, sub))) {
+		str += strlen(sub);
+		++count;
+	}
 	sq_pushinteger(v, count);
 	return 1;
 }
@@ -767,104 +767,104 @@ static SQInteger strcount(HSQUIRRELVM v) {
 static SQInteger strcrc(HSQUIRRELVM v) {
 	Common::CRC32 crc;
 	const SQChar *str;
-    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
-      return sq_throwerror(v, _SC("failed to get string"));
-    }
-    uint32 u = crc.crcFast((const byte*)str, strlen(str));
-    sq_pushinteger(v, (SQInteger)u);
-    return 1;
+	if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+		return sq_throwerror(v, _SC("failed to get string"));
+	}
+	uint32 u = crc.crcFast((const byte *)str, strlen(str));
+	sq_pushinteger(v, (SQInteger)u);
+	return 1;
 }
 
 static SQInteger strfind(HSQUIRRELVM v) {
 	const SQChar *str1;
-    if (SQ_FAILED(sq_getstring(v, 2, &str1))) {
-      return sq_throwerror(v, _SC("failed to get string1"));
-    }
-    const SQChar *str2;
-    if (SQ_FAILED(sq_getstring(v, 3, &str2))) {
-      return sq_throwerror(v, _SC("failed to get string1"));
-    }
-    const char* p = strstr(str1, str2);
-    if (p == nullptr) {
-      sq_pushinteger(v, -1);
-    } else {
-      sq_pushinteger(v, p - str1);
-    }
-    return 1;
+	if (SQ_FAILED(sq_getstring(v, 2, &str1))) {
+		return sq_throwerror(v, _SC("failed to get string1"));
+	}
+	const SQChar *str2;
+	if (SQ_FAILED(sq_getstring(v, 3, &str2))) {
+		return sq_throwerror(v, _SC("failed to get string1"));
+	}
+	const char *p = strstr(str1, str2);
+	if (p == nullptr) {
+		sq_pushinteger(v, -1);
+	} else {
+		sq_pushinteger(v, p - str1);
+	}
+	return 1;
 }
 
 static SQInteger strfirst(HSQUIRRELVM v) {
 	const SQChar *str;
-    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
-      return sq_throwerror(v, _SC("failed to get string"));
-    }
-    if (strlen(str) > 0) {
-      const SQChar s[2]{str[0], '\0'};
-      sq_pushstring(v, s, 1);
-      return 1;
-    }
-    sq_pushnull(v);
-    return 1;
+	if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+		return sq_throwerror(v, _SC("failed to get string"));
+	}
+	if (strlen(str) > 0) {
+		const SQChar s[2]{str[0], '\0'};
+		sq_pushstring(v, s, 1);
+		return 1;
+	}
+	sq_pushnull(v);
+	return 1;
 }
 
 static SQInteger strlast(HSQUIRRELVM v) {
 	const SQChar *str;
-    if (SQ_FAILED(sq_getstring(v, 2, &str))) {
-      return sq_throwerror(v, _SC("failed to get string"));
-    }
-    auto len = strlen(str);
-    if (len > 0) {
-      const SQChar s[2]{str[len - 1], '\0'};
-      sq_pushstring(v, s, 1);
-      return 1;
-    }
-    sq_pushnull(v);
-    return 1;
+	if (SQ_FAILED(sq_getstring(v, 2, &str))) {
+		return sq_throwerror(v, _SC("failed to get string"));
+	}
+	auto len = strlen(str);
+	if (len > 0) {
+		const SQChar s[2]{str[len - 1], '\0'};
+		sq_pushstring(v, s, 1);
+		return 1;
+	}
+	sq_pushnull(v);
+	return 1;
 }
 
 static SQInteger strlines(HSQUIRRELVM v) {
 	Common::String text;
-    if (SQ_FAILED(sqget(v, 2, text))) {
-      return sq_throwerror(v, _SC("failed to get text"));
-    }
-    Common::String line;
-	Common::MemoryReadStream ms((const byte*)text.c_str(), text.size());
-    sq_newarray(v, 0);
-    while (!ms.eos()) {
-	  line = ms.readLine();
-      sq_pushstring(v, line.c_str(), line.size());
-      sq_arrayappend(v, -2);
-    }
-    return 1;
+	if (SQ_FAILED(sqget(v, 2, text))) {
+		return sq_throwerror(v, _SC("failed to get text"));
+	}
+	Common::String line;
+	Common::MemoryReadStream ms((const byte *)text.c_str(), text.size());
+	sq_newarray(v, 0);
+	while (!ms.eos()) {
+		line = ms.readLine();
+		sq_pushstring(v, line.c_str(), line.size());
+		sq_arrayappend(v, -2);
+	}
+	return 1;
 }
 
 static void replaceAll(Common::String &text, const Common::String &search, const Common::String &replace) {
-  auto pos = text.find(search);
-  while (pos != Common::String::npos) {
-    text.replace(pos, search.size(), replace);
-    pos = text.find(search, pos + replace.size());
-  }
+	auto pos = text.find(search);
+	while (pos != Common::String::npos) {
+		text.replace(pos, search.size(), replace);
+		pos = text.find(search, pos + replace.size());
+	}
 }
 
 static SQInteger strreplace(HSQUIRRELVM v) {
 	const SQChar *input;
-    const SQChar *search;
-    const SQChar *replace;
-    if (SQ_FAILED(sq_getstring(v, 2, &input))) {
-      return sq_throwerror(v, _SC("failed to get input"));
-    }
-    if (SQ_FAILED(sq_getstring(v, 3, &search))) {
-      return sq_throwerror(v, _SC("failed to get search"));
-    }
-    if (SQ_FAILED(sq_getstring(v, 4, &replace))) {
-      return sq_throwerror(v, _SC("failed to get replace"));
-    }
-    Common::String strInput(input);
-    Common::String strSearch(search);
-    Common::String strReplace(replace);
-    replaceAll(strInput, strSearch, strReplace);
-    sq_pushstring(v, strInput.c_str(), strInput.size());
-    return 1;
+	const SQChar *search;
+	const SQChar *replace;
+	if (SQ_FAILED(sq_getstring(v, 2, &input))) {
+		return sq_throwerror(v, _SC("failed to get input"));
+	}
+	if (SQ_FAILED(sq_getstring(v, 3, &search))) {
+		return sq_throwerror(v, _SC("failed to get search"));
+	}
+	if (SQ_FAILED(sq_getstring(v, 4, &replace))) {
+		return sq_throwerror(v, _SC("failed to get replace"));
+	}
+	Common::String strInput(input);
+	Common::String strSearch(search);
+	Common::String strReplace(replace);
+	replaceAll(strInput, strSearch, strReplace);
+	sq_pushstring(v, strInput.c_str(), strInput.size());
+	return 1;
 }
 
 // Splits the string `str` into substrings using a string separator.
@@ -877,7 +877,7 @@ static SQInteger strsplit(HSQUIRRELVM v) {
 		return sq_throwerror(v, "Failed to get delimiter");
 	sq_newarray(v, 0);
 	size_t delLen = strlen(delimiter);
-	if(delLen == 0)
+	if (delLen == 0)
 		return 1;
 
 	size_t len = str.size();
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 369e80eb964..36852ebfc81 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/gfx.h"
 #include "twp/twp.h"
+#include "twp/gfx.h"
 
 namespace Twp {
 
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index c2b64202eca..46be59b70c0 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -23,12 +23,12 @@
 #define TWP_GFX_H
 
 #include "common/array.h"
-#include "graphics/surface.h"
 #include "common/hashmap.h"
 #include "common/rect.h"
+#include "graphics/surface.h"
+#include "graphics/opengl/shader.h"
 #include "math/vector2d.h"
 #include "math/matrix4.h"
-#include "graphics/opengl/shader.h"
 
 namespace Twp {
 
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 544fdd248d7..0e60620d745 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -19,10 +19,10 @@
  *
  */
 
-#include "twp/ggpack.h"
 #include "common/archive.h"
 #include "common/debug.h"
 #include "twp/detection.h"
+#include "twp/ggpack.h"
 
 namespace Twp {
 
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index edcdc0c549c..7f34ec3b232 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -33,14 +33,14 @@ namespace Twp {
 
 struct XorKey {
 	Common::Array<int> magicBytes;
-    int multiplier;
+	int multiplier;
 };
 
-class MemStream: public Common::SeekableReadStream {
+class MemStream : public Common::SeekableReadStream {
 public:
 	MemStream();
 
-	bool open(const byte* buf, int64 bufSize);
+	bool open(const byte *buf, int64 bufSize);
 	uint32 read(void *dataPtr, uint32 dataSize);
 	bool eos() const;
 
@@ -49,33 +49,33 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    const byte* _buf = nullptr;
+	const byte *_buf = nullptr;
 	int64 _bufSize = 0;
 	int64 _pos = 0;
 };
 
-class OutMemStream: public Common::SeekableWriteStream {
+class OutMemStream : public Common::SeekableWriteStream {
 public:
 	OutMemStream();
 
-	bool open(byte* buf, int64 bufSize);
+	bool open(byte *buf, int64 bufSize);
 	uint32 write(const void *dataPtr, uint32 dataSize) override;
 
-	int64 pos() const  override;
-	int64 size() const  override;
-	bool seek(int64 offset, int whence = SEEK_SET)  override;
+	int64 pos() const override;
+	int64 size() const override;
+	bool seek(int64 offset, int whence = SEEK_SET) override;
 
 private:
-    byte* _buf = nullptr;
+	byte *_buf = nullptr;
 	int64 _bufSize = 0;
 	int64 _pos = 0;
 };
 
-class XorStream: public Common::SeekableReadStream {
+class XorStream : public Common::SeekableReadStream {
 public:
 	XorStream();
 
-	bool open(Common::SeekableReadStream *stream, int len, const XorKey& key);
+	bool open(Common::SeekableReadStream *stream, int len, const XorKey &key);
 	uint32 read(void *dataPtr, uint32 dataSize);
 	bool eos() const;
 
@@ -84,14 +84,14 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    Common::SeekableReadStream *_s = nullptr;
-    int _previous = 0;
-    int _start = 0;
-    int _size = 0;
-    XorKey _key;
+	Common::SeekableReadStream *_s = nullptr;
+	int _previous = 0;
+	int _start = 0;
+	int _size = 0;
+	XorKey _key;
 };
 
-class RangeStream: public Common::SeekableReadStream {
+class RangeStream : public Common::SeekableReadStream {
 public:
 	RangeStream();
 
@@ -104,22 +104,22 @@ public:
 	bool seek(int64 offset, int whence = SEEK_SET);
 
 private:
-    Common::SeekableReadStream *_s = nullptr;
-    int64 _start = 0;
-    int64 _size = 0;
+	Common::SeekableReadStream *_s = nullptr;
+	int64 _start = 0;
+	int64 _size = 0;
 };
 
 class GGHashMapDecoder {
 public:
 	GGHashMapDecoder();
 
-	Common::JSONValue* open(Common::SeekableReadStream *stream);
+	Common::JSONValue *open(Common::SeekableReadStream *stream);
 
 private:
-	Common::JSONValue* readHash();
+	Common::JSONValue *readHash();
 	Common::String readString(uint32 i);
-	Common::JSONValue* readValue();
-	Common::JSONValue* readArray();
+	Common::JSONValue *readValue();
+	Common::JSONValue *readArray();
 
 private:
 	Common::SeekableReadStream *_stream = nullptr;
@@ -130,29 +130,29 @@ class GGHashMapEncoder {
 public:
 	GGHashMapEncoder();
 
-	void open(Common::SeekableWriteStream* stream);
-	void write(const Common::JSONObject& obj);
+	void open(Common::SeekableWriteStream *stream);
+	void write(const Common::JSONObject &obj);
 
 private:
 	void writeMarker(byte marker);
-	void writeString(const Common::String& s);
-	void writeRawString(const Common::String& s);
+	void writeString(const Common::String &s);
+	void writeRawString(const Common::String &s);
 	void writeNull();
 	void writeInt(int value);
 	void writeFloat(float value);
-	void writeArray(const Common::JSONArray& arr);
-	void writeValue(const Common::JSONValue* obj);
+	void writeArray(const Common::JSONArray &arr);
+	void writeValue(const Common::JSONValue *obj);
 	void writeMap(const Common::JSONObject &obj);
 	void writeKeys();
-	void writeKey(const Common::String& key);
+	void writeKey(const Common::String &key);
 
 private:
-	Common::SeekableWriteStream* _s = nullptr;
+	Common::SeekableWriteStream *_s = nullptr;
 	Common::StableMap<Common::String, uint32> _strings;
 };
 
 struct GGPackEntry {
-    int offset, size;
+	int offset, size;
 };
 
 typedef Common::HashMap<Common::String, GGPackEntry, Common::IgnoreCase_Hash> GGPackEntries;
@@ -160,12 +160,13 @@ typedef Common::HashMap<Common::String, GGPackEntry, Common::IgnoreCase_Hash> GG
 class GGPackDecoder {
 public:
 	friend class GGPackEntryReader;
+
 public:
 	GGPackDecoder();
 
-	bool open(Common::SeekableReadStream *s, const XorKey& key);
+	bool open(Common::SeekableReadStream *s, const XorKey &key);
 
-	bool assetExists(const char* asset) { return _entries.contains(asset); }
+	bool assetExists(const char *asset) { return _entries.contains(asset); }
 
 private:
 	XorKey _key;
@@ -176,15 +177,15 @@ private:
 class GGPackSet {
 public:
 	void init();
-	bool assetExists(const char* asset);
+	bool assetExists(const char *asset);
 
 	bool containsDLC() const;
 
 public:
-	Common::StableMap<long, GGPackDecoder, Common::Greater<long>> _packs;
+	Common::StableMap<long, GGPackDecoder, Common::Greater<long> > _packs;
 };
 
-class GGBnutReader: public Common::ReadStream {
+class GGBnutReader : public Common::ReadStream {
 public:
 	GGBnutReader();
 
@@ -198,12 +199,12 @@ private:
 	int _cursor = 0;
 };
 
-class GGPackEntryReader: public Common::SeekableReadStream {
+class GGPackEntryReader : public Common::SeekableReadStream {
 public:
 	GGPackEntryReader();
 
-	bool open(GGPackDecoder& pack, const Common::String& entry);
-	bool open(GGPackSet& packs, const Common::String& entry);
+	bool open(GGPackDecoder &pack, const Common::String &entry);
+	bool open(GGPackSet &packs, const Common::String &entry);
 
 	uint32 read(void *dataPtr, uint32 dataSize) override;
 	bool eos() const override;
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index d70331f3ef3..4cdfdc88934 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -19,22 +19,22 @@
  *
  */
 
-#include "twp/hud.h"
-#include "twp/twp.h"
 #include "common/config-manager.h"
+#include "twp/twp.h"
+#include "twp/hud.h"
 
 namespace Twp {
 
 Verb::Verb() = default;
 
-Verb::Verb(VerbId verbId, const Common::String& img, const Common::String& f, const Common::String& t, const Common::String& k, int fl)
-: id(verbId), image(img), fun(f), text(t), key(k), flags(fl) {
+Verb::Verb(VerbId verbId, const Common::String &img, const Common::String &f, const Common::String &t, const Common::String &k, int fl)
+	: id(verbId), image(img), fun(f), text(t), key(k), flags(fl) {
 }
 
 VerbUiColors::VerbUiColors() = default;
 
 VerbUiColors::VerbUiColors(Color s, Color vbNormal, Color vbNormalTint, Color vbHiglight, Color vbHiglightTint, Color dlgNormal, Color dlgHighlt, Color invFrame, Color inventoryBack, Color retroNml, Color retroHighlt)
-: sentence(s), verbNormal(vbNormal), verbNormalTint(vbNormalTint), verbHighlight(vbHiglight), verbHighlightTint(vbHiglightTint), dialogNormal(dlgNormal), dialogHighlight(dlgHighlt), inventoryFrame(invFrame), inventoryBackground(inventoryBack), retroNormal(retroNml), retroHighlight(retroHighlt) {
+	: sentence(s), verbNormal(vbNormal), verbNormalTint(vbNormalTint), verbHighlight(vbHiglight), verbHighlightTint(vbHiglightTint), dialogNormal(dlgNormal), dialogHighlight(dlgHighlt), inventoryFrame(invFrame), inventoryBackground(inventoryBack), retroNormal(retroNml), retroHighlight(retroHighlt) {
 }
 
 ActorSlot::ActorSlot() = default;
@@ -108,7 +108,7 @@ void HudShader::applyUniforms() {
 
 Hud::Hud() : Node("hud") {
 	_zOrder = 100;
-	for (auto & _actorSlot : _actorSlots) {
+	for (auto &_actorSlot : _actorSlots) {
 		ActorSlot *slot = &_actorSlot;
 		slot->actor = nullptr;
 	}
@@ -119,7 +119,7 @@ void Hud::init() {
 }
 
 ActorSlot *Hud::actorSlot(Common::SharedPtr<Object> actor) {
-	for (auto & _actorSlot : _actorSlots) {
+	for (auto &_actorSlot : _actorSlots) {
 		ActorSlot *slot = &_actorSlot;
 		if (slot->actor == actor) {
 			return slot;
@@ -135,7 +135,7 @@ void Hud::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color color,
 }
 
 void Hud::drawCore(Math::Matrix4 trsf) {
-	ActorSlot *slot = this->actorSlot(_actor);
+	ActorSlot *slot = actorSlot(_actor);
 	if (!slot)
 		return;
 
@@ -145,7 +145,7 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	const SpriteSheetFrame &backingFrame = gameSheet->getFrame(classic ? "ui_backing_tall" : "ui_backing");
 	Texture *gameTexture = g_twp->_resManager.texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
-	g_twp->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha*getAlpha()), trsf);
+	g_twp->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha * getAlpha()), trsf);
 
 	bool verbHlt = ConfMan.getBool("invertVerbHighlight");
 	Color verbHighlight = verbHlt ? Color() : slot->verbUiColors.verbHighlight;
@@ -216,5 +216,4 @@ void Hud::setVisible(bool visible) {
 	}
 }
 
-
 } // namespace Twp
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index 1fd2a9c7b78..a2485916da2 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -58,7 +58,7 @@ struct Verb {
 	int flags{};
 
 	Verb();
-	Verb(VerbId id, const Common::String& image, const Common::String& fun, const Common::String& text, const Common::String& key, int flags = 0);
+	Verb(VerbId id, const Common::String &image, const Common::String &fun, const Common::String &text, const Common::String &key, int flags = 0);
 };
 
 struct ActorSlot {
@@ -72,7 +72,7 @@ public:
 	ActorSlot();
 
 	Verb *getVerb(int id) {
-		for (auto & verb : verbs) {
+		for (auto &verb : verbs) {
 			if (verb.id.id == id) {
 				return &verb;
 			}
diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
index 9f2787584d0..2b0d84e9cba 100644
--- a/engines/twp/ids.cpp
+++ b/engines/twp/ids.cpp
@@ -116,4 +116,4 @@ Facing getOppositeFacing(Facing facing) {
 		return FACE_LEFT;
 	}
 }
-}
+} // namespace Twp
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index d59ce87cf4a..53e8c3c7f59 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -41,193 +41,193 @@
 #define START_CALLBACKID 8000000
 #define END_CALLBACKID 10000000
 
-#define ALL  1
-#define HERE  0
-#define GONE  4
-#define OFF  0
-#define ON  1
-#define FULL  0
-#define EMPTY  1
-#define OPEN  1
-#define CLOSED  0
-#define FALSE  0
-#define TRUE  1
-#define MOUSE  1
-#define CONTROLLER  2
-#define DIRECTDRIVE  3
-#define TOUCH  4
-#define REMOTE  5
-#define FADE_IN  0
-#define FADE_OUT  1
-#define FADE_WOBBLE  2
-#define FADE_WOBBLE_TO_SEPIA  3
-#define FACE_FLIP  16
-#define DIR_FRONT  4
-#define DIR_BACK  8
-#define DIR_LEFT  2
-#define DIR_RIGHT  1
-#define LINEAR  0
-#define EASE_IN  1
-#define EASE_INOUT  2
-#define EASE_OUT  3
-#define SLOW_EASE_IN  4
-#define SLOW_EASE_OUT  5
-#define LOOPING  0x10
-#define SWING  0X20
-#define STOP_LOOPING  0X40
-#define ALIGN_LEFT    0x0000000010000000
-#define ALIGN_CENTER  0x0000000020000000
-#define ALIGN_RIGHT   0x0000000040000000
-#define ALIGN_TOP     0xFFFFFFFF80000000
-#define ALIGN_BOTTOM  0x0000000001000000
-#define LESS_SPACING  0x0000000000200000
-#define EX_ALLOW_SAVEGAMES  0x01
-#define EX_POP_CHARACTER_SELECTION  0x02
-#define EX_CAMERA_TRACKING  0x03
-#define EX_BUTTON_HOVER_SOUND  0x04
-#define EX_RESTART  0x06
-#define EX_IDLE_TIME  0x07
-#define EX_AUTOSAVE  0x08
-#define EX_AUTOSAVE_STATE  0x09
-#define EX_DISABLE_SAVESYSTEM  0x0A
-#define EX_SHOW_OPTIONS  11
-#define EX_OPTIONS_MUSIC  12
-#define EX_FORCE_TALKIE_TEXT  13
-#define GRASS_BACKANDFORTH  0x00
-#define DOOR  0x40
-#define DOOR_LEFT  0x140
-#define DOOR_RIGHT  0x240
-#define DOOR_BACK  0x440
-#define DOOR_FRONT  0x840
-#define FAR_LOOK  0x8
-#define USE_WITH  2
-#define USE_ON  4
-#define USE_IN  32
-#define GIVEABLE  0x1000
-#define TALKABLE  0x2000
-#define IMMEDIATE  0x4000
-#define FEMALE  0x80000
-#define MALE  0x100000
-#define PERSON  0x200000
-#define REACH_HIGH  0x8000
-#define REACH_MED  0x10000
-#define REACH_LOW  0x20000
-#define REACH_NONE  0x40000
-#define VERB_WALKTO  1
-#define VERB_LOOKAT  2
-#define VERB_TALKTO  3
-#define VERB_PICKUP  4
-#define VERB_OPEN  5
-#define VERB_CLOSE  6
-#define VERB_PUSH  7
-#define VERB_PULL  8
-#define VERB_GIVE  9
-#define VERB_USE  10
-#define VERB_DIALOG  13
-#define VERBFLAG_INSTANT  1
-#define TWP_NO  0
-#define TWP_YES  1
-#define TEMP_UNSELECTABLE  2
-#define TEMP_SELECTABLE  3
-#define MAC  1
-#define WIN  2
-#define LINUX  3
-#define XBOX  4
-#define IOS  5
-#define ANDROID  6
-#define SWITCH  7
-#define PS4  8
-#define EFFECT_NONE          0
-#define EFFECT_SEPIA         1
-#define EFFECT_EGA           2
-#define EFFECT_VHS           3
-#define EFFECT_GHOST         4
+#define ALL 1
+#define HERE 0
+#define GONE 4
+#define OFF 0
+#define ON 1
+#define FULL 0
+#define EMPTY 1
+#define OPEN 1
+#define CLOSED 0
+#define FALSE 0
+#define TRUE 1
+#define MOUSE 1
+#define CONTROLLER 2
+#define DIRECTDRIVE 3
+#define TOUCH 4
+#define REMOTE 5
+#define FADE_IN 0
+#define FADE_OUT 1
+#define FADE_WOBBLE 2
+#define FADE_WOBBLE_TO_SEPIA 3
+#define FACE_FLIP 16
+#define DIR_FRONT 4
+#define DIR_BACK 8
+#define DIR_LEFT 2
+#define DIR_RIGHT 1
+#define LINEAR 0
+#define EASE_IN 1
+#define EASE_INOUT 2
+#define EASE_OUT 3
+#define SLOW_EASE_IN 4
+#define SLOW_EASE_OUT 5
+#define LOOPING 0x10
+#define SWING 0X20
+#define STOP_LOOPING 0X40
+#define ALIGN_LEFT 0x0000000010000000
+#define ALIGN_CENTER 0x0000000020000000
+#define ALIGN_RIGHT 0x0000000040000000
+#define ALIGN_TOP 0xFFFFFFFF80000000
+#define ALIGN_BOTTOM 0x0000000001000000
+#define LESS_SPACING 0x0000000000200000
+#define EX_ALLOW_SAVEGAMES 0x01
+#define EX_POP_CHARACTER_SELECTION 0x02
+#define EX_CAMERA_TRACKING 0x03
+#define EX_BUTTON_HOVER_SOUND 0x04
+#define EX_RESTART 0x06
+#define EX_IDLE_TIME 0x07
+#define EX_AUTOSAVE 0x08
+#define EX_AUTOSAVE_STATE 0x09
+#define EX_DISABLE_SAVESYSTEM 0x0A
+#define EX_SHOW_OPTIONS 11
+#define EX_OPTIONS_MUSIC 12
+#define EX_FORCE_TALKIE_TEXT 13
+#define GRASS_BACKANDFORTH 0x00
+#define DOOR 0x40
+#define DOOR_LEFT 0x140
+#define DOOR_RIGHT 0x240
+#define DOOR_BACK 0x440
+#define DOOR_FRONT 0x840
+#define FAR_LOOK 0x8
+#define USE_WITH 2
+#define USE_ON 4
+#define USE_IN 32
+#define GIVEABLE 0x1000
+#define TALKABLE 0x2000
+#define IMMEDIATE 0x4000
+#define FEMALE 0x80000
+#define MALE 0x100000
+#define PERSON 0x200000
+#define REACH_HIGH 0x8000
+#define REACH_MED 0x10000
+#define REACH_LOW 0x20000
+#define REACH_NONE 0x40000
+#define VERB_WALKTO 1
+#define VERB_LOOKAT 2
+#define VERB_TALKTO 3
+#define VERB_PICKUP 4
+#define VERB_OPEN 5
+#define VERB_CLOSE 6
+#define VERB_PUSH 7
+#define VERB_PULL 8
+#define VERB_GIVE 9
+#define VERB_USE 10
+#define VERB_DIALOG 13
+#define VERBFLAG_INSTANT 1
+#define TWP_NO 0
+#define TWP_YES 1
+#define TEMP_UNSELECTABLE 2
+#define TEMP_SELECTABLE 3
+#define MAC 1
+#define WIN 2
+#define LINUX 3
+#define XBOX 4
+#define IOS 5
+#define ANDROID 6
+#define SWITCH 7
+#define PS4 8
+#define EFFECT_NONE 0
+#define EFFECT_SEPIA 1
+#define EFFECT_EGA 2
+#define EFFECT_VHS 3
+#define EFFECT_GHOST 4
 #define EFFECT_BLACKANDWHITE 5
-#define UI_INPUT_ON  1
-#define UI_INPUT_OFF  2
-#define UI_VERBS_ON  4
-#define UI_VERBS_OFF  8
-#define UI_HUDOBJECTS_ON  0x10
-#define UI_HUDOBJECTS_OFF  0x20
-#define UI_CURSOR_ON  0x40
-#define UI_CURSOR_OFF  0x80
+#define UI_INPUT_ON 1
+#define UI_INPUT_OFF 2
+#define UI_VERBS_ON 4
+#define UI_VERBS_OFF 8
+#define UI_HUDOBJECTS_ON 0x10
+#define UI_HUDOBJECTS_OFF 0x20
+#define UI_CURSOR_ON 0x40
+#define UI_CURSOR_OFF 0x80
 
 // these codes corresponds to SDL key codes used in TWP
-#define KEY_UP  0x40000052
-#define KEY_RIGHT  0x4000004F
-#define KEY_DOWN  0x40000051
-#define KEY_LEFT  0x40000050
-#define KEY_PAD1  0x40000059
-#define KEY_PAD2  0x4000005A
-#define KEY_PAD3  0x4000005B
-#define KEY_PAD4  0x4000005C
-#define KEY_PAD5  0x4000005D
-#define KEY_PAD6  0x4000005E
-#define KEY_PAD7  0x4000005F
-#define KEY_PAD8  0x40000056
-#define KEY_PAD9  0x40000061
-#define KEY_ESCAPE  0x08
-#define KEY_TAB  0x09
-#define KEY_RETURN  0x0D
-#define KEY_BACKSPACE  0x1B
-#define KEY_SPACE  0X20
-#define KEY_A  0x61
-#define KEY_B  0x62
-#define KEY_C  0x63
-#define KEY_D  0x64
-#define KEY_E  0x65
-#define KEY_F  0x66
-#define KEY_G  0x67
-#define KEY_H  0x68
-#define KEY_I  0x69
-#define KEY_J  0x6A
-#define KEY_K  0x6B
-#define KEY_L  0x6C
-#define KEY_M  0x6D
-#define KEY_N  0x6E
-#define KEY_O  0x6F
-#define KEY_P  0x70
-#define KEY_Q  0x71
-#define KEY_R  0x72
-#define KEY_S  0x73
-#define KEY_T  0x74
-#define KEY_U  0x75
-#define KEY_V  0x76
-#define KEY_W  0x77
-#define KEY_X  0x78
-#define KEY_Y  0x79
-#define KEY_Z  0x7A
-#define KEY_0  0x30
-#define KEY_1  0x31
-#define KEY_2  0x32
-#define KEY_3  0x33
-#define KEY_4  0x34
-#define KEY_5  0x35
-#define KEY_6  0x36
-#define KEY_7  0x37
-#define KEY_8  0x38
-#define KEY_9  0x39
-#define KEY_F1  0x4000003A
-#define KEY_F2  0x4000003B
-#define KEY_F3  0x4000003C
-#define KEY_F4  0x4000003D
-#define KEY_F5  0x4000003E
-#define KEY_F6  0x4000003F
-#define KEY_F7  0x40000040
-#define KEY_F8  0x40000041
-#define KEY_F9  0x40000042
-#define KEY_F10  0x40000043
-#define KEY_F11  0x40000044
-#define KEY_F12  0x40000045
+#define KEY_UP 0x40000052
+#define KEY_RIGHT 0x4000004F
+#define KEY_DOWN 0x40000051
+#define KEY_LEFT 0x40000050
+#define KEY_PAD1 0x40000059
+#define KEY_PAD2 0x4000005A
+#define KEY_PAD3 0x4000005B
+#define KEY_PAD4 0x4000005C
+#define KEY_PAD5 0x4000005D
+#define KEY_PAD6 0x4000005E
+#define KEY_PAD7 0x4000005F
+#define KEY_PAD8 0x40000056
+#define KEY_PAD9 0x40000061
+#define KEY_ESCAPE 0x08
+#define KEY_TAB 0x09
+#define KEY_RETURN 0x0D
+#define KEY_BACKSPACE 0x1B
+#define KEY_SPACE 0X20
+#define KEY_A 0x61
+#define KEY_B 0x62
+#define KEY_C 0x63
+#define KEY_D 0x64
+#define KEY_E 0x65
+#define KEY_F 0x66
+#define KEY_G 0x67
+#define KEY_H 0x68
+#define KEY_I 0x69
+#define KEY_J 0x6A
+#define KEY_K 0x6B
+#define KEY_L 0x6C
+#define KEY_M 0x6D
+#define KEY_N 0x6E
+#define KEY_O 0x6F
+#define KEY_P 0x70
+#define KEY_Q 0x71
+#define KEY_R 0x72
+#define KEY_S 0x73
+#define KEY_T 0x74
+#define KEY_U 0x75
+#define KEY_V 0x76
+#define KEY_W 0x77
+#define KEY_X 0x78
+#define KEY_Y 0x79
+#define KEY_Z 0x7A
+#define KEY_0 0x30
+#define KEY_1 0x31
+#define KEY_2 0x32
+#define KEY_3 0x33
+#define KEY_4 0x34
+#define KEY_5 0x35
+#define KEY_6 0x36
+#define KEY_7 0x37
+#define KEY_8 0x38
+#define KEY_9 0x39
+#define KEY_F1 0x4000003A
+#define KEY_F2 0x4000003B
+#define KEY_F3 0x4000003C
+#define KEY_F4 0x4000003D
+#define KEY_F5 0x4000003E
+#define KEY_F6 0x4000003F
+#define KEY_F7 0x40000040
+#define KEY_F8 0x40000041
+#define KEY_F9 0x40000042
+#define KEY_F10 0x40000043
+#define KEY_F11 0x40000044
+#define KEY_F12 0x40000045
 
-#define BUTTON_A  0x3E8
-#define BUTTON_B  0x3E9
-#define BUTTON_X  0x3EA
-#define BUTTON_Y  0x3EB
-#define BUTTON_START  0x3EC
-#define BUTTON_BACK  0x3EC
-#define BUTTON_MOUSE_LEFT  0x3ED
-#define BUTTON_MOUSE_RIGHT  0x3EE
+#define BUTTON_A 0x3E8
+#define BUTTON_B 0x3E9
+#define BUTTON_X 0x3EA
+#define BUTTON_Y 0x3EB
+#define BUTTON_START 0x3EC
+#define BUTTON_BACK 0x3EC
+#define BUTTON_MOUSE_LEFT 0x3ED
+#define BUTTON_MOUSE_RIGHT 0x3EE
 
 namespace Twp {
 
@@ -241,11 +241,11 @@ enum Facing {
 }
 
 namespace Common {
-template<> struct Hash<Twp::Facing> : public UnaryFunction<Twp::Facing, uint> {
+template<>
+struct Hash<Twp::Facing> : public UnaryFunction<Twp::Facing, uint> {
 	uint operator()(Twp::Facing val) const { return (uint)val; }
 };
-}
-
+} // namespace Common
 
 namespace Twp {
 
diff --git a/engines/twp/imgui_impl_opengl3_scummvm.cpp b/engines/twp/imgui_impl_opengl3_scummvm.cpp
index 10e46dd9b2c..b3de1008b4e 100644
--- a/engines/twp/imgui_impl_opengl3_scummvm.cpp
+++ b/engines/twp/imgui_impl_opengl3_scummvm.cpp
@@ -113,7 +113,7 @@
 #ifndef IMGUI_DISABLE
 #include "imgui_impl_opengl3_scummvm.h"
 #include <stdio.h>
-#include <stdint.h>     // intptr_t
+#include <stdint.h> // intptr_t
 #if defined(__APPLE__)
 #include <TargetConditionals.h>
 #endif
@@ -121,25 +121,25 @@
 // Clang/GCC warnings with -Weverything
 #if defined(__clang__)
 #pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wold-style-cast"         // warning: use of old-style cast
-#pragma clang diagnostic ignored "-Wsign-conversion"        // warning: implicit conversion changes signedness
-#pragma clang diagnostic ignored "-Wunused-macros"          // warning: macro is not used
+#pragma clang diagnostic ignored "-Wold-style-cast"  // warning: use of old-style cast
+#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
+#pragma clang diagnostic ignored "-Wunused-macros"   // warning: macro is not used
 #pragma clang diagnostic ignored "-Wnonportable-system-include-path"
-#pragma clang diagnostic ignored "-Wcast-function-type"     // warning: cast between incompatible function types (for loader)
+#pragma clang diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader)
 #endif
 #if defined(__GNUC__)
 #pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpragmas"                  // warning: unknown option after '#pragma GCC diagnostic' kind
-#pragma GCC diagnostic ignored "-Wunknown-warning-option"   // warning: unknown warning group 'xxx'
-#pragma GCC diagnostic ignored "-Wcast-function-type"       // warning: cast between incompatible function types (for loader)
+#pragma GCC diagnostic ignored "-Wpragmas"                // warning: unknown option after '#pragma GCC diagnostic' kind
+#pragma GCC diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx'
+#pragma GCC diagnostic ignored "-Wcast-function-type"     // warning: cast between incompatible function types (for loader)
 #endif
 
 // GL includes
 #if defined(IMGUI_IMPL_OPENGL_ES2)
 #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
-#include <OpenGLES/ES2/gl.h>    // Use GL ES 2
+#include <OpenGLES/ES2/gl.h> // Use GL ES 2
 #else
-#include <GLES2/gl2.h>          // Use GL ES 2
+#include <GLES2/gl2.h> // Use GL ES 2
 #endif
 #if defined(__EMSCRIPTEN__)
 #ifndef GL_GLEXT_PROTOTYPES
@@ -149,9 +149,9 @@
 #endif
 #elif defined(IMGUI_IMPL_OPENGL_ES3)
 #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
-#include <OpenGLES/ES3/gl.h>    // Use GL ES 3
+#include <OpenGLES/ES3/gl.h> // Use GL ES 3
 #else
-#include <GLES3/gl3.h>          // Use GL ES 3
+#include <GLES3/gl3.h> // Use GL ES 3
 #endif
 #elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
 // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
@@ -170,9 +170,9 @@
 #define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
 #elif defined(__EMSCRIPTEN__)
 #define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-#define glBindVertexArray       glBindVertexArrayOES
-#define glGenVertexArrays       glGenVertexArraysOES
-#define glDeleteVertexArrays    glDeleteVertexArraysOES
+#define glBindVertexArray glBindVertexArrayOES
+#define glGenVertexArrays glGenVertexArraysOES
+#define glDeleteVertexArrays glDeleteVertexArraysOES
 #define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES
 #endif
 
@@ -202,738 +202,761 @@
 #endif
 
 // [Debugging]
-//#define IMGUI_IMPL_OPENGL_DEBUG
+// #define IMGUI_IMPL_OPENGL_DEBUG
 #ifdef IMGUI_IMPL_OPENGL_DEBUG
 #include <stdio.h>
-#define GL_CALL(_CALL)      do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0)  // Call with error check
+#define GL_CALL(_CALL)                                                              \
+	do {                                                                            \
+		_CALL;                                                                      \
+		GLenum gl_err = glGetError();                                               \
+		if (gl_err != 0)                                                            \
+			fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); \
+	} while (0) // Call with error check
 #else
-#define GL_CALL(_CALL)      _CALL   // Call without error check
+#define GL_CALL(_CALL) _CALL // Call without error check
 #endif
 
 // OpenGL Data
-struct ImGui_ImplOpenGL3_Data
-{
-    GLuint          GlVersion;               // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
-    char            GlslVersionString[32];   // Specified by user or detected based on compile time GL settings.
-    bool            GlProfileIsES2;
-    bool            GlProfileIsES3;
-    bool            GlProfileIsCompat;
-    GLint           GlProfileMask;
-    GLuint          FontTexture;
-    GLuint          ShaderHandle;
-    GLint           AttribLocationTex;       // Uniforms location
-    GLint           AttribLocationProjMtx;
-    GLuint          AttribLocationVtxPos;    // Vertex attributes location
-    GLuint          AttribLocationVtxUV;
-    GLuint          AttribLocationVtxColor;
-    unsigned int    VboHandle, ElementsHandle;
-    GLsizeiptr      VertexBufferSize;
-    GLsizeiptr      IndexBufferSize;
-    bool            HasClipOrigin;
-    bool            UseBufferSubData;
-
-    ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
+struct ImGui_ImplOpenGL3_Data {
+	GLuint GlVersion;           // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
+	char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings.
+	bool GlProfileIsES2;
+	bool GlProfileIsES3;
+	bool GlProfileIsCompat;
+	GLint GlProfileMask;
+	GLuint FontTexture;
+	GLuint ShaderHandle;
+	GLint AttribLocationTex; // Uniforms location
+	GLint AttribLocationProjMtx;
+	GLuint AttribLocationVtxPos; // Vertex attributes location
+	GLuint AttribLocationVtxUV;
+	GLuint AttribLocationVtxColor;
+	unsigned int VboHandle, ElementsHandle;
+	GLsizeiptr VertexBufferSize;
+	GLsizeiptr IndexBufferSize;
+	bool HasClipOrigin;
+	bool UseBufferSubData;
+
+	ImGui_ImplOpenGL3_Data() { memset((void *)this, 0, sizeof(*this)); }
 };
 
 // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
 // It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
+static ImGui_ImplOpenGL3_Data *ImGui_ImplOpenGL3_GetBackendData() {
+	return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data *)ImGui::GetIO().BackendRendererUserData : nullptr;
 }
 
 // OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)
 #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-struct ImGui_ImplOpenGL3_VtxAttribState
-{
-    GLint   Enabled, Size, Type, Normalized, Stride;
-    GLvoid* Ptr;
-
-    void GetState(GLint index)
-    {
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);
-        glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);
-    }
-    void SetState(GLint index)
-    {
-        glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);
-        if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index);
-    }
+struct ImGui_ImplOpenGL3_VtxAttribState {
+	GLint Enabled, Size, Type, Normalized, Stride;
+	GLvoid *Ptr;
+
+	void GetState(GLint index) {
+		glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);
+		glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);
+		glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);
+		glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);
+		glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);
+		glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);
+	}
+	void SetState(GLint index) {
+		glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);
+		if (Enabled)
+			glEnableVertexAttribArray(index);
+		else
+			glDisableVertexAttribArray(index);
+	}
 };
 #endif
 
 // Functions
-bool    ImGui_ImplOpenGL3_Init(const char* glsl_version)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
+bool ImGui_ImplOpenGL3_Init(const char *glsl_version) {
+	ImGuiIO &io = ImGui::GetIO();
+	IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
 
-    // Initialize our loader
+	// Initialize our loader
 #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
-    if (imgl3wInit() != 0)
-    {
-        fprintf(stderr, "Failed to initialize OpenGL loader!\n");
-        return false;
-    }
+	if (imgl3wInit() != 0) {
+		fprintf(stderr, "Failed to initialize OpenGL loader!\n");
+		return false;
+	}
 #endif
 
-    // Setup backend capabilities flags
-    ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_opengl3";
+	// Setup backend capabilities flags
+	ImGui_ImplOpenGL3_Data *bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
+	io.BackendRendererUserData = (void *)bd;
+	io.BackendRendererName = "imgui_impl_opengl3";
 
-    // Query for GL version (e.g. 320 for GL 3.2)
+	// Query for GL version (e.g. 320 for GL 3.2)
 #if defined(IMGUI_IMPL_OPENGL_ES2)
-    // GLES 2
-    bd->GlVersion = 200;
-    bd->GlProfileIsES2 = true;
+	// GLES 2
+	bd->GlVersion = 200;
+	bd->GlProfileIsES2 = true;
 #else
-    // Desktop or GLES 3
-    GLint major = 0;
-    GLint minor = 0;
-    glGetIntegerv(GL_MAJOR_VERSION, &major);
-    glGetIntegerv(GL_MINOR_VERSION, &minor);
-    if (major == 0 && minor == 0)
-    {
-        // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
-        const char* gl_version = (const char*)glGetString(GL_VERSION);
-        sscanf(gl_version, "%d.%d", &major, &minor);
-    }
-    bd->GlVersion = (GLuint)(major * 100 + minor * 10);
+	// Desktop or GLES 3
+	GLint major = 0;
+	GLint minor = 0;
+	glGetIntegerv(GL_MAJOR_VERSION, &major);
+	glGetIntegerv(GL_MINOR_VERSION, &minor);
+	if (major == 0 && minor == 0) {
+		// Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
+		const char *gl_version = (const char *)glGetString(GL_VERSION);
+		sscanf(gl_version, "%d.%d", &major, &minor);
+	}
+	bd->GlVersion = (GLuint)(major * 100 + minor * 10);
 #if defined(GL_CONTEXT_PROFILE_MASK)
-    if (bd->GlVersion >= 320)
-        glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
-    bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
+	if (bd->GlVersion >= 320)
+		glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
+	bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
 #endif
 
 #if defined(IMGUI_IMPL_OPENGL_ES3)
-    bd->GlProfileIsES3 = true;
+	bd->GlProfileIsES3 = true;
 #endif
 
-    bd->UseBufferSubData = false;
-    /*
-    // Query vendor to enable glBufferSubData kludge
+	bd->UseBufferSubData = false;
+	/*
+	// Query vendor to enable glBufferSubData kludge
 #ifdef _WIN32
-    if (const char* vendor = (const char*)glGetString(GL_VENDOR))
-        if (strncmp(vendor, "Intel", 5) == 0)
-            bd->UseBufferSubData = true;
+	if (const char* vendor = (const char*)glGetString(GL_VENDOR))
+		if (strncmp(vendor, "Intel", 5) == 0)
+			bd->UseBufferSubData = true;
 #endif
-    */
+	*/
 #endif
 
 #ifdef IMGUI_IMPL_OPENGL_DEBUG
-    printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
+	printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char *)glGetString(GL_VENDOR), (const char *)glGetString(GL_RENDERER)); // [DEBUG]
 #endif
 
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
-    if (bd->GlVersion >= 320)
-        io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
+	if (bd->GlVersion >= 320)
+		io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
 #endif
 
-    // Store GLSL version string so we can refer to it later in case we recreate shaders.
-    // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
-    if (glsl_version == nullptr)
-    {
+	// Store GLSL version string so we can refer to it later in case we recreate shaders.
+	// Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
+	if (glsl_version == nullptr) {
 #if defined(IMGUI_IMPL_OPENGL_ES2)
-        glsl_version = "#version 100";
+		glsl_version = "#version 100";
 #elif defined(IMGUI_IMPL_OPENGL_ES3)
-        glsl_version = "#version 300 es";
+		glsl_version = "#version 300 es";
 #elif defined(__APPLE__)
-        glsl_version = "#version 150";
+		glsl_version = "#version 150";
 #else
-        glsl_version = "#version 130";
+		glsl_version = "#version 130";
 #endif
-    }
-    IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
-    strcpy(bd->GlslVersionString, glsl_version);
-    strcat(bd->GlslVersionString, "\n");
+	}
+	IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
+	strcpy(bd->GlslVersionString, glsl_version);
+	strcat(bd->GlslVersionString, "\n");
 
-    // Make an arbitrary GL call (we don't actually need the result)
-    // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!
-    GLint current_texture;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
+	// Make an arbitrary GL call (we don't actually need the result)
+	// IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!
+	GLint current_texture;
+	glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
 
-    // Detect extensions we support
-    bd->HasClipOrigin = (bd->GlVersion >= 450);
+	// Detect extensions we support
+	bd->HasClipOrigin = (bd->GlVersion >= 450);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
-    GLint num_extensions = 0;
-    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
-    for (GLint i = 0; i < num_extensions; i++)
-    {
-        const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
-        if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0)
-            bd->HasClipOrigin = true;
-    }
+	GLint num_extensions = 0;
+	glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+	for (GLint i = 0; i < num_extensions; i++) {
+		const char *extension = (const char *)glGetStringi(GL_EXTENSIONS, i);
+		if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0)
+			bd->HasClipOrigin = true;
+	}
 #endif
 
-    return true;
+	return true;
 }
 
-void    ImGui_ImplOpenGL3_Shutdown()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplOpenGL3_DestroyDeviceObjects();
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
+void ImGui_ImplOpenGL3_Shutdown() {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
+	ImGuiIO &io = ImGui::GetIO();
+
+	ImGui_ImplOpenGL3_DestroyDeviceObjects();
+	io.BackendRendererName = nullptr;
+	io.BackendRendererUserData = nullptr;
+	io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+	IM_DELETE(bd);
 }
 
-void    ImGui_ImplOpenGL3_NewFrame()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?");
+void ImGui_ImplOpenGL3_NewFrame() {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?");
 
-    if (!bd->ShaderHandle)
-        ImGui_ImplOpenGL3_CreateDeviceObjects();
+	if (!bd->ShaderHandle)
+		ImGui_ImplOpenGL3_CreateDeviceObjects();
 }
 
-static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
-    glEnable(GL_BLEND);
-    glBlendEquation(GL_FUNC_ADD);
-    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-    glDisable(GL_CULL_FACE);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_STENCIL_TEST);
-    glEnable(GL_SCISSOR_TEST);
+static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData *draw_data, int fb_width, int fb_height, GLuint vertex_array_object) {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+
+	// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
+	glEnable(GL_BLEND);
+	glBlendEquation(GL_FUNC_ADD);
+	glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+	glDisable(GL_CULL_FACE);
+	glDisable(GL_DEPTH_TEST);
+	glDisable(GL_STENCIL_TEST);
+	glEnable(GL_SCISSOR_TEST);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    if (bd->GlVersion >= 310)
-        glDisable(GL_PRIMITIVE_RESTART);
+	if (bd->GlVersion >= 310)
+		glDisable(GL_PRIMITIVE_RESTART);
 #endif
 #ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
 #endif
 
-    // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
+	// Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
 #if defined(GL_CLIP_ORIGIN)
-    bool clip_origin_lower_left = true;
-    if (bd->HasClipOrigin)
-    {
-        GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
-        if (current_clip_origin == GL_UPPER_LEFT)
-            clip_origin_lower_left = false;
-    }
-#endif
-
-    // Setup viewport, orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));
-    float L = draw_data->DisplayPos.x;
-    float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-    float T = draw_data->DisplayPos.y;
-    float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+	bool clip_origin_lower_left = true;
+	if (bd->HasClipOrigin) {
+		GLenum current_clip_origin = 0;
+		glGetIntegerv(GL_CLIP_ORIGIN, (GLint *)&current_clip_origin);
+		if (current_clip_origin == GL_UPPER_LEFT)
+			clip_origin_lower_left = false;
+	}
+#endif
+
+	// Setup viewport, orthographic projection matrix
+	// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
+	GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));
+	float L = draw_data->DisplayPos.x;
+	float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+	float T = draw_data->DisplayPos.y;
+	float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
 #if defined(GL_CLIP_ORIGIN)
-    if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
-#endif
-    const float ortho_projection[4][4] =
-    {
-        { 2.0f/(R-L),   0.0f,         0.0f,   0.0f },
-        { 0.0f,         2.0f/(T-B),   0.0f,   0.0f },
-        { 0.0f,         0.0f,        -1.0f,   0.0f },
-        { (R+L)/(L-R),  (T+B)/(B-T),  0.0f,   1.0f },
-    };
-    glUseProgram(bd->ShaderHandle);
-    glUniform1i(bd->AttribLocationTex, 0);
-    glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
+	if (!clip_origin_lower_left) {
+		float tmp = T;
+		T = B;
+		B = tmp;
+	} // Swap top and bottom if origin is upper left
+#endif
+	const float ortho_projection[4][4] =
+		{
+			{2.0f / (R - L), 0.0f, 0.0f, 0.0f},
+			{0.0f, 2.0f / (T - B), 0.0f, 0.0f},
+			{0.0f, 0.0f, -1.0f, 0.0f},
+			{(R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f},
+		};
+	glUseProgram(bd->ShaderHandle);
+	glUniform1i(bd->AttribLocationTex, 0);
+	glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
 
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
-        glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
+	if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
+		glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
 #endif
 
-    (void)vertex_array_object;
+	(void)vertex_array_object;
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(vertex_array_object);
-#endif
-
-    // Bind vertex/index buffers and setup attributes for ImDrawVert
-    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));
-    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos,   2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV,    2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
+	glBindVertexArray(vertex_array_object);
+#endif
+
+	// Bind vertex/index buffers and setup attributes for ImDrawVert
+	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));
+	GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));
+	GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
+	GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
+	GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
+	GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid *)offsetof(ImDrawVert, pos)));
+	GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid *)offsetof(ImDrawVert, uv)));
+	GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid *)offsetof(ImDrawVert, col)));
 }
 
 // OpenGL3 Render function.
 // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
 // This is in order to be able to run within an OpenGL engine that doesn't do so.
-void    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
-    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
-    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
-    if (fb_width <= 0 || fb_height <= 0)
-        return;
-
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Backup GL state
-    GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
-    glActiveTexture(GL_TEXTURE0);
-    GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);
-    GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);
+void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData *draw_data) {
+	// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+	int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
+	int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
+	if (fb_width <= 0 || fb_height <= 0)
+		return;
+
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+
+	// Backup GL state
+	GLenum last_active_texture;
+	glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *)&last_active_texture);
+	glActiveTexture(GL_TEXTURE0);
+	GLuint last_program;
+	glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&last_program);
+	GLuint last_texture;
+	glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *)&last_texture);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }
-#endif
-    GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
+	GLuint last_sampler;
+	if (bd->GlVersion >= 330 || bd->GlProfileIsES3) {
+		glGetIntegerv(GL_SAMPLER_BINDING, (GLint *)&last_sampler);
+	} else {
+		last_sampler = 0;
+	}
+#endif
+	GLuint last_array_buffer;
+	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint *)&last_array_buffer);
 #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.
-    GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);
+	// This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.
+	GLint last_element_array_buffer;
+	glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
+	ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos;
+	last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);
+	ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv;
+	last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);
+	ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color;
+	last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);
 #endif
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);
+	GLuint last_vertex_array_object;
+	glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint *)&last_vertex_array_object);
 #endif
 #ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
-#endif
-    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
-    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
-    GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);
-    GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);
-    GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);
-    GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);
-    GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);
-    GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);
-    GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
-    GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
-    GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
-    GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
-    GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
+	GLint last_polygon_mode[2];
+	glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
+#endif
+	GLint last_viewport[4];
+	glGetIntegerv(GL_VIEWPORT, last_viewport);
+	GLint last_scissor_box[4];
+	glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
+	GLenum last_blend_src_rgb;
+	glGetIntegerv(GL_BLEND_SRC_RGB, (GLint *)&last_blend_src_rgb);
+	GLenum last_blend_dst_rgb;
+	glGetIntegerv(GL_BLEND_DST_RGB, (GLint *)&last_blend_dst_rgb);
+	GLenum last_blend_src_alpha;
+	glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint *)&last_blend_src_alpha);
+	GLenum last_blend_dst_alpha;
+	glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint *)&last_blend_dst_alpha);
+	GLenum last_blend_equation_rgb;
+	glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint *)&last_blend_equation_rgb);
+	GLenum last_blend_equation_alpha;
+	glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint *)&last_blend_equation_alpha);
+	GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
+	GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
+	GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
+	GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
+	GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
+	GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
 #endif
 
-    // Setup desired GL state
-    // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
-    // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
-    GLuint vertex_array_object = 0;
+	// Setup desired GL state
+	// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
+	// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
+	GLuint vertex_array_object = 0;
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GL_CALL(glGenVertexArrays(1, &vertex_array_object));
-#endif
-    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
-
-    // Will project scissor/clipping rectangles into framebuffer space
-    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
-    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
-
-    // Render command lists
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-
-        // Upload vertex/index buffers
-        // - OpenGL drivers are in a very sorry state nowadays....
-        //   During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports
-        //   of leaks on Intel GPU when using multi-viewports on Windows.
-        // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.
-        // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.
-        //   We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.
-        // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.
-        const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
-        const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
-        if (bd->UseBufferSubData)
-        {
-            if (bd->VertexBufferSize < vtx_buffer_size)
-            {
-                bd->VertexBufferSize = vtx_buffer_size;
-                GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));
-            }
-            if (bd->IndexBufferSize < idx_buffer_size)
-            {
-                bd->IndexBufferSize = idx_buffer_size;
-                GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));
-            }
-            GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data));
-            GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data));
-        }
-        else
-        {
-            GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
-            GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
-        }
-
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback != nullptr)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
-                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)
-                GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
-
-                // Bind texture, Draw
-                GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
+	GL_CALL(glGenVertexArrays(1, &vertex_array_object));
+#endif
+	ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
+
+	// Will project scissor/clipping rectangles into framebuffer space
+	ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
+	ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
+
+	// Render command lists
+	for (int n = 0; n < draw_data->CmdListsCount; n++) {
+		const ImDrawList *cmd_list = draw_data->CmdLists[n];
+
+		// Upload vertex/index buffers
+		// - OpenGL drivers are in a very sorry state nowadays....
+		//   During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports
+		//   of leaks on Intel GPU when using multi-viewports on Windows.
+		// - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.
+		// - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.
+		//   We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.
+		// - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.
+		const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
+		const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
+		if (bd->UseBufferSubData) {
+			if (bd->VertexBufferSize < vtx_buffer_size) {
+				bd->VertexBufferSize = vtx_buffer_size;
+				GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));
+			}
+			if (bd->IndexBufferSize < idx_buffer_size) {
+				bd->IndexBufferSize = idx_buffer_size;
+				GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));
+			}
+			GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid *)cmd_list->VtxBuffer.Data));
+			GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid *)cmd_list->IdxBuffer.Data));
+		} else {
+			GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid *)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
+			GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid *)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
+		}
+
+		for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
+			const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
+			if (pcmd->UserCallback != nullptr) {
+				// User callback, registered via ImDrawList::AddCallback()
+				// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
+				if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
+					ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
+				else
+					pcmd->UserCallback(cmd_list, pcmd);
+			} else {
+				// Project scissor/clipping rectangles into framebuffer space
+				ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
+				ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
+				if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
+					continue;
+
+				// Apply scissor/clipping rectangle (Y is inverted in OpenGL)
+				GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
+
+				// Bind texture, Draw
+				GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
-                if (bd->GlVersion >= 320)
-                    GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
-                else
+				if (bd->GlVersion >= 320)
+					GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void *)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
+				else
 #endif
-                GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
-            }
-        }
-    }
+					GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void *)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
+			}
+		}
+	}
 
-    // Destroy the temporary VAO
+	// Destroy the temporary VAO
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));
+	GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));
 #endif
 
-    // Restore modified GL state
-    // This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.
-    if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program);
-    glBindTexture(GL_TEXTURE_2D, last_texture);
+	// Restore modified GL state
+	// This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.
+	if (last_program == 0 || glIsProgram(last_program))
+		glUseProgram(last_program);
+	glBindTexture(GL_TEXTURE_2D, last_texture);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
-        glBindSampler(0, last_sampler);
+	if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
+		glBindSampler(0, last_sampler);
 #endif
-    glActiveTexture(last_active_texture);
+	glActiveTexture(last_active_texture);
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(last_vertex_array_object);
+	glBindVertexArray(last_vertex_array_object);
 #endif
-    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+	glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
 #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
-    last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);
-    last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);
-    last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);
-#endif
-    glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
-    glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
-    if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
-    if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
-    if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
-    if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
-    if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
+	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
+	last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);
+	last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);
+	last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);
+#endif
+	glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
+	glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
+	if (last_enable_blend)
+		glEnable(GL_BLEND);
+	else
+		glDisable(GL_BLEND);
+	if (last_enable_cull_face)
+		glEnable(GL_CULL_FACE);
+	else
+		glDisable(GL_CULL_FACE);
+	if (last_enable_depth_test)
+		glEnable(GL_DEPTH_TEST);
+	else
+		glDisable(GL_DEPTH_TEST);
+	if (last_enable_stencil_test)
+		glEnable(GL_STENCIL_TEST);
+	else
+		glDisable(GL_STENCIL_TEST);
+	if (last_enable_scissor_test)
+		glEnable(GL_SCISSOR_TEST);
+	else
+		glDisable(GL_SCISSOR_TEST);
 #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
+	if (bd->GlVersion >= 310) {
+		if (last_enable_primitive_restart)
+			glEnable(GL_PRIMITIVE_RESTART);
+		else
+			glDisable(GL_PRIMITIVE_RESTART);
+	}
 #endif
 
 #ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons
-    if (bd->GlVersion <= 310 || bd->GlProfileIsCompat)
-    {
-        glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]);
-        glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
-    }
-    else
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);
-    }
+	// Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons
+	if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) {
+		glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]);
+		glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
+	} else {
+		glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);
+	}
 #endif // IMGUI_IMPL_HAS_POLYGON_MODE
 
-    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
-    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
-    (void)bd; // Not all compilation paths use this
+	glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
+	glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
+	(void)bd; // Not all compilation paths use this
 }
 
-bool ImGui_ImplOpenGL3_CreateFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Build texture atlas
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
-
-    // Upload texture to graphics system
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    GLint last_texture;
-    GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
-    GL_CALL(glGenTextures(1, &bd->FontTexture));
-    GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
-    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
-    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+bool ImGui_ImplOpenGL3_CreateFontsTexture() {
+	ImGuiIO &io = ImGui::GetIO();
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+
+	// Build texture atlas
+	unsigned char *pixels;
+	int width, height;
+	io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
+
+	// Upload texture to graphics system
+	// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
+	GLint last_texture;
+	GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
+	GL_CALL(glGenTextures(1, &bd->FontTexture));
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
+	GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+	GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
 #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
-    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
+	GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
 #endif
-    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
+	GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
 
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
+	// Store our identifier
+	io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
 
-    // Restore state
-    GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
+	// Restore state
+	GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
 
-    return true;
+	return true;
 }
 
-void ImGui_ImplOpenGL3_DestroyFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    if (bd->FontTexture)
-    {
-        glDeleteTextures(1, &bd->FontTexture);
-        io.Fonts->SetTexID(0);
-        bd->FontTexture = 0;
-    }
+void ImGui_ImplOpenGL3_DestroyFontsTexture() {
+	ImGuiIO &io = ImGui::GetIO();
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	if (bd->FontTexture) {
+		glDeleteTextures(1, &bd->FontTexture);
+		io.Fonts->SetTexID(0);
+		bd->FontTexture = 0;
+	}
 }
 
 // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
-static bool CheckShader(GLuint handle, const char* desc)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    GLint status = 0, log_length = 0;
-    glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
-    glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
-    if ((GLboolean)status == GL_FALSE)
-        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString);
-    if (log_length > 1)
-    {
-        ImVector<char> buf;
-        buf.resize((int)(log_length + 1));
-        glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
-        fprintf(stderr, "%s\n", buf.begin());
-    }
-    return (GLboolean)status == GL_TRUE;
+static bool CheckShader(GLuint handle, const char *desc) {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	GLint status = 0, log_length = 0;
+	glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
+	glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
+	if ((GLboolean)status == GL_FALSE)
+		fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString);
+	if (log_length > 1) {
+		ImVector<char> buf;
+		buf.resize((int)(log_length + 1));
+		glGetShaderInfoLog(handle, log_length, nullptr, (GLchar *)buf.begin());
+		fprintf(stderr, "%s\n", buf.begin());
+	}
+	return (GLboolean)status == GL_TRUE;
 }
 
 // If you get an error please report on GitHub. You may try different GL context version or GLSL version.
-static bool CheckProgram(GLuint handle, const char* desc)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    GLint status = 0, log_length = 0;
-    glGetProgramiv(handle, GL_LINK_STATUS, &status);
-    glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
-    if ((GLboolean)status == GL_FALSE)
-        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString);
-    if (log_length > 1)
-    {
-        ImVector<char> buf;
-        buf.resize((int)(log_length + 1));
-        glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
-        fprintf(stderr, "%s\n", buf.begin());
-    }
-    return (GLboolean)status == GL_TRUE;
+static bool CheckProgram(GLuint handle, const char *desc) {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	GLint status = 0, log_length = 0;
+	glGetProgramiv(handle, GL_LINK_STATUS, &status);
+	glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
+	if ((GLboolean)status == GL_FALSE)
+		fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString);
+	if (log_length > 1) {
+		ImVector<char> buf;
+		buf.resize((int)(log_length + 1));
+		glGetProgramInfoLog(handle, log_length, nullptr, (GLchar *)buf.begin());
+		fprintf(stderr, "%s\n", buf.begin());
+	}
+	return (GLboolean)status == GL_TRUE;
 }
 
-bool    ImGui_ImplOpenGL3_CreateDeviceObjects()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
+bool ImGui_ImplOpenGL3_CreateDeviceObjects() {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
 
-    // Backup GL state
-    GLint last_texture, last_array_buffer;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
-    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
+	// Backup GL state
+	GLint last_texture, last_array_buffer;
+	glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GLint last_vertex_array;
-    glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
-#endif
-
-    // Parse GLSL version string
-    int glsl_version = 130;
-    sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
-
-    const GLchar* vertex_shader_glsl_120 =
-        "uniform mat4 ProjMtx;\n"
-        "attribute vec2 Position;\n"
-        "attribute vec2 UV;\n"
-        "attribute vec4 Color;\n"
-        "varying vec2 Frag_UV;\n"
-        "varying vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_130 =
-        "uniform mat4 ProjMtx;\n"
-        "in vec2 Position;\n"
-        "in vec2 UV;\n"
-        "in vec4 Color;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_300_es =
-        "precision highp float;\n"
-        "layout (location = 0) in vec2 Position;\n"
-        "layout (location = 1) in vec2 UV;\n"
-        "layout (location = 2) in vec4 Color;\n"
-        "uniform mat4 ProjMtx;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_410_core =
-        "layout (location = 0) in vec2 Position;\n"
-        "layout (location = 1) in vec2 UV;\n"
-        "layout (location = 2) in vec4 Color;\n"
-        "uniform mat4 ProjMtx;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_120 =
-        "#ifdef GL_ES\n"
-        "    precision mediump float;\n"
-        "#endif\n"
-        "uniform sampler2D Texture;\n"
-        "varying vec2 Frag_UV;\n"
-        "varying vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_130 =
-        "uniform sampler2D Texture;\n"
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_300_es =
-        "precision mediump float;\n"
-        "uniform sampler2D Texture;\n"
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "layout (location = 0) out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_410_core =
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "uniform sampler2D Texture;\n"
-        "layout (location = 0) out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    // Select shaders matching our GLSL versions
-    const GLchar* vertex_shader = nullptr;
-    const GLchar* fragment_shader = nullptr;
-    if (glsl_version < 130)
-    {
-        vertex_shader = vertex_shader_glsl_120;
-        fragment_shader = fragment_shader_glsl_120;
-    }
-    else if (glsl_version >= 410)
-    {
-        vertex_shader = vertex_shader_glsl_410_core;
-        fragment_shader = fragment_shader_glsl_410_core;
-    }
-    else if (glsl_version == 300)
-    {
-        vertex_shader = vertex_shader_glsl_300_es;
-        fragment_shader = fragment_shader_glsl_300_es;
-    }
-    else
-    {
-        vertex_shader = vertex_shader_glsl_130;
-        fragment_shader = fragment_shader_glsl_130;
-    }
-
-    // Create shaders
-    const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
-    GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
-    glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
-    glCompileShader(vert_handle);
-    CheckShader(vert_handle, "vertex shader");
-
-    const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
-    GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
-    glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
-    glCompileShader(frag_handle);
-    CheckShader(frag_handle, "fragment shader");
-
-    // Link
-    bd->ShaderHandle = glCreateProgram();
-    glAttachShader(bd->ShaderHandle, vert_handle);
-    glAttachShader(bd->ShaderHandle, frag_handle);
-    glLinkProgram(bd->ShaderHandle);
-    CheckProgram(bd->ShaderHandle, "shader program");
-
-    glDetachShader(bd->ShaderHandle, vert_handle);
-    glDetachShader(bd->ShaderHandle, frag_handle);
-    glDeleteShader(vert_handle);
-    glDeleteShader(frag_handle);
-
-    bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
-    bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
-    bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
-    bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
-    bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
-
-    // Create buffers
-    glGenBuffers(1, &bd->VboHandle);
-    glGenBuffers(1, &bd->ElementsHandle);
-
-    ImGui_ImplOpenGL3_CreateFontsTexture();
-
-    // Restore modified GL state
-    glBindTexture(GL_TEXTURE_2D, last_texture);
-    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+	GLint last_vertex_array;
+	glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
+#endif
+
+	// Parse GLSL version string
+	int glsl_version = 130;
+	sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
+
+	const GLchar *vertex_shader_glsl_120 =
+		"uniform mat4 ProjMtx;\n"
+		"attribute vec2 Position;\n"
+		"attribute vec2 UV;\n"
+		"attribute vec4 Color;\n"
+		"varying vec2 Frag_UV;\n"
+		"varying vec4 Frag_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Frag_UV = UV;\n"
+		"    Frag_Color = Color;\n"
+		"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+		"}\n";
+
+	const GLchar *vertex_shader_glsl_130 =
+		"uniform mat4 ProjMtx;\n"
+		"in vec2 Position;\n"
+		"in vec2 UV;\n"
+		"in vec4 Color;\n"
+		"out vec2 Frag_UV;\n"
+		"out vec4 Frag_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Frag_UV = UV;\n"
+		"    Frag_Color = Color;\n"
+		"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+		"}\n";
+
+	const GLchar *vertex_shader_glsl_300_es =
+		"precision highp float;\n"
+		"layout (location = 0) in vec2 Position;\n"
+		"layout (location = 1) in vec2 UV;\n"
+		"layout (location = 2) in vec4 Color;\n"
+		"uniform mat4 ProjMtx;\n"
+		"out vec2 Frag_UV;\n"
+		"out vec4 Frag_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Frag_UV = UV;\n"
+		"    Frag_Color = Color;\n"
+		"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+		"}\n";
+
+	const GLchar *vertex_shader_glsl_410_core =
+		"layout (location = 0) in vec2 Position;\n"
+		"layout (location = 1) in vec2 UV;\n"
+		"layout (location = 2) in vec4 Color;\n"
+		"uniform mat4 ProjMtx;\n"
+		"out vec2 Frag_UV;\n"
+		"out vec4 Frag_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Frag_UV = UV;\n"
+		"    Frag_Color = Color;\n"
+		"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+		"}\n";
+
+	const GLchar *fragment_shader_glsl_120 =
+		"#ifdef GL_ES\n"
+		"    precision mediump float;\n"
+		"#endif\n"
+		"uniform sampler2D Texture;\n"
+		"varying vec2 Frag_UV;\n"
+		"varying vec4 Frag_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
+		"}\n";
+
+	const GLchar *fragment_shader_glsl_130 =
+		"uniform sampler2D Texture;\n"
+		"in vec2 Frag_UV;\n"
+		"in vec4 Frag_Color;\n"
+		"out vec4 Out_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+		"}\n";
+
+	const GLchar *fragment_shader_glsl_300_es =
+		"precision mediump float;\n"
+		"uniform sampler2D Texture;\n"
+		"in vec2 Frag_UV;\n"
+		"in vec4 Frag_Color;\n"
+		"layout (location = 0) out vec4 Out_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+		"}\n";
+
+	const GLchar *fragment_shader_glsl_410_core =
+		"in vec2 Frag_UV;\n"
+		"in vec4 Frag_Color;\n"
+		"uniform sampler2D Texture;\n"
+		"layout (location = 0) out vec4 Out_Color;\n"
+		"void main()\n"
+		"{\n"
+		"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
+		"}\n";
+
+	// Select shaders matching our GLSL versions
+	const GLchar *vertex_shader = nullptr;
+	const GLchar *fragment_shader = nullptr;
+	if (glsl_version < 130) {
+		vertex_shader = vertex_shader_glsl_120;
+		fragment_shader = fragment_shader_glsl_120;
+	} else if (glsl_version >= 410) {
+		vertex_shader = vertex_shader_glsl_410_core;
+		fragment_shader = fragment_shader_glsl_410_core;
+	} else if (glsl_version == 300) {
+		vertex_shader = vertex_shader_glsl_300_es;
+		fragment_shader = fragment_shader_glsl_300_es;
+	} else {
+		vertex_shader = vertex_shader_glsl_130;
+		fragment_shader = fragment_shader_glsl_130;
+	}
+
+	// Create shaders
+	const GLchar *vertex_shader_with_version[2] = {bd->GlslVersionString, vertex_shader};
+	GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
+	glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
+	glCompileShader(vert_handle);
+	CheckShader(vert_handle, "vertex shader");
+
+	const GLchar *fragment_shader_with_version[2] = {bd->GlslVersionString, fragment_shader};
+	GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
+	glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
+	glCompileShader(frag_handle);
+	CheckShader(frag_handle, "fragment shader");
+
+	// Link
+	bd->ShaderHandle = glCreateProgram();
+	glAttachShader(bd->ShaderHandle, vert_handle);
+	glAttachShader(bd->ShaderHandle, frag_handle);
+	glLinkProgram(bd->ShaderHandle);
+	CheckProgram(bd->ShaderHandle, "shader program");
+
+	glDetachShader(bd->ShaderHandle, vert_handle);
+	glDetachShader(bd->ShaderHandle, frag_handle);
+	glDeleteShader(vert_handle);
+	glDeleteShader(frag_handle);
+
+	bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
+	bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
+	bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
+	bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
+	bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
+
+	// Create buffers
+	glGenBuffers(1, &bd->VboHandle);
+	glGenBuffers(1, &bd->ElementsHandle);
+
+	ImGui_ImplOpenGL3_CreateFontsTexture();
+
+	// Restore modified GL state
+	glBindTexture(GL_TEXTURE_2D, last_texture);
+	glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
 #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(last_vertex_array);
+	glBindVertexArray(last_vertex_array);
 #endif
 
-    return true;
+	return true;
 }
 
-void    ImGui_ImplOpenGL3_DestroyDeviceObjects()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    if (bd->VboHandle)      { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
-    if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
-    if (bd->ShaderHandle)   { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
-    ImGui_ImplOpenGL3_DestroyFontsTexture();
+void ImGui_ImplOpenGL3_DestroyDeviceObjects() {
+	ImGui_ImplOpenGL3_Data *bd = ImGui_ImplOpenGL3_GetBackendData();
+	if (bd->VboHandle) {
+		glDeleteBuffers(1, &bd->VboHandle);
+		bd->VboHandle = 0;
+	}
+	if (bd->ElementsHandle) {
+		glDeleteBuffers(1, &bd->ElementsHandle);
+		bd->ElementsHandle = 0;
+	}
+	if (bd->ShaderHandle) {
+		glDeleteProgram(bd->ShaderHandle);
+		bd->ShaderHandle = 0;
+	}
+	ImGui_ImplOpenGL3_DestroyFontsTexture();
 }
 
 //-----------------------------------------------------------------------------
diff --git a/engines/twp/imgui_impl_opengl3_scummvm.h b/engines/twp/imgui_impl_opengl3_scummvm.h
index e9bfccdb734..f0923c51def 100644
--- a/engines/twp/imgui_impl_opengl3_scummvm.h
+++ b/engines/twp/imgui_impl_opengl3_scummvm.h
@@ -26,37 +26,36 @@
 //  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
 
 #pragma once
-#include "imgui/imgui.h"      // IMGUI_IMPL_API
+#include "imgui/imgui.h" // IMGUI_IMPL_API
 #ifndef IMGUI_DISABLE
 
 // Backend API
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
+IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char *glsl_version = nullptr);
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData *draw_data);
 
 // (Optional) Called by Init/NewFrame/Shutdown
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateFontsTexture();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyFontsTexture();
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyDeviceObjects();
+IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
+IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
+IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
 
 // Specific OpenGL ES versions
-//#define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten
-//#define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android
+// #define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten
+// #define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android
 
 // You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
-#if !defined(IMGUI_IMPL_OPENGL_ES2) \
- && !defined(IMGUI_IMPL_OPENGL_ES3)
+#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)
 
 // Try to detect GLES on matching platforms
 #if defined(__APPLE__)
 #include <TargetConditionals.h>
 #endif
 #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
-#define IMGUI_IMPL_OPENGL_ES3               // iOS, Android  -> GL ES 3, "#version 300 es"
+#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android  -> GL ES 3, "#version 300 es"
 #elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
-#define IMGUI_IMPL_OPENGL_ES2               // Emscripten    -> GL ES 2, "#version 100"
+#define IMGUI_IMPL_OPENGL_ES2 // Emscripten    -> GL ES 2, "#version 100"
 #else
 // Otherwise imgui_impl_opengl3_loader.h will be used.
 #endif
diff --git a/engines/twp/imgui_impl_sdl2_scummvm.cpp b/engines/twp/imgui_impl_sdl2_scummvm.cpp
index 68a2c82a844..69ac732455f 100644
--- a/engines/twp/imgui_impl_sdl2_scummvm.cpp
+++ b/engines/twp/imgui_impl_sdl2_scummvm.cpp
@@ -395,7 +395,7 @@ static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) {
 	return ImGuiKey_None;
 }
 
-static void ImGui_ImplSDL2_UpdateKeyModifiers(const Common::KeyState& keyState) {
+static void ImGui_ImplSDL2_UpdateKeyModifiers(const Common::KeyState &keyState) {
 	ImGuiIO &io = ImGui::GetIO();
 	io.AddKeyEvent(ImGuiMod_Ctrl, keyState.hasFlags(Common::KBD_CTRL));
 	io.AddKeyEvent(ImGuiMod_Shift, keyState.hasFlags(Common::KBD_SHIFT));
@@ -463,18 +463,18 @@ bool ImGui_ImplSDL2_ProcessEvent(const Common::Event *event) {
 		bd->MouseButtonsDown = mouse_down ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
 		return true;
 	}
-    case Common::EVENT_KEYDOWN:
-    case Common::EVENT_KEYUP: {
-
-		    ImGui_ImplSDL2_UpdateKeyModifiers(event->kbd);
-		    ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->kbd.keycode);
-		    io.AddKeyEvent(key, (event->type == Common::EVENT_KEYDOWN));
-			io.SetKeyEventNativeData(key, event->kbd.keycode, event->kbd.keycode);
-			if(event->type == Common::EVENT_KEYUP) {
-				io.AddInputCharacter(event->kbd.ascii);
-			}
-		    return true;
+	case Common::EVENT_KEYDOWN:
+	case Common::EVENT_KEYUP: {
+
+		ImGui_ImplSDL2_UpdateKeyModifiers(event->kbd);
+		ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->kbd.keycode);
+		io.AddKeyEvent(key, (event->type == Common::EVENT_KEYDOWN));
+		io.SetKeyEventNativeData(key, event->kbd.keycode, event->kbd.keycode);
+		if (event->type == Common::EVENT_KEYUP) {
+			io.AddInputCharacter(event->kbd.ascii);
 		}
+		return true;
+	}
 	case Common::EVENT_FOCUS_GAINED:
 		io.AddFocusEvent(true);
 		return true;
diff --git a/engines/twp/imgui_impl_sdl2_scummvm.h b/engines/twp/imgui_impl_sdl2_scummvm.h
index 4d68098e200..8ce98b50399 100644
--- a/engines/twp/imgui_impl_sdl2_scummvm.h
+++ b/engines/twp/imgui_impl_sdl2_scummvm.h
@@ -19,7 +19,7 @@
 // - Introduction, links and more at the top of imgui.cpp
 
 #pragma once
-#include "imgui/imgui.h"      // IMGUI_IMPL_API
+#include "imgui/imgui.h" // IMGUI_IMPL_API
 #ifndef IMGUI_DISABLE
 
 struct SDL_Window;
@@ -27,21 +27,21 @@ struct SDL_Renderer;
 typedef union SDL_Event SDL_Event;
 
 namespace Common {
-    struct Event;
+struct Event;
 }
 
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForVulkan(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForD3D(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForMetal(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOther(SDL_Window* window);
-IMGUI_IMPL_API void     ImGui_ImplSDL2_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplSDL2_NewFrame();
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_ProcessEvent(const Common::Event* event);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window *window, void *sdl_gl_context);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window *window);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window *window);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window *window);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window *window, SDL_Renderer *renderer);
+IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOther(SDL_Window *window);
+IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame();
+IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const Common::Event *event);
 
 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter
+static inline void ImGui_ImplSDL2_NewFrame(SDL_Window *) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter
 #endif
 
 #endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index a1e79faac7e..2d9f72bbf97 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -1,7 +1,28 @@
+/* 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 "common/math.h"
 #include "twp/lighting.h"
 #include "twp/room.h"
 #include "twp/twp.h"
-#include "common/math.h"
 
 namespace Twp {
 
diff --git a/engines/twp/lighting.h b/engines/twp/lighting.h
index a7a69e56c2f..7da33a9108f 100644
--- a/engines/twp/lighting.h
+++ b/engines/twp/lighting.h
@@ -36,10 +36,10 @@ public:
 	Lighting();
 	virtual ~Lighting();
 
-	void setSpriteOffset(const Math::Vector2d& offset);
+	void setSpriteOffset(const Math::Vector2d &offset);
 	void setSpriteSheetFrame(const SpriteSheetFrame &frame, const Texture &getNumTextures, bool flipX);
 
-	void update(const Lights& lights);
+	void update(const Lights &lights);
 
 private:
 	virtual void applyUniforms() final;
diff --git a/engines/twp/lip.h b/engines/twp/lip.h
index 401eb054464..f6030014993 100644
--- a/engines/twp/lip.h
+++ b/engines/twp/lip.h
@@ -40,7 +40,7 @@ struct LipItem {
 // have additional information about the mouth shapes.
 class Lip {
 public:
-	void load(Common::SeekableReadStream* stream);
+	void load(Common::SeekableReadStream *stream);
 	// Gets the letter corresponding to a mouth shape at a spcific time.
 	char letter(float time);
 
@@ -48,6 +48,6 @@ private:
 	Common::Array<LipItem> _items;
 };
 
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 9658e29ab78..c58bd3323af 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -23,7 +23,6 @@
 #include "gui/widgets/edittext.h"
 #include "gui/widgets/popup.h"
 #include "gui/ThemeEval.h"
-
 #include "common/translation.h"
 #include "common/savefile.h"
 #include "backends/keymapper/keymapper.h"
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index bd5b86f4de6..14b75672418 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -21,7 +21,6 @@
  */
 
 #include "common/config-manager.h"
-
 #include "twp/twp.h"
 #include "twp/squtil.h"
 
@@ -29,7 +28,7 @@ namespace Twp {
 
 OffsetTo::~OffsetTo() = default;
 
-OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im)
+OffsetTo::OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d &pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getOffset(), pos, duration, im) {
 }
@@ -43,7 +42,7 @@ void OffsetTo::update(float elapsed) {
 
 MoveTo::~MoveTo() = default;
 
-MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im)
+MoveTo::MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d &pos, InterpolationMethod im)
 	: _obj(obj),
 	  _tween(obj->_node->getPos(), pos, duration, im) {
 }
@@ -244,7 +243,8 @@ void WalkTo::actorArrived() {
 }
 
 void WalkTo::update(float elapsed) {
-	if(!_enabled) return;
+	if (!_enabled)
+		return;
 	if (_state == kWalking && !_path.empty()) {
 		Vector2i dest = _path[0];
 		float d = distance(dest, (Vector2i)_obj->_node->getAbsPos());
@@ -273,16 +273,16 @@ void WalkTo::update(float elapsed) {
 		}
 	}
 
-	if(_state == kArrived) {
+	if (_state == kArrived) {
 		Common::SharedPtr<Motor> reach = _obj->getReach();
 		if (reach && reach->isEnabled()) {
 			reach->update(elapsed);
-			_state =  kReach;
+			_state = kReach;
 			return;
 		}
 	}
 
-	if(_state == kReach) {
+	if (_state == kReach) {
 		Common::SharedPtr<Motor> reach = _obj->getReach();
 		if (reach) {
 			if (reach->isEnabled()) {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 0caaa00ef22..770f8b44c4b 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -95,7 +95,7 @@ class Object;
 class OffsetTo : public Motor {
 public:
 	virtual ~OffsetTo();
-	OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im);
+	OffsetTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d &pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
@@ -108,7 +108,7 @@ private:
 class MoveTo : public Motor {
 public:
 	virtual ~MoveTo();
-	MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d& pos, InterpolationMethod im);
+	MoveTo(float duration, Common::SharedPtr<Object> obj, const Math::Vector2d &pos, InterpolationMethod im);
 
 private:
 	virtual void update(float elasped) override;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 3d4cf72a228..421c9204628 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -83,7 +83,8 @@ Object::Object(HSQOBJECT o, const Common::String &key)
 }
 
 Object::~Object() {
-	if(_nodeAnim) _nodeAnim->remove();
+	if (_nodeAnim)
+		_nodeAnim->remove();
 	_node->remove();
 
 	if (_layer) {
@@ -358,7 +359,7 @@ ObjectIcons Object::getIcons() const {
 
 Common::String Object::getIcon() {
 	ObjectIcons result = getIcons();
-	if(result.icons.empty())
+	if (result.icons.empty())
 		return "";
 	_iconIndex = _iconIndex % result.icons.size();
 	return result.icons[_iconIndex];
@@ -401,10 +402,10 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 		}
 		object->_room = room;
 
-		if(roomChanged && isActor(object->getId())) {
-			if(room == g_twp->_room) {
+		if (roomChanged && isActor(object->getId())) {
+			if (room == g_twp->_room) {
 				g_twp->actorEnter(object);
-			} else if(oldRoom == g_twp->_room) {
+			} else if (oldRoom == g_twp->_room) {
 				g_twp->actorExit(object);
 			}
 		}
@@ -431,8 +432,8 @@ void Object::stopObjectMotors() {
 	_node->setRotationOffset(0);
 	_node->setOffset({0.f, 0.f});
 	_node->setShakeOffset({0.f, 0.f});
-	_node->setScale({1.f,1.f});
-	if(isActor(getId()))
+	_node->setScale({1.f, 1.f});
+	if (isActor(getId()))
 		stand();
 }
 
diff --git a/engines/twp/objectanimation.h b/engines/twp/objectanimation.h
index b4ab7ab2af9..b13d21444bd 100644
--- a/engines/twp/objectanimation.h
+++ b/engines/twp/objectanimation.h
@@ -43,4 +43,3 @@ struct ObjectAnimation {
 } // namespace Twp
 
 #endif
-
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index b54f7377528..8b7c6139e03 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -22,13 +22,13 @@
 #include "twp/twp.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
-#include "squirrel/squirrel.h"
-#include "squirrel/sqvm.h"
-#include "squirrel/sqstring.h"
-#include "squirrel/sqstate.h"
-#include "squirrel/sqtable.h"
-#include "squirrel/sqfuncproto.h"
-#include "squirrel/sqclosure.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/squirrel/sqvm.h"
+#include "twp/squirrel/sqstring.h"
+#include "twp/squirrel/sqstate.h"
+#include "twp/squirrel/sqtable.h"
+#include "twp/squirrel/sqfuncproto.h"
+#include "twp/squirrel/sqclosure.h"
 
 namespace Twp {
 
@@ -206,15 +206,15 @@ static SQInteger isObject(HSQUIRRELVM v) {
 
 static SQInteger jiggleInventory(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
-    if (!obj) {
-      return sq_throwerror(v, "failed to get object");
-    }
-    SQInteger enabled = 0;
-    if (SQ_FAILED(sq_getinteger(v, 3, &enabled))) {
-      return sq_throwerror(v, "failed to get enabled");
-    }
-    obj->_jiggle = enabled != 0;
-    return 0;
+	if (!obj) {
+		return sq_throwerror(v, "failed to get object");
+	}
+	SQInteger enabled = 0;
+	if (SQ_FAILED(sq_getinteger(v, 3, &enabled))) {
+		return sq_throwerror(v, "failed to get enabled");
+	}
+	obj->_jiggle = enabled != 0;
+	return 0;
 }
 
 // Rotate the object around its origin back and forth by the specified amount of pixels.
@@ -995,7 +995,7 @@ static SQInteger removeInventory(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	if(isActor(obj->getId())) {
+	if (isActor(obj->getId())) {
 		obj->_inventory.clear();
 		obj->_inventoryOffset = 0;
 	} else if (obj->_owner) {
diff --git a/engines/twp/prefs.cpp b/engines/twp/prefs.cpp
index 4e4e11368a9..d4c77a1b827 100644
--- a/engines/twp/prefs.cpp
+++ b/engines/twp/prefs.cpp
@@ -19,9 +19,9 @@
  *
  */
 
+#include "common/config-manager.h"
 #include "twp/prefs.h"
 #include "twp/util.h"
-#include "common/config-manager.h"
 
 namespace Twp {
 
diff --git a/engines/twp/prefs.h b/engines/twp/prefs.h
index f9b973a0abe..fcc33c60277 100644
--- a/engines/twp/prefs.h
+++ b/engines/twp/prefs.h
@@ -27,21 +27,21 @@
 namespace Twp {
 
 struct TempPref {
-    float gameSpeedFactor = 1.f;
-    bool forceTalkieText = false;
+	float gameSpeedFactor = 1.f;
+	bool forceTalkieText = false;
 };
 
 class Preferences {
 public:
 	Preferences();
 
-    bool hasPrefs(const Common::String& name);
-	Common::JSONValue* prefsAsJson(const Common::String& name);
+	bool hasPrefs(const Common::String &name);
+	Common::JSONValue *prefsAsJson(const Common::String &name);
 
-	Common::String prefs(const Common::String name, const Common::String& def) const;
-	float prefs(const Common::String& name, float def) const;
-	bool prefs(const Common::String& name, bool def) const;
-	int prefs(const Common::String& name, int def) const;
+	Common::String prefs(const Common::String name, const Common::String &def) const;
+	float prefs(const Common::String &name, float def) const;
+	bool prefs(const Common::String &name, bool def) const;
+	int prefs(const Common::String &name, int def) const;
 
 	void setPrefs(const Common::String &name, const Common::String &value);
 	void setPrefs(const Common::String &name, float value);
@@ -50,13 +50,13 @@ public:
 
 	void savePrefs();
 
-	Common::String getKey(const Common::String& path);
+	Common::String getKey(const Common::String &path);
 
 private:
-	Common::JSONValue* _node = nullptr;
-    TempPref _tmp;
+	Common::JSONValue *_node = nullptr;
+	TempPref _tmp;
 };
 
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 96efb3eefba..88d49a29129 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -21,8 +21,8 @@
 
 #include "common/config-manager.h"
 #include "image/png.h"
-#include "twp/resmanager.h"
 #include "twp/twp.h"
+#include "twp/resmanager.h"
 
 namespace Twp {
 
@@ -33,7 +33,7 @@ static Common::String getKey(const char *path) {
 	p = p.substr(0, i);
 	if ((len > 4) && p.hasSuffixIgnoreCase("_en")) {
 		Common::String lang(ConfMan.get("language"));
-		Common::String filename(p.substr(0, p.size()-2));
+		Common::String filename(p.substr(0, p.size() - 2));
 		const char *ext = path + i;
 		return Common::String::format("%s%s%s", filename.c_str(), lang.c_str(), ext);
 	}
@@ -43,13 +43,13 @@ static Common::String getKey(const char *path) {
 void ResManager::loadTexture(const Common::String &name) {
 	debugC(kDebugRes, "Load texture %s", name.c_str());
 	GGPackEntryReader r;
-	if(!r.open(g_twp->_pack, name)) {
+	if (!r.open(g_twp->_pack, name)) {
 		error("Texture %s not found", name.c_str());
 	}
 	Image::PNGDecoder d;
 	d.loadStream(r);
 	const Graphics::Surface *surface = d.getSurface();
-	if(!surface) {
+	if (!surface) {
 		error("PNG %s not loaded", name.c_str());
 		return;
 	}
@@ -80,7 +80,7 @@ void ResManager::loadSpriteSheet(const Common::String &name) {
 void ResManager::loadFont(const Common::String &name) {
 	if (name == "sayline") {
 		debugC(kDebugRes, "Load font %s", name.c_str());
-		Common::String resName = ConfMan.getBool("retroFonts") ? "FontRetroSheet": "FontModernSheet";
+		Common::String resName = ConfMan.getBool("retroFonts") ? "FontRetroSheet" : "FontModernSheet";
 		Common::SharedPtr<GGFont> fontModernSheet(new GGFont());
 		fontModernSheet->load(resName);
 		_fonts[name] = fontModernSheet;
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index 388da057797..f6ccf9695a0 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -35,8 +35,8 @@ public:
 	ResManager() {}
 
 	Texture *texture(const Common::String &name);
-	SpriteSheet *spriteSheet(const Common::String& name);
-	Common::SharedPtr<Font> font(const Common::String& name);
+	SpriteSheet *spriteSheet(const Common::String &name);
+	Common::SharedPtr<Font> font(const Common::String &name);
 
 private:
 	void loadTexture(const Common::String &name);
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 00c8227ba8f..5a29c6f6b93 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -125,7 +125,7 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 		ClipperLib::Clipper c2;
 		c2.AddPaths(solutions, ClipperLib::ptSubject, true);
 		c2.AddPaths(clips, ClipperLib::ptClip, true);
-        c2.Execute(ClipperLib::ClipType::ctDifference, solutions2, ClipperLib::pftEvenOdd);
+		c2.Execute(ClipperLib::ClipType::ctDifference, solutions2, ClipperLib::pftEvenOdd);
 
 		for (size_t i = 0; i < solutions2.size(); i++) {
 			result.push_back(toWalkbox(solutions2[i]));
@@ -262,7 +262,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	}
 
 	{
-		 Common::SharedPtr<Layer> layer(new Layer(backNames, Math::Vector2d(1, 1), 0));
+		Common::SharedPtr<Layer> layer(new Layer(backNames, Math::Vector2d(1, 1), 0));
 		room->_layers.push_back(layer);
 	}
 
diff --git a/engines/twp/room.h b/engines/twp/room.h
index a502987f8e8..cfa174b4a54 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -33,8 +33,8 @@
 #include "twp/scenegraph.h"
 #include "twp/graph.h"
 
-#define FULLSCREENCLOSEUP	1
-#define FULLSCREENROOM		2
+#define FULLSCREENCLOSEUP 1
+#define FULLSCREENROOM 2
 
 namespace Twp {
 
@@ -95,10 +95,10 @@ struct Lights {
 };
 
 struct ScalingTrigger {
-	ScalingTrigger(Common::SharedPtr<Object>  obj, Scaling* scaling);
+	ScalingTrigger(Common::SharedPtr<Object> obj, Scaling *scaling);
 
-	Common::SharedPtr<Object>  _obj;
-	Scaling* _scaling = nullptr;
+	Common::SharedPtr<Object> _obj;
+	Scaling *_scaling = nullptr;
 };
 
 class PathFinder;
@@ -130,24 +130,24 @@ public:
 	Common::Array<Vector2i> calculatePath(Vector2i frm, Vector2i to);
 
 public:
-	Common::String _name;              // Name of the room
-	Common::String _sheet;             // Name of the spritesheet to use
-	Math::Vector2d _roomSize;          // Size of the room
-	int _fullscreen = 0;               // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
-	int _height = 0;                   // Height of the room (what else ?)
-	Common::Array<Common::SharedPtr<Layer> > _layers;    // Parallax layers of a room
-	Common::Array<Walkbox> _walkboxes; // Represents the areas where an actor can or cannot walk
+	Common::String _name;                             // Name of the room
+	Common::String _sheet;                            // Name of the spritesheet to use
+	Math::Vector2d _roomSize;                         // Size of the room
+	int _fullscreen = 0;                              // Indicates if a room is a closeup room (fullscreen=1) or not (fullscreen=2), just a guess
+	int _height = 0;                                  // Height of the room (what else ?)
+	Common::Array<Common::SharedPtr<Layer> > _layers; // Parallax layers of a room
+	Common::Array<Walkbox> _walkboxes;                // Represents the areas where an actor can or cannot walk
 	Common::Array<Walkbox> _mergedPolygon;
-	Common::Array<Scaling> _scalings;  // Defines the scaling of the actor in the room
-	Scaling _scaling;                  // Defines the scaling of the actor in the room
-	HSQOBJECT _table;                  // Squirrel table representing this room
-	bool _entering = false;            // Indicates whether or not an actor is entering this room
-	Lights _lights;                    // Lights of the room
+	Common::Array<Scaling> _scalings;                    // Defines the scaling of the actor in the room
+	Scaling _scaling;                                    // Defines the scaling of the actor in the room
+	HSQOBJECT _table;                                    // Squirrel table representing this room
+	bool _entering = false;                              // Indicates whether or not an actor is entering this room
+	Lights _lights;                                      // Lights of the room
 	Common::Array<Common::SharedPtr<Object> > _triggers; // Triggers currently enabled in the room
-	Common::Array<ScalingTrigger> _scalingTriggers; // Scaling Triggers of the room
+	Common::Array<ScalingTrigger> _scalingTriggers;      // Scaling Triggers of the room
 	bool _pseudo = false;
 	Common::SharedPtr<Scene> _scene;
-	OverlayNode _overlayNode;	// Represents an overlay
+	OverlayNode _overlayNode; // Represents an overlay
 	RoomEffect _effect = RoomEffect::None;
 	Common::SharedPtr<Motor> _overlayTo;
 	Common::SharedPtr<Motor> _rotateTo;
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 7e048d6cc43..bc662cc0f2c 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/sqgame.h"
 #include "twp/squtil.h"
 
 namespace Twp {
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 086b26c7e34..836bdbba4dd 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -362,7 +362,8 @@ static void loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &jso
 }
 
 static void setActor(const Common::String &key) {
-	if(key.empty()) return;
+	if (key.empty())
+		return;
 	for (size_t i = 0; i < g_twp->_actors.size(); i++) {
 		Common::SharedPtr<Object> a = g_twp->_actors[i];
 		if (a->_key == key) {
@@ -410,7 +411,7 @@ bool SaveGameManager::loadGame(const SaveGame &savegame) {
 	loadObjects(json["objects"]->asObject());
 	g_twp->setRoom(room(json["currentRoom"]->asString()));
 	setActor(json["selectedActor"]->asString());
-	if(g_twp->_actor)
+	if (g_twp->_actor)
 		g_twp->cameraAt(g_twp->_actor->_node->getPos());
 
 	HSQUIRRELVM v = g_twp->getVm();
@@ -1000,7 +1001,7 @@ void SaveGameManager::saveGame(Common::WriteStream *ws) {
 	byte marker = (8 - ((fullSize + 9) % 8));
 
 	// write at the end 16 bytes: hashdata (4 bytes) + savetime (4 bytes) + marker (8 bytes)
-	int32 *p = (int32*)(buffer.data() + fullSize);
+	int32 *p = (int32 *)(buffer.data() + fullSize);
 	p[0] = hash;
 	p[1] = savetime;
 	memset(&p[2], marker, 8);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 6bbb1341999..c2ce6adab03 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -22,8 +22,8 @@
 #include "math/matrix3.h"
 #include "common/algorithm.h"
 #include "common/config-manager.h"
-#include "twp/scenegraph.h"
 #include "twp/twp.h"
+#include "twp/scenegraph.h"
 #include "twp/lighting.h"
 
 namespace Twp {
@@ -201,7 +201,7 @@ void Node::onDrawChildren(Math::Matrix4 trsf) {
 	for (size_t i = 0; i < children.size(); i++) {
 		_children.push_back(children[i].node);
 	}
-	for (auto& child : _children) {
+	for (auto &child : _children) {
 		child->draw(trsf);
 	}
 }
@@ -398,7 +398,8 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 		} else {
 			const SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
 			const SpriteSheetFrame *sf = sheet->frame(frame);
-			if(!sf) return;
+			if (!sf)
+				return;
 			Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
 			if (_obj->_lit) {
 				g_twp->getGfx().use(g_twp->_lighting.get());
@@ -776,7 +777,7 @@ void SpriteNode::drawCore(Math::Matrix4 trsf) {
 	setAnchor(anchor);
 
 	Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
-	g_twp->getGfx().drawSprite(rect, *texture, this->getComputedColor(), trsf);
+	g_twp->getGfx().drawSprite(rect, *texture, getComputedColor(), trsf);
 }
 
 NoOverrideNode::NoOverrideNode() : Node("NoOverride") {
diff --git a/engines/twp/scenegraph.h b/engines/twp/scenegraph.h
index 879fe1b026f..c41b03bb82f 100644
--- a/engines/twp/scenegraph.h
+++ b/engines/twp/scenegraph.h
@@ -23,10 +23,10 @@
 #ifndef TWP_SCENEGRAPH_H
 #define TWP_SCENEGRAPH_H
 
-#include "math/vector2d.h"
-#include "math/matrix4.h"
 #include "common/array.h"
 #include "common/str.h"
+#include "math/vector2d.h"
+#include "math/matrix4.h"
 #include "twp/gfx.h"
 #include "twp/rectf.h"
 #include "twp/font.h"
diff --git a/engines/twp/shaders.cpp b/engines/twp/shaders.cpp
index bc620240fe8..615388bcbc8 100644
--- a/engines/twp/shaders.cpp
+++ b/engines/twp/shaders.cpp
@@ -19,13 +19,13 @@
  *
  */
 
-#include "twp/shaders.h"
 #include "twp/twp.h"
+#include "twp/shaders.h"
 
 namespace Twp {
 
 const char *vsrc = R"(
-	uniform mat4 u_transform;
+uniform mat4 u_transform;
 attribute vec2 a_position;
 attribute vec4 a_color;
 attribute vec2 a_texCoords;
diff --git a/engines/twp/shaders.h b/engines/twp/shaders.h
index 6977cbc7984..39fc88432e8 100644
--- a/engines/twp/shaders.h
+++ b/engines/twp/shaders.h
@@ -27,28 +27,27 @@
 
 namespace Twp {
 
-extern const char* vsrc;
-extern const char* bwShader;
-extern const char* ghostShader;
-extern const char* sepiaShader;
+extern const char *vsrc;
+extern const char *bwShader;
+extern const char *ghostShader;
+extern const char *sepiaShader;
 
 struct ShaderParams {
-    RoomEffect effect = RoomEffect::None;
-    float sepiaFlicker = 1.f;
-    float randomValue[5];
-    float timeLapse;
-    float iGlobalTime;
-    float iNoiseThreshold = 1.f;
-    float iFade = 1.f;
-    float wobbleIntensity = 1.f;
-    Color shadows = Color(-0.3f, 0.f, 0.f);
-    Color midtones = Color(-0.2f, 0.f, 0.1f);
-    Color highlights = Color(0.f, 0.f, 0.2f);
+	RoomEffect effect = RoomEffect::None;
+	float sepiaFlicker = 1.f;
+	float randomValue[5];
+	float timeLapse;
+	float iGlobalTime;
+	float iNoiseThreshold = 1.f;
+	float iFade = 1.f;
+	float wobbleIntensity = 1.f;
+	Color shadows = Color(-0.3f, 0.f, 0.f);
+	Color midtones = Color(-0.2f, 0.f, 0.1f);
+	Color highlights = Color(0.f, 0.f, 0.2f);
 
 	void updateShader();
 };
 
-
 enum class FadeEffect {
 	None,
 	In,
@@ -56,7 +55,7 @@ enum class FadeEffect {
 	Wobble
 };
 
-class FadeShader: public Shader {
+class FadeShader : public Shader {
 public:
 	FadeShader();
 	virtual ~FadeShader() override;
@@ -76,14 +75,14 @@ public:
 	Math::Vector2d _cameraPos;
 	float _duration = 0.f;
 	float _elapsed = 0.f;
-	float _movement = 0.f;		// movement for wobble effect
-	float _fade = 0.f;			// fade value between [0.f,1.f]
-	bool _fadeToSepia = false;	// true to fade to sepia
+	float _movement = 0.f;     // movement for wobble effect
+	float _fade = 0.f;         // fade value between [0.f,1.f]
+	bool _fadeToSepia = false; // true to fade to sepia
 
 private:
 	int _textureLoc[2];
 };
 
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 3bf1963e381..122e192a9e9 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/sqgame.h"
 #include "twp/squtil.h"
 
 namespace Twp {
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index 0ca01b4be16..f25b9777d11 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -53,35 +53,35 @@ void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
 		parseFrame(it->_key, it->_value->asObject(), _frameTable[it->_key]);
 	}
 
-	const Common::JSONObject& jMeta = json->asObject()["meta"]->asObject();
+	const Common::JSONObject &jMeta = json->asObject()["meta"]->asObject();
 	meta.image = jMeta["image"]->asString();
 }
 
-Common::String SpriteSheet::getKey(const Common::String& key) {
-	if(key.hasSuffixIgnoreCase("_en")) {
+Common::String SpriteSheet::getKey(const Common::String &key) {
+	if (key.hasSuffixIgnoreCase("_en")) {
 		Common::String lang(ConfMan.get("language"));
-		Common::String newKey(Common::String::format("%s%s", key.substr(0, key.size()-2).c_str(), lang.c_str()));
+		Common::String newKey(Common::String::format("%s%s", key.substr(0, key.size() - 2).c_str(), lang.c_str()));
 		return newKey;
 	}
 	return key;
 }
 
-const SpriteSheetFrame& SpriteSheet::getFrame(const Common::String& key) const {
-	if(key.hasSuffixIgnoreCase("_en")) {
+const SpriteSheetFrame &SpriteSheet::getFrame(const Common::String &key) const {
+	if (key.hasSuffixIgnoreCase("_en")) {
 		Common::String newKey(getKey(key));
-		if(_frameTable.contains(newKey))
+		if (_frameTable.contains(newKey))
 			return _frameTable[newKey];
 	}
 	return _frameTable[key];
 }
 
-const SpriteSheetFrame* SpriteSheet::frame(const Common::String& key) const {
-	if(key.hasSuffixIgnoreCase("_en")) {
+const SpriteSheetFrame *SpriteSheet::frame(const Common::String &key) const {
+	if (key.hasSuffixIgnoreCase("_en")) {
 		Common::String newKey(getKey(key));
-		if(_frameTable.contains(newKey))
+		if (_frameTable.contains(newKey))
 			return &_frameTable[newKey];
 	}
-	if(_frameTable.contains(key))
+	if (_frameTable.contains(key))
 		return &_frameTable[key];
 	return nullptr;
 }
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
index 048eed79e3f..7d2583bbe97 100644
--- a/engines/twp/spritesheet.h
+++ b/engines/twp/spritesheet.h
@@ -43,10 +43,10 @@ struct SpriteSheetFrame {
 
 struct SpriteSheet {
 	void parseSpriteSheet(const Common::String &contents);
-	const SpriteSheetFrame* frame(const Common::String& key) const;
-	const SpriteSheetFrame& getFrame(const Common::String& key) const;
+	const SpriteSheetFrame *frame(const Common::String &key) const;
+	const SpriteSheetFrame &getFrame(const Common::String &key) const;
 
-	static Common::String getKey(const Common::String& key);
+	static Common::String getKey(const Common::String &key);
 
 	SpriteSheetMetadata meta;
 	Common::HashMap<Common::String, SpriteSheetFrame> _frameTable;
diff --git a/engines/twp/sqgame.cpp b/engines/twp/sqgame.cpp
index d8147151978..72bf65ebab7 100644
--- a/engines/twp/sqgame.cpp
+++ b/engines/twp/sqgame.cpp
@@ -33,4 +33,4 @@ void regFunc(HSQUIRRELVM v, SQFUNCTION f, const SQChar *functionName, SQInteger
 	sq_pop(v, 1); // pops the root table
 }
 
-}
+} // namespace Twp
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index fa542138d29..39814601ea0 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -22,14 +22,14 @@
 #include "twp/lighting.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
-#include "squirrel/squirrel.h"
-#include "squirrel/sqvm.h"
-#include "squirrel/sqstring.h"
-#include "squirrel/sqstate.h"
-#include "squirrel/sqtable.h"
-#include "squirrel/sqstdaux.h"
-#include "squirrel/sqfuncproto.h"
-#include "squirrel/sqclosure.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/squirrel/sqvm.h"
+#include "twp/squirrel/sqstring.h"
+#include "twp/squirrel/sqstate.h"
+#include "twp/squirrel/sqtable.h"
+#include "twp/squirrel/sqstdaux.h"
+#include "twp/squirrel/sqfuncproto.h"
+#include "twp/squirrel/sqclosure.h"
 
 namespace Twp {
 
@@ -421,7 +421,7 @@ Common::SharedPtr<ThreadBase> sqthread(int id) {
 }
 
 Light *sqlight(int id) {
-	if(!g_twp->_room)
+	if (!g_twp->_room)
 		return nullptr;
 
 	for (size_t i = 0; i < MAX_LIGHTS; i++) {
@@ -458,7 +458,7 @@ Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v) {
 	}
 
 	auto it = Common::find_if(g_twp->_threads.begin(), g_twp->_threads.end(), GetThread(v));
-	if(it != g_twp->_threads.end())
+	if (it != g_twp->_threads.end())
 		return *it;
 	return nullptr;
 }
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index f35fe23d88b..f963238dc72 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -22,11 +22,12 @@
 #ifndef TWP_SQUTIL_H
 #define TWP_SQUTIL_H
 
-#include "squirrel/squirrel.h"
+#include "common/util.h"
 #include "common/str.h"
 #include "common/util.h"
 #include "twp/twp.h"
 #include "twp/vm.h"
+#include "twp/squirrel/squirrel.h"
 
 namespace Twp {
 
@@ -89,7 +90,7 @@ void setId(HSQOBJECT &o, int id);
 
 void sqgetarray(HSQUIRRELVM v, HSQOBJECT o, Common::Array<Common::String> &arr);
 SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::String> &arr);
-SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundDefinition> >&  arr);
+SQRESULT sqgetarray(HSQUIRRELVM v, int i, Common::Array<Common::SharedPtr<SoundDefinition> > &arr);
 
 template<typename TFunc>
 void sqgetitems(HSQOBJECT o, TFunc func) {
@@ -105,7 +106,7 @@ void sqgetitems(HSQOBJECT o, TFunc func) {
 	sq_pop(v, 2);
 }
 
-void sqgetpairs(HSQOBJECT obj, void func(const Common::String& key, HSQOBJECT& obj, void* data), void* data);
+void sqgetpairs(HSQOBJECT obj, void func(const Common::String &key, HSQOBJECT &obj, void *data), void *data);
 
 template<typename TFunc>
 void sqgetpairs(HSQOBJECT obj, TFunc func) {
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index cdb18b152f0..3fcb8481518 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/sqgame.h"
 #include "twp/twp.h"
+#include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/task.h"
@@ -550,7 +550,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		warning("exCommand EX_DISABLE_SAVESYSTEM: not implemented");
 		break;
 	case EX_SHOW_OPTIONS:
-    	g_twp->openMainMenuDialog();
+		g_twp->openMainMenuDialog();
 		break;
 	case EX_OPTIONS_MUSIC:
 		warning("TODO: exCommand EX_OPTIONS_MUSIC: not implemented");
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 5be24b306c7..91b6152daa4 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -19,8 +19,8 @@
  *
  */
 
-#include "twp/ids.h"
 #include "twp/twp.h"
+#include "twp/ids.h"
 #include "twp/thread.h"
 #include "twp/squtil.h"
 
@@ -59,7 +59,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 	_pauseable = true;
 
 	HSQUIRRELVM v = g_twp->getVm();
-	for (auto & _arg : _args) {
+	for (auto &_arg : _args) {
 		sq_addref(v, &_arg);
 	}
 	sq_addref(v, &_threadObj);
@@ -70,7 +70,7 @@ Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQ
 Thread::~Thread() {
 	debugC(kDebugGame, "delete thread %d, %s, global: %s", _id, _name.c_str(), _global ? "yes" : "no");
 	HSQUIRRELVM v = g_twp->getVm();
-	for (auto & _arg : _args) {
+	for (auto &_arg : _args) {
 		sq_release(v, &_arg);
 	}
 	sq_release(v, &_threadObj);
@@ -115,7 +115,7 @@ void Thread::stop() {
 	suspend();
 }
 
-Cutscene::Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
+Cutscene::Cutscene(const Common::String &name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj)
 	: _parentThreadId(parentThreadId),
 	  _threadObj(threadObj),
 	  _closure(closure),
@@ -132,7 +132,7 @@ Cutscene::Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT thr
 	g_twp->_inputState.setInputActive(false);
 	g_twp->_inputState.setShowCursor(false);
 	for (auto thread : g_twp->_threads) {
-			if (thread->isGlobal())
+		if (thread->isGlobal())
 			thread->pause();
 	}
 	HSQUIRRELVM vm = g_twp->getVm();
diff --git a/engines/twp/thread.h b/engines/twp/thread.h
index e02bdbdb52d..6d20cc2ff6b 100644
--- a/engines/twp/thread.h
+++ b/engines/twp/thread.h
@@ -44,7 +44,7 @@ public:
 		resume();
 	}
 
-	void setName(const Common::String& name) { _name = name; }
+	void setName(const Common::String &name) { _name = name; }
 	Common::String getName() const { return _name; }
 
 	int getId() const { return _id; }
@@ -61,7 +61,6 @@ public:
 	virtual void stop() = 0;
 
 protected:
-
 public:
 	float _waitTime = 0.f;
 	int _numFrames = 0;
@@ -77,7 +76,7 @@ protected:
 
 class Thread final : public ThreadBase {
 public:
-	Thread(const Common::String& name, bool global, HSQOBJECT threadObj, HSQOBJECT envObj, HSQOBJECT closureObj, const Common::Array<HSQOBJECT> args);
+	Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQOBJECT envObj, HSQOBJECT closureObj, const Common::Array<HSQOBJECT> args);
 	virtual ~Thread() override final;
 
 	virtual bool isGlobal() override final { return _global; }
@@ -105,7 +104,7 @@ enum CutsceneState {
 class Object;
 class Cutscene final : public ThreadBase {
 public:
-	Cutscene(const Common::String& name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
+	Cutscene(const Common::String &name, int parentThreadId, HSQOBJECT threadObj, HSQOBJECT closure, HSQOBJECT closureOverride, HSQOBJECT envObj);
 	~Cutscene() override final;
 
 	void start();
diff --git a/engines/twp/tsv.h b/engines/twp/tsv.h
index 12773f249c9..97f51da771f 100644
--- a/engines/twp/tsv.h
+++ b/engines/twp/tsv.h
@@ -29,14 +29,14 @@ namespace Twp {
 
 class TextDb {
 public:
-	void parseTsv(Common::SeekableReadStream& stream);
-	Common::String getText(const Common::String& text);
+	void parseTsv(Common::SeekableReadStream &stream);
+	Common::String getText(const Common::String &text);
 	Common::String getText(int id);
 
 private:
-  Common::HashMap<int, Common::String> _texts;
+	Common::HashMap<int, Common::String> _texts;
 };
 
-}
+} // namespace Twp
 
 #endif
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 5529c7b7e44..c279418e10b 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -23,13 +23,6 @@
 #include "config.h"
 #endif
 
-#ifdef USE_IMGUI
-#include "imgui/imgui.h"
-#include "imgui_impl_sdl2_scummvm.h"
-#include "imgui_impl_opengl3_scummvm.h"
-#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
-#endif
-
 #include "common/config-manager.h"
 #include "common/events.h"
 #include "common/savefile.h"
@@ -37,6 +30,13 @@
 #include "engines/util.h"
 #include "graphics/opengl/system_headers.h"
 
+#ifdef USE_IMGUI
+#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+#include "imgui/imgui.h"
+#include "imgui_impl_sdl2_scummvm.h"
+#include "imgui_impl_opengl3_scummvm.h"
+#endif
+
 #include "twp/twp.h"
 #include "twp/console.h"
 #include "twp/lighting.h"
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 9607c5a3cb6..9c248ec8566 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -22,12 +22,12 @@
 #ifndef TWP_UTIL_H
 #define TWP_UTIL_H
 
-#include "twp/ids.h"
-#include "twp/objectanimation.h"
-#include "math/vector2d.h"
-#include "math/matrix4.h"
 #include "common/formats/json.h"
 #include "common/rect.h"
+#include "math/vector2d.h"
+#include "math/matrix4.h"
+#include "twp/ids.h"
+#include "twp/objectanimation.h"
 
 namespace Twp {
 
@@ -108,7 +108,7 @@ size_t find_if(const Common::Array<T> &array, Pred p) {
 }
 
 template<typename T>
-size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T* o) {
+size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T *o) {
 	for (size_t i = 0; i < array.size(); i++) {
 		if (array[i].get() == o) {
 			return i;
diff --git a/engines/twp/vm.cpp b/engines/twp/vm.cpp
index d908afa5495..5617b35148b 100644
--- a/engines/twp/vm.cpp
+++ b/engines/twp/vm.cpp
@@ -22,17 +22,17 @@
 #include "twp/twp.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
-#include "squirrel/squirrel.h"
-#include "squirrel/sqvm.h"
-#include "squirrel/sqstring.h"
-#include "squirrel/sqstate.h"
-#include "squirrel/sqtable.h"
-#include "squirrel/sqstdstring.h"
-#include "squirrel/sqstdmath.h"
-#include "squirrel/sqstdio.h"
-#include "squirrel/sqstdaux.h"
-#include "squirrel/sqfuncproto.h"
-#include "squirrel/sqclosure.h"
+#include "twp/squirrel/squirrel.h"
+#include "twp/squirrel/sqvm.h"
+#include "twp/squirrel/sqstring.h"
+#include "twp/squirrel/sqstate.h"
+#include "twp/squirrel/sqtable.h"
+#include "twp/squirrel/sqstdstring.h"
+#include "twp/squirrel/sqstdmath.h"
+#include "twp/squirrel/sqstdio.h"
+#include "twp/squirrel/sqstdaux.h"
+#include "twp/squirrel/sqfuncproto.h"
+#include "twp/squirrel/sqclosure.h"
 
 namespace Twp {
 
diff --git a/engines/twp/yack.h b/engines/twp/yack.h
index ab886013446..75c235ce3de 100644
--- a/engines/twp/yack.h
+++ b/engines/twp/yack.h
@@ -52,7 +52,7 @@ enum class YackTokenId {
 // enumeration that lists all errors that can occur
 enum YackError {
 	ERR_NONE,              // no error
-	ERR_INVALIDYackToken,      // invalid YackToken
+	ERR_INVALIDYackToken,  // invalid YackToken
 	ERR_STRINGEXPECTED,    // string expected
 	ERR_COLONEXPECTED,     // `:` expected
 	ERR_COMMAEXPECTED,     // `,` expected
@@ -384,7 +384,7 @@ private:
 class YackParser {
 public:
 	YackParser() {}
-	YCompilationUnit* parse(Common::SeekableReadStream *stream);
+	YCompilationUnit *parse(Common::SeekableReadStream *stream);
 
 private:
 	bool match(const std::initializer_list<YackTokenId> &ids);


Commit: 48a37f03aa5b2154765376d8cd52a78c6dedcdec
    https://github.com/scummvm/scummvm/commit/48a37f03aa5b2154765376d8cd52a78c6dedcdec
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use GLuint and GLint in Gfx class

Changed paths:
    engines/twp/gfx.h


diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 46be59b70c0..83bcc0101de 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -190,7 +190,7 @@ private:
 
 private:
 	Texture _emptyTexture;
-	uint32 _vbo = 0, _ebo = 0;
+	GLuint _vbo = 0, _ebo = 0;
 	Shader _defaultShader;
 	Shader *_shader = nullptr;
 	Math::Matrix4 _mvp;
@@ -198,7 +198,7 @@ private:
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture *_texture = nullptr;
-	int32 _oldFbo = 0;
+	GLint _oldFbo = 0;
 };
 } // namespace Twp
 


Commit: 305fe167f45936756729dd4bcc3e67c1bdd720e8
    https://github.com/scummvm/scummvm/commit/305fe167f45936756729dd4bcc3e67c1bdd720e8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix Windows compilations

Changed paths:
    configure
    engines/twp/actorlib.cpp
    engines/twp/detection_tables.h
    engines/twp/enginedialogtarget.h
    engines/twp/genlib.cpp
    engines/twp/gfx.h
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/soundlib.cpp
    engines/twp/squtil.cpp
    engines/twp/syslib.cpp
    engines/twp/twp.cpp


diff --git a/configure b/configure
index f9dc9c74658..480b50c1ea1 100755
--- a/configure
+++ b/configure
@@ -6771,10 +6771,10 @@ echo "$_discord"
 
 
 #
-# Check for Imgui
+# Check for ImGui
 #
 if test "$_opengl" = yes ; then
-	echocheck "Imgui"
+	echocheck "ImGui"
 	define_in_config_if_yes "$_imgui" 'USE_IMGUI'
 	echo "$_imgui"
 fi
diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index c17bc6344d9..2d35bf2307e 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -101,12 +101,12 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> actor = sqactor(v, 2);
 		if (!actor)
 			return sq_throwerror(v, "failed to get actor");
-		int x, y;
+		SQInteger x, y;
 		if (SQ_FAILED(sqget(v, 3, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
-		debugC(kDebugActScript, "actorAt %s room %d, %d", actor->_key.c_str(), x, y);
+		debugC(kDebugActScript, "actorAt %s room %lld, %lld", actor->_key.c_str(), x, y);
 		actor->stopWalking();
 		actor->_node->setPos(Math::Vector2d(x, y));
 		return 0;
@@ -119,15 +119,15 @@ static SQInteger actorAt(HSQUIRRELVM v) {
 		Common::SharedPtr<Room> room = sqroom(v, 3);
 		if (!room)
 			return sq_throwerror(v, "failed to get room");
-		int x, y;
+		SQInteger x, y;
 		if (SQ_FAILED(sqget(v, 4, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 5, y)))
 			return sq_throwerror(v, "failed to get y");
-		int dir = 0;
+		SQInteger dir = 0;
 		if ((numArgs == 6) && SQ_FAILED(sqget(v, 6, dir)))
 			return sq_throwerror(v, "failed to get direction");
-		debugC(kDebugActScript, "actorAt %s, pos = (%d,%d), dir = %d", actor->_key.c_str(), x, y, dir);
+		debugC(kDebugActScript, "actorAt %s, pos = (%lld,%lld), dir = %lld", actor->_key.c_str(), x, y, dir);
 		actor->stopWalking();
 		actor->_node->setPos(Math::Vector2d(x, y));
 		actor->setFacing(getFacing(dir, actor->getFacing()));
@@ -161,7 +161,7 @@ static SQInteger actorColor(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int c;
+	SQInteger c;
 	if (SQ_FAILED(sqget(v, 3, c)))
 		return sq_throwerror(v, "failed to get color");
 	actor->_node->setColor(Color::fromRgba(c));
@@ -228,7 +228,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		Common::SharedPtr<Object> obj(sqobj(v, 3));
 		if (!obj)
 			return sq_throwerror(v, "failed to get object");
-		int dist;
+		SQInteger dist;
 		if (SQ_FAILED(sqget(v, 4, dist)))
 			return sq_throwerror(v, "failed to get distance");
 		if (actor->_room != obj->_room)
@@ -254,7 +254,7 @@ static SQInteger actorFace(HSQUIRRELVM v) {
 	}
 
 	if (sq_gettype(v, 3) == OT_INTEGER) {
-		int dir = 0;
+		SQInteger dir = 0;
 		if (SQ_FAILED(sqget(v, 3, dir)))
 			return sq_throwerror(v, "failed to get direction");
 		// FACE_FLIP ?
@@ -278,7 +278,7 @@ static SQInteger actorHidden(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int hidden = 0;
+	SQInteger hidden = 0;
 	if (SQ_FAILED(sqget(v, 3, hidden)))
 		return sq_throwerror(v, "failed to get hidden");
 	if (hidden && (g_twp->_actor == actor)) {
@@ -382,7 +382,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	switch (nArgs) {
 	case 2: {
-		int selectable;
+		SQInteger selectable;
 		if (SQ_FAILED(sqget(v, 2, selectable)))
 			return sq_throwerror(v, "failed to get selectable");
 		switch (selectable) {
@@ -408,7 +408,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 3, selectable)))
 			return sq_throwerror(v, "failed to get selectable");
 		if (sq_gettype(v, 2) == OT_INTEGER) {
-			int slot;
+			SQInteger slot;
 			if (SQ_FAILED(sqget(v, 2, slot)))
 				return sq_throwerror(v, "failed to get slot");
 			g_twp->_hud._actorSlots[slot - 1].selectable = selectable;
@@ -441,7 +441,7 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get actor");
 	switch (sq_gettype(v, 3)) {
 	case OT_INTEGER: {
-		int facing = 0;
+		SQInteger facing = 0;
 		if (SQ_FAILED(sqget(v, 3, facing)))
 			return sq_throwerror(v, "failed to get facing");
 		if (facing == 0)
@@ -451,11 +451,11 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 	} break;
 	case OT_TABLE: {
 		HSQOBJECT obj;
-		int back = FACE_BACK;
-		int front = FACE_FRONT;
-		int left = FACE_LEFT;
-		int right = FACE_RIGHT;
-		int reset = 0;
+		SQInteger back = FACE_BACK;
+		SQInteger front = FACE_FRONT;
+		SQInteger left = FACE_LEFT;
+		SQInteger right = FACE_RIGHT;
+		SQInteger reset = 0;
 		sq_getstackobj(v, 3, &obj);
 		sqgetf(v, obj, "back", back);
 		sqgetf(v, obj, "front", front);
@@ -498,7 +498,7 @@ static SQInteger actorPlayAnimation(HSQUIRRELVM v) {
 	Common::String animation;
 	if (SQ_FAILED(sqget(v, 3, animation)))
 		return sq_throwerror(v, "failed to get animation");
-	int loop = 0;
+	SQInteger loop = 0;
 	if ((sq_gettop(v) >= 4) && (SQ_FAILED(sqget(v, 4, loop))))
 		return sq_throwerror(v, "failed to get loop");
 	debugC(kDebugActScript, "Play anim %s %s loop=%s", actor->_key.c_str(), animation.c_str(), loop ? "yes" : "no");
@@ -515,7 +515,7 @@ static SQInteger actorRenderOffset(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int x, y;
+	SQInteger x, y;
 	if (SQ_FAILED(sqget(v, 3, x)))
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 4, y)))
@@ -551,7 +551,7 @@ static SQInteger actorTalkColors(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int color;
+	SQInteger color;
 	if (SQ_FAILED(sqget(v, 3, color)))
 		return sq_throwerror(v, "failed to get talk color");
 	actor->_talkColor = Color::rgb(color);
@@ -582,7 +582,7 @@ static SQInteger actorTurnTo(HSQUIRRELVM v) {
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
 	if (sq_gettype(v, 3) == OT_INTEGER) {
-		int facing = 0;
+		SQInteger facing = 0;
 		if (SQ_FAILED(sqget(v, 3, facing)))
 			return sq_throwerror(v, "failed to get facing");
 		actor->turn((Facing)facing);
@@ -600,7 +600,7 @@ static SQInteger actorTalkOffset(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqobj(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int x, y;
+	SQInteger x, y;
 	if (SQ_FAILED(sqget(v, 3, x)))
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 4, y)))
@@ -620,7 +620,7 @@ static SQInteger actorUsePos(HSQUIRRELVM v) {
 	else
 		usePos = obj->_usePos;
 	if (sq_gettop(v) == 4) {
-		int dir;
+		SQInteger dir;
 		if (SQ_FAILED(sqget(v, 4, dir)))
 			return sq_throwerror(v, "failed to get direction");
 		else
@@ -638,7 +638,7 @@ static SQInteger actorUseWalkboxes(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int useWalkboxes = 1;
+	SQInteger useWalkboxes = 1;
 	if (SQ_FAILED(sqget(v, 3, useWalkboxes)))
 		return sq_throwerror(v, "failed to get useWalkboxes");
 	actor->_useWalkboxes = useWalkboxes != 0;
@@ -670,7 +670,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int dist;
+	SQInteger dist;
 	if (SQ_FAILED(sqget(v, 3, dist)))
 		return sq_throwerror(v, "failed to get dist");
 	Math::Vector2d dir;
@@ -725,7 +725,7 @@ static SQInteger actorWalkSpeed(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	int x, y;
+	SQInteger x, y;
 	if (SQ_FAILED(sqget(v, 3, x)))
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 4, y)))
@@ -747,17 +747,17 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 		else
 			Object::walk(actor, obj);
 	} else if ((nArgs == 4) || (nArgs == 5)) {
-		int x, y;
+		SQInteger x, y;
 		if (SQ_FAILED(sqget(v, 3, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
 			return sq_throwerror(v, "failed to get y");
-		int facing = 0;
+		SQInteger facing = 0;
 		if (nArgs == 5) {
 			if (SQ_FAILED(sqget(v, 5, facing)))
 				return sq_throwerror(v, "failed to get dir");
 		}
-		Object::walk(actor, Vector2i(x, y), facing);
+		Object::walk(actor, Vector2i(static_cast<int>(x), static_cast<int>(y)), facing);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
@@ -765,7 +765,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 }
 
 static SQInteger addSelectableActor(HSQUIRRELVM v) {
-	int slot;
+	SQInteger slot;
 	if (SQ_FAILED(sqget(v, 2, slot)))
 		return sq_throwerror(v, "failed to get slot");
 	Common::SharedPtr<Object> actor = sqactor(v, 3);
@@ -804,7 +804,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 }
 
 static SQInteger flashSelectableActor(HSQUIRRELVM v) {
-	int time = 0;
+	SQInteger time = 0;
 	if (SQ_FAILED(sqget(v, 2, time)))
 		return sq_throwerror(v, "failed to get time");
 	g_twp->flashSelectableActor(time);
@@ -872,7 +872,7 @@ static SQInteger sayLine(HSQUIRRELVM v) {
 // See also:
 // - `mumbleLine method`
 static SQInteger sayLineAt(HSQUIRRELVM v) {
-	int x, y;
+	SQInteger x, y;
 	Common::String text;
 	float duration = -1.0f;
 	if (SQ_FAILED(sqget(v, 2, x)))
@@ -881,7 +881,7 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get y");
 	Color color;
 	if (sq_gettype(v, 4) == OT_INTEGER) {
-		int c;
+		SQInteger c;
 		if (SQ_FAILED(sqget(v, 4, c)))
 			return sq_throwerror(v, "failed to get color");
 		color = Color::rgb(c);
@@ -901,7 +901,7 @@ static SQInteger sayLineAt(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get text");
 	}
 
-	warning("TODO: saylineAt: (%d,%d) text=%s color=%s duration=%f", x, y, text.c_str(), color.toStr().c_str(), duration);
+	warning("TODO: saylineAt: (%lld,%lld) text=%s color=%s duration=%f", x, y, text.c_str(), color.toStr().c_str(), duration);
 	return 0;
 }
 
@@ -1009,7 +1009,7 @@ static SQInteger triggerActors(HSQUIRRELVM v) {
 }
 
 static SQInteger verbUIColors(HSQUIRRELVM v) {
-	int actorSlot;
+	SQInteger actorSlot;
 	if (SQ_FAILED(sqget(v, 2, actorSlot)))
 		return sq_throwerror(v, "failed to get actorSlot");
 	HSQOBJECT table;
@@ -1019,7 +1019,7 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get verb definitionTable");
 
 	// get mandatory colors
-	int
+	SQInteger
 		sentence = 0,
 		verbNormal = 0,
 		verbNormalTint = 0,
@@ -1036,10 +1036,10 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	sqgetf(table, "inventoryBackground", inventoryBackground);
 
 	// get optional colors
-	int retroNormal = verbNormal;
-	int retroHighlight = verbNormalTint;
-	int dialogNormal = verbNormal;
-	int dialogHighlight = verbHighlight;
+	SQInteger retroNormal = verbNormal;
+	SQInteger retroHighlight = verbNormalTint;
+	SQInteger dialogNormal = verbNormal;
+	SQInteger dialogHighlight = verbHighlight;
 	sqgetf(table, "retroNormal", retroNormal);
 	sqgetf(table, "retroHighlight", retroHighlight);
 	sqgetf(table, "dialogNormal", dialogNormal);
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index 974c924da76..a72bb6cc434 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -26,7 +26,7 @@ const PlainGameDescriptor twpGames[] = {
 	{0, 0}};
 
 const ADGameDescription gameDescriptions[] = {
-	// Thimbleweed Park - GOG version
+	// Thimbleweed Park - GOG/1.0.958
 	{
 		"twp",
 		"",
@@ -35,7 +35,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::kPlatformUnknown,
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOMIDI)},
-	// Thimbleweed Park - EPIC Games version
+	// Thimbleweed Park - EPIC Games/1.0.955
 	{
 		"twp",
 		"",
diff --git a/engines/twp/enginedialogtarget.h b/engines/twp/enginedialogtarget.h
index 205f46934f5..a026fd4c8ae 100644
--- a/engines/twp/enginedialogtarget.h
+++ b/engines/twp/enginedialogtarget.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TWP_DIALOG_TARGET_H
-#define TWP_DIALOG_TARGET_H
+#ifndef TWP_ENGINEDIALOG_TARGET_H
+#define TWP_ENGINEDIALOG_TARGET_H
 
 #include "twp/dialog.h"
 
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index b8259ff6baf..9423c552f3a 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -103,7 +103,7 @@ static SQInteger cameraAt(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	Math::Vector2d pos;
 	if (numArgs == 3) {
-		int x, y;
+		SQInteger x, y;
 		if (SQ_FAILED(sqget(v, 2, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 3, y)))
@@ -126,7 +126,7 @@ static SQInteger cameraAt(HSQUIRRELVM v) {
 
 // Sets how far the camera can pan.
 static SQInteger cameraBounds(HSQUIRRELVM v) {
-	int xMin, xMax, yMin, yMax;
+	SQInteger xMin, xMax, yMin, yMax;
 	if (SQ_FAILED(sqget(v, 2, xMin)))
 		return sq_throwerror(v, "failed to get xMin");
 	if (SQ_FAILED(sqget(v, 3, xMax)))
@@ -201,12 +201,12 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get duration");
 	} else if (numArgs == 4) {
 		if (sq_gettype(v, 2) == OT_INTEGER) {
-			int x;
+			SQInteger x;
 			if (SQ_FAILED(sqget(v, 2, x)))
 				return sq_throwerror(v, "failed to get x");
 			if (SQ_FAILED(sqget(v, 3, duration)))
 				return sq_throwerror(v, "failed to get duration");
-			int im;
+			SQInteger im;
 			if (SQ_FAILED(sqget(v, 4, im)))
 				return sq_throwerror(v, "failed to get interpolation method");
 			pos = Math::Vector2d(x, g_twp->getGfx().cameraPos().getY());
@@ -215,21 +215,21 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 			Common::SharedPtr<Object> obj = sqobj(v, 2);
 			if (SQ_FAILED(sqget(v, 3, duration)))
 				return sq_throwerror(v, "failed to get duration");
-			int im;
+			SQInteger im;
 			if (SQ_FAILED(sqget(v, 4, im)))
 				return sq_throwerror(v, "failed to get interpolation method");
 			pos = obj->_node->getAbsPos();
 			interpolation = (InterpolationKind)im;
 		}
 	} else if (numArgs == 5) {
-		int x, y;
+		SQInteger x, y;
 		if (SQ_FAILED(sqget(v, 2, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 3, y)))
 			return sq_throwerror(v, "failed to get y");
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
-		int im;
+		SQInteger im;
 		if (SQ_FAILED(sqget(v, 5, im)))
 			return sq_throwerror(v, "failed to get interpolation method");
 		pos = Math::Vector2d(x, y);
@@ -252,7 +252,7 @@ static SQInteger cameraPos(HSQUIRRELVM v) {
 
 // Converts an integer to a char.
 static SQInteger sqChr(HSQUIRRELVM v) {
-	int value;
+	SQInteger value;
 	sqget(v, 2, value);
 	Common::String s;
 	s += char(value);
@@ -274,10 +274,10 @@ static SQInteger cursorPosY(HSQUIRRELVM v) {
 
 static SQInteger distance(HSQUIRRELVM v) {
 	if (sq_gettype(v, 2) == OT_INTEGER) {
-		int num1;
+		SQInteger num1;
 		if (SQ_FAILED(sqget(v, 2, num1)))
 			return sq_throwerror(v, "failed to get num1");
-		int num2;
+		SQInteger num2;
 		if (SQ_FAILED(sqget(v, 3, num2)))
 			return sq_throwerror(v, "failed to get num2");
 		float d = abs(num1 - num2);
@@ -298,7 +298,7 @@ static SQInteger distance(HSQUIRRELVM v) {
 
 static SQInteger findScreenPosition(HSQUIRRELVM v) {
 	if (sq_gettype(v, 2) == OT_INTEGER) {
-		int verb;
+		SQInteger verb;
 		if (SQ_FAILED(sqget(v, 2, verb)))
 			return sq_throwerror(v, "failed to get verb");
 		ActorSlot *actorSlot = g_twp->_hud.actorSlot(g_twp->_actor);
@@ -522,12 +522,12 @@ static SQInteger ord(HSQUIRRELVM v) {
 // They will execute in reverse order (it's a stack).
 static SQInteger pushSentence(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
-	int id;
+	SQInteger id;
 	if (SQ_FAILED(sqget(v, 2, id)))
 		return sq_throwerror(v, "Failed to get verb id");
 
 	if (id == VERB_DIALOG) {
-		int choice;
+		SQInteger choice;
 		if (SQ_FAILED(sqget(v, 3, choice)))
 			return sq_throwerror(v, "Failed to get choice");
 		// use pushSentence with VERB_DIALOG
@@ -619,7 +619,7 @@ static SQInteger randomseed(HSQUIRRELVM v) {
 		return 1;
 	}
 	case 2: {
-		int seed = 0;
+		SQInteger seed = 0;
 		if (sq_gettype(v, 2) == OT_NULL) {
 			g_twp->getRandomSource().setSeed(g_twp->getRandomSource().generateNewSeed());
 			return 0;
@@ -673,10 +673,10 @@ static SQInteger setUserPref(HSQUIRRELVM v) {
 }
 
 static SQInteger setVerb(HSQUIRRELVM v) {
-	int actorSlot;
+	SQInteger actorSlot;
 	if (SQ_FAILED(sqget(v, 2, actorSlot)))
 		return sq_throwerror(v, "failed to get actor slot");
-	int verbSlot;
+	SQInteger verbSlot;
 	if (SQ_FAILED(sqget(v, 3, verbSlot)))
 		return sq_throwerror(v, "failed to get verb slot");
 	HSQOBJECT table;
@@ -684,12 +684,12 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get verb definitionTable");
 	if (!sq_istable(table))
 		return sq_throwerror(v, "verb definitionTable is not a table");
-	int id = 0;
+	SQInteger id = 0;
 	Common::String image;
 	Common::String text;
 	Common::String fun;
 	Common::String key;
-	int flags = 0;
+	SQInteger flags = 0;
 	sqgetf(table, "verb", id);
 	sqgetf(table, "text", text);
 	if (sqrawexists(table, "image"))
diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index 83bcc0101de..a01ec3b6958 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -108,9 +108,9 @@ public:
 	void capture(Common::Array<byte> &data);
 
 public:
-	uint32 id = 0;
+	GLuint id = 0;
 	int width = 0, height = 0;
-	uint fbo = 0;
+	GLuint fbo = 0;
 };
 
 class RenderTexture : public Texture {
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 14b75672418..eac720c9968 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -232,7 +232,7 @@ void WalkTo::actorArrived() {
 				n2Table = noun2->_table;
 			else
 				sq_resetobject(&n2Table);
-			sqcall(_obj->_table, funcName.c_str(), verb.id, noun1->_table, n2Table);
+			sqcall(_obj->_table, funcName.c_str(), (SQInteger)verb.id, noun1->_table, n2Table);
 		}
 
 		if (needsReach)
@@ -476,7 +476,7 @@ void Talking::disable() {
 }
 
 int Talking::onTalkieId(int id) {
-	int result = 0;
+	SQInteger result = 0;
 	sqcallfunc(result, "onTalkieID", _obj->_table, id);
 	if (result == 0)
 		result = id;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 421c9204628..235b1149185 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -218,11 +218,11 @@ void Object::trig(const Common::String &name) {
 			warning("Trigger #%d not found in object #%i (%s)", trigNum, getId(), _key.c_str());
 		}
 	} else {
-		int id = 0;
+		SQInteger id = 0;
 		sqgetf(sqrootTbl(g_twp->getVm()), name.substr(1), id);
 		Common::SharedPtr<SoundDefinition> sound = sqsounddef(id);
 		if (!sound)
-			warning("Cannot trig sound '%s', sound not found (id=%d, %s)", name.c_str(), id, _key.c_str());
+			warning("Cannot trig sound '%s', sound not found (id=%lld, %s)", name.c_str(), id, _key.c_str());
 		else
 			g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	}
@@ -252,7 +252,7 @@ float Object::popScale() const {
 }
 
 int Object::defaultVerbId() {
-	int result = VERB_LOOKAT;
+	SQInteger result = VERB_LOOKAT;
 	if (sqrawexists(_table, "defaultVerb"))
 		sqgetf(_table, "defaultVerb", result);
 	else if (isActor(getId())) {
@@ -366,7 +366,7 @@ Common::String Object::getIcon() {
 }
 
 int Object::getFlags() {
-	int result = 0;
+	SQInteger result = 0;
 	if (sqrawexists(_table, "flags"))
 		sqgetf(_table, "flags", result);
 	return result;
@@ -647,7 +647,7 @@ void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
 }
 
 int Object::flags() {
-	int result = 0;
+	SQInteger result = 0;
 	if (sqrawexists(_table, "flags"))
 		sqgetf(_table, "flags", result);
 	return result;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 8b7c6139e03..cacc0b3b086 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -91,7 +91,7 @@ static SQInteger createTextObject(HSQUIRRELVM v) {
 	TextVAlignment tvAlign = tvCenter;
 	float maxWidth = 0.0f;
 	if (sq_gettop(v) == 4) {
-		int align;
+		SQInteger align;
 		if (SQ_FAILED(sqget(v, 4, align)))
 			return sq_throwerror(v, "failed to get align");
 		uint64 hAlign = align & 0x0000000070000000;
@@ -164,7 +164,7 @@ static SQInteger deleteObject(HSQUIRRELVM v) {
 //     if (button == Phone.phoneReceiver) {    ... }
 // }
 static SQInteger findObjectAt(HSQUIRRELVM v) {
-	int x, y;
+	SQInteger x, y;
 	if (SQ_FAILED(sqget(v, 2, x)))
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 3, y)))
@@ -245,7 +245,7 @@ static SQInteger loopObjectState(HSQUIRRELVM v) {
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
 	if (sq_gettype(v, 3) == OT_INTEGER) {
-		int index;
+		SQInteger index;
 		if (SQ_FAILED(sqget(v, 3, index)))
 			return sq_throwerror(v, "failed to get state");
 		obj->play(index, true);
@@ -293,7 +293,7 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 		float t = 0.0f;
 		if (SQ_FAILED(sqget(v, 4, t)))
 			return sq_throwerror(v, "failed to get time");
-		int interpolation = 0;
+		SQInteger interpolation = 0;
 		if ((sq_gettop(v) >= 5) && (SQ_FAILED(sqget(v, 5, interpolation))))
 			interpolation = 0;
 		obj->setAlphaTo(Common::SharedPtr<AlphaTo>(new AlphaTo(t, obj, alpha, intToInterpolationMethod(interpolation))));
@@ -351,7 +351,7 @@ static SQInteger objectCenter(HSQUIRRELVM v) {
 static SQInteger objectColor(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		int color = 0;
+		SQInteger color = 0;
 		if (SQ_FAILED(sqget(v, 3, color)))
 			return sq_throwerror(v, "failed to get color");
 		obj->_node->setColor(Color::fromRgba(color));
@@ -366,7 +366,7 @@ static SQInteger objectDependentOn(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> parent = sqobj(v, 3);
 	if (!parent)
 		return sq_throwerror(v, "failed to get parent object");
-	int state = 0;
+	SQInteger state = 0;
 	if (SQ_FAILED(sqget(v, 4, state)))
 		return sq_throwerror(v, "failed to get state");
 	child->dependentOn(parent, state);
@@ -395,7 +395,7 @@ static SQInteger objectFPS(HSQUIRRELVM v) {
 static SQInteger objectHidden(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		int hidden = 0;
+		SQInteger hidden = 0;
 		sqget(v, 3, hidden);
 		debugC(kDebugObjScript, "Sets object visible %s/%s to %s", obj->_name.c_str(), obj->_key.c_str(), hidden == 0 ? "true" : "false");
 		obj->_node->setVisible(hidden == 0);
@@ -419,10 +419,10 @@ static SQInteger objectHotspot(HSQUIRRELVM v) {
 		sqpush(v, Rectf::fromPosAndSize(Math::Vector2d(obj->_hotspot.left + pos.getX(), obj->_hotspot.bottom + pos.getY()), Math::Vector2d(obj->_hotspot.width(), obj->_hotspot.height())));
 		return 1;
 	}
-	int left = 0;
-	int top = 0;
-	int right = 0;
-	int bottom = 0;
+	SQInteger left = 0;
+	SQInteger top = 0;
+	SQInteger right = 0;
+	SQInteger bottom = 0;
 	if (SQ_FAILED(sqget(v, 3, left)))
 		return sq_throwerror(v, "failed to get left");
 	if (SQ_FAILED(sqget(v, 4, top)))
@@ -457,7 +457,7 @@ static SQInteger objectIcon(HSQUIRRELVM v) {
 	case OT_ARRAY: {
 		Common::String icon;
 		Common::StringArray icons;
-		int fps = 10;
+		SQInteger fps = 10;
 		sq_push(v, 3);
 		sq_pushnull(v); // null iterator
 		if (SQ_SUCCEEDED(sq_next(v, -2)))
@@ -506,8 +506,8 @@ static SQInteger objectLit(HSQUIRRELVM v) {
 static SQInteger objectMoveTo(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		int x = 0;
-		int y = 0;
+		SQInteger x = 0;
+		SQInteger y = 0;
 		if (SQ_FAILED(sqget(v, 3, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
@@ -515,7 +515,7 @@ static SQInteger objectMoveTo(HSQUIRRELVM v) {
 		float duration = 0.0f;
 		if (SQ_FAILED(sqget(v, 5, duration)))
 			return sq_throwerror(v, "failed to get duration");
-		int interpolation = 0;
+		SQInteger interpolation = 0;
 		if ((sq_gettop(v) >= 6) && SQ_FAILED(sqget(v, 6, interpolation)))
 			interpolation = 0;
 		Math::Vector2d destPos = Math::Vector2d(x, y);
@@ -532,8 +532,8 @@ static SQInteger objectMoveTo(HSQUIRRELVM v) {
 static SQInteger objectOffset(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		int x = 0;
-		int y = 0;
+		SQInteger x = 0;
+		SQInteger y = 0;
 		if (SQ_FAILED(sqget(v, 3, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
@@ -547,8 +547,8 @@ static SQInteger objectOffset(HSQUIRRELVM v) {
 static SQInteger objectOffsetTo(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (obj) {
-		int x = 0;
-		int y = 0;
+		SQInteger x = 0;
+		SQInteger y = 0;
 		if (SQ_FAILED(sqget(v, 3, x)))
 			return sq_throwerror(v, "failed to get x");
 		if (SQ_FAILED(sqget(v, 4, y)))
@@ -588,7 +588,7 @@ static SQInteger objectParallaxLayer(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	int layer = 0;
+	SQInteger layer = 0;
 	if (SQ_FAILED(sqget(v, 3, layer)))
 		return sq_throwerror(v, "failed to get parallax layer");
 	g_twp->_room->objectParallaxLayer(obj, layer);
@@ -690,7 +690,7 @@ static SQInteger objectRotateTo(HSQUIRRELVM v) {
 		float duration = 0.0f;
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
-		int interpolation = 0;
+		SQInteger interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
 		obj->setRotateTo(Common::SharedPtr<RotateTo>(new RotateTo(duration, obj->_node.get(), rotation, intToInterpolationMethod(interpolation))));
@@ -719,7 +719,7 @@ static SQInteger objectScaleTo(HSQUIRRELVM v) {
 		float duration = 0.0f;
 		if (SQ_FAILED(sqget(v, 4, duration)))
 			return sq_throwerror(v, "failed to get duration");
-		int interpolation = 0;
+		SQInteger interpolation = 0;
 		if ((sq_gettop(v) >= 5) && SQ_FAILED(sqget(v, 5, interpolation)))
 			interpolation = 0;
 		obj->setScaleTo(Common::SharedPtr<ScaleTo>(new ScaleTo(duration, obj->_node.get(), scale, intToInterpolationMethod(interpolation))));
@@ -768,7 +768,7 @@ static SQInteger objectState(HSQUIRRELVM v) {
 		return 1;
 	}
 	if (nArgs == 3) {
-		int state;
+		SQInteger state;
 		if (SQ_FAILED(sqget(v, 3, state)))
 			return sq_throwerror(v, "failed to get state");
 		obj->setState(state);
@@ -807,7 +807,7 @@ static SQInteger objectSort(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	int zsort;
+	SQInteger zsort;
 	if (SQ_FAILED(sqget(v, 3, zsort)))
 		return sq_throwerror(v, "failed to get zsort");
 	obj->_node->setZSort(zsort);
@@ -823,7 +823,7 @@ static SQInteger objectUsePos(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	int x, y, dir;
+	SQInteger x, y, dir;
 	if (SQ_FAILED(sqget(v, 3, x)))
 		return sq_throwerror(v, "failed to get x");
 	if (SQ_FAILED(sqget(v, 4, y)))
@@ -883,7 +883,7 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object or actor");
-	int verb;
+	SQInteger verb;
 	if (SQ_FAILED(sqget(v, 3, verb)))
 		return sq_throwerror(v, "failed to get verb");
 
@@ -982,7 +982,7 @@ static SQInteger popInventory(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	int count;
+	SQInteger count;
 	if (SQ_FAILED(sqget(v, 3, count)))
 		return sq_throwerror(v, "failed to get count");
 	obj->setPop(count);
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 5a29c6f6b93..4b7b7c5679d 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -100,7 +100,7 @@ static Walkbox toWalkbox(const ClipperLib::Path &path) {
 	Common::Array<Vector2i> pts;
 	for (size_t i = 0; i < path.size(); i++) {
 		const ClipperLib::IntPoint &pt = path[i];
-		pts.push_back(Vector2i{pt.X, pt.Y});
+		pts.push_back(Vector2i(static_cast<int>(pt.X), static_cast<int>(pt.Y)));
 	}
 	return Walkbox(pts, ClipperLib::Orientation(path));
 }
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index bc662cc0f2c..16429aa90cb 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -47,29 +47,29 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
 	Vector2i pos1, pos2;
 	if (numArgs == 3) {
-		int x = 0;
+		SQInteger x = 0;
 		if (SQ_FAILED(sqget(v, 2, x)))
 			return sq_throwerror(v, "failed to get x");
-		int y = 0;
+		SQInteger y = 0;
 		if (SQ_FAILED(sqget(v, 3, y)))
 			return sq_throwerror(v, "failed to get y");
-		pos1 = Vector2i(x, y);
+		pos1 = Vector2i(static_cast<int>(x), static_cast<int>(y));
 		pos2 = pos1;
 	} else if (numArgs == 5) {
-		int x1 = 0;
+		SQInteger x1 = 0;
 		if (SQ_FAILED(sqget(v, 2, x1)))
 			return sq_throwerror(v, "failed to get x1");
-		int y1 = 0;
+		SQInteger y1 = 0;
 		if (SQ_FAILED(sqget(v, 3, y1)))
 			return sq_throwerror(v, "failed to get y1");
-		pos1 = Vector2i(x1, y1);
-		int x2 = 0;
+		pos1 = Vector2i(static_cast<int>(x1), static_cast<int>(y1));
+		SQInteger x2 = 0;
 		if (SQ_FAILED(sqget(v, 4, x2)))
 			return sq_throwerror(v, "failed to get x2");
-		int y2 = 0;
+		SQInteger y2 = 0;
 		if (SQ_FAILED(sqget(v, 5, y1)))
 			return sq_throwerror(v, "failed to get y2");
-		pos2 = Vector2i(x2, y2);
+		pos2 = Vector2i(static_cast<int>(x2), static_cast<int>(y2));
 	} else {
 		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
 	}
@@ -87,17 +87,17 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 }
 
 static SQInteger createLight(HSQUIRRELVM v) {
-	int color;
+	SQInteger color;
 	if (SQ_FAILED(sqget(v, 2, color)))
 		return sq_throwerror(v, "failed to get color");
-	int x;
+	SQInteger x;
 	if (SQ_FAILED(sqget(v, 3, x)))
 		return sq_throwerror(v, "failed to get x");
-	int y;
+	SQInteger y;
 	if (SQ_FAILED(sqget(v, 4, y)))
 		return sq_throwerror(v, "failed to get y");
 	Light *light = g_twp->_room->createLight(Color::rgb(color), Math::Vector2d(x, y));
-	debugC(kDebugRoomScript, "createLight(%d) -> %d", color, light->id);
+	debugC(kDebugRoomScript, "createLight(%lld) -> %d", color, light->id);
 	sqpush(v, light->id);
 	return 1;
 }
@@ -208,7 +208,7 @@ static SQInteger lightTurnOn(HSQUIRRELVM v) {
 static SQInteger lightZRange(HSQUIRRELVM v) {
 	const Light *light = sqlight(v, 2);
 	if (light) {
-		int nearY, farY;
+		SQInteger nearY, farY;
 		if (SQ_FAILED(sqget(v, 3, nearY)))
 			return sq_throwerror(v, "failed to get nearY");
 		if (SQ_FAILED(sqget(v, 4, farY)))
@@ -361,7 +361,7 @@ static SQInteger roomActors(HSQUIRRELVM v) {
 }
 
 static SQInteger roomEffect(HSQUIRRELVM v) {
-	int effect = 0;
+	SQInteger effect = 0;
 	if (SQ_FAILED(sqget(v, 2, effect)))
 		return sq_throwerror(v, "failed to get effect");
 	RoomEffect roomEffect = (RoomEffect)effect;
@@ -438,7 +438,7 @@ static SQInteger roomFade(HSQUIRRELVM v) {
 // roomLayer(GrateEntry, -2, NO)  // Make lights out layer invisible
 static SQInteger roomLayer(HSQUIRRELVM v) {
 	Common::SharedPtr<Room> r = sqroom(v, 2);
-	int layer;
+	SQInteger layer;
 	SQInteger enabled;
 	if (SQ_FAILED(sqget(v, 3, layer)))
 		return sq_throwerror(v, "failed to get layer");
@@ -468,7 +468,7 @@ static SQInteger roomLayer(HSQUIRRELVM v) {
 //     roomOverlayColor(0x800040AA)
 // }
 static SQInteger roomOverlayColor(HSQUIRRELVM v) {
-	int startColor;
+	SQInteger startColor;
 	SQInteger numArgs = sq_gettop(v);
 	if (SQ_FAILED(sqget(v, 2, startColor)))
 		return sq_throwerror(v, "failed to get startColor");
@@ -477,7 +477,7 @@ static SQInteger roomOverlayColor(HSQUIRRELVM v) {
 		room->_overlayTo->disable();
 	room->setOverlay(Color::fromRgba(startColor));
 	if (numArgs == 4) {
-		int endColor;
+		SQInteger endColor;
 		if (SQ_FAILED(sqget(v, 3, endColor)))
 			return sq_throwerror(v, "failed to get endColor");
 		float duration;
@@ -506,7 +506,7 @@ static SQInteger roomSize(HSQUIRRELVM v) {
 }
 
 static SQInteger setAmbientLight(HSQUIRRELVM v) {
-	int c = 0;
+	SQInteger c = 0;
 	if (SQ_FAILED(sqget(v, 2, c)))
 		return sq_throwerror(v, "failed to get color");
 	g_twp->_room->_lights._ambientLight = Color::rgb(c);
@@ -520,7 +520,7 @@ static SQInteger walkboxHidden(HSQUIRRELVM v) {
 	Common::String walkbox;
 	if (SQ_FAILED(sqget(v, 2, walkbox)))
 		return sq_throwerror(v, "failed to get object or walkbox");
-	int hidden = 0;
+	SQInteger hidden = 0;
 	if (SQ_FAILED(sqget(v, 3, hidden)))
 		return sq_throwerror(v, "failed to get object or hidden");
 	g_twp->_room->walkboxHidden(walkbox, hidden != 0);
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 836bdbba4dd..f3a41e9fff8 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -637,7 +637,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 	case OT_TABLE: {
 		Common::JSONObject jObj;
 		if (checkId) {
-			int id = 0;
+			SQInteger id = 0;
 			sqgetf(obj, "_id", id);
 			if (isActor(id)) {
 				Common::SharedPtr<Object> a(actor(id));
@@ -801,7 +801,7 @@ static Common::JSONValue *createJDialog() {
 static Common::JSONValue *createJEasyMode() {
 	HSQOBJECT g;
 	sqgetf("g", g);
-	int easyMode;
+	SQInteger easyMode;
 	sqgetf(g, "easy_mode", easyMode);
 	return new Common::JSONValue((long long int)easyMode);
 }
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index 122e192a9e9..aefa1f45608 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -48,12 +48,12 @@ static SQInteger actorSound(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor or object");
-	int trigNum = 0;
+	SQInteger trigNum = 0;
 	if (SQ_FAILED(sqget(v, 3, trigNum)))
 		return sq_throwerror(v, "failed to get trigger number");
 	SQInteger numSounds = sq_gettop(v) - 3;
 	if (numSounds != 0) {
-		int tmp = 0;
+		SQInteger tmp = 0;
 		if ((numSounds == 1) && (SQ_SUCCEEDED(sqget(v, 4, tmp))) && (tmp == 0)) {
 			obj->_triggers.erase(trigNum);
 		} else {
@@ -95,7 +95,7 @@ static SQInteger defineSound(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // fadeOutSound(soundElevatorMusic, 0.5)
 static SQInteger fadeOutSound(HSQUIRRELVM v) {
-	int sound = 0;
+	SQInteger sound = 0;
 	if (SQ_FAILED(sqget(v, 2, sound)))
 		return sq_throwerror(v, "failed to get sound");
 	float t;
@@ -110,7 +110,7 @@ static SQInteger fadeOutSound(HSQUIRRELVM v) {
 // .. code-block:: Squirrel
 // if (isSoundPlaying(soundElevatorMusic)) { ...}
 static SQInteger isSoundPlaying(HSQUIRRELVM v) {
-	int soundId;
+	SQInteger soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
 	sqpush(v, g_twp->_audio.playing(soundId));
@@ -126,7 +126,7 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 3);
 	if (!obj)
 		return sq_throwerror(v, "failed to get actor or object");
-	int loopTimes = 1;
+	SQInteger loopTimes = 1;
 	float fadeInTime = 0.0f;
 	if (nArgs >= 4) {
 		sqget(v, 4, loopTimes);
@@ -153,9 +153,9 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 static SQInteger playSound(HSQUIRRELVM v) {
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound) {
-		int soundId = 0;
+		SQInteger soundId = 0;
 		sqget(v, 2, soundId);
-		return sq_throwerror(v, Common::String::format("failed to get sound: %d", soundId).c_str());
+		return sq_throwerror(v, Common::String::format("failed to get sound: %lld", soundId).c_str());
 	}
 	int soundId = g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	sqpush(v, soundId);
@@ -204,7 +204,7 @@ static SQInteger loadSound(HSQUIRRELVM v) {
 //     _music = loopMusic(musicTempA)
 // }
 static SQInteger loopMusic(HSQUIRRELVM v) {
-	int loopTimes = -1;
+	SQInteger loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
@@ -222,7 +222,7 @@ static SQInteger loopMusic(HSQUIRRELVM v) {
 }
 
 static SQInteger loopObjectSound(HSQUIRRELVM v) {
-	int loopTimes = -1;
+	SQInteger loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
@@ -265,7 +265,7 @@ static SQInteger loopObjectSound(HSQUIRRELVM v) {
 //     loopSound(soundPhoneBusy, 3)
 // }
 static SQInteger loopSound(HSQUIRRELVM v) {
-	int loopTimes = -1;
+	SQInteger loopTimes = -1;
 	float fadeInTime = 0.f;
 	SQInteger numArgs = sq_gettop(v);
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
@@ -343,7 +343,7 @@ static SQInteger soundMixVolume(HSQUIRRELVM v) {
 // jiggleObject(quickieToilet, 0.25)
 // breaktime(0.2)
 static SQInteger soundVolume(HSQUIRRELVM v) {
-	int soundId;
+	SQInteger soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
 	float volume = 1.0f;
@@ -365,7 +365,7 @@ static SQInteger stopAllSounds(HSQUIRRELVM) {
 // .. code-block:: Squirrel
 // stopSound(soundElevatorMusic)
 static SQInteger stopSound(HSQUIRRELVM v) {
-	int soundId;
+	SQInteger soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
 	g_twp->_audio.stop(soundId);
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 39814601ea0..7b3b3fee71b 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -157,14 +157,6 @@ SQRESULT sqget(HSQUIRRELVM v, int i, SQInteger &value) {
 	return sq_getinteger(v, i, &value);
 }
 
-template<>
-SQRESULT sqget(HSQUIRRELVM v, int i, int &value) {
-	SQInteger itg;
-	SQRESULT result = sq_getinteger(v, i, &itg);
-	value = static_cast<int>(itg);
-	return result;
-}
-
 template<>
 SQRESULT sqget(HSQUIRRELVM v, int i, bool &value) {
 	SQInteger itg;
@@ -354,7 +346,7 @@ Common::SharedPtr<SoundDefinition> sqsounddef(int id) {
 }
 
 Common::SharedPtr<SoundDefinition> sqsounddef(HSQUIRRELVM v, int i) {
-	int id;
+	SQInteger id;
 	if (SQ_SUCCEEDED(sqget(v, i, id)))
 		return sqsounddef(id);
 	return nullptr;
@@ -398,7 +390,7 @@ void sqexec(HSQUIRRELVM v, const char *code, const char *filename) {
 }
 
 Common::SharedPtr<ThreadBase> sqthread(HSQUIRRELVM v, int i) {
-	int id;
+	SQInteger id;
 	if (SQ_SUCCEEDED(sqget(v, i, id)))
 		return sqthread(id);
 	return nullptr;
@@ -434,7 +426,7 @@ Light *sqlight(int id) {
 }
 
 Light *sqlight(HSQUIRRELVM v, int i) {
-	int id;
+	SQInteger id;
 	if (SQ_SUCCEEDED(sqget(v, i, id)))
 		return sqlight(id);
 	return nullptr;
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 3fcb8481518..16d628aaecd 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -176,7 +176,7 @@ static void threadTime(Common::SharedPtr<ThreadBase> tb, void *data) {
 static SQInteger breakhere(HSQUIRRELVM v) {
 	SQObjectType t = sq_gettype(v, 2);
 	if (t == OT_INTEGER) {
-		int numFrames;
+		SQInteger numFrames;
 		if (SQ_FAILED(sqget(v, 2, numFrames)))
 			return sq_throwerror(v, "failed to get numFrames");
 		return breakfunc(v, threadFrames, &numFrames);
@@ -324,15 +324,15 @@ static SQInteger breakwhileinputoff(HSQUIRRELVM v) {
 //     // Continue executing other code
 // }
 static SQInteger breakwhilerunning(HSQUIRRELVM v) {
-	int id = 0;
+	SQInteger id = 0;
 	if (sq_gettype(v, 2) == OT_INTEGER)
 		sqget(v, 2, id);
-	debugC(kDebugSysScript, "breakwhilerunning: %d", id);
+	debugC(kDebugSysScript, "breakwhilerunning: %lld", id);
 
 	Common::SharedPtr<ThreadBase> t = sqthread(id);
 	if (!t) {
 		if (!isSound(id)) {
-			warning("thread and sound not found: %d", id);
+			warning("thread and sound not found: %lld", id);
 			return 0;
 		}
 		return breakwhilecond(
@@ -446,7 +446,7 @@ private:
 // Breaks until specified sound has finished playing.
 // Once sound finishes, the method will continue running.
 static SQInteger breakwhilesound(HSQUIRRELVM v) {
-	int soundId = 0;
+	SQInteger soundId = 0;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
 	return breakwhilecond(v, SoundPlaying(soundId), "breakwhilesound(%d)", soundId);
@@ -504,12 +504,12 @@ static SQInteger dumpvar(HSQUIRRELVM v) {
 }
 
 static SQInteger exCommand(HSQUIRRELVM v) {
-	int cmd;
+	SQInteger cmd;
 	if (SQ_FAILED(sqget(v, 2, cmd)))
 		return sq_throwerror(v, "Failed to get command");
 	switch (cmd) {
 	case EX_AUTOSAVE_STATE: {
-		int enabled;
+		SQInteger enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
 		g_twp->_saveGameManager._autoSave = enabled != 0;
@@ -520,7 +520,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		}
 	} break;
 	case EX_ALLOW_SAVEGAMES: {
-		int enabled;
+		SQInteger enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
 		g_twp->_saveGameManager._allowSaveGame = enabled != 0;
@@ -560,7 +560,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		warning("exCommand EX_FORCE_TALKIE_TEXT: not implemented");
 		break;
 	default:
-		warning("exCommand(%d) not implemented", cmd);
+		warning("exCommand(%lld) not implemented", cmd);
 		break;
 	}
 	return 0;
@@ -639,7 +639,7 @@ static SQInteger sysInputState(HSQUIRRELVM v) {
 		return 1;
 	}
 	if (numArgs == 2) {
-		int state;
+		SQInteger state;
 		if (SQ_FAILED(sqget(v, 2, state)))
 			return sq_throwerror(v, "failed to get state");
 		g_twp->_inputState.setState((InputStateFlag)state);
@@ -705,7 +705,7 @@ static SQInteger microTime(HSQUIRRELVM v) {
 }
 
 static SQInteger moveCursorTo(HSQUIRRELVM v) {
-	int x, y;
+	SQInteger x, y;
 	if (SQ_FAILED(sqget(v, 2, x)))
 		return sq_throwerror(v, "Failed to get x");
 	if (SQ_FAILED(sqget(v, 3, y)))
@@ -721,7 +721,7 @@ static SQInteger moveCursorTo(HSQUIRRELVM v) {
 
 // removeCallback(id: int) remove the given callback
 static SQInteger removeCallback(HSQUIRRELVM v) {
-	int id = 0;
+	SQInteger id = 0;
 	if (SQ_FAILED(sqget(v, 2, id)))
 		return sq_throwerror(v, "failed to get callback");
 	for (size_t i = 0; i < g_twp->_callbacks.size(); i++) {
@@ -750,7 +750,7 @@ static SQInteger startglobalthread(HSQUIRRELVM v) {
 // * `startthread`
 // * `startglobalthread`
 static SQInteger stopthread(HSQUIRRELVM v) {
-	int id = 0;
+	SQInteger id = 0;
 	if (SQ_FAILED(sqget(v, 2, id))) {
 		sqpush(v, 0);
 		return 1;
@@ -798,7 +798,7 @@ static SQInteger threadpauseable(HSQUIRRELVM v) {
 	Common::SharedPtr<ThreadBase> t = sqthread(v, 2);
 	if (!t)
 		return sq_throwerror(v, "failed to get thread");
-	int pauseable = 0;
+	SQInteger pauseable = 0;
 	if (SQ_FAILED(sqget(v, 3, pauseable)))
 		return sq_throwerror(v, "failed to get pauseable");
 	t->_pauseable = pauseable != 0;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c279418e10b..328bee0164e 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1074,7 +1074,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 
 			if (sqrawexists(obj->_table, "initState")) {
 				// info fmt"initState {obj.key}"
-				int state;
+				SQInteger state;
 				sqgetf(obj->_table, "initState", state);
 				obj->setState(state, true);
 			} else {


Commit: 85aa9525172df62ec37d2a332225c2052e04d92d
    https://github.com/scummvm/scummvm/commit/85aa9525172df62ec37d2a332225c2052e04d92d
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Make it clearer ImGui flag

Changed paths:
    configure


diff --git a/configure b/configure
index 480b50c1ea1..1578862b132 100755
--- a/configure
+++ b/configure
@@ -6773,10 +6773,13 @@ echo "$_discord"
 #
 # Check for ImGui
 #
+echocheck "ImGui"
+
 if test "$_opengl" = yes ; then
-	echocheck "ImGui"
-	define_in_config_if_yes "$_imgui" 'USE_IMGUI'
-	echo "$_imgui"
+  define_in_config_if_yes "$_imgui" 'USE_IMGUI'
+  echo "$_imgui"
+else
+  echo "no (requires OpenGL)"
 fi
 
 #


Commit: 2c84ed86b84942f2b1e5b9f883f4f26d0d3dcb1a
    https://github.com/scummvm/scummvm/commit/2c84ed86b84942f2b1e5b9f883f4f26d0d3dcb1a
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Reuse normal settings on the "Audio" tab

Changed paths:
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h
    engines/twp/metaengine.cpp
    engines/twp/motor.cpp


diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index 9c2ed7c46f4..61ee2b5a5cc 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -35,26 +35,28 @@ TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Comm
 	text->setAlign(Graphics::TextAlign::kTextAlignStart);
 
 	_enableToiletPaperOverGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck1",
+																// I18N: Setting to switch toiled paper to be shown as "over".
 																_("Toilet paper over"),
 																_("The toilet paper in some toilets will be shown “over”.\nIt’s a joke option that has no effects on the gameplay.."));
 	_enableAnnoyingInJokesGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck2",
+																// I18N: Setting to enable or disable additional jokes in the game.
 																_("Annoying in-jokes"),
 																_("The game will include in-jokes and references to past adventure games, in the form of both dialogues and objects.\nThere is a game achievement that can be obtained only if the in-jokes option is switched on."));
 
-	text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.ConrolsLabel", _("Conrols:"));
+	text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsLabel", _("Controls:"));
 	text->setAlign(Graphics::TextAlign::kTextAlignStart);
 
+	// I18N: Setting to invert verb colors or keep the original verb colors.
 	_enableInvertVerbColorsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck1", _("Invert verb colors"), _(""));
+	// I18N: Setting to use retro or modern fonts.
 	_enableRetroFontsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck2", _("Retro Fonts"), _(""));
+	// I18N: Setting to use retro or modern verbs.
 	_enableRetroVerbsGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck3", _("Retro Verbs"), _(""));
+	// I18N: Setting to use classic sentence or modern sentence.
 	_enableClassicSentenceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.ControlsCheck4", _("Classic Sentence"), _(""));
 
-	text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.TextAndSpeechLabel", _("Text and Speech:"));
-	text->setAlign(Graphics::TextAlign::kTextAlignStart);
-
-	_enableDisplayTextGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Display Text"), _(""));
-	_enableHearVoiceGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck2", _("Hear Voice"), _(""));
-	_enableDLC = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck3", _("Ransome *unbeeped* (DLC)"), _(""));
+	// I18N: Settings to enable or disable Ransome unbeeped DLC.
+	_enableDLC = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Ransome *unbeeped* (DLC)"), _(""));
 
 	_langGUIDropdown = new GUI::PopUpWidget(widgetsBoss(), "TwpGameOptionsDialog.LangDropDown");
 	_langGUIDropdown->appendEntry(_("English"));
@@ -73,16 +75,13 @@ void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Comm
 		.addWidget("VideoLabel", "OptionsLabel")
 		.addWidget("VideoCheck1", "Checkbox")
 		.addWidget("VideoCheck2", "Checkbox")
-		.addWidget("ConrolsLabel", "OptionsLabel")
+		.addWidget("ControlsLabel", "OptionsLabel")
 		.addWidget("ControlsCheck1", "Checkbox")
 		.addWidget("ControlsCheck2", "Checkbox")
 		.addWidget("ControlsCheck3", "Checkbox")
 		.addWidget("ControlsCheck4", "Checkbox")
-		.addWidget("TextAndSpeechLabel", "OptionsLabel")
 		.addWidget("LangDropDown", "PopUp")
-		.addWidget("TextCheck1", "Checkbox")
-		.addWidget("TextCheck2", "Checkbox")
-		.addWidget("TextCheck3", "Checkbox");
+		.addWidget("TextCheck1", "Checkbox");
 
 	layouts.closeLayout().closeDialog();
 }
@@ -94,8 +93,6 @@ void TwpOptionsContainerWidget::load() {
 	_enableRetroFontsGUICheckbox->setState(ConfMan.getBool("retroFonts", _domain));
 	_enableRetroVerbsGUICheckbox->setState(ConfMan.getBool("retroVerbs", _domain));
 	_enableClassicSentenceGUICheckbox->setState(ConfMan.getBool("hudSentence", _domain));
-	_enableDisplayTextGUICheckbox->setState(ConfMan.getBool("talkiesShowText", _domain));
-	_enableHearVoiceGUICheckbox->setState(ConfMan.getBool("talkiesHearVoice", _domain));
 	_enableDLC->setState(ConfMan.getBool("ransomeUnbeeped", _domain));
 	Common::String lang = ConfMan.get("language", _domain);
 	int index = 0;
@@ -115,8 +112,6 @@ bool TwpOptionsContainerWidget::save() {
 	ConfMan.setBool("retroFonts", _enableRetroFontsGUICheckbox->getState(), _domain);
 	ConfMan.setBool("retroVerbs", _enableRetroVerbsGUICheckbox->getState(), _domain);
 	ConfMan.setBool("hudSentence", _enableClassicSentenceGUICheckbox->getState(), _domain);
-	ConfMan.setBool("talkiesShowText", _enableDisplayTextGUICheckbox->getState(), _domain);
-	ConfMan.setBool("talkiesHearVoice", _enableHearVoiceGUICheckbox->getState(), _domain);
 	ConfMan.setBool("ransomeUnbeeped", _enableDLC->getState(), _domain);
 	ConfMan.set("language", lang_items[_langGUIDropdown->getSelected()], _domain);
 	return true;
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index ae9c3867c46..2ba84184583 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -45,8 +45,6 @@ private:
 	GUI::CheckboxWidget *_enableRetroFontsGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableRetroVerbsGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableClassicSentenceGUICheckbox = nullptr;
-	GUI::CheckboxWidget *_enableDisplayTextGUICheckbox = nullptr;
-	GUI::CheckboxWidget *_enableHearVoiceGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableDLC = nullptr;
 	GUI::PopUpWidget *_langGUIDropdown = nullptr;
 };
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index c58bd3323af..e01e3c52d78 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -81,8 +81,6 @@ void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
 	ConfMan.registerDefault("retroFonts", false);
 	ConfMan.registerDefault("retroVerbs", false);
 	ConfMan.registerDefault("hudSentence", false);
-	ConfMan.registerDefault("talkiesShowText", true);
-	ConfMan.registerDefault("talkiesHearVoice", true);
 	ConfMan.registerDefault("language", "en");
 }
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index eac720c9968..fbcfca20e0f 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -359,8 +359,8 @@ void Talking::update(float elapsed) {
 }
 
 int Talking::loadActorSpeech(const Common::String &name) {
-	if (!ConfMan.getBool("talkiesHearVoice")) {
-		debugC(kDebugGame, "talking %s: talkiesHearVoice: false", _obj->_key.c_str());
+	if (ConfMan.getBool("speech_mute")) {
+		debugC(kDebugGame, "talking %s: speech_mute: true", _obj->_key.c_str());
 		return 0;
 	}
 
@@ -451,20 +451,24 @@ void Talking::say(const Common::String &text) {
 	if (_obj->_sayNode) {
 		_obj->_sayNode->remove();
 	}
-	Text text2("sayline", txt, thCenter, tvCenter, SCREEN_WIDTH * 3.f / 4.f, _color);
-	_obj->_sayNode = Common::SharedPtr<TextNode>(new TextNode());
-	_obj->_sayNode->setText(text2);
-	_obj->_sayNode->setColor(_color);
-	_node = _obj->_sayNode;
-	Math::Vector2d pos = g_twp->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
-
-	// clamp position to keep it on screen
-	pos.setX(Twp::clamp(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
-	pos.setY(Twp::clamp(pos.getY(), 10.f + text2.getBounds().getY(), SCREEN_HEIGHT - text2.getBounds().getY()));
-
-	_obj->_sayNode->setPos(pos);
-	_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
-	g_twp->_screenScene.addChild(_obj->_sayNode.get());
+
+	if (ConfMan.getBool("subtitles")) {
+		Text text2("sayline", txt, thCenter, tvCenter, SCREEN_WIDTH * 3.f / 4.f, _color);
+		_obj->_sayNode = Common::SharedPtr<TextNode>(new TextNode());
+		_obj->_sayNode->setText(text2);
+		_obj->_sayNode->setColor(_color);
+		_node = _obj->_sayNode;
+		Math::Vector2d pos = g_twp->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
+
+		// clamp position to keep it on screen
+		pos.setX(Twp::clamp(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
+		pos.setY(Twp::clamp(pos.getY(), 10.f + text2.getBounds().getY(), SCREEN_HEIGHT - text2.getBounds().getY()));
+
+		_obj->_sayNode->setPos(pos);
+		_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
+		g_twp->_screenScene.addChild(_obj->_sayNode.get());
+	}
+
 	_elapsed = 0.f;
 }
 
@@ -472,7 +476,8 @@ void Talking::disable() {
 	Motor::disable();
 	_texts.clear();
 	_obj->setHeadIndex(1);
-	_node->remove();
+	if(_node)
+		_node->remove();
 }
 
 int Talking::onTalkieId(int id) {


Commit: 8a4d11eb64dff866ecf798df8755154cdca7c1f9
    https://github.com/scummvm/scummvm/commit/8a4d11eb64dff866ecf798df8755154cdca7c1f9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix wrong error message in ggpack

Changed paths:
    engines/twp/ggpack.cpp


diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 0e60620d745..1b0a7dc576f 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -409,7 +409,7 @@ Common::JSONValue *GGHashMapDecoder::readValue() {
 		return ggType == GGP_INTEGER ? new Common::JSONValue((long long int)atol(num_str.c_str())) : new Common::JSONValue(atof(num_str.c_str()));
 	}
 	default:
-		error("Not Implemented: value type {ggType}");
+		error("Not Implemented: value type: %d", ggType);
 		return new Common::JSONValue();
 	}
 }


Commit: 4f34f5e172dc999e39fe9e3f8f52ba638821d6bf
    https://github.com/scummvm/scummvm/commit/4f34f5e172dc999e39fe9e3f8f52ba638821d6bf
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix define in enginedialogtarget.h

Changed paths:
    engines/twp/enginedialogtarget.h


diff --git a/engines/twp/enginedialogtarget.h b/engines/twp/enginedialogtarget.h
index a026fd4c8ae..a23c2b0866c 100644
--- a/engines/twp/enginedialogtarget.h
+++ b/engines/twp/enginedialogtarget.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TWP_ENGINEDIALOG_TARGET_H
-#define TWP_ENGINEDIALOG_TARGET_H
+#ifndef TWP_ENGINEDIALOGTARGET_H
+#define TWP_ENGINEDIALOGTARGET_H
 
 #include "twp/dialog.h"
 


Commit: 276e16cbdeedabf72f075547634d64ea5b60d1db
    https://github.com/scummvm/scummvm/commit/276e16cbdeedabf72f075547634d64ea5b60d1db
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use and restrict common language option

Changed paths:
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/dialogs.cpp
    engines/twp/dialogs.h


diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 800bd854c2c..199d7891318 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -39,8 +39,20 @@ const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
 	{Twp::kDebugGame, "Game", "Game debug level"},
 	DEBUG_CHANNEL_END};
 
-TwpMetaEngineDetection::TwpMetaEngineDetection() : AdvancedMetaEngineDetection(Twp::gameDescriptions,
-																			   sizeof(ADGameDescription), Twp::twpGames) {
+TwpMetaEngineDetection::TwpMetaEngineDetection()
+	: AdvancedMetaEngineDetection(Twp::gameDescriptions,
+								  sizeof(ADGameDescription), Twp::twpGames) {
+}
+
+DetectedGame TwpMetaEngineDetection::toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const {
+	DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame, extraInfo);
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::DE_DEU));
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ES_ESP));
+	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::RU_RUS));
+	return game;
 }
 
 REGISTER_PLUGIN_STATIC(TWP_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TwpMetaEngineDetection);
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index f5bebe1ff12..aad0c984f8f 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -70,6 +70,9 @@ public:
 	const DebugChannelDef *getDebugChannels() const override {
 		return debugFlagList;
 	}
+
+	/** Convert an AD game description into the shared game description format. */
+	DetectedGame toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const override;
 };
 
 #endif // TWP_DETECTION_H
diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index 61ee2b5a5cc..0cfbaca0629 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -28,8 +28,6 @@
 
 namespace Twp {
 
-static const char *lang_items[] = {"en", "fr", "it", "de", "es"};
-
 TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) : OptionsContainerWidget(boss, name, "TwpGameOptionsDialog", false, domain) {
 	GUI::StaticTextWidget *text = new GUI::StaticTextWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoLabel", _("Video:"));
 	text->setAlign(Graphics::TextAlign::kTextAlignStart);
@@ -57,13 +55,6 @@ TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Comm
 
 	// I18N: Settings to enable or disable Ransome unbeeped DLC.
 	_enableDLC = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.TextCheck1", _("Ransome *unbeeped* (DLC)"), _(""));
-
-	_langGUIDropdown = new GUI::PopUpWidget(widgetsBoss(), "TwpGameOptionsDialog.LangDropDown");
-	_langGUIDropdown->appendEntry(_("English"));
-	_langGUIDropdown->appendEntry(_("French"));
-	_langGUIDropdown->appendEntry(_("Italian"));
-	_langGUIDropdown->appendEntry(_("German"));
-	_langGUIDropdown->appendEntry(_("Spanish"));
 }
 
 void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
@@ -80,7 +71,6 @@ void TwpOptionsContainerWidget::defineLayout(GUI::ThemeEval &layouts, const Comm
 		.addWidget("ControlsCheck2", "Checkbox")
 		.addWidget("ControlsCheck3", "Checkbox")
 		.addWidget("ControlsCheck4", "Checkbox")
-		.addWidget("LangDropDown", "PopUp")
 		.addWidget("TextCheck1", "Checkbox");
 
 	layouts.closeLayout().closeDialog();
@@ -94,15 +84,6 @@ void TwpOptionsContainerWidget::load() {
 	_enableRetroVerbsGUICheckbox->setState(ConfMan.getBool("retroVerbs", _domain));
 	_enableClassicSentenceGUICheckbox->setState(ConfMan.getBool("hudSentence", _domain));
 	_enableDLC->setState(ConfMan.getBool("ransomeUnbeeped", _domain));
-	Common::String lang = ConfMan.get("language", _domain);
-	int index = 0;
-	for (int i = 0; i < ARRAYSIZE(lang_items); i++) {
-		if (lang == lang_items[i]) {
-			index = i;
-			break;
-		}
-	}
-	_langGUIDropdown->setSelected(index);
 }
 
 bool TwpOptionsContainerWidget::save() {
@@ -113,7 +94,6 @@ bool TwpOptionsContainerWidget::save() {
 	ConfMan.setBool("retroVerbs", _enableRetroVerbsGUICheckbox->getState(), _domain);
 	ConfMan.setBool("hudSentence", _enableClassicSentenceGUICheckbox->getState(), _domain);
 	ConfMan.setBool("ransomeUnbeeped", _enableDLC->getState(), _domain);
-	ConfMan.set("language", lang_items[_langGUIDropdown->getSelected()], _domain);
 	return true;
 }
 
diff --git a/engines/twp/dialogs.h b/engines/twp/dialogs.h
index 2ba84184583..b8354aa5b0f 100644
--- a/engines/twp/dialogs.h
+++ b/engines/twp/dialogs.h
@@ -46,7 +46,6 @@ private:
 	GUI::CheckboxWidget *_enableRetroVerbsGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableClassicSentenceGUICheckbox = nullptr;
 	GUI::CheckboxWidget *_enableDLC = nullptr;
-	GUI::PopUpWidget *_langGUIDropdown = nullptr;
 };
 
 } // namespace Twp


Commit: 1be4d5b5a5a8dc2ab70f30d298f67eea70b901f0
    https://github.com/scummvm/scummvm/commit/1be4d5b5a5a8dc2ab70f30d298f67eea70b901f0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Make OpenGL ES 2.0 compatible

Thanks to larsamannen.
OpenGL ES 2.0 restricts the wrap modes for non power of two, NPOT,
textures (where width and height values are not power of two).
OpenGL ES 2.0 require clamping to edge for NPOT textures to not result
in a black texture when rendering.

Make sure to set wrap mode to CLAMP_EDGE and the filtering mode to
linear when loading and creating textures with NPOT sizes.

Changed paths:
    engines/twp/gfx.cpp


diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 36852ebfc81..9ef90a4cd0e 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -69,8 +69,8 @@ void Texture::load(const Graphics::Surface &surface) {
 	glGenTextures(1, &id);
 	glBindTexture(GL_TEXTURE_2D, id);
 	// set the texture wrapping/filtering options (on the currently bound texture object)
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 	glPixelStorei(GL_UNPACK_ALIGNMENT, surface.format.bytesPerPixel);
@@ -110,9 +110,11 @@ RenderTexture::RenderTexture(Math::Vector2d size) {
 	// then create an empty texture
 	glGenTextures(1, &id);
 	glBindTexture(GL_TEXTURE_2D, id);
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
 	glBindTexture(GL_TEXTURE_2D, 0);
 
 	// then attach it to framebuffer object


Commit: b840146b6c9fc144373c2adb5777463b1f7b55bc
    https://github.com/scummvm/scummvm/commit/b840146b6c9fc144373c2adb5777463b1f7b55bc
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Move imgui into graphics/

Changed paths:
  A graphics/imgui/LICENSE.txt
  A graphics/imgui/backends/imgui_impl_opengl3_loader.h
  A graphics/imgui/backends/imgui_impl_opengl3_scummvm.cpp
  A graphics/imgui/backends/imgui_impl_opengl3_scummvm.h
  A graphics/imgui/backends/imgui_impl_sdl2_scummvm.cpp
  A graphics/imgui/backends/imgui_impl_sdl2_scummvm.h
  A graphics/imgui/imconfig.h
  A graphics/imgui/imgui.cpp
  A graphics/imgui/imgui.h
  A graphics/imgui/imgui_demo.cpp
  A graphics/imgui/imgui_draw.cpp
  A graphics/imgui/imgui_internal.h
  A graphics/imgui/imgui_tables.cpp
  A graphics/imgui/imgui_widgets.cpp
  A graphics/imgui/imstb_rectpack.h
  A graphics/imgui/imstb_textedit.h
  A graphics/imgui/imstb_truetype.h
  R engines/twp/imgui/.editorconfig
  R engines/twp/imgui/.gitattributes
  R engines/twp/imgui/.github/FUNDING.yml
  R engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
  R engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
  R engines/twp/imgui/.github/pull_request_template.md
  R engines/twp/imgui/.github/workflows/build.yml
  R engines/twp/imgui/.github/workflows/scheduled.yml
  R engines/twp/imgui/.github/workflows/static-analysis.yml
  R engines/twp/imgui/.gitignore
  R engines/twp/imgui/LICENSE.txt
  R engines/twp/imgui/backends/imgui_impl_allegro5.cpp
  R engines/twp/imgui/backends/imgui_impl_allegro5.h
  R engines/twp/imgui/backends/imgui_impl_android.cpp
  R engines/twp/imgui/backends/imgui_impl_android.h
  R engines/twp/imgui/backends/imgui_impl_dx10.cpp
  R engines/twp/imgui/backends/imgui_impl_dx10.h
  R engines/twp/imgui/backends/imgui_impl_dx11.cpp
  R engines/twp/imgui/backends/imgui_impl_dx11.h
  R engines/twp/imgui/backends/imgui_impl_dx12.cpp
  R engines/twp/imgui/backends/imgui_impl_dx12.h
  R engines/twp/imgui/backends/imgui_impl_dx9.cpp
  R engines/twp/imgui/backends/imgui_impl_dx9.h
  R engines/twp/imgui/backends/imgui_impl_glfw.cpp
  R engines/twp/imgui/backends/imgui_impl_glfw.h
  R engines/twp/imgui/backends/imgui_impl_glut.cpp
  R engines/twp/imgui/backends/imgui_impl_glut.h
  R engines/twp/imgui/backends/imgui_impl_metal.h
  R engines/twp/imgui/backends/imgui_impl_metal.mm
  R engines/twp/imgui/backends/imgui_impl_opengl2.cpp
  R engines/twp/imgui/backends/imgui_impl_opengl2.h
  R engines/twp/imgui/backends/imgui_impl_opengl3.cpp
  R engines/twp/imgui/backends/imgui_impl_opengl3.h
  R engines/twp/imgui/backends/imgui_impl_opengl3_loader.h
  R engines/twp/imgui/backends/imgui_impl_osx.h
  R engines/twp/imgui/backends/imgui_impl_osx.mm
  R engines/twp/imgui/backends/imgui_impl_sdl2.cpp
  R engines/twp/imgui/backends/imgui_impl_sdl2.h
  R engines/twp/imgui/backends/imgui_impl_sdl3.cpp
  R engines/twp/imgui/backends/imgui_impl_sdl3.h
  R engines/twp/imgui/backends/imgui_impl_sdlrenderer2.cpp
  R engines/twp/imgui/backends/imgui_impl_sdlrenderer2.h
  R engines/twp/imgui/backends/imgui_impl_sdlrenderer3.cpp
  R engines/twp/imgui/backends/imgui_impl_sdlrenderer3.h
  R engines/twp/imgui/backends/imgui_impl_vulkan.cpp
  R engines/twp/imgui/backends/imgui_impl_vulkan.h
  R engines/twp/imgui/backends/imgui_impl_wgpu.cpp
  R engines/twp/imgui/backends/imgui_impl_wgpu.h
  R engines/twp/imgui/backends/imgui_impl_win32.cpp
  R engines/twp/imgui/backends/imgui_impl_win32.h
  R engines/twp/imgui/backends/vulkan/generate_spv.sh
  R engines/twp/imgui/backends/vulkan/glsl_shader.frag
  R engines/twp/imgui/backends/vulkan/glsl_shader.vert
  R engines/twp/imgui/docs/BACKENDS.md
  R engines/twp/imgui/docs/CHANGELOG.txt
  R engines/twp/imgui/docs/CONTRIBUTING.md
  R engines/twp/imgui/docs/EXAMPLES.md
  R engines/twp/imgui/docs/FAQ.md
  R engines/twp/imgui/docs/FONTS.md
  R engines/twp/imgui/docs/README.md
  R engines/twp/imgui/docs/TODO.txt
  R engines/twp/imgui/examples/README.txt
  R engines/twp/imgui/examples/example_allegro5/README.md
  R engines/twp/imgui/examples/example_allegro5/imconfig_allegro5.h
  R engines/twp/imgui/examples/example_allegro5/main.cpp
  R engines/twp/imgui/examples/example_android_opengl3/CMakeLists.txt
  R engines/twp/imgui/examples/example_android_opengl3/android/.gitignore
  R engines/twp/imgui/examples/example_android_opengl3/android/app/build.gradle
  R engines/twp/imgui/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml
  R engines/twp/imgui/examples/example_android_opengl3/android/app/src/main/java/MainActivity.kt
  R engines/twp/imgui/examples/example_android_opengl3/android/build.gradle
  R engines/twp/imgui/examples/example_android_opengl3/android/settings.gradle
  R engines/twp/imgui/examples/example_android_opengl3/main.cpp
  R engines/twp/imgui/examples/example_apple_metal/README.md
  R engines/twp/imgui/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
  R engines/twp/imgui/examples/example_apple_metal/iOS/Info-iOS.plist
  R engines/twp/imgui/examples/example_apple_metal/iOS/LaunchScreen.storyboard
  R engines/twp/imgui/examples/example_apple_metal/macOS/Info-macOS.plist
  R engines/twp/imgui/examples/example_apple_metal/macOS/MainMenu.storyboard
  R engines/twp/imgui/examples/example_apple_metal/main.mm
  R engines/twp/imgui/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
  R engines/twp/imgui/examples/example_apple_opengl2/main.mm
  R engines/twp/imgui/examples/example_emscripten_wgpu/Makefile
  R engines/twp/imgui/examples/example_emscripten_wgpu/README.md
  R engines/twp/imgui/examples/example_emscripten_wgpu/main.cpp
  R engines/twp/imgui/examples/example_glfw_metal/Makefile
  R engines/twp/imgui/examples/example_glfw_metal/main.mm
  R engines/twp/imgui/examples/example_glfw_opengl2/Makefile
  R engines/twp/imgui/examples/example_glfw_opengl2/main.cpp
  R engines/twp/imgui/examples/example_glfw_opengl3/Makefile
  R engines/twp/imgui/examples/example_glfw_opengl3/Makefile.emscripten
  R engines/twp/imgui/examples/example_glfw_opengl3/main.cpp
  R engines/twp/imgui/examples/example_glfw_vulkan/CMakeLists.txt
  R engines/twp/imgui/examples/example_glfw_vulkan/main.cpp
  R engines/twp/imgui/examples/example_glut_opengl2/Makefile
  R engines/twp/imgui/examples/example_glut_opengl2/main.cpp
  R engines/twp/imgui/examples/example_null/Makefile
  R engines/twp/imgui/examples/example_null/main.cpp
  R engines/twp/imgui/examples/example_sdl2_directx11/main.cpp
  R engines/twp/imgui/examples/example_sdl2_metal/Makefile
  R engines/twp/imgui/examples/example_sdl2_metal/main.mm
  R engines/twp/imgui/examples/example_sdl2_opengl2/Makefile
  R engines/twp/imgui/examples/example_sdl2_opengl2/README.md
  R engines/twp/imgui/examples/example_sdl2_opengl2/main.cpp
  R engines/twp/imgui/examples/example_sdl2_opengl3/Makefile
  R engines/twp/imgui/examples/example_sdl2_opengl3/Makefile.emscripten
  R engines/twp/imgui/examples/example_sdl2_opengl3/README.md
  R engines/twp/imgui/examples/example_sdl2_opengl3/main.cpp
  R engines/twp/imgui/examples/example_sdl2_sdlrenderer2/Makefile
  R engines/twp/imgui/examples/example_sdl2_sdlrenderer2/README.md
  R engines/twp/imgui/examples/example_sdl2_sdlrenderer2/main.cpp
  R engines/twp/imgui/examples/example_sdl2_vulkan/main.cpp
  R engines/twp/imgui/examples/example_sdl3_opengl3/Makefile
  R engines/twp/imgui/examples/example_sdl3_opengl3/Makefile.emscripten
  R engines/twp/imgui/examples/example_sdl3_opengl3/README.md
  R engines/twp/imgui/examples/example_sdl3_opengl3/main.cpp
  R engines/twp/imgui/examples/example_sdl3_sdlrenderer3/Makefile
  R engines/twp/imgui/examples/example_sdl3_sdlrenderer3/main.cpp
  R engines/twp/imgui/examples/example_win32_directx10/main.cpp
  R engines/twp/imgui/examples/example_win32_directx11/main.cpp
  R engines/twp/imgui/examples/example_win32_directx12/main.cpp
  R engines/twp/imgui/examples/example_win32_directx9/main.cpp
  R engines/twp/imgui/examples/example_win32_opengl3/main.cpp
  R engines/twp/imgui/examples/libs/emscripten/emscripten_mainloop_stub.h
  R engines/twp/imgui/examples/libs/emscripten/shell_minimal.html
  R engines/twp/imgui/examples/libs/glfw/COPYING.txt
  R engines/twp/imgui/examples/libs/glfw/include/GLFW/glfw3.h
  R engines/twp/imgui/examples/libs/glfw/include/GLFW/glfw3native.h
  R engines/twp/imgui/examples/libs/glfw/lib-vc2010-32/glfw3.lib
  R engines/twp/imgui/examples/libs/glfw/lib-vc2010-64/glfw3.lib
  R engines/twp/imgui/examples/libs/usynergy/README.txt
  R engines/twp/imgui/examples/libs/usynergy/uSynergy.c
  R engines/twp/imgui/examples/libs/usynergy/uSynergy.h
  R engines/twp/imgui/imconfig.h
  R engines/twp/imgui/imgui.cpp
  R engines/twp/imgui/imgui.h
  R engines/twp/imgui/imgui_demo.cpp
  R engines/twp/imgui/imgui_draw.cpp
  R engines/twp/imgui/imgui_internal.h
  R engines/twp/imgui/imgui_tables.cpp
  R engines/twp/imgui/imgui_widgets.cpp
  R engines/twp/imgui/imstb_rectpack.h
  R engines/twp/imgui/imstb_textedit.h
  R engines/twp/imgui/imstb_truetype.h
  R engines/twp/imgui/misc/README.txt
  R engines/twp/imgui/misc/cpp/README.txt
  R engines/twp/imgui/misc/cpp/imgui_stdlib.cpp
  R engines/twp/imgui/misc/cpp/imgui_stdlib.h
  R engines/twp/imgui/misc/debuggers/README.txt
  R engines/twp/imgui/misc/debuggers/imgui.gdb
  R engines/twp/imgui/misc/debuggers/imgui.natstepfilter
  R engines/twp/imgui/misc/debuggers/imgui.natvis
  R engines/twp/imgui/misc/fonts/Cousine-Regular.ttf
  R engines/twp/imgui/misc/fonts/DroidSans.ttf
  R engines/twp/imgui/misc/fonts/Karla-Regular.ttf
  R engines/twp/imgui/misc/fonts/ProggyClean.ttf
  R engines/twp/imgui/misc/fonts/ProggyTiny.ttf
  R engines/twp/imgui/misc/fonts/Roboto-Medium.ttf
  R engines/twp/imgui/misc/fonts/binary_to_compressed_c.cpp
  R engines/twp/imgui/misc/freetype/README.md
  R engines/twp/imgui/misc/freetype/imgui_freetype.cpp
  R engines/twp/imgui/misc/freetype/imgui_freetype.h
  R engines/twp/imgui/misc/single_file/imgui_single_file.h
  R engines/twp/imgui_impl_opengl3_scummvm.cpp
  R engines/twp/imgui_impl_opengl3_scummvm.h
  R engines/twp/imgui_impl_sdl2_scummvm.cpp
  R engines/twp/imgui_impl_sdl2_scummvm.h
    configure
    engines/twp/debugtools.cpp
    engines/twp/module.mk
    engines/twp/twp.cpp
    graphics/module.mk


diff --git a/configure b/configure
index 1578862b132..68bbe453188 100755
--- a/configure
+++ b/configure
@@ -277,7 +277,7 @@ _endian=unknown
 _need_memalign=yes
 _have_x86=no
 _have_amd64=no
-_imgui=no
+_imgui=yes
 
 # Add (virtual) features
 add_feature 16bit "16bit color" "_16bit"
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index d6797e2a7e2..6d8fdbabbe6 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -19,8 +19,10 @@
  *
  */
 
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#include "graphics/imgui/imgui.h"
+
 #include "common/debug-channels.h"
-#include "imgui/imgui.h"
 #include "twp/twp.h"
 #include "twp/debugtools.h"
 #include "twp/thread.h"
diff --git a/engines/twp/imgui/.editorconfig b/engines/twp/imgui/.editorconfig
deleted file mode 100644
index 5adfefa20d8..00000000000
--- a/engines/twp/imgui/.editorconfig
+++ /dev/null
@@ -1,28 +0,0 @@
-# See http://editorconfig.org to read about the EditorConfig format.
-# - In theory automatically supported by VS2017+ and most common IDE or text editors.
-# - In practice VS2019-VS2022 stills don't trim trailing whitespaces correctly :(
-#   - Suggest installing this to trim whitespaces:
-#      GitHub https://github.com/madskristensen/TrailingWhitespace
-#      VS2019 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer
-#      VS2022 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespace64
-#     (in spite of its name doesn't only visualize but also trims)
-#   - Alternative for older VS2010 to VS2015: https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig
-
-# top-most EditorConfig file
-root = true
-
-# Default settings:
-# Use 4 spaces as indentation
-[*]
-indent_style = space
-indent_size = 4
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[imstb_*]
-indent_size = 3
-trim_trailing_whitespace = false
-
-[Makefile]
-indent_style = tab
-indent_size = 4
diff --git a/engines/twp/imgui/.gitattributes b/engines/twp/imgui/.gitattributes
deleted file mode 100644
index d48470eeff1..00000000000
--- a/engines/twp/imgui/.gitattributes
+++ /dev/null
@@ -1,30 +0,0 @@
-* text=auto
-
-*.c text
-*.cpp text
-*.h text
-*.m text
-*.mm text
-*.md text
-*.txt text
-*.html text
-*.bat text
-*.frag text
-*.vert text
-*.mkb text
-*.icf text
-
-*.sln text eol=crlf
-*.vcxproj text eol=crlf
-*.vcxproj.filters text eol=crlf
-*.natvis text eol=crlf
-
-Makefile text eol=lf
-*.sh text eol=lf
-*.pbxproj text eol=lf
-*.storyboard text eol=lf
-*.plist text eol=lf
-
-*.png binary
-*.ttf binary
-*.lib binary
diff --git a/engines/twp/imgui/.github/FUNDING.yml b/engines/twp/imgui/.github/FUNDING.yml
deleted file mode 100644
index 2aa08b4474c..00000000000
--- a/engines/twp/imgui/.github/FUNDING.yml
+++ /dev/null
@@ -1 +0,0 @@
-custom: ['https://github.com/ocornut/imgui/wiki/Sponsors']
diff --git a/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml b/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0cec6..00000000000
--- a/engines/twp/imgui/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml b/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
deleted file mode 100644
index 792d8f63ed4..00000000000
--- a/engines/twp/imgui/.github/ISSUE_TEMPLATE/issue_template.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-name: "Ask a question, report a bug, request a feature, etc."
-description: "Ask any question, discuss best practices, report a bug, request a feature."
-body:
-  - type: markdown
-    attributes:
-      value: |
-        FOR FIRST-TIME USERS ISSUES COMPILING/LINKING/RUNNING or LOADING FONTS, please use [GitHub Discussions](https://github.com/ocornut/imgui/discussions)
-        For anything else: we are happy to use 'GitHub Issues' for many types of open-ended questions. We are encouraging 'Issues' becoming a large, centralized and cross-referenced database of Dear ImGui contents.
-
-        Be mindful that messages are being sent to the e-mail box of "Watching" users. Try to proof-read your messages before sending them. Edits are not seen by those users.
-  - type: markdown
-    attributes:
-      value: |
-        **Prerequisites:**
-        - I have read [Frequently Asked Questions](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md).
-        - I have read [Contributing Guidelines -> General Advices](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#getting-started--general-advice).
-        - I have read [Contributing Guidelines -> How to open an Issue](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#how-to-open-an-issue).
-        - I have searched [Github Issues and PR](https://github.com/ocornut/imgui/issues?q=) for discussion of similar topics.
-
-        ----
-  - type: input
-    id: specs_version
-    attributes:
-      label: "Version/Branch of Dear ImGui:"
-      description: "(please specify if you have made substantial modifications to your copy)"
-      value: "Version 1.XX, Branch: XXX (master/docking/etc.)"
-      placeholder: "Version 1.XX, Branch: XXX (master/docking/etc.)"
-    validations:
-      required: true
-  - type: input
-    id: specs_backend
-    attributes:
-      label: "Back-ends:"
-      description: (or specify when using custom engine/back-ends)
-      value: "imgui_impl_XXX.cpp + imgui_impl_XXX.cpp"
-      placeholder: "imgui_impl_XXX.cpp  + imgui_impl_XXX.cpp or n/a"
-    validations:
-      required: true
-  - type: input
-    id: specs_compiler_os
-    attributes:
-      label: "Compiler, OS:"
-      placeholder: "e.g. Windows 11 + MSVC 2022, macOS + Clang 12, Linux + GCC etc."
-    validations:
-      required: true
-  - type: textarea
-    id: specs_full
-    attributes:
-      label: "Full config/build information:"
-      placeholder: |
-        (If you can run, you may go to 'Demo->Tools->About Dear ImGui->Config/Build Info' to obtain detailed information that you can paste here)
-    validations:
-      required: false
-  - type: textarea
-    id: issue_description
-    attributes:
-      label: "Details:"
-      description: "Try to be explicit with your goals, your expectations and what you have tried. Be mindful of [The XY Problem](https://xyproblem.info). What you have in mind or in your code is not obvious to other people. People frequently discuss problems and suggest incorrect solutions without first clarifying their goals. When requesting a new feature, please describe the usage context (how you intend to use it, why you need it, etc.). If you tried something and it failed, show us what you tried. If you are reporting a bug, explain what's the bug, how does it occur, etc. If you are reporting a crash, please include a debugger callstack."
-      value: |
-        **My Issue/Question:**
-
-        XXX _(please provide as much context as possible)_
-    validations:
-      required: true
-  - type: textarea
-    id: screenshots
-    attributes:
-      label: "Screenshots/Video:"
-      description: "Attach screenshots or gif/videos to clarify the context. They often convey useful information that is omitted by the description."
-      placeholder: "(You can drag files here)"
-    validations:
-      required: false
-  - type: textarea
-    id: repro_code
-    attributes:
-      label: "Minimal, Complete and Verifiable Example code:"
-      description: "Provide an [MCVE](https://stackoverflow.com/help/mcve) to demonstrate your problem. An ideal submission includes a small piece of code that anyone can paste into one of the examples applications (examples/*/main.cpp) or the demo (imgui_demo.cpp) to understand and reproduce it. Narrowing your problem to its shortest and purest form is the easiest way to understand it, explain it and fix it. Please test your shortened code to ensure it exhibits the problem. Often while creating the MCVE you will solve the problem! Many questions that are missing a standalone verifiable example are missing the actual cause of their issue in the description, which ends up wasting everyone's time."
-      value: |
-        ```cpp
-        // Here's some code anyone can copy and paste to reproduce your issue
-        ImGui::Begin("Example Bug");
-        MoreCodeToExplainMyIssue();
-        ImGui::End();
-        ```
-    validations:
-      required: false
-  - type: markdown
-    attributes:
-      value: |
-        Thank you for taking the time to read prerequisites, filling this template and double-checking your message and your code!
diff --git a/engines/twp/imgui/.github/pull_request_template.md b/engines/twp/imgui/.github/pull_request_template.md
deleted file mode 100644
index 638545bd6d3..00000000000
--- a/engines/twp/imgui/.github/pull_request_template.md
+++ /dev/null
@@ -1,6 +0,0 @@
-(Click "Preview" to turn any http URL into a clickable link)
-
-1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md)
-
-2. Clear this template before submitting your PR.
-
diff --git a/engines/twp/imgui/.github/workflows/build.yml b/engines/twp/imgui/.github/workflows/build.yml
deleted file mode 100644
index 45688c47034..00000000000
--- a/engines/twp/imgui/.github/workflows/build.yml
+++ /dev/null
@@ -1,507 +0,0 @@
-name: build
-
-on:
-  push:
-  pull_request:
-  workflow_run:
-    # Use a workflow as a trigger of scheduled builds. Forked repositories can disable scheduled builds by disabling
-    # "scheduled" workflow, while maintaining ability to perform local CI builds.
-    workflows:
-      - scheduled
-    branches:
-      - master
-      - docking
-    types:
-      - requested
-
-jobs:
-  Windows:
-    runs-on: windows-2019
-    env:
-      VS_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\
-      MSBUILD_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\
-    steps:
-      - uses: actions/checkout at v3
-
-      - name: Install Dependencies
-        shell: powershell
-        run: |
-          Invoke-WebRequest -Uri "https://www.libsdl.org/release/SDL2-devel-2.26.3-VC.zip" -OutFile "SDL2-devel-2.26.3-VC.zip"
-          Expand-Archive -Path SDL2-devel-2.26.3-VC.zip
-          echo "SDL2_DIR=$(pwd)\SDL2-devel-2.26.3-VC\SDL2-2.26.3\" >>${env:GITHUB_ENV}
-
-          Invoke-WebRequest -Uri "https://github.com/ocornut/imgui/files/3789205/vulkan-sdk-1.1.121.2.zip" -OutFile vulkan-sdk-1.1.121.2.zip
-          Expand-Archive -Path vulkan-sdk-1.1.121.2.zip
-          echo "VULKAN_SDK=$(pwd)\vulkan-sdk-1.1.121.2\" >>${env:GITHUB_ENV}
-
-      - name: Fix Projects
-        shell: powershell
-        run: |
-          # CI workers do not supporter older Visual Studio versions. Fix projects to target newer available version.
-          gci -recurse -filter "*.vcxproj" | ForEach-Object {
-            (Get-Content $_.FullName) -Replace "<PlatformToolset>v\d{3}</PlatformToolset>","<PlatformToolset>v142</PlatformToolset>" | Set-Content -Path $_.FullName
-            (Get-Content $_.FullName) -Replace "<WindowsTargetPlatformVersion>[\d\.]+</WindowsTargetPlatformVersion>","<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>" | Set-Content -Path $_.FullName
-          }
-
-      # Not using matrix here because it would inflate job count too much. Check out and setup is done for every job and that makes build times way too long.
-      - name: Build example_null (extra warnings, mingw 64-bit)
-        run: mingw32-make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-      - name: Build example_null (mingw 64-bit, as DLL)
-        shell: bash
-        run: |
-          echo '#ifdef _EXPORT'                                  >  example_single_file.cpp
-          echo '#  define IMGUI_API __declspec(dllexport)'       >> example_single_file.cpp
-          echo '#else'                                           >> example_single_file.cpp
-          echo '#  define IMGUI_API __declspec(dllimport)'       >> example_single_file.cpp
-          echo '#endif'                                          >> example_single_file.cpp
-          echo '#define IMGUI_IMPLEMENTATION'                    >> example_single_file.cpp
-          echo '#include "misc/single_file/imgui_single_file.h"' >> example_single_file.cpp
-          g++ -I. -Wall -Wformat -D_EXPORT -shared -o libimgui.dll -Wl,--out-implib,libimgui.a example_single_file.cpp -limm32
-          g++ -I. -Wall -Wformat -o example_null.exe examples/example_null/main.cpp -L. -limgui
-          rm -f example_null.exe libimgui.* example_single_file.*
-
-      - name: Build example_null (extra warnings, msvc 64-bit)
-        shell: cmd
-        run: |
-          cd examples\example_null
-          call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat"
-          .\build_win32.bat /W4
-
-      - name: Build example_null (single file build)
-        shell: bash
-        run: |
-          cat > example_single_file.cpp <<'EOF'
-
-          #define IMGUI_IMPLEMENTATION
-          #include "misc/single_file/imgui_single_file.h"
-          #include "examples/example_null/main.cpp"
-
-          EOF
-          g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32
-
-      - name: Build example_null (with IMGUI_DISABLE_WIN32_FUNCTIONS)
-        shell: bash
-        run: |
-          cat > example_single_file.cpp <<'EOF'
-
-          #define IMGUI_DISABLE_WIN32_FUNCTIONS
-          #define IMGUI_IMPLEMENTATION
-          #include "misc/single_file/imgui_single_file.h"
-          #include "examples/example_null/main.cpp"
-
-          EOF
-          g++ -I. -Wall -Wformat -o example_single_file.exe example_single_file.cpp -limm32
-
-      - name: Build example_null (as DLL)
-        shell: cmd
-        run: |
-          call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat"
-
-          echo #ifdef _EXPORT                                  >  example_single_file.cpp
-          echo #  define IMGUI_API __declspec(dllexport)       >> example_single_file.cpp
-          echo #else                                           >> example_single_file.cpp
-          echo #  define IMGUI_API __declspec(dllimport)       >> example_single_file.cpp
-          echo #endif                                          >> example_single_file.cpp
-          echo #define IMGUI_IMPLEMENTATION                    >> example_single_file.cpp
-          echo #include "misc/single_file/imgui_single_file.h" >> example_single_file.cpp
-
-          cl.exe /D_USRDLL /D_WINDLL /D_EXPORT /I. example_single_file.cpp /LD /FeImGui.dll /link
-          cl.exe /I. ImGui.lib /Feexample_null.exe examples/example_null/main.cpp
-
-      - name: Build Win32 example_glfw_opengl2
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-
-      - name: Build Win32 example_glfw_opengl3
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build Win32 example_glfw_vulkan
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build Win32 example_sdl2_vulkan
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build Win32 example_sdl2_opengl2
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build Win32 example_sdl2_opengl3
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-
-      - name: Build Win32 example_sdl2_directx11
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build Win32 example_win32_directx9
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx9/example_win32_directx9.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-
-      - name: Build Win32 example_win32_directx10
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx10/example_win32_directx10.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-
-      - name: Build Win32 example_win32_directx11
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=Win32 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_glfw_opengl2
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_glfw_opengl3
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release'
-
-      - name: Build x64 example_glfw_vulkan
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj /p:Platform=x64 /p:Configuration=Release'
-
-      - name: Build x64 example_sdl2_vulkan
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_sdl2_opengl2
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_sdl2_opengl3
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_sdl2_directx11
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release'
-
-      - name: Build x64 example_win32_directx9
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx9/example_win32_directx9.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_win32_directx10
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx10/example_win32_directx10.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_win32_directx11
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx11/example_win32_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release'
-        if: github.event_name == 'workflow_run'
-
-      - name: Build x64 example_win32_directx12
-        shell: cmd
-        run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_win32_directx12/example_win32_directx12.vcxproj /p:Platform=x64 /p:Configuration=Release'
-
-  Linux:
-    runs-on: ubuntu-22.04
-    steps:
-    - uses: actions/checkout at v3
-
-    - name: Install Dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y libglfw3-dev libsdl2-dev gcc-multilib g++-multilib libfreetype6-dev libvulkan-dev
-
-    - name: Build example_null (extra warnings, gcc 32-bit)
-      run: |
-        make -C examples/example_null clean
-        CXXFLAGS="$CXXFLAGS -m32 -Werror" make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-    - name: Build example_null (extra warnings, gcc 64-bit)
-      run: |
-        make -C examples/example_null clean
-        CXXFLAGS="$CXXFLAGS -m64 -Werror" make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-    - name: Build example_null (extra warnings, clang 32-bit)
-      run: |
-        make -C examples/example_null clean
-        CXXFLAGS="$CXXFLAGS -m32 -Werror" CXX=clang++ make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-    - name: Build example_null (extra warnings, clang 64-bit)
-      run: |
-        make -C examples/example_null clean
-        CXXFLAGS="$CXXFLAGS -m64 -Werror" CXX=clang++ make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-    - name: Build example_null (extra warnings, empty IM_ASSERT)
-      run: |
-          cat > example_single_file.cpp <<'EOF'
-
-          #define IM_ASSERT(x)
-          #define IMGUI_IMPLEMENTATION
-          #include "misc/single_file/imgui_single_file.h"
-          #include "examples/example_null/main.cpp"
-
-          EOF
-          g++ -I. -std=c++11 -Wall -Wformat -Wextra -Werror -Wno-zero-as-null-pointer-constant -Wno-double-promotion -Wno-variadic-macros -Wno-empty-body -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (freetype)
-      run: |
-        make -C examples/example_null clean
-        make -C examples/example_null WITH_FREETYPE=1
-
-    - name: Build example_null (single file build)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with ImWchar32)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_USE_WCHAR32
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with large ImDrawIdx + pointer ImTextureID)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define ImTextureID void*
-        #define ImDrawIdx unsigned int
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IMGUI_DISABLE_OBSOLETE_KEYIO)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_DISABLE_OBSOLETE_KEYIO
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IMGUI_DISABLE_DEMO_WINDOWS and IMGUI_DISABLE_DEBUG_TOOLS)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_DISABLE_DEMO_WINDOWS
-        #define IMGUI_DISABLE_DEBUG_TOOLS
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IMGUI_DISABLE_FILE_FUNCTIONS)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_DISABLE_FILE_FUNCTIONS
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IMGUI_USE_BGRA_PACKED_COLOR)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_USE_BGRA_PACKED_COLOR
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (with IM_VEC2_CLASS_EXTRA and IM_VEC4_CLASS_EXTRA)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        struct MyVec2 { float x; float y; MyVec2(float x, float y) : x(x), y(y) { } };
-        struct MyVec4 { float x; float y; float z; float w;
-        MyVec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } };
-        #define IM_VEC2_CLASS_EXTRA                                             \
-                ImVec2(const MyVec2& f) { x = f.x; y = f.y; }                   \
-                operator MyVec2() const { return MyVec2(x, y); }
-        #define IM_VEC4_CLASS_EXTRA                                             \
-                ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \
-                operator MyVec4() const { return MyVec4(x, y, z, w); }
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        g++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (without c++ runtime, Clang)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_IMPLEMENTATION
-        #define IMGUI_DISABLE_DEMO_WINDOWS
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        clang++ -I. -std=c++11 -Wall -Wformat -nodefaultlibs -fno-rtti -fno-exceptions -fno-threadsafe-statics -lc -lm -o example_single_file example_single_file.cpp
-
-    - name: Build example_glfw_opengl2
-      run: make -C examples/example_glfw_opengl2
-
-    - name: Build example_glfw_opengl3
-      run: make -C examples/example_glfw_opengl3
-      if: github.event_name == 'workflow_run'
-
-    - name: Build example_sdl2_opengl2
-      run: make -C examples/example_sdl2_opengl2
-      if: github.event_name == 'workflow_run'
-
-    - name: Build example_sdl2_opengl3
-      run: make -C examples/example_sdl2_opengl3
-
-    - name: Build with IMGUI_IMPL_VULKAN_NO_PROTOTYPES
-      run: g++ -c -I. -std=c++11 -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp
-
-  MacOS:
-    runs-on: macos-latest
-    steps:
-    - uses: actions/checkout at v3
-
-    - name: Install Dependencies
-      run: |
-        brew install glfw3 sdl2
-
-    - name: Build example_null (extra warnings, clang 64-bit)
-      run: make -C examples/example_null WITH_EXTRA_WARNINGS=1
-
-    - name: Build example_null (single file build)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        clang++ -I. -std=c++11 -Wall -Wformat -o example_single_file example_single_file.cpp
-
-    - name: Build example_null (without c++ runtime)
-      run: |
-        cat > example_single_file.cpp <<'EOF'
-
-        #define IMGUI_IMPLEMENTATION
-        #include "misc/single_file/imgui_single_file.h"
-        #include "examples/example_null/main.cpp"
-
-        EOF
-        clang++ -I. -std=c++11 -Wall -Wformat -nodefaultlibs -fno-rtti -fno-exceptions -fno-threadsafe-statics -lc -lm -o example_single_file example_single_file.cpp
-
-    - name: Build example_glfw_opengl2
-      run: make -C examples/example_glfw_opengl2
-
-    - name: Build example_glfw_opengl3
-      run: make -C examples/example_glfw_opengl3
-      if: github.event_name == 'workflow_run'
-
-    - name: Build example_glfw_metal
-      run: make -C examples/example_glfw_metal
-
-    - name: Build example_sdl2_metal
-      run: make -C examples/example_sdl2_metal
-
-    - name: Build example_sdl2_opengl2
-      run: make -C examples/example_sdl2_opengl2
-      if: github.event_name == 'workflow_run'
-
-    - name: Build example_sdl2_opengl3
-      run: make -C examples/example_sdl2_opengl3
-
-    - name: Build example_apple_metal
-      run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos
-
-    - name: Build example_apple_opengl2
-      run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2
-
-  iOS:
-    runs-on: macos-latest
-    steps:
-    - uses: actions/checkout at v3
-
-    - name: Build example_apple_metal
-      run: |
-        # Code signing is required, but we disable it because it is irrelevant for CI builds.
-        xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_ios CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
-
-  Emscripten:
-    runs-on: ubuntu-22.04
-    steps:
-    - uses: actions/checkout at v3
-
-    - name: Install Dependencies
-      run: |
-        wget -q https://github.com/emscripten-core/emsdk/archive/master.tar.gz
-        tar -xvf master.tar.gz
-        emsdk-master/emsdk update
-        emsdk-master/emsdk install latest
-        emsdk-master/emsdk activate latest
-
-    - name: Build example_sdl2_opengl3 with Emscripten
-      run: |
-        pushd emsdk-master
-        source ./emsdk_env.sh
-        popd
-        make -C examples/example_sdl2_opengl3 -f Makefile.emscripten
-
-    - name: Build example_emscripten_wgpu
-      run: |
-        pushd emsdk-master
-        source ./emsdk_env.sh
-        popd
-        make -C examples/example_emscripten_wgpu
-
-  Android:
-    runs-on: ubuntu-22.04
-    steps:
-    - uses: actions/checkout at v3
-
-    - name: Build example_android_opengl3
-      run: |
-        cd examples/example_android_opengl3/android
-        gradle assembleDebug --stacktrace
diff --git a/engines/twp/imgui/.github/workflows/scheduled.yml b/engines/twp/imgui/.github/workflows/scheduled.yml
deleted file mode 100644
index 2a08578fb65..00000000000
--- a/engines/twp/imgui/.github/workflows/scheduled.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# This is a dummy workflow used to trigger scheduled builds. Forked repositories most likely should disable this
-# workflow to avoid daily builds of inactive repositories.
-#
-name: scheduled
-
-on:
-  schedule:
-    - cron:  '0 9 * * *'
-
-jobs:
-  scheduled:
-    runs-on: ubuntu-latest
-    steps:
-      - run: exit 0
diff --git a/engines/twp/imgui/.github/workflows/static-analysis.yml b/engines/twp/imgui/.github/workflows/static-analysis.yml
deleted file mode 100644
index caa9b3a4e44..00000000000
--- a/engines/twp/imgui/.github/workflows/static-analysis.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: static-analysis
-
-on:
-  workflow_run:
-    # Perform static analysis together with build workflow. Build triggers of "build" workflow do not need to be repeated here.
-    workflows:
-      - build
-    types:
-      - requested
-
-jobs:
-  PVS-Studio:
-    runs-on: ubuntu-22.04
-    steps:
-      - uses: actions/checkout at v3
-        with:
-          fetch-depth: 1
-
-      - name: Install Dependencies
-        env:
-          # The Secret variable setup in GitHub must be in format: "name_or_email key", on a single line
-          PVS_STUDIO_LICENSE: ${{ secrets.PVS_STUDIO_LICENSE }}
-        run: |
-          if [[ "$PVS_STUDIO_LICENSE" != "" ]];
-          then
-            wget -q https://files.viva64.com/etc/pubkey.txt
-            sudo apt-key add pubkey.txt
-            sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list
-            sudo apt-get update
-            sudo apt-get install -y pvs-studio
-            pvs-studio-analyzer credentials -o pvs-studio.lic $PVS_STUDIO_LICENSE
-          fi
-
-      - name: PVS-Studio static analysis
-        run: |
-          if [[ ! -f pvs-studio.lic ]];
-          then
-            echo "PVS Studio license is missing. No analysis will be performed."
-            echo "If you have a PVS Studio license please create a project secret named PVS_STUDIO_LICENSE with your license."
-            echo "You may use a free license. More information at https://www.viva64.com/en/b/0457/"
-            exit 0
-          fi
-          cd examples/example_null
-          pvs-studio-analyzer trace -- make WITH_EXTRA_WARNINGS=1
-          pvs-studio-analyzer analyze -e ../../imstb_rectpack.h -e ../../imstb_textedit.h -e ../../imstb_truetype.h -l ../../pvs-studio.lic -o pvs-studio.log
-          plog-converter -a 'GA:1,2;OP:1' -d V1071 -t errorfile -w pvs-studio.log
diff --git a/engines/twp/imgui/.gitignore b/engines/twp/imgui/.gitignore
deleted file mode 100644
index 211d21dda88..00000000000
--- a/engines/twp/imgui/.gitignore
+++ /dev/null
@@ -1,59 +0,0 @@
-## OSX artifacts
-.DS_Store
-
-## Dear ImGui artifacts
-imgui.ini
-
-## General build artifacts
-*.o
-*.obj
-*.exe
-examples/*/Debug/*
-examples/*/Release/*
-examples/*/x64/*
-
-## Visual Studio artifacts
-.vs
-ipch
-*.opensdf
-*.log
-*.pdb
-*.ilk
-*.user
-*.sdf
-*.suo
-*.VC.db
-*.VC.VC.opendb
-
-## Getting files created in JSON/Schemas/Catalog/ from a VS2022 update
-JSON/
-
-## Commonly used CMake directories
-build*/
-
-## Xcode artifacts
-project.xcworkspace
-xcuserdata
-
-## Emscripten artifacts
-examples/*.o.tmp
-examples/*.out.js
-examples/*.out.wasm
-examples/example_glfw_opengl3/web/*
-examples/example_sdl2_opengl3/web/*
-examples/example_emscripten_wgpu/web/*
-
-## JetBrains IDE artifacts
-.idea
-cmake-build-*
-
-## Unix executables from our example Makefiles
-examples/example_glfw_metal/example_glfw_metal
-examples/example_glfw_opengl2/example_glfw_opengl2
-examples/example_glfw_opengl3/example_glfw_opengl3
-examples/example_glut_opengl2/example_glut_opengl2
-examples/example_null/example_null
-examples/example_sdl2_metal/example_sdl2_metal
-examples/example_sdl2_opengl2/example_sdl2_opengl2
-examples/example_sdl2_opengl3/example_sdl2_opengl3
-examples/example_sdl2_sdlrenderer/example_sdl2_sdlrenderer
diff --git a/engines/twp/imgui/backends/imgui_impl_allegro5.cpp b/engines/twp/imgui/backends/imgui_impl_allegro5.cpp
deleted file mode 100644
index abb6b9ad0ba..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_allegro5.cpp
+++ /dev/null
@@ -1,613 +0,0 @@
-// dear imgui: Renderer + Platform Backend for Allegro 5
-// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Clipboard support (from Allegro 5.1.12)
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-// Issues:
-//  [ ] Renderer: The renderer is suboptimal as we need to convert vertices manually.
-//  [ ] Platform: Missing gamepad support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-11-30: Renderer: Restoring using al_draw_indexed_prim() when Allegro version is >= 5.2.5.
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-17: Inputs: always calling io.AddKeyModsEvent() next and before key event (not in NewFrame) to fix input queue with very low framerates.
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2021-12-08: Renderer: Fixed mishandling of the the ImDrawCmd::IdxOffset field! This is an old bug but it never had an effect until some internal rendering changes in 1.86.
-//  2021-08-17: Calling io.AddFocusEvent() on ALLEGRO_EVENT_DISPLAY_SWITCH_OUT/ALLEGRO_EVENT_DISPLAY_SWITCH_IN events.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-05-19: Renderer: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-02-18: Change blending equation to preserve alpha in output buffer.
-//  2020-08-10: Inputs: Fixed horizontal mouse wheel direction.
-//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
-//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
-//  2019-05-11: Inputs: Don't filter character value from ALLEGRO_EVENT_KEY_CHAR before calling AddInputCharacter().
-//  2019-04-30: Renderer: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2018-11-30: Platform: Added touchscreen support.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName/io.BackendRendererName so they can be displayed in the About Window.
-//  2018-06-13: Platform: Added clipboard support (from Allegro 5.1.12).
-//  2018-06-13: Renderer: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-06-13: Renderer: Stopped using al_draw_indexed_prim() as it is buggy in Allegro's DX9 backend.
-//  2018-06-13: Renderer: Backup/restore transform and clipping rectangle.
-//  2018-06-11: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
-//  2018-04-18: Misc: Renamed file from imgui_impl_a5.cpp to imgui_impl_allegro5.cpp.
-//  2018-04-18: Misc: Added support for 32-bit vertex indices to avoid conversion at runtime. Added imconfig_allegro5.h to enforce 32-bit indices when included from imgui.h.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplAllegro5_RenderDrawData() in the .h file so you can call it yourself.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_allegro5.h"
-#include <stdint.h>     // uint64_t
-#include <cstring>      // memcpy
-
-// Allegro
-#include <allegro5/allegro.h>
-#include <allegro5/allegro_primitives.h>
-#ifdef _WIN32
-#include <allegro5/allegro_windows.h>
-#endif
-#define ALLEGRO_HAS_CLIPBOARD           (ALLEGRO_VERSION_INT >= ((5 << 24) | (1 << 16) | (12 << 8))) // Clipboard only supported from Allegro 5.1.12
-#define ALLEGRO_HAS_DRAW_INDEXED_PRIM   (ALLEGRO_VERSION_INT >= ((5 << 24) | (2 << 16) | ( 5 << 8))) // DX9 implementation of al_draw_indexed_prim() got fixed in Allegro 5.2.5
-
-// Visual Studio warnings
-#ifdef _MSC_VER
-#pragma warning (disable: 4127) // condition expression is constant
-#endif
-
-struct ImDrawVertAllegro
-{
-    ImVec2          pos;
-    ImVec2          uv;
-    ALLEGRO_COLOR   col;
-};
-
-// FIXME-OPT: Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 float as well..
-// FIXME-OPT: Consider inlining al_map_rgba()?
-// see https://github.com/liballeg/allegro5/blob/master/src/pixels.c#L554
-// and https://github.com/liballeg/allegro5/blob/master/include/allegro5/internal/aintern_pixels.h
-#define DRAW_VERT_IMGUI_TO_ALLEGRO(DST, SRC)  { (DST)->pos = (SRC)->pos; (DST)->uv = (SRC)->uv; unsigned char* c = (unsigned char*)&(SRC)->col; (DST)->col = al_map_rgba(c[0], c[1], c[2], c[3]); }
-
-// Allegro Data
-struct ImGui_ImplAllegro5_Data
-{
-    ALLEGRO_DISPLAY*            Display;
-    ALLEGRO_BITMAP*             Texture;
-    double                      Time;
-    ALLEGRO_MOUSE_CURSOR*       MouseCursorInvisible;
-    ALLEGRO_VERTEX_DECL*        VertexDecl;
-    char*                       ClipboardTextData;
-
-    ImVector<ImDrawVertAllegro> BufVertices;
-    ImVector<int>               BufIndices;
-
-    ImGui_ImplAllegro5_Data()   { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
-static ImGui_ImplAllegro5_Data* ImGui_ImplAllegro5_GetBackendData()     { return ImGui::GetCurrentContext() ? (ImGui_ImplAllegro5_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; }
-
-static void ImGui_ImplAllegro5_SetupRenderState(ImDrawData* draw_data)
-{
-    // Setup blending
-    al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
-
-    // Setup orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
-    {
-        float L = draw_data->DisplayPos.x;
-        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-        float T = draw_data->DisplayPos.y;
-        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
-        ALLEGRO_TRANSFORM transform;
-        al_identity_transform(&transform);
-        al_use_transform(&transform);
-        al_orthographic_transform(&transform, L, T, 1.0f, R, B, -1.0f);
-        al_use_projection_transform(&transform);
-    }
-}
-
-// Render function.
-void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized
-    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
-        return;
-
-    // Backup Allegro state that will be modified
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    ALLEGRO_TRANSFORM last_transform = *al_get_current_transform();
-    ALLEGRO_TRANSFORM last_projection_transform = *al_get_current_projection_transform();
-    int last_clip_x, last_clip_y, last_clip_w, last_clip_h;
-    al_get_clipping_rectangle(&last_clip_x, &last_clip_y, &last_clip_w, &last_clip_h);
-    int last_blender_op, last_blender_src, last_blender_dst;
-    al_get_blender(&last_blender_op, &last_blender_src, &last_blender_dst);
-
-    // Setup desired render state
-    ImGui_ImplAllegro5_SetupRenderState(draw_data);
-
-    // Render command lists
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-
-        ImVector<ImDrawVertAllegro>& vertices = bd->BufVertices;
-#if ALLEGRO_HAS_DRAW_INDEXED_PRIM
-        vertices.resize(cmd_list->VtxBuffer.Size);
-        for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
-        {
-            const ImDrawVert* src_v = &cmd_list->VtxBuffer[i];
-            ImDrawVertAllegro* dst_v = &vertices[i];
-            DRAW_VERT_IMGUI_TO_ALLEGRO(dst_v, src_v);
-        }
-        const int* indices = nullptr;
-        if (sizeof(ImDrawIdx) == 2)
-        {
-            // FIXME-OPT: Allegro doesn't support 16-bit indices.
-            // You can '#define ImDrawIdx int' in imconfig.h to request Dear ImGui to output 32-bit indices.
-            // Otherwise, we convert them from 16-bit to 32-bit at runtime here, which works perfectly but is a little wasteful.
-            bd->BufIndices.resize(cmd_list->IdxBuffer.Size);
-            for (int i = 0; i < cmd_list->IdxBuffer.Size; ++i)
-                bd->BufIndices[i] = (int)cmd_list->IdxBuffer.Data[i];
-            indices = bd->BufIndices.Data;
-        }
-        else if (sizeof(ImDrawIdx) == 4)
-        {
-            indices = (const int*)cmd_list->IdxBuffer.Data;
-        }
-#else
-        // Allegro's implementation of al_draw_indexed_prim() for DX9 was broken until 5.2.5. Unindex buffers ourselves while converting vertex format.
-        vertices.resize(cmd_list->IdxBuffer.Size);
-        for (int i = 0; i < cmd_list->IdxBuffer.Size; i++)
-        {
-            const ImDrawVert* src_v = &cmd_list->VtxBuffer[cmd_list->IdxBuffer[i]];
-            ImDrawVertAllegro* dst_v = &vertices[i];
-            DRAW_VERT_IMGUI_TO_ALLEGRO(dst_v, src_v);
-        }
-#endif
-
-        // Render command lists
-        ImVec2 clip_off = draw_data->DisplayPos;
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplAllegro5_SetupRenderState(draw_data);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
-                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle, Draw
-                ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->GetTexID();
-                al_set_clipping_rectangle(clip_min.x, clip_min.y, clip_max.x - clip_min.x, clip_max.y - clip_min.y);
-#if ALLEGRO_HAS_DRAW_INDEXED_PRIM
-                al_draw_indexed_prim(&vertices[0], bd->VertexDecl, texture, &indices[pcmd->IdxOffset], pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST);
-#else
-                al_draw_prim(&vertices[0], bd->VertexDecl, texture, pcmd->IdxOffset, pcmd->IdxOffset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST);
-#endif
-            }
-        }
-    }
-
-    // Restore modified Allegro state
-    al_set_blender(last_blender_op, last_blender_src, last_blender_dst);
-    al_set_clipping_rectangle(last_clip_x, last_clip_y, last_clip_w, last_clip_h);
-    al_use_transform(&last_transform);
-    al_use_projection_transform(&last_projection_transform);
-}
-
-bool ImGui_ImplAllegro5_CreateDeviceObjects()
-{
-    // Build texture atlas
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-
-    // Create texture
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    int flags = al_get_new_bitmap_flags();
-    int fmt = al_get_new_bitmap_format();
-    al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR);
-    al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE);
-    ALLEGRO_BITMAP* img = al_create_bitmap(width, height);
-    al_set_new_bitmap_flags(flags);
-    al_set_new_bitmap_format(fmt);
-    if (!img)
-        return false;
-
-    ALLEGRO_LOCKED_REGION* locked_img = al_lock_bitmap(img, al_get_bitmap_format(img), ALLEGRO_LOCK_WRITEONLY);
-    if (!locked_img)
-    {
-        al_destroy_bitmap(img);
-        return false;
-    }
-    memcpy(locked_img->data, pixels, sizeof(int) * width * height);
-    al_unlock_bitmap(img);
-
-    // Convert software texture to hardware texture.
-    ALLEGRO_BITMAP* cloned_img = al_clone_bitmap(img);
-    al_destroy_bitmap(img);
-    if (!cloned_img)
-        return false;
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)(intptr_t)cloned_img);
-    bd->Texture = cloned_img;
-
-    // Create an invisible mouse cursor
-    // Because al_hide_mouse_cursor() seems to mess up with the actual inputs..
-    ALLEGRO_BITMAP* mouse_cursor = al_create_bitmap(8, 8);
-    bd->MouseCursorInvisible = al_create_mouse_cursor(mouse_cursor, 0, 0);
-    al_destroy_bitmap(mouse_cursor);
-
-    return true;
-}
-
-void ImGui_ImplAllegro5_InvalidateDeviceObjects()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    if (bd->Texture)
-    {
-        io.Fonts->SetTexID(0);
-        al_destroy_bitmap(bd->Texture);
-        bd->Texture = nullptr;
-    }
-    if (bd->MouseCursorInvisible)
-    {
-        al_destroy_mouse_cursor(bd->MouseCursorInvisible);
-        bd->MouseCursorInvisible = nullptr;
-    }
-}
-
-#if ALLEGRO_HAS_CLIPBOARD
-static const char* ImGui_ImplAllegro5_GetClipboardText(void*)
-{
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    if (bd->ClipboardTextData)
-        al_free(bd->ClipboardTextData);
-    bd->ClipboardTextData = al_get_clipboard_text(bd->Display);
-    return bd->ClipboardTextData;
-}
-
-static void ImGui_ImplAllegro5_SetClipboardText(void*, const char* text)
-{
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    al_set_clipboard_text(bd->Display, text);
-}
-#endif
-
-static ImGuiKey ImGui_ImplAllegro5_KeyCodeToImGuiKey(int key_code)
-{
-    switch (key_code)
-    {
-        case ALLEGRO_KEY_TAB: return ImGuiKey_Tab;
-        case ALLEGRO_KEY_LEFT: return ImGuiKey_LeftArrow;
-        case ALLEGRO_KEY_RIGHT: return ImGuiKey_RightArrow;
-        case ALLEGRO_KEY_UP: return ImGuiKey_UpArrow;
-        case ALLEGRO_KEY_DOWN: return ImGuiKey_DownArrow;
-        case ALLEGRO_KEY_PGUP: return ImGuiKey_PageUp;
-        case ALLEGRO_KEY_PGDN: return ImGuiKey_PageDown;
-        case ALLEGRO_KEY_HOME: return ImGuiKey_Home;
-        case ALLEGRO_KEY_END: return ImGuiKey_End;
-        case ALLEGRO_KEY_INSERT: return ImGuiKey_Insert;
-        case ALLEGRO_KEY_DELETE: return ImGuiKey_Delete;
-        case ALLEGRO_KEY_BACKSPACE: return ImGuiKey_Backspace;
-        case ALLEGRO_KEY_SPACE: return ImGuiKey_Space;
-        case ALLEGRO_KEY_ENTER: return ImGuiKey_Enter;
-        case ALLEGRO_KEY_ESCAPE: return ImGuiKey_Escape;
-        case ALLEGRO_KEY_QUOTE: return ImGuiKey_Apostrophe;
-        case ALLEGRO_KEY_COMMA: return ImGuiKey_Comma;
-        case ALLEGRO_KEY_MINUS: return ImGuiKey_Minus;
-        case ALLEGRO_KEY_FULLSTOP: return ImGuiKey_Period;
-        case ALLEGRO_KEY_SLASH: return ImGuiKey_Slash;
-        case ALLEGRO_KEY_SEMICOLON: return ImGuiKey_Semicolon;
-        case ALLEGRO_KEY_EQUALS: return ImGuiKey_Equal;
-        case ALLEGRO_KEY_OPENBRACE: return ImGuiKey_LeftBracket;
-        case ALLEGRO_KEY_BACKSLASH: return ImGuiKey_Backslash;
-        case ALLEGRO_KEY_CLOSEBRACE: return ImGuiKey_RightBracket;
-        case ALLEGRO_KEY_TILDE: return ImGuiKey_GraveAccent;
-        case ALLEGRO_KEY_CAPSLOCK: return ImGuiKey_CapsLock;
-        case ALLEGRO_KEY_SCROLLLOCK: return ImGuiKey_ScrollLock;
-        case ALLEGRO_KEY_NUMLOCK: return ImGuiKey_NumLock;
-        case ALLEGRO_KEY_PRINTSCREEN: return ImGuiKey_PrintScreen;
-        case ALLEGRO_KEY_PAUSE: return ImGuiKey_Pause;
-        case ALLEGRO_KEY_PAD_0: return ImGuiKey_Keypad0;
-        case ALLEGRO_KEY_PAD_1: return ImGuiKey_Keypad1;
-        case ALLEGRO_KEY_PAD_2: return ImGuiKey_Keypad2;
-        case ALLEGRO_KEY_PAD_3: return ImGuiKey_Keypad3;
-        case ALLEGRO_KEY_PAD_4: return ImGuiKey_Keypad4;
-        case ALLEGRO_KEY_PAD_5: return ImGuiKey_Keypad5;
-        case ALLEGRO_KEY_PAD_6: return ImGuiKey_Keypad6;
-        case ALLEGRO_KEY_PAD_7: return ImGuiKey_Keypad7;
-        case ALLEGRO_KEY_PAD_8: return ImGuiKey_Keypad8;
-        case ALLEGRO_KEY_PAD_9: return ImGuiKey_Keypad9;
-        case ALLEGRO_KEY_PAD_DELETE: return ImGuiKey_KeypadDecimal;
-        case ALLEGRO_KEY_PAD_SLASH: return ImGuiKey_KeypadDivide;
-        case ALLEGRO_KEY_PAD_ASTERISK: return ImGuiKey_KeypadMultiply;
-        case ALLEGRO_KEY_PAD_MINUS: return ImGuiKey_KeypadSubtract;
-        case ALLEGRO_KEY_PAD_PLUS: return ImGuiKey_KeypadAdd;
-        case ALLEGRO_KEY_PAD_ENTER: return ImGuiKey_KeypadEnter;
-        case ALLEGRO_KEY_PAD_EQUALS: return ImGuiKey_KeypadEqual;
-        case ALLEGRO_KEY_LCTRL: return ImGuiKey_LeftCtrl;
-        case ALLEGRO_KEY_LSHIFT: return ImGuiKey_LeftShift;
-        case ALLEGRO_KEY_ALT: return ImGuiKey_LeftAlt;
-        case ALLEGRO_KEY_LWIN: return ImGuiKey_LeftSuper;
-        case ALLEGRO_KEY_RCTRL: return ImGuiKey_RightCtrl;
-        case ALLEGRO_KEY_RSHIFT: return ImGuiKey_RightShift;
-        case ALLEGRO_KEY_ALTGR: return ImGuiKey_RightAlt;
-        case ALLEGRO_KEY_RWIN: return ImGuiKey_RightSuper;
-        case ALLEGRO_KEY_MENU: return ImGuiKey_Menu;
-        case ALLEGRO_KEY_0: return ImGuiKey_0;
-        case ALLEGRO_KEY_1: return ImGuiKey_1;
-        case ALLEGRO_KEY_2: return ImGuiKey_2;
-        case ALLEGRO_KEY_3: return ImGuiKey_3;
-        case ALLEGRO_KEY_4: return ImGuiKey_4;
-        case ALLEGRO_KEY_5: return ImGuiKey_5;
-        case ALLEGRO_KEY_6: return ImGuiKey_6;
-        case ALLEGRO_KEY_7: return ImGuiKey_7;
-        case ALLEGRO_KEY_8: return ImGuiKey_8;
-        case ALLEGRO_KEY_9: return ImGuiKey_9;
-        case ALLEGRO_KEY_A: return ImGuiKey_A;
-        case ALLEGRO_KEY_B: return ImGuiKey_B;
-        case ALLEGRO_KEY_C: return ImGuiKey_C;
-        case ALLEGRO_KEY_D: return ImGuiKey_D;
-        case ALLEGRO_KEY_E: return ImGuiKey_E;
-        case ALLEGRO_KEY_F: return ImGuiKey_F;
-        case ALLEGRO_KEY_G: return ImGuiKey_G;
-        case ALLEGRO_KEY_H: return ImGuiKey_H;
-        case ALLEGRO_KEY_I: return ImGuiKey_I;
-        case ALLEGRO_KEY_J: return ImGuiKey_J;
-        case ALLEGRO_KEY_K: return ImGuiKey_K;
-        case ALLEGRO_KEY_L: return ImGuiKey_L;
-        case ALLEGRO_KEY_M: return ImGuiKey_M;
-        case ALLEGRO_KEY_N: return ImGuiKey_N;
-        case ALLEGRO_KEY_O: return ImGuiKey_O;
-        case ALLEGRO_KEY_P: return ImGuiKey_P;
-        case ALLEGRO_KEY_Q: return ImGuiKey_Q;
-        case ALLEGRO_KEY_R: return ImGuiKey_R;
-        case ALLEGRO_KEY_S: return ImGuiKey_S;
-        case ALLEGRO_KEY_T: return ImGuiKey_T;
-        case ALLEGRO_KEY_U: return ImGuiKey_U;
-        case ALLEGRO_KEY_V: return ImGuiKey_V;
-        case ALLEGRO_KEY_W: return ImGuiKey_W;
-        case ALLEGRO_KEY_X: return ImGuiKey_X;
-        case ALLEGRO_KEY_Y: return ImGuiKey_Y;
-        case ALLEGRO_KEY_Z: return ImGuiKey_Z;
-        case ALLEGRO_KEY_F1: return ImGuiKey_F1;
-        case ALLEGRO_KEY_F2: return ImGuiKey_F2;
-        case ALLEGRO_KEY_F3: return ImGuiKey_F3;
-        case ALLEGRO_KEY_F4: return ImGuiKey_F4;
-        case ALLEGRO_KEY_F5: return ImGuiKey_F5;
-        case ALLEGRO_KEY_F6: return ImGuiKey_F6;
-        case ALLEGRO_KEY_F7: return ImGuiKey_F7;
-        case ALLEGRO_KEY_F8: return ImGuiKey_F8;
-        case ALLEGRO_KEY_F9: return ImGuiKey_F9;
-        case ALLEGRO_KEY_F10: return ImGuiKey_F10;
-        case ALLEGRO_KEY_F11: return ImGuiKey_F11;
-        case ALLEGRO_KEY_F12: return ImGuiKey_F12;
-        default: return ImGuiKey_None;
-    }
-}
-
-bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplAllegro5_Data* bd = IM_NEW(ImGui_ImplAllegro5_Data)();
-    io.BackendPlatformUserData = (void*)bd;
-    io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5";
-    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)
-
-    bd->Display = display;
-
-    // Create custom vertex declaration.
-    // Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 floats.
-    // We still use a custom declaration to use 'ALLEGRO_PRIM_TEX_COORD' instead of 'ALLEGRO_PRIM_TEX_COORD_PIXEL' else we can't do a reliable conversion.
-    ALLEGRO_VERTEX_ELEMENT elems[] =
-    {
-        { ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_2, offsetof(ImDrawVertAllegro, pos) },
-        { ALLEGRO_PRIM_TEX_COORD, ALLEGRO_PRIM_FLOAT_2, offsetof(ImDrawVertAllegro, uv) },
-        { ALLEGRO_PRIM_COLOR_ATTR, 0, offsetof(ImDrawVertAllegro, col) },
-        { 0, 0, 0 }
-    };
-    bd->VertexDecl = al_create_vertex_decl(elems, sizeof(ImDrawVertAllegro));
-
-#if ALLEGRO_HAS_CLIPBOARD
-    io.SetClipboardTextFn = ImGui_ImplAllegro5_SetClipboardText;
-    io.GetClipboardTextFn = ImGui_ImplAllegro5_GetClipboardText;
-    io.ClipboardUserData = nullptr;
-#endif
-
-    return true;
-}
-
-void ImGui_ImplAllegro5_Shutdown()
-{
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplAllegro5_InvalidateDeviceObjects();
-    if (bd->VertexDecl)
-        al_destroy_vertex_decl(bd->VertexDecl);
-    if (bd->ClipboardTextData)
-        al_free(bd->ClipboardTextData);
-
-    io.BackendPlatformName = io.BackendRendererName = nullptr;
-    io.BackendPlatformUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors;
-    IM_DELETE(bd);
-}
-
-// ev->keyboard.modifiers seems always zero so using that...
-static void ImGui_ImplAllegro5_UpdateKeyModifiers()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ALLEGRO_KEYBOARD_STATE keys;
-    al_get_keyboard_state(&keys);
-    io.AddKeyEvent(ImGuiMod_Ctrl, al_key_down(&keys, ALLEGRO_KEY_LCTRL) || al_key_down(&keys, ALLEGRO_KEY_RCTRL));
-    io.AddKeyEvent(ImGuiMod_Shift, al_key_down(&keys, ALLEGRO_KEY_LSHIFT) || al_key_down(&keys, ALLEGRO_KEY_RSHIFT));
-    io.AddKeyEvent(ImGuiMod_Alt, al_key_down(&keys, ALLEGRO_KEY_ALT) || al_key_down(&keys, ALLEGRO_KEY_ALTGR));
-    io.AddKeyEvent(ImGuiMod_Super, al_key_down(&keys, ALLEGRO_KEY_LWIN) || al_key_down(&keys, ALLEGRO_KEY_RWIN));
-}
-
-// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
-// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
-// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
-// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
-bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* ev)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-
-    switch (ev->type)
-    {
-    case ALLEGRO_EVENT_MOUSE_AXES:
-        if (ev->mouse.display == bd->Display)
-        {
-            io.AddMousePosEvent(ev->mouse.x, ev->mouse.y);
-            io.AddMouseWheelEvent(-ev->mouse.dw, ev->mouse.dz);
-        }
-        return true;
-    case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
-    case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
-        if (ev->mouse.display == bd->Display && ev->mouse.button > 0 && ev->mouse.button <= 5)
-            io.AddMouseButtonEvent(ev->mouse.button - 1, ev->type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN);
-        return true;
-    case ALLEGRO_EVENT_TOUCH_MOVE:
-        if (ev->touch.display == bd->Display)
-            io.AddMousePosEvent(ev->touch.x, ev->touch.y);
-        return true;
-    case ALLEGRO_EVENT_TOUCH_BEGIN:
-    case ALLEGRO_EVENT_TOUCH_END:
-    case ALLEGRO_EVENT_TOUCH_CANCEL:
-        if (ev->touch.display == bd->Display && ev->touch.primary)
-            io.AddMouseButtonEvent(0, ev->type == ALLEGRO_EVENT_TOUCH_BEGIN);
-        return true;
-    case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY:
-        if (ev->mouse.display == bd->Display)
-            io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
-        return true;
-    case ALLEGRO_EVENT_KEY_CHAR:
-        if (ev->keyboard.display == bd->Display)
-            if (ev->keyboard.unichar != 0)
-                io.AddInputCharacter((unsigned int)ev->keyboard.unichar);
-        return true;
-    case ALLEGRO_EVENT_KEY_DOWN:
-    case ALLEGRO_EVENT_KEY_UP:
-        if (ev->keyboard.display == bd->Display)
-        {
-            ImGui_ImplAllegro5_UpdateKeyModifiers();
-            ImGuiKey key = ImGui_ImplAllegro5_KeyCodeToImGuiKey(ev->keyboard.keycode);
-            io.AddKeyEvent(key, (ev->type == ALLEGRO_EVENT_KEY_DOWN));
-            io.SetKeyEventNativeData(key, ev->keyboard.keycode, -1); // To support legacy indexing (<1.87 user code)
-        }
-        return true;
-    case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT:
-        if (ev->display.source == bd->Display)
-            io.AddFocusEvent(false);
-        return true;
-    case ALLEGRO_EVENT_DISPLAY_SWITCH_IN:
-        if (ev->display.source == bd->Display)
-        {
-            io.AddFocusEvent(true);
-#if defined(ALLEGRO_UNSTABLE)
-            al_clear_keyboard_state(bd->Display);
-#endif
-        }
-        return true;
-    }
-    return false;
-}
-
-static void ImGui_ImplAllegro5_UpdateMouseCursor()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
-        return;
-
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
-    {
-        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-        al_set_mouse_cursor(bd->Display, bd->MouseCursorInvisible);
-    }
-    else
-    {
-        ALLEGRO_SYSTEM_MOUSE_CURSOR cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_DEFAULT;
-        switch (imgui_cursor)
-        {
-        case ImGuiMouseCursor_TextInput:    cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_EDIT; break;
-        case ImGuiMouseCursor_ResizeAll:    cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_MOVE; break;
-        case ImGuiMouseCursor_ResizeNS:     cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_N; break;
-        case ImGuiMouseCursor_ResizeEW:     cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_E; break;
-        case ImGuiMouseCursor_ResizeNESW:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NE; break;
-        case ImGuiMouseCursor_ResizeNWSE:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NW; break;
-        case ImGuiMouseCursor_NotAllowed:   cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_UNAVAILABLE; break;
-        }
-        al_set_system_mouse_cursor(bd->Display, cursor_id);
-    }
-}
-
-void ImGui_ImplAllegro5_NewFrame()
-{
-    ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplAllegro5_Init()?");
-
-    if (!bd->Texture)
-        ImGui_ImplAllegro5_CreateDeviceObjects();
-
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Setup display size (every frame to accommodate for window resizing)
-    int w, h;
-    w = al_get_display_width(bd->Display);
-    h = al_get_display_height(bd->Display);
-    io.DisplaySize = ImVec2((float)w, (float)h);
-
-    // Setup time step
-    double current_time = al_get_time();
-    io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
-    bd->Time = current_time;
-
-    // Setup mouse cursor shape
-    ImGui_ImplAllegro5_UpdateMouseCursor();
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_allegro5.h b/engines/twp/imgui/backends/imgui_impl_allegro5.h
deleted file mode 100644
index 5b63654ef9c..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_allegro5.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// dear imgui: Renderer + Platform Backend for Allegro 5
-// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Clipboard support (from Allegro 5.1.12)
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-// Issues:
-//  [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually.
-//  [ ] Platform: Missing gamepad support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct ALLEGRO_DISPLAY;
-union ALLEGRO_EVENT;
-
-IMGUI_IMPL_API bool     ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display);
-IMGUI_IMPL_API void     ImGui_ImplAllegro5_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplAllegro5_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data);
-IMGUI_IMPL_API bool     ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* event);
-
-// Use if you want to reset your rendering device without losing Dear ImGui state.
-IMGUI_IMPL_API bool     ImGui_ImplAllegro5_CreateDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplAllegro5_InvalidateDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_android.cpp b/engines/twp/imgui/backends/imgui_impl_android.cpp
deleted file mode 100644
index 7dd2afc2efc..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_android.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-// dear imgui: Platform Binding for Android native app
-// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
-
-// Implemented features:
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
-// Missing features:
-//  [ ] Platform: Clipboard support.
-//  [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
-// Important:
-//  - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
-//  - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
-//  - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2021-03-04: Initial version.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_android.h"
-#include <time.h>
-#include <android/native_window.h>
-#include <android/input.h>
-#include <android/keycodes.h>
-#include <android/log.h>
-
-// Android data
-static double                                   g_Time = 0.0;
-static ANativeWindow*                           g_Window;
-static char                                     g_LogTag[] = "ImGuiExample";
-
-static ImGuiKey ImGui_ImplAndroid_KeyCodeToImGuiKey(int32_t key_code)
-{
-    switch (key_code)
-    {
-        case AKEYCODE_TAB:                  return ImGuiKey_Tab;
-        case AKEYCODE_DPAD_LEFT:            return ImGuiKey_LeftArrow;
-        case AKEYCODE_DPAD_RIGHT:           return ImGuiKey_RightArrow;
-        case AKEYCODE_DPAD_UP:              return ImGuiKey_UpArrow;
-        case AKEYCODE_DPAD_DOWN:            return ImGuiKey_DownArrow;
-        case AKEYCODE_PAGE_UP:              return ImGuiKey_PageUp;
-        case AKEYCODE_PAGE_DOWN:            return ImGuiKey_PageDown;
-        case AKEYCODE_MOVE_HOME:            return ImGuiKey_Home;
-        case AKEYCODE_MOVE_END:             return ImGuiKey_End;
-        case AKEYCODE_INSERT:               return ImGuiKey_Insert;
-        case AKEYCODE_FORWARD_DEL:          return ImGuiKey_Delete;
-        case AKEYCODE_DEL:                  return ImGuiKey_Backspace;
-        case AKEYCODE_SPACE:                return ImGuiKey_Space;
-        case AKEYCODE_ENTER:                return ImGuiKey_Enter;
-        case AKEYCODE_ESCAPE:               return ImGuiKey_Escape;
-        case AKEYCODE_APOSTROPHE:           return ImGuiKey_Apostrophe;
-        case AKEYCODE_COMMA:                return ImGuiKey_Comma;
-        case AKEYCODE_MINUS:                return ImGuiKey_Minus;
-        case AKEYCODE_PERIOD:               return ImGuiKey_Period;
-        case AKEYCODE_SLASH:                return ImGuiKey_Slash;
-        case AKEYCODE_SEMICOLON:            return ImGuiKey_Semicolon;
-        case AKEYCODE_EQUALS:               return ImGuiKey_Equal;
-        case AKEYCODE_LEFT_BRACKET:         return ImGuiKey_LeftBracket;
-        case AKEYCODE_BACKSLASH:            return ImGuiKey_Backslash;
-        case AKEYCODE_RIGHT_BRACKET:        return ImGuiKey_RightBracket;
-        case AKEYCODE_GRAVE:                return ImGuiKey_GraveAccent;
-        case AKEYCODE_CAPS_LOCK:            return ImGuiKey_CapsLock;
-        case AKEYCODE_SCROLL_LOCK:          return ImGuiKey_ScrollLock;
-        case AKEYCODE_NUM_LOCK:             return ImGuiKey_NumLock;
-        case AKEYCODE_SYSRQ:                return ImGuiKey_PrintScreen;
-        case AKEYCODE_BREAK:                return ImGuiKey_Pause;
-        case AKEYCODE_NUMPAD_0:             return ImGuiKey_Keypad0;
-        case AKEYCODE_NUMPAD_1:             return ImGuiKey_Keypad1;
-        case AKEYCODE_NUMPAD_2:             return ImGuiKey_Keypad2;
-        case AKEYCODE_NUMPAD_3:             return ImGuiKey_Keypad3;
-        case AKEYCODE_NUMPAD_4:             return ImGuiKey_Keypad4;
-        case AKEYCODE_NUMPAD_5:             return ImGuiKey_Keypad5;
-        case AKEYCODE_NUMPAD_6:             return ImGuiKey_Keypad6;
-        case AKEYCODE_NUMPAD_7:             return ImGuiKey_Keypad7;
-        case AKEYCODE_NUMPAD_8:             return ImGuiKey_Keypad8;
-        case AKEYCODE_NUMPAD_9:             return ImGuiKey_Keypad9;
-        case AKEYCODE_NUMPAD_DOT:           return ImGuiKey_KeypadDecimal;
-        case AKEYCODE_NUMPAD_DIVIDE:        return ImGuiKey_KeypadDivide;
-        case AKEYCODE_NUMPAD_MULTIPLY:      return ImGuiKey_KeypadMultiply;
-        case AKEYCODE_NUMPAD_SUBTRACT:      return ImGuiKey_KeypadSubtract;
-        case AKEYCODE_NUMPAD_ADD:           return ImGuiKey_KeypadAdd;
-        case AKEYCODE_NUMPAD_ENTER:         return ImGuiKey_KeypadEnter;
-        case AKEYCODE_NUMPAD_EQUALS:        return ImGuiKey_KeypadEqual;
-        case AKEYCODE_CTRL_LEFT:            return ImGuiKey_LeftCtrl;
-        case AKEYCODE_SHIFT_LEFT:           return ImGuiKey_LeftShift;
-        case AKEYCODE_ALT_LEFT:             return ImGuiKey_LeftAlt;
-        case AKEYCODE_META_LEFT:            return ImGuiKey_LeftSuper;
-        case AKEYCODE_CTRL_RIGHT:           return ImGuiKey_RightCtrl;
-        case AKEYCODE_SHIFT_RIGHT:          return ImGuiKey_RightShift;
-        case AKEYCODE_ALT_RIGHT:            return ImGuiKey_RightAlt;
-        case AKEYCODE_META_RIGHT:           return ImGuiKey_RightSuper;
-        case AKEYCODE_MENU:                 return ImGuiKey_Menu;
-        case AKEYCODE_0:                    return ImGuiKey_0;
-        case AKEYCODE_1:                    return ImGuiKey_1;
-        case AKEYCODE_2:                    return ImGuiKey_2;
-        case AKEYCODE_3:                    return ImGuiKey_3;
-        case AKEYCODE_4:                    return ImGuiKey_4;
-        case AKEYCODE_5:                    return ImGuiKey_5;
-        case AKEYCODE_6:                    return ImGuiKey_6;
-        case AKEYCODE_7:                    return ImGuiKey_7;
-        case AKEYCODE_8:                    return ImGuiKey_8;
-        case AKEYCODE_9:                    return ImGuiKey_9;
-        case AKEYCODE_A:                    return ImGuiKey_A;
-        case AKEYCODE_B:                    return ImGuiKey_B;
-        case AKEYCODE_C:                    return ImGuiKey_C;
-        case AKEYCODE_D:                    return ImGuiKey_D;
-        case AKEYCODE_E:                    return ImGuiKey_E;
-        case AKEYCODE_F:                    return ImGuiKey_F;
-        case AKEYCODE_G:                    return ImGuiKey_G;
-        case AKEYCODE_H:                    return ImGuiKey_H;
-        case AKEYCODE_I:                    return ImGuiKey_I;
-        case AKEYCODE_J:                    return ImGuiKey_J;
-        case AKEYCODE_K:                    return ImGuiKey_K;
-        case AKEYCODE_L:                    return ImGuiKey_L;
-        case AKEYCODE_M:                    return ImGuiKey_M;
-        case AKEYCODE_N:                    return ImGuiKey_N;
-        case AKEYCODE_O:                    return ImGuiKey_O;
-        case AKEYCODE_P:                    return ImGuiKey_P;
-        case AKEYCODE_Q:                    return ImGuiKey_Q;
-        case AKEYCODE_R:                    return ImGuiKey_R;
-        case AKEYCODE_S:                    return ImGuiKey_S;
-        case AKEYCODE_T:                    return ImGuiKey_T;
-        case AKEYCODE_U:                    return ImGuiKey_U;
-        case AKEYCODE_V:                    return ImGuiKey_V;
-        case AKEYCODE_W:                    return ImGuiKey_W;
-        case AKEYCODE_X:                    return ImGuiKey_X;
-        case AKEYCODE_Y:                    return ImGuiKey_Y;
-        case AKEYCODE_Z:                    return ImGuiKey_Z;
-        case AKEYCODE_F1:                   return ImGuiKey_F1;
-        case AKEYCODE_F2:                   return ImGuiKey_F2;
-        case AKEYCODE_F3:                   return ImGuiKey_F3;
-        case AKEYCODE_F4:                   return ImGuiKey_F4;
-        case AKEYCODE_F5:                   return ImGuiKey_F5;
-        case AKEYCODE_F6:                   return ImGuiKey_F6;
-        case AKEYCODE_F7:                   return ImGuiKey_F7;
-        case AKEYCODE_F8:                   return ImGuiKey_F8;
-        case AKEYCODE_F9:                   return ImGuiKey_F9;
-        case AKEYCODE_F10:                  return ImGuiKey_F10;
-        case AKEYCODE_F11:                  return ImGuiKey_F11;
-        case AKEYCODE_F12:                  return ImGuiKey_F12;
-        default:                            return ImGuiKey_None;
-    }
-}
-
-int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    int32_t event_type = AInputEvent_getType(input_event);
-    switch (event_type)
-    {
-    case AINPUT_EVENT_TYPE_KEY:
-    {
-        int32_t event_key_code = AKeyEvent_getKeyCode(input_event);
-        int32_t event_scan_code = AKeyEvent_getScanCode(input_event);
-        int32_t event_action = AKeyEvent_getAction(input_event);
-        int32_t event_meta_state = AKeyEvent_getMetaState(input_event);
-
-        io.AddKeyEvent(ImGuiMod_Ctrl,  (event_meta_state & AMETA_CTRL_ON)  != 0);
-        io.AddKeyEvent(ImGuiMod_Shift, (event_meta_state & AMETA_SHIFT_ON) != 0);
-        io.AddKeyEvent(ImGuiMod_Alt,   (event_meta_state & AMETA_ALT_ON)   != 0);
-        io.AddKeyEvent(ImGuiMod_Super, (event_meta_state & AMETA_META_ON)  != 0);
-
-        switch (event_action)
-        {
-        // FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer
-        // goes up from a key. We use a simple key event queue/ and process one event per key per frame in
-        // ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787
-        case AKEY_EVENT_ACTION_DOWN:
-        case AKEY_EVENT_ACTION_UP:
-        {
-            ImGuiKey key = ImGui_ImplAndroid_KeyCodeToImGuiKey(event_key_code);
-            if (key != ImGuiKey_None)
-            {
-                io.AddKeyEvent(key, event_action == AKEY_EVENT_ACTION_DOWN);
-                io.SetKeyEventNativeData(key, event_key_code, event_scan_code);
-            }
-
-            break;
-        }
-        default:
-            break;
-        }
-        break;
-    }
-    case AINPUT_EVENT_TYPE_MOTION:
-    {
-        int32_t event_action = AMotionEvent_getAction(input_event);
-        int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-        event_action &= AMOTION_EVENT_ACTION_MASK;
-
-        switch (AMotionEvent_getToolType(input_event, event_pointer_index))
-        {
-        case AMOTION_EVENT_TOOL_TYPE_MOUSE:
-            io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
-            break;
-        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
-        case AMOTION_EVENT_TOOL_TYPE_ERASER:
-            io.AddMouseSourceEvent(ImGuiMouseSource_Pen);
-            break;
-        case AMOTION_EVENT_TOOL_TYPE_FINGER:
-        default:
-            io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen);
-            break;
-        }
-
-        switch (event_action)
-        {
-        case AMOTION_EVENT_ACTION_DOWN:
-        case AMOTION_EVENT_ACTION_UP:
-        {
-            // Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP,
-            // but we have to process them separately to identify the actual button pressed. This is done below via
-            // AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback).
-            int tool_type = AMotionEvent_getToolType(input_event, event_pointer_index);
-            if (tool_type == AMOTION_EVENT_TOOL_TYPE_FINGER || tool_type == AMOTION_EVENT_TOOL_TYPE_UNKNOWN)
-            {
-                io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
-                io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN);
-            }
-            break;
-        }
-        case AMOTION_EVENT_ACTION_BUTTON_PRESS:
-        case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
-        {
-            int32_t button_state = AMotionEvent_getButtonState(input_event);
-            io.AddMouseButtonEvent(0, (button_state & AMOTION_EVENT_BUTTON_PRIMARY) != 0);
-            io.AddMouseButtonEvent(1, (button_state & AMOTION_EVENT_BUTTON_SECONDARY) != 0);
-            io.AddMouseButtonEvent(2, (button_state & AMOTION_EVENT_BUTTON_TERTIARY) != 0);
-            break;
-        }
-        case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse)
-        case AMOTION_EVENT_ACTION_MOVE:       // Touch pointer moves while DOWN
-            io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
-            break;
-        case AMOTION_EVENT_ACTION_SCROLL:
-            io.AddMouseWheelEvent(AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index), AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index));
-            break;
-        default:
-            break;
-        }
-    }
-        return 1;
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-bool ImGui_ImplAndroid_Init(ANativeWindow* window)
-{
-    g_Window = window;
-    g_Time = 0.0;
-
-    // Setup backend capabilities flags
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendPlatformName = "imgui_impl_android";
-
-    return true;
-}
-
-void ImGui_ImplAndroid_Shutdown()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendPlatformName = nullptr;
-}
-
-void ImGui_ImplAndroid_NewFrame()
-{
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Setup display size (every frame to accommodate for window resizing)
-    int32_t window_width = ANativeWindow_getWidth(g_Window);
-    int32_t window_height = ANativeWindow_getHeight(g_Window);
-    int display_width = window_width;
-    int display_height = window_height;
-
-    io.DisplaySize = ImVec2((float)window_width, (float)window_height);
-    if (window_width > 0 && window_height > 0)
-        io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
-
-    // Setup time step
-    struct timespec current_timespec;
-    clock_gettime(CLOCK_MONOTONIC, &current_timespec);
-    double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
-    io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
-    g_Time = current_time;
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_android.h b/engines/twp/imgui/backends/imgui_impl_android.h
deleted file mode 100644
index 557bf0e0921..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_android.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// dear imgui: Platform Binding for Android native app
-// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
-
-// Implemented features:
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
-// Missing features:
-//  [ ] Platform: Clipboard support.
-//  [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
-// Important:
-//  - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
-//  - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
-//  - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct ANativeWindow;
-struct AInputEvent;
-
-IMGUI_IMPL_API bool     ImGui_ImplAndroid_Init(ANativeWindow* window);
-IMGUI_IMPL_API int32_t  ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event);
-IMGUI_IMPL_API void     ImGui_ImplAndroid_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplAndroid_NewFrame();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx10.cpp b/engines/twp/imgui/backends/imgui_impl_dx10.cpp
deleted file mode 100644
index 96e27471c4d..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx10.cpp
+++ /dev/null
@@ -1,589 +0,0 @@
-// dear imgui: Renderer Backend for DirectX10
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-05-19: DirectX10: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-02-18: DirectX10: Change blending equation to preserve alpha in output buffer.
-//  2019-07-21: DirectX10: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData().
-//  2019-05-29: DirectX10: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: DirectX10: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-07-13: DirectX10: Fixed unreleased resources in Init and Shutdown functions.
-//  2018-06-08: Misc: Extracted imgui_impl_dx10.cpp/.h away from the old combined DX10+Win32 example.
-//  2018-06-08: DirectX10: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-04-09: Misc: Fixed erroneous call to io.Fonts->ClearInputData() + ClearTexData() that was left in DX10 example but removed in 1.47 (Nov 2015) on other backends.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX10_RenderDrawData() in the .h file so you can call it yourself.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2016-05-07: DirectX10: Disabling depth-write.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_dx10.h"
-
-// DirectX
-#include <stdio.h>
-#include <d3d10_1.h>
-#include <d3d10.h>
-#include <d3dcompiler.h>
-#ifdef _MSC_VER
-#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
-#endif
-
-// DirectX data
-struct ImGui_ImplDX10_Data
-{
-    ID3D10Device*               pd3dDevice;
-    IDXGIFactory*               pFactory;
-    ID3D10Buffer*               pVB;
-    ID3D10Buffer*               pIB;
-    ID3D10VertexShader*         pVertexShader;
-    ID3D10InputLayout*          pInputLayout;
-    ID3D10Buffer*               pVertexConstantBuffer;
-    ID3D10PixelShader*          pPixelShader;
-    ID3D10SamplerState*         pFontSampler;
-    ID3D10ShaderResourceView*   pFontTextureView;
-    ID3D10RasterizerState*      pRasterizerState;
-    ID3D10BlendState*           pBlendState;
-    ID3D10DepthStencilState*    pDepthStencilState;
-    int                         VertexBufferSize;
-    int                         IndexBufferSize;
-
-    ImGui_ImplDX10_Data()       { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
-};
-
-struct VERTEX_CONSTANT_BUFFER_DX10
-{
-    float   mvp[4][4];
-};
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplDX10_Data* ImGui_ImplDX10_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplDX10_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// Functions
-static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* ctx)
-{
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-
-    // Setup viewport
-    D3D10_VIEWPORT vp;
-    memset(&vp, 0, sizeof(D3D10_VIEWPORT));
-    vp.Width = (UINT)draw_data->DisplaySize.x;
-    vp.Height = (UINT)draw_data->DisplaySize.y;
-    vp.MinDepth = 0.0f;
-    vp.MaxDepth = 1.0f;
-    vp.TopLeftX = vp.TopLeftY = 0;
-    ctx->RSSetViewports(1, &vp);
-
-    // Bind shader and vertex buffers
-    unsigned int stride = sizeof(ImDrawVert);
-    unsigned int offset = 0;
-    ctx->IASetInputLayout(bd->pInputLayout);
-    ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
-    ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
-    ctx->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-    ctx->VSSetShader(bd->pVertexShader);
-    ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
-    ctx->PSSetShader(bd->pPixelShader);
-    ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
-    ctx->GSSetShader(nullptr);
-
-    // Setup render state
-    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
-    ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
-    ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
-    ctx->RSSetState(bd->pRasterizerState);
-}
-
-// Render function
-void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized
-    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
-        return;
-
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    ID3D10Device* ctx = bd->pd3dDevice;
-
-    // Create and grow vertex/index buffers if needed
-    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
-    {
-        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
-        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
-        D3D10_BUFFER_DESC desc;
-        memset(&desc, 0, sizeof(D3D10_BUFFER_DESC));
-        desc.Usage = D3D10_USAGE_DYNAMIC;
-        desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
-        desc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
-        desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
-        desc.MiscFlags = 0;
-        if (ctx->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
-            return;
-    }
-
-    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
-    {
-        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
-        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
-        D3D10_BUFFER_DESC desc;
-        memset(&desc, 0, sizeof(D3D10_BUFFER_DESC));
-        desc.Usage = D3D10_USAGE_DYNAMIC;
-        desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
-        desc.BindFlags = D3D10_BIND_INDEX_BUFFER;
-        desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
-        if (ctx->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
-            return;
-    }
-
-    // Copy and convert all vertices into a single contiguous buffer
-    ImDrawVert* vtx_dst = nullptr;
-    ImDrawIdx* idx_dst = nullptr;
-    bd->pVB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&vtx_dst);
-    bd->pIB->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&idx_dst);
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
-        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
-        vtx_dst += cmd_list->VtxBuffer.Size;
-        idx_dst += cmd_list->IdxBuffer.Size;
-    }
-    bd->pVB->Unmap();
-    bd->pIB->Unmap();
-
-    // Setup orthographic projection matrix into our constant buffer
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    {
-        void* mapped_resource;
-        if (bd->pVertexConstantBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
-            return;
-        VERTEX_CONSTANT_BUFFER_DX10* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX10*)mapped_resource;
-        float L = draw_data->DisplayPos.x;
-        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-        float T = draw_data->DisplayPos.y;
-        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
-        float mvp[4][4] =
-        {
-            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
-            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
-            { 0.0f,         0.0f,           0.5f,       0.0f },
-            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
-        };
-        memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
-        bd->pVertexConstantBuffer->Unmap();
-    }
-
-    // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
-    struct BACKUP_DX10_STATE
-    {
-        UINT                        ScissorRectsCount, ViewportsCount;
-        D3D10_RECT                  ScissorRects[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
-        D3D10_VIEWPORT              Viewports[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
-        ID3D10RasterizerState*      RS;
-        ID3D10BlendState*           BlendState;
-        FLOAT                       BlendFactor[4];
-        UINT                        SampleMask;
-        UINT                        StencilRef;
-        ID3D10DepthStencilState*    DepthStencilState;
-        ID3D10ShaderResourceView*   PSShaderResource;
-        ID3D10SamplerState*         PSSampler;
-        ID3D10PixelShader*          PS;
-        ID3D10VertexShader*         VS;
-        ID3D10GeometryShader*       GS;
-        D3D10_PRIMITIVE_TOPOLOGY    PrimitiveTopology;
-        ID3D10Buffer*               IndexBuffer, *VertexBuffer, *VSConstantBuffer;
-        UINT                        IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
-        DXGI_FORMAT                 IndexBufferFormat;
-        ID3D10InputLayout*          InputLayout;
-    };
-    BACKUP_DX10_STATE old = {};
-    old.ScissorRectsCount = old.ViewportsCount = D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
-    ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
-    ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
-    ctx->RSGetState(&old.RS);
-    ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
-    ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
-    ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
-    ctx->PSGetSamplers(0, 1, &old.PSSampler);
-    ctx->PSGetShader(&old.PS);
-    ctx->VSGetShader(&old.VS);
-    ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
-    ctx->GSGetShader(&old.GS);
-    ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
-    ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
-    ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
-    ctx->IAGetInputLayout(&old.InputLayout);
-
-    // Setup desired DX state
-    ImGui_ImplDX10_SetupRenderState(draw_data, ctx);
-
-    // Render command lists
-    // (Because we merged all buffers into a single one, we maintain our own offset into them)
-    int global_vtx_offset = 0;
-    int global_idx_offset = 0;
-    ImVec2 clip_off = draw_data->DisplayPos;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplDX10_SetupRenderState(draw_data, ctx);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
-                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle
-                const D3D10_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
-                ctx->RSSetScissorRects(1, &r);
-
-                // Bind texture, Draw
-                ID3D10ShaderResourceView* texture_srv = (ID3D10ShaderResourceView*)pcmd->GetTexID();
-                ctx->PSSetShaderResources(0, 1, &texture_srv);
-                ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
-            }
-        }
-        global_idx_offset += cmd_list->IdxBuffer.Size;
-        global_vtx_offset += cmd_list->VtxBuffer.Size;
-    }
-
-    // Restore modified DX state
-    ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
-    ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
-    ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
-    ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
-    ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
-    ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
-    ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
-    ctx->PSSetShader(old.PS); if (old.PS) old.PS->Release();
-    ctx->VSSetShader(old.VS); if (old.VS) old.VS->Release();
-    ctx->GSSetShader(old.GS); if (old.GS) old.GS->Release();
-    ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
-    ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
-    ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
-    ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
-    ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
-}
-
-static void ImGui_ImplDX10_CreateFontsTexture()
-{
-    // Build texture atlas
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-
-    // Upload texture to graphics system
-    {
-        D3D10_TEXTURE2D_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.Width = width;
-        desc.Height = height;
-        desc.MipLevels = 1;
-        desc.ArraySize = 1;
-        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        desc.SampleDesc.Count = 1;
-        desc.Usage = D3D10_USAGE_DEFAULT;
-        desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
-        desc.CPUAccessFlags = 0;
-
-        ID3D10Texture2D* pTexture = nullptr;
-        D3D10_SUBRESOURCE_DATA subResource;
-        subResource.pSysMem = pixels;
-        subResource.SysMemPitch = desc.Width * 4;
-        subResource.SysMemSlicePitch = 0;
-        bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
-        IM_ASSERT(pTexture != nullptr);
-
-        // Create texture view
-        D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc;
-        ZeroMemory(&srv_desc, sizeof(srv_desc));
-        srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
-        srv_desc.Texture2D.MipLevels = desc.MipLevels;
-        srv_desc.Texture2D.MostDetailedMip = 0;
-        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &bd->pFontTextureView);
-        pTexture->Release();
-    }
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);
-
-    // Create texture sampler
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    {
-        D3D10_SAMPLER_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR;
-        desc.AddressU = D3D10_TEXTURE_ADDRESS_WRAP;
-        desc.AddressV = D3D10_TEXTURE_ADDRESS_WRAP;
-        desc.AddressW = D3D10_TEXTURE_ADDRESS_WRAP;
-        desc.MipLODBias = 0.f;
-        desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS;
-        desc.MinLOD = 0.f;
-        desc.MaxLOD = 0.f;
-        bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
-    }
-}
-
-bool    ImGui_ImplDX10_CreateDeviceObjects()
-{
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    if (!bd->pd3dDevice)
-        return false;
-    if (bd->pFontSampler)
-        ImGui_ImplDX10_InvalidateDeviceObjects();
-
-    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
-    // If you would like to use this DX10 sample code but remove this dependency you can:
-    //  1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
-    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
-    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
-
-    // Create the vertex shader
-    {
-        static const char* vertexShader =
-            "cbuffer vertexBuffer : register(b0) \
-            {\
-              float4x4 ProjectionMatrix; \
-            };\
-            struct VS_INPUT\
-            {\
-              float2 pos : POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            struct PS_INPUT\
-            {\
-              float4 pos : SV_POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            PS_INPUT main(VS_INPUT input)\
-            {\
-              PS_INPUT output;\
-              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
-              output.col = input.col;\
-              output.uv  = input.uv;\
-              return output;\
-            }";
-
-        ID3DBlob* vertexShaderBlob;
-        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pVertexShader) != S_OK)
-        {
-            vertexShaderBlob->Release();
-            return false;
-        }
-
-        // Create the input layout
-        D3D10_INPUT_ELEMENT_DESC local_layout[] =
-        {
-            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D10_INPUT_PER_VERTEX_DATA, 0 },
-            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D10_INPUT_PER_VERTEX_DATA, 0 },
-            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D10_INPUT_PER_VERTEX_DATA, 0 },
-        };
-        if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
-        {
-            vertexShaderBlob->Release();
-            return false;
-        }
-        vertexShaderBlob->Release();
-
-        // Create the constant buffer
-        {
-            D3D10_BUFFER_DESC desc;
-            desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX10);
-            desc.Usage = D3D10_USAGE_DYNAMIC;
-            desc.BindFlags = D3D10_BIND_CONSTANT_BUFFER;
-            desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
-            desc.MiscFlags = 0;
-            bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
-        }
-    }
-
-    // Create the pixel shader
-    {
-        static const char* pixelShader =
-            "struct PS_INPUT\
-            {\
-            float4 pos : SV_POSITION;\
-            float4 col : COLOR0;\
-            float2 uv  : TEXCOORD0;\
-            };\
-            sampler sampler0;\
-            Texture2D texture0;\
-            \
-            float4 main(PS_INPUT input) : SV_Target\
-            {\
-            float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
-            return out_col; \
-            }";
-
-        ID3DBlob* pixelShaderBlob;
-        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), &bd->pPixelShader) != S_OK)
-        {
-            pixelShaderBlob->Release();
-            return false;
-        }
-        pixelShaderBlob->Release();
-    }
-
-    // Create the blending setup
-    {
-        D3D10_BLEND_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.AlphaToCoverageEnable = false;
-        desc.BlendEnable[0] = true;
-        desc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
-        desc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
-        desc.BlendOp = D3D10_BLEND_OP_ADD;
-        desc.SrcBlendAlpha = D3D10_BLEND_ONE;
-        desc.DestBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA;
-        desc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
-        desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;
-        bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
-    }
-
-    // Create the rasterizer state
-    {
-        D3D10_RASTERIZER_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.FillMode = D3D10_FILL_SOLID;
-        desc.CullMode = D3D10_CULL_NONE;
-        desc.ScissorEnable = true;
-        desc.DepthClipEnable = true;
-        bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
-    }
-
-    // Create depth-stencil State
-    {
-        D3D10_DEPTH_STENCIL_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.DepthEnable = false;
-        desc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
-        desc.DepthFunc = D3D10_COMPARISON_ALWAYS;
-        desc.StencilEnable = false;
-        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
-        desc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
-        desc.BackFace = desc.FrontFace;
-        bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
-    }
-
-    ImGui_ImplDX10_CreateFontsTexture();
-
-    return true;
-}
-
-void    ImGui_ImplDX10_InvalidateDeviceObjects()
-{
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    if (!bd->pd3dDevice)
-        return;
-
-    if (bd->pFontSampler)           { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
-    if (bd->pFontTextureView)       { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
-    if (bd->pIB)                    { bd->pIB->Release(); bd->pIB = nullptr; }
-    if (bd->pVB)                    { bd->pVB->Release(); bd->pVB = nullptr; }
-    if (bd->pBlendState)            { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
-    if (bd->pDepthStencilState)     { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
-    if (bd->pRasterizerState)       { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
-    if (bd->pPixelShader)           { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
-    if (bd->pVertexConstantBuffer)  { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
-    if (bd->pInputLayout)           { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
-    if (bd->pVertexShader)          { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
-}
-
-bool    ImGui_ImplDX10_Init(ID3D10Device* device)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplDX10_Data* bd = IM_NEW(ImGui_ImplDX10_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_dx10";
-    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-
-    // Get factory from device
-    IDXGIDevice* pDXGIDevice = nullptr;
-    IDXGIAdapter* pDXGIAdapter = nullptr;
-    IDXGIFactory* pFactory = nullptr;
-    if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
-        if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
-            if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
-            {
-                bd->pd3dDevice = device;
-                bd->pFactory = pFactory;
-            }
-    if (pDXGIDevice) pDXGIDevice->Release();
-    if (pDXGIAdapter) pDXGIAdapter->Release();
-    bd->pd3dDevice->AddRef();
-
-    return true;
-}
-
-void ImGui_ImplDX10_Shutdown()
-{
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplDX10_InvalidateDeviceObjects();
-    if (bd->pFactory) { bd->pFactory->Release(); }
-    if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
-}
-
-void ImGui_ImplDX10_NewFrame()
-{
-    ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX10_Init()?");
-
-    if (!bd->pFontSampler)
-        ImGui_ImplDX10_CreateDeviceObjects();
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx10.h b/engines/twp/imgui/backends/imgui_impl_dx10.h
deleted file mode 100644
index e7e798aa496..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx10.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// dear imgui: Renderer Backend for DirectX10
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct ID3D10Device;
-
-IMGUI_IMPL_API bool     ImGui_ImplDX10_Init(ID3D10Device* device);
-IMGUI_IMPL_API void     ImGui_ImplDX10_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplDX10_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data);
-
-// Use if you want to reset your rendering device without losing Dear ImGui state.
-IMGUI_IMPL_API void     ImGui_ImplDX10_InvalidateDeviceObjects();
-IMGUI_IMPL_API bool     ImGui_ImplDX10_CreateDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx11.cpp b/engines/twp/imgui/backends/imgui_impl_dx11.cpp
deleted file mode 100644
index bbc26a7514e..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx11.cpp
+++ /dev/null
@@ -1,605 +0,0 @@
-// dear imgui: Renderer Backend for DirectX11
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
-//  2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
-//  2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
-//  2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.
-//  2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.
-//  2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.
-//  2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2016-05-07: DirectX11: Disabling depth-write.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_dx11.h"
-
-// DirectX
-#include <stdio.h>
-#include <d3d11.h>
-#include <d3dcompiler.h>
-#ifdef _MSC_VER
-#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
-#endif
-
-// DirectX11 data
-struct ImGui_ImplDX11_Data
-{
-    ID3D11Device*               pd3dDevice;
-    ID3D11DeviceContext*        pd3dDeviceContext;
-    IDXGIFactory*               pFactory;
-    ID3D11Buffer*               pVB;
-    ID3D11Buffer*               pIB;
-    ID3D11VertexShader*         pVertexShader;
-    ID3D11InputLayout*          pInputLayout;
-    ID3D11Buffer*               pVertexConstantBuffer;
-    ID3D11PixelShader*          pPixelShader;
-    ID3D11SamplerState*         pFontSampler;
-    ID3D11ShaderResourceView*   pFontTextureView;
-    ID3D11RasterizerState*      pRasterizerState;
-    ID3D11BlendState*           pBlendState;
-    ID3D11DepthStencilState*    pDepthStencilState;
-    int                         VertexBufferSize;
-    int                         IndexBufferSize;
-
-    ImGui_ImplDX11_Data()       { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
-};
-
-struct VERTEX_CONSTANT_BUFFER_DX11
-{
-    float   mvp[4][4];
-};
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// Functions
-static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx)
-{
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-
-    // Setup viewport
-    D3D11_VIEWPORT vp;
-    memset(&vp, 0, sizeof(D3D11_VIEWPORT));
-    vp.Width = draw_data->DisplaySize.x;
-    vp.Height = draw_data->DisplaySize.y;
-    vp.MinDepth = 0.0f;
-    vp.MaxDepth = 1.0f;
-    vp.TopLeftX = vp.TopLeftY = 0;
-    ctx->RSSetViewports(1, &vp);
-
-    // Setup shader and vertex buffers
-    unsigned int stride = sizeof(ImDrawVert);
-    unsigned int offset = 0;
-    ctx->IASetInputLayout(bd->pInputLayout);
-    ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
-    ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
-    ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-    ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
-    ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
-    ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
-    ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
-    ctx->GSSetShader(nullptr, nullptr, 0);
-    ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
-    ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
-    ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
-
-    // Setup blend state
-    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
-    ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
-    ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
-    ctx->RSSetState(bd->pRasterizerState);
-}
-
-// Render function
-void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized
-    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
-        return;
-
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    ID3D11DeviceContext* ctx = bd->pd3dDeviceContext;
-
-    // Create and grow vertex/index buffers if needed
-    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
-    {
-        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
-        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
-        D3D11_BUFFER_DESC desc;
-        memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
-        desc.Usage = D3D11_USAGE_DYNAMIC;
-        desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
-        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
-        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
-        desc.MiscFlags = 0;
-        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
-            return;
-    }
-    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
-    {
-        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
-        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
-        D3D11_BUFFER_DESC desc;
-        memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
-        desc.Usage = D3D11_USAGE_DYNAMIC;
-        desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
-        desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
-        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
-        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
-            return;
-    }
-
-    // Upload vertex/index data into a single contiguous GPU buffer
-    D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
-    if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
-        return;
-    if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
-        return;
-    ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
-    ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
-        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
-        vtx_dst += cmd_list->VtxBuffer.Size;
-        idx_dst += cmd_list->IdxBuffer.Size;
-    }
-    ctx->Unmap(bd->pVB, 0);
-    ctx->Unmap(bd->pIB, 0);
-
-    // Setup orthographic projection matrix into our constant buffer
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    {
-        D3D11_MAPPED_SUBRESOURCE mapped_resource;
-        if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
-            return;
-        VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
-        float L = draw_data->DisplayPos.x;
-        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-        float T = draw_data->DisplayPos.y;
-        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
-        float mvp[4][4] =
-        {
-            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
-            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
-            { 0.0f,         0.0f,           0.5f,       0.0f },
-            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
-        };
-        memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
-        ctx->Unmap(bd->pVertexConstantBuffer, 0);
-    }
-
-    // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
-    struct BACKUP_DX11_STATE
-    {
-        UINT                        ScissorRectsCount, ViewportsCount;
-        D3D11_RECT                  ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
-        D3D11_VIEWPORT              Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
-        ID3D11RasterizerState*      RS;
-        ID3D11BlendState*           BlendState;
-        FLOAT                       BlendFactor[4];
-        UINT                        SampleMask;
-        UINT                        StencilRef;
-        ID3D11DepthStencilState*    DepthStencilState;
-        ID3D11ShaderResourceView*   PSShaderResource;
-        ID3D11SamplerState*         PSSampler;
-        ID3D11PixelShader*          PS;
-        ID3D11VertexShader*         VS;
-        ID3D11GeometryShader*       GS;
-        UINT                        PSInstancesCount, VSInstancesCount, GSInstancesCount;
-        ID3D11ClassInstance         *PSInstances[256], *VSInstances[256], *GSInstances[256];   // 256 is max according to PSSetShader documentation
-        D3D11_PRIMITIVE_TOPOLOGY    PrimitiveTopology;
-        ID3D11Buffer*               IndexBuffer, *VertexBuffer, *VSConstantBuffer;
-        UINT                        IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
-        DXGI_FORMAT                 IndexBufferFormat;
-        ID3D11InputLayout*          InputLayout;
-    };
-    BACKUP_DX11_STATE old = {};
-    old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
-    ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
-    ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
-    ctx->RSGetState(&old.RS);
-    ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
-    ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
-    ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
-    ctx->PSGetSamplers(0, 1, &old.PSSampler);
-    old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
-    ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
-    ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
-    ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
-    ctx->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
-
-    ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
-    ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
-    ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
-    ctx->IAGetInputLayout(&old.InputLayout);
-
-    // Setup desired DX state
-    ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
-
-    // Render command lists
-    // (Because we merged all buffers into a single one, we maintain our own offset into them)
-    int global_idx_offset = 0;
-    int global_vtx_offset = 0;
-    ImVec2 clip_off = draw_data->DisplayPos;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback != nullptr)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
-                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle
-                const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
-                ctx->RSSetScissorRects(1, &r);
-
-                // Bind texture, Draw
-                ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();
-                ctx->PSSetShaderResources(0, 1, &texture_srv);
-                ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
-            }
-        }
-        global_idx_offset += cmd_list->IdxBuffer.Size;
-        global_vtx_offset += cmd_list->VtxBuffer.Size;
-    }
-
-    // Restore modified DX state
-    ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
-    ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
-    ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
-    ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
-    ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
-    ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
-    ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
-    ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
-    for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
-    ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
-    ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
-    ctx->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
-    for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
-    ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
-    ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
-    ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
-    ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
-}
-
-static void ImGui_ImplDX11_CreateFontsTexture()
-{
-    // Build texture atlas
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-
-    // Upload texture to graphics system
-    {
-        D3D11_TEXTURE2D_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.Width = width;
-        desc.Height = height;
-        desc.MipLevels = 1;
-        desc.ArraySize = 1;
-        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        desc.SampleDesc.Count = 1;
-        desc.Usage = D3D11_USAGE_DEFAULT;
-        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
-        desc.CPUAccessFlags = 0;
-
-        ID3D11Texture2D* pTexture = nullptr;
-        D3D11_SUBRESOURCE_DATA subResource;
-        subResource.pSysMem = pixels;
-        subResource.SysMemPitch = desc.Width * 4;
-        subResource.SysMemSlicePitch = 0;
-        bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
-        IM_ASSERT(pTexture != nullptr);
-
-        // Create texture view
-        D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
-        ZeroMemory(&srvDesc, sizeof(srvDesc));
-        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
-        srvDesc.Texture2D.MipLevels = desc.MipLevels;
-        srvDesc.Texture2D.MostDetailedMip = 0;
-        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);
-        pTexture->Release();
-    }
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);
-
-    // Create texture sampler
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    {
-        D3D11_SAMPLER_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
-        desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
-        desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
-        desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
-        desc.MipLODBias = 0.f;
-        desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
-        desc.MinLOD = 0.f;
-        desc.MaxLOD = 0.f;
-        bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
-    }
-}
-
-bool    ImGui_ImplDX11_CreateDeviceObjects()
-{
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    if (!bd->pd3dDevice)
-        return false;
-    if (bd->pFontSampler)
-        ImGui_ImplDX11_InvalidateDeviceObjects();
-
-    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
-    // If you would like to use this DX11 sample code but remove this dependency you can:
-    //  1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
-    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
-    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
-
-    // Create the vertex shader
-    {
-        static const char* vertexShader =
-            "cbuffer vertexBuffer : register(b0) \
-            {\
-              float4x4 ProjectionMatrix; \
-            };\
-            struct VS_INPUT\
-            {\
-              float2 pos : POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            struct PS_INPUT\
-            {\
-              float4 pos : SV_POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            PS_INPUT main(VS_INPUT input)\
-            {\
-              PS_INPUT output;\
-              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
-              output.col = input.col;\
-              output.uv  = input.uv;\
-              return output;\
-            }";
-
-        ID3DBlob* vertexShaderBlob;
-        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &bd->pVertexShader) != S_OK)
-        {
-            vertexShaderBlob->Release();
-            return false;
-        }
-
-        // Create the input layout
-        D3D11_INPUT_ELEMENT_DESC local_layout[] =
-        {
-            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
-            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D11_INPUT_PER_VERTEX_DATA, 0 },
-            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
-        };
-        if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
-        {
-            vertexShaderBlob->Release();
-            return false;
-        }
-        vertexShaderBlob->Release();
-
-        // Create the constant buffer
-        {
-            D3D11_BUFFER_DESC desc;
-            desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
-            desc.Usage = D3D11_USAGE_DYNAMIC;
-            desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
-            desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
-            desc.MiscFlags = 0;
-            bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
-        }
-    }
-
-    // Create the pixel shader
-    {
-        static const char* pixelShader =
-            "struct PS_INPUT\
-            {\
-            float4 pos : SV_POSITION;\
-            float4 col : COLOR0;\
-            float2 uv  : TEXCOORD0;\
-            };\
-            sampler sampler0;\
-            Texture2D texture0;\
-            \
-            float4 main(PS_INPUT input) : SV_Target\
-            {\
-            float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
-            return out_col; \
-            }";
-
-        ID3DBlob* pixelShaderBlob;
-        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &bd->pPixelShader) != S_OK)
-        {
-            pixelShaderBlob->Release();
-            return false;
-        }
-        pixelShaderBlob->Release();
-    }
-
-    // Create the blending setup
-    {
-        D3D11_BLEND_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.AlphaToCoverageEnable = false;
-        desc.RenderTarget[0].BlendEnable = true;
-        desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
-        desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
-        desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
-        desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
-        desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
-        desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
-        desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
-        bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
-    }
-
-    // Create the rasterizer state
-    {
-        D3D11_RASTERIZER_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.FillMode = D3D11_FILL_SOLID;
-        desc.CullMode = D3D11_CULL_NONE;
-        desc.ScissorEnable = true;
-        desc.DepthClipEnable = true;
-        bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
-    }
-
-    // Create depth-stencil State
-    {
-        D3D11_DEPTH_STENCIL_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.DepthEnable = false;
-        desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
-        desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
-        desc.StencilEnable = false;
-        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
-        desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
-        desc.BackFace = desc.FrontFace;
-        bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
-    }
-
-    ImGui_ImplDX11_CreateFontsTexture();
-
-    return true;
-}
-
-void    ImGui_ImplDX11_InvalidateDeviceObjects()
-{
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    if (!bd->pd3dDevice)
-        return;
-
-    if (bd->pFontSampler)           { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
-    if (bd->pFontTextureView)       { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well.
-    if (bd->pIB)                    { bd->pIB->Release(); bd->pIB = nullptr; }
-    if (bd->pVB)                    { bd->pVB->Release(); bd->pVB = nullptr; }
-    if (bd->pBlendState)            { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
-    if (bd->pDepthStencilState)     { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
-    if (bd->pRasterizerState)       { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
-    if (bd->pPixelShader)           { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
-    if (bd->pVertexConstantBuffer)  { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
-    if (bd->pInputLayout)           { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
-    if (bd->pVertexShader)          { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
-}
-
-bool    ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_dx11";
-    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-
-    // Get factory from device
-    IDXGIDevice* pDXGIDevice = nullptr;
-    IDXGIAdapter* pDXGIAdapter = nullptr;
-    IDXGIFactory* pFactory = nullptr;
-
-    if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
-        if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
-            if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
-            {
-                bd->pd3dDevice = device;
-                bd->pd3dDeviceContext = device_context;
-                bd->pFactory = pFactory;
-            }
-    if (pDXGIDevice) pDXGIDevice->Release();
-    if (pDXGIAdapter) pDXGIAdapter->Release();
-    bd->pd3dDevice->AddRef();
-    bd->pd3dDeviceContext->AddRef();
-
-    return true;
-}
-
-void ImGui_ImplDX11_Shutdown()
-{
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplDX11_InvalidateDeviceObjects();
-    if (bd->pFactory)             { bd->pFactory->Release(); }
-    if (bd->pd3dDevice)           { bd->pd3dDevice->Release(); }
-    if (bd->pd3dDeviceContext)    { bd->pd3dDeviceContext->Release(); }
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
-}
-
-void ImGui_ImplDX11_NewFrame()
-{
-    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?");
-
-    if (!bd->pFontSampler)
-        ImGui_ImplDX11_CreateDeviceObjects();
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx11.h b/engines/twp/imgui/backends/imgui_impl_dx11.h
deleted file mode 100644
index 20887f37070..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx11.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// dear imgui: Renderer Backend for DirectX11
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct ID3D11Device;
-struct ID3D11DeviceContext;
-
-IMGUI_IMPL_API bool     ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
-IMGUI_IMPL_API void     ImGui_ImplDX11_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplDX11_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
-
-// Use if you want to reset your rendering device without losing Dear ImGui state.
-IMGUI_IMPL_API void     ImGui_ImplDX11_InvalidateDeviceObjects();
-IMGUI_IMPL_API bool     ImGui_ImplDX11_CreateDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx12.cpp b/engines/twp/imgui/backends/imgui_impl_dx12.cpp
deleted file mode 100644
index 15aadc05807..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx12.cpp
+++ /dev/null
@@ -1,761 +0,0 @@
-// dear imgui: Renderer Backend for DirectX12
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
-// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*.
-// To build this on 32-bit systems:
-// - [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file)
-// - [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like.
-// - [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!)
-// - [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file)
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-02-18: DirectX12: Change blending equation to preserve alpha in output buffer.
-//  2021-01-11: DirectX12: Improve Windows 7 compatibility (for D3D12On7) by loading d3d12.dll dynamically.
-//  2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning.
-//  2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID.
-//  2019-10-18: DirectX12: *BREAKING CHANGE* Added extra ID3D12DescriptorHeap parameter to ImGui_ImplDX12_Init() function.
-//  2019-05-29: DirectX12: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: DirectX12: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2019-03-29: Misc: Various minor tidying up.
-//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-06-12: DirectX12: Moved the ID3D12GraphicsCommandList* parameter from NewFrame() to RenderDrawData().
-//  2018-06-08: Misc: Extracted imgui_impl_dx12.cpp/.h away from the old combined DX12+Win32 example.
-//  2018-06-08: DirectX12: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle (to ease support for future multi-viewport).
-//  2018-02-22: Merged into master with all Win32 code synchronized to other examples.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_dx12.h"
-
-// DirectX
-#include <d3d12.h>
-#include <dxgi1_4.h>
-#include <d3dcompiler.h>
-#ifdef _MSC_VER
-#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
-#endif
-
-// DirectX data
-struct ImGui_ImplDX12_RenderBuffers;
-struct ImGui_ImplDX12_Data
-{
-    ID3D12Device*               pd3dDevice;
-    ID3D12RootSignature*        pRootSignature;
-    ID3D12PipelineState*        pPipelineState;
-    DXGI_FORMAT                 RTVFormat;
-    ID3D12Resource*             pFontTextureResource;
-    D3D12_CPU_DESCRIPTOR_HANDLE hFontSrvCpuDescHandle;
-    D3D12_GPU_DESCRIPTOR_HANDLE hFontSrvGpuDescHandle;
-    ID3D12DescriptorHeap*       pd3dSrvDescHeap;
-    UINT                        numFramesInFlight;
-
-    ImGui_ImplDX12_RenderBuffers* pFrameResources;
-    UINT                        frameIndex;
-
-    ImGui_ImplDX12_Data()       { memset((void*)this, 0, sizeof(*this)); frameIndex = UINT_MAX; }
-};
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// Buffers used during the rendering of a frame
-struct ImGui_ImplDX12_RenderBuffers
-{
-    ID3D12Resource*     IndexBuffer;
-    ID3D12Resource*     VertexBuffer;
-    int                 IndexBufferSize;
-    int                 VertexBufferSize;
-};
-
-struct VERTEX_CONSTANT_BUFFER_DX12
-{
-    float   mvp[4][4];
-};
-
-// Functions
-static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx, ImGui_ImplDX12_RenderBuffers* fr)
-{
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-
-    // Setup orthographic projection matrix into our constant buffer
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
-    VERTEX_CONSTANT_BUFFER_DX12 vertex_constant_buffer;
-    {
-        float L = draw_data->DisplayPos.x;
-        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-        float T = draw_data->DisplayPos.y;
-        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
-        float mvp[4][4] =
-        {
-            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },
-            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },
-            { 0.0f,         0.0f,           0.5f,       0.0f },
-            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },
-        };
-        memcpy(&vertex_constant_buffer.mvp, mvp, sizeof(mvp));
-    }
-
-    // Setup viewport
-    D3D12_VIEWPORT vp;
-    memset(&vp, 0, sizeof(D3D12_VIEWPORT));
-    vp.Width = draw_data->DisplaySize.x;
-    vp.Height = draw_data->DisplaySize.y;
-    vp.MinDepth = 0.0f;
-    vp.MaxDepth = 1.0f;
-    vp.TopLeftX = vp.TopLeftY = 0.0f;
-    ctx->RSSetViewports(1, &vp);
-
-    // Bind shader and vertex buffers
-    unsigned int stride = sizeof(ImDrawVert);
-    unsigned int offset = 0;
-    D3D12_VERTEX_BUFFER_VIEW vbv;
-    memset(&vbv, 0, sizeof(D3D12_VERTEX_BUFFER_VIEW));
-    vbv.BufferLocation = fr->VertexBuffer->GetGPUVirtualAddress() + offset;
-    vbv.SizeInBytes = fr->VertexBufferSize * stride;
-    vbv.StrideInBytes = stride;
-    ctx->IASetVertexBuffers(0, 1, &vbv);
-    D3D12_INDEX_BUFFER_VIEW ibv;
-    memset(&ibv, 0, sizeof(D3D12_INDEX_BUFFER_VIEW));
-    ibv.BufferLocation = fr->IndexBuffer->GetGPUVirtualAddress();
-    ibv.SizeInBytes = fr->IndexBufferSize * sizeof(ImDrawIdx);
-    ibv.Format = sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
-    ctx->IASetIndexBuffer(&ibv);
-    ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-    ctx->SetPipelineState(bd->pPipelineState);
-    ctx->SetGraphicsRootSignature(bd->pRootSignature);
-    ctx->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0);
-
-    // Setup blend factor
-    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
-    ctx->OMSetBlendFactor(blend_factor);
-}
-
-template<typename T>
-static inline void SafeRelease(T*& res)
-{
-    if (res)
-        res->Release();
-    res = nullptr;
-}
-
-// Render function
-void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx)
-{
-    // Avoid rendering when minimized
-    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
-        return;
-
-    // FIXME: I'm assuming that this only gets called once per frame!
-    // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    bd->frameIndex = bd->frameIndex + 1;
-    ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[bd->frameIndex % bd->numFramesInFlight];
-
-    // Create and grow vertex/index buffers if needed
-    if (fr->VertexBuffer == nullptr || fr->VertexBufferSize < draw_data->TotalVtxCount)
-    {
-        SafeRelease(fr->VertexBuffer);
-        fr->VertexBufferSize = draw_data->TotalVtxCount + 5000;
-        D3D12_HEAP_PROPERTIES props;
-        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
-        props.Type = D3D12_HEAP_TYPE_UPLOAD;
-        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
-        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
-        D3D12_RESOURCE_DESC desc;
-        memset(&desc, 0, sizeof(D3D12_RESOURCE_DESC));
-        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
-        desc.Width = fr->VertexBufferSize * sizeof(ImDrawVert);
-        desc.Height = 1;
-        desc.DepthOrArraySize = 1;
-        desc.MipLevels = 1;
-        desc.Format = DXGI_FORMAT_UNKNOWN;
-        desc.SampleDesc.Count = 1;
-        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
-        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
-        if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fr->VertexBuffer)) < 0)
-            return;
-    }
-    if (fr->IndexBuffer == nullptr || fr->IndexBufferSize < draw_data->TotalIdxCount)
-    {
-        SafeRelease(fr->IndexBuffer);
-        fr->IndexBufferSize = draw_data->TotalIdxCount + 10000;
-        D3D12_HEAP_PROPERTIES props;
-        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
-        props.Type = D3D12_HEAP_TYPE_UPLOAD;
-        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
-        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
-        D3D12_RESOURCE_DESC desc;
-        memset(&desc, 0, sizeof(D3D12_RESOURCE_DESC));
-        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
-        desc.Width = fr->IndexBufferSize * sizeof(ImDrawIdx);
-        desc.Height = 1;
-        desc.DepthOrArraySize = 1;
-        desc.MipLevels = 1;
-        desc.Format = DXGI_FORMAT_UNKNOWN;
-        desc.SampleDesc.Count = 1;
-        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
-        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
-        if (bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fr->IndexBuffer)) < 0)
-            return;
-    }
-
-    // Upload vertex/index data into a single contiguous GPU buffer
-    void* vtx_resource, *idx_resource;
-    D3D12_RANGE range;
-    memset(&range, 0, sizeof(D3D12_RANGE));
-    if (fr->VertexBuffer->Map(0, &range, &vtx_resource) != S_OK)
-        return;
-    if (fr->IndexBuffer->Map(0, &range, &idx_resource) != S_OK)
-        return;
-    ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource;
-    ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
-        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
-        vtx_dst += cmd_list->VtxBuffer.Size;
-        idx_dst += cmd_list->IdxBuffer.Size;
-    }
-    fr->VertexBuffer->Unmap(0, &range);
-    fr->IndexBuffer->Unmap(0, &range);
-
-    // Setup desired DX state
-    ImGui_ImplDX12_SetupRenderState(draw_data, ctx, fr);
-
-    // Render command lists
-    // (Because we merged all buffers into a single one, we maintain our own offset into them)
-    int global_vtx_offset = 0;
-    int global_idx_offset = 0;
-    ImVec2 clip_off = draw_data->DisplayPos;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback != nullptr)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplDX12_SetupRenderState(draw_data, ctx, fr);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
-                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply Scissor/clipping rectangle, Bind texture, Draw
-                const D3D12_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
-                D3D12_GPU_DESCRIPTOR_HANDLE texture_handle = {};
-                texture_handle.ptr = (UINT64)pcmd->GetTexID();
-                ctx->SetGraphicsRootDescriptorTable(1, texture_handle);
-                ctx->RSSetScissorRects(1, &r);
-                ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);
-            }
-        }
-        global_idx_offset += cmd_list->IdxBuffer.Size;
-        global_vtx_offset += cmd_list->VtxBuffer.Size;
-    }
-}
-
-static void ImGui_ImplDX12_CreateFontsTexture()
-{
-    // Build texture atlas
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-
-    // Upload texture to graphics system
-    {
-        D3D12_HEAP_PROPERTIES props;
-        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
-        props.Type = D3D12_HEAP_TYPE_DEFAULT;
-        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
-        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
-
-        D3D12_RESOURCE_DESC desc;
-        ZeroMemory(&desc, sizeof(desc));
-        desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
-        desc.Alignment = 0;
-        desc.Width = width;
-        desc.Height = height;
-        desc.DepthOrArraySize = 1;
-        desc.MipLevels = 1;
-        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        desc.SampleDesc.Count = 1;
-        desc.SampleDesc.Quality = 0;
-        desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
-        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
-
-        ID3D12Resource* pTexture = nullptr;
-        bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
-            D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture));
-
-        UINT uploadPitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u);
-        UINT uploadSize = height * uploadPitch;
-        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
-        desc.Alignment = 0;
-        desc.Width = uploadSize;
-        desc.Height = 1;
-        desc.DepthOrArraySize = 1;
-        desc.MipLevels = 1;
-        desc.Format = DXGI_FORMAT_UNKNOWN;
-        desc.SampleDesc.Count = 1;
-        desc.SampleDesc.Quality = 0;
-        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
-        desc.Flags = D3D12_RESOURCE_FLAG_NONE;
-
-        props.Type = D3D12_HEAP_TYPE_UPLOAD;
-        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
-        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
-
-        ID3D12Resource* uploadBuffer = nullptr;
-        HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
-            D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer));
-        IM_ASSERT(SUCCEEDED(hr));
-
-        void* mapped = nullptr;
-        D3D12_RANGE range = { 0, uploadSize };
-        hr = uploadBuffer->Map(0, &range, &mapped);
-        IM_ASSERT(SUCCEEDED(hr));
-        for (int y = 0; y < height; y++)
-            memcpy((void*) ((uintptr_t) mapped + y * uploadPitch), pixels + y * width * 4, width * 4);
-        uploadBuffer->Unmap(0, &range);
-
-        D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
-        srcLocation.pResource = uploadBuffer;
-        srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
-        srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        srcLocation.PlacedFootprint.Footprint.Width = width;
-        srcLocation.PlacedFootprint.Footprint.Height = height;
-        srcLocation.PlacedFootprint.Footprint.Depth = 1;
-        srcLocation.PlacedFootprint.Footprint.RowPitch = uploadPitch;
-
-        D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
-        dstLocation.pResource = pTexture;
-        dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
-        dstLocation.SubresourceIndex = 0;
-
-        D3D12_RESOURCE_BARRIER barrier = {};
-        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
-        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
-        barrier.Transition.pResource   = pTexture;
-        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
-        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
-        barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
-
-        ID3D12Fence* fence = nullptr;
-        hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
-        IM_ASSERT(SUCCEEDED(hr));
-
-        HANDLE event = CreateEvent(0, 0, 0, 0);
-        IM_ASSERT(event != nullptr);
-
-        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
-        queueDesc.Type     = D3D12_COMMAND_LIST_TYPE_DIRECT;
-        queueDesc.Flags    = D3D12_COMMAND_QUEUE_FLAG_NONE;
-        queueDesc.NodeMask = 1;
-
-        ID3D12CommandQueue* cmdQueue = nullptr;
-        hr = bd->pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue));
-        IM_ASSERT(SUCCEEDED(hr));
-
-        ID3D12CommandAllocator* cmdAlloc = nullptr;
-        hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
-        IM_ASSERT(SUCCEEDED(hr));
-
-        ID3D12GraphicsCommandList* cmdList = nullptr;
-        hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList));
-        IM_ASSERT(SUCCEEDED(hr));
-
-        cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr);
-        cmdList->ResourceBarrier(1, &barrier);
-
-        hr = cmdList->Close();
-        IM_ASSERT(SUCCEEDED(hr));
-
-        cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList);
-        hr = cmdQueue->Signal(fence, 1);
-        IM_ASSERT(SUCCEEDED(hr));
-
-        fence->SetEventOnCompletion(1, event);
-        WaitForSingleObject(event, INFINITE);
-
-        cmdList->Release();
-        cmdAlloc->Release();
-        cmdQueue->Release();
-        CloseHandle(event);
-        fence->Release();
-        uploadBuffer->Release();
-
-        // Create texture view
-        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
-        ZeroMemory(&srvDesc, sizeof(srvDesc));
-        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
-        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
-        srvDesc.Texture2D.MipLevels = desc.MipLevels;
-        srvDesc.Texture2D.MostDetailedMip = 0;
-        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
-        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, bd->hFontSrvCpuDescHandle);
-        SafeRelease(bd->pFontTextureResource);
-        bd->pFontTextureResource = pTexture;
-    }
-
-    // Store our identifier
-    // READ THIS IF THE STATIC_ASSERT() TRIGGERS:
-    // - Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
-    // - This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*.
-    // [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file)
-    // [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like.
-    // [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!)
-    // [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file)
-    static_assert(sizeof(ImTextureID) >= sizeof(bd->hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
-    io.Fonts->SetTexID((ImTextureID)bd->hFontSrvGpuDescHandle.ptr);
-}
-
-bool    ImGui_ImplDX12_CreateDeviceObjects()
-{
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    if (!bd || !bd->pd3dDevice)
-        return false;
-    if (bd->pPipelineState)
-        ImGui_ImplDX12_InvalidateDeviceObjects();
-
-    // Create the root signature
-    {
-        D3D12_DESCRIPTOR_RANGE descRange = {};
-        descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
-        descRange.NumDescriptors = 1;
-        descRange.BaseShaderRegister = 0;
-        descRange.RegisterSpace = 0;
-        descRange.OffsetInDescriptorsFromTableStart = 0;
-
-        D3D12_ROOT_PARAMETER param[2] = {};
-
-        param[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
-        param[0].Constants.ShaderRegister = 0;
-        param[0].Constants.RegisterSpace = 0;
-        param[0].Constants.Num32BitValues = 16;
-        param[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
-
-        param[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
-        param[1].DescriptorTable.NumDescriptorRanges = 1;
-        param[1].DescriptorTable.pDescriptorRanges = &descRange;
-        param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
-
-        // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
-        D3D12_STATIC_SAMPLER_DESC staticSampler = {};
-        staticSampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
-        staticSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
-        staticSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
-        staticSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
-        staticSampler.MipLODBias = 0.f;
-        staticSampler.MaxAnisotropy = 0;
-        staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
-        staticSampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
-        staticSampler.MinLOD = 0.f;
-        staticSampler.MaxLOD = 0.f;
-        staticSampler.ShaderRegister = 0;
-        staticSampler.RegisterSpace = 0;
-        staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
-
-        D3D12_ROOT_SIGNATURE_DESC desc = {};
-        desc.NumParameters = _countof(param);
-        desc.pParameters = param;
-        desc.NumStaticSamplers = 1;
-        desc.pStaticSamplers = &staticSampler;
-        desc.Flags =
-            D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT |
-            D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
-            D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
-            D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS;
-
-        // Load d3d12.dll and D3D12SerializeRootSignature() function address dynamically to facilitate using with D3D12On7.
-        // See if any version of d3d12.dll is already loaded in the process. If so, give preference to that.
-        static HINSTANCE d3d12_dll = ::GetModuleHandleA("d3d12.dll");
-        if (d3d12_dll == nullptr)
-        {
-            // Attempt to load d3d12.dll from local directories. This will only succeed if
-            // (1) the current OS is Windows 7, and
-            // (2) there exists a version of d3d12.dll for Windows 7 (D3D12On7) in one of the following directories.
-            // See https://github.com/ocornut/imgui/pull/3696 for details.
-            const char* localD3d12Paths[] = { ".\\d3d12.dll", ".\\d3d12on7\\d3d12.dll", ".\\12on7\\d3d12.dll" }; // A. current directory, B. used by some games, C. used in Microsoft D3D12On7 sample
-            for (int i = 0; i < IM_ARRAYSIZE(localD3d12Paths); i++)
-                if ((d3d12_dll = ::LoadLibraryA(localD3d12Paths[i])) != nullptr)
-                    break;
-
-            // If failed, we are on Windows >= 10.
-            if (d3d12_dll == nullptr)
-                d3d12_dll = ::LoadLibraryA("d3d12.dll");
-
-            if (d3d12_dll == nullptr)
-                return false;
-        }
-
-        PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignatureFn = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)::GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature");
-        if (D3D12SerializeRootSignatureFn == nullptr)
-            return false;
-
-        ID3DBlob* blob = nullptr;
-        if (D3D12SerializeRootSignatureFn(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, nullptr) != S_OK)
-            return false;
-
-        bd->pd3dDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&bd->pRootSignature));
-        blob->Release();
-    }
-
-    // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
-    // If you would like to use this DX12 sample code but remove this dependency you can:
-    //  1) compile once, save the compiled shader blobs into a file or source code and assign them to psoDesc.VS/PS [preferred solution]
-    //  2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
-    // See https://github.com/ocornut/imgui/pull/638 for sources and details.
-
-    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
-    memset(&psoDesc, 0, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
-    psoDesc.NodeMask = 1;
-    psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
-    psoDesc.pRootSignature = bd->pRootSignature;
-    psoDesc.SampleMask = UINT_MAX;
-    psoDesc.NumRenderTargets = 1;
-    psoDesc.RTVFormats[0] = bd->RTVFormat;
-    psoDesc.SampleDesc.Count = 1;
-    psoDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
-
-    ID3DBlob* vertexShaderBlob;
-    ID3DBlob* pixelShaderBlob;
-
-    // Create the vertex shader
-    {
-        static const char* vertexShader =
-            "cbuffer vertexBuffer : register(b0) \
-            {\
-              float4x4 ProjectionMatrix; \
-            };\
-            struct VS_INPUT\
-            {\
-              float2 pos : POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            struct PS_INPUT\
-            {\
-              float4 pos : SV_POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            \
-            PS_INPUT main(VS_INPUT input)\
-            {\
-              PS_INPUT output;\
-              output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
-              output.col = input.col;\
-              output.uv  = input.uv;\
-              return output;\
-            }";
-
-        if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vertexShaderBlob, nullptr)))
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        psoDesc.VS = { vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize() };
-
-        // Create the input layout
-        static D3D12_INPUT_ELEMENT_DESC local_layout[] =
-        {
-            { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-            { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
-        };
-        psoDesc.InputLayout = { local_layout, 3 };
-    }
-
-    // Create the pixel shader
-    {
-        static const char* pixelShader =
-            "struct PS_INPUT\
-            {\
-              float4 pos : SV_POSITION;\
-              float4 col : COLOR0;\
-              float2 uv  : TEXCOORD0;\
-            };\
-            SamplerState sampler0 : register(s0);\
-            Texture2D texture0 : register(t0);\
-            \
-            float4 main(PS_INPUT input) : SV_Target\
-            {\
-              float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
-              return out_col; \
-            }";
-
-        if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &pixelShaderBlob, nullptr)))
-        {
-            vertexShaderBlob->Release();
-            return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
-        }
-        psoDesc.PS = { pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize() };
-    }
-
-    // Create the blending setup
-    {
-        D3D12_BLEND_DESC& desc = psoDesc.BlendState;
-        desc.AlphaToCoverageEnable = false;
-        desc.RenderTarget[0].BlendEnable = true;
-        desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
-        desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
-        desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
-        desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
-        desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
-        desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
-        desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
-    }
-
-    // Create the rasterizer state
-    {
-        D3D12_RASTERIZER_DESC& desc = psoDesc.RasterizerState;
-        desc.FillMode = D3D12_FILL_MODE_SOLID;
-        desc.CullMode = D3D12_CULL_MODE_NONE;
-        desc.FrontCounterClockwise = FALSE;
-        desc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
-        desc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
-        desc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
-        desc.DepthClipEnable = true;
-        desc.MultisampleEnable = FALSE;
-        desc.AntialiasedLineEnable = FALSE;
-        desc.ForcedSampleCount = 0;
-        desc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
-    }
-
-    // Create depth-stencil State
-    {
-        D3D12_DEPTH_STENCIL_DESC& desc = psoDesc.DepthStencilState;
-        desc.DepthEnable = false;
-        desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
-        desc.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
-        desc.StencilEnable = false;
-        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
-        desc.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
-        desc.BackFace = desc.FrontFace;
-    }
-
-    HRESULT result_pipeline_state = bd->pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&bd->pPipelineState));
-    vertexShaderBlob->Release();
-    pixelShaderBlob->Release();
-    if (result_pipeline_state != S_OK)
-        return false;
-
-    ImGui_ImplDX12_CreateFontsTexture();
-
-    return true;
-}
-
-void    ImGui_ImplDX12_InvalidateDeviceObjects()
-{
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    if (!bd || !bd->pd3dDevice)
-        return;
-
-    ImGuiIO& io = ImGui::GetIO();
-    SafeRelease(bd->pRootSignature);
-    SafeRelease(bd->pPipelineState);
-    SafeRelease(bd->pFontTextureResource);
-    io.Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
-
-    for (UINT i = 0; i < bd->numFramesInFlight; i++)
-    {
-        ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i];
-        SafeRelease(fr->IndexBuffer);
-        SafeRelease(fr->VertexBuffer);
-    }
-}
-
-bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap,
-                         D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_dx12";
-    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-
-    bd->pd3dDevice = device;
-    bd->RTVFormat = rtv_format;
-    bd->hFontSrvCpuDescHandle = font_srv_cpu_desc_handle;
-    bd->hFontSrvGpuDescHandle = font_srv_gpu_desc_handle;
-    bd->pFrameResources = new ImGui_ImplDX12_RenderBuffers[num_frames_in_flight];
-    bd->numFramesInFlight = num_frames_in_flight;
-    bd->pd3dSrvDescHeap = cbv_srv_heap;
-    bd->frameIndex = UINT_MAX;
-
-    // Create buffers with a default size (they will later be grown as needed)
-    for (int i = 0; i < num_frames_in_flight; i++)
-    {
-        ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i];
-        fr->IndexBuffer = nullptr;
-        fr->VertexBuffer = nullptr;
-        fr->IndexBufferSize = 10000;
-        fr->VertexBufferSize = 5000;
-    }
-
-    return true;
-}
-
-void ImGui_ImplDX12_Shutdown()
-{
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Clean up windows and device objects
-    ImGui_ImplDX12_InvalidateDeviceObjects();
-    delete[] bd->pFrameResources;
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
-}
-
-void ImGui_ImplDX12_NewFrame()
-{
-    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX12_Init()?");
-
-    if (!bd->pPipelineState)
-        ImGui_ImplDX12_CreateDeviceObjects();
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx12.h b/engines/twp/imgui/backends/imgui_impl_dx12.h
deleted file mode 100644
index 8710910b4ce..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx12.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// dear imgui: Renderer Backend for DirectX12
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'.
-// See imgui_impl_dx12.cpp file for details.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-#include <dxgiformat.h> // DXGI_FORMAT
-
-struct ID3D12Device;
-struct ID3D12DescriptorHeap;
-struct ID3D12GraphicsCommandList;
-struct D3D12_CPU_DESCRIPTOR_HANDLE;
-struct D3D12_GPU_DESCRIPTOR_HANDLE;
-
-// cmd_list is the command list that the implementation will use to render imgui draw lists.
-// Before calling the render function, caller must prepare cmd_list by resetting it and setting the appropriate
-// render target and descriptor heap that contains font_srv_cpu_desc_handle/font_srv_gpu_desc_handle.
-// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture.
-IMGUI_IMPL_API bool     ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap,
-                                            D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle);
-IMGUI_IMPL_API void     ImGui_ImplDX12_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplDX12_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list);
-
-// Use if you want to reset your rendering device without losing Dear ImGui state.
-IMGUI_IMPL_API void     ImGui_ImplDX12_InvalidateDeviceObjects();
-IMGUI_IMPL_API bool     ImGui_ImplDX12_CreateDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx9.cpp b/engines/twp/imgui/backends/imgui_impl_dx9.cpp
deleted file mode 100644
index 48fee4bd5b2..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx9.cpp
+++ /dev/null
@@ -1,388 +0,0 @@
-// dear imgui: Renderer Backend for DirectX9
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
-//  2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
-//  2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
-//  2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
-//  2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
-//  2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
-//  2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288.
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example.
-//  2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_dx9.h"
-
-// DirectX
-#include <d3d9.h>
-
-// DirectX data
-struct ImGui_ImplDX9_Data
-{
-    LPDIRECT3DDEVICE9           pd3dDevice;
-    LPDIRECT3DVERTEXBUFFER9     pVB;
-    LPDIRECT3DINDEXBUFFER9      pIB;
-    LPDIRECT3DTEXTURE9          FontTexture;
-    int                         VertexBufferSize;
-    int                         IndexBufferSize;
-
-    ImGui_ImplDX9_Data()        { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
-};
-
-struct CUSTOMVERTEX
-{
-    float    pos[3];
-    D3DCOLOR col;
-    float    uv[2];
-};
-#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
-
-#ifdef IMGUI_USE_BGRA_PACKED_COLOR
-#define IMGUI_COL_TO_DX9_ARGB(_COL)     (_COL)
-#else
-#define IMGUI_COL_TO_DX9_ARGB(_COL)     (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
-#endif
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// Functions
-static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
-{
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-
-    // Setup viewport
-    D3DVIEWPORT9 vp;
-    vp.X = vp.Y = 0;
-    vp.Width = (DWORD)draw_data->DisplaySize.x;
-    vp.Height = (DWORD)draw_data->DisplaySize.y;
-    vp.MinZ = 0.0f;
-    vp.MaxZ = 1.0f;
-    bd->pd3dDevice->SetViewport(&vp);
-
-    // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
-    bd->pd3dDevice->SetPixelShader(nullptr);
-    bd->pd3dDevice->SetVertexShader(nullptr);
-    bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
-    bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
-    bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
-    bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
-    bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
-    bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
-    bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
-    bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
-    bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
-    bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
-    bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
-    bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
-    bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE);
-    bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
-    bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
-    bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
-    bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
-    bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
-    bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
-
-    // Setup orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    // Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH()
-    {
-        float L = draw_data->DisplayPos.x + 0.5f;
-        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
-        float T = draw_data->DisplayPos.y + 0.5f;
-        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
-        D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f, 1.0f } } };
-        D3DMATRIX mat_projection =
-        { { {
-            2.0f/(R-L),   0.0f,         0.0f,  0.0f,
-            0.0f,         2.0f/(T-B),   0.0f,  0.0f,
-            0.0f,         0.0f,         0.5f,  0.0f,
-            (L+R)/(L-R),  (T+B)/(B-T),  0.5f,  1.0f
-        } } };
-        bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
-        bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
-        bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
-    }
-}
-
-// Render function.
-void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized
-    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
-        return;
-
-    // Create and grow buffers if needed
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
-    {
-        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
-        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
-        if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
-            return;
-    }
-    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
-    {
-        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
-        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
-        if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
-            return;
-    }
-
-    // Backup the DX9 state
-    IDirect3DStateBlock9* d3d9_state_block = nullptr;
-    if (bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
-        return;
-    if (d3d9_state_block->Capture() < 0)
-    {
-        d3d9_state_block->Release();
-        return;
-    }
-
-    // Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
-    D3DMATRIX last_world, last_view, last_projection;
-    bd->pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
-    bd->pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
-    bd->pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
-
-    // Allocate buffers
-    CUSTOMVERTEX* vtx_dst;
-    ImDrawIdx* idx_dst;
-    if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
-    {
-        d3d9_state_block->Release();
-        return;
-    }
-    if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
-    {
-        bd->pVB->Unlock();
-        d3d9_state_block->Release();
-        return;
-    }
-
-    // Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
-    // FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
-    //  1) to avoid repacking colors:   #define IMGUI_USE_BGRA_PACKED_COLOR
-    //  2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
-        for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
-        {
-            vtx_dst->pos[0] = vtx_src->pos.x;
-            vtx_dst->pos[1] = vtx_src->pos.y;
-            vtx_dst->pos[2] = 0.0f;
-            vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
-            vtx_dst->uv[0] = vtx_src->uv.x;
-            vtx_dst->uv[1] = vtx_src->uv.y;
-            vtx_dst++;
-            vtx_src++;
-        }
-        memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
-        idx_dst += cmd_list->IdxBuffer.Size;
-    }
-    bd->pVB->Unlock();
-    bd->pIB->Unlock();
-    bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
-    bd->pd3dDevice->SetIndices(bd->pIB);
-    bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
-
-    // Setup desired DX state
-    ImGui_ImplDX9_SetupRenderState(draw_data);
-
-    // Render command lists
-    // (Because we merged all buffers into a single one, we maintain our own offset into them)
-    int global_vtx_offset = 0;
-    int global_idx_offset = 0;
-    ImVec2 clip_off = draw_data->DisplayPos;
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback != nullptr)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplDX9_SetupRenderState(draw_data);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
-                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply Scissor/clipping rectangle, Bind texture, Draw
-                const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
-                const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
-                bd->pd3dDevice->SetTexture(0, texture);
-                bd->pd3dDevice->SetScissorRect(&r);
-                bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
-            }
-        }
-        global_idx_offset += cmd_list->IdxBuffer.Size;
-        global_vtx_offset += cmd_list->VtxBuffer.Size;
-    }
-
-    // Restore the DX9 transform
-    bd->pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
-    bd->pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
-    bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
-
-    // Restore the DX9 state
-    d3d9_state_block->Apply();
-    d3d9_state_block->Release();
-}
-
-bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_dx9";
-    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-
-    bd->pd3dDevice = device;
-    bd->pd3dDevice->AddRef();
-
-    return true;
-}
-
-void ImGui_ImplDX9_Shutdown()
-{
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplDX9_InvalidateDeviceObjects();
-    if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
-}
-
-static bool ImGui_ImplDX9_CreateFontsTexture()
-{
-    // Build texture atlas
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    unsigned char* pixels;
-    int width, height, bytes_per_pixel;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
-
-    // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
-#ifndef IMGUI_USE_BGRA_PACKED_COLOR
-    if (io.Fonts->TexPixelsUseColors)
-    {
-        ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel);
-        for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++)
-            *dst = IMGUI_COL_TO_DX9_ARGB(*src);
-        pixels = (unsigned char*)dst_start;
-    }
-#endif
-
-    // Upload texture to graphics system
-    bd->FontTexture = nullptr;
-    if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0)
-        return false;
-    D3DLOCKED_RECT tex_locked_rect;
-    if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK)
-        return false;
-    for (int y = 0; y < height; y++)
-        memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel);
-    bd->FontTexture->UnlockRect(0);
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)bd->FontTexture);
-
-#ifndef IMGUI_USE_BGRA_PACKED_COLOR
-    if (io.Fonts->TexPixelsUseColors)
-        ImGui::MemFree(pixels);
-#endif
-
-    return true;
-}
-
-bool ImGui_ImplDX9_CreateDeviceObjects()
-{
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    if (!bd || !bd->pd3dDevice)
-        return false;
-    if (!ImGui_ImplDX9_CreateFontsTexture())
-        return false;
-    return true;
-}
-
-void ImGui_ImplDX9_InvalidateDeviceObjects()
-{
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    if (!bd || !bd->pd3dDevice)
-        return;
-    if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
-    if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
-    if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
-}
-
-void ImGui_ImplDX9_NewFrame()
-{
-    ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX9_Init()?");
-
-    if (!bd->FontTexture)
-        ImGui_ImplDX9_CreateDeviceObjects();
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_dx9.h b/engines/twp/imgui/backends/imgui_impl_dx9.h
deleted file mode 100644
index 3965583bd86..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_dx9.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// dear imgui: Renderer Backend for DirectX9
-// This needs to be used along with a Platform Backend (e.g. Win32)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct IDirect3DDevice9;
-
-IMGUI_IMPL_API bool     ImGui_ImplDX9_Init(IDirect3DDevice9* device);
-IMGUI_IMPL_API void     ImGui_ImplDX9_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplDX9_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
-
-// Use if you want to reset your rendering device without losing Dear ImGui state.
-IMGUI_IMPL_API bool     ImGui_ImplDX9_CreateDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplDX9_InvalidateDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glfw.cpp b/engines/twp/imgui/backends/imgui_impl_glfw.cpp
deleted file mode 100644
index b49c99bb883..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_glfw.cpp
+++ /dev/null
@@ -1,856 +0,0 @@
-// dear imgui: Platform Backend for GLFW
-// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
-// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
-// (Requires: GLFW 3.1+. Prefer GLFW 3.3+ or GLFW 3.4+ for full feature support.)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only).
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-12-19: Emscripten: Added ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback() to register canvas selector and auto-resize GLFW window.
-//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys.
-//  2023-07-18: Inputs: Revert ignoring mouse data on GLFW_CURSOR_DISABLED as it can be used differently. User may set ImGuiConfigFLags_NoMouse if desired. (#5625, #6609)
-//  2023-06-12: Accept glfwGetTime() not returning a monotonically increasing value. This seems to happens on some Windows setup when peripherals disconnect, and is likely to also happen on browser + Emscripten. (#6491)
-//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen on Windows ONLY, using a custom WndProc hook. (#2702)
-//  2023-03-16: Inputs: Fixed key modifiers handling on secondary viewports (docking branch). Broken on 2023/01/04. (#6248, #6034)
-//  2023-03-14: Emscripten: Avoid using glfwGetError() and glfwGetGamepadState() which are not correctly implemented in Emscripten emulation. (#6240)
-//  2023-02-03: Emscripten: Registering custom low-level mouse wheel handler to get more accurate scrolling impulses on Emscripten. (#4019, #6096)
-//  2023-01-04: Inputs: Fixed mods state on Linux when using Alt-GR text input (e.g. German keyboard layout), could lead to broken text input. Revert a 2022/01/17 change were we resumed using mods provided by GLFW, turns out they were faulty.
-//  2022-11-22: Perform a dummy glfwGetError() read to cancel missing names with glfwGetKeyName(). (#5908)
-//  2022-10-18: Perform a dummy glfwGetError() read to cancel missing mouse cursors errors. Using GLFW_VERSION_COMBINED directly. (#5785)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-09-01: Inputs: Honor GLFW_CURSOR_DISABLED by not setting mouse position *EDIT* Reverted 2023-07-18.
-//  2022-04-30: Inputs: Fixed ImGui_ImplGlfw_TranslateUntranslatedKey() for lower case letters on OSX.
-//  2022-03-23: Inputs: Fixed a regression in 1.87 which resulted in keyboard modifiers events being reported incorrectly on Linux/X11.
-//  2022-02-07: Added ImGui_ImplGlfw_InstallCallbacks()/ImGui_ImplGlfw_RestoreCallbacks() helpers to facilitate user installing callbacks after initializing backend.
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
-//  2022-01-12: *BREAKING CHANGE*: Now using glfwSetCursorPosCallback(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetCursorPosCallback() and forward it to the backend via ImGui_ImplGlfw_CursorPosCallback().
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2022-01-05: Inputs: Converting GLFW untranslated keycodes back to translated keycodes (in the ImGui_ImplGlfw_KeyCallback() function) in order to match the behavior of every other backend, and facilitate the use of GLFW with lettered-shortcuts API.
-//  2021-08-17: *BREAKING CHANGE*: Now using glfwSetWindowFocusCallback() to calling io.AddFocusEvent(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() and forward it to the backend via ImGui_ImplGlfw_WindowFocusCallback().
-//  2021-07-29: *BREAKING CHANGE*: Now using glfwSetCursorEnterCallback(). MousePos is correctly reported when the host platform window is hovered but not focused. If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() callback and forward it to the backend via ImGui_ImplGlfw_CursorEnterCallback().
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors.
-//  2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor).
-//  2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown.
-//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
-//  2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter().
-//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
-//  2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them.
-//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
-//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
-//  2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples.
-//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
-//  2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()).
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
-//  2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set.
-//  2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
-//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
-//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
-//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
-//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_glfw.h"
-
-// Clang warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wold-style-cast"     // warning: use of old-style cast
-#pragma clang diagnostic ignored "-Wsign-conversion"    // warning: implicit conversion changes signedness
-#endif
-
-// GLFW
-#include <GLFW/glfw3.h>
-
-#ifdef _WIN32
-#undef APIENTRY
-#define GLFW_EXPOSE_NATIVE_WIN32
-#include <GLFW/glfw3native.h>   // for glfwGetWin32Window()
-#endif
-#ifdef __APPLE__
-#define GLFW_EXPOSE_NATIVE_COCOA
-#include <GLFW/glfw3native.h>   // for glfwGetCocoaWindow()
-#endif
-
-#ifdef __EMSCRIPTEN__
-#include <emscripten.h>
-#include <emscripten/html5.h>
-#endif
-
-// We gather version tests as define in order to easily see which features are version-dependent.
-#define GLFW_VERSION_COMBINED           (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 + GLFW_VERSION_REVISION)
-#ifdef GLFW_RESIZE_NESW_CURSOR          // Let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released?
-#define GLFW_HAS_NEW_CURSORS            (GLFW_VERSION_COMBINED >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR
-#else
-#define GLFW_HAS_NEW_CURSORS            (0)
-#endif
-#define GLFW_HAS_GAMEPAD_API            (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetGamepadState() new api
-#define GLFW_HAS_GETKEYNAME             (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName()
-#define GLFW_HAS_GETERROR               (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError()
-
-// GLFW data
-enum GlfwClientApi
-{
-    GlfwClientApi_Unknown,
-    GlfwClientApi_OpenGL,
-    GlfwClientApi_Vulkan
-};
-
-struct ImGui_ImplGlfw_Data
-{
-    GLFWwindow*             Window;
-    GlfwClientApi           ClientApi;
-    double                  Time;
-    GLFWwindow*             MouseWindow;
-    GLFWcursor*             MouseCursors[ImGuiMouseCursor_COUNT];
-    ImVec2                  LastValidMousePos;
-    bool                    InstalledCallbacks;
-    bool                    CallbacksChainForAllWindows;
-#ifdef __EMSCRIPTEN__
-    const char*             CanvasSelector;
-#endif
-
-    // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
-    GLFWwindowfocusfun      PrevUserCallbackWindowFocus;
-    GLFWcursorposfun        PrevUserCallbackCursorPos;
-    GLFWcursorenterfun      PrevUserCallbackCursorEnter;
-    GLFWmousebuttonfun      PrevUserCallbackMousebutton;
-    GLFWscrollfun           PrevUserCallbackScroll;
-    GLFWkeyfun              PrevUserCallbackKey;
-    GLFWcharfun             PrevUserCallbackChar;
-    GLFWmonitorfun          PrevUserCallbackMonitor;
-#ifdef _WIN32
-    WNDPROC                 GlfwWndProc;
-#endif
-
-    ImGui_ImplGlfw_Data()   { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
-// - Because glfwPollEvents() process all windows and some events may be called outside of it, you will need to register your own callbacks
-//   (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks.
-// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it.
-// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
-static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
-}
-
-// Functions
-static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
-{
-    return glfwGetClipboardString((GLFWwindow*)user_data);
-}
-
-static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
-{
-    glfwSetClipboardString((GLFWwindow*)user_data, text);
-}
-
-static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
-{
-    switch (key)
-    {
-        case GLFW_KEY_TAB: return ImGuiKey_Tab;
-        case GLFW_KEY_LEFT: return ImGuiKey_LeftArrow;
-        case GLFW_KEY_RIGHT: return ImGuiKey_RightArrow;
-        case GLFW_KEY_UP: return ImGuiKey_UpArrow;
-        case GLFW_KEY_DOWN: return ImGuiKey_DownArrow;
-        case GLFW_KEY_PAGE_UP: return ImGuiKey_PageUp;
-        case GLFW_KEY_PAGE_DOWN: return ImGuiKey_PageDown;
-        case GLFW_KEY_HOME: return ImGuiKey_Home;
-        case GLFW_KEY_END: return ImGuiKey_End;
-        case GLFW_KEY_INSERT: return ImGuiKey_Insert;
-        case GLFW_KEY_DELETE: return ImGuiKey_Delete;
-        case GLFW_KEY_BACKSPACE: return ImGuiKey_Backspace;
-        case GLFW_KEY_SPACE: return ImGuiKey_Space;
-        case GLFW_KEY_ENTER: return ImGuiKey_Enter;
-        case GLFW_KEY_ESCAPE: return ImGuiKey_Escape;
-        case GLFW_KEY_APOSTROPHE: return ImGuiKey_Apostrophe;
-        case GLFW_KEY_COMMA: return ImGuiKey_Comma;
-        case GLFW_KEY_MINUS: return ImGuiKey_Minus;
-        case GLFW_KEY_PERIOD: return ImGuiKey_Period;
-        case GLFW_KEY_SLASH: return ImGuiKey_Slash;
-        case GLFW_KEY_SEMICOLON: return ImGuiKey_Semicolon;
-        case GLFW_KEY_EQUAL: return ImGuiKey_Equal;
-        case GLFW_KEY_LEFT_BRACKET: return ImGuiKey_LeftBracket;
-        case GLFW_KEY_BACKSLASH: return ImGuiKey_Backslash;
-        case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey_RightBracket;
-        case GLFW_KEY_GRAVE_ACCENT: return ImGuiKey_GraveAccent;
-        case GLFW_KEY_CAPS_LOCK: return ImGuiKey_CapsLock;
-        case GLFW_KEY_SCROLL_LOCK: return ImGuiKey_ScrollLock;
-        case GLFW_KEY_NUM_LOCK: return ImGuiKey_NumLock;
-        case GLFW_KEY_PRINT_SCREEN: return ImGuiKey_PrintScreen;
-        case GLFW_KEY_PAUSE: return ImGuiKey_Pause;
-        case GLFW_KEY_KP_0: return ImGuiKey_Keypad0;
-        case GLFW_KEY_KP_1: return ImGuiKey_Keypad1;
-        case GLFW_KEY_KP_2: return ImGuiKey_Keypad2;
-        case GLFW_KEY_KP_3: return ImGuiKey_Keypad3;
-        case GLFW_KEY_KP_4: return ImGuiKey_Keypad4;
-        case GLFW_KEY_KP_5: return ImGuiKey_Keypad5;
-        case GLFW_KEY_KP_6: return ImGuiKey_Keypad6;
-        case GLFW_KEY_KP_7: return ImGuiKey_Keypad7;
-        case GLFW_KEY_KP_8: return ImGuiKey_Keypad8;
-        case GLFW_KEY_KP_9: return ImGuiKey_Keypad9;
-        case GLFW_KEY_KP_DECIMAL: return ImGuiKey_KeypadDecimal;
-        case GLFW_KEY_KP_DIVIDE: return ImGuiKey_KeypadDivide;
-        case GLFW_KEY_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
-        case GLFW_KEY_KP_SUBTRACT: return ImGuiKey_KeypadSubtract;
-        case GLFW_KEY_KP_ADD: return ImGuiKey_KeypadAdd;
-        case GLFW_KEY_KP_ENTER: return ImGuiKey_KeypadEnter;
-        case GLFW_KEY_KP_EQUAL: return ImGuiKey_KeypadEqual;
-        case GLFW_KEY_LEFT_SHIFT: return ImGuiKey_LeftShift;
-        case GLFW_KEY_LEFT_CONTROL: return ImGuiKey_LeftCtrl;
-        case GLFW_KEY_LEFT_ALT: return ImGuiKey_LeftAlt;
-        case GLFW_KEY_LEFT_SUPER: return ImGuiKey_LeftSuper;
-        case GLFW_KEY_RIGHT_SHIFT: return ImGuiKey_RightShift;
-        case GLFW_KEY_RIGHT_CONTROL: return ImGuiKey_RightCtrl;
-        case GLFW_KEY_RIGHT_ALT: return ImGuiKey_RightAlt;
-        case GLFW_KEY_RIGHT_SUPER: return ImGuiKey_RightSuper;
-        case GLFW_KEY_MENU: return ImGuiKey_Menu;
-        case GLFW_KEY_0: return ImGuiKey_0;
-        case GLFW_KEY_1: return ImGuiKey_1;
-        case GLFW_KEY_2: return ImGuiKey_2;
-        case GLFW_KEY_3: return ImGuiKey_3;
-        case GLFW_KEY_4: return ImGuiKey_4;
-        case GLFW_KEY_5: return ImGuiKey_5;
-        case GLFW_KEY_6: return ImGuiKey_6;
-        case GLFW_KEY_7: return ImGuiKey_7;
-        case GLFW_KEY_8: return ImGuiKey_8;
-        case GLFW_KEY_9: return ImGuiKey_9;
-        case GLFW_KEY_A: return ImGuiKey_A;
-        case GLFW_KEY_B: return ImGuiKey_B;
-        case GLFW_KEY_C: return ImGuiKey_C;
-        case GLFW_KEY_D: return ImGuiKey_D;
-        case GLFW_KEY_E: return ImGuiKey_E;
-        case GLFW_KEY_F: return ImGuiKey_F;
-        case GLFW_KEY_G: return ImGuiKey_G;
-        case GLFW_KEY_H: return ImGuiKey_H;
-        case GLFW_KEY_I: return ImGuiKey_I;
-        case GLFW_KEY_J: return ImGuiKey_J;
-        case GLFW_KEY_K: return ImGuiKey_K;
-        case GLFW_KEY_L: return ImGuiKey_L;
-        case GLFW_KEY_M: return ImGuiKey_M;
-        case GLFW_KEY_N: return ImGuiKey_N;
-        case GLFW_KEY_O: return ImGuiKey_O;
-        case GLFW_KEY_P: return ImGuiKey_P;
-        case GLFW_KEY_Q: return ImGuiKey_Q;
-        case GLFW_KEY_R: return ImGuiKey_R;
-        case GLFW_KEY_S: return ImGuiKey_S;
-        case GLFW_KEY_T: return ImGuiKey_T;
-        case GLFW_KEY_U: return ImGuiKey_U;
-        case GLFW_KEY_V: return ImGuiKey_V;
-        case GLFW_KEY_W: return ImGuiKey_W;
-        case GLFW_KEY_X: return ImGuiKey_X;
-        case GLFW_KEY_Y: return ImGuiKey_Y;
-        case GLFW_KEY_Z: return ImGuiKey_Z;
-        case GLFW_KEY_F1: return ImGuiKey_F1;
-        case GLFW_KEY_F2: return ImGuiKey_F2;
-        case GLFW_KEY_F3: return ImGuiKey_F3;
-        case GLFW_KEY_F4: return ImGuiKey_F4;
-        case GLFW_KEY_F5: return ImGuiKey_F5;
-        case GLFW_KEY_F6: return ImGuiKey_F6;
-        case GLFW_KEY_F7: return ImGuiKey_F7;
-        case GLFW_KEY_F8: return ImGuiKey_F8;
-        case GLFW_KEY_F9: return ImGuiKey_F9;
-        case GLFW_KEY_F10: return ImGuiKey_F10;
-        case GLFW_KEY_F11: return ImGuiKey_F11;
-        case GLFW_KEY_F12: return ImGuiKey_F12;
-        case GLFW_KEY_F13: return ImGuiKey_F13;
-        case GLFW_KEY_F14: return ImGuiKey_F14;
-        case GLFW_KEY_F15: return ImGuiKey_F15;
-        case GLFW_KEY_F16: return ImGuiKey_F16;
-        case GLFW_KEY_F17: return ImGuiKey_F17;
-        case GLFW_KEY_F18: return ImGuiKey_F18;
-        case GLFW_KEY_F19: return ImGuiKey_F19;
-        case GLFW_KEY_F20: return ImGuiKey_F20;
-        case GLFW_KEY_F21: return ImGuiKey_F21;
-        case GLFW_KEY_F22: return ImGuiKey_F22;
-        case GLFW_KEY_F23: return ImGuiKey_F23;
-        case GLFW_KEY_F24: return ImGuiKey_F24;
-        default: return ImGuiKey_None;
-    }
-}
-
-// X11 does not include current pressed/released modifier key in 'mods' flags submitted by GLFW
-// See https://github.com/ocornut/imgui/issues/6034 and https://github.com/glfw/glfw/issues/1630
-static void ImGui_ImplGlfw_UpdateKeyModifiers(GLFWwindow* window)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddKeyEvent(ImGuiMod_Ctrl,  (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS));
-    io.AddKeyEvent(ImGuiMod_Shift, (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT)   == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT)   == GLFW_PRESS));
-    io.AddKeyEvent(ImGuiMod_Alt,   (glfwGetKey(window, GLFW_KEY_LEFT_ALT)     == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_ALT)     == GLFW_PRESS));
-    io.AddKeyEvent(ImGuiMod_Super, (glfwGetKey(window, GLFW_KEY_LEFT_SUPER)   == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SUPER)   == GLFW_PRESS));
-}
-
-static bool ImGui_ImplGlfw_ShouldChainCallback(GLFWwindow* window)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    return bd->CallbacksChainForAllWindows ? true : (window == bd->Window);
-}
-
-void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackMousebutton != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackMousebutton(window, button, action, mods);
-
-    ImGui_ImplGlfw_UpdateKeyModifiers(window);
-
-    ImGuiIO& io = ImGui::GetIO();
-    if (button >= 0 && button < ImGuiMouseButton_COUNT)
-        io.AddMouseButtonEvent(button, action == GLFW_PRESS);
-}
-
-void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackScroll != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackScroll(window, xoffset, yoffset);
-
-#ifdef __EMSCRIPTEN__
-    // Ignore GLFW events: will be processed in ImGui_ImplEmscripten_WheelCallback().
-    return;
-#endif
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMouseWheelEvent((float)xoffset, (float)yoffset);
-}
-
-static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode)
-{
-#if GLFW_HAS_GETKEYNAME && !defined(__EMSCRIPTEN__)
-    // GLFW 3.1+ attempts to "untranslate" keys, which goes the opposite of what every other framework does, making using lettered shortcuts difficult.
-    // (It had reasons to do so: namely GLFW is/was more likely to be used for WASD-type game controls rather than lettered shortcuts, but IHMO the 3.1 change could have been done differently)
-    // See https://github.com/glfw/glfw/issues/1502 for details.
-    // Adding a workaround to undo this (so our keys are translated->untranslated->translated, likely a lossy process).
-    // This won't cover edge cases but this is at least going to cover common cases.
-    if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL)
-        return key;
-    GLFWerrorfun prev_error_callback = glfwSetErrorCallback(nullptr);
-    const char* key_name = glfwGetKeyName(key, scancode);
-    glfwSetErrorCallback(prev_error_callback);
-#if GLFW_HAS_GETERROR && !defined(__EMSCRIPTEN__) // Eat errors (see #5908)
-    (void)glfwGetError(nullptr);
-#endif
-    if (key_name && key_name[0] != 0 && key_name[1] == 0)
-    {
-        const char char_names[] = "`-=[]\\,;\'./";
-        const int char_keys[] = { GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_MINUS, GLFW_KEY_EQUAL, GLFW_KEY_LEFT_BRACKET, GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_BACKSLASH, GLFW_KEY_COMMA, GLFW_KEY_SEMICOLON, GLFW_KEY_APOSTROPHE, GLFW_KEY_PERIOD, GLFW_KEY_SLASH, 0 };
-        IM_ASSERT(IM_ARRAYSIZE(char_names) == IM_ARRAYSIZE(char_keys));
-        if (key_name[0] >= '0' && key_name[0] <= '9')               { key = GLFW_KEY_0 + (key_name[0] - '0'); }
-        else if (key_name[0] >= 'A' && key_name[0] <= 'Z')          { key = GLFW_KEY_A + (key_name[0] - 'A'); }
-        else if (key_name[0] >= 'a' && key_name[0] <= 'z')          { key = GLFW_KEY_A + (key_name[0] - 'a'); }
-        else if (const char* p = strchr(char_names, key_name[0]))   { key = char_keys[p - char_names]; }
-    }
-    // if (action == GLFW_PRESS) printf("key %d scancode %d name '%s'\n", key, scancode, key_name);
-#else
-    IM_UNUSED(scancode);
-#endif
-    return key;
-}
-
-void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackKey != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackKey(window, keycode, scancode, action, mods);
-
-    if (action != GLFW_PRESS && action != GLFW_RELEASE)
-        return;
-
-    ImGui_ImplGlfw_UpdateKeyModifiers(window);
-
-    keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode);
-
-    ImGuiIO& io = ImGui::GetIO();
-    ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode);
-    io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
-    io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code)
-}
-
-void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackWindowFocus != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackWindowFocus(window, focused);
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddFocusEvent(focused != 0);
-}
-
-void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackCursorPos != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackCursorPos(window, x, y);
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMousePosEvent((float)x, (float)y);
-    bd->LastValidMousePos = ImVec2((float)x, (float)y);
-}
-
-// Workaround: X11 seems to send spurious Leave/Enter events which would make us lose our position,
-// so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984)
-void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackCursorEnter != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackCursorEnter(window, entered);
-
-    ImGuiIO& io = ImGui::GetIO();
-    if (entered)
-    {
-        bd->MouseWindow = window;
-        io.AddMousePosEvent(bd->LastValidMousePos.x, bd->LastValidMousePos.y);
-    }
-    else if (!entered && bd->MouseWindow == window)
-    {
-        bd->LastValidMousePos = io.MousePos;
-        bd->MouseWindow = nullptr;
-        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
-    }
-}
-
-void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if (bd->PrevUserCallbackChar != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window))
-        bd->PrevUserCallbackChar(window, c);
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddInputCharacter(c);
-}
-
-void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int)
-{
-	// Unused in 'master' branch but 'docking' branch will use this, so we declare it ahead of it so if you have to install callbacks you can install this one too.
-}
-
-#ifdef __EMSCRIPTEN__
-static EM_BOOL ImGui_ImplEmscripten_WheelCallback(int, const EmscriptenWheelEvent* ev, void*)
-{
-    // Mimic Emscripten_HandleWheel() in SDL.
-    // Corresponding equivalent in GLFW JS emulation layer has incorrect quantizing preventing small values. See #6096
-    float multiplier = 0.0f;
-    if (ev->deltaMode == DOM_DELTA_PIXEL)       { multiplier = 1.0f / 100.0f; } // 100 pixels make up a step.
-    else if (ev->deltaMode == DOM_DELTA_LINE)   { multiplier = 1.0f / 3.0f; }   // 3 lines make up a step.
-    else if (ev->deltaMode == DOM_DELTA_PAGE)   { multiplier = 80.0f; }         // A page makes up 80 steps.
-    float wheel_x = ev->deltaX * -multiplier;
-    float wheel_y = ev->deltaY * -multiplier;
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMouseWheelEvent(wheel_x, wheel_y);
-    //IMGUI_DEBUG_LOG("[Emsc] mode %d dx: %.2f, dy: %.2f, dz: %.2f --> feed %.2f %.2f\n", (int)ev->deltaMode, ev->deltaX, ev->deltaY, ev->deltaZ, wheel_x, wheel_y);
-    return EM_TRUE;
-}
-#endif
-
-#ifdef _WIN32
-// GLFW doesn't allow to distinguish Mouse vs TouchScreen vs Pen.
-// Add support for Win32 (based on imgui_impl_win32), because we rely on _TouchScreen info to trickle inputs differently.
-static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo()
-{
-    LPARAM extra_info = ::GetMessageExtraInfo();
-    if ((extra_info & 0xFFFFFF80) == 0xFF515700)
-        return ImGuiMouseSource_Pen;
-    if ((extra_info & 0xFFFFFF80) == 0xFF515780)
-        return ImGuiMouseSource_TouchScreen;
-    return ImGuiMouseSource_Mouse;
-}
-static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    switch (msg)
-    {
-    case WM_MOUSEMOVE: case WM_NCMOUSEMOVE:
-    case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_LBUTTONUP:
-    case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_RBUTTONUP:
-    case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_MBUTTONUP:
-    case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: case WM_XBUTTONUP:
-        ImGui::GetIO().AddMouseSourceEvent(GetMouseSourceFromMessageExtraInfo());
-        break;
-    }
-    return ::CallWindowProcW(bd->GlfwWndProc, hWnd, msg, wParam, lParam);
-}
-#endif
-
-void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!");
-    IM_ASSERT(bd->Window == window);
-
-    bd->PrevUserCallbackWindowFocus = glfwSetWindowFocusCallback(window, ImGui_ImplGlfw_WindowFocusCallback);
-    bd->PrevUserCallbackCursorEnter = glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback);
-    bd->PrevUserCallbackCursorPos = glfwSetCursorPosCallback(window, ImGui_ImplGlfw_CursorPosCallback);
-    bd->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
-    bd->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
-    bd->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
-    bd->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
-    bd->PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
-    bd->InstalledCallbacks = true;
-}
-
-void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!");
-    IM_ASSERT(bd->Window == window);
-
-    glfwSetWindowFocusCallback(window, bd->PrevUserCallbackWindowFocus);
-    glfwSetCursorEnterCallback(window, bd->PrevUserCallbackCursorEnter);
-    glfwSetCursorPosCallback(window, bd->PrevUserCallbackCursorPos);
-    glfwSetMouseButtonCallback(window, bd->PrevUserCallbackMousebutton);
-    glfwSetScrollCallback(window, bd->PrevUserCallbackScroll);
-    glfwSetKeyCallback(window, bd->PrevUserCallbackKey);
-    glfwSetCharCallback(window, bd->PrevUserCallbackChar);
-    glfwSetMonitorCallback(bd->PrevUserCallbackMonitor);
-    bd->InstalledCallbacks = false;
-    bd->PrevUserCallbackWindowFocus = nullptr;
-    bd->PrevUserCallbackCursorEnter = nullptr;
-    bd->PrevUserCallbackCursorPos = nullptr;
-    bd->PrevUserCallbackMousebutton = nullptr;
-    bd->PrevUserCallbackScroll = nullptr;
-    bd->PrevUserCallbackKey = nullptr;
-    bd->PrevUserCallbackChar = nullptr;
-    bd->PrevUserCallbackMonitor = nullptr;
-}
-
-// Set to 'true' to enable chaining installed callbacks for all windows (including secondary viewports created by backends or by user.
-// This is 'false' by default meaning we only chain callbacks for the main viewport.
-// We cannot set this to 'true' by default because user callbacks code may be not testing the 'window' parameter of their callback.
-// If you set this to 'true' your user callback code will need to make sure you are testing the 'window' parameter.
-void ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows)
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    bd->CallbacksChainForAllWindows = chain_for_all_windows;
-}
-
-static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
-    //printf("GLFW_VERSION: %d.%d.%d (%d)", GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION, GLFW_VERSION_COMBINED);
-
-    // Setup backend capabilities flags
-    ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)();
-    io.BackendPlatformUserData = (void*)bd;
-    io.BackendPlatformName = "imgui_impl_glfw";
-    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;         // We can honor GetMouseCursor() values (optional)
-    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;          // We can honor io.WantSetMousePos requests (optional, rarely used)
-
-    bd->Window = window;
-    bd->Time = 0.0;
-
-    io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
-    io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
-    io.ClipboardUserData = bd->Window;
-
-    // Create mouse cursors
-    // (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
-    // GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
-    // Missing cursors will return nullptr and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
-    GLFWerrorfun prev_error_callback = glfwSetErrorCallback(nullptr);
-    bd->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
-#if GLFW_HAS_NEW_CURSORS
-    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
-#else
-    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
-    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
-#endif
-    glfwSetErrorCallback(prev_error_callback);
-#if GLFW_HAS_GETERROR && !defined(__EMSCRIPTEN__) // Eat errors (see #5908)
-    (void)glfwGetError(nullptr);
-#endif
-
-    // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
-    if (install_callbacks)
-        ImGui_ImplGlfw_InstallCallbacks(window);
-    // Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096)
-    // We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves.
-    // FIXME: May break chaining in case user registered their own Emscripten callback?
-#ifdef __EMSCRIPTEN__
-    emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, ImGui_ImplEmscripten_WheelCallback);
-#endif
-
-    // Set platform dependent data in viewport
-    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
-#ifdef _WIN32
-    main_viewport->PlatformHandleRaw = glfwGetWin32Window(bd->Window);
-#elif defined(__APPLE__)
-    main_viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(bd->Window);
-#else
-    IM_UNUSED(main_viewport);
-#endif
-
-    // Windows: register a WndProc hook so we can intercept some messages.
-#ifdef _WIN32
-    bd->GlfwWndProc = (WNDPROC)::GetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC);
-    IM_ASSERT(bd->GlfwWndProc != nullptr);
-    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc);
-#endif
-
-    bd->ClientApi = client_api;
-    return true;
-}
-
-bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks)
-{
-    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL);
-}
-
-bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks)
-{
-    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan);
-}
-
-bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks)
-{
-    return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
-}
-
-void ImGui_ImplGlfw_Shutdown()
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    if (bd->InstalledCallbacks)
-        ImGui_ImplGlfw_RestoreCallbacks(bd->Window);
-#ifdef __EMSCRIPTEN__
-    emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr);
-#endif
-
-    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
-        glfwDestroyCursor(bd->MouseCursors[cursor_n]);
-
-    // Windows: register a WndProc hook so we can intercept some messages.
-#ifdef _WIN32
-    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
-    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->GlfwWndProc);
-    bd->GlfwWndProc = nullptr;
-#endif
-
-    io.BackendPlatformName = nullptr;
-    io.BackendPlatformUserData = nullptr;
-    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
-    IM_DELETE(bd);
-}
-
-static void ImGui_ImplGlfw_UpdateMouseData()
-{
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-
-
-    // (those braces are here to reduce diff with multi-viewports support in 'docking' branch)
-    {
-        GLFWwindow* window = bd->Window;
-
-#ifdef __EMSCRIPTEN__
-        const bool is_window_focused = true;
-#else
-        const bool is_window_focused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
-#endif
-        if (is_window_focused)
-        {
-            // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
-            if (io.WantSetMousePos)
-                glfwSetCursorPos(window, (double)io.MousePos.x, (double)io.MousePos.y);
-
-            // (Optional) Fallback to provide mouse position when focused (ImGui_ImplGlfw_CursorPosCallback already provides this when hovered or captured)
-            if (bd->MouseWindow == nullptr)
-            {
-                double mouse_x, mouse_y;
-                glfwGetCursorPos(window, &mouse_x, &mouse_y);
-                bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y);
-                io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
-            }
-        }
-    }
-}
-
-static void ImGui_ImplGlfw_UpdateMouseCursor()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
-        return;
-
-    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-    // (those braces are here to reduce diff with multi-viewports support in 'docking' branch)
-    {
-        GLFWwindow* window = bd->Window;
-        if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
-        {
-            // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
-        }
-        else
-        {
-            // Show OS mouse cursor
-            // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
-            glfwSetCursor(window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]);
-            glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
-        }
-    }
-}
-
-// Update gamepad inputs
-static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v  > 1.0f ? 1.0f : v; }
-static void ImGui_ImplGlfw_UpdateGamepads()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
-        return;
-
-    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
-#if GLFW_HAS_GAMEPAD_API && !defined(__EMSCRIPTEN__)
-    GLFWgamepadstate gamepad;
-    if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad))
-        return;
-    #define MAP_BUTTON(KEY_NO, BUTTON_NO, _UNUSED)          do { io.AddKeyEvent(KEY_NO, gamepad.buttons[BUTTON_NO] != 0); } while (0)
-    #define MAP_ANALOG(KEY_NO, AXIS_NO, _UNUSED, V0, V1)    do { float v = gamepad.axes[AXIS_NO]; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
-#else
-    int axes_count = 0, buttons_count = 0;
-    const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
-    const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
-    if (axes_count == 0 || buttons_count == 0)
-        return;
-    #define MAP_BUTTON(KEY_NO, _UNUSED, BUTTON_NO)          do { io.AddKeyEvent(KEY_NO, (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS)); } while (0)
-    #define MAP_ANALOG(KEY_NO, _UNUSED, AXIS_NO, V0, V1)    do { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
-#endif
-    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-    MAP_BUTTON(ImGuiKey_GamepadStart,       GLFW_GAMEPAD_BUTTON_START,          7);
-    MAP_BUTTON(ImGuiKey_GamepadBack,        GLFW_GAMEPAD_BUTTON_BACK,           6);
-    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,    GLFW_GAMEPAD_BUTTON_X,              2);     // Xbox X, PS Square
-    MAP_BUTTON(ImGuiKey_GamepadFaceRight,   GLFW_GAMEPAD_BUTTON_B,              1);     // Xbox B, PS Circle
-    MAP_BUTTON(ImGuiKey_GamepadFaceUp,      GLFW_GAMEPAD_BUTTON_Y,              3);     // Xbox Y, PS Triangle
-    MAP_BUTTON(ImGuiKey_GamepadFaceDown,    GLFW_GAMEPAD_BUTTON_A,              0);     // Xbox A, PS Cross
-    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,    GLFW_GAMEPAD_BUTTON_DPAD_LEFT,      13);
-    MAP_BUTTON(ImGuiKey_GamepadDpadRight,   GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,     11);
-    MAP_BUTTON(ImGuiKey_GamepadDpadUp,      GLFW_GAMEPAD_BUTTON_DPAD_UP,        10);
-    MAP_BUTTON(ImGuiKey_GamepadDpadDown,    GLFW_GAMEPAD_BUTTON_DPAD_DOWN,      12);
-    MAP_BUTTON(ImGuiKey_GamepadL1,          GLFW_GAMEPAD_BUTTON_LEFT_BUMPER,    4);
-    MAP_BUTTON(ImGuiKey_GamepadR1,          GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER,   5);
-    MAP_ANALOG(ImGuiKey_GamepadL2,          GLFW_GAMEPAD_AXIS_LEFT_TRIGGER,     4,      -0.75f,  +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadR2,          GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER,    5,      -0.75f,  +1.0f);
-    MAP_BUTTON(ImGuiKey_GamepadL3,          GLFW_GAMEPAD_BUTTON_LEFT_THUMB,     8);
-    MAP_BUTTON(ImGuiKey_GamepadR3,          GLFW_GAMEPAD_BUTTON_RIGHT_THUMB,    9);
-    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,  GLFW_GAMEPAD_AXIS_LEFT_X,           0,      -0.25f,  -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickRight, GLFW_GAMEPAD_AXIS_LEFT_X,           0,      +0.25f,  +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickUp,    GLFW_GAMEPAD_AXIS_LEFT_Y,           1,      -0.25f,  -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickDown,  GLFW_GAMEPAD_AXIS_LEFT_Y,           1,      +0.25f,  +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,  GLFW_GAMEPAD_AXIS_RIGHT_X,          2,      -0.25f,  -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickRight, GLFW_GAMEPAD_AXIS_RIGHT_X,          2,      +0.25f,  +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickUp,    GLFW_GAMEPAD_AXIS_RIGHT_Y,          3,      -0.25f,  -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickDown,  GLFW_GAMEPAD_AXIS_RIGHT_Y,          3,      +0.25f,  +1.0f);
-    #undef MAP_BUTTON
-    #undef MAP_ANALOG
-}
-
-void ImGui_ImplGlfw_NewFrame()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?");
-
-    // Setup display size (every frame to accommodate for window resizing)
-    int w, h;
-    int display_w, display_h;
-    glfwGetWindowSize(bd->Window, &w, &h);
-    glfwGetFramebufferSize(bd->Window, &display_w, &display_h);
-    io.DisplaySize = ImVec2((float)w, (float)h);
-    if (w > 0 && h > 0)
-        io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h);
-
-    // Setup time step
-    // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644)
-    double current_time = glfwGetTime();
-    if (current_time <= bd->Time)
-        current_time = bd->Time + 0.00001f;
-    io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
-    bd->Time = current_time;
-
-    ImGui_ImplGlfw_UpdateMouseData();
-    ImGui_ImplGlfw_UpdateMouseCursor();
-
-    // Update game controllers (if enabled and available)
-    ImGui_ImplGlfw_UpdateGamepads();
-}
-
-#ifdef __EMSCRIPTEN__
-static EM_BOOL ImGui_ImplGlfw_OnCanvasSizeChange(int event_type, const EmscriptenUiEvent* event, void* user_data)
-{
-    ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data;
-    double canvas_width, canvas_height;
-    emscripten_get_element_css_size(bd->CanvasSelector, &canvas_width, &canvas_height);
-    glfwSetWindowSize(bd->Window, (int)canvas_width, (int)canvas_height);
-    return true;
-}
-
-static EM_BOOL ImGui_ImplEmscripten_FullscreenChangeCallback(int event_type, const EmscriptenFullscreenChangeEvent* event, void* user_data)
-{
-    ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data;
-    double canvas_width, canvas_height;
-    emscripten_get_element_css_size(bd->CanvasSelector, &canvas_width, &canvas_height);
-    glfwSetWindowSize(bd->Window, (int)canvas_width, (int)canvas_height);
-    return true;
-}
-
-// 'canvas_selector' is a CSS selector. The event listener is applied to the first element that matches the query.
-// STRING MUST PERSIST FOR THE APPLICATION DURATION. PLEASE USE A STRING LITERAL OR ENSURE POINTER WILL STAY VALID.
-void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector)
-{
-    IM_ASSERT(canvas_selector != nullptr);
-    ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?");
-
-    bd->CanvasSelector = canvas_selector;
-    emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, bd, false, ImGui_ImplGlfw_OnCanvasSizeChange);
-    emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, bd, false, ImGui_ImplEmscripten_FullscreenChangeCallback);
-
-    // Change the size of the GLFW window according to the size of the canvas
-    ImGui_ImplGlfw_OnCanvasSizeChange(EMSCRIPTEN_EVENT_RESIZE, {}, bd);
-}
-#endif
-
-//-----------------------------------------------------------------------------
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glfw.h b/engines/twp/imgui/backends/imgui_impl_glfw.h
deleted file mode 100644
index 6a9acd05b27..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_glfw.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// dear imgui: Platform Backend for GLFW
-// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
-// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only).
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct GLFWwindow;
-struct GLFWmonitor;
-
-IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
-IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
-IMGUI_IMPL_API bool     ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplGlfw_NewFrame();
-
-// Emscripten related initialization phase methods
-#ifdef __EMSCRIPTEN__
-IMGUI_IMPL_API void     ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector);
-#endif
-
-// GLFW callbacks install
-// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any.
-// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks.
-IMGUI_IMPL_API void     ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window);
-
-// GFLW callbacks options:
-// - Set 'chain_for_all_windows=true' to enable chaining callbacks for all windows (including secondary viewports created by backends or by user)
-IMGUI_IMPL_API void     ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows);
-
-// GLFW callbacks (individual callbacks to call yourself if you didn't install callbacks)
-IMGUI_IMPL_API void     ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused);        // Since 1.84
-IMGUI_IMPL_API void     ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered);        // Since 1.84
-IMGUI_IMPL_API void     ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y);   // Since 1.87
-IMGUI_IMPL_API void     ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
-IMGUI_IMPL_API void     ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glut.cpp b/engines/twp/imgui/backends/imgui_impl_glut.cpp
deleted file mode 100644
index 5dada3037a3..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_glut.cpp
+++ /dev/null
@@ -1,308 +0,0 @@
-// dear imgui: Platform Backend for GLUT/FreeGLUT
-// This needs to be used along with a Renderer (e.g. OpenGL2)
-
-// !!! GLUT/FreeGLUT IS OBSOLETE PREHISTORIC SOFTWARE. Using GLUT is not recommended unless you really miss the 90's. !!!
-// !!! If someone or something is teaching you GLUT today, you are being abused. Please show some resistance. !!!
-// !!! Nowadays, prefer using GLFW or SDL instead!
-
-// Implemented features:
-//  [X] Platform: Partial keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLUT values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-// Issues:
-//  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
-//  [ ] Platform: Missing horizontal mouse wheel support.
-//  [ ] Platform: Missing mouse cursor shape/visibility support.
-//  [ ] Platform: Missing clipboard support (not supported by Glut).
-//  [ ] Platform: Missing gamepad support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-04-17: BREAKING: Removed call to ImGui::NewFrame() from ImGui_ImplGLUT_NewFrame(). Needs to be called from the main application loop, like with every other backends.
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2019-04-03: Misc: Renamed imgui_impl_freeglut.cpp/.h to imgui_impl_glut.cpp/.h.
-//  2019-03-25: Misc: Made io.DeltaTime always above zero.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
-//  2018-03-22: Added GLUT Platform binding.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_glut.h"
-#define GL_SILENCE_DEPRECATION
-#ifdef __APPLE__
-#include <GLUT/glut.h>
-#else
-#include <GL/freeglut.h>
-#endif
-
-#ifdef _MSC_VER
-#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
-#endif
-
-static int g_Time = 0;          // Current time, in milliseconds
-
-// Glut has 1 function for characters and one for "special keys". We map the characters in the 0..255 range and the keys above.
-static ImGuiKey ImGui_ImplGLUT_KeyToImGuiKey(int key)
-{
-    switch (key)
-    {
-        case '\t':                      return ImGuiKey_Tab;
-        case 256 + GLUT_KEY_LEFT:       return ImGuiKey_LeftArrow;
-        case 256 + GLUT_KEY_RIGHT:      return ImGuiKey_RightArrow;
-        case 256 + GLUT_KEY_UP:         return ImGuiKey_UpArrow;
-        case 256 + GLUT_KEY_DOWN:       return ImGuiKey_DownArrow;
-        case 256 + GLUT_KEY_PAGE_UP:    return ImGuiKey_PageUp;
-        case 256 + GLUT_KEY_PAGE_DOWN:  return ImGuiKey_PageDown;
-        case 256 + GLUT_KEY_HOME:       return ImGuiKey_Home;
-        case 256 + GLUT_KEY_END:        return ImGuiKey_End;
-        case 256 + GLUT_KEY_INSERT:     return ImGuiKey_Insert;
-        case 127:                       return ImGuiKey_Delete;
-        case 8:                         return ImGuiKey_Backspace;
-        case ' ':                       return ImGuiKey_Space;
-        case 13:                        return ImGuiKey_Enter;
-        case 27:                        return ImGuiKey_Escape;
-        case 39:                        return ImGuiKey_Apostrophe;
-        case 44:                        return ImGuiKey_Comma;
-        case 45:                        return ImGuiKey_Minus;
-        case 46:                        return ImGuiKey_Period;
-        case 47:                        return ImGuiKey_Slash;
-        case 59:                        return ImGuiKey_Semicolon;
-        case 61:                        return ImGuiKey_Equal;
-        case 91:                        return ImGuiKey_LeftBracket;
-        case 92:                        return ImGuiKey_Backslash;
-        case 93:                        return ImGuiKey_RightBracket;
-        case 96:                        return ImGuiKey_GraveAccent;
-        //case 0:                         return ImGuiKey_CapsLock;
-        //case 0:                         return ImGuiKey_ScrollLock;
-        case 256 + 0x006D:              return ImGuiKey_NumLock;
-        //case 0:                         return ImGuiKey_PrintScreen;
-        //case 0:                         return ImGuiKey_Pause;
-        //case '0':                       return ImGuiKey_Keypad0;
-        //case '1':                       return ImGuiKey_Keypad1;
-        //case '2':                       return ImGuiKey_Keypad2;
-        //case '3':                       return ImGuiKey_Keypad3;
-        //case '4':                       return ImGuiKey_Keypad4;
-        //case '5':                       return ImGuiKey_Keypad5;
-        //case '6':                       return ImGuiKey_Keypad6;
-        //case '7':                       return ImGuiKey_Keypad7;
-        //case '8':                       return ImGuiKey_Keypad8;
-        //case '9':                       return ImGuiKey_Keypad9;
-        //case 46:                        return ImGuiKey_KeypadDecimal;
-        //case 47:                        return ImGuiKey_KeypadDivide;
-        case 42:                        return ImGuiKey_KeypadMultiply;
-        //case 45:                        return ImGuiKey_KeypadSubtract;
-        case 43:                        return ImGuiKey_KeypadAdd;
-        //case 13:                        return ImGuiKey_KeypadEnter;
-        //case 0:                         return ImGuiKey_KeypadEqual;
-        case 256 + 0x0072:              return ImGuiKey_LeftCtrl;
-        case 256 + 0x0070:              return ImGuiKey_LeftShift;
-        case 256 + 0x0074:              return ImGuiKey_LeftAlt;
-        //case 0:                         return ImGuiKey_LeftSuper;
-        case 256 + 0x0073:              return ImGuiKey_RightCtrl;
-        case 256 + 0x0071:              return ImGuiKey_RightShift;
-        case 256 + 0x0075:              return ImGuiKey_RightAlt;
-        //case 0:                         return ImGuiKey_RightSuper;
-        //case 0:                         return ImGuiKey_Menu;
-        case '0':                       return ImGuiKey_0;
-        case '1':                       return ImGuiKey_1;
-        case '2':                       return ImGuiKey_2;
-        case '3':                       return ImGuiKey_3;
-        case '4':                       return ImGuiKey_4;
-        case '5':                       return ImGuiKey_5;
-        case '6':                       return ImGuiKey_6;
-        case '7':                       return ImGuiKey_7;
-        case '8':                       return ImGuiKey_8;
-        case '9':                       return ImGuiKey_9;
-        case 'A': case 'a':             return ImGuiKey_A;
-        case 'B': case 'b':             return ImGuiKey_B;
-        case 'C': case 'c':             return ImGuiKey_C;
-        case 'D': case 'd':             return ImGuiKey_D;
-        case 'E': case 'e':             return ImGuiKey_E;
-        case 'F': case 'f':             return ImGuiKey_F;
-        case 'G': case 'g':             return ImGuiKey_G;
-        case 'H': case 'h':             return ImGuiKey_H;
-        case 'I': case 'i':             return ImGuiKey_I;
-        case 'J': case 'j':             return ImGuiKey_J;
-        case 'K': case 'k':             return ImGuiKey_K;
-        case 'L': case 'l':             return ImGuiKey_L;
-        case 'M': case 'm':             return ImGuiKey_M;
-        case 'N': case 'n':             return ImGuiKey_N;
-        case 'O': case 'o':             return ImGuiKey_O;
-        case 'P': case 'p':             return ImGuiKey_P;
-        case 'Q': case 'q':             return ImGuiKey_Q;
-        case 'R': case 'r':             return ImGuiKey_R;
-        case 'S': case 's':             return ImGuiKey_S;
-        case 'T': case 't':             return ImGuiKey_T;
-        case 'U': case 'u':             return ImGuiKey_U;
-        case 'V': case 'v':             return ImGuiKey_V;
-        case 'W': case 'w':             return ImGuiKey_W;
-        case 'X': case 'x':             return ImGuiKey_X;
-        case 'Y': case 'y':             return ImGuiKey_Y;
-        case 'Z': case 'z':             return ImGuiKey_Z;
-        case 256 + GLUT_KEY_F1:         return ImGuiKey_F1;
-        case 256 + GLUT_KEY_F2:         return ImGuiKey_F2;
-        case 256 + GLUT_KEY_F3:         return ImGuiKey_F3;
-        case 256 + GLUT_KEY_F4:         return ImGuiKey_F4;
-        case 256 + GLUT_KEY_F5:         return ImGuiKey_F5;
-        case 256 + GLUT_KEY_F6:         return ImGuiKey_F6;
-        case 256 + GLUT_KEY_F7:         return ImGuiKey_F7;
-        case 256 + GLUT_KEY_F8:         return ImGuiKey_F8;
-        case 256 + GLUT_KEY_F9:         return ImGuiKey_F9;
-        case 256 + GLUT_KEY_F10:        return ImGuiKey_F10;
-        case 256 + GLUT_KEY_F11:        return ImGuiKey_F11;
-        case 256 + GLUT_KEY_F12:        return ImGuiKey_F12;
-        default:                        return ImGuiKey_None;
-    }
-}
-
-bool ImGui_ImplGLUT_Init()
-{
-    ImGuiIO& io = ImGui::GetIO();
-
-#ifdef FREEGLUT
-    io.BackendPlatformName = "imgui_impl_glut (freeglut)";
-#else
-    io.BackendPlatformName = "imgui_impl_glut";
-#endif
-    g_Time = 0;
-
-    return true;
-}
-
-void ImGui_ImplGLUT_InstallFuncs()
-{
-    glutReshapeFunc(ImGui_ImplGLUT_ReshapeFunc);
-    glutMotionFunc(ImGui_ImplGLUT_MotionFunc);
-    glutPassiveMotionFunc(ImGui_ImplGLUT_MotionFunc);
-    glutMouseFunc(ImGui_ImplGLUT_MouseFunc);
-#ifdef __FREEGLUT_EXT_H__
-    glutMouseWheelFunc(ImGui_ImplGLUT_MouseWheelFunc);
-#endif
-    glutKeyboardFunc(ImGui_ImplGLUT_KeyboardFunc);
-    glutKeyboardUpFunc(ImGui_ImplGLUT_KeyboardUpFunc);
-    glutSpecialFunc(ImGui_ImplGLUT_SpecialFunc);
-    glutSpecialUpFunc(ImGui_ImplGLUT_SpecialUpFunc);
-}
-
-void ImGui_ImplGLUT_Shutdown()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendPlatformName = nullptr;
-}
-
-void ImGui_ImplGLUT_NewFrame()
-{
-    // Setup time step
-    ImGuiIO& io = ImGui::GetIO();
-    int current_time = glutGet(GLUT_ELAPSED_TIME);
-    int delta_time_ms = (current_time - g_Time);
-    if (delta_time_ms <= 0)
-        delta_time_ms = 1;
-    io.DeltaTime = delta_time_ms / 1000.0f;
-    g_Time = current_time;
-}
-
-static void ImGui_ImplGLUT_UpdateKeyModifiers()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    int glut_key_mods = glutGetModifiers();
-    io.AddKeyEvent(ImGuiMod_Ctrl, (glut_key_mods & GLUT_ACTIVE_CTRL) != 0);
-    io.AddKeyEvent(ImGuiMod_Shift, (glut_key_mods & GLUT_ACTIVE_SHIFT) != 0);
-    io.AddKeyEvent(ImGuiMod_Alt, (glut_key_mods & GLUT_ACTIVE_ALT) != 0);
-}
-
-static void ImGui_ImplGLUT_AddKeyEvent(ImGuiKey key, bool down, int native_keycode)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddKeyEvent(key, down);
-    io.SetKeyEventNativeData(key, native_keycode, -1); // To support legacy indexing (<1.87 user code)
-}
-
-void ImGui_ImplGLUT_KeyboardFunc(unsigned char c, int x, int y)
-{
-    // Send character to imgui
-    //printf("char_down_func %d '%c'\n", c, c);
-    ImGuiIO& io = ImGui::GetIO();
-    if (c >= 32)
-        io.AddInputCharacter((unsigned int)c);
-
-    ImGuiKey key = ImGui_ImplGLUT_KeyToImGuiKey(c);
-    ImGui_ImplGLUT_AddKeyEvent(key, true, c);
-    ImGui_ImplGLUT_UpdateKeyModifiers();
-    (void)x; (void)y; // Unused
-}
-
-void ImGui_ImplGLUT_KeyboardUpFunc(unsigned char c, int x, int y)
-{
-    //printf("char_up_func %d '%c'\n", c, c);
-    ImGuiKey key = ImGui_ImplGLUT_KeyToImGuiKey(c);
-    ImGui_ImplGLUT_AddKeyEvent(key, false, c);
-    ImGui_ImplGLUT_UpdateKeyModifiers();
-    (void)x; (void)y; // Unused
-}
-
-void ImGui_ImplGLUT_SpecialFunc(int key, int x, int y)
-{
-    //printf("key_down_func %d\n", key);
-    ImGuiKey imgui_key = ImGui_ImplGLUT_KeyToImGuiKey(key + 256);
-    ImGui_ImplGLUT_AddKeyEvent(imgui_key, true, key + 256);
-    ImGui_ImplGLUT_UpdateKeyModifiers();
-    (void)x; (void)y; // Unused
-}
-
-void ImGui_ImplGLUT_SpecialUpFunc(int key, int x, int y)
-{
-    //printf("key_up_func %d\n", key);
-    ImGuiKey imgui_key = ImGui_ImplGLUT_KeyToImGuiKey(key + 256);
-    ImGui_ImplGLUT_AddKeyEvent(imgui_key, false, key + 256);
-    ImGui_ImplGLUT_UpdateKeyModifiers();
-    (void)x; (void)y; // Unused
-}
-
-void ImGui_ImplGLUT_MouseFunc(int glut_button, int state, int x, int y)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMousePosEvent((float)x, (float)y);
-    int button = -1;
-    if (glut_button == GLUT_LEFT_BUTTON) button = 0;
-    if (glut_button == GLUT_RIGHT_BUTTON) button = 1;
-    if (glut_button == GLUT_MIDDLE_BUTTON) button = 2;
-    if (button != -1 && (state == GLUT_DOWN || state == GLUT_UP))
-        io.AddMouseButtonEvent(button, state == GLUT_DOWN);
-}
-
-#ifdef __FREEGLUT_EXT_H__
-void ImGui_ImplGLUT_MouseWheelFunc(int button, int dir, int x, int y)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMousePosEvent((float)x, (float)y);
-    if (dir != 0)
-        io.AddMouseWheelEvent(0.0f, dir > 0 ? 1.0f : -1.0f);
-    (void)button; // Unused
-}
-#endif
-
-void ImGui_ImplGLUT_ReshapeFunc(int w, int h)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.DisplaySize = ImVec2((float)w, (float)h);
-}
-
-void ImGui_ImplGLUT_MotionFunc(int x, int y)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddMousePosEvent((float)x, (float)y);
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_glut.h b/engines/twp/imgui/backends/imgui_impl_glut.h
deleted file mode 100644
index 062110edd62..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_glut.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// dear imgui: Platform Backend for GLUT/FreeGLUT
-// This needs to be used along with a Renderer (e.g. OpenGL2)
-
-// !!! GLUT/FreeGLUT IS OBSOLETE PREHISTORIC SOFTWARE. Using GLUT is not recommended unless you really miss the 90's. !!!
-// !!! If someone or something is teaching you GLUT today, you are being abused. Please show some resistance. !!!
-// !!! Nowadays, prefer using GLFW or SDL instead!
-
-// Implemented features:
-//  [X] Platform: Partial keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLUT values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-// Issues:
-//  [ ] Platform: GLUT is unable to distinguish e.g. Backspace from CTRL+H or TAB from CTRL+I
-//  [ ] Platform: Missing horizontal mouse wheel support.
-//  [ ] Platform: Missing mouse cursor shape/visibility support.
-//  [ ] Platform: Missing clipboard support (not supported by Glut).
-//  [ ] Platform: Missing gamepad support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#ifndef IMGUI_DISABLE
-#include "imgui.h"      // IMGUI_IMPL_API
-
-IMGUI_IMPL_API bool     ImGui_ImplGLUT_Init();
-IMGUI_IMPL_API void     ImGui_ImplGLUT_InstallFuncs();
-IMGUI_IMPL_API void     ImGui_ImplGLUT_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplGLUT_NewFrame();
-
-// You can call ImGui_ImplGLUT_InstallFuncs() to get all those functions installed automatically,
-// or call them yourself from your own GLUT handlers. We are using the same weird names as GLUT for consistency..
-//------------------------------------ GLUT name ---------------------------------------------- Decent Name ---------
-IMGUI_IMPL_API void     ImGui_ImplGLUT_ReshapeFunc(int w, int h);                           // ~ ResizeFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_MotionFunc(int x, int y);                            // ~ MouseMoveFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_MouseFunc(int button, int state, int x, int y);      // ~ MouseButtonFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_MouseWheelFunc(int button, int dir, int x, int y);   // ~ MouseWheelFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_KeyboardFunc(unsigned char c, int x, int y);         // ~ CharPressedFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_KeyboardUpFunc(unsigned char c, int x, int y);       // ~ CharReleasedFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_SpecialFunc(int key, int x, int y);                  // ~ KeyPressedFunc
-IMGUI_IMPL_API void     ImGui_ImplGLUT_SpecialUpFunc(int key, int x, int y);                // ~ KeyReleasedFunc
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_metal.h b/engines/twp/imgui/backends/imgui_impl_metal.h
deleted file mode 100644
index 53706d1f716..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_metal.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// dear imgui: Renderer Backend for Metal
-// This needs to be used along with a Platform Backend (e.g. OSX)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-//-----------------------------------------------------------------------------
-// ObjC API
-//-----------------------------------------------------------------------------
-
-#ifdef __OBJC__
-
- at class MTLRenderPassDescriptor;
- at protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder;
-
-IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device);
-IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
-IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor);
-IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
-                                                   id<MTLCommandBuffer> commandBuffer,
-                                                   id<MTLRenderCommandEncoder> commandEncoder);
-
-// Called by Init/NewFrame/Shutdown
-IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device);
-IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
-IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device);
-IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
-
-#endif
-
-//-----------------------------------------------------------------------------
-// C++ API
-//-----------------------------------------------------------------------------
-
-// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file
-// More info about using Metal from C++: https://developer.apple.com/metal/cpp/
-
-#ifdef IMGUI_IMPL_METAL_CPP
-#include <Metal/Metal.hpp>
-#ifndef __OBJC__
-
-IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device);
-IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
-IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor);
-IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
-                                                   MTL::CommandBuffer* commandBuffer,
-                                                   MTL::RenderCommandEncoder* commandEncoder);
-
-// Called by Init/NewFrame/Shutdown
-IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device);
-IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
-IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device);
-IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
-
-#endif
-#endif
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_metal.mm b/engines/twp/imgui/backends/imgui_impl_metal.mm
deleted file mode 100644
index fd5686be6cd..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_metal.mm
+++ /dev/null
@@ -1,590 +0,0 @@
-// dear imgui: Renderer Backend for Metal
-// This needs to be used along with a Platform Backend (e.g. OSX)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID!
-//  [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'.
-//  2022-07-05: Metal: Add dispatch synchronization.
-//  2022-06-30: Metal: Use __bridge for ARC based systems.
-//  2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler.
-//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
-//  2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code).
-//  2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file.
-//  2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
-//  2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-02-18: Metal: Change blending equation to preserve alpha in output buffer.
-//  2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst.
-//  2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-07-05: Metal: Added new Metal backend implementation.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_metal.h"
-#import <time.h>
-#import <Metal/Metal.h>
-
-#pragma mark - Support classes
-
-// A wrapper around a MTLBuffer object that knows the last time it was reused
- at interface MetalBuffer : NSObject
- at property (nonatomic, strong) id<MTLBuffer> buffer;
- at property (nonatomic, assign) double        lastReuseTime;
-- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer;
- at end
-
-// An object that encapsulates the data necessary to uniquely identify a
-// render pipeline state. These are used as cache keys.
- at interface FramebufferDescriptor : NSObject<NSCopying>
- at property (nonatomic, assign) unsigned long  sampleCount;
- at property (nonatomic, assign) MTLPixelFormat colorPixelFormat;
- at property (nonatomic, assign) MTLPixelFormat depthPixelFormat;
- at property (nonatomic, assign) MTLPixelFormat stencilPixelFormat;
-- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor;
- at end
-
-// A singleton that stores long-lived objects that are needed by the Metal
-// renderer backend. Stores the render pipeline state cache and the default
-// font texture, and manages the reusable buffer cache.
- at interface MetalContext : NSObject
- at property (nonatomic, strong) id<MTLDevice>                 device;
- at property (nonatomic, strong) id<MTLDepthStencilState>      depthStencilState;
- at property (nonatomic, strong) FramebufferDescriptor*        framebufferDescriptor; // framebuffer descriptor for current frame; transient
- at property (nonatomic, strong) NSMutableDictionary*          renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
- at property (nonatomic, strong, nullable) id<MTLTexture>      fontTexture;
- at property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
- at property (nonatomic, assign) double                        lastBufferCachePurge;
-- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
-- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device;
- at end
-
-struct ImGui_ImplMetal_Data
-{
-    MetalContext*               SharedMetalContext;
-
-    ImGui_ImplMetal_Data()      { memset(this, 0, sizeof(*this)); }
-};
-
-static ImGui_ImplMetal_Data*    ImGui_ImplMetal_CreateBackendData() { return IM_NEW(ImGui_ImplMetal_Data)(); }
-static ImGui_ImplMetal_Data*    ImGui_ImplMetal_GetBackendData()    { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; }
-static void                     ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); }
-
-static inline CFTimeInterval    GetMachAbsoluteTimeInSeconds()      { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
-
-#ifdef IMGUI_IMPL_METAL_CPP
-
-#pragma mark - Dear ImGui Metal C++ Backend API
-
-bool ImGui_ImplMetal_Init(MTL::Device* device)
-{
-    return ImGui_ImplMetal_Init((__bridge id<MTLDevice>)(device));
-}
-
-void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor)
-{
-    ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor));
-}
-
-void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
-                                    MTL::CommandBuffer* commandBuffer,
-                                    MTL::RenderCommandEncoder* commandEncoder)
-{
-    ImGui_ImplMetal_RenderDrawData(draw_data,
-                                   (__bridge id<MTLCommandBuffer>)(commandBuffer),
-                                   (__bridge id<MTLRenderCommandEncoder>)(commandEncoder));
-
-}
-
-bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device)
-{
-    return ImGui_ImplMetal_CreateFontsTexture((__bridge id<MTLDevice>)(device));
-}
-
-bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device)
-{
-    return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device));
-}
-
-#endif // #ifdef IMGUI_IMPL_METAL_CPP
-
-#pragma mark - Dear ImGui Metal Backend API
-
-bool ImGui_ImplMetal_Init(id<MTLDevice> device)
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_CreateBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_metal";
-    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-
-    bd->SharedMetalContext = [[MetalContext alloc] init];
-    bd->SharedMetalContext.device = device;
-
-    return true;
-}
-
-void ImGui_ImplMetal_Shutdown()
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGui_ImplMetal_DestroyDeviceObjects();
-    ImGui_ImplMetal_DestroyBackendData();
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-}
-
-void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    IM_ASSERT(bd->SharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?");
-    bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
-
-    if (bd->SharedMetalContext.depthStencilState == nil)
-        ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device);
-}
-
-static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer,
-    id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState,
-    MetalBuffer* vertexBuffer, size_t vertexBufferOffset)
-{
-    IM_UNUSED(commandBuffer);
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    [commandEncoder setCullMode:MTLCullModeNone];
-    [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState];
-
-    // Setup viewport, orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to
-    // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
-    MTLViewport viewport =
-    {
-        .originX = 0.0,
-        .originY = 0.0,
-        .width = (double)(drawData->DisplaySize.x * drawData->FramebufferScale.x),
-        .height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y),
-        .znear = 0.0,
-        .zfar = 1.0
-    };
-    [commandEncoder setViewport:viewport];
-
-    float L = drawData->DisplayPos.x;
-    float R = drawData->DisplayPos.x + drawData->DisplaySize.x;
-    float T = drawData->DisplayPos.y;
-    float B = drawData->DisplayPos.y + drawData->DisplaySize.y;
-    float N = (float)viewport.znear;
-    float F = (float)viewport.zfar;
-    const float ortho_projection[4][4] =
-    {
-        { 2.0f/(R-L),   0.0f,           0.0f,   0.0f },
-        { 0.0f,         2.0f/(T-B),     0.0f,   0.0f },
-        { 0.0f,         0.0f,        1/(F-N),   0.0f },
-        { (R+L)/(L-R),  (T+B)/(B-T), N/(F-N),   1.0f },
-    };
-    [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
-
-    [commandEncoder setRenderPipelineState:renderPipelineState];
-
-    [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0];
-    [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0];
-}
-
-// Metal Render function.
-void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    MetalContext* ctx = bd->SharedMetalContext;
-
-    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
-    int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x);
-    int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y);
-    if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0)
-        return;
-
-    // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
-    // The hit rate for this cache should be very near 100%.
-    id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor];
-    if (renderPipelineState == nil)
-    {
-        // No luck; make a new render pipeline state
-        renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device];
-
-        // Cache render pipeline state for later reuse
-        ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState;
-    }
-
-    size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert);
-    size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx);
-    MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
-    MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
-
-    ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
-
-    // Will project scissor/clipping rectangles into framebuffer space
-    ImVec2 clip_off = drawData->DisplayPos;         // (0,0) unless using multi-viewports
-    ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
-
-    // Render command lists
-    size_t vertexBufferOffset = 0;
-    size_t indexBufferOffset = 0;
-    for (int n = 0; n < drawData->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = drawData->CmdLists[n];
-
-        memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, cmd_list->VtxBuffer.Data, (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
-        memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, cmd_list->IdxBuffer.Data, (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
-
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
-                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
-
-                // Clamp to viewport as setScissorRect() won't accept values that are off bounds
-                if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
-                if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
-                if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
-                if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-                if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this
-                    continue;
-
-                // Apply scissor/clipping rectangle
-                MTLScissorRect scissorRect =
-                {
-                    .x = NSUInteger(clip_min.x),
-                    .y = NSUInteger(clip_min.y),
-                    .width = NSUInteger(clip_max.x - clip_min.x),
-                    .height = NSUInteger(clip_max.y - clip_min.y)
-                };
-                [commandEncoder setScissorRect:scissorRect];
-
-                // Bind texture, Draw
-                if (ImTextureID tex_id = pcmd->GetTexID())
-                    [commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(tex_id) atIndex:0];
-
-                [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];
-                [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
-                                           indexCount:pcmd->ElemCount
-                                            indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
-                                          indexBuffer:indexBuffer.buffer
-                                    indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)];
-            }
-        }
-
-        vertexBufferOffset += (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
-        indexBufferOffset += (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx);
-    }
-
-    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>)
-    {
-        dispatch_async(dispatch_get_main_queue(), ^{
-            ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-            if (bd != nullptr)
-            {
-                @synchronized(bd->SharedMetalContext.bufferCache)
-                {
-                    [bd->SharedMetalContext.bufferCache addObject:vertexBuffer];
-                    [bd->SharedMetalContext.bufferCache addObject:indexBuffer];
-                }
-            }
-        });
-    }];
-}
-
-bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device)
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-
-    // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
-    // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
-    // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
-    // You can make that change in your implementation.
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
-    MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
-                                                                                                 width:(NSUInteger)width
-                                                                                                height:(NSUInteger)height
-                                                                                             mipmapped:NO];
-    textureDescriptor.usage = MTLTextureUsageShaderRead;
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
-    textureDescriptor.storageMode = MTLStorageModeManaged;
-#else
-    textureDescriptor.storageMode = MTLStorageModeShared;
-#endif
-    id <MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor];
-    [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4];
-    bd->SharedMetalContext.fontTexture = texture;
-    io.Fonts->SetTexID((__bridge void*)bd->SharedMetalContext.fontTexture); // ImTextureID == void*
-
-    return (bd->SharedMetalContext.fontTexture != nil);
-}
-
-void ImGui_ImplMetal_DestroyFontsTexture()
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    bd->SharedMetalContext.fontTexture = nil;
-    io.Fonts->SetTexID(0);
-}
-
-bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
-    depthStencilDescriptor.depthWriteEnabled = NO;
-    depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
-    bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
-    ImGui_ImplMetal_CreateFontsTexture(device);
-
-    return true;
-}
-
-void ImGui_ImplMetal_DestroyDeviceObjects()
-{
-    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
-    ImGui_ImplMetal_DestroyFontsTexture();
-    [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
-}
-
-#pragma mark - MetalBuffer implementation
-
- at implementation MetalBuffer
-- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer
-{
-    if ((self = [super init]))
-    {
-        _buffer = buffer;
-        _lastReuseTime = GetMachAbsoluteTimeInSeconds();
-    }
-    return self;
-}
- at end
-
-#pragma mark - FramebufferDescriptor implementation
-
- at implementation FramebufferDescriptor
-- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor
-{
-    if ((self = [super init]))
-    {
-        _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount;
-        _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat;
-        _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat;
-        _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat;
-    }
-    return self;
-}
-
-- (nonnull id)copyWithZone:(nullable NSZone*)zone
-{
-    FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init];
-    copy.sampleCount = self.sampleCount;
-    copy.colorPixelFormat = self.colorPixelFormat;
-    copy.depthPixelFormat = self.depthPixelFormat;
-    copy.stencilPixelFormat = self.stencilPixelFormat;
-    return copy;
-}
-
-- (NSUInteger)hash
-{
-    NSUInteger sc = _sampleCount & 0x3;
-    NSUInteger cf = _colorPixelFormat & 0x3FF;
-    NSUInteger df = _depthPixelFormat & 0x3FF;
-    NSUInteger sf = _stencilPixelFormat & 0x3FF;
-    NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc;
-    return hash;
-}
-
-- (BOOL)isEqual:(id)object
-{
-    FramebufferDescriptor* other = object;
-    if (![other isKindOfClass:[FramebufferDescriptor class]])
-        return NO;
-    return other.sampleCount == self.sampleCount      &&
-    other.colorPixelFormat   == self.colorPixelFormat &&
-    other.depthPixelFormat   == self.depthPixelFormat &&
-    other.stencilPixelFormat == self.stencilPixelFormat;
-}
-
- at end
-
-#pragma mark - MetalContext implementation
-
- at implementation MetalContext
-- (instancetype)init
-{
-    if ((self = [super init]))
-    {
-        self.renderPipelineStateCache = [NSMutableDictionary dictionary];
-        self.bufferCache = [NSMutableArray array];
-        _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds();
-    }
-    return self;
-}
-
-- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device
-{
-    uint64_t now = GetMachAbsoluteTimeInSeconds();
-
-    @synchronized(self.bufferCache)
-    {
-        // Purge old buffers that haven't been useful for a while
-        if (now - self.lastBufferCachePurge > 1.0)
-        {
-            NSMutableArray* survivors = [NSMutableArray array];
-            for (MetalBuffer* candidate in self.bufferCache)
-                if (candidate.lastReuseTime > self.lastBufferCachePurge)
-                    [survivors addObject:candidate];
-            self.bufferCache = [survivors mutableCopy];
-            self.lastBufferCachePurge = now;
-        }
-
-        // See if we have a buffer we can reuse
-        MetalBuffer* bestCandidate = nil;
-        for (MetalBuffer* candidate in self.bufferCache)
-            if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime))
-                bestCandidate = candidate;
-
-        if (bestCandidate != nil)
-        {
-            [self.bufferCache removeObject:bestCandidate];
-            bestCandidate.lastReuseTime = now;
-            return bestCandidate;
-        }
-    }
-
-    // No luck; make a new buffer
-    id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared];
-    return [[MetalBuffer alloc] initWithBuffer:backing];
-}
-
-// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
-- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device
-{
-    NSError* error = nil;
-
-    NSString* shaderSource = @""
-    "#include <metal_stdlib>\n"
-    "using namespace metal;\n"
-    "\n"
-    "struct Uniforms {\n"
-    "    float4x4 projectionMatrix;\n"
-    "};\n"
-    "\n"
-    "struct VertexIn {\n"
-    "    float2 position  [[attribute(0)]];\n"
-    "    float2 texCoords [[attribute(1)]];\n"
-    "    uchar4 color     [[attribute(2)]];\n"
-    "};\n"
-    "\n"
-    "struct VertexOut {\n"
-    "    float4 position [[position]];\n"
-    "    float2 texCoords;\n"
-    "    float4 color;\n"
-    "};\n"
-    "\n"
-    "vertex VertexOut vertex_main(VertexIn in                 [[stage_in]],\n"
-    "                             constant Uniforms &uniforms [[buffer(1)]]) {\n"
-    "    VertexOut out;\n"
-    "    out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
-    "    out.texCoords = in.texCoords;\n"
-    "    out.color = float4(in.color) / float4(255.0);\n"
-    "    return out;\n"
-    "}\n"
-    "\n"
-    "fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
-    "                             texture2d<half, access::sample> texture [[texture(0)]]) {\n"
-    "    constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
-    "    half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
-    "    return half4(in.color) * texColor;\n"
-    "}\n";
-
-    id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
-    if (library == nil)
-    {
-        NSLog(@"Error: failed to create Metal library: %@", error);
-        return nil;
-    }
-
-    id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
-    id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
-
-    if (vertexFunction == nil || fragmentFunction == nil)
-    {
-        NSLog(@"Error: failed to find Metal shader functions in library: %@", error);
-        return nil;
-    }
-
-    MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
-    vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos);
-    vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
-    vertexDescriptor.attributes[0].bufferIndex = 0;
-    vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv);
-    vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
-    vertexDescriptor.attributes[1].bufferIndex = 0;
-    vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col);
-    vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
-    vertexDescriptor.attributes[2].bufferIndex = 0;
-    vertexDescriptor.layouts[0].stepRate = 1;
-    vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
-    vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
-
-    MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
-    pipelineDescriptor.vertexFunction = vertexFunction;
-    pipelineDescriptor.fragmentFunction = fragmentFunction;
-    pipelineDescriptor.vertexDescriptor = vertexDescriptor;
-    pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount;
-    pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat;
-    pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
-    pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
-    pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
-    pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
-    pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
-    pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
-    pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
-    pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat;
-    pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat;
-
-    id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
-    if (error != nil)
-        NSLog(@"Error: failed to create Metal pipeline state: %@", error);
-
-    return renderPipelineState;
-}
-
- at end
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl2.cpp b/engines/twp/imgui/backends/imgui_impl_opengl2.cpp
deleted file mode 100644
index 0d703769084..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_opengl2.cpp
+++ /dev/null
@@ -1,302 +0,0 @@
-// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline)
-// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)**
-// **Prefer using the code in imgui_impl_opengl3.cpp**
-// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read.
-// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more
-// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might
-// confuse your GPU driver.
-// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API.
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2021-12-08: OpenGL: Fixed mishandling of the the ImDrawCmd::IdxOffset field! This is an old bug but it never had an effect until some internal rendering changes in 1.86.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-01-03: OpenGL: Backup, setup and restore GL_SHADE_MODEL state, disable GL_STENCIL_TEST and disable GL_NORMAL_ARRAY client state to increase compatibility with legacy OpenGL applications.
-//  2020-01-23: OpenGL: Backup, setup and restore GL_TEXTURE_ENV to increase compatibility with legacy OpenGL applications.
-//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-08-03: OpenGL: Disabling/restoring GL_LIGHTING and GL_COLOR_MATERIAL to increase compatibility with legacy OpenGL applications.
-//  2018-06-08: Misc: Extracted imgui_impl_opengl2.cpp/.h away from the old combined GLFW/SDL+OpenGL2 examples.
-//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplOpenGL2_RenderDrawData() in the .h file so you can call it yourself.
-//  2017-09-01: OpenGL: Save and restore current polygon mode.
-//  2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal).
-//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_opengl2.h"
-#include <stdint.h>     // intptr_t
-
-// Clang/GCC warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-macros"                      // warning: macro is not used
-#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
-#endif
-
-// Include OpenGL header (without an OpenGL loader) requires a bit of fiddling
-#if defined(_WIN32) && !defined(APIENTRY)
-#define APIENTRY __stdcall                  // It is customary to use APIENTRY for OpenGL function pointer declarations on all platforms.  Additionally, the Windows OpenGL header needs APIENTRY.
-#endif
-#if defined(_WIN32) && !defined(WINGDIAPI)
-#define WINGDIAPI __declspec(dllimport)     // Some Windows OpenGL headers need this
-#endif
-#if defined(__APPLE__)
-#define GL_SILENCE_DEPRECATION
-#include <OpenGL/gl.h>
-#else
-#include <GL/gl.h>
-#endif
-
-struct ImGui_ImplOpenGL2_Data
-{
-    GLuint       FontTexture;
-
-    ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplOpenGL2_Data* ImGui_ImplOpenGL2_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL2_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// Functions
-bool    ImGui_ImplOpenGL2_Init()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Setup backend capabilities flags
-    ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_opengl2";
-
-    return true;
-}
-
-void    ImGui_ImplOpenGL2_Shutdown()
-{
-    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplOpenGL2_DestroyDeviceObjects();
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    IM_DELETE(bd);
-}
-
-void    ImGui_ImplOpenGL2_NewFrame()
-{
-    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL2_Init()?");
-
-    if (!bd->FontTexture)
-        ImGui_ImplOpenGL2_CreateDeviceObjects();
-}
-
-static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height)
-{
-    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill.
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    //glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // In order to composite our output buffer we need to preserve alpha
-    glDisable(GL_CULL_FACE);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_STENCIL_TEST);
-    glDisable(GL_LIGHTING);
-    glDisable(GL_COLOR_MATERIAL);
-    glEnable(GL_SCISSOR_TEST);
-    glEnableClientState(GL_VERTEX_ARRAY);
-    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-    glEnableClientState(GL_COLOR_ARRAY);
-    glDisableClientState(GL_NORMAL_ARRAY);
-    glEnable(GL_TEXTURE_2D);
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-    glShadeModel(GL_SMOOTH);
-    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-
-    // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!),
-    // you may need to backup/reset/restore other state, e.g. for current shader using the commented lines below.
-    // (DO NOT MODIFY THIS FILE! Add the code in your calling function)
-    //   GLint last_program;
-    //   glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
-    //   glUseProgram(0);
-    //   ImGui_ImplOpenGL2_RenderDrawData(...);
-    //   glUseProgram(last_program)
-    // There are potentially many more states you could need to clear/setup that we can't access from default headers.
-    // e.g. glBindBuffer(GL_ARRAY_BUFFER, 0), glDisable(GL_TEXTURE_CUBE_MAP).
-
-    // Setup viewport, orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
-    glMatrixMode(GL_PROJECTION);
-    glPushMatrix();
-    glLoadIdentity();
-    glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f);
-    glMatrixMode(GL_MODELVIEW);
-    glPushMatrix();
-    glLoadIdentity();
-}
-
-// OpenGL2 Render function.
-// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
-// This is in order to be able to run within an OpenGL engine that doesn't do so.
-void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
-    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
-    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
-    if (fb_width == 0 || fb_height == 0)
-        return;
-
-    // Backup GL state
-    GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
-    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
-    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
-    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
-    GLint last_shade_model; glGetIntegerv(GL_SHADE_MODEL, &last_shade_model);
-    GLint last_tex_env_mode; glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &last_tex_env_mode);
-    glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
-
-    // Setup desired GL state
-    ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height);
-
-    // Will project scissor/clipping rectangles into framebuffer space
-    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
-    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
-
-    // Render command lists
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-        const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;
-        const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;
-        glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, pos)));
-        glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, uv)));
-        glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + offsetof(ImDrawVert, col)));
-
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
-                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)
-                glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y));
-
-                // Bind texture, Draw
-                glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
-                glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset);
-            }
-        }
-    }
-
-    // Restore modified GL state
-    glDisableClientState(GL_COLOR_ARRAY);
-    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-    glDisableClientState(GL_VERTEX_ARRAY);
-    glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture);
-    glMatrixMode(GL_MODELVIEW);
-    glPopMatrix();
-    glMatrixMode(GL_PROJECTION);
-    glPopMatrix();
-    glPopAttrib();
-    glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
-    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
-    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
-    glShadeModel(last_shade_model);
-    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode);
-}
-
-bool ImGui_ImplOpenGL2_CreateFontsTexture()
-{
-    // Build texture atlas
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
-
-    // Upload texture to graphics system
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    GLint last_texture;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
-    glGenTextures(1, &bd->FontTexture);
-    glBindTexture(GL_TEXTURE_2D, bd->FontTexture);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
-
-    // Restore state
-    glBindTexture(GL_TEXTURE_2D, last_texture);
-
-    return true;
-}
-
-void ImGui_ImplOpenGL2_DestroyFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
-    if (bd->FontTexture)
-    {
-        glDeleteTextures(1, &bd->FontTexture);
-        io.Fonts->SetTexID(0);
-        bd->FontTexture = 0;
-    }
-}
-
-bool    ImGui_ImplOpenGL2_CreateDeviceObjects()
-{
-    return ImGui_ImplOpenGL2_CreateFontsTexture();
-}
-
-void    ImGui_ImplOpenGL2_DestroyDeviceObjects()
-{
-    ImGui_ImplOpenGL2_DestroyFontsTexture();
-}
-
-//-----------------------------------------------------------------------------
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl2.h b/engines/twp/imgui/backends/imgui_impl_opengl2.h
deleted file mode 100644
index 9c756c71d6b..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_opengl2.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline)
-// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)**
-// **Prefer using the code in imgui_impl_opengl3.cpp**
-// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read.
-// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more
-// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might
-// confuse your GPU driver.
-// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API.
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_Init();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL2_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL2_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data);
-
-// Called by Init/NewFrame/Shutdown
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_CreateFontsTexture();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL2_DestroyFontsTexture();
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL2_CreateDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL2_DestroyDeviceObjects();
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl3.cpp b/engines/twp/imgui/backends/imgui_impl_opengl3.cpp
deleted file mode 100644
index a36a7ac278b..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_opengl3.cpp
+++ /dev/null
@@ -1,948 +0,0 @@
-// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
-// - Desktop GL: 2.x 3.x 4.x
-// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
-// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-//  [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
-
-// About WebGL/ES:
-// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
-// - This is done automatically on iOS, Android and Emscripten targets.
-// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
-//  2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983)
-//  2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)
-//  2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)
-//  2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)
-//  2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)
-//  2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224)
-//  2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)
-//  2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)
-//  2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.
-//  2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).
-//  2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.
-//  2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
-//  2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
-//  2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.
-//  2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.
-//  2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version.
-//  2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
-//  2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
-//  2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
-//  2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
-//  2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
-//  2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
-//  2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader.
-//  2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
-//  2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
-//  2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
-//  2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
-//  2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
-//  2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
-//  2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
-//  2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
-//  2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
-//  2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
-//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
-//  2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
-//  2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
-//  2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
-//  2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
-//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
-//  2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
-//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
-//  2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
-//  2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
-//  2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
-//  2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
-//  2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.
-//  2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
-//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
-//  2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
-//  2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.
-//  2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
-//  2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
-//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
-//  2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.
-//  2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.
-//  2017-05-01: OpenGL: Fixed save and restore of current blend func state.
-//  2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.
-//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
-//  2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)
-
-//----------------------------------------
-// OpenGL    GLSL      GLSL
-// version   version   string
-//----------------------------------------
-//  2.0       110       "#version 110"
-//  2.1       120       "#version 120"
-//  3.0       130       "#version 130"
-//  3.1       140       "#version 140"
-//  3.2       150       "#version 150"
-//  3.3       330       "#version 330 core"
-//  4.0       400       "#version 400 core"
-//  4.1       410       "#version 410 core"
-//  4.2       420       "#version 410 core"
-//  4.3       430       "#version 430 core"
-//  ES 2.0    100       "#version 100"      = WebGL 1.0
-//  ES 3.0    300       "#version 300 es"   = WebGL 2.0
-//----------------------------------------
-
-#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_opengl3.h"
-#include <stdio.h>
-#include <stdint.h>     // intptr_t
-#if defined(__APPLE__)
-#include <TargetConditionals.h>
-#endif
-
-// Clang/GCC warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wold-style-cast"         // warning: use of old-style cast
-#pragma clang diagnostic ignored "-Wsign-conversion"        // warning: implicit conversion changes signedness
-#pragma clang diagnostic ignored "-Wunused-macros"          // warning: macro is not used
-#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
-#pragma clang diagnostic ignored "-Wcast-function-type"     // warning: cast between incompatible function types (for loader)
-#endif
-#if defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpragmas"                  // warning: unknown option after '#pragma GCC diagnostic' kind
-#pragma GCC diagnostic ignored "-Wunknown-warning-option"   // warning: unknown warning group 'xxx'
-#pragma GCC diagnostic ignored "-Wcast-function-type"       // warning: cast between incompatible function types (for loader)
-#endif
-
-// GL includes
-#if defined(IMGUI_IMPL_OPENGL_ES2)
-#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
-#include <OpenGLES/ES2/gl.h>    // Use GL ES 2
-#else
-#include <GLES2/gl2.h>          // Use GL ES 2
-#endif
-#if defined(__EMSCRIPTEN__)
-#ifndef GL_GLEXT_PROTOTYPES
-#define GL_GLEXT_PROTOTYPES
-#endif
-#include <GLES2/gl2ext.h>
-#endif
-#elif defined(IMGUI_IMPL_OPENGL_ES3)
-#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
-#include <OpenGLES/ES3/gl.h>    // Use GL ES 3
-#else
-#include <GLES3/gl3.h>          // Use GL ES 3
-#endif
-#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
-// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
-// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w.
-// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.).
-// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp):
-// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped
-// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases
-// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version.
-#define IMGL3W_IMPL
-#include "imgui_impl_opengl3_loader.h"
-#endif
-
-// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension
-#ifndef IMGUI_IMPL_OPENGL_ES2
-#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-#elif defined(__EMSCRIPTEN__)
-#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-#define glBindVertexArray       glBindVertexArrayOES
-#define glGenVertexArrays       glGenVertexArraysOES
-#define glDeleteVertexArrays    glDeleteVertexArraysOES
-#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES
-#endif
-
-// Desktop GL 2.0+ has glPolygonMode() which GL ES and WebGL don't have.
-#ifdef GL_POLYGON_MODE
-#define IMGUI_IMPL_HAS_POLYGON_MODE
-#endif
-
-// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.
-#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2)
-#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
-#endif
-
-// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler()
-#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3))
-#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-#endif
-
-// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state
-#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1)
-#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-#endif
-
-// Desktop GL use extension detection
-#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)
-#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
-#endif
-
-// [Debugging]
-//#define IMGUI_IMPL_OPENGL_DEBUG
-#ifdef IMGUI_IMPL_OPENGL_DEBUG
-#include <stdio.h>
-#define GL_CALL(_CALL)      do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0)  // Call with error check
-#else
-#define GL_CALL(_CALL)      _CALL   // Call without error check
-#endif
-
-// OpenGL Data
-struct ImGui_ImplOpenGL3_Data
-{
-    GLuint          GlVersion;               // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
-    char            GlslVersionString[32];   // Specified by user or detected based on compile time GL settings.
-    bool            GlProfileIsES2;
-    bool            GlProfileIsES3;
-    bool            GlProfileIsCompat;
-    GLint           GlProfileMask;
-    GLuint          FontTexture;
-    GLuint          ShaderHandle;
-    GLint           AttribLocationTex;       // Uniforms location
-    GLint           AttribLocationProjMtx;
-    GLuint          AttribLocationVtxPos;    // Vertex attributes location
-    GLuint          AttribLocationVtxUV;
-    GLuint          AttribLocationVtxColor;
-    unsigned int    VboHandle, ElementsHandle;
-    GLsizeiptr      VertexBufferSize;
-    GLsizeiptr      IndexBufferSize;
-    bool            HasClipOrigin;
-    bool            UseBufferSubData;
-
-    ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
-}
-
-// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)
-#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-struct ImGui_ImplOpenGL3_VtxAttribState
-{
-    GLint   Enabled, Size, Type, Normalized, Stride;
-    GLvoid* Ptr;
-
-    void GetState(GLint index)
-    {
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);
-        glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);
-        glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);
-    }
-    void SetState(GLint index)
-    {
-        glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);
-        if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index);
-    }
-};
-#endif
-
-// Functions
-bool    ImGui_ImplOpenGL3_Init(const char* glsl_version)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
-
-    // Initialize our loader
-#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
-    if (imgl3wInit() != 0)
-    {
-        fprintf(stderr, "Failed to initialize OpenGL loader!\n");
-        return false;
-    }
-#endif
-
-    // Setup backend capabilities flags
-    ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
-    io.BackendRendererUserData = (void*)bd;
-    io.BackendRendererName = "imgui_impl_opengl3";
-
-    // Query for GL version (e.g. 320 for GL 3.2)
-#if defined(IMGUI_IMPL_OPENGL_ES2)
-    // GLES 2
-    bd->GlVersion = 200;
-    bd->GlProfileIsES2 = true;
-#else
-    // Desktop or GLES 3
-    GLint major = 0;
-    GLint minor = 0;
-    glGetIntegerv(GL_MAJOR_VERSION, &major);
-    glGetIntegerv(GL_MINOR_VERSION, &minor);
-    if (major == 0 && minor == 0)
-    {
-        // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
-        const char* gl_version = (const char*)glGetString(GL_VERSION);
-        sscanf(gl_version, "%d.%d", &major, &minor);
-    }
-    bd->GlVersion = (GLuint)(major * 100 + minor * 10);
-#if defined(GL_CONTEXT_PROFILE_MASK)
-    if (bd->GlVersion >= 320)
-        glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
-    bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
-#endif
-
-#if defined(IMGUI_IMPL_OPENGL_ES3)
-    bd->GlProfileIsES3 = true;
-#endif
-
-    bd->UseBufferSubData = false;
-    /*
-    // Query vendor to enable glBufferSubData kludge
-#ifdef _WIN32
-    if (const char* vendor = (const char*)glGetString(GL_VENDOR))
-        if (strncmp(vendor, "Intel", 5) == 0)
-            bd->UseBufferSubData = true;
-#endif
-    */
-#endif
-
-#ifdef IMGUI_IMPL_OPENGL_DEBUG
-    printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
-#endif
-
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
-    if (bd->GlVersion >= 320)
-        io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
-#endif
-
-    // Store GLSL version string so we can refer to it later in case we recreate shaders.
-    // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
-    if (glsl_version == nullptr)
-    {
-#if defined(IMGUI_IMPL_OPENGL_ES2)
-        glsl_version = "#version 100";
-#elif defined(IMGUI_IMPL_OPENGL_ES3)
-        glsl_version = "#version 300 es";
-#elif defined(__APPLE__)
-        glsl_version = "#version 150";
-#else
-        glsl_version = "#version 130";
-#endif
-    }
-    IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
-    strcpy(bd->GlslVersionString, glsl_version);
-    strcat(bd->GlslVersionString, "\n");
-
-    // Make an arbitrary GL call (we don't actually need the result)
-    // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!
-    GLint current_texture;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
-
-    // Detect extensions we support
-    bd->HasClipOrigin = (bd->GlVersion >= 450);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS
-    GLint num_extensions = 0;
-    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
-    for (GLint i = 0; i < num_extensions; i++)
-    {
-        const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
-        if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0)
-            bd->HasClipOrigin = true;
-    }
-#endif
-
-    return true;
-}
-
-void    ImGui_ImplOpenGL3_Shutdown()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    ImGui_ImplOpenGL3_DestroyDeviceObjects();
-    io.BackendRendererName = nullptr;
-    io.BackendRendererUserData = nullptr;
-    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
-    IM_DELETE(bd);
-}
-
-void    ImGui_ImplOpenGL3_NewFrame()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?");
-
-    if (!bd->ShaderHandle)
-        ImGui_ImplOpenGL3_CreateDeviceObjects();
-}
-
-static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
-    glEnable(GL_BLEND);
-    glBlendEquation(GL_FUNC_ADD);
-    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-    glDisable(GL_CULL_FACE);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_STENCIL_TEST);
-    glEnable(GL_SCISSOR_TEST);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    if (bd->GlVersion >= 310)
-        glDisable(GL_PRIMITIVE_RESTART);
-#endif
-#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-#endif
-
-    // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
-#if defined(GL_CLIP_ORIGIN)
-    bool clip_origin_lower_left = true;
-    if (bd->HasClipOrigin)
-    {
-        GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
-        if (current_clip_origin == GL_UPPER_LEFT)
-            clip_origin_lower_left = false;
-    }
-#endif
-
-    // Setup viewport, orthographic projection matrix
-    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
-    GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));
-    float L = draw_data->DisplayPos.x;
-    float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
-    float T = draw_data->DisplayPos.y;
-    float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
-#if defined(GL_CLIP_ORIGIN)
-    if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
-#endif
-    const float ortho_projection[4][4] =
-    {
-        { 2.0f/(R-L),   0.0f,         0.0f,   0.0f },
-        { 0.0f,         2.0f/(T-B),   0.0f,   0.0f },
-        { 0.0f,         0.0f,        -1.0f,   0.0f },
-        { (R+L)/(L-R),  (T+B)/(B-T),  0.0f,   1.0f },
-    };
-    glUseProgram(bd->ShaderHandle);
-    glUniform1i(bd->AttribLocationTex, 0);
-    glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
-
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
-        glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
-#endif
-
-    (void)vertex_array_object;
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(vertex_array_object);
-#endif
-
-    // Bind vertex/index buffers and setup attributes for ImDrawVert
-    GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));
-    GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
-    GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos,   2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV,    2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
-    GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
-}
-
-// OpenGL3 Render function.
-// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
-// This is in order to be able to run within an OpenGL engine that doesn't do so.
-void    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
-{
-    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
-    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
-    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
-    if (fb_width <= 0 || fb_height <= 0)
-        return;
-
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Backup GL state
-    GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
-    glActiveTexture(GL_TEXTURE0);
-    GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);
-    GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }
-#endif
-    GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
-#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.
-    GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);
-    ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);
-#endif
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);
-#endif
-#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
-#endif
-    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
-    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
-    GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);
-    GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);
-    GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);
-    GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);
-    GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);
-    GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);
-    GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
-    GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
-    GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
-    GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
-    GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
-#endif
-
-    // Setup desired GL state
-    // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
-    // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
-    GLuint vertex_array_object = 0;
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GL_CALL(glGenVertexArrays(1, &vertex_array_object));
-#endif
-    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
-
-    // Will project scissor/clipping rectangles into framebuffer space
-    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
-    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
-
-    // Render command lists
-    for (int n = 0; n < draw_data->CmdListsCount; n++)
-    {
-        const ImDrawList* cmd_list = draw_data->CmdLists[n];
-
-        // Upload vertex/index buffers
-        // - OpenGL drivers are in a very sorry state nowadays....
-        //   During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports
-        //   of leaks on Intel GPU when using multi-viewports on Windows.
-        // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.
-        // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.
-        //   We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.
-        // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.
-        const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
-        const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
-        if (bd->UseBufferSubData)
-        {
-            if (bd->VertexBufferSize < vtx_buffer_size)
-            {
-                bd->VertexBufferSize = vtx_buffer_size;
-                GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));
-            }
-            if (bd->IndexBufferSize < idx_buffer_size)
-            {
-                bd->IndexBufferSize = idx_buffer_size;
-                GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));
-            }
-            GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data));
-            GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data));
-        }
-        else
-        {
-            GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
-            GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
-        }
-
-        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
-        {
-            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
-            if (pcmd->UserCallback != nullptr)
-            {
-                // User callback, registered via ImDrawList::AddCallback()
-                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
-                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
-                    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
-                else
-                    pcmd->UserCallback(cmd_list, pcmd);
-            }
-            else
-            {
-                // Project scissor/clipping rectangles into framebuffer space
-                ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
-                ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
-                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
-                    continue;
-
-                // Apply scissor/clipping rectangle (Y is inverted in OpenGL)
-                GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
-
-                // Bind texture, Draw
-                GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
-                if (bd->GlVersion >= 320)
-                    GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
-                else
-#endif
-                GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
-            }
-        }
-    }
-
-    // Destroy the temporary VAO
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));
-#endif
-
-    // Restore modified GL state
-    // This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.
-    if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program);
-    glBindTexture(GL_TEXTURE_2D, last_texture);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
-    if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
-        glBindSampler(0, last_sampler);
-#endif
-    glActiveTexture(last_active_texture);
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(last_vertex_array_object);
-#endif
-    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
-#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
-    last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);
-    last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);
-    last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);
-#endif
-    glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
-    glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
-    if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
-    if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
-    if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
-    if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
-    if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
-#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
-    if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
-#endif
-
-#ifdef IMGUI_IMPL_HAS_POLYGON_MODE
-    // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons
-    if (bd->GlVersion <= 310 || bd->GlProfileIsCompat)
-    {
-        glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]);
-        glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
-    }
-    else
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);
-    }
-#endif // IMGUI_IMPL_HAS_POLYGON_MODE
-
-    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
-    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
-    (void)bd; // Not all compilation paths use this
-}
-
-bool ImGui_ImplOpenGL3_CreateFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Build texture atlas
-    unsigned char* pixels;
-    int width, height;
-    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
-
-    // Upload texture to graphics system
-    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
-    GLint last_texture;
-    GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
-    GL_CALL(glGenTextures(1, &bd->FontTexture));
-    GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
-    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
-    GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
-#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
-    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
-#endif
-    GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
-
-    // Store our identifier
-    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
-
-    // Restore state
-    GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
-
-    return true;
-}
-
-void ImGui_ImplOpenGL3_DestroyFontsTexture()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    if (bd->FontTexture)
-    {
-        glDeleteTextures(1, &bd->FontTexture);
-        io.Fonts->SetTexID(0);
-        bd->FontTexture = 0;
-    }
-}
-
-// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
-static bool CheckShader(GLuint handle, const char* desc)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    GLint status = 0, log_length = 0;
-    glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
-    glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
-    if ((GLboolean)status == GL_FALSE)
-        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString);
-    if (log_length > 1)
-    {
-        ImVector<char> buf;
-        buf.resize((int)(log_length + 1));
-        glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
-        fprintf(stderr, "%s\n", buf.begin());
-    }
-    return (GLboolean)status == GL_TRUE;
-}
-
-// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
-static bool CheckProgram(GLuint handle, const char* desc)
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    GLint status = 0, log_length = 0;
-    glGetProgramiv(handle, GL_LINK_STATUS, &status);
-    glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
-    if ((GLboolean)status == GL_FALSE)
-        fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString);
-    if (log_length > 1)
-    {
-        ImVector<char> buf;
-        buf.resize((int)(log_length + 1));
-        glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
-        fprintf(stderr, "%s\n", buf.begin());
-    }
-    return (GLboolean)status == GL_TRUE;
-}
-
-bool    ImGui_ImplOpenGL3_CreateDeviceObjects()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-
-    // Backup GL state
-    GLint last_texture, last_array_buffer;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
-    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    GLint last_vertex_array;
-    glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
-#endif
-
-    // Parse GLSL version string
-    int glsl_version = 130;
-    sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
-
-    const GLchar* vertex_shader_glsl_120 =
-        "uniform mat4 ProjMtx;\n"
-        "attribute vec2 Position;\n"
-        "attribute vec2 UV;\n"
-        "attribute vec4 Color;\n"
-        "varying vec2 Frag_UV;\n"
-        "varying vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_130 =
-        "uniform mat4 ProjMtx;\n"
-        "in vec2 Position;\n"
-        "in vec2 UV;\n"
-        "in vec4 Color;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_300_es =
-        "precision highp float;\n"
-        "layout (location = 0) in vec2 Position;\n"
-        "layout (location = 1) in vec2 UV;\n"
-        "layout (location = 2) in vec4 Color;\n"
-        "uniform mat4 ProjMtx;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* vertex_shader_glsl_410_core =
-        "layout (location = 0) in vec2 Position;\n"
-        "layout (location = 1) in vec2 UV;\n"
-        "layout (location = 2) in vec4 Color;\n"
-        "uniform mat4 ProjMtx;\n"
-        "out vec2 Frag_UV;\n"
-        "out vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Frag_UV = UV;\n"
-        "    Frag_Color = Color;\n"
-        "    gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_120 =
-        "#ifdef GL_ES\n"
-        "    precision mediump float;\n"
-        "#endif\n"
-        "uniform sampler2D Texture;\n"
-        "varying vec2 Frag_UV;\n"
-        "varying vec4 Frag_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_130 =
-        "uniform sampler2D Texture;\n"
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_300_es =
-        "precision mediump float;\n"
-        "uniform sampler2D Texture;\n"
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "layout (location = 0) out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    const GLchar* fragment_shader_glsl_410_core =
-        "in vec2 Frag_UV;\n"
-        "in vec4 Frag_Color;\n"
-        "uniform sampler2D Texture;\n"
-        "layout (location = 0) out vec4 Out_Color;\n"
-        "void main()\n"
-        "{\n"
-        "    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
-        "}\n";
-
-    // Select shaders matching our GLSL versions
-    const GLchar* vertex_shader = nullptr;
-    const GLchar* fragment_shader = nullptr;
-    if (glsl_version < 130)
-    {
-        vertex_shader = vertex_shader_glsl_120;
-        fragment_shader = fragment_shader_glsl_120;
-    }
-    else if (glsl_version >= 410)
-    {
-        vertex_shader = vertex_shader_glsl_410_core;
-        fragment_shader = fragment_shader_glsl_410_core;
-    }
-    else if (glsl_version == 300)
-    {
-        vertex_shader = vertex_shader_glsl_300_es;
-        fragment_shader = fragment_shader_glsl_300_es;
-    }
-    else
-    {
-        vertex_shader = vertex_shader_glsl_130;
-        fragment_shader = fragment_shader_glsl_130;
-    }
-
-    // Create shaders
-    const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
-    GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
-    glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
-    glCompileShader(vert_handle);
-    CheckShader(vert_handle, "vertex shader");
-
-    const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
-    GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
-    glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
-    glCompileShader(frag_handle);
-    CheckShader(frag_handle, "fragment shader");
-
-    // Link
-    bd->ShaderHandle = glCreateProgram();
-    glAttachShader(bd->ShaderHandle, vert_handle);
-    glAttachShader(bd->ShaderHandle, frag_handle);
-    glLinkProgram(bd->ShaderHandle);
-    CheckProgram(bd->ShaderHandle, "shader program");
-
-    glDetachShader(bd->ShaderHandle, vert_handle);
-    glDetachShader(bd->ShaderHandle, frag_handle);
-    glDeleteShader(vert_handle);
-    glDeleteShader(frag_handle);
-
-    bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
-    bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
-    bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
-    bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
-    bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
-
-    // Create buffers
-    glGenBuffers(1, &bd->VboHandle);
-    glGenBuffers(1, &bd->ElementsHandle);
-
-    ImGui_ImplOpenGL3_CreateFontsTexture();
-
-    // Restore modified GL state
-    glBindTexture(GL_TEXTURE_2D, last_texture);
-    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
-#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
-    glBindVertexArray(last_vertex_array);
-#endif
-
-    return true;
-}
-
-void    ImGui_ImplOpenGL3_DestroyDeviceObjects()
-{
-    ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
-    if (bd->VboHandle)      { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
-    if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
-    if (bd->ShaderHandle)   { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
-    ImGui_ImplOpenGL3_DestroyFontsTexture();
-}
-
-//-----------------------------------------------------------------------------
-
-#if defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_opengl3.h b/engines/twp/imgui/backends/imgui_impl_opengl3.h
deleted file mode 100644
index 23eb924791e..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_opengl3.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
-// - Desktop GL: 2.x 3.x 4.x
-// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
-// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
-
-// Implemented features:
-//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-//  [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
-
-// About WebGL/ES:
-// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
-// - This is done automatically on iOS, Android and Emscripten targets.
-// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// About GLSL version:
-//  The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string.
-//  On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
-//  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-// Backend API
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_NewFrame();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
-
-// (Optional) Called by Init/NewFrame/Shutdown
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateFontsTexture();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyFontsTexture();
-IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateDeviceObjects();
-IMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyDeviceObjects();
-
-// Specific OpenGL ES versions
-//#define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten
-//#define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android
-
-// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
-#if !defined(IMGUI_IMPL_OPENGL_ES2) \
- && !defined(IMGUI_IMPL_OPENGL_ES3)
-
-// Try to detect GLES on matching platforms
-#if defined(__APPLE__)
-#include <TargetConditionals.h>
-#endif
-#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
-#define IMGUI_IMPL_OPENGL_ES3               // iOS, Android  -> GL ES 3, "#version 300 es"
-#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
-#define IMGUI_IMPL_OPENGL_ES2               // Emscripten    -> GL ES 2, "#version 100"
-#else
-// Otherwise imgui_impl_opengl3_loader.h will be used.
-#endif
-
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_osx.h b/engines/twp/imgui/backends/imgui_impl_osx.h
deleted file mode 100644
index 4033ad93e8a..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_osx.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// dear imgui: Platform Backend for OSX / Cocoa
-// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..)
-// - Not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac.
-// - Requires linking with the GameController framework ("-framework GameController").
-
-// Implemented features:
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-//  [X] Platform: Mouse support. Can discriminate Mouse/Pen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: IME support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-#ifdef __OBJC__
-
- at class NSEvent;
- at class NSView;
-
-IMGUI_IMPL_API bool     ImGui_ImplOSX_Init(NSView* _Nonnull view);
-IMGUI_IMPL_API void     ImGui_ImplOSX_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplOSX_NewFrame(NSView* _Nullable view);
-
-#endif
-
-//-----------------------------------------------------------------------------
-// C++ API
-//-----------------------------------------------------------------------------
-
-#ifdef IMGUI_IMPL_METAL_CPP_EXTENSIONS
-// #include <AppKit/AppKit.hpp>
-#ifndef __OBJC__
-
-IMGUI_IMPL_API bool     ImGui_ImplOSX_Init(void* _Nonnull view);
-IMGUI_IMPL_API void     ImGui_ImplOSX_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplOSX_NewFrame(void* _Nullable view);
-
-#endif
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_osx.mm b/engines/twp/imgui/backends/imgui_impl_osx.mm
deleted file mode 100644
index b1ca5b03673..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_osx.mm
+++ /dev/null
@@ -1,812 +0,0 @@
-// dear imgui: Platform Backend for OSX / Cocoa
-// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..)
-// - Not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac.
-// - Requires linking with the GameController framework ("-framework GameController").
-
-// Implemented features:
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-//  [X] Platform: Mouse support. Can discriminate Mouse/Pen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend).
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: IME support.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#import "imgui.h"
-#ifndef IMGUI_DISABLE
-#import "imgui_impl_osx.h"
-#import <Cocoa/Cocoa.h>
-#import <Carbon/Carbon.h>
-#import <GameController/GameController.h>
-#import <time.h>
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F20 function keys. Stopped mapping F13 into PrintScreen.
-//  2023-04-09: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_Pen.
-//  2023-02-01: Fixed scroll wheel scaling for devices emitting events with hasPreciseScrollingDeltas==false (e.g. non-Apple mices).
-//  2022-11-02: Fixed mouse coordinates before clicking the host window.
-//  2022-10-06: Fixed mouse inputs on flipped views.
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-05-03: Inputs: Removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture.
-//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
-//  2022-03-22: Inputs: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key
-//  2022-02-07: Inputs: Forward keyDown/keyUp events to OS when unused by dear imgui.
-//  2022-01-31: Fixed building with old Xcode versions that are missing gamepad features.
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-12: Inputs: Added basic Platform IME support, hooking the io.SetPlatformImeDataFn() function.
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2021-12-13: *BREAKING CHANGE* Add NSView parameter to ImGui_ImplOSX_Init(). Generally fix keyboard support. Using kVK_* codes for keyboard keys.
-//  2021-12-13: Add game controller support.
-//  2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards.
-//  2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events.
-//  2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key.
-//  2021-04-19: Inputs: Added a fix for keys remaining stuck in pressed state when CMD-tabbing into different application.
-//  2021-01-27: Inputs: Added a fix for mouse position not being reported when mouse buttons other than left one are down.
-//  2020-10-28: Inputs: Added a fix for handling keypad-enter key.
-//  2020-05-25: Inputs: Added a fix for missing trackpad clicks when done with "soft tap".
-//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
-//  2019-10-11: Inputs:  Fix using Backspace key.
-//  2019-07-21: Re-added clipboard handlers as they are not enabled by default in core imgui.cpp (reverted 2019-05-18 change).
-//  2019-05-28: Inputs: Added mouse cursor shape and visibility support.
-//  2019-05-18: Misc: Removed clipboard handlers as they are now supported by core imgui.cpp.
-//  2019-05-11: Inputs: Don't filter character values before calling AddInputCharacter() apart from 0xF700..0xFFFF range.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
-//  2018-07-07: Initial version.
-
-#define APPLE_HAS_BUTTON_OPTIONS (__IPHONE_OS_VERSION_MIN_REQUIRED >= 130000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 || __TV_OS_VERSION_MIN_REQUIRED >= 130000)
-#define APPLE_HAS_CONTROLLER     (__IPHONE_OS_VERSION_MIN_REQUIRED >= 140000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 110000 || __TV_OS_VERSION_MIN_REQUIRED >= 140000)
-#define APPLE_HAS_THUMBSTICKS    (__IPHONE_OS_VERSION_MIN_REQUIRED >= 120100 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101401 || __TV_OS_VERSION_MIN_REQUIRED >= 120100)
-
- at class ImGuiObserver;
- at class KeyEventResponder;
-
-// Data
-struct ImGui_ImplOSX_Data
-{
-    CFTimeInterval              Time;
-    NSCursor*                   MouseCursors[ImGuiMouseCursor_COUNT];
-    bool                        MouseCursorHidden;
-    ImGuiObserver*              Observer;
-    KeyEventResponder*          KeyEventResponder;
-    NSTextInputContext*         InputContext;
-    id                          Monitor;
-
-    ImGui_ImplOSX_Data()        { memset(this, 0, sizeof(*this)); }
-};
-
-static ImGui_ImplOSX_Data*      ImGui_ImplOSX_CreateBackendData()   { return IM_NEW(ImGui_ImplOSX_Data)(); }
-static ImGui_ImplOSX_Data*      ImGui_ImplOSX_GetBackendData()      { return (ImGui_ImplOSX_Data*)ImGui::GetIO().BackendPlatformUserData; }
-static void                     ImGui_ImplOSX_DestroyBackendData()  { IM_DELETE(ImGui_ImplOSX_GetBackendData()); }
-
-static inline CFTimeInterval    GetMachAbsoluteTimeInSeconds()      { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
-
-// Forward Declarations
-static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view);
-static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view);
-
-// Undocumented methods for creating cursors.
- at interface NSCursor()
-+ (id)_windowResizeNorthWestSouthEastCursor;
-+ (id)_windowResizeNorthEastSouthWestCursor;
-+ (id)_windowResizeNorthSouthCursor;
-+ (id)_windowResizeEastWestCursor;
- at end
-
-/**
- KeyEventResponder implements the NSTextInputClient protocol as is required by the macOS text input manager.
-
- The macOS text input manager is invoked by calling the interpretKeyEvents method from the keyDown method.
- Keyboard events are then evaluated by the macOS input manager and valid text input is passed back via the
- insertText:replacementRange method.
-
- This is the same approach employed by other cross-platform libraries such as SDL2:
-  https://github.com/spurious/SDL-mirror/blob/e17aacbd09e65a4fd1e166621e011e581fb017a8/src/video/cocoa/SDL_cocoakeyboard.m#L53
- and GLFW:
-  https://github.com/glfw/glfw/blob/b55a517ae0c7b5127dffa79a64f5406021bf9076/src/cocoa_window.m#L722-L723
- */
- at interface KeyEventResponder: NSView<NSTextInputClient>
- at end
-
- at implementation KeyEventResponder
-{
-    float _posX;
-    float _posY;
-    NSRect _imeRect;
-}
-
-#pragma mark - Public
-
-- (void)setImePosX:(float)posX imePosY:(float)posY
-{
-    _posX = posX;
-    _posY = posY;
-}
-
-- (void)updateImePosWithView:(NSView *)view
-{
-    NSWindow *window = view.window;
-    if (!window)
-        return;
-    NSRect contentRect = [window contentRectForFrameRect:window.frame];
-    NSRect rect = NSMakeRect(_posX, contentRect.size.height - _posY, 0, 0);
-    _imeRect = [window convertRectToScreen:rect];
-}
-
-- (void)viewDidMoveToWindow
-{
-    // Ensure self is a first responder to receive the input events.
-    [self.window makeFirstResponder:self];
-}
-
-- (void)keyDown:(NSEvent*)event
-{
-    if (!ImGui_ImplOSX_HandleEvent(event, self))
-        [super keyDown:event];
-
-    // Call to the macOS input manager system.
-    [self interpretKeyEvents:@[event]];
-}
-
-- (void)keyUp:(NSEvent*)event
-{
-    if (!ImGui_ImplOSX_HandleEvent(event, self))
-        [super keyUp:event];
-}
-
-- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
-{
-    ImGuiIO& io = ImGui::GetIO();
-
-    NSString* characters;
-    if ([aString isKindOfClass:[NSAttributedString class]])
-        characters = [aString string];
-    else
-        characters = (NSString*)aString;
-
-    io.AddInputCharactersUTF8(characters.UTF8String);
-}
-
-- (BOOL)acceptsFirstResponder
-{
-    return YES;
-}
-
-- (void)doCommandBySelector:(SEL)myselector
-{
-}
-
-- (nullable NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
-{
-    return nil;
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)point
-{
-    return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
-{
-    return _imeRect;
-}
-
-- (BOOL)hasMarkedText
-{
-    return NO;
-}
-
-- (NSRange)markedRange
-{
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (NSRange)selectedRange
-{
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (void)setMarkedText:(nonnull id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
-{
-}
-
-- (void)unmarkText
-{
-}
-
-- (nonnull NSArray<NSAttributedStringKey>*)validAttributesForMarkedText
-{
-    return @[];
-}
-
- at end
-
- at interface ImGuiObserver : NSObject
-
-- (void)onApplicationBecomeActive:(NSNotification*)aNotification;
-- (void)onApplicationBecomeInactive:(NSNotification*)aNotification;
-
- at end
-
- at implementation ImGuiObserver
-
-- (void)onApplicationBecomeActive:(NSNotification*)aNotification
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddFocusEvent(true);
-}
-
-- (void)onApplicationBecomeInactive:(NSNotification*)aNotification
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddFocusEvent(false);
-}
-
- at end
-
-// Functions
-static ImGuiKey ImGui_ImplOSX_KeyCodeToImGuiKey(int key_code)
-{
-    switch (key_code)
-    {
-        case kVK_ANSI_A: return ImGuiKey_A;
-        case kVK_ANSI_S: return ImGuiKey_S;
-        case kVK_ANSI_D: return ImGuiKey_D;
-        case kVK_ANSI_F: return ImGuiKey_F;
-        case kVK_ANSI_H: return ImGuiKey_H;
-        case kVK_ANSI_G: return ImGuiKey_G;
-        case kVK_ANSI_Z: return ImGuiKey_Z;
-        case kVK_ANSI_X: return ImGuiKey_X;
-        case kVK_ANSI_C: return ImGuiKey_C;
-        case kVK_ANSI_V: return ImGuiKey_V;
-        case kVK_ANSI_B: return ImGuiKey_B;
-        case kVK_ANSI_Q: return ImGuiKey_Q;
-        case kVK_ANSI_W: return ImGuiKey_W;
-        case kVK_ANSI_E: return ImGuiKey_E;
-        case kVK_ANSI_R: return ImGuiKey_R;
-        case kVK_ANSI_Y: return ImGuiKey_Y;
-        case kVK_ANSI_T: return ImGuiKey_T;
-        case kVK_ANSI_1: return ImGuiKey_1;
-        case kVK_ANSI_2: return ImGuiKey_2;
-        case kVK_ANSI_3: return ImGuiKey_3;
-        case kVK_ANSI_4: return ImGuiKey_4;
-        case kVK_ANSI_6: return ImGuiKey_6;
-        case kVK_ANSI_5: return ImGuiKey_5;
-        case kVK_ANSI_Equal: return ImGuiKey_Equal;
-        case kVK_ANSI_9: return ImGuiKey_9;
-        case kVK_ANSI_7: return ImGuiKey_7;
-        case kVK_ANSI_Minus: return ImGuiKey_Minus;
-        case kVK_ANSI_8: return ImGuiKey_8;
-        case kVK_ANSI_0: return ImGuiKey_0;
-        case kVK_ANSI_RightBracket: return ImGuiKey_RightBracket;
-        case kVK_ANSI_O: return ImGuiKey_O;
-        case kVK_ANSI_U: return ImGuiKey_U;
-        case kVK_ANSI_LeftBracket: return ImGuiKey_LeftBracket;
-        case kVK_ANSI_I: return ImGuiKey_I;
-        case kVK_ANSI_P: return ImGuiKey_P;
-        case kVK_ANSI_L: return ImGuiKey_L;
-        case kVK_ANSI_J: return ImGuiKey_J;
-        case kVK_ANSI_Quote: return ImGuiKey_Apostrophe;
-        case kVK_ANSI_K: return ImGuiKey_K;
-        case kVK_ANSI_Semicolon: return ImGuiKey_Semicolon;
-        case kVK_ANSI_Backslash: return ImGuiKey_Backslash;
-        case kVK_ANSI_Comma: return ImGuiKey_Comma;
-        case kVK_ANSI_Slash: return ImGuiKey_Slash;
-        case kVK_ANSI_N: return ImGuiKey_N;
-        case kVK_ANSI_M: return ImGuiKey_M;
-        case kVK_ANSI_Period: return ImGuiKey_Period;
-        case kVK_ANSI_Grave: return ImGuiKey_GraveAccent;
-        case kVK_ANSI_KeypadDecimal: return ImGuiKey_KeypadDecimal;
-        case kVK_ANSI_KeypadMultiply: return ImGuiKey_KeypadMultiply;
-        case kVK_ANSI_KeypadPlus: return ImGuiKey_KeypadAdd;
-        case kVK_ANSI_KeypadClear: return ImGuiKey_NumLock;
-        case kVK_ANSI_KeypadDivide: return ImGuiKey_KeypadDivide;
-        case kVK_ANSI_KeypadEnter: return ImGuiKey_KeypadEnter;
-        case kVK_ANSI_KeypadMinus: return ImGuiKey_KeypadSubtract;
-        case kVK_ANSI_KeypadEquals: return ImGuiKey_KeypadEqual;
-        case kVK_ANSI_Keypad0: return ImGuiKey_Keypad0;
-        case kVK_ANSI_Keypad1: return ImGuiKey_Keypad1;
-        case kVK_ANSI_Keypad2: return ImGuiKey_Keypad2;
-        case kVK_ANSI_Keypad3: return ImGuiKey_Keypad3;
-        case kVK_ANSI_Keypad4: return ImGuiKey_Keypad4;
-        case kVK_ANSI_Keypad5: return ImGuiKey_Keypad5;
-        case kVK_ANSI_Keypad6: return ImGuiKey_Keypad6;
-        case kVK_ANSI_Keypad7: return ImGuiKey_Keypad7;
-        case kVK_ANSI_Keypad8: return ImGuiKey_Keypad8;
-        case kVK_ANSI_Keypad9: return ImGuiKey_Keypad9;
-        case kVK_Return: return ImGuiKey_Enter;
-        case kVK_Tab: return ImGuiKey_Tab;
-        case kVK_Space: return ImGuiKey_Space;
-        case kVK_Delete: return ImGuiKey_Backspace;
-        case kVK_Escape: return ImGuiKey_Escape;
-        case kVK_CapsLock: return ImGuiKey_CapsLock;
-        case kVK_Control: return ImGuiKey_LeftCtrl;
-        case kVK_Shift: return ImGuiKey_LeftShift;
-        case kVK_Option: return ImGuiKey_LeftAlt;
-        case kVK_Command: return ImGuiKey_LeftSuper;
-        case kVK_RightControl: return ImGuiKey_RightCtrl;
-        case kVK_RightShift: return ImGuiKey_RightShift;
-        case kVK_RightOption: return ImGuiKey_RightAlt;
-        case kVK_RightCommand: return ImGuiKey_RightSuper;
-//      case kVK_Function: return ImGuiKey_;
-//      case kVK_VolumeUp: return ImGuiKey_;
-//      case kVK_VolumeDown: return ImGuiKey_;
-//      case kVK_Mute: return ImGuiKey_;
-        case kVK_F1: return ImGuiKey_F1;
-        case kVK_F2: return ImGuiKey_F2;
-        case kVK_F3: return ImGuiKey_F3;
-        case kVK_F4: return ImGuiKey_F4;
-        case kVK_F5: return ImGuiKey_F5;
-        case kVK_F6: return ImGuiKey_F6;
-        case kVK_F7: return ImGuiKey_F7;
-        case kVK_F8: return ImGuiKey_F8;
-        case kVK_F9: return ImGuiKey_F9;
-        case kVK_F10: return ImGuiKey_F10;
-        case kVK_F11: return ImGuiKey_F11;
-        case kVK_F12: return ImGuiKey_F12;
-        case kVK_F13: return ImGuiKey_F13;
-        case kVK_F14: return ImGuiKey_F14;
-        case kVK_F15: return ImGuiKey_F15;
-        case kVK_F16: return ImGuiKey_F16;
-        case kVK_F17: return ImGuiKey_F17;
-        case kVK_F18: return ImGuiKey_F18;
-        case kVK_F19: return ImGuiKey_F19;
-        case kVK_F20: return ImGuiKey_F20;
-        case 0x6E: return ImGuiKey_Menu;
-        case kVK_Help: return ImGuiKey_Insert;
-        case kVK_Home: return ImGuiKey_Home;
-        case kVK_PageUp: return ImGuiKey_PageUp;
-        case kVK_ForwardDelete: return ImGuiKey_Delete;
-        case kVK_End: return ImGuiKey_End;
-        case kVK_PageDown: return ImGuiKey_PageDown;
-        case kVK_LeftArrow: return ImGuiKey_LeftArrow;
-        case kVK_RightArrow: return ImGuiKey_RightArrow;
-        case kVK_DownArrow: return ImGuiKey_DownArrow;
-        case kVK_UpArrow: return ImGuiKey_UpArrow;
-        default: return ImGuiKey_None;
-    }
-}
-
-#ifdef IMGUI_IMPL_METAL_CPP_EXTENSIONS
-
-IMGUI_IMPL_API bool ImGui_ImplOSX_Init(void* _Nonnull view) {
-    return ImGui_ImplOSX_Init((__bridge NSView*)(view));
-}
-
-IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(void* _Nullable view) {
-    return ImGui_ImplOSX_NewFrame((__bridge NSView*)(view));
-}
-
-#endif
-
-
-bool ImGui_ImplOSX_Init(NSView* view)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_CreateBackendData();
-    io.BackendPlatformUserData = (void*)bd;
-
-    // Setup backend capabilities flags
-    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
-    //io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;          // We can honor io.WantSetMousePos requests (optional, rarely used)
-    io.BackendPlatformName = "imgui_impl_osx";
-
-    bd->Observer = [ImGuiObserver new];
-
-    // Load cursors. Some of them are undocumented.
-    bd->MouseCursorHidden = false;
-    bd->MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor];
-    bd->MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor];
-    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor];
-    bd->MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor];
-    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor];
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
-    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
-
-    // Note that imgui.cpp also include default OSX clipboard handlers which can be enabled
-    // by adding '#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS' in imconfig.h and adding '-framework ApplicationServices' to your linker command-line.
-    // Since we are already in ObjC land here, it is easy for us to add a clipboard handler using the NSPasteboard api.
-    io.SetClipboardTextFn = [](void*, const char* str) -> void
-    {
-        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
-        [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
-        [pasteboard setString:[NSString stringWithUTF8String:str] forType:NSPasteboardTypeString];
-    };
-
-    io.GetClipboardTextFn = [](void*) -> const char*
-    {
-        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
-        NSString* available = [pasteboard availableTypeFromArray: [NSArray arrayWithObject:NSPasteboardTypeString]];
-        if (![available isEqualToString:NSPasteboardTypeString])
-            return nullptr;
-
-        NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
-        if (string == nil)
-            return nullptr;
-
-        const char* string_c = (const char*)[string UTF8String];
-        size_t string_len = strlen(string_c);
-        static ImVector<char> s_clipboard;
-        s_clipboard.resize((int)string_len + 1);
-        strcpy(s_clipboard.Data, string_c);
-        return s_clipboard.Data;
-    };
-
-    [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
-                                             selector:@selector(onApplicationBecomeActive:)
-                                                 name:NSApplicationDidBecomeActiveNotification
-                                               object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:bd->Observer
-                                             selector:@selector(onApplicationBecomeInactive:)
-                                                 name:NSApplicationDidResignActiveNotification
-                                               object:nil];
-
-    // Add the NSTextInputClient to the view hierarchy,
-    // to receive keyboard events and translate them to input text.
-    bd->KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
-    bd->InputContext = [[NSTextInputContext alloc] initWithClient:bd->KeyEventResponder];
-    [view addSubview:bd->KeyEventResponder];
-    ImGui_ImplOSX_AddTrackingArea(view);
-
-    io.SetPlatformImeDataFn = [](ImGuiViewport* viewport, ImGuiPlatformImeData* data) -> void
-    {
-        ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-        if (data->WantVisible)
-        {
-            [bd->InputContext activate];
-        }
-        else
-        {
-            [bd->InputContext discardMarkedText];
-            [bd->InputContext invalidateCharacterCoordinates];
-            [bd->InputContext deactivate];
-        }
-        [bd->KeyEventResponder setImePosX:data->InputPos.x imePosY:data->InputPos.y + data->InputLineHeight];
-    };
-
-    return true;
-}
-
-void ImGui_ImplOSX_Shutdown()
-{
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-
-    bd->Observer = nullptr;
-    if (bd->Monitor != nullptr)
-    {
-        [NSEvent removeMonitor:bd->Monitor];
-        bd->Monitor = nullptr;
-    }
-
-    ImGui_ImplOSX_DestroyBackendData();
-
-    ImGuiIO& io = ImGui::GetIO();
-    io.BackendPlatformName = nullptr;
-    io.BackendPlatformUserData = nullptr;
-    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasGamepad);
-}
-
-static void ImGui_ImplOSX_UpdateMouseCursor()
-{
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
-        return;
-
-    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
-    {
-        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-        if (!bd->MouseCursorHidden)
-        {
-            bd->MouseCursorHidden = true;
-            [NSCursor hide];
-        }
-    }
-    else
-    {
-        NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow];
-        // -[NSCursor set] generates measureable overhead if called unconditionally.
-        if (desired != NSCursor.currentCursor)
-        {
-            [desired set];
-        }
-        if (bd->MouseCursorHidden)
-        {
-            bd->MouseCursorHidden = false;
-            [NSCursor unhide];
-        }
-    }
-}
-
-static void ImGui_ImplOSX_UpdateGamepads()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    memset(io.NavInputs, 0, sizeof(io.NavInputs));
-    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
-        return;
-
-#if APPLE_HAS_CONTROLLER
-    GCController* controller = GCController.current;
-#else
-    GCController* controller = GCController.controllers.firstObject;
-#endif
-    if (controller == nil || controller.extendedGamepad == nil)
-    {
-        io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
-        return;
-    }
-
-    GCExtendedGamepad* gp = controller.extendedGamepad;
-
-    // Update gamepad inputs
-    #define IM_SATURATE(V)                        (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
-    #define MAP_BUTTON(KEY_NO, BUTTON_NAME)       { io.AddKeyEvent(KEY_NO, gp.BUTTON_NAME.isPressed); }
-    #define MAP_ANALOG(KEY_NO, AXIS_NAME, V0, V1) { float vn = (float)(gp.AXIS_NAME.value - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
-    const float thumb_dead_zone = 0.0f;
-
-#if APPLE_HAS_BUTTON_OPTIONS
-    MAP_BUTTON(ImGuiKey_GamepadBack,            buttonOptions);
-#endif
-    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        buttonX);              // Xbox X, PS Square
-    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       buttonB);              // Xbox B, PS Circle
-    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          buttonY);              // Xbox Y, PS Triangle
-    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        buttonA);              // Xbox A, PS Cross
-    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        dpad.left);
-    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       dpad.right);
-    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          dpad.up);
-    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        dpad.down);
-    MAP_ANALOG(ImGuiKey_GamepadL1,              leftShoulder, 0.0f, 1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadR1,              rightShoulder, 0.0f, 1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadL2,              leftTrigger,  0.0f, 1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadR2,              rightTrigger, 0.0f, 1.0f);
-#if APPLE_HAS_THUMBSTICKS
-    MAP_BUTTON(ImGuiKey_GamepadL3,              leftThumbstickButton);
-    MAP_BUTTON(ImGuiKey_GamepadR3,              rightThumbstickButton);
-#endif
-    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      leftThumbstick.xAxis,  -thumb_dead_zone, -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     leftThumbstick.xAxis,  +thumb_dead_zone, +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        leftThumbstick.yAxis,  +thumb_dead_zone, +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      leftThumbstick.yAxis,  -thumb_dead_zone, -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      rightThumbstick.xAxis, -thumb_dead_zone, -1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     rightThumbstick.xAxis, +thumb_dead_zone, +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        rightThumbstick.yAxis, +thumb_dead_zone, +1.0f);
-    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      rightThumbstick.yAxis, -thumb_dead_zone, -1.0f);
-    #undef MAP_BUTTON
-    #undef MAP_ANALOG
-
-    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-}
-
-static void ImGui_ImplOSX_UpdateImePosWithView(NSView* view)
-{
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-    if (io.WantTextInput)
-        [bd->KeyEventResponder updateImePosWithView:view];
-}
-
-void ImGui_ImplOSX_NewFrame(NSView* view)
-{
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Setup display size
-    if (view)
-    {
-        const float dpi = (float)[view.window backingScaleFactor];
-        io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height);
-        io.DisplayFramebufferScale = ImVec2(dpi, dpi);
-    }
-
-    // Setup time step
-    if (bd->Time == 0.0)
-        bd->Time = GetMachAbsoluteTimeInSeconds();
-
-    double current_time = GetMachAbsoluteTimeInSeconds();
-    io.DeltaTime = (float)(current_time - bd->Time);
-    bd->Time = current_time;
-
-    ImGui_ImplOSX_UpdateMouseCursor();
-    ImGui_ImplOSX_UpdateGamepads();
-    ImGui_ImplOSX_UpdateImePosWithView(view);
-}
-
-// Must only be called for a mouse event, otherwise an exception occurs
-// (Note that NSEventTypeScrollWheel is considered "other input". Oddly enough an exception does not occur with it, but the value will sometimes be wrong!)
-static ImGuiMouseSource GetMouseSource(NSEvent* event)
-{
-    switch (event.subtype)
-    {
-        case NSEventSubtypeTabletPoint:
-            return ImGuiMouseSource_Pen;
-        // macOS considers input from relative touch devices (like the trackpad or Apple Magic Mouse) to be touch input.
-        // This doesn't really make sense for Dear ImGui, which expects absolute touch devices only.
-        // There does not seem to be a simple way to disambiguate things here so we consider NSEventSubtypeTouch events to always come from mice.
-        // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingTouchEvents/HandlingTouchEvents.html#//apple_ref/doc/uid/10000060i-CH13-SW24
-        //case NSEventSubtypeTouch:
-        //    return ImGuiMouseSource_TouchScreen;
-        case NSEventSubtypeMouseEvent:
-        default:
-            return ImGuiMouseSource_Mouse;
-    }
-}
-
-static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
-{
-    ImGuiIO& io = ImGui::GetIO();
-
-    if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown)
-    {
-        int button = (int)[event buttonNumber];
-        if (button >= 0 && button < ImGuiMouseButton_COUNT)
-        {
-            io.AddMouseSourceEvent(GetMouseSource(event));
-            io.AddMouseButtonEvent(button, true);
-        }
-        return io.WantCaptureMouse;
-    }
-
-    if (event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp)
-    {
-        int button = (int)[event buttonNumber];
-        if (button >= 0 && button < ImGuiMouseButton_COUNT)
-        {
-            io.AddMouseSourceEvent(GetMouseSource(event));
-            io.AddMouseButtonEvent(button, false);
-        }
-        return io.WantCaptureMouse;
-    }
-
-    if (event.type == NSEventTypeMouseMoved || event.type == NSEventTypeLeftMouseDragged || event.type == NSEventTypeRightMouseDragged || event.type == NSEventTypeOtherMouseDragged)
-    {
-        NSPoint mousePoint = event.locationInWindow;
-        if (event.window == nil)
-            mousePoint = [[view window] convertPointFromScreen:mousePoint];
-        mousePoint = [view convertPoint:mousePoint fromView:nil];
-        if ([view isFlipped])
-            mousePoint = NSMakePoint(mousePoint.x, mousePoint.y);
-        else
-            mousePoint = NSMakePoint(mousePoint.x, view.bounds.size.height - mousePoint.y);
-        io.AddMouseSourceEvent(GetMouseSource(event));
-        io.AddMousePosEvent((float)mousePoint.x, (float)mousePoint.y);
-        return io.WantCaptureMouse;
-    }
-
-    if (event.type == NSEventTypeScrollWheel)
-    {
-        // Ignore canceled events.
-        //
-        // From macOS 12.1, scrolling with two fingers and then decelerating
-        // by tapping two fingers results in two events appearing:
-        //
-        // 1. A scroll wheel NSEvent, with a phase == NSEventPhaseMayBegin, when the user taps
-        // two fingers to decelerate or stop the scroll events.
-        //
-        // 2. A scroll wheel NSEvent, with a phase == NSEventPhaseCancelled, when the user releases the
-        // two-finger tap. It is this event that sometimes contains large values for scrollingDeltaX and
-        // scrollingDeltaY. When these are added to the current x and y positions of the scrolling view,
-        // it appears to jump up or down. It can be observed in Preview, various JetBrains IDEs and here.
-        if (event.phase == NSEventPhaseCancelled)
-            return false;
-
-        double wheel_dx = 0.0;
-        double wheel_dy = 0.0;
-
-        #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
-        if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6)
-        {
-            wheel_dx = [event scrollingDeltaX];
-            wheel_dy = [event scrollingDeltaY];
-            if ([event hasPreciseScrollingDeltas])
-            {
-                wheel_dx *= 0.01;
-                wheel_dy *= 0.01;
-            }
-        }
-        else
-        #endif // MAC_OS_X_VERSION_MAX_ALLOWED
-        {
-            wheel_dx = [event deltaX] * 0.1;
-            wheel_dy = [event deltaY] * 0.1;
-        }
-        if (wheel_dx != 0.0 || wheel_dy != 0.0)
-            io.AddMouseWheelEvent((float)wheel_dx, (float)wheel_dy);
-
-        return io.WantCaptureMouse;
-    }
-
-    if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp)
-    {
-        if ([event isARepeat])
-            return io.WantCaptureKeyboard;
-
-        int key_code = (int)[event keyCode];
-        ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
-        io.AddKeyEvent(key, event.type == NSEventTypeKeyDown);
-        io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
-
-        return io.WantCaptureKeyboard;
-    }
-
-    if (event.type == NSEventTypeFlagsChanged)
-    {
-        unsigned short key_code = [event keyCode];
-        NSEventModifierFlags modifier_flags = [event modifierFlags];
-
-        io.AddKeyEvent(ImGuiMod_Shift, (modifier_flags & NSEventModifierFlagShift)   != 0);
-        io.AddKeyEvent(ImGuiMod_Ctrl,  (modifier_flags & NSEventModifierFlagControl) != 0);
-        io.AddKeyEvent(ImGuiMod_Alt,   (modifier_flags & NSEventModifierFlagOption)  != 0);
-        io.AddKeyEvent(ImGuiMod_Super, (modifier_flags & NSEventModifierFlagCommand) != 0);
-
-        ImGuiKey key = ImGui_ImplOSX_KeyCodeToImGuiKey(key_code);
-        if (key != ImGuiKey_None)
-        {
-            // macOS does not generate down/up event for modifiers. We're trying
-            // to use hardware dependent masks to extract that information.
-            // 'imgui_mask' is left as a fallback.
-            NSEventModifierFlags mask = 0;
-            switch (key)
-            {
-                case ImGuiKey_LeftCtrl:   mask = 0x0001; break;
-                case ImGuiKey_RightCtrl:  mask = 0x2000; break;
-                case ImGuiKey_LeftShift:  mask = 0x0002; break;
-                case ImGuiKey_RightShift: mask = 0x0004; break;
-                case ImGuiKey_LeftSuper:  mask = 0x0008; break;
-                case ImGuiKey_RightSuper: mask = 0x0010; break;
-                case ImGuiKey_LeftAlt:    mask = 0x0020; break;
-                case ImGuiKey_RightAlt:   mask = 0x0040; break;
-                default:
-                    return io.WantCaptureKeyboard;
-            }
-
-            NSEventModifierFlags modifier_flags = [event modifierFlags];
-            io.AddKeyEvent(key, (modifier_flags & mask) != 0);
-            io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
-        }
-
-        return io.WantCaptureKeyboard;
-    }
-
-    return false;
-}
-
-static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view)
-{
-    // If we want to receive key events, we either need to be in the responder chain of the key view,
-    // or else we can install a local monitor. The consequence of this heavy-handed approach is that
-    // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our
-    // window, we'd want to be much more careful than just ingesting the complete event stream.
-    // To match the behavior of other backends, we pass every event down to the OS.
-    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
-    if (bd->Monitor)
-        return;
-    NSEventMask eventMask = 0;
-    eventMask |= NSEventMaskMouseMoved | NSEventMaskScrollWheel;
-    eventMask |= NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
-    eventMask |= NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | NSEventMaskRightMouseDragged;
-    eventMask |= NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp | NSEventMaskOtherMouseDragged;
-    eventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
-    bd->Monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask
-                                                        handler:^NSEvent* _Nullable(NSEvent* event)
-    {
-        ImGui_ImplOSX_HandleEvent(event, view);
-        return event;
-    }];
-}
-
-//-----------------------------------------------------------------------------
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_sdl2.cpp b/engines/twp/imgui/backends/imgui_impl_sdl2.cpp
deleted file mode 100644
index 9f950bb1716..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_sdl2.cpp
+++ /dev/null
@@ -1,674 +0,0 @@
-// dear imgui: Platform Backend for SDL2
-// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
-// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
-// (Prefer SDL 2.0.5+ for full feature support.)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-//  [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
-//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
-//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
-//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
-//  2023-02-07: Implement IME handler (io.SetPlatformImeDataFn will call SDL_SetTextInputRect()/SDL_StartTextInput()).
-//  2023-02-07: *BREAKING CHANGE* Renamed this backend file from imgui_impl_sdl.cpp/.h to imgui_impl_sdl2.cpp/.h in prevision for the future release of SDL3.
-//  2023-02-02: Avoid calling SDL_SetCursor() when cursor has not changed, as the function is surprisingly costly on Mac with latest SDL (may be fixed in next SDL version).
-//  2023-02-02: Added support for SDL 2.0.18+ preciseX/preciseY mouse wheel data for smooth scrolling + Scaling X value on Emscripten (bug?). (#4019, #6096)
-//  2023-02-02: Removed SDL_MOUSEWHEEL value clamping, as values seem correct in latest Emscripten. (#4019)
-//  2023-02-01: Flipping SDL_MOUSEWHEEL 'wheel.x' value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2022-09-26: Inputs: Disable SDL 2.0.22 new "auto capture" (SDL_HINT_MOUSE_AUTO_CAPTURE) which prevents drag and drop across windows for multi-viewport support + don't capture when drag and dropping. (#5710)
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-03-22: Inputs: Fix mouse position issues when dragging outside of boundaries. SDL_CaptureMouse() erroneously still gives out LEAVE events when hovering OS decorations.
-//  2022-03-22: Inputs: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2).
-//  2022-02-04: Added SDL_Renderer* parameter to ImGui_ImplSDL2_InitForSDLRenderer(), so we can use SDL_GetRendererOutputSize() instead of SDL_GL_GetDrawableSize() when bound to a SDL_Renderer.
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
-//  2022-01-12: Update mouse inputs using SDL_MOUSEMOTION/SDL_WINDOWEVENT_LEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API.
-//  2022-01-12: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted.
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2021-08-17: Calling io.AddFocusEvent() on SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST.
-//  2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+)
-//  2021-06-29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950)
-//  2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
-//  2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).
-//  2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).
-//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
-//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
-//  2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
-//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
-//  2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
-//  2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.
-//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
-//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
-//  2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.
-//  2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.
-//  2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).
-//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
-//  2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
-//  2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).
-//  2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.
-//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
-//  2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS.
-//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
-//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
-//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_sdl2.h"
-
-// Clang warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
-#endif
-
-// SDL
-#include <SDL.h>
-#include <SDL_syswm.h>
-#if defined(__APPLE__)
-#include <TargetConditionals.h>
-#endif
-
-#if SDL_VERSION_ATLEAST(2,0,4) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    1
-#else
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    0
-#endif
-#define SDL_HAS_VULKAN                      SDL_VERSION_ATLEAST(2,0,6)
-
-// SDL Data
-struct ImGui_ImplSDL2_Data
-{
-    SDL_Window*     Window;
-    SDL_Renderer*   Renderer;
-    Uint64          Time;
-    Uint32          MouseWindowID;
-    int             MouseButtonsDown;
-    SDL_Cursor*     MouseCursors[ImGuiMouseCursor_COUNT];
-    SDL_Cursor*     LastMouseCursor;
-    int             PendingMouseLeaveFrame;
-    char*           ClipboardTextData;
-    bool            MouseCanUseGlobalState;
-
-    ImGui_ImplSDL2_Data()   { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
-// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
-static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
-}
-
-// Functions
-static const char* ImGui_ImplSDL2_GetClipboardText(void*)
-{
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-    if (bd->ClipboardTextData)
-        SDL_free(bd->ClipboardTextData);
-    bd->ClipboardTextData = SDL_GetClipboardText();
-    return bd->ClipboardTextData;
-}
-
-static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)
-{
-    SDL_SetClipboardText(text);
-}
-
-// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow().
-static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data)
-{
-    if (data->WantVisible)
-    {
-        SDL_Rect r;
-        r.x = (int)data->InputPos.x;
-        r.y = (int)data->InputPos.y;
-        r.w = 1;
-        r.h = (int)data->InputLineHeight;
-        SDL_SetTextInputRect(&r);
-    }
-}
-
-static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode)
-{
-    switch (keycode)
-    {
-        case SDLK_TAB: return ImGuiKey_Tab;
-        case SDLK_LEFT: return ImGuiKey_LeftArrow;
-        case SDLK_RIGHT: return ImGuiKey_RightArrow;
-        case SDLK_UP: return ImGuiKey_UpArrow;
-        case SDLK_DOWN: return ImGuiKey_DownArrow;
-        case SDLK_PAGEUP: return ImGuiKey_PageUp;
-        case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
-        case SDLK_HOME: return ImGuiKey_Home;
-        case SDLK_END: return ImGuiKey_End;
-        case SDLK_INSERT: return ImGuiKey_Insert;
-        case SDLK_DELETE: return ImGuiKey_Delete;
-        case SDLK_BACKSPACE: return ImGuiKey_Backspace;
-        case SDLK_SPACE: return ImGuiKey_Space;
-        case SDLK_RETURN: return ImGuiKey_Enter;
-        case SDLK_ESCAPE: return ImGuiKey_Escape;
-        case SDLK_QUOTE: return ImGuiKey_Apostrophe;
-        case SDLK_COMMA: return ImGuiKey_Comma;
-        case SDLK_MINUS: return ImGuiKey_Minus;
-        case SDLK_PERIOD: return ImGuiKey_Period;
-        case SDLK_SLASH: return ImGuiKey_Slash;
-        case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
-        case SDLK_EQUALS: return ImGuiKey_Equal;
-        case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
-        case SDLK_BACKSLASH: return ImGuiKey_Backslash;
-        case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
-        case SDLK_BACKQUOTE: return ImGuiKey_GraveAccent;
-        case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
-        case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
-        case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
-        case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
-        case SDLK_PAUSE: return ImGuiKey_Pause;
-        case SDLK_KP_0: return ImGuiKey_Keypad0;
-        case SDLK_KP_1: return ImGuiKey_Keypad1;
-        case SDLK_KP_2: return ImGuiKey_Keypad2;
-        case SDLK_KP_3: return ImGuiKey_Keypad3;
-        case SDLK_KP_4: return ImGuiKey_Keypad4;
-        case SDLK_KP_5: return ImGuiKey_Keypad5;
-        case SDLK_KP_6: return ImGuiKey_Keypad6;
-        case SDLK_KP_7: return ImGuiKey_Keypad7;
-        case SDLK_KP_8: return ImGuiKey_Keypad8;
-        case SDLK_KP_9: return ImGuiKey_Keypad9;
-        case SDLK_KP_PERIOD: return ImGuiKey_KeypadDecimal;
-        case SDLK_KP_DIVIDE: return ImGuiKey_KeypadDivide;
-        case SDLK_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
-        case SDLK_KP_MINUS: return ImGuiKey_KeypadSubtract;
-        case SDLK_KP_PLUS: return ImGuiKey_KeypadAdd;
-        case SDLK_KP_ENTER: return ImGuiKey_KeypadEnter;
-        case SDLK_KP_EQUALS: return ImGuiKey_KeypadEqual;
-        case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
-        case SDLK_LSHIFT: return ImGuiKey_LeftShift;
-        case SDLK_LALT: return ImGuiKey_LeftAlt;
-        case SDLK_LGUI: return ImGuiKey_LeftSuper;
-        case SDLK_RCTRL: return ImGuiKey_RightCtrl;
-        case SDLK_RSHIFT: return ImGuiKey_RightShift;
-        case SDLK_RALT: return ImGuiKey_RightAlt;
-        case SDLK_RGUI: return ImGuiKey_RightSuper;
-        case SDLK_APPLICATION: return ImGuiKey_Menu;
-        case SDLK_0: return ImGuiKey_0;
-        case SDLK_1: return ImGuiKey_1;
-        case SDLK_2: return ImGuiKey_2;
-        case SDLK_3: return ImGuiKey_3;
-        case SDLK_4: return ImGuiKey_4;
-        case SDLK_5: return ImGuiKey_5;
-        case SDLK_6: return ImGuiKey_6;
-        case SDLK_7: return ImGuiKey_7;
-        case SDLK_8: return ImGuiKey_8;
-        case SDLK_9: return ImGuiKey_9;
-        case SDLK_a: return ImGuiKey_A;
-        case SDLK_b: return ImGuiKey_B;
-        case SDLK_c: return ImGuiKey_C;
-        case SDLK_d: return ImGuiKey_D;
-        case SDLK_e: return ImGuiKey_E;
-        case SDLK_f: return ImGuiKey_F;
-        case SDLK_g: return ImGuiKey_G;
-        case SDLK_h: return ImGuiKey_H;
-        case SDLK_i: return ImGuiKey_I;
-        case SDLK_j: return ImGuiKey_J;
-        case SDLK_k: return ImGuiKey_K;
-        case SDLK_l: return ImGuiKey_L;
-        case SDLK_m: return ImGuiKey_M;
-        case SDLK_n: return ImGuiKey_N;
-        case SDLK_o: return ImGuiKey_O;
-        case SDLK_p: return ImGuiKey_P;
-        case SDLK_q: return ImGuiKey_Q;
-        case SDLK_r: return ImGuiKey_R;
-        case SDLK_s: return ImGuiKey_S;
-        case SDLK_t: return ImGuiKey_T;
-        case SDLK_u: return ImGuiKey_U;
-        case SDLK_v: return ImGuiKey_V;
-        case SDLK_w: return ImGuiKey_W;
-        case SDLK_x: return ImGuiKey_X;
-        case SDLK_y: return ImGuiKey_Y;
-        case SDLK_z: return ImGuiKey_Z;
-        case SDLK_F1: return ImGuiKey_F1;
-        case SDLK_F2: return ImGuiKey_F2;
-        case SDLK_F3: return ImGuiKey_F3;
-        case SDLK_F4: return ImGuiKey_F4;
-        case SDLK_F5: return ImGuiKey_F5;
-        case SDLK_F6: return ImGuiKey_F6;
-        case SDLK_F7: return ImGuiKey_F7;
-        case SDLK_F8: return ImGuiKey_F8;
-        case SDLK_F9: return ImGuiKey_F9;
-        case SDLK_F10: return ImGuiKey_F10;
-        case SDLK_F11: return ImGuiKey_F11;
-        case SDLK_F12: return ImGuiKey_F12;
-        case SDLK_F13: return ImGuiKey_F13;
-        case SDLK_F14: return ImGuiKey_F14;
-        case SDLK_F15: return ImGuiKey_F15;
-        case SDLK_F16: return ImGuiKey_F16;
-        case SDLK_F17: return ImGuiKey_F17;
-        case SDLK_F18: return ImGuiKey_F18;
-        case SDLK_F19: return ImGuiKey_F19;
-        case SDLK_F20: return ImGuiKey_F20;
-        case SDLK_F21: return ImGuiKey_F21;
-        case SDLK_F22: return ImGuiKey_F22;
-        case SDLK_F23: return ImGuiKey_F23;
-        case SDLK_F24: return ImGuiKey_F24;
-        case SDLK_AC_BACK: return ImGuiKey_AppBack;
-        case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
-    }
-    return ImGuiKey_None;
-}
-
-static void ImGui_ImplSDL2_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & KMOD_CTRL) != 0);
-    io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & KMOD_SHIFT) != 0);
-    io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & KMOD_ALT) != 0);
-    io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & KMOD_GUI) != 0);
-}
-
-// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
-// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
-// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
-// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
-// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
-bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-
-    switch (event->type)
-    {
-        case SDL_MOUSEMOTION:
-        {
-            ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
-            io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
-            return true;
-        }
-        case SDL_MOUSEWHEEL:
-        {
-            //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
-#if SDL_VERSION_ATLEAST(2,0,18) // If this fails to compile on Emscripten: update to latest Emscripten!
-            float wheel_x = -event->wheel.preciseX;
-            float wheel_y = event->wheel.preciseY;
-#else
-            float wheel_x = -(float)event->wheel.x;
-            float wheel_y = (float)event->wheel.y;
-#endif
-#ifdef __EMSCRIPTEN__
-            wheel_x /= 100.0f;
-#endif
-            io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMouseWheelEvent(wheel_x, wheel_y);
-            return true;
-        }
-        case SDL_MOUSEBUTTONDOWN:
-        case SDL_MOUSEBUTTONUP:
-        {
-            int mouse_button = -1;
-            if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
-            if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
-            if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
-            if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
-            if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
-            if (mouse_button == -1)
-                break;
-            io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMouseButtonEvent(mouse_button, (event->type == SDL_MOUSEBUTTONDOWN));
-            bd->MouseButtonsDown = (event->type == SDL_MOUSEBUTTONDOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
-            return true;
-        }
-        case SDL_TEXTINPUT:
-        {
-            io.AddInputCharactersUTF8(event->text.text);
-            return true;
-        }
-        case SDL_KEYDOWN:
-        case SDL_KEYUP:
-        {
-            ImGui_ImplSDL2_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod);
-            ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->key.keysym.sym);
-            io.AddKeyEvent(key, (event->type == SDL_KEYDOWN));
-            io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
-            return true;
-        }
-        case SDL_WINDOWEVENT:
-        {
-            // - When capturing mouse, SDL will send a bunch of conflicting LEAVE/ENTER event on every mouse move, but the final ENTER tends to be right.
-            // - However we won't get a correct LEAVE event for a captured window.
-            // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
-            //   causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
-            //   we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
-            Uint8 window_event = event->window.event;
-            if (window_event == SDL_WINDOWEVENT_ENTER)
-            {
-                bd->MouseWindowID = event->window.windowID;
-                bd->PendingMouseLeaveFrame = 0;
-            }
-            if (window_event == SDL_WINDOWEVENT_LEAVE)
-                bd->PendingMouseLeaveFrame = ImGui::GetFrameCount() + 1;
-            if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED)
-                io.AddFocusEvent(true);
-            else if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
-                io.AddFocusEvent(false);
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
-
-    // Check and store if we are on a SDL backend that supports global mouse position
-    // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
-    bool mouse_can_use_global_state = false;
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-    const char* sdl_backend = SDL_GetCurrentVideoDriver();
-    const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
-    for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
-        if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
-            mouse_can_use_global_state = true;
-#endif
-
-    // Setup backend capabilities flags
-    ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)();
-    io.BackendPlatformUserData = (void*)bd;
-    io.BackendPlatformName = "imgui_impl_sdl2";
-    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)
-    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;        // We can honor io.WantSetMousePos requests (optional, rarely used)
-
-    bd->Window = window;
-    bd->Renderer = renderer;
-    bd->MouseCanUseGlobalState = mouse_can_use_global_state;
-
-    io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
-    io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
-    io.ClipboardUserData = nullptr;
-    io.SetPlatformImeDataFn = ImGui_ImplSDL2_SetPlatformImeData;
-
-    // Load mouse cursors
-    bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
-    bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
-    bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
-    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
-
-    // Set platform dependent data in viewport
-    // Our mouse update function expect PlatformHandle to be filled for the main viewport
-    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
-    main_viewport->PlatformHandleRaw = nullptr;
-    SDL_SysWMinfo info;
-    SDL_VERSION(&info.version);
-    if (SDL_GetWindowWMInfo(window, &info))
-    {
-#if defined(SDL_VIDEO_DRIVER_WINDOWS)
-        main_viewport->PlatformHandleRaw = (void*)info.info.win.window;
-#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
-        main_viewport->PlatformHandleRaw = (void*)info.info.cocoa.window;
-#endif
-    }
-
-    // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
-    // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
-    // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
-    // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
-    // you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
-#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
-    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
-#endif
-
-    // From 2.0.18: Enable native IME.
-    // IMPORTANT: This is used at the time of SDL_CreateWindow() so this will only affects secondary windows, if any.
-    // For the main window to be affected, your application needs to call this manually before calling SDL_CreateWindow().
-#ifdef SDL_HINT_IME_SHOW_UI
-    SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
-#endif
-
-    // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
-#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
-    SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
-#endif
-
-    return true;
-}
-
-bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
-{
-    IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
-    return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)
-{
-#if !SDL_HAS_VULKAN
-    IM_ASSERT(0 && "Unsupported");
-#endif
-    return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)
-{
-#if !defined(_WIN32)
-    IM_ASSERT(0 && "Unsupported");
-#endif
-    return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window)
-{
-    return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
-{
-    return ImGui_ImplSDL2_Init(window, renderer);
-}
-
-bool ImGui_ImplSDL2_InitForOther(SDL_Window* window)
-{
-    return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-void ImGui_ImplSDL2_Shutdown()
-{
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    if (bd->ClipboardTextData)
-        SDL_free(bd->ClipboardTextData);
-    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
-        SDL_FreeCursor(bd->MouseCursors[cursor_n]);
-    bd->LastMouseCursor = nullptr;
-
-    io.BackendPlatformName = nullptr;
-    io.BackendPlatformUserData = nullptr;
-    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
-    IM_DELETE(bd);
-}
-
-static void ImGui_ImplSDL2_UpdateMouseData()
-{
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-
-    // We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below)
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-    // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
-    SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
-    SDL_Window* focused_window = SDL_GetKeyboardFocus();
-    const bool is_app_focused = (bd->Window == focused_window);
-#else
-    const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
-#endif
-    if (is_app_focused)
-    {
-        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
-        if (io.WantSetMousePos)
-            SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y);
-
-        // (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured)
-        if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
-        {
-            int window_x, window_y, mouse_x_global, mouse_y_global;
-            SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
-            SDL_GetWindowPosition(bd->Window, &window_x, &window_y);
-            io.AddMousePosEvent((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y));
-        }
-    }
-}
-
-static void ImGui_ImplSDL2_UpdateMouseCursor()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
-        return;
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-
-    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
-    {
-        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-        SDL_ShowCursor(SDL_FALSE);
-    }
-    else
-    {
-        // Show OS mouse cursor
-        SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
-        if (bd->LastMouseCursor != expected_cursor)
-        {
-            SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
-            bd->LastMouseCursor = expected_cursor;
-        }
-        SDL_ShowCursor(SDL_TRUE);
-    }
-}
-
-static void ImGui_ImplSDL2_UpdateGamepads()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
-        return;
-
-    // Get gamepad
-    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
-    SDL_GameController* game_controller = SDL_GameControllerOpen(0);
-    if (!game_controller)
-        return;
-    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-
-    // Update gamepad inputs
-    #define IM_SATURATE(V)                      (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
-    #define MAP_BUTTON(KEY_NO, BUTTON_NO)       { io.AddKeyEvent(KEY_NO, SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0); }
-    #define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
-    const int thumb_dead_zone = 8000;           // SDL_gamecontroller.h suggests using this value.
-    MAP_BUTTON(ImGuiKey_GamepadStart,           SDL_CONTROLLER_BUTTON_START);
-    MAP_BUTTON(ImGuiKey_GamepadBack,            SDL_CONTROLLER_BUTTON_BACK);
-    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        SDL_CONTROLLER_BUTTON_X);              // Xbox X, PS Square
-    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       SDL_CONTROLLER_BUTTON_B);              // Xbox B, PS Circle
-    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          SDL_CONTROLLER_BUTTON_Y);              // Xbox Y, PS Triangle
-    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        SDL_CONTROLLER_BUTTON_A);              // Xbox A, PS Cross
-    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        SDL_CONTROLLER_BUTTON_DPAD_LEFT);
-    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
-    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          SDL_CONTROLLER_BUTTON_DPAD_UP);
-    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        SDL_CONTROLLER_BUTTON_DPAD_DOWN);
-    MAP_BUTTON(ImGuiKey_GamepadL1,              SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
-    MAP_BUTTON(ImGuiKey_GamepadR1,              SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
-    MAP_ANALOG(ImGuiKey_GamepadL2,              SDL_CONTROLLER_AXIS_TRIGGERLEFT,  0.0f, 32767);
-    MAP_ANALOG(ImGuiKey_GamepadR2,              SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767);
-    MAP_BUTTON(ImGuiKey_GamepadL3,              SDL_CONTROLLER_BUTTON_LEFTSTICK);
-    MAP_BUTTON(ImGuiKey_GamepadR3,              SDL_CONTROLLER_BUTTON_RIGHTSTICK);
-    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      SDL_CONTROLLER_AXIS_LEFTX,  -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     SDL_CONTROLLER_AXIS_LEFTX,  +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        SDL_CONTROLLER_AXIS_LEFTY,  -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      SDL_CONTROLLER_AXIS_LEFTY,  +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      SDL_CONTROLLER_AXIS_RIGHTX, -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     SDL_CONTROLLER_AXIS_RIGHTX, +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        SDL_CONTROLLER_AXIS_RIGHTY, -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767);
-    #undef MAP_BUTTON
-    #undef MAP_ANALOG
-}
-
-void ImGui_ImplSDL2_NewFrame()
-{
-    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL2_Init()?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Setup display size (every frame to accommodate for window resizing)
-    int w, h;
-    int display_w, display_h;
-    SDL_GetWindowSize(bd->Window, &w, &h);
-    if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
-        w = h = 0;
-    if (bd->Renderer != nullptr)
-        SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h);
-    else
-        SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h);
-    io.DisplaySize = ImVec2((float)w, (float)h);
-    if (w > 0 && h > 0)
-        io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
-
-    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
-    // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
-    static Uint64 frequency = SDL_GetPerformanceFrequency();
-    Uint64 current_time = SDL_GetPerformanceCounter();
-    if (current_time <= bd->Time)
-        current_time = bd->Time + 1;
-    io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
-    bd->Time = current_time;
-
-    if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
-    {
-        bd->MouseWindowID = 0;
-        bd->PendingMouseLeaveFrame = 0;
-        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
-    }
-
-    ImGui_ImplSDL2_UpdateMouseData();
-    ImGui_ImplSDL2_UpdateMouseCursor();
-
-    // Update game controllers (if enabled and available)
-    ImGui_ImplSDL2_UpdateGamepads();
-}
-
-//-----------------------------------------------------------------------------
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_sdl2.h b/engines/twp/imgui/backends/imgui_impl_sdl2.h
deleted file mode 100644
index dd5e047e7a0..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_sdl2.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// dear imgui: Platform Backend for SDL2
-// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
-// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-//  [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-#pragma once
-#include "imgui.h"      // IMGUI_IMPL_API
-#ifndef IMGUI_DISABLE
-
-struct SDL_Window;
-struct SDL_Renderer;
-typedef union SDL_Event SDL_Event;
-
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForVulkan(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForD3D(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForMetal(SDL_Window* window);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOther(SDL_Window* window);
-IMGUI_IMPL_API void     ImGui_ImplSDL2_Shutdown();
-IMGUI_IMPL_API void     ImGui_ImplSDL2_NewFrame();
-IMGUI_IMPL_API bool     ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);
-
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_sdl3.cpp b/engines/twp/imgui/backends/imgui_impl_sdl3.cpp
deleted file mode 100644
index d41a5e46100..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_sdl3.cpp
+++ /dev/null
@@ -1,618 +0,0 @@
-// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
-// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
-// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
-// (IMPORTANT: SDL 3.0.0 is NOT YET RELEASED. IT IS POSSIBLE THAT ITS SPECS/API WILL CHANGE BEFORE RELEASE)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-// Missing features:
-//  [x] Platform: Basic IME support. Position somehow broken in SDL3 + app needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-11-13: Updated for recent SDL3 API changes.
-//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
-//  2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
-//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
-//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
-//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
-//  2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
-
-#include "imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_sdl3.h"
-
-// Clang warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
-#endif
-
-// SDL
-#include <SDL3/SDL.h>
-#if defined(__APPLE__)
-#include <TargetConditionals.h>
-#endif
-#ifdef _WIN32
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <windows.h>
-#endif
-
-#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    1
-#else
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    0
-#endif
-
-// SDL Data
-struct ImGui_ImplSDL3_Data
-{
-    SDL_Window*     Window;
-    SDL_Renderer*   Renderer;
-    Uint64          Time;
-    Uint32          MouseWindowID;
-    int             MouseButtonsDown;
-    SDL_Cursor*     MouseCursors[ImGuiMouseCursor_COUNT];
-    SDL_Cursor*     LastMouseCursor;
-    int             PendingMouseLeaveFrame;
-    char*           ClipboardTextData;
-    bool            MouseCanUseGlobalState;
-
-    ImGui_ImplSDL3_Data()   { memset((void*)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
-// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
-static ImGui_ImplSDL3_Data* ImGui_ImplSDL3_GetBackendData()
-{
-    return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
-}
-
-// Functions
-static const char* ImGui_ImplSDL3_GetClipboardText(void*)
-{
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-    if (bd->ClipboardTextData)
-        SDL_free(bd->ClipboardTextData);
-    bd->ClipboardTextData = SDL_GetClipboardText();
-    return bd->ClipboardTextData;
-}
-
-static void ImGui_ImplSDL3_SetClipboardText(void*, const char* text)
-{
-    SDL_SetClipboardText(text);
-}
-
-static void ImGui_ImplSDL3_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data)
-{
-    if (data->WantVisible)
-    {
-        SDL_Rect r;
-        r.x = (int)data->InputPos.x;
-        r.y = (int)data->InputPos.y;
-        r.w = 1;
-        r.h = (int)data->InputLineHeight;
-        SDL_SetTextInputRect(&r);
-    }
-}
-
-static ImGuiKey ImGui_ImplSDL3_KeycodeToImGuiKey(int keycode)
-{
-    switch (keycode)
-    {
-        case SDLK_TAB: return ImGuiKey_Tab;
-        case SDLK_LEFT: return ImGuiKey_LeftArrow;
-        case SDLK_RIGHT: return ImGuiKey_RightArrow;
-        case SDLK_UP: return ImGuiKey_UpArrow;
-        case SDLK_DOWN: return ImGuiKey_DownArrow;
-        case SDLK_PAGEUP: return ImGuiKey_PageUp;
-        case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
-        case SDLK_HOME: return ImGuiKey_Home;
-        case SDLK_END: return ImGuiKey_End;
-        case SDLK_INSERT: return ImGuiKey_Insert;
-        case SDLK_DELETE: return ImGuiKey_Delete;
-        case SDLK_BACKSPACE: return ImGuiKey_Backspace;
-        case SDLK_SPACE: return ImGuiKey_Space;
-        case SDLK_RETURN: return ImGuiKey_Enter;
-        case SDLK_ESCAPE: return ImGuiKey_Escape;
-        case SDLK_QUOTE: return ImGuiKey_Apostrophe;
-        case SDLK_COMMA: return ImGuiKey_Comma;
-        case SDLK_MINUS: return ImGuiKey_Minus;
-        case SDLK_PERIOD: return ImGuiKey_Period;
-        case SDLK_SLASH: return ImGuiKey_Slash;
-        case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
-        case SDLK_EQUALS: return ImGuiKey_Equal;
-        case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
-        case SDLK_BACKSLASH: return ImGuiKey_Backslash;
-        case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
-        case SDLK_BACKQUOTE: return ImGuiKey_GraveAccent;
-        case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
-        case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
-        case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
-        case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
-        case SDLK_PAUSE: return ImGuiKey_Pause;
-        case SDLK_KP_0: return ImGuiKey_Keypad0;
-        case SDLK_KP_1: return ImGuiKey_Keypad1;
-        case SDLK_KP_2: return ImGuiKey_Keypad2;
-        case SDLK_KP_3: return ImGuiKey_Keypad3;
-        case SDLK_KP_4: return ImGuiKey_Keypad4;
-        case SDLK_KP_5: return ImGuiKey_Keypad5;
-        case SDLK_KP_6: return ImGuiKey_Keypad6;
-        case SDLK_KP_7: return ImGuiKey_Keypad7;
-        case SDLK_KP_8: return ImGuiKey_Keypad8;
-        case SDLK_KP_9: return ImGuiKey_Keypad9;
-        case SDLK_KP_PERIOD: return ImGuiKey_KeypadDecimal;
-        case SDLK_KP_DIVIDE: return ImGuiKey_KeypadDivide;
-        case SDLK_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
-        case SDLK_KP_MINUS: return ImGuiKey_KeypadSubtract;
-        case SDLK_KP_PLUS: return ImGuiKey_KeypadAdd;
-        case SDLK_KP_ENTER: return ImGuiKey_KeypadEnter;
-        case SDLK_KP_EQUALS: return ImGuiKey_KeypadEqual;
-        case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
-        case SDLK_LSHIFT: return ImGuiKey_LeftShift;
-        case SDLK_LALT: return ImGuiKey_LeftAlt;
-        case SDLK_LGUI: return ImGuiKey_LeftSuper;
-        case SDLK_RCTRL: return ImGuiKey_RightCtrl;
-        case SDLK_RSHIFT: return ImGuiKey_RightShift;
-        case SDLK_RALT: return ImGuiKey_RightAlt;
-        case SDLK_RGUI: return ImGuiKey_RightSuper;
-        case SDLK_APPLICATION: return ImGuiKey_Menu;
-        case SDLK_0: return ImGuiKey_0;
-        case SDLK_1: return ImGuiKey_1;
-        case SDLK_2: return ImGuiKey_2;
-        case SDLK_3: return ImGuiKey_3;
-        case SDLK_4: return ImGuiKey_4;
-        case SDLK_5: return ImGuiKey_5;
-        case SDLK_6: return ImGuiKey_6;
-        case SDLK_7: return ImGuiKey_7;
-        case SDLK_8: return ImGuiKey_8;
-        case SDLK_9: return ImGuiKey_9;
-        case SDLK_a: return ImGuiKey_A;
-        case SDLK_b: return ImGuiKey_B;
-        case SDLK_c: return ImGuiKey_C;
-        case SDLK_d: return ImGuiKey_D;
-        case SDLK_e: return ImGuiKey_E;
-        case SDLK_f: return ImGuiKey_F;
-        case SDLK_g: return ImGuiKey_G;
-        case SDLK_h: return ImGuiKey_H;
-        case SDLK_i: return ImGuiKey_I;
-        case SDLK_j: return ImGuiKey_J;
-        case SDLK_k: return ImGuiKey_K;
-        case SDLK_l: return ImGuiKey_L;
-        case SDLK_m: return ImGuiKey_M;
-        case SDLK_n: return ImGuiKey_N;
-        case SDLK_o: return ImGuiKey_O;
-        case SDLK_p: return ImGuiKey_P;
-        case SDLK_q: return ImGuiKey_Q;
-        case SDLK_r: return ImGuiKey_R;
-        case SDLK_s: return ImGuiKey_S;
-        case SDLK_t: return ImGuiKey_T;
-        case SDLK_u: return ImGuiKey_U;
-        case SDLK_v: return ImGuiKey_V;
-        case SDLK_w: return ImGuiKey_W;
-        case SDLK_x: return ImGuiKey_X;
-        case SDLK_y: return ImGuiKey_Y;
-        case SDLK_z: return ImGuiKey_Z;
-        case SDLK_F1: return ImGuiKey_F1;
-        case SDLK_F2: return ImGuiKey_F2;
-        case SDLK_F3: return ImGuiKey_F3;
-        case SDLK_F4: return ImGuiKey_F4;
-        case SDLK_F5: return ImGuiKey_F5;
-        case SDLK_F6: return ImGuiKey_F6;
-        case SDLK_F7: return ImGuiKey_F7;
-        case SDLK_F8: return ImGuiKey_F8;
-        case SDLK_F9: return ImGuiKey_F9;
-        case SDLK_F10: return ImGuiKey_F10;
-        case SDLK_F11: return ImGuiKey_F11;
-        case SDLK_F12: return ImGuiKey_F12;
-        case SDLK_F13: return ImGuiKey_F13;
-        case SDLK_F14: return ImGuiKey_F14;
-        case SDLK_F15: return ImGuiKey_F15;
-        case SDLK_F16: return ImGuiKey_F16;
-        case SDLK_F17: return ImGuiKey_F17;
-        case SDLK_F18: return ImGuiKey_F18;
-        case SDLK_F19: return ImGuiKey_F19;
-        case SDLK_F20: return ImGuiKey_F20;
-        case SDLK_F21: return ImGuiKey_F21;
-        case SDLK_F22: return ImGuiKey_F22;
-        case SDLK_F23: return ImGuiKey_F23;
-        case SDLK_F24: return ImGuiKey_F24;
-        case SDLK_AC_BACK: return ImGuiKey_AppBack;
-        case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
-    }
-    return ImGuiKey_None;
-}
-
-static void ImGui_ImplSDL3_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
-    io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
-    io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
-    io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
-}
-
-// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
-// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
-// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
-// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
-// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
-bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-
-    switch (event->type)
-    {
-        case SDL_EVENT_MOUSE_MOTION:
-        {
-            ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
-            io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
-            return true;
-        }
-        case SDL_EVENT_MOUSE_WHEEL:
-        {
-            //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
-            float wheel_x = -event->wheel.x;
-            float wheel_y = event->wheel.y;
-    #ifdef __EMSCRIPTEN__
-            wheel_x /= 100.0f;
-    #endif
-            io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMouseWheelEvent(wheel_x, wheel_y);
-            return true;
-        }
-        case SDL_EVENT_MOUSE_BUTTON_DOWN:
-        case SDL_EVENT_MOUSE_BUTTON_UP:
-        {
-            int mouse_button = -1;
-            if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
-            if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
-            if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
-            if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
-            if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
-            if (mouse_button == -1)
-                break;
-            io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
-            io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN));
-            bd->MouseButtonsDown = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
-            return true;
-        }
-        case SDL_EVENT_TEXT_INPUT:
-        {
-            io.AddInputCharactersUTF8(event->text.text);
-            return true;
-        }
-        case SDL_EVENT_KEY_DOWN:
-        case SDL_EVENT_KEY_UP:
-        {
-            ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod);
-            ImGuiKey key = ImGui_ImplSDL3_KeycodeToImGuiKey(event->key.keysym.sym);
-            io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
-            io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
-            return true;
-        }
-        case SDL_EVENT_WINDOW_MOUSE_ENTER:
-        {
-            bd->MouseWindowID = event->window.windowID;
-            bd->PendingMouseLeaveFrame = 0;
-            return true;
-        }
-        // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
-        //   causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
-        //   we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
-        // FIXME: Unconfirmed whether this is still needed with SDL3.
-        case SDL_EVENT_WINDOW_MOUSE_LEAVE:
-        {
-            bd->PendingMouseLeaveFrame = ImGui::GetFrameCount() + 1;
-            return true;
-        }
-        case SDL_EVENT_WINDOW_FOCUS_GAINED:
-            io.AddFocusEvent(true);
-            return true;
-        case SDL_EVENT_WINDOW_FOCUS_LOST:
-            io.AddFocusEvent(false);
-            return true;
-    }
-    return false;
-}
-
-static void ImGui_ImplSDL3_SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window)
-{
-    IM_UNUSED(window);
-    viewport->PlatformHandleRaw = nullptr;
-#if defined(__WIN32__) && !defined(__WINRT__)
-    viewport->PlatformHandleRaw = (HWND)SDL_GetProperty(SDL_GetWindowProperties(window), "SDL.window.win32.hwnd", nullptr);
-#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
-    viewport->PlatformHandleRaw = (void*)SDL_GetProperty(SDL_GetWindowProperties(window), "SDL.window.cocoa.window", nullptr);
-#endif
-}
-
-static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
-{
-    ImGuiIO& io = ImGui::GetIO();
-    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
-    IM_UNUSED(sdl_gl_context); // Unused in this branch
-
-    // Check and store if we are on a SDL backend that supports global mouse position
-    // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
-    bool mouse_can_use_global_state = false;
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-    const char* sdl_backend = SDL_GetCurrentVideoDriver();
-    const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
-    for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
-        if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
-            mouse_can_use_global_state = true;
-#endif
-
-    // Setup backend capabilities flags
-    ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)();
-    io.BackendPlatformUserData = (void*)bd;
-    io.BackendPlatformName = "imgui_impl_sdl3";
-    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
-    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;            // We can honor io.WantSetMousePos requests (optional, rarely used)
-
-    bd->Window = window;
-    bd->Renderer = renderer;
-    bd->MouseCanUseGlobalState = mouse_can_use_global_state;
-
-    io.SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
-    io.GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
-    io.ClipboardUserData = nullptr;
-    io.SetPlatformImeDataFn = ImGui_ImplSDL3_SetPlatformImeData;
-
-    // Load mouse cursors
-    bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
-    bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
-    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
-    bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
-    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
-
-    // Set platform dependent data in viewport
-    // Our mouse update function expect PlatformHandle to be filled for the main viewport
-    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
-    ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);
-
-    // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
-    // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
-    // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
-    // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
-    // you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
-#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
-    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
-#endif
-
-    // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
-#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
-    SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
-#endif
-
-    return true;
-}
-
-bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
-{
-    IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
-    return ImGui_ImplSDL3_Init(window, nullptr, sdl_gl_context);
-}
-
-bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window)
-{
-    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
-}
-
-bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
-{
-#if !defined(_WIN32)
-    IM_ASSERT(0 && "Unsupported");
-#endif
-    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
-}
-
-bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window)
-{
-    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
-}
-
-bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
-{
-    return ImGui_ImplSDL3_Init(window, renderer, nullptr);
-}
-
-bool ImGui_ImplSDL3_InitForOther(SDL_Window* window)
-{
-    return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
-}
-
-void ImGui_ImplSDL3_Shutdown()
-{
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    if (bd->ClipboardTextData)
-        SDL_free(bd->ClipboardTextData);
-    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
-        SDL_DestroyCursor(bd->MouseCursors[cursor_n]);
-    bd->LastMouseCursor = nullptr;
-
-    io.BackendPlatformName = nullptr;
-    io.BackendPlatformUserData = nullptr;
-    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
-    IM_DELETE(bd);
-}
-
-static void ImGui_ImplSDL3_UpdateMouseData()
-{
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-    ImGuiIO& io = ImGui::GetIO();
-
-    // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-    // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
-    SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
-    SDL_Window* focused_window = SDL_GetKeyboardFocus();
-    const bool is_app_focused = (bd->Window == focused_window);
-#else
-    SDL_Window* focused_window = bd->Window;
-    const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
-#endif
-    if (is_app_focused)
-    {
-        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
-        if (io.WantSetMousePos)
-            SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y);
-
-        // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
-        if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
-        {
-            // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
-            float mouse_x_global, mouse_y_global;
-            int window_x, window_y;
-            SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
-            SDL_GetWindowPosition(focused_window, &window_x, &window_y);
-            io.AddMousePosEvent(mouse_x_global - window_x, mouse_y_global - window_y);
-        }
-    }
-}
-
-static void ImGui_ImplSDL3_UpdateMouseCursor()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
-        return;
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-
-    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
-    {
-        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-        SDL_HideCursor();
-    }
-    else
-    {
-        // Show OS mouse cursor
-        SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
-        if (bd->LastMouseCursor != expected_cursor)
-        {
-            SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
-            bd->LastMouseCursor = expected_cursor;
-        }
-        SDL_ShowCursor();
-    }
-}
-
-static void ImGui_ImplSDL3_UpdateGamepads()
-{
-    ImGuiIO& io = ImGui::GetIO();
-    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
-        return;
-
-    // Get gamepad
-    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
-    SDL_Gamepad* gamepad = SDL_OpenGamepad(0);
-    if (!gamepad)
-        return;
-    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-
-    // Update gamepad inputs
-    #define IM_SATURATE(V)                      (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
-    #define MAP_BUTTON(KEY_NO, BUTTON_NO)       { io.AddKeyEvent(KEY_NO, SDL_GetGamepadButton(gamepad, BUTTON_NO) != 0); }
-    #define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GetGamepadAxis(gamepad, AXIS_NO) - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
-    const int thumb_dead_zone = 8000;           // SDL_gamecontroller.h suggests using this value.
-    MAP_BUTTON(ImGuiKey_GamepadStart,           SDL_GAMEPAD_BUTTON_START);
-    MAP_BUTTON(ImGuiKey_GamepadBack,            SDL_GAMEPAD_BUTTON_BACK);
-    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        SDL_GAMEPAD_BUTTON_WEST);           // Xbox X, PS Square
-    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       SDL_GAMEPAD_BUTTON_EAST);           // Xbox B, PS Circle
-    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          SDL_GAMEPAD_BUTTON_NORTH);          // Xbox Y, PS Triangle
-    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        SDL_GAMEPAD_BUTTON_SOUTH);          // Xbox A, PS Cross
-    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        SDL_GAMEPAD_BUTTON_DPAD_LEFT);
-    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
-    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          SDL_GAMEPAD_BUTTON_DPAD_UP);
-    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        SDL_GAMEPAD_BUTTON_DPAD_DOWN);
-    MAP_BUTTON(ImGuiKey_GamepadL1,              SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
-    MAP_BUTTON(ImGuiKey_GamepadR1,              SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
-    MAP_ANALOG(ImGuiKey_GamepadL2,              SDL_GAMEPAD_AXIS_LEFT_TRIGGER,  0.0f, 32767);
-    MAP_ANALOG(ImGuiKey_GamepadR2,              SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767);
-    MAP_BUTTON(ImGuiKey_GamepadL3,              SDL_GAMEPAD_BUTTON_LEFT_STICK);
-    MAP_BUTTON(ImGuiKey_GamepadR3,              SDL_GAMEPAD_BUTTON_RIGHT_STICK);
-    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      SDL_GAMEPAD_AXIS_LEFTX,  -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     SDL_GAMEPAD_AXIS_LEFTX,  +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        SDL_GAMEPAD_AXIS_LEFTY,  -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      SDL_GAMEPAD_AXIS_LEFTY,  +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      SDL_GAMEPAD_AXIS_RIGHTX, -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     SDL_GAMEPAD_AXIS_RIGHTX, +thumb_dead_zone, +32767);
-    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, -32768);
-    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      SDL_GAMEPAD_AXIS_RIGHTY, +thumb_dead_zone, +32767);
-    #undef MAP_BUTTON
-    #undef MAP_ANALOG
-}
-
-void ImGui_ImplSDL3_NewFrame()
-{
-    ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
-    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL3_Init()?");
-    ImGuiIO& io = ImGui::GetIO();
-
-    // Setup display size (every frame to accommodate for window resizing)
-    int w, h;
-    int display_w, display_h;
-    SDL_GetWindowSize(bd->Window, &w, &h);
-    if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
-        w = h = 0;
-    SDL_GetWindowSizeInPixels(bd->Window, &display_w, &display_h);
-    io.DisplaySize = ImVec2((float)w, (float)h);
-    if (w > 0 && h > 0)
-        io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
-
-    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
-    // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
-    static Uint64 frequency = SDL_GetPerformanceFrequency();
-    Uint64 current_time = SDL_GetPerformanceCounter();
-    if (current_time <= bd->Time)
-        current_time = bd->Time + 1;
-    io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
-    bd->Time = current_time;
-
-    if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
-    {
-        bd->MouseWindowID = 0;
-        bd->PendingMouseLeaveFrame = 0;
-        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
-    }
-
-    ImGui_ImplSDL3_UpdateMouseData();
-    ImGui_ImplSDL3_UpdateMouseCursor();
-
-    // Update game controllers (if enabled and available)
-    ImGui_ImplSDL3_UpdateGamepads();
-}
-
-//-----------------------------------------------------------------------------
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/engines/twp/imgui/backends/imgui_impl_sdl3.h b/engines/twp/imgui/backends/imgui_impl_sdl3.h
deleted file mode 100644
index 9baa7e68c49..00000000000
--- a/engines/twp/imgui/backends/imgui_impl_sdl3.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
-// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
-// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
-// (IMPORTANT: SDL 3.0.0 is NOT YET RELEASED. IT IS POSSIBLE THAT ITS SPECS/API WILL CHANGE BEFORE RELEASE)


Commit: 6cb0874af26ee6de9a480586eb510e5dbf1aaaee
    https://github.com/scummvm/scummvm/commit/6cb0874af26ee6de9a480586eb510e5dbf1aaaee
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove the preferences class and share getKey

Changed paths:
  R engines/twp/prefs.cpp
  R engines/twp/prefs.h
    engines/twp/genlib.cpp
    engines/twp/module.mk
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/spritesheet.cpp
    engines/twp/spritesheet.h
    engines/twp/twp.h


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 9423c552f3a..3a6f8925bcd 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -341,10 +341,9 @@ static SQInteger getUserPref(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, key))) {
 		return sq_throwerror(v, "failed to get key");
 	}
-	if (g_twp->getPrefs().hasPrefs(key)) {
-		sqpush(v, g_twp->getPrefs().prefsAsJson(key));
-		return 1;
-	}
+	// TODO: use ConfMan to search for key
+
+	// if configuration
 	int numArgs = sq_gettop(v);
 	if (numArgs == 3) {
 		HSQOBJECT obj;
@@ -474,7 +473,7 @@ static SQInteger loadArray(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, orgFilename)))
 		return sq_throwerror(v, "failed to get filename");
 	debugC(kDebugGenScript, "loadArray: %s", orgFilename);
-	Common::String filename = g_twp->getPrefs().getKey(orgFilename);
+	Common::String filename = ResManager::getKey(orgFilename);
 	GGPackEntryReader entry;
 	entry.open(g_twp->_pack, g_twp->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
 	sq_newarray(v, 0);
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index fa00303ef97..d47e4ce4453 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -23,7 +23,6 @@ MODULE_OBJS = \
 	motor.o \
 	object.o \
 	objlib.o \
-	prefs.o \
 	resmanager.o \
 	rectf.o \
 	room.o \
diff --git a/engines/twp/prefs.cpp b/engines/twp/prefs.cpp
deleted file mode 100644
index d4c77a1b827..00000000000
--- a/engines/twp/prefs.cpp
+++ /dev/null
@@ -1,102 +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 "common/config-manager.h"
-#include "twp/prefs.h"
-#include "twp/util.h"
-
-namespace Twp {
-
-Preferences::Preferences() {
-	_node = new Common::JSONValue(Common::JSONObject());
-}
-
-Common::String Preferences::prefs(const Common::String name, const Common::String &def) const {
-	const Common::JSONObject &jObj = _node->asObject();
-	return jObj.contains(name) ? jObj[name]->asString() : def;
-}
-
-float Preferences::prefs(const Common::String &name, float def) const {
-	const Common::JSONObject &jObj = _node->asObject();
-	return jObj.contains(name) ? jObj[name]->asNumber() : def;
-}
-
-bool Preferences::prefs(const Common::String &name, bool def) const {
-	const Common::JSONObject &jObj = _node->asObject();
-	return jObj.contains(name) ? jObj[name]->asIntegerNumber() != 0 : def;
-}
-
-int Preferences::prefs(const Common::String &name, int def) const {
-	const Common::JSONObject &jObj = _node->asObject();
-	return jObj.contains(name) ? jObj[name]->asIntegerNumber() : def;
-}
-
-void Preferences::setPrefs(const Common::String &name, const Common::String &value) {
-	Common::JSONObject jObj = _node->asObject();
-	jObj[name] = new Common::JSONValue(value);
-	delete _node;
-	_node = new Common::JSONValue(jObj);
-	savePrefs();
-}
-
-void Preferences::setPrefs(const Common::String &name, float value) {
-	Common::JSONObject jObj = _node->asObject();
-	jObj[name] = new Common::JSONValue(value);
-	delete _node;
-	_node = new Common::JSONValue(jObj);
-	savePrefs();
-}
-
-void Preferences::setPrefs(const Common::String &name, int value) {
-	Common::JSONObject jObj = _node->asObject();
-	jObj[name] = new Common::JSONValue((long long int)value);
-	delete _node;
-	_node = new Common::JSONValue(jObj);
-	savePrefs();
-}
-
-void Preferences::setPrefs(const Common::String &name, bool value) {
-	Common::JSONObject jObj = _node->asObject();
-	jObj[name] = new Common::JSONValue((long long int)(value ? 1 : 0));
-	delete _node;
-	_node = new Common::JSONValue(jObj);
-	savePrefs();
-}
-
-bool Preferences::hasPrefs(const Common::String &name) {
-	return _node->asObject().contains(name);
-}
-
-Common::JSONValue *Preferences::prefsAsJson(const Common::String &name) {
-	return _node->asObject()[name];
-}
-
-void Preferences::savePrefs() {
-	// TODO: savePrefs()
-}
-
-Common::String Preferences::getKey(const Common::String &path) {
-	Common::String t(path);
-	replace(t, "_en", "_" + ConfMan.get("language"));
-	return t;
-}
-
-} // namespace Twp
diff --git a/engines/twp/prefs.h b/engines/twp/prefs.h
deleted file mode 100644
index fcc33c60277..00000000000
--- a/engines/twp/prefs.h
+++ /dev/null
@@ -1,62 +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 TWP_PREFS_H
-#define TWP_PREFS_H
-
-#include "common/formats/json.h"
-
-namespace Twp {
-
-struct TempPref {
-	float gameSpeedFactor = 1.f;
-	bool forceTalkieText = false;
-};
-
-class Preferences {
-public:
-	Preferences();
-
-	bool hasPrefs(const Common::String &name);
-	Common::JSONValue *prefsAsJson(const Common::String &name);
-
-	Common::String prefs(const Common::String name, const Common::String &def) const;
-	float prefs(const Common::String &name, float def) const;
-	bool prefs(const Common::String &name, bool def) const;
-	int prefs(const Common::String &name, int def) const;
-
-	void setPrefs(const Common::String &name, const Common::String &value);
-	void setPrefs(const Common::String &name, float value);
-	void setPrefs(const Common::String &name, int value);
-	void setPrefs(const Common::String &name, bool value);
-
-	void savePrefs();
-
-	Common::String getKey(const Common::String &path);
-
-private:
-	Common::JSONValue *_node = nullptr;
-	TempPref _tmp;
-};
-
-} // namespace Twp
-
-#endif
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 88d49a29129..0d1dbfdfdff 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -26,18 +26,10 @@
 
 namespace Twp {
 
-static Common::String getKey(const char *path) {
-	int len = strlen(path);
-	Common::String p(path);
-	size_t i = p.findLastOf(".");
-	p = p.substr(0, i);
-	if ((len > 4) && p.hasSuffixIgnoreCase("_en")) {
-		Common::String lang(ConfMan.get("language"));
-		Common::String filename(p.substr(0, p.size() - 2));
-		const char *ext = path + i;
-		return Common::String::format("%s%s%s", filename.c_str(), lang.c_str(), ext);
-	}
-	return path;
+Common::String ResManager::getKey(const Common::String &path) {
+	Common::String t(path);
+	replace(t, "_en", "_" + ConfMan.get("language"));
+	return t;
 }
 
 void ResManager::loadTexture(const Common::String &name) {
@@ -97,7 +89,7 @@ void ResManager::loadFont(const Common::String &name) {
 }
 
 SpriteSheet *ResManager::spriteSheet(const Common::String &name) {
-	Common::String key = getKey(name.c_str());
+	Common::String key(getKey(name.c_str()));
 	if (!_spriteSheets.contains(key)) {
 		loadSpriteSheet(key.c_str());
 	}
@@ -105,7 +97,7 @@ SpriteSheet *ResManager::spriteSheet(const Common::String &name) {
 }
 
 Common::SharedPtr<Font> ResManager::font(const Common::String &name) {
-	Common::String key = getKey(name.c_str());
+	Common::String key(getKey(name.c_str()));
 	if (!_fonts.contains(key)) {
 		loadFont(key.c_str());
 	}
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index f6ccf9695a0..96d58230e8d 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -34,6 +34,7 @@ class ResManager {
 public:
 	ResManager() {}
 
+	static Common::String getKey(const Common::String &path);
 	Texture *texture(const Common::String &name);
 	SpriteSheet *spriteSheet(const Common::String &name);
 	Common::SharedPtr<Font> font(const Common::String &name);
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index f25b9777d11..b9b13110d98 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -57,18 +57,9 @@ void SpriteSheet::parseSpriteSheet(const Common::String &contents) {
 	meta.image = jMeta["image"]->asString();
 }
 
-Common::String SpriteSheet::getKey(const Common::String &key) {
-	if (key.hasSuffixIgnoreCase("_en")) {
-		Common::String lang(ConfMan.get("language"));
-		Common::String newKey(Common::String::format("%s%s", key.substr(0, key.size() - 2).c_str(), lang.c_str()));
-		return newKey;
-	}
-	return key;
-}
-
 const SpriteSheetFrame &SpriteSheet::getFrame(const Common::String &key) const {
 	if (key.hasSuffixIgnoreCase("_en")) {
-		Common::String newKey(getKey(key));
+		Common::String newKey(ResManager::getKey(key));
 		if (_frameTable.contains(newKey))
 			return _frameTable[newKey];
 	}
@@ -77,7 +68,7 @@ const SpriteSheetFrame &SpriteSheet::getFrame(const Common::String &key) const {
 
 const SpriteSheetFrame *SpriteSheet::frame(const Common::String &key) const {
 	if (key.hasSuffixIgnoreCase("_en")) {
-		Common::String newKey(getKey(key));
+		Common::String newKey(ResManager::getKey(key));
 		if (_frameTable.contains(newKey))
 			return &_frameTable[newKey];
 	}
diff --git a/engines/twp/spritesheet.h b/engines/twp/spritesheet.h
index 7d2583bbe97..290573821e6 100644
--- a/engines/twp/spritesheet.h
+++ b/engines/twp/spritesheet.h
@@ -46,8 +46,6 @@ struct SpriteSheet {
 	const SpriteSheetFrame *frame(const Common::String &key) const;
 	const SpriteSheetFrame &getFrame(const Common::String &key) const;
 
-	static Common::String getKey(const Common::String &key);
-
 	SpriteSheetMetadata meta;
 	Common::HashMap<Common::String, SpriteSheetFrame> _frameTable;
 };
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index f3660a9656d..4ab0011ce5e 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -39,7 +39,6 @@
 #include "twp/ggpack.h"
 #include "twp/squirrel/squirrel.h"
 #include "twp/camera.h"
-#include "twp/prefs.h"
 #include "twp/tsv.h"
 #include "twp/scenegraph.h"
 #include "twp/dialog.h"
@@ -92,7 +91,6 @@ public:
 	 * Gets the random source
 	 */
 	Common::RandomSource &getRandomSource() { return _randomSource; }
-	Preferences &getPrefs() { return _prefs; }
 
 	HSQUIRRELVM getVm() { return _vm.get(); }
 	inline Gfx &getGfx() { return _gfx; }
@@ -230,7 +228,6 @@ public:
 
 private:
 	Gfx _gfx;
-	Preferences _prefs;
 	SentenceNode _sentence;
 	WalkboxNode _walkboxNode;
 	PathNode _pathNode;


Commit: 31fa339069635e31ce98c56180afcbd8ac1fcec9
    https://github.com/scummvm/scummvm/commit/31fa339069635e31ce98c56180afcbd8ac1fcec9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix some warnings in genlib.cpp

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 3a6f8925bcd..fc8d08494fc 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -310,7 +310,7 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 				SpriteSheet *verbSheet = g_twp->_resManager.spriteSheet("VerbSheet");
 				const SpriteSheetFrame *verbFrame = &verbSheet->getFrame(Common::String::format("%s_en", vb.image.c_str()));
 				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
-				debugC(kDebugGenScript, "findScreenPosition(%d) => %f,%f", verb, pos.getX(), pos.getY());
+				debugC(kDebugGenScript, "findScreenPosition(%lld) => %f,%f", verb, pos.getX(), pos.getY());
 				sqpush(v, pos);
 				return 1;
 			}
@@ -699,7 +699,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 		sqgetf(table, "key", key);
 	if (sqrawexists(table, "flags"))
 		sqgetf(table, "flags", flags);
-	debugC(kDebugGenScript, "setVerb %d, %d, %d, %s", actorSlot, verbSlot, id, text.c_str());
+	debugC(kDebugGenScript, "setVerb %lld, %lld, %lld, %s", actorSlot, verbSlot, id, text.c_str());
 	VerbId verbId;
 	verbId.id = id;
 	g_twp->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);


Commit: 718359055785d6076f9a2f6294727f7dc2dc4472
    https://github.com/scummvm/scummvm/commit/718359055785d6076f9a2f6294727f7dc2dc4472
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Show ImGui window only if debug flag enabled

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/detection.cpp
    engines/twp/detection.h


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 6d8fdbabbe6..61517b70f48 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -589,6 +589,9 @@ static void drawScenegraph() {
 }
 
 void onImGuiRender() {
+	if (!debugChannelSet(-1, kDebugConsole))
+		return;
+
 	drawGeneral();
 	drawThreads();
 	drawObjects();
diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 199d7891318..759d358765d 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -37,6 +37,7 @@ const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
 	{Twp::kDebugActScript, "Actor", "Enable debug actor script dump"},
 	{Twp::kDebugSndScript, "Sound", "Enable debug sound script dump"},
 	{Twp::kDebugGame, "Game", "Game debug level"},
+	{Twp::kDebugConsole, "imgui", "Show ImGui debug window (if available)"},
 	DEBUG_CHANNEL_END};
 
 TwpMetaEngineDetection::TwpMetaEngineDetection()
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index aad0c984f8f..4f30d5a2ea3 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -38,6 +38,7 @@ enum TwpDebugChannels {
 	kDebugActScript,
 	kDebugSndScript,
 	kDebugGame,
+	kDebugConsole
 };
 
 extern const PlainGameDescriptor twpGames[];


Commit: f1c3b01f7e00961de50e2a95c2bb0f253b1b0f78
    https://github.com/scummvm/scummvm/commit/f1c3b01f7e00961de50e2a95c2bb0f253b1b0f78
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix Linux compilation with imgui enabled

This is due to <X11/Xlib.h> doing a lot of defines with common names and messing with all classes or structure using the same names

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index c3fdf435ccc..a00a16893eb 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -28,7 +28,22 @@
 #include "graphics/imgui/imgui.h"
 #include "graphics/imgui/backends/imgui_impl_sdl2_scummvm.h"
 #include "graphics/imgui/backends/imgui_impl_opengl3_scummvm.h"
+#undef FORBIDDEN_SYMBOL_ALLOW_ALL
 #include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+// here I undefined these symbols because of the <X11/Xlib.h> defining them
+// and messing with all classes or structure using the same names
+#undef Bool
+#undef CursorShape
+#undef Expose
+#undef KeyPress
+#undef KeyRelease
+#undef FocusIn
+#undef FocusOut
+#undef FontChange
+#undef None
+#undef Status
+#undef Unsorted
+
 #endif
 
 #include "common/config-manager.h"


Commit: 72278f8902443013b3cea01187115b821fb21a29
    https://github.com/scummvm/scummvm/commit/72278f8902443013b3cea01187115b821fb21a29
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix can hear actors in dialogs

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/enginedialogtarget.cpp
    engines/twp/motor.cpp


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index bf2c47b07fd..844e284c9e9 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -150,8 +150,7 @@ void ExpVisitor::visit(const YLimit &node) {
 }
 
 void ExpVisitor::visit(const YSay &node) {
-	Common::String text(g_twp->getTextDb().getText(node._text));
-	_dialog->_action = _dialog->_tgt->say(node._actor, text);
+	_dialog->_action = _dialog->_tgt->say(node._actor, node._text);
 }
 
 CondVisitor::CondVisitor(Dialog *dialog) : _dialog(dialog) {}
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index ebe574cbfc0..1f4d0815c71 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -21,6 +21,7 @@
 
 #include "twp/twp.h"
 #include "twp/enginedialogtarget.h"
+#include "twp/squtil.h"
 
 namespace Twp {
 
@@ -53,11 +54,10 @@ private:
 };
 
 static Common::SharedPtr<Object> actor(const Common::String &name) {
-	for (auto a : g_twp->_actors) {
-		if (a->_key == name)
-			return a;
-	}
-	return nullptr;
+	HSQOBJECT obj;
+	sq_resetobject(&obj);
+	sqgetf(name, obj);
+	return sqactor(obj);
 }
 
 static Common::SharedPtr<Object> actorOrCurrent(const Common::String &name) {
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index fbcfca20e0f..c52f26506a8 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -390,8 +390,11 @@ void Talking::say(const Common::String &text) {
 		return;
 
 	Common::String txt(text);
-	if (text[0] == '@') {
-		int id = atoi(text.c_str() + 1);
+	if (txt[0] == '$') {
+		txt = g_twp->getTextDb().getText(txt);
+	}
+	if (txt[0] == '@') {
+		int id = atoi(txt.c_str() + 1);
 		txt = g_twp->_textDb.getText(id);
 
 		id = onTalkieId(id);
@@ -413,8 +416,8 @@ void Talking::say(const Common::String &text) {
 		}
 
 		_obj->_sound = loadActorSpeech(name);
-	} else if (text[0] == '^') {
-		txt = text.substr(1);
+	} else if (txt[0] == '^') {
+		txt = txt.substr(1);
 	}
 
 	// remove text in parentheses
@@ -476,7 +479,7 @@ void Talking::disable() {
 	Motor::disable();
 	_texts.clear();
 	_obj->setHeadIndex(1);
-	if(_node)
+	if (_node)
 		_node->remove();
 }
 


Commit: 0f05553590ad39b52692df4531978ac26778def9
    https://github.com/scummvm/scummvm/commit/0f05553590ad39b52692df4531978ac26778def9
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix invalid types use

Changed paths:
    engines/twp/savegame.cpp
    engines/twp/savegame.h
    engines/twp/time.cpp
    engines/twp/time.h


diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index f3a41e9fff8..9baee3bca8b 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -373,10 +373,10 @@ static void setActor(const Common::String &key) {
 	}
 }
 
-static int32_t computeHash(byte *data, size_t n) {
-	int32_t result = 0x6583463;
+static int32 computeHash(byte *data, size_t n) {
+	int32 result = 0x6583463;
 	for (size_t i = 0; i < n; i++) {
-		result += (int32_t)data[i];
+		result += (int32)data[i];
 	}
 	return result;
 }
@@ -426,9 +426,9 @@ bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &
 	Common::Array<byte> data(stream->size());
 	stream->read(data.data(), data.size());
 	Common::BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
-	savegame.hashData = *(int32_t *)(&data[data.size() - 16]);
-	savegame.time = *(int32_t *)&data[data.size() - 12];
-	int32_t hashCheck = computeHash(data.data(), data.size() - 16);
+	savegame.hashData = *(int32 *)(&data[data.size() - 16]);
+	savegame.time = *(int32 *)&data[data.size() - 12];
+	int32 hashCheck = computeHash(data.data(), data.size() - 16);
 	if (savegame.hashData != hashCheck)
 		return false;
 
diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
index 45a020585aa..f8ede3d8d30 100644
--- a/engines/twp/savegame.h
+++ b/engines/twp/savegame.h
@@ -28,9 +28,9 @@
 namespace Twp {
 
 struct SaveGame {
-	int32_t hashData = 0;
-	int64_t time = 0;
-	int64_t gameTime = 0;
+	int32 hashData = 0;
+	int64 time = 0;
+	int64 gameTime = 0;
 	bool easyMode = false;
 	Common::ScopedPtr<Common::JSONValue> jSavegame;
 };
diff --git a/engines/twp/time.cpp b/engines/twp/time.cpp
index c5a6df72104..c645070b2c3 100644
--- a/engines/twp/time.cpp
+++ b/engines/twp/time.cpp
@@ -25,7 +25,7 @@
 
 namespace Twp {
 
-Common::String formatTime(int64_t t, const char *format) {
+Common::String formatTime(int64 t, const char *format) {
 	time_t time = (time_t)t;
 	struct tm *tm = localtime(&time);
 	char buf[64];
@@ -33,7 +33,7 @@ Common::String formatTime(int64_t t, const char *format) {
 	return Common::String(buf);
 }
 
-DateTime toDateTime(int64_t t) {
+DateTime toDateTime(int64 t) {
 	time_t time = (time_t)t;
 	struct tm *tm = localtime(&time);
 	DateTime dateTime;
@@ -45,8 +45,8 @@ DateTime toDateTime(int64_t t) {
 	return dateTime;
 }
 
-int64_t getTime() {
-	return (int64_t)time(NULL);
+int64 getTime() {
+	return (int64)time(NULL);
 }
 
 } // namespace Twp
diff --git a/engines/twp/time.h b/engines/twp/time.h
index d81c57c9d3d..a7bbfb00391 100644
--- a/engines/twp/time.h
+++ b/engines/twp/time.h
@@ -31,9 +31,9 @@ struct DateTime {
 	int hour, min;
 };
 
-Common::String formatTime(int64_t time, const char *format);
-DateTime toDateTime(int64_t time);
-int64_t getTime();
+Common::String formatTime(int64 time, const char *format);
+DateTime toDateTime(int64 time);
+int64 getTime();
 
 } // namespace Twp
 


Commit: f1a741221ac99347d1985229fd230ff49930745b
    https://github.com/scummvm/scummvm/commit/f1a741221ac99347d1985229fd230ff49930745b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Stop audio before destructing engine

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index a00a16893eb..ef1267cad99 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -92,6 +92,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 }
 
 TwpEngine::~TwpEngine() {
+	_mixer->stopAll();
 	delete _screen;
 }
 


Commit: b8eea416a22b5520b832f2a5df2118865c6a9663
    https://github.com/scummvm/scummvm/commit/b8eea416a22b5520b832f2a5df2118865c6a9663
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix various compilation issues

Changed paths:
    engines/twp/dialog.cpp
    engines/twp/gfx.cpp
    engines/twp/ids.cpp
    engines/twp/object.cpp
    engines/twp/rectf.cpp
    engines/twp/squirrel/sqcompiler.cpp
    engines/twp/squirrel/sqlexer.cpp
    engines/twp/squirrel/sqobject.cpp
    engines/twp/squirrel/sqstdrex.cpp
    engines/twp/squirrel/sqstdstring.cpp
    engines/twp/squirrel/sqvm.cpp
    engines/twp/thread.cpp
    engines/twp/util.cpp


diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index 844e284c9e9..c6e92840824 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -213,7 +213,10 @@ void Dialog::choose(DialogSlot *slot) {
 }
 
 void Dialog::start(const Common::String &actor, const Common::String &name, const Common::String &node) {
-	_context = DialogContext{.actor = actor, .dialogName = name, .parrot = true, .limit = MAXCHOICES};
+	_context.actor = actor;
+	_context.dialogName = name;
+	_context.parrot = true;
+	_context.limit = MAXCHOICES;
 	// keepIf(self.states, proc(x: DialogConditionState): bool = x.mode != TempOnce);
 	Common::String path = name + ".byack";
 	debugC(kDebugDialog, "start dialog %s", path.c_str());
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 9ef90a4cd0e..84da6c11236 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -258,7 +258,6 @@ void Gfx::drawPrimitives(uint32 primitivesType, Vertex *vertices, int v_size, Ma
 
 		GL_CALL(glActiveTexture(GL_TEXTURE0));
 		GL_CALL(glBindTexture(GL_TEXTURE_2D, _texture->id));
-		GL_CALL(glUniform1i(0, 0));
 
 		Math::Matrix4 m = getFinalTransform(trsf);
 		_shader->_shader.setUniform("u_transform", m);
diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
index 2b0d84e9cba..4dcabfe86a1 100644
--- a/engines/twp/ids.cpp
+++ b/engines/twp/ids.cpp
@@ -106,6 +106,7 @@ int newLightId() {
 
 Facing getOppositeFacing(Facing facing) {
 	switch (facing) {
+	default:
 	case FACE_FRONT:
 		return FACE_BACK;
 	case FACE_BACK:
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 235b1149185..e52fa136cd8 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -232,6 +232,7 @@ Common::String Object::suffix() const {
 	switch (getFacing()) {
 	case FACE_BACK:
 		return "_back";
+	default:
 	case FACE_FRONT:
 		return "_front";
 	case FACE_LEFT:
diff --git a/engines/twp/rectf.cpp b/engines/twp/rectf.cpp
index c94dbb50d3e..53300d30d8b 100644
--- a/engines/twp/rectf.cpp
+++ b/engines/twp/rectf.cpp
@@ -40,8 +40,8 @@ Rectf Rectf::fromMinMax(Math::Vector2d min, Math::Vector2d max) {
 	return {min.getX(), min.getY(), max.getX() - min.getX() + 1, max.getY() - min.getY() + 1};
 }
 
-Rectf Rectf::operator/(Math::Vector2d v) {
-	return Rectf(r.x / v.getX(), r.y / v.getY(), r.w / v.getX(), r.h / v.getY());
+Rectf Rectf::operator/(Math::Vector2d nv) {
+	return Rectf(r.x / nv.getX(), r.y / nv.getY(), r.w / nv.getX(), r.h / nv.getY());
 }
 
 } // namespace Twp
diff --git a/engines/twp/squirrel/sqcompiler.cpp b/engines/twp/squirrel/sqcompiler.cpp
index 43778fd71ab..9f357952ca7 100755
--- a/engines/twp/squirrel/sqcompiler.cpp
+++ b/engines/twp/squirrel/sqcompiler.cpp
@@ -978,6 +978,7 @@ public:
                     Expect(_SC(':')); Expression();
                     break;
                 }
+                // fallthrough
             default :
                 _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER)));
                 Expect(_SC('=')); Expression();
diff --git a/engines/twp/squirrel/sqlexer.cpp b/engines/twp/squirrel/sqlexer.cpp
index f6a1ab645df..65937bc2748 100755
--- a/engines/twp/squirrel/sqlexer.cpp
+++ b/engines/twp/squirrel/sqlexer.cpp
@@ -117,6 +117,7 @@ void SQLexer::LexBlockComment()
             case _SC('*'): { NEXT(); if(CUR_CHAR == _SC('/')) { done = true; NEXT(); }}; continue;
             case _SC('\n'): _currentline++; NEXT(); continue;
             case SQUIRREL_EOB: Error(_SC("missing \"*/\" in comment"));
+            // fallthrough
             default: NEXT();
         }
     }
@@ -207,7 +208,8 @@ SQInteger SQLexer::Lex()
                 RETURN_TOKEN(stype);
             }
             Error(_SC("error parsing the string"));
-                       }
+            return 0;
+            }
         case _SC('"'):
         case _SC('\''): {
             SQInteger stype;
@@ -215,6 +217,7 @@ SQInteger SQLexer::Lex()
                 RETURN_TOKEN(stype);
             }
             Error(_SC("error parsing the string"));
+            return 0;
             }
         case _SC('{'): case _SC('}'): case _SC('('): case _SC(')'): case _SC('['): case _SC(']'):
         case _SC(';'): case _SC(','): case _SC('?'): case _SC('^'): case _SC('~'):
diff --git a/engines/twp/squirrel/sqobject.cpp b/engines/twp/squirrel/sqobject.cpp
index 7d7f297fcb0..be376131252 100755
--- a/engines/twp/squirrel/sqobject.cpp
+++ b/engines/twp/squirrel/sqobject.cpp
@@ -512,11 +512,11 @@ bool SQFunctionProto::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr
 
     for(i = 0; i < noutervalues; i++){
         SQUnsignedInteger type;
-        SQObjectPtr name;
+        SQObjectPtr objname;
         _CHECK_IO(SafeRead(v,read,up, &type, sizeof(SQUnsignedInteger)));
         _CHECK_IO(ReadObject(v, up, read, o));
-        _CHECK_IO(ReadObject(v, up, read, name));
-        f->_outervalues[i] = SQOuterVar(name,o, (SQOuterType)type);
+        _CHECK_IO(ReadObject(v, up, read, objname));
+        f->_outervalues[i] = SQOuterVar(objname,o, (SQOuterType)type);
     }
     _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART));
 
diff --git a/engines/twp/squirrel/sqstdrex.cpp b/engines/twp/squirrel/sqstdrex.cpp
index 73d0444e0b5..2112afec55b 100755
--- a/engines/twp/squirrel/sqstdrex.cpp
+++ b/engines/twp/squirrel/sqstdrex.cpp
@@ -164,6 +164,7 @@ static SQInteger sqstd_rex_charnode(SQRex *exp,SQBool isclass)
                     exp->_p++;
                     return node;
                 } //else default
+                // fallthrough
             default:
                 t = *exp->_p; exp->_p++;
                 return sqstd_rex_newnode(exp,t);
diff --git a/engines/twp/squirrel/sqstdstring.cpp b/engines/twp/squirrel/sqstdstring.cpp
index cdac0870731..63faa58c3fd 100755
--- a/engines/twp/squirrel/sqstdstring.cpp
+++ b/engines/twp/squirrel/sqstdstring.cpp
@@ -121,6 +121,7 @@ SQRESULT sqstd_format(HSQUIRRELVM v,SQInteger nformatstringidx,SQInteger *outlen
                 fmt[fpos++] = _SC('\0');
                 }
 #endif
+                // fallthrough
             case 'c':
                 if(SQ_FAILED(sq_getinteger(v,nparam,&ti)))
                     return sq_throwerror(v,_SC("integer expected for the specified format"));
diff --git a/engines/twp/squirrel/sqvm.cpp b/engines/twp/squirrel/sqvm.cpp
index 5bca95e9b71..4e77a554e00 100755
--- a/engines/twp/squirrel/sqvm.cpp
+++ b/engines/twp/squirrel/sqvm.cpp
@@ -236,6 +236,7 @@ bool SQVM::ObjCmp(const SQObjectPtr &o1,const SQObjectPtr &o2,SQInteger &result)
                     return false;
                 }
             }
+            // fallthrough
             //continues through (no break needed)
         default:
             _RET_SUCCEED( _userpointer(o1) < _userpointer(o2)?-1:1 );
@@ -314,6 +315,7 @@ bool SQVM::ToString(const SQObjectPtr &o,SQObjectPtr &res)
                 }
             }
         }
+        // fallthrough
     default:
         scsprintf(_sp(sq_rsl((sizeof(void*)*2)+NUMBER_MAX_CHAR)),sq_rsl((sizeof(void*)*2)+NUMBER_MAX_CHAR),_SC("(%s : 0x%p)"),GetTypeName(o),(void*)_rawval(o));
     }
@@ -573,6 +575,7 @@ bool SQVM::FOREACH_OP(SQObjectPtr &o1,SQObjectPtr &o2,SQObjectPtr
             _generator(o1)->Resume(this, o3);
             _FINISH(0);
         }
+        // fallthrough
     default:
         Raise_Error(_SC("cannot iterate %s"), GetTypeName(o1));
     }
@@ -740,6 +743,7 @@ exception_restore:
                     continue;
                 }
                               }
+                // fallthrough
             case _OP_CALL: {
                     SQObjectPtr clo = STK(arg1);
                     switch (sq_type(clo)) {
@@ -748,7 +752,7 @@ exception_restore:
                         continue;
                     case OT_NATIVECLOSURE: {
                         bool suspend;
-						bool tailcall;
+                        bool tailcall;
                         _GUARD(CallNative(_nativeclosure(clo), arg3, _stackbase+arg2, clo, (SQInt32)sarg0, suspend, tailcall));
                         if(suspend){
                             _suspended = SQTrue;
@@ -789,20 +793,18 @@ exception_restore:
                     case OT_TABLE:
                     case OT_USERDATA:
                     case OT_INSTANCE:{
-                        SQObjectPtr closure;
-                        if(_delegable(clo)->_delegate && _delegable(clo)->GetMetaMethod(this,MT_CALL,closure)) {
+                        SQObjectPtr objclosure;
+                        if(_delegable(clo)->_delegate && _delegable(clo)->GetMetaMethod(this,MT_CALL,objclosure)) {
                             Push(clo);
                             for (SQInteger i = 0; i < arg3; i++) Push(STK(arg2 + i));
-                            if(!CallMetaMethod(closure, MT_CALL, arg3+1, clo)) SQ_THROW();
+                            if(!CallMetaMethod(objclosure, MT_CALL, arg3+1, clo)) SQ_THROW();
                             if(sarg0 != -1) {
                                 STK(arg0) = clo;
                             }
                             break;
                         }
-
-                        //Raise_Error(_SC("attempt to call '%s'"), GetTypeName(clo));
-                        //SQ_THROW();
                       }
+                      // fallthrough
                     default:
                         Raise_Error(_SC("attempt to call '%s'"), GetTypeName(clo));
                         SQ_THROW();
@@ -1076,11 +1078,11 @@ exception_trap:
 
         while( ci ) {
             if(ci->_etraps > 0) {
-                SQExceptionTrap &et = _etraps.top();
-                ci->_ip = et._ip;
-                _top = et._stacksize;
-                _stackbase = et._stackbase;
-                _stack._vals[_stackbase + et._extarget] = currerror;
+                SQExceptionTrap &extrap = _etraps.top();
+                ci->_ip = extrap._ip;
+                _top = extrap._stacksize;
+                _stackbase = extrap._stackbase;
+                _stack._vals[_stackbase + extrap._extarget] = currerror;
                 _etraps.pop_back(); traps--; ci->_etraps--;
                 while(last_top >= _top) _stack._vals[last_top--].Null();
                 goto exception_restore;
@@ -1135,10 +1137,10 @@ void SQVM::CallDebugHook(SQInteger type,SQInteger forcedline)
         _debughook_native(this,type,src,line,fname);
     }
     else {
-        SQObjectPtr temp_reg;
+        SQObjectPtr tmp_reg;
         SQInteger nparams=5;
         Push(_roottable); Push(type); Push(func->_sourcename); Push(forcedline?forcedline:func->GetLine(ci->_ip)); Push(func->_name);
-        Call(_debughook_closure,nparams,_top-nparams,temp_reg,SQFalse);
+        Call(_debughook_closure,nparams,_top-nparams,tmp_reg,SQFalse);
         Pop(nparams);
     }
     _debughook = true;
@@ -1320,7 +1322,7 @@ SQInteger SQVM::FallBackGet(const SQObjectPtr &self,const SQObjectPtr &key,SQObj
         else {
             return FALLBACK_NO_MATCH;
         }
-        //go through
+        // fallthrough
     case OT_INSTANCE: {
         SQObjectPtr closure;
         if(_delegable(self)->GetMetaMethod(this, MT_GET, closure)) {
@@ -1388,7 +1390,7 @@ SQInteger SQVM::FallBackSet(const SQObjectPtr &self,const SQObjectPtr &key,const
         if(_table(self)->_delegate) {
             if(Set(_table(self)->_delegate,key,val,DONT_FALL_BACK)) return FALLBACK_OK;
         }
-        //keps on going
+        // fallthrough
     case OT_INSTANCE:
     case OT_USERDATA:{
         SQObjectPtr closure;
@@ -1418,7 +1420,7 @@ SQInteger SQVM::FallBackSet(const SQObjectPtr &self,const SQObjectPtr &key,const
 
 bool SQVM::Clone(const SQObjectPtr &self,SQObjectPtr &target)
 {
-    SQObjectPtr temp_reg;
+    SQObjectPtr tmp_reg;
     SQObjectPtr newobj;
     switch(sq_type(self)){
     case OT_TABLE:
@@ -1431,7 +1433,7 @@ cloned_mt:
         if(_delegable(newobj)->_delegate && _delegable(newobj)->GetMetaMethod(this,MT_CLONED,closure)) {
             Push(newobj);
             Push(self);
-            if(!CallMetaMethod(closure,MT_CLONED,2,temp_reg))
+            if(!CallMetaMethod(closure,MT_CLONED,2,tmp_reg))
                 return false;
         }
         }
@@ -1740,7 +1742,7 @@ SQObjectPtr &SQVM::GetUp(SQInteger n) { CheckStackAccess(_top+n); return _stack[
 SQObjectPtr &SQVM::GetAt(SQInteger n) { CheckStackAccess(n); return _stack[n]; }
 
 void SQVM::CheckStackAccess(SQInteger n) {
-    if(n < 0 || n >= _stack.size()){
+    if(n < 0 || n >= (SQInteger)_stack.size()){
         //std::ostringstream s;
         //s << "Stack of the VM accessed with n=" << n << " and stacksize=" << _stack.size();
         //throw std::out_of_range(s.str());
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 91b6152daa4..edf32fff1cd 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -236,6 +236,8 @@ bool Cutscene::update(float elapsed) {
 	case csQuit:
 		return true;
 	}
+
+	return false;
 }
 
 bool Cutscene::hasOverride() const {
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 47b75c485ac..e25b35bd5ba 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -40,6 +40,7 @@ Facing flip(Facing facing) {
 	switch (facing) {
 	case FACE_BACK:
 		return FACE_FRONT;
+	default:
 	case FACE_FRONT:
 		return FACE_BACK;
 	case FACE_LEFT:


Commit: 7f44571779ed72ec202bcaef6d7c08ec4041630e
    https://github.com/scummvm/scummvm/commit/7f44571779ed72ec202bcaef6d7c08ec4041630e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix node removing

Changed paths:
    engines/twp/scenegraph.cpp


diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index c2ce6adab03..7b9a1e5eb72 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -54,6 +54,12 @@ Node::Node(const Common::String &name, Math::Vector2d scale, Color color)
 
 Node::~Node() {
 	remove();
+	if (_children.empty())
+		return;
+
+	for (size_t i = 0; i < _children.size(); i++) {
+		_children[i]->_parent = nullptr;
+	}
 }
 
 void Node::addChild(Node *child) {
@@ -95,11 +101,12 @@ void Node::removeChild(Node *child) {
 }
 
 void Node::clear() {
-	if (_children.size() > 0) {
-		Common::Array<Node *> children(_children);
-		for (size_t i = 0; i < children.size(); i++) {
-			children[i]->remove();
-		}
+	if (_children.empty())
+		return;
+
+	Common::Array<Node *> children(_children);
+	for (size_t i = 0; i < children.size(); i++) {
+		children[i]->remove();
 	}
 	_children.clear();
 }


Commit: dafe00424fa9cdd4ab1d4e513c6f6283dd6415ff
    https://github.com/scummvm/scummvm/commit/dafe00424fa9cdd4ab1d4e513c6f6283dd6415ff
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove two full stops in dialogs.cpp

Changed paths:
    engines/twp/dialogs.cpp


diff --git a/engines/twp/dialogs.cpp b/engines/twp/dialogs.cpp
index 0cfbaca0629..056171b4678 100644
--- a/engines/twp/dialogs.cpp
+++ b/engines/twp/dialogs.cpp
@@ -35,7 +35,7 @@ TwpOptionsContainerWidget::TwpOptionsContainerWidget(GuiObject *boss, const Comm
 	_enableToiletPaperOverGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck1",
 																// I18N: Setting to switch toiled paper to be shown as "over".
 																_("Toilet paper over"),
-																_("The toilet paper in some toilets will be shown “over”.\nIt’s a joke option that has no effects on the gameplay.."));
+																_("The toilet paper in some toilets will be shown “over”.\nIt’s a joke option that has no effects on the gameplay."));
 	_enableAnnoyingInJokesGUICheckbox = new GUI::CheckboxWidget(widgetsBoss(), "TwpGameOptionsDialog.VideoCheck2",
 																// I18N: Setting to enable or disable additional jokes in the game.
 																_("Annoying in-jokes"),


Commit: c7a24e15bca9b9f2f6a54a82c6e58eaad5e30223
    https://github.com/scummvm/scummvm/commit/c7a24e15bca9b9f2f6a54a82c6e58eaad5e30223
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
COMMON: Change BTEACrypto class to namespace

Changed paths:
    common/btea.cpp
    common/btea.h


diff --git a/common/btea.cpp b/common/btea.cpp
index 63ff48996d2..5d8d94b8069 100644
--- a/common/btea.cpp
+++ b/common/btea.cpp
@@ -23,19 +23,13 @@
 
 namespace Common {
 
+namespace BTEACrypto {
+
 #define DELTA 0x9e3779b9
 #define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
 
-void BTEACrypto::encrypt(uint32 *v, int n, const uint32 *key) {
-	btea(v, n, key);
-}
-
-void BTEACrypto::decrypt(uint32 *v, int n, const uint32 *key) {
-	btea(v, -n, key);
-}
-
 // This method comes from https://en.wikipedia.org/wiki/XXTEA
-void BTEACrypto::btea(uint32 *v, int n, const uint32 *key) {
+void btea(uint32 *v, int n, const uint32 *key) {
 	uint32 y, z, sum;
 	unsigned int p, rounds, e;
 	if (n > 1) { /* Coding Part */
@@ -69,4 +63,14 @@ void BTEACrypto::btea(uint32 *v, int n, const uint32 *key) {
 		} while (--rounds);
 	}
 }
+
+void encrypt(uint32 *v, int n, const uint32 *key) {
+	btea(v, n, key);
+}
+
+void decrypt(uint32 *v, int n, const uint32 *key) {
+	btea(v, -n, key);
+}
+
+} // namespace BTEACrypto
 } // namespace Common
diff --git a/common/btea.h b/common/btea.h
index a5b5d842853..1817cfc98fb 100644
--- a/common/btea.h
+++ b/common/btea.h
@@ -25,34 +25,31 @@
 #include "common/system.h"
 
 namespace Common {
-
+namespace BTEACrypto {
 /**
- * Corrected Block TEA (aka XXTEA) class for ScummVM.
+ * Corrected Block TEA (aka XXTEA) for ScummVM.
  *
  * In cryptography, Corrected Block TEA (often referred to as XXTEA)
  * is a block cipher designed to correct weaknesses in the original Block TEA.
  */
-class BTEACrypto {
-public:
-	/**
-	 * Encrypt data with a specified key.
-	 * @param[in,out] data    the data to encrypt
-	 * @param[in] n	          the size of the data
-	 * @param[in] key         the key to use to encrypt the data
-	 */
-	static void encrypt(uint32 *data, int n, const uint32 *key);
-	/**
-	 * Decrypt data encrypted before with btea with a specified key.
-	 * @param[in,out] data    the data to decrypt
-	 * @param[in] n	          the size of the data
-	 * @param[in] key         the key to use to decrypt the data
-	 */
-	static void decrypt(uint32 *data, int n, const uint32 *key);
 
-private:
-	static void btea(uint32 *v, int n, const uint32 *k);
-};
+/**
+ * Encrypt data with a specified key.
+ * @param[in,out] data    the data to encrypt
+ * @param[in] n	          the size of the data
+ * @param[in] key         the key to use to encrypt the data
+ */
+void encrypt(uint32 *data, int n, const uint32 *key);
+
+/**
+ * Decrypt data encrypted before with btea with a specified key.
+ * @param[in,out] data    the data to decrypt
+ * @param[in] n	          the size of the data
+ * @param[in] key         the key to use to decrypt the data
+ */
+void decrypt(uint32 *data, int n, const uint32 *key);
 
+} // End of namespace BTEACrypto
 } // End of namespace Common
 
 #endif // COMMON_BTEA_H


Commit: e0857c72004a731b5923220b4c69a0730ff5f218
    https://github.com/scummvm/scummvm/commit/e0857c72004a731b5923220b4c69a0730ff5f218
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix clipper inclusion

Changed paths:
    engines/twp/clipper/clipper.cpp


diff --git a/engines/twp/clipper/clipper.cpp b/engines/twp/clipper/clipper.cpp
index 92a10efd33d..1c52b148750 100644
--- a/engines/twp/clipper/clipper.cpp
+++ b/engines/twp/clipper/clipper.cpp
@@ -38,7 +38,7 @@
  *                                                                              *
  *******************************************************************************/
 
-#include "clipper.hpp"
+#include "twp/clipper/clipper.hpp"
 #include "common/debug.h"
 
 namespace ClipperLib {


Commit: ab907a83a82d8b9abf07b10873ba8bb983204344
    https://github.com/scummvm/scummvm/commit/ab907a83a82d8b9abf07b10873ba8bb983204344
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix strange init in DialogConditionState

Changed paths:
    engines/twp/dialog.h


diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index 1dd8f841963..a2744f0a6b9 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -78,7 +78,7 @@ enum DialogSelMode {
 struct DialogConditionState {
 	DialogConditionMode mode;
 	Common::String actorKey, dialog;
-	int line{};
+	int line;
 
 	DialogConditionState();
 	DialogConditionState(DialogConditionMode mode, const Common::String &actorKey, const Common::String &dialog, int line);


Commit: 86f2963abd92f94ecad59c3946aca4b1c6fc738b
    https://github.com/scummvm/scummvm/commit/86f2963abd92f94ecad59c3946aca4b1c6fc738b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix ids inclusion

Changed paths:
    engines/twp/ids.cpp


diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
index 4dcabfe86a1..d172de416d5 100644
--- a/engines/twp/ids.cpp
+++ b/engines/twp/ids.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-#include "ids.h"
+#include "twp/ids.h"
 
 namespace Twp {
 


Commit: f2fccce4f6bfbba6bb3292eb4f651697dd5e4303
    https://github.com/scummvm/scummvm/commit/f2fccce4f6bfbba6bb3292eb4f651697dd5e4303
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix files order in module.mk

Changed paths:
    engines/twp/module.mk


diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index d47e4ce4453..6198c3da355 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -43,6 +43,7 @@ MODULE_OBJS = \
 	vm.o \
 	walkboxnode.o \
 	yack.o \
+	clipper/clipper.o \
 	squirrel/sqapi.o \
 	squirrel/sqbaselib.o \
 	squirrel/sqfuncstate.o \
@@ -62,7 +63,6 @@ MODULE_OBJS = \
 	squirrel/sqstdblob.o \
 	squirrel/sqstdrex.o \
 	squirrel/sqstdaux.o \
-	clipper/clipper.o \
 
 ifdef USE_IMGUI
 MODULE_OBJS += \


Commit: e1571a91830de2807ca44a998a1d352975db9fa0
    https://github.com/scummvm/scummvm/commit/e1571a91830de2807ca44a998a1d352975db9fa0
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix bad formatting in hud.cpp

Changed paths:
    engines/twp/hud.cpp


diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 4cdfdc88934..7ddf0cdfc87 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -71,7 +71,7 @@ void HudShader::init() {
 		v_ranges = u_ranges;
 	})";
 
-	const char* f_source = R"(
+	const char *f_source = R"(
 	varying vec4 v_color;
 	varying vec2 v_texCoords;
 	varying vec4 v_shadowColor;


Commit: 7503b204c30c4e84950680d2c492d905e9a3ae31
    https://github.com/scummvm/scummvm/commit/7503b204c30c4e84950680d2c492d905e9a3ae31
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use CLIP instead of clamp

Changed paths:
    engines/twp/audio.cpp
    engines/twp/camera.cpp
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/objlib.cpp
    engines/twp/scenegraph.cpp
    engines/twp/twp.cpp
    engines/twp/util.h


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index dce9ee6afa6..ec9ad1d2d35 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -125,7 +125,7 @@ void AudioSystem::stop(int id) {
 }
 
 void AudioSystem::setMasterVolume(float vol) {
-	_masterVolume = Twp::clamp(vol, 0.f, 1.f);
+	_masterVolume = CLIP(vol, 0.f, 1.f);
 
 	// update sounds
 	for (auto &_slot : _slots) {
@@ -172,7 +172,7 @@ void AudioSystem::updateVolume(AudioSlot *slot) {
 					volObj = (width - (diff - (0.25f * width))) / width;
 				}
 
-				float pan = clamp((obj->_node->getAbsPos().getX() - x) / (width / 2), -1.0f, 1.0f);
+				float pan = CLIP((obj->_node->getAbsPos().getX() - x) / (width / 2), -1.0f, 1.0f);
 				g_twp->_mixer->setChannelBalance(slot->handle, (int8)(pan * 127));
 			}
 			vol *= volObj;
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 1a55fec67f7..cec0ac4ba1b 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -28,10 +28,10 @@ void Camera::clamp(Math::Vector2d at) {
 		Math::Vector2d roomSize = _room->_roomSize;
 		Math::Vector2d screenSize = _room->getScreenSize();
 
-		_pos.setX(Twp::clamp(at.getX(), screenSize.getX() / 2.f + _bounds.left(), screenSize.getX() / 2 + _bounds.right()));
-		_pos.setY(Twp::clamp(at.getY(), _bounds.bottom(), _bounds.top() - screenSize.getY() / 2));
-		_pos.setX(Twp::clamp(_pos.getX(), screenSize.getX() / 2.f, MAX(roomSize.getX() - screenSize.getX() / 2.f, 0.f)));
-		_pos.setY(Twp::clamp(_pos.getY(), screenSize.getY() / 2, MAX(roomSize.getY() - screenSize.getY() / 2, 0.f)));
+		_pos.setX(CLIP(at.getX(), screenSize.getX() / 2.f + _bounds.left(), screenSize.getX() / 2 + _bounds.right()));
+		_pos.setY(CLIP(at.getY(), _bounds.bottom(), _bounds.top() - screenSize.getY() / 2));
+		_pos.setX(CLIP(_pos.getX(), screenSize.getX() / 2.f, MAX(roomSize.getX() - screenSize.getX() / 2.f, 0.f)));
+		_pos.setY(CLIP(_pos.getY(), screenSize.getY() / 2, MAX(roomSize.getY() - screenSize.getY() / 2, 0.f)));
 	}
 }
 
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index c52f26506a8..88babc2439e 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -261,7 +261,7 @@ void WalkTo::update(float elapsed) {
 		} else {
 			Math::Vector2d delta = (Math::Vector2d)dest - _obj->_node->getAbsPos();
 			float duration = d / _wsd;
-			float factor = Twp::clamp(elapsed / duration, 0.f, 1.f);
+			float factor = CLIP(elapsed / duration, 0.f, 1.f);
 
 			Math::Vector2d dd = delta * factor;
 			_obj->_node->setPos(_obj->_node->getPos() + dd);
@@ -464,8 +464,8 @@ void Talking::say(const Common::String &text) {
 		Math::Vector2d pos = g_twp->roomToScreen(_obj->_node->getAbsPos() + Math::Vector2d(_obj->_talkOffset.getX(), _obj->_talkOffset.getY()));
 
 		// clamp position to keep it on screen
-		pos.setX(Twp::clamp(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
-		pos.setY(Twp::clamp(pos.getY(), 10.f + text2.getBounds().getY(), SCREEN_HEIGHT - text2.getBounds().getY()));
+		pos.setX(CLIP(pos.getX(), 10.f + text2.getBounds().getX() / 2.f, SCREEN_WIDTH - text2.getBounds().getX() / 2.f));
+		pos.setY(CLIP(pos.getY(), 10.f + text2.getBounds().getY(), SCREEN_HEIGHT - text2.getBounds().getY()));
 
 		_obj->_sayNode->setPos(pos);
 		_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 770f8b44c4b..01c80ec56d0 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -47,7 +47,7 @@ public:
 	void update(float el) {
 		if (enabled && running()) {
 			elapsed += el;
-			float f = clamp(elapsed / duration, 0.0f, 1.0f);
+			float f = CLIP(elapsed / duration, 0.0f, 1.0f);
 			if (!dir_forward)
 				f = 1.0f - f;
 			if ((elapsed > duration) && (swing || loop)) {
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index e52fa136cd8..b86a5de1d04 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -795,12 +795,12 @@ void Object::jiggle(float amount) {
 void Object::inventoryScrollUp() {
 	_inventoryOffset -= 1;
 	if (_inventoryOffset < 0)
-		_inventoryOffset = clamp(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
+		_inventoryOffset = CLIP(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
 }
 
 void Object::inventoryScrollDown() {
 	_inventoryOffset++;
-	_inventoryOffset = clamp(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
+	_inventoryOffset = CLIP(_inventoryOffset, 0, ((int)_inventory.size() - 5) / 4);
 }
 
 void TalkingState::say(const Common::StringArray &texts, Common::SharedPtr<Object> obj) {
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index cacc0b3b086..3e61e7c5411 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -289,7 +289,7 @@ static SQInteger objectAlphaTo(HSQUIRRELVM v) {
 		float alpha = 0.0f;
 		if (SQ_FAILED(sqget(v, 3, alpha)))
 			return sq_throwerror(v, "failed to get alpha");
-		alpha = clamp(alpha, 0.0f, 1.0f);
+		alpha = CLIP(alpha, 0.0f, 1.0f);
 		float t = 0.0f;
 		if (SQ_FAILED(sqget(v, 4, t)))
 			return sq_throwerror(v, "failed to get time");
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 7b9a1e5eb72..44eaa0bac0f 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -811,7 +811,7 @@ bool NoOverrideNode::update(float elapsed) {
 		return false;
 	}
 	_elapsed += elapsed;
-	setAlpha(clamp((2.f - _elapsed) / 2.f, 0.f, 1.f));
+	setAlpha(CLIP((2.f - _elapsed) / 2.f, 0.f, 1.f));
 	debugC(kDebugGame, "no override: %.2f, %.2f", _elapsed, getAlpha());
 	return true;
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index ef1267cad99..8cff87a28e2 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -672,7 +672,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 			_fadeShader->_room->_scene->draw();
 		}
 
-		_fadeShader->_fade = clamp(_fadeShader->_elapsed / _fadeShader->_duration, 0.f, 1.f);
+		_fadeShader->_fade = CLIP(_fadeShader->_elapsed / _fadeShader->_duration, 0.f, 1.f);
 
 		// draw fade
 		Texture *texture1 = nullptr;
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 9c248ec8566..0965f1c260e 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -66,15 +66,6 @@ struct Vector2i {
 template<typename T, class DL = Common::DefaultDeleter<T> >
 using unique_ptr = Common::ScopedPtr<T, DL>;
 
-template<typename T>
-T clamp(T x, T a, T b) {
-	if (x < a)
-		return a;
-	if (x > b)
-		return b;
-	return x;
-}
-
 // game util
 Facing getFacing(int dir, Facing facing);
 Facing flip(Facing facing);


Commit: f0f5b3e10df24cee92e5764a8771f0b54a678476
    https://github.com/scummvm/scummvm/commit/f0f5b3e10df24cee92e5764a8771f0b54a678476
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix TWP_VM_H define

Changed paths:
    engines/twp/vm.h


diff --git a/engines/twp/vm.h b/engines/twp/vm.h
index 466430a3fa9..db0b3a18a5f 100644
--- a/engines/twp/vm.h
+++ b/engines/twp/vm.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TWPVM_H
-#define TWPVM_H
+#ifndef TWP_VM_H
+#define TWP_VM_H
 
 #include "twp/squirrel/squirrel.h"
 
@@ -40,4 +40,4 @@ private:
 };
 } // End of namespace Twp
 
-#endif // TWPVM_H
+#endif // TWP_VM_H


Commit: 3248bf19a68a9cf198ae60d9c4bd38575ba9431c
    https://github.com/scummvm/scummvm/commit/3248bf19a68a9cf198ae60d9c4bd38575ba9431c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix TWP_WALKBOXNODE_H define

Changed paths:
    engines/twp/walkboxnode.h


diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index cb84075db7d..d97734f4646 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef TWP_WALKBOXMODE_H
-#define TWP_WALKBOXMODE_H
+#ifndef TWP_WALKBOXNODE_H
+#define TWP_WALKBOXNODE_H
 
 #include "twp/scenegraph.h"
 


Commit: 2ef2f29ea00c527240e599046a68dab73207620e
    https://github.com/scummvm/scummvm/commit/2ef2f29ea00c527240e599046a68dab73207620e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Minimize the include dependency

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/actorswitcher.cpp
    engines/twp/audio.cpp
    engines/twp/camera.cpp
    engines/twp/debugtools.cpp
    engines/twp/dialog.cpp
    engines/twp/dialog.h
    engines/twp/enginedialogtarget.cpp
    engines/twp/font.cpp
    engines/twp/genlib.cpp
    engines/twp/gfx.cpp
    engines/twp/hud.cpp
    engines/twp/hud.h
    engines/twp/lighting.cpp
    engines/twp/metaengine.cpp
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp
    engines/twp/roomlib.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/soundlib.cpp
    engines/twp/spritesheet.cpp
    engines/twp/squtil.cpp
    engines/twp/squtil.h
    engines/twp/syslib.cpp
    engines/twp/task.h
    engines/twp/thread.cpp
    engines/twp/tsv.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h
    engines/twp/walkboxnode.cpp


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 2d35bf2307e..647fe50b009 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -20,6 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/hud.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 
@@ -411,7 +415,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 			SQInteger slot;
 			if (SQ_FAILED(sqget(v, 2, slot)))
 				return sq_throwerror(v, "failed to get slot");
-			g_twp->_hud._actorSlots[slot - 1].selectable = selectable;
+			g_twp->_hud->_actorSlots[slot - 1].selectable = selectable;
 		} else {
 			Common::SharedPtr<Object> actor = sqactor(v, 2);
 			if (!actor)
@@ -419,7 +423,7 @@ static SQInteger actorSlotSelectable(HSQUIRRELVM v) {
 			Common::String key;
 			sqgetf(actor->_table, "_key", key);
 			debugC(kDebugActScript, "actorSlotSelectable(%s, %s)", key.c_str(), selectable ? "yes" : "no");
-			ActorSlot *slot = g_twp->_hud.actorSlot(actor);
+			ActorSlot *slot = g_twp->_hud->actorSlot(actor);
 			if (!slot)
 				warning("slot for actor %s not found", key.c_str());
 			else
@@ -769,7 +773,7 @@ static SQInteger addSelectableActor(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, slot)))
 		return sq_throwerror(v, "failed to get slot");
 	Common::SharedPtr<Object> actor = sqactor(v, 3);
-	g_twp->_hud._actorSlots[slot - 1].actor = actor;
+	g_twp->_hud->_actorSlots[slot - 1].actor = actor;
 	return 0;
 }
 
@@ -926,7 +930,7 @@ static SQInteger isActorSelectable(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> actor = sqactor(v, 2);
 	if (!actor)
 		return sq_throwerror(v, "failed to get actor");
-	ActorSlot *slot = g_twp->_hud.actorSlot(actor);
+	ActorSlot *slot = g_twp->_hud->actorSlot(actor);
 	bool selectable = slot && slot->selectable;
 	sqpush(v, selectable);
 	return 1;
@@ -1045,7 +1049,7 @@ static SQInteger verbUIColors(HSQUIRRELVM v) {
 	sqgetf(table, "dialogNormal", dialogNormal);
 	sqgetf(table, "dialogHighlight", dialogHighlight);
 
-	g_twp->_hud._actorSlots[actorSlot - 1].verbUiColors =
+	g_twp->_hud->_actorSlots[actorSlot - 1].verbUiColors =
 		VerbUiColors(
 			Color::rgb(sentence),
 			Color::rgb(verbNormal),
diff --git a/engines/twp/actorswitcher.cpp b/engines/twp/actorswitcher.cpp
index 4e8dfc66d96..1d142e6db71 100644
--- a/engines/twp/actorswitcher.cpp
+++ b/engines/twp/actorswitcher.cpp
@@ -21,6 +21,8 @@
 
 #include "twp/twp.h"
 #include "twp/actorswitcher.h"
+#include "twp/resmanager.h"
+#include "twp/util.h"
 
 #define DISABLE_ALPHA 0.f
 #define ENABLE_ALPHA 1.f
@@ -69,8 +71,8 @@ float ActorSwitcher::getAlpha(size_t index) const {
 }
 
 void ActorSwitcher::drawIcon(const Common::String &icon, Color backColor, Color frameColor, Math::Matrix4 trsf, int index) {
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager->texture(gameSheet->meta.image);
 	const SpriteSheetFrame &iconBackFrame = gameSheet->getFrame("icon_background");
 	const SpriteSheetFrame &iconActorFrame = gameSheet->getFrame(icon);
 	const SpriteSheetFrame &iconFrame = gameSheet->getFrame("icon_frame");
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index ec9ad1d2d35..2963462cb31 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -27,6 +27,8 @@
 #include "audio/decoders/wave.h"
 #include "twp/twp.h"
 #include "twp/audio.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/squtil.h"
 
 namespace Twp {
@@ -62,7 +64,7 @@ SoundDefinition::SoundDefinition(const Common::String &name) : _name(name), _id(
 void SoundDefinition::load() {
 	if (!_loaded) {
 		GGPackEntryReader entry;
-		entry.open(g_twp->_pack, _name);
+		entry.open(*g_twp->_pack, _name);
 		_buffer.resize(entry.size());
 		entry.read(_buffer.data(), entry.size());
 	}
diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index cec0ac4ba1b..82136976a32 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -20,6 +20,8 @@
  */
 
 #include "twp/twp.h"
+#include "twp/object.h"
+#include "twp/room.h"
 
 namespace Twp {
 
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 61517b70f48..86608f86c30 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -25,9 +25,18 @@
 #include "common/debug-channels.h"
 #include "twp/twp.h"
 #include "twp/debugtools.h"
-#include "twp/thread.h"
+#include "twp/detection.h"
+#include "twp/dialog.h"
+#include "twp/hud.h"
 #include "twp/lighting.h"
+#include "twp/object.h"
+#include "twp/resmanager.h"
+#include "twp/room.h"
+#include "twp/savegame.h"
+#include "twp/shaders.h"
 #include "twp/squtil.h"
+#include "twp/thread.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -249,7 +258,7 @@ static void drawResources() {
 		ImGui::TableSetupColumn("Resolution");
 		ImGui::TableHeadersRow();
 
-		for (auto &res : g_twp->_resManager._textures) {
+		for (auto &res : g_twp->_resManager->_textures) {
 			ImGui::TableNextRow();
 			ImGui::TableNextColumn();
 			bool selected = state._textureSelected == res._key;
@@ -268,7 +277,7 @@ static void drawResources() {
 	ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 10.f));
 	ImGui::Text("Preview:");
 	ImGui::BeginChild("TexturePreview", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY);
-	for (auto &res : g_twp->_resManager._textures) {
+	for (auto &res : g_twp->_resManager->_textures) {
 		if (state._textureSelected == res._key) {
 			ImGui::Image((ImTextureID)(intptr_t)res._value.id, ImVec2(res._value.width, res._value.height));
 			break;
@@ -285,7 +294,7 @@ static void drawAudio() {
 
 	// count the number of active sounds
 	int count = 0;
-	for (auto &s : g_twp->_audio._slots) {
+	for (auto &s : g_twp->_audio->_slots) {
 		if (s.busy)
 			count++;
 	}
@@ -306,7 +315,7 @@ static void drawAudio() {
 		ImGui::TableHeadersRow();
 
 		for (int i = 0; i < NUM_AUDIO_SLOTS; i++) {
-			auto &sound = g_twp->_audio._slots[i];
+			auto &sound = g_twp->_audio->_slots[i];
 			ImGui::TableNextRow();
 			ImGui::TableNextColumn();
 			ImGui::Text("#%d", i);
@@ -327,7 +336,7 @@ static void drawAudio() {
 				ImGui::Text("%0.1f", pan);
 				ImGui::SameLine();
 				if (ImGui::SmallButton("STOP")) {
-					g_twp->_audio.stop(sound.id);
+					g_twp->_audio->stop(sound.id);
 				}
 			}
 		}
@@ -350,14 +359,14 @@ static void drawGeneral() {
 	ImGui::TextColored(gray, "Cutscene:");
 	ImGui::SameLine();
 	ImGui::Text("%s", g_twp->_cutscene ? g_twp->_cutscene->getName().c_str() : "no");
-	DialogState dialogState = g_twp->_dialog.getState();
+	DialogState dialogState = g_twp->_dialog->getState();
 	ImGui::TextColored(gray, "In dialog:");
 	ImGui::SameLine();
 	ImGui::Text("%s", ((dialogState == Active) ? "yes" : (dialogState == WaitingForChoice ? "waiting for choice" : "no")));
 	ImGui::TextColored(gray, "Verb:");
 	ImGui::SameLine();
-	Common::String verb = g_twp->getTextDb().getText(g_twp->_hud._verb.text);
-	ImGui::Text("%s %d", verb.c_str(), g_twp->_hud._verb.id.id);
+	Common::String verb = g_twp->getTextDb().getText(g_twp->_hud->_verb.text);
+	ImGui::Text("%s %d", verb.c_str(), g_twp->_hud->_verb.id.id);
 
 	auto mousePos = g_twp->_cursor.pos;
 	ImGui::TextColored(gray, "Pos (screen):");
@@ -378,7 +387,7 @@ static void drawGeneral() {
 	ImGui::SameLine();
 	ImGui::Checkbox("Verbs", &g_twp->_inputState._inputVerbsActive);
 	ImGui::SameLine();
-	ImGui::Checkbox("Allow SaveGame", &g_twp->_saveGameManager._allowSaveGame);
+	ImGui::Checkbox("Allow SaveGame", &g_twp->_saveGameManager->_allowSaveGame);
 
 	ImGui::Separator();
 	bool isSwitcherOn = g_twp->_actorSwitcher._mode == asOn;
@@ -430,16 +439,16 @@ static void drawGeneral() {
 		ImGui::Text("%s", !g_twp->_followActor ? "(none)" : g_twp->_followActor->_key.c_str());
 		ImGui::TextColored(gray, "moving:");
 		ImGui::SameLine();
-		ImGui::Text("%s", g_twp->_camera.isMoving() ? "yes" : "no");
+		ImGui::Text("%s", g_twp->_camera->isMoving() ? "yes" : "no");
 		auto halfScreenSize = g_twp->_room->getScreenSize() / 2.0f;
 		auto camPos = g_twp->cameraPos() - halfScreenSize;
 		if (ImGui::DragFloat2("Camera pos", camPos.getData())) {
 			g_twp->follow(nullptr);
 			g_twp->cameraAt(camPos);
 		}
-		auto bounds = g_twp->_camera.getBounds();
+		auto bounds = g_twp->_camera->getBounds();
 		if (ImGui::DragFloat4("Bounds", bounds.v)) {
-			g_twp->_camera.setBounds(bounds);
+			g_twp->_camera->setBounds(bounds);
 		}
 	}
 
@@ -501,11 +510,11 @@ static void drawGeneral() {
 		int effect = static_cast<int>(room->_effect);
 		if (ImGui::Combo("effect", &effect, RoomEffects))
 			room->_effect = (RoomEffect)effect;
-		ImGui::DragFloat("iFade", &g_twp->_shaderParams.iFade, 0.01f, 0.f, 1.f);
-		ImGui::DragFloat("wobbleIntensity", &g_twp->_shaderParams.wobbleIntensity, 0.01f, 0.f, 1.f);
-		ImGui::DragFloat3("shadows", g_twp->_shaderParams.shadows.v, 0.01f, -1.f, 1.f);
-		ImGui::DragFloat3("midtones", g_twp->_shaderParams.midtones.v, 0.01f, -1.f, 1.f);
-		ImGui::DragFloat3("highlights", g_twp->_shaderParams.highlights.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat("iFade", &g_twp->_shaderParams->iFade, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat("wobbleIntensity", &g_twp->_shaderParams->wobbleIntensity, 0.01f, 0.f, 1.f);
+		ImGui::DragFloat3("shadows", g_twp->_shaderParams->shadows.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("midtones", g_twp->_shaderParams->midtones.v, 0.01f, -1.f, 1.f);
+		ImGui::DragFloat3("highlights", g_twp->_shaderParams->highlights.v, 0.01f, -1.f, 1.f);
 	}
 
 	// Fade Effects
@@ -561,7 +570,7 @@ static void drawScenegraph() {
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
 	ImGui::Begin("Scenegraph", &state._showScenegraph);
-	drawNode(&g_twp->_scene);
+	drawNode(g_twp->_scene.get());
 	ImGui::End();
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
diff --git a/engines/twp/dialog.cpp b/engines/twp/dialog.cpp
index c6e92840824..a730c8c3bbf 100644
--- a/engines/twp/dialog.cpp
+++ b/engines/twp/dialog.cpp
@@ -20,7 +20,11 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/dialog.h"
+#include "twp/motor.h"
 #include "twp/squtil.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -221,7 +225,7 @@ void Dialog::start(const Common::String &actor, const Common::String &name, cons
 	Common::String path = name + ".byack";
 	debugC(kDebugDialog, "start dialog %s", path.c_str());
 	GGPackEntryReader reader;
-	reader.open(g_twp->_pack, path);
+	reader.open(*g_twp->_pack, path);
 	YackParser parser;
 	_cu.reset(parser.parse(&reader));
 	selectLabel(0, node);
diff --git a/engines/twp/dialog.h b/engines/twp/dialog.h
index a2744f0a6b9..7a0db5a0731 100644
--- a/engines/twp/dialog.h
+++ b/engines/twp/dialog.h
@@ -133,7 +133,7 @@ public:
 
 class ExpVisitor : public YackVisitor {
 public:
-	ExpVisitor(Dialog *dialog);
+	explicit ExpVisitor(Dialog *dialog);
 	virtual ~ExpVisitor() override;
 
 private:
diff --git a/engines/twp/enginedialogtarget.cpp b/engines/twp/enginedialogtarget.cpp
index 1f4d0815c71..0666abda964 100644
--- a/engines/twp/enginedialogtarget.cpp
+++ b/engines/twp/enginedialogtarget.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
 #include "twp/enginedialogtarget.h"
+#include "twp/hud.h"
 #include "twp/squtil.h"
 
 namespace Twp {
@@ -69,12 +71,12 @@ static Common::SharedPtr<Object> actorOrCurrent(const Common::String &name) {
 
 Color EngineDialogTarget::actorColor(const Common::String &actor) {
 	Common::SharedPtr<Object> act = actorOrCurrent(actor);
-	return g_twp->_hud.actorSlot(act)->verbUiColors.dialogNormal;
+	return g_twp->_hud->actorSlot(act)->verbUiColors.dialogNormal;
 }
 
 Color EngineDialogTarget::actorColorHover(const Common::String &actor) {
 	Common::SharedPtr<Object> act = actorOrCurrent(actor);
-	return g_twp->_hud.actorSlot(act)->verbUiColors.dialogHighlight;
+	return g_twp->_hud->actorSlot(act)->verbUiColors.dialogHighlight;
 }
 
 Common::SharedPtr<Motor> EngineDialogTarget::say(const Common::String &actor, const Common::String &text) {
diff --git a/engines/twp/font.cpp b/engines/twp/font.cpp
index 741b5c20bf5..790d759fcf6 100644
--- a/engines/twp/font.cpp
+++ b/engines/twp/font.cpp
@@ -20,7 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
 #include "twp/font.h"
+#include "twp/ggpack.h"
+#include "twp/resmanager.h"
 
 namespace Twp {
 
@@ -168,7 +171,7 @@ bool TokenReader::readToken(Token &token) {
 GGFont::~GGFont() {}
 
 void GGFont::load(const Common::String &path) {
-	SpriteSheet *spritesheet = g_twp->_resManager.spriteSheet(path);
+	SpriteSheet *spritesheet = g_twp->_resManager->spriteSheet(path);
 	int lineHeight = 0;
 	for (auto it = spritesheet->_frameTable.begin(); it != spritesheet->_frameTable.end(); it++) {
 		const SpriteSheetFrame &frame = it->_value;
@@ -195,12 +198,12 @@ BmFont::~BmFont() {}
 
 void BmFont::load(const Common::String &name) {
 	Common::String path = name + ".fnt";
-	if (!g_twp->_pack.assetExists(path.c_str())) {
+	if (!g_twp->_pack->assetExists(path.c_str())) {
 		path = name + "Font.fnt";
 	}
 	debugC(kDebugRes, "Load font %s", path.c_str());
 	GGPackEntryReader entry;
-	if (!entry.open(g_twp->_pack, path)) {
+	if (!entry.open(*g_twp->_pack, path)) {
 		error("error loading font %s", path.c_str());
 	}
 	char tmp[80];
@@ -261,8 +264,8 @@ Math::Vector2d Text::getBounds() {
 void Text::update() {
 	if (_dirty) {
 		_dirty = false;
-		_font = g_twp->_resManager.font(_fontName);
-		_texture = g_twp->_resManager.texture(_font->getName() + ".png");
+		_font = g_twp->_resManager->font(_fontName);
+		_texture = g_twp->_resManager->texture(_font->getName() + ".png");
 
 		// Reset
 		_vertices.clear();
diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index fc8d08494fc..08e2014e6cb 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -21,6 +21,11 @@
 
 #include "common/crc.h"
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/dialog.h"
+#include "twp/hud.h"
+#include "twp/resmanager.h"
+#include "twp/room.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/sqvm.h"
@@ -29,6 +34,7 @@
 #include "twp/squirrel/sqtable.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -54,7 +60,7 @@ static void shuffle(Common::Array<T> &array) {
 }
 
 static SQInteger activeVerb(HSQUIRRELVM v) {
-	sqpush(v, g_twp->_hud._verb.id.id);
+	sqpush(v, g_twp->_hud->_verb.id.id);
 	return 1;
 }
 
@@ -86,7 +92,7 @@ static SQInteger assetExists(HSQUIRRELVM v) {
 	const SQChar *filename;
 	if (SQ_FAILED(sq_getstring(v, 2, &filename)))
 		return sq_throwerror(v, "failed to get filename");
-	sqpush(v, g_twp->_pack.assetExists(filename));
+	sqpush(v, g_twp->_pack->assetExists(filename));
 	return 1;
 }
 
@@ -135,7 +141,7 @@ static SQInteger cameraBounds(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get yMin");
 	if (SQ_FAILED(sqget(v, 5, yMax)))
 		return sq_throwerror(v, "failed to get yMax");
-	g_twp->_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(xMin, yMin), Math::Vector2d(xMax, yMax)));
+	g_twp->_camera->setBounds(Rectf::fromMinMax(Math::Vector2d(xMin, yMin), Math::Vector2d(xMax, yMax)));
 	return 0;
 }
 
@@ -240,7 +246,7 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 	Math::Vector2d halfScreen(g_twp->_room->getScreenSize() / 2.f);
 	debugC(kDebugGenScript, "cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
 	g_twp->follow(nullptr);
-	g_twp->_camera.panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
+	g_twp->_camera->panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
 	return 0;
 }
 
@@ -301,13 +307,13 @@ static SQInteger findScreenPosition(HSQUIRRELVM v) {
 		SQInteger verb;
 		if (SQ_FAILED(sqget(v, 2, verb)))
 			return sq_throwerror(v, "failed to get verb");
-		ActorSlot *actorSlot = g_twp->_hud.actorSlot(g_twp->_actor);
+		ActorSlot *actorSlot = g_twp->_hud->actorSlot(g_twp->_actor);
 		if (!actorSlot)
 			return 0;
 		for (int i = 1; i < MAX_VERBS; i++) {
 			Verb vb = actorSlot->verbs[i];
 			if (vb.id.id == verb) {
-				SpriteSheet *verbSheet = g_twp->_resManager.spriteSheet("VerbSheet");
+				SpriteSheet *verbSheet = g_twp->_resManager->spriteSheet("VerbSheet");
 				const SpriteSheetFrame *verbFrame = &verbSheet->getFrame(Common::String::format("%s_en", vb.image.c_str()));
 				Math::Vector2d pos(verbFrame->spriteSourceSize.left + verbFrame->frame.width() / 2.f, verbFrame->sourceSize.getY() - verbFrame->spriteSourceSize.top - verbFrame->spriteSourceSize.height() + verbFrame->frame.height() / 2.f);
 				debugC(kDebugGenScript, "findScreenPosition(%lld) => %f,%f", verb, pos.getX(), pos.getY());
@@ -376,7 +382,7 @@ static SQInteger incutscene(HSQUIRRELVM v) {
 }
 
 static SQInteger indialog(HSQUIRRELVM v) {
-	sqpush(v, (int)g_twp->_dialog.getState());
+	sqpush(v, (int)g_twp->_dialog->getState());
 	return 1;
 }
 
@@ -475,7 +481,7 @@ static SQInteger loadArray(HSQUIRRELVM v) {
 	debugC(kDebugGenScript, "loadArray: %s", orgFilename);
 	Common::String filename = ResManager::getKey(orgFilename);
 	GGPackEntryReader entry;
-	entry.open(g_twp->_pack, g_twp->_pack.assetExists(filename.c_str()) ? filename : orgFilename);
+	entry.open(*g_twp->_pack, g_twp->_pack->assetExists(filename.c_str()) ? filename : orgFilename);
 	sq_newarray(v, 0);
 	while (!entry.eos()) {
 		Common::String line = entry.readLine();
@@ -530,7 +536,7 @@ static SQInteger pushSentence(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 3, choice)))
 			return sq_throwerror(v, "Failed to get choice");
 		// use pushSentence with VERB_DIALOG
-		g_twp->_dialog.choose(choice);
+		g_twp->_dialog->choose(choice);
 		return 0;
 	}
 
@@ -702,7 +708,7 @@ static SQInteger setVerb(HSQUIRRELVM v) {
 	debugC(kDebugGenScript, "setVerb %lld, %lld, %lld, %s", actorSlot, verbSlot, id, text.c_str());
 	VerbId verbId;
 	verbId.id = id;
-	g_twp->_hud._actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
+	g_twp->_hud->_actorSlots[actorSlot - 1].verbs[verbSlot] = Verb(verbId, image, fun, text, key, flags);
 	return 0;
 }
 
@@ -719,7 +725,7 @@ static SQInteger startDialog(HSQUIRRELVM v) {
 		}
 	}
 	Common::String actor = g_twp->_actor ? g_twp->_actor->_key : "";
-	g_twp->_dialog.start(actor, dialog, node);
+	g_twp->_dialog->start(actor, dialog, node);
 	return 0;
 }
 
diff --git a/engines/twp/gfx.cpp b/engines/twp/gfx.cpp
index 84da6c11236..268fc002dad 100644
--- a/engines/twp/gfx.cpp
+++ b/engines/twp/gfx.cpp
@@ -19,8 +19,10 @@
  *
  */
 
+#include "common/system.h"
 #include "twp/twp.h"
 #include "twp/gfx.h"
+#include "twp/shaders.h"
 
 namespace Twp {
 
diff --git a/engines/twp/hud.cpp b/engines/twp/hud.cpp
index 7ddf0cdfc87..8e65ae3540b 100644
--- a/engines/twp/hud.cpp
+++ b/engines/twp/hud.cpp
@@ -22,6 +22,7 @@
 #include "common/config-manager.h"
 #include "twp/twp.h"
 #include "twp/hud.h"
+#include "twp/resmanager.h"
 
 namespace Twp {
 
@@ -140,10 +141,10 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 		return;
 
 	// draw HUD background
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
 	bool classic = ConfMan.getBool("hudSentence");
 	const SpriteSheetFrame &backingFrame = gameSheet->getFrame(classic ? "ui_backing_tall" : "ui_backing");
-	Texture *gameTexture = g_twp->_resManager.texture(gameSheet->meta.image);
+	Texture *gameTexture = g_twp->_resManager->texture(gameSheet->meta.image);
 	float alpha = 0.33f; // prefs(UiBackingAlpha);
 	g_twp->getGfx().drawSprite(backingFrame.frame, *gameTexture, Color(0, 0, 0, alpha * getAlpha()), trsf);
 
@@ -152,8 +153,8 @@ void Hud::drawCore(Math::Matrix4 trsf) {
 	Color verbColor = verbHlt ? slot->verbUiColors.verbHighlight : Color();
 
 	// draw actor's verbs
-	SpriteSheet *verbSheet = g_twp->_resManager.spriteSheet("VerbSheet");
-	Texture *verbTexture = g_twp->_resManager.texture(verbSheet->meta.image);
+	SpriteSheet *verbSheet = g_twp->_resManager->spriteSheet("VerbSheet");
+	Texture *verbTexture = g_twp->_resManager->texture(verbSheet->meta.image);
 	Common::String lang = ConfMan.get("language");
 	bool retroVerbs = ConfMan.getBool("retroVerbs");
 	Common::String verbSuffix = retroVerbs ? "_retro" : "";
diff --git a/engines/twp/hud.h b/engines/twp/hud.h
index a2485916da2..10df3b63b08 100644
--- a/engines/twp/hud.h
+++ b/engines/twp/hud.h
@@ -23,7 +23,6 @@
 #include "twp/object.h"
 #include "twp/scenegraph.h"
 
-#define NUMVERBS 9
 #define MAX_VERBS 22
 #define NUMACTORS 6
 
diff --git a/engines/twp/lighting.cpp b/engines/twp/lighting.cpp
index 2d9f72bbf97..59f700d5c72 100644
--- a/engines/twp/lighting.cpp
+++ b/engines/twp/lighting.cpp
@@ -23,6 +23,7 @@
 #include "twp/lighting.h"
 #include "twp/room.h"
 #include "twp/twp.h"
+#include "twp/walkboxnode.h"
 
 namespace Twp {
 
@@ -195,7 +196,7 @@ void Lighting::update(const Lights &lights) {
 	if (_currentDebug != _debug) {
 		init("lighting", vshader, _debug ? debug_fshader : fshader);
 		_currentDebug = _debug;
-		g_twp->_lightingNode.setVisible(_debug);
+		g_twp->_lightingNode->setVisible(_debug);
 	}
 	_ambientLight = lights._ambientLight;
 	u_numberLights = 0;
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index e01e3c52d78..80595f9a952 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -19,19 +19,22 @@
  *
  */
 
+#include "backends/keymapper/keymapper.h"
+#include "backends/keymapper/action.h"
+#include "common/translation.h"
+#include "common/savefile.h"
+#include "engines/advancedDetector.h"
+#include "graphics/scaler.h"
 #include "gui/gui-manager.h"
 #include "gui/widgets/edittext.h"
 #include "gui/widgets/popup.h"
 #include "gui/ThemeEval.h"
-#include "common/translation.h"
-#include "common/savefile.h"
-#include "backends/keymapper/keymapper.h"
-#include "backends/keymapper/action.h"
-#include "graphics/scaler.h"
 #include "image/png.h"
+#include "twp/twp.h"
+#include "twp/detection.h"
 #include "twp/metaengine.h"
 #include "twp/detection.h"
-#include "twp/twp.h"
+#include "twp/savegame.h"
 #include "twp/time.h"
 #include "twp/actions.h"
 #include "twp/dialogs.h"
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 88babc2439e..51aeab307a2 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -22,7 +22,11 @@
 
 #include "common/config-manager.h"
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/squtil.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -337,11 +341,11 @@ void Talking::update(float elapsed) {
 
 	_elapsed += elapsed;
 	if (_obj->_sound) {
-		if (!g_twp->_audio.playing(_obj->_sound)) {
+		if (!g_twp->_audio->playing(_obj->_sound)) {
 			debugC(kDebugGame, "talking %s audio stopped", _obj->_key.c_str());
 			_obj->_sound = 0;
 		} else {
-			float e = static_cast<float>(g_twp->_audio.getElapsed(_obj->_sound)) / 1000.f;
+			float e = static_cast<float>(g_twp->_audio->getElapsed(_obj->_sound)) / 1000.f;
 			char letter = _lip.letter(e);
 			_obj->setHeadIndex(letterToIndex(letter));
 		}
@@ -368,14 +372,14 @@ int Talking::loadActorSpeech(const Common::String &name) {
 	Common::String filename(name);
 	filename.toUppercase();
 	filename += ".ogg";
-	if (g_twp->_pack.assetExists(filename.c_str())) {
+	if (g_twp->_pack->assetExists(filename.c_str())) {
 		Common::SharedPtr<SoundDefinition> soundDefinition(new SoundDefinition(filename));
 		if (!soundDefinition) {
 			debugC(kDebugGame, "File %s.ogg not found", name.c_str());
 		} else {
-			g_twp->_audio._soundDefs.push_back(soundDefinition);
-			int id = g_twp->_audio.play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
-			int duration = g_twp->_audio.getDuration(id);
+			g_twp->_audio->_soundDefs.push_back(soundDefinition);
+			int id = g_twp->_audio->play(soundDefinition, Audio::Mixer::SoundType::kSpeechSoundType, 0, 0, 1.f);
+			int duration = g_twp->_audio->getDuration(id);
 			debugC(kDebugGame, "talking %s audio id: %d, dur: %d", _obj->_key.c_str(), id, duration);
 			if (duration)
 				_duration = static_cast<float>(duration) / 1000.f;
@@ -395,7 +399,7 @@ void Talking::say(const Common::String &text) {
 	}
 	if (txt[0] == '@') {
 		int id = atoi(txt.c_str() + 1);
-		txt = g_twp->_textDb.getText(id);
+		txt = g_twp->_textDb->getText(id);
 
 		id = onTalkieId(id);
 		Common::String key = talkieKey();
@@ -404,15 +408,15 @@ void Talking::say(const Common::String &text) {
 		Common::String path = name + ".lip";
 
 		debugC(kDebugGame, "Load lip %s", path.c_str());
-		if (g_twp->_pack.assetExists(path.c_str())) {
+		if (g_twp->_pack->assetExists(path.c_str())) {
 			GGPackEntryReader entry;
-			entry.open(g_twp->_pack, path);
+			entry.open(*g_twp->_pack, path);
 			_lip.load(&entry);
 			debugC(kDebugGame, "Lip %s loaded", path.c_str());
 		}
 
 		if (_obj->_sound) {
-			g_twp->_audio.stop(_obj->_sound);
+			g_twp->_audio->stop(_obj->_sound);
 		}
 
 		_obj->_sound = loadActorSpeech(name);
@@ -469,7 +473,7 @@ void Talking::say(const Common::String &text) {
 
 		_obj->_sayNode->setPos(pos);
 		_obj->_sayNode->setAnchorNorm(Math::Vector2d(0.5f, 0.5f));
-		g_twp->_screenScene.addChild(_obj->_sayNode.get());
+		g_twp->_screenScene->addChild(_obj->_sayNode.get());
 	}
 
 	_elapsed = 0.f;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index b86a5de1d04..94bb75bd93b 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -20,6 +20,9 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/squtil.h"
 
 #define MIN_TALK_DIST 60
@@ -224,7 +227,7 @@ void Object::trig(const Common::String &name) {
 		if (!sound)
 			warning("Cannot trig sound '%s', sound not found (id=%lld, %s)", name.c_str(), id, _key.c_str());
 		else
-			g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+			g_twp->_audio->play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	}
 }
 
@@ -521,7 +524,7 @@ void Object::blinkRate(Common::SharedPtr<Object> obj, float min, float max) {
 
 void Object::setCostume(const Common::String &name, const Common::String &sheet) {
 	GGPackEntryReader entry;
-	entry.open(g_twp->_pack, name + ".json");
+	entry.open(*g_twp->_pack, name + ".json");
 
 	GGHashMapDecoder dec;
 	Common::ScopedPtr<Common::JSONValue> json(dec.open(&entry));
@@ -657,12 +660,12 @@ int Object::flags() {
 UseFlag Object::useFlag() {
 	int flags = getFlags();
 	if (flags & USE_WITH)
-		return ufUseWith;
+		return UseFlag::ufUseWith;
 	if (flags & USE_ON)
-		return ufUseOn;
+		return UseFlag::ufUseOn;
 	if (flags & USE_IN)
-		return ufUseIn;
-	return ufNone;
+		return UseFlag::ufUseIn;
+	return UseFlag::ufNone;
 }
 
 float Object::getScale() {
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 3ac94082041..937cd1003b9 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -54,7 +54,7 @@ enum Direction {
 	dBack = 8
 };
 
-enum UseFlag {
+enum class UseFlag {
 	ufNone,
 	ufUseWith,
 	ufUseOn,
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 3e61e7c5411..31db3c78953 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -20,6 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/hud.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 #include "twp/squirrel/squirrel.h"
@@ -29,6 +33,7 @@
 #include "twp/squirrel/sqtable.h"
 #include "twp/squirrel/sqfuncproto.h"
 #include "twp/squirrel/sqclosure.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
@@ -733,7 +738,7 @@ static SQInteger objectScreenSpace(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	g_twp->_screenScene.addChild(obj->_node.get());
+	g_twp->_screenScene->addChild(obj->_node.get());
 	return 0;
 }
 
@@ -888,7 +893,7 @@ static SQInteger objectValidVerb(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get verb");
 
 	if (g_twp->_actor) {
-		ActorSlot *slot = g_twp->_hud.actorSlot(g_twp->_actor);
+		ActorSlot *slot = g_twp->_hud->actorSlot(g_twp->_actor);
 		for (int i = 0; i < MAX_VERBS; i++) {
 			Verb *vb = &slot->verbs[i];
 			if (vb->id.id == verb) {
@@ -915,7 +920,7 @@ static SQInteger pickupObject(HSQUIRRELVM v) {
 		sq_getstackobj(v, 2, &o);
 		Common::String name;
 		sqgetf(o, "name", name);
-		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_twp->_textDb.getText(name).c_str()).c_str());
+		return sq_throwerror(v, Common::String::format("failed to get object %x, %s", o._type, g_twp->_textDb->getText(name).c_str()).c_str());
 	}
 	Common::SharedPtr<Object> actor;
 	if (sq_gettop(v) >= 3) {
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 0d1dbfdfdff..8c83187267b 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -22,6 +22,8 @@
 #include "common/config-manager.h"
 #include "image/png.h"
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/ggpack.h"
 #include "twp/resmanager.h"
 
 namespace Twp {
@@ -35,7 +37,7 @@ Common::String ResManager::getKey(const Common::String &path) {
 void ResManager::loadTexture(const Common::String &name) {
 	debugC(kDebugRes, "Load texture %s", name.c_str());
 	GGPackEntryReader r;
-	if (!r.open(g_twp->_pack, name)) {
+	if (!r.open(*g_twp->_pack, name)) {
 		error("Texture %s not found", name.c_str());
 	}
 	Image::PNGDecoder d;
@@ -59,7 +61,7 @@ Texture *ResManager::texture(const Common::String &name) {
 
 void ResManager::loadSpriteSheet(const Common::String &name) {
 	GGPackEntryReader r;
-	r.open(g_twp->_pack, name + ".json");
+	r.open(*g_twp->_pack, name + ".json");
 
 	// read all contents
 	Common::Array<char> data(r.size());
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 4b7b7c5679d..409cd867762 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -20,6 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/resmanager.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/squtil.h"
 #include "twp/clipper/clipper.hpp"
 
@@ -353,7 +357,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 	int width = 0;
 	for (size_t i = 0; i < backNames.size(); i++) {
 		Common::String name = backNames[i];
-		width += g_twp->_resManager.spriteSheet(room->_sheet)->getFrame(name).sourceSize.getX();
+		width += g_twp->_resManager->spriteSheet(room->_sheet)->getFrame(name).sourceSize.getX();
 	}
 	room->_roomSize.setX(width);
 }
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index 16429aa90cb..c76d5e2e34f 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -20,6 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/object.h"
+#include "twp/room.h"
+#include "twp/shaders.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 
@@ -368,23 +372,23 @@ static SQInteger roomEffect(HSQUIRRELVM v) {
 	SQInteger nArgs = sq_gettop(v);
 	if (roomEffect == RoomEffect::Ghost) {
 		if (nArgs == 14) {
-			sqget(v, 3, g_twp->_shaderParams.iFade);
-			sqget(v, 4, g_twp->_shaderParams.wobbleIntensity);
-			sqget(v, 6, g_twp->_shaderParams.shadows.rgba.r);
-			sqget(v, 7, g_twp->_shaderParams.shadows.rgba.g);
-			sqget(v, 8, g_twp->_shaderParams.shadows.rgba.b);
-			sqget(v, 9, g_twp->_shaderParams.midtones.rgba.r);
-			sqget(v, 10, g_twp->_shaderParams.midtones.rgba.g);
-			sqget(v, 11, g_twp->_shaderParams.midtones.rgba.b);
-			sqget(v, 12, g_twp->_shaderParams.highlights.rgba.r);
-			sqget(v, 13, g_twp->_shaderParams.highlights.rgba.g);
-			sqget(v, 14, g_twp->_shaderParams.highlights.rgba.b);
+			sqget(v, 3, g_twp->_shaderParams->iFade);
+			sqget(v, 4, g_twp->_shaderParams->wobbleIntensity);
+			sqget(v, 6, g_twp->_shaderParams->shadows.rgba.r);
+			sqget(v, 7, g_twp->_shaderParams->shadows.rgba.g);
+			sqget(v, 8, g_twp->_shaderParams->shadows.rgba.b);
+			sqget(v, 9, g_twp->_shaderParams->midtones.rgba.r);
+			sqget(v, 10, g_twp->_shaderParams->midtones.rgba.g);
+			sqget(v, 11, g_twp->_shaderParams->midtones.rgba.b);
+			sqget(v, 12, g_twp->_shaderParams->highlights.rgba.r);
+			sqget(v, 13, g_twp->_shaderParams->highlights.rgba.g);
+			sqget(v, 14, g_twp->_shaderParams->highlights.rgba.b);
 		} else {
-			g_twp->_shaderParams.iFade = 1.f;
-			g_twp->_shaderParams.wobbleIntensity = 1.f;
-			g_twp->_shaderParams.shadows = Color(-0.3f, 0.f, 0.f);
-			g_twp->_shaderParams.midtones = Color(-0.2f, 0.f, 0.1f);
-			g_twp->_shaderParams.highlights = Color(0.f, 0.f, 0.2f);
+			g_twp->_shaderParams->iFade = 1.f;
+			g_twp->_shaderParams->wobbleIntensity = 1.f;
+			g_twp->_shaderParams->shadows = Color(-0.3f, 0.f, 0.f);
+			g_twp->_shaderParams->midtones = Color(-0.2f, 0.f, 0.1f);
+			g_twp->_shaderParams->highlights = Color(0.f, 0.f, 0.2f);
 		}
 	}
 	g_twp->_room->_effect = (RoomEffect)effect;
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index 9baee3bca8b..f62dc98dc52 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -21,7 +21,13 @@
 
 #include "common/btea.h"
 #include "common/savefile.h"
+#include "twp/callback.h"
+#include "twp/detection.h"
+#include "twp/dialog.h"
 #include "twp/ggpack.h"
+#include "twp/hud.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/savegame.h"
 #include "twp/squtil.h"
 #include "twp/time.h"
@@ -74,7 +80,7 @@ static DialogConditionState parseState(Common::String &dialog) {
 		i++;
 	}
 
-	while (!g_twp->_pack.assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
+	while (!g_twp->_pack->assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
 		dialogName += dialog[i];
 		i++;
 	}
@@ -460,14 +466,14 @@ void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
 	for (size_t i = 0; i < jSelectableActors.size(); i++) {
 		const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
 		Common::SharedPtr<Object> act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
-		g_twp->_hud._actorSlots[i].actor = act;
-		g_twp->_hud._actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
+		g_twp->_hud->_actorSlots[i].actor = act;
+		g_twp->_hud->_actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
 	}
 }
 
 void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 	debugC(kDebugGame, "loadDialog");
-	g_twp->_dialog._states.clear();
+	g_twp->_dialog->_states.clear();
 	for (auto it = json.begin(); it != json.end(); it++) {
 		Common::String dialog(it->_key);
 		// dialog format: mode dialog number actor
@@ -479,7 +485,7 @@ void SaveGameManager::loadDialog(const Common::JSONObject &json) {
 		// $: showonceever
 		// ^: temponce
 		DialogConditionState state = parseState(dialog);
-		g_twp->_dialog._states.push_back(state);
+		g_twp->_dialog->_states.push_back(state);
 		// TODO: what to do with this dialog value ?
 		// let value = property.second.getInt()
 	}
@@ -545,7 +551,7 @@ void SaveGameManager::loadInventory(const Common::JSONValue *json) {
 		const Common::JSONObject &jInventory = json->asObject();
 		const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
 		for (int i = 0; i < NUMACTORS; i++) {
-			Common::SharedPtr<Object> a(g_twp->_hud._actorSlots[i].actor);
+			Common::SharedPtr<Object> a(g_twp->_hud->_actorSlots[i].actor);
 			if (a) {
 				a->_inventory.clear();
 				const Common::JSONObject &jSlot = jSlots[i]->asObject();
@@ -788,8 +794,8 @@ Common::String createJDlgStateKey(const DialogConditionState &state) {
 
 static Common::JSONValue *createJDialog() {
 	Common::JSONObject json;
-	for (size_t i = 0; i < g_twp->_dialog._states.size(); i++) {
-		const DialogConditionState &state = g_twp->_dialog._states[i];
+	for (size_t i = 0; i < g_twp->_dialog->_states.size(); i++) {
+		const DialogConditionState &state = g_twp->_dialog->_states[i];
 		if (state.mode != TempOnce) {
 			// TODO: value should be 1 or another value ?
 			json[createJDlgStateKey(state)] = new Common::JSONValue(state.mode == ShowOnce ? 2LL : 1LL);
@@ -822,7 +828,7 @@ static Common::JSONValue *createJSelectableActor(const ActorSlot &slot) {
 static Common::JSONValue *createJSelectableActors() {
 	Common::JSONArray json;
 	for (int i = 0; i < NUMACTORS; i++) {
-		const ActorSlot &slot = g_twp->_hud._actorSlots[i];
+		const ActorSlot &slot = g_twp->_hud->_actorSlots[i];
 		json.push_back(createJSelectableActor(slot));
 	}
 	return new Common::JSONValue(json);
@@ -875,7 +881,7 @@ static Common::JSONValue *createJInventory(const ActorSlot &slot) {
 static Common::JSONValue *createJInventory() {
 	Common::JSONArray slots;
 	for (int i = 0; i < NUMACTORS; i++) {
-		const ActorSlot &slot = g_twp->_hud._actorSlots[i];
+		const ActorSlot &slot = g_twp->_hud->_actorSlots[i];
 		slots.push_back(createJInventory(slot));
 	}
 	Common::JSONObject json;
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 44eaa0bac0f..0f73fde0287 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -23,8 +23,12 @@
 #include "common/algorithm.h"
 #include "common/config-manager.h"
 #include "twp/twp.h"
-#include "twp/scenegraph.h"
+#include "twp/detection.h"
 #include "twp/lighting.h"
+#include "twp/object.h"
+#include "twp/resmanager.h"
+#include "twp/room.h"
+#include "twp/scenegraph.h"
 
 namespace Twp {
 
@@ -274,8 +278,8 @@ void ParallaxNode::onDrawChildren(Math::Matrix4 trsf) {
 
 void ParallaxNode::drawCore(Math::Matrix4 trsf) {
 	Gfx &gfx = g_twp->getGfx();
-	SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
-	Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
+	SpriteSheet *sheet = g_twp->_resManager->spriteSheet(_sheet);
+	Texture *texture = g_twp->_resManager->texture(sheet->meta.image);
 
 	// enable debug lighting ?
 	if (_zOrder == 0 && g_twp->_lighting->_debug) {
@@ -398,16 +402,16 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 			}
 		}
 		if (_sheet == "raw") {
-			Texture *texture = g_twp->_resManager.texture(frame);
+			Texture *texture = g_twp->_resManager->texture(frame);
 			Math::Vector3d pos(-texture->width / 2.f, -texture->height / 2.f, 0.f);
 			trsf.translate(pos);
 			g_twp->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
 		} else {
-			const SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
+			const SpriteSheet *sheet = g_twp->_resManager->spriteSheet(_sheet);
 			const SpriteSheetFrame *sf = sheet->frame(frame);
 			if (!sf)
 				return;
-			Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
+			Texture *texture = g_twp->_resManager->texture(sheet->meta.image);
 			if (_obj->_lit) {
 				g_twp->getGfx().use(g_twp->_lighting.get());
 				Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
@@ -499,8 +503,8 @@ Common::String InputState::getCursorName() const {
 void InputState::drawCore(Math::Matrix4 trsf) {
 	Common::String cursorName = getCursorName();
 	// draw cursor
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager->texture(gameSheet->meta.image);
 	if (ConfMan.getBool("hudSentence") && _hotspot) {
 		cursorName = "hotspot_" + cursorName;
 	}
@@ -591,8 +595,8 @@ void Inventory::drawSprite(const SpriteSheetFrame &sf, Texture *texture, Color c
 
 void Inventory::drawArrows(Math::Matrix4 trsf) {
 	bool isRetro = ConfMan.getBool("retroVerbs");
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager->texture(gameSheet->meta.image);
 	const SpriteSheetFrame *arrowUp = &gameSheet->getFrame(isRetro ? "scroll_up_retro" : "scroll_up");
 	const SpriteSheetFrame *arrowDn = &gameSheet->getFrame(isRetro ? "scroll_down_retro" : "scroll_down");
 	float alphaUp = hasUpArrow(_actor) ? 1.f : 0.f;
@@ -607,8 +611,8 @@ void Inventory::drawArrows(Math::Matrix4 trsf) {
 }
 
 void Inventory::drawBack(Math::Matrix4 trsf) {
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager->texture(gameSheet->meta.image);
 	const SpriteSheetFrame *back = &gameSheet->getFrame("inventory_background");
 
 	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + back->sourceSize.getX() / 2.f;
@@ -635,8 +639,8 @@ void Inventory::drawBack(Math::Matrix4 trsf) {
 void Inventory::drawItems(Math::Matrix4 trsf) {
 	float startOffsetX = SCREEN_WIDTH / 2.f + ARROWWIDTH + MARGIN + BACKWIDTH / 2.f;
 	float startOffsetY = MARGINBOTTOM + 1.5f * BACKHEIGHT + BACKOFFSET;
-	SpriteSheet *itemsSheet = g_twp->_resManager.spriteSheet("InventoryItems");
-	Texture *texture = g_twp->_resManager.texture(itemsSheet->meta.image);
+	SpriteSheet *itemsSheet = g_twp->_resManager->spriteSheet("InventoryItems");
+	Texture *texture = g_twp->_resManager->texture(itemsSheet->meta.image);
 	int count = MIN(NUMOBJECTS, (int)(_actor->_inventory.size() - _actor->_inventoryOffset * NUMOBJECTSBYROW));
 
 	for (int i = 0; i < count; i++) {
@@ -773,7 +777,7 @@ void SpriteNode::setSprite(const Common::String &sheet, const Common::String &fr
 }
 
 void SpriteNode::drawCore(Math::Matrix4 trsf) {
-	SpriteSheet *sheet = g_twp->_resManager.spriteSheet(_sheet);
+	SpriteSheet *sheet = g_twp->_resManager->spriteSheet(_sheet);
 	const SpriteSheetFrame *frame = &sheet->getFrame(_frame);
 
 	Common::Rect rect = frame->frame;
@@ -783,7 +787,7 @@ void SpriteNode::drawCore(Math::Matrix4 trsf) {
 	Math::Vector2d anchor((int)(x), (int)(y));
 	setAnchor(anchor);
 
-	Texture *texture = g_twp->_resManager.texture(sheet->meta.image);
+	Texture *texture = g_twp->_resManager->texture(sheet->meta.image);
 	g_twp->getGfx().drawSprite(rect, *texture, getComputedColor(), trsf);
 }
 
@@ -830,8 +834,8 @@ void HotspotMarkerNode::drawSprite(const SpriteSheetFrame &sf, Texture *texture,
 }
 
 void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
-	SpriteSheet *gameSheet = g_twp->_resManager.spriteSheet("GameSheet");
-	Texture *texture = g_twp->_resManager.texture(gameSheet->meta.image);
+	SpriteSheet *gameSheet = g_twp->_resManager->spriteSheet("GameSheet");
+	Texture *texture = g_twp->_resManager->texture(gameSheet->meta.image);
 	const SpriteSheetFrame *frame = &gameSheet->getFrame("hotspot_marker");
 	Color color = Color::create(255, 165, 0);
 	for (size_t i = 0; i < g_twp->_room->_layers.size(); i++) {
diff --git a/engines/twp/soundlib.cpp b/engines/twp/soundlib.cpp
index aefa1f45608..58031a60d0a 100644
--- a/engines/twp/soundlib.cpp
+++ b/engines/twp/soundlib.cpp
@@ -20,6 +20,8 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
+#include "twp/object.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
 
@@ -32,7 +34,7 @@ public:
 
 	virtual void trig() override {
 		int i = g_twp->getRandomSource().getRandomNumber(_sounds.size() - 1);
-		g_twp->_audio.play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 1.f, _objId);
+		g_twp->_audio->play(_sounds[i], Audio::Mixer::SoundType::kPlainSoundType, 0, 0.f, 1.f, _objId);
 	}
 
 private:
@@ -85,7 +87,7 @@ static SQInteger defineSound(HSQUIRRELVM v) {
 	if (SQ_FAILED(sqget(v, 2, filename)))
 		return sq_throwerror(v, "failed to get filename");
 	Common::SharedPtr<SoundDefinition> sound(new SoundDefinition(filename));
-	g_twp->_audio._soundDefs.push_back(sound);
+	g_twp->_audio->_soundDefs.push_back(sound);
 	debugC(kDebugSndScript, "defineSound(%s)-> %d", filename.c_str(), sound->getId());
 	sqpush(v, sound->getId());
 	return 1;
@@ -101,7 +103,7 @@ static SQInteger fadeOutSound(HSQUIRRELVM v) {
 	float t;
 	if (SQ_FAILED(sqget(v, 3, t)))
 		return sq_throwerror(v, "failed to get fadeOut time");
-	g_twp->_audio.fadeOut(sound, t);
+	g_twp->_audio->fadeOut(sound, t);
 	return 0;
 }
 
@@ -113,7 +115,7 @@ static SQInteger isSoundPlaying(HSQUIRRELVM v) {
 	SQInteger soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-	sqpush(v, g_twp->_audio.playing(soundId));
+	sqpush(v, g_twp->_audio->playing(soundId));
 	return 1;
 }
 
@@ -134,10 +136,10 @@ static SQInteger playObjectSound(HSQUIRRELVM v) {
 	}
 
 	if (obj->_sound) {
-		g_twp->_audio.stop(obj->_sound);
+		g_twp->_audio->stop(obj->_sound);
 	}
 
-	int soundId = g_twp->_audio.play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
+	int soundId = g_twp->_audio->play(soundDef, Audio::Mixer::SoundType::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
 	obj->_sound = soundId;
 	sqpush(v, soundId);
 	return 1;
@@ -157,7 +159,7 @@ static SQInteger playSound(HSQUIRRELVM v) {
 		sqget(v, 2, soundId);
 		return sq_throwerror(v, Common::String::format("failed to get sound: %lld", soundId).c_str());
 	}
-	int soundId = g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	int soundId = g_twp->_audio->play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -178,7 +180,7 @@ static SQInteger playSoundVolume(HSQUIRRELVM v) {
 	Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 2);
 	if (!sound)
 		return sq_throwerror(v, "failed to get sound");
-	int soundId = g_twp->_audio.play(sound, Audio::Mixer::SoundType::kPlainSoundType);
+	int soundId = g_twp->_audio->play(sound, Audio::Mixer::SoundType::kPlainSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -216,7 +218,7 @@ static SQInteger loopMusic(HSQUIRRELVM v) {
 	if (numArgs == 4) {
 		sqget(v, 4, fadeInTime);
 	}
-	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kMusicSoundType, loopTimes, fadeInTime);
+	int soundId = g_twp->_audio->play(sound, Audio::Mixer::kMusicSoundType, loopTimes, fadeInTime);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -241,7 +243,7 @@ static SQInteger loopObjectSound(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get fadeInTime");
 		}
 	}
-	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
+	int soundId = g_twp->_audio->play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime, 1.f, obj->getId());
 	sqpush(v, soundId);
 	return 1;
 }
@@ -281,7 +283,7 @@ static SQInteger loopSound(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get fadeInTime");
 		}
 	}
-	int soundId = g_twp->_audio.play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
+	int soundId = g_twp->_audio->play(sound, Audio::Mixer::kPlainSoundType, loopTimes, fadeInTime);
 	debugC(kDebugSndScript, "loopSound %s: %d", sound->getName().c_str(), soundId);
 	sqpush(v, soundId);
 	return 1;
@@ -308,10 +310,10 @@ static SQInteger masterSoundVolume(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 2, volume))) {
 			return sq_throwerror(v, "failed to get volume");
 		}
-		g_twp->_audio.setMasterVolume(volume);
+		g_twp->_audio->setMasterVolume(volume);
 		return 0;
 	}
-	volume = g_twp->_audio.getMasterVolume();
+	volume = g_twp->_audio->getMasterVolume();
 	sqpush(v, volume);
 	return 1;
 }
@@ -324,7 +326,7 @@ static SQInteger playMusic(HSQUIRRELVM v) {
 	Common::SharedPtr<SoundDefinition> soundDef = sqsounddef(v, 2);
 	if (!soundDef)
 		return sq_throwerror(v, "failed to get music");
-	int soundId = g_twp->_audio.play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
+	int soundId = g_twp->_audio->play(soundDef, Audio::Mixer::SoundType::kMusicSoundType);
 	sqpush(v, soundId);
 	return 1;
 }
@@ -349,7 +351,7 @@ static SQInteger soundVolume(HSQUIRRELVM v) {
 	float volume = 1.0f;
 	if (SQ_FAILED(sqget(v, 3, volume)))
 		return sq_throwerror(v, "failed to get volume");
-	g_twp->_audio.setVolume(soundId, volume);
+	g_twp->_audio->setVolume(soundId, volume);
 	return 0;
 }
 
@@ -368,7 +370,7 @@ static SQInteger stopSound(HSQUIRRELVM v) {
 	SQInteger soundId;
 	if (SQ_FAILED(sqget(v, 2, soundId)))
 		return sq_throwerror(v, "failed to get sound");
-	g_twp->_audio.stop(soundId);
+	g_twp->_audio->stop(soundId);
 	return 0;
 }
 
diff --git a/engines/twp/spritesheet.cpp b/engines/twp/spritesheet.cpp
index b9b13110d98..6af2c04bc6a 100644
--- a/engines/twp/spritesheet.cpp
+++ b/engines/twp/spritesheet.cpp
@@ -23,6 +23,7 @@
 #include "common/formats/json.h"
 #include "common/config-manager.h"
 #include "twp/twp.h"
+#include "twp/resmanager.h"
 #include "twp/spritesheet.h"
 
 namespace Twp {
diff --git a/engines/twp/squtil.cpp b/engines/twp/squtil.cpp
index 7b3b3fee71b..ab898c8a9cd 100644
--- a/engines/twp/squtil.cpp
+++ b/engines/twp/squtil.cpp
@@ -19,7 +19,10 @@
  *
  */
 
+#include "twp/detection.h"
 #include "twp/lighting.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/squtil.h"
 #include "twp/thread.h"
 #include "twp/squirrel/squirrel.h"
@@ -337,8 +340,8 @@ Common::SharedPtr<Object> sqactor(HSQUIRRELVM v, int i) {
 }
 
 Common::SharedPtr<SoundDefinition> sqsounddef(int id) {
-	for (size_t i = 0; i < g_twp->_audio._soundDefs.size(); i++) {
-		Common::SharedPtr<SoundDefinition> sound = g_twp->_audio._soundDefs[i];
+	for (size_t i = 0; i < g_twp->_audio->_soundDefs.size(); i++) {
+		Common::SharedPtr<SoundDefinition> sound = g_twp->_audio->_soundDefs[i];
 		if (sound->getId() == id)
 			return sound;
 	}
diff --git a/engines/twp/squtil.h b/engines/twp/squtil.h
index f963238dc72..d1ddca84e26 100644
--- a/engines/twp/squtil.h
+++ b/engines/twp/squtil.h
@@ -26,6 +26,7 @@
 #include "common/str.h"
 #include "common/util.h"
 #include "twp/twp.h"
+#include "twp/audio.h"
 #include "twp/vm.h"
 #include "twp/squirrel/squirrel.h"
 
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 16d628aaecd..1067b905375 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -20,10 +20,15 @@
  */
 
 #include "twp/twp.h"
+#include "twp/callback.h"
+#include "twp/dialog.h"
+#include "twp/object.h"
+#include "twp/room.h"
+#include "twp/savegame.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
-#include "twp/thread.h"
 #include "twp/task.h"
+#include "twp/thread.h"
 #include "twp/squirrel/sqvm.h"
 #include "twp/squirrel/sqstring.h"
 #include "twp/squirrel/sqstate.h"
@@ -258,7 +263,7 @@ static SQInteger breakwhileanimating(HSQUIRRELVM v) {
 
 struct CameraMoving {
 	bool operator()() {
-		return g_twp->_camera.isMoving();
+		return g_twp->_camera->isMoving();
 	}
 };
 
@@ -284,7 +289,7 @@ static SQInteger breakwhilecutscene(HSQUIRRELVM v) {
 
 struct DialogRunning {
 	bool operator()() {
-		return g_twp->_dialog.getState() != DialogState::None;
+		return g_twp->_dialog->getState() != DialogState::None;
 	}
 };
 
@@ -336,7 +341,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 			return 0;
 		}
 		return breakwhilecond(
-			v, [id] { return g_twp->_audio.playing(id); }, "breakwhilerunningsound(%d)", id);
+			v, [id] { return g_twp->_audio->playing(id); }, "breakwhilerunningsound(%d)", id);
 	}
 	return breakwhilecond(
 		v, [id] { return sqthread(id) != nullptr; }, "breakwhilerunning(%d)", id);
@@ -436,7 +441,7 @@ struct SoundPlaying {
 	explicit SoundPlaying(int soundId) : _soundId(soundId) {}
 
 	bool operator()() {
-		return g_twp->_audio.playing(_soundId);
+		return g_twp->_audio->playing(_soundId);
 	}
 
 private:
@@ -512,10 +517,10 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		SQInteger enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
-		g_twp->_saveGameManager._autoSave = enabled != 0;
+		g_twp->_saveGameManager->_autoSave = enabled != 0;
 	} break;
 	case EX_AUTOSAVE: {
-		if (g_twp->_saveGameManager._autoSave) {
+		if (g_twp->_saveGameManager->_autoSave) {
 			g_twp->saveGameState(0, "", true);
 		}
 	} break;
@@ -523,7 +528,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		SQInteger enabled;
 		if (SQ_FAILED(sqget(v, 3, enabled)))
 			return sq_throwerror(v, "Failed to get enabled");
-		g_twp->_saveGameManager._allowSaveGame = enabled != 0;
+		g_twp->_saveGameManager->_allowSaveGame = enabled != 0;
 	} break;
 	case EX_POP_CHARACTER_SELECTION:
 		// seems not to be used
@@ -536,7 +541,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		Common::SharedPtr<SoundDefinition> sound = sqsounddef(v, 3);
 		if (!sound)
 			return sq_throwerror(v, "failed to get sound for EX_BUTTON_HOVER_SOUND");
-		g_twp->_audio._soundHover = sound;
+		g_twp->_audio->_soundHover = sound;
 	} break;
 	case EX_RESTART:
 		warning("TODO: exCommand EX_RESTART: not implemented");
diff --git a/engines/twp/task.h b/engines/twp/task.h
index 79be06cc9a1..72d41418bba 100644
--- a/engines/twp/task.h
+++ b/engines/twp/task.h
@@ -23,6 +23,7 @@
 #define TWP_TASK_H
 
 #include "common/str.h"
+#include "twp/detection.h"
 #include "twp/thread.h"
 
 namespace Twp {
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index edf32fff1cd..1b2886308ec 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
 #include "twp/ids.h"
 #include "twp/thread.h"
 #include "twp/squtil.h"
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index d7c495b5416..679dc9c6422 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "twp/twp.h"
+#include "twp/detection.h"
 #include "twp/squtil.h"
+#include "twp/tsv.h"
 
 namespace Twp {
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 8cff87a28e2..4c8419e196d 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -49,19 +49,31 @@
 #include "common/config-manager.h"
 #include "common/events.h"
 #include "common/savefile.h"
-#include "image/png.h"
 #include "engines/util.h"
+#include "graphics/screen.h"
 #include "graphics/opengl/system_headers.h"
+#include "image/png.h"
 
 #include "twp/twp.h"
+#include "twp/actions.h"
+#include "twp/callback.h"
 #include "twp/console.h"
+#include "twp/debugtools.h"
+#include "twp/detection.h"
+#include "twp/enginedialogtarget.h"
+#include "twp/hud.h"
 #include "twp/lighting.h"
-#include "twp/thread.h"
+#include "twp/resmanager.h"
+#include "twp/room.h"
+#include "twp/savegame.h"
+#include "twp/scenegraph.h"
+#include "twp/shaders.h"
 #include "twp/squtil.h"
 #include "twp/task.h"
-#include "twp/enginedialogtarget.h"
-#include "twp/actions.h"
-#include "twp/debugtools.h"
+#include "twp/thread.h"
+#include "twp/tsv.h"
+#include "twp/vm.h"
+#include "twp/walkboxnode.h"
 
 namespace Twp {
 
@@ -76,19 +88,33 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	  _gameDescription(gameDesc),
 	  _randomSource("Twp") {
 	g_twp = this;
-	_dialog._tgt.reset(new EngineDialogTarget());
+	_dialog.reset(new Dialog());
+	_dialog->_tgt.reset(new EngineDialogTarget());
 	sq_resetobject(&_defaultObj);
-	_screenScene.setName("Screen");
-	_scene.addChild(&_walkboxNode);
-	_screenScene.addChild(&_pathNode);
-	_screenScene.addChild(&_lightingNode);
-	_screenScene.addChild(&_hotspotMarker);
-	_screenScene.addChild(&_inputState);
-	_screenScene.addChild(&_sentence);
-	_screenScene.addChild(&_dialog);
-	_screenScene.addChild(&_uiInv);
-	_screenScene.addChild(&_actorSwitcher);
-	_screenScene.addChild(&_noOverride);
+
+	_audio.reset(new AudioSystem());
+	_scene.reset(new Scene());
+	_screenScene.reset(new Scene());
+	_walkboxNode.reset(new WalkboxNode());
+	_pathNode.reset(new PathNode());
+	_hotspotMarker.reset(new HotspotMarkerNode());
+	_lightingNode.reset(new LightingNode());
+	_noOverride.reset(new NoOverrideNode());
+	_hud.reset(new Hud());
+	_pack.reset(new GGPackSet());
+	_saveGameManager.reset(new SaveGameManager());
+
+	_screenScene->setName("Screen");
+	_scene->addChild(_walkboxNode.get());
+	_screenScene->addChild(_pathNode.get());
+	_screenScene->addChild(_lightingNode.get());
+	_screenScene->addChild(_hotspotMarker.get());
+	_screenScene->addChild(&_inputState);
+	_screenScene->addChild(&_sentence);
+	_screenScene->addChild(_dialog.get());
+	_screenScene->addChild(&_uiInv);
+	_screenScene->addChild(&_actorSwitcher);
+	_screenScene->addChild(_noOverride.get());
 }
 
 TwpEngine::~TwpEngine() {
@@ -247,7 +273,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					cancelSentence(_actor);
 					if (_actor->_room == _room)
 						Object::walk(_actor, (Vector2i)roomPos);
-					_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+					_hud->_verb = _hud->actorSlot(_actor)->verbs[0];
 					_holdToMove = true;
 				}
 			}
@@ -264,40 +290,40 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 }
 
 Verb TwpEngine::verb() {
-	Verb result = _hud._verb;
+	Verb result = _hud->_verb;
 	if (result.id.id == VERB_WALKTO && _noun1 && _noun1->inInventory())
-		result = *_hud.actorSlot(_actor)->getVerb(_noun1->defaultVerbId());
+		result = *_hud->actorSlot(_actor)->getVerb(_noun1->defaultVerbId());
 	else if (_actor) {
-		result = *_hud.actorSlot(_actor)->getVerb(_hud._verb.id.id);
+		result = *_hud->actorSlot(_actor)->getVerb(_hud->_verb.id.id);
 	}
 	return result;
 }
 
 Common::String TwpEngine::cursorText() {
 	Common::String result;
-	if (_dialog.getState() == DialogState::None && _inputState.getInputActive()) {
-		if (_hud.isVisible() && _hud._over) {
-			return _hud._verb.id.id > 1 ? _textDb.getText(verb().text) : "";
+	if (_dialog->getState() == DialogState::None && _inputState.getInputActive()) {
+		if (_hud->isVisible() && _hud->_over) {
+			return _hud->_verb.id.id > 1 ? _textDb->getText(verb().text) : "";
 		}
 
 		// give can be used only on inventory and talkto to talkable objects (actors)
-		result = !_noun1 || (_hud._verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud._verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb.getText(_noun1->getName());
+		result = !_noun1 || (_hud->_verb.id.id == VERB_GIVE && !_noun1->inInventory()) || (_hud->_verb.id.id == VERB_TALKTO && !(_noun1->getFlags() & TALKABLE)) ? "" : _textDb->getText(_noun1->getName());
 
 		// add verb if not walk to or if noun1 is present
-		if ((_hud._verb.id.id > 1) || (result.size() > 0)) {
+		if ((_hud->_verb.id.id > 1) || (result.size() > 0)) {
 			// if inventory, use default verb instead of walkto
 			Common::String verbText = verb().text;
-			result = result.size() > 0 ? Common::String::format("%s %s", _textDb.getText(verbText).c_str(), result.c_str()) : _textDb.getText(verbText);
-			if (_useFlag == ufUseWith)
-				result += " " + _textDb.getText(10000);
-			else if (_useFlag == ufUseOn)
-				result += " " + _textDb.getText(10001);
-			else if (_useFlag == ufUseIn)
-				result += " " + _textDb.getText(10002);
-			else if (_useFlag == ufGiveTo)
-				result += " " + _textDb.getText(10003);
+			result = result.size() > 0 ? Common::String::format("%s %s", _textDb->getText(verbText).c_str(), result.c_str()) : _textDb->getText(verbText);
+			if (_useFlag == UseFlag::ufUseWith)
+				result += " " + _textDb->getText(10000);
+			else if (_useFlag == UseFlag::ufUseOn)
+				result += " " + _textDb->getText(10001);
+			else if (_useFlag == UseFlag::ufUseIn)
+				result += " " + _textDb->getText(10002);
+			else if (_useFlag == UseFlag::ufGiveTo)
+				result += " " + _textDb->getText(10003);
 			if (_noun2)
-				result += " " + _textDb.getText(_noun2->getName());
+				result += " " + _textDb->getText(_noun2->getName());
 		}
 	}
 	return result;
@@ -365,13 +391,13 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 	if (_actor) {
 		// add current actor first
 		{
-			ActorSlot *slot = _hud.actorSlot(_actor);
+			ActorSlot *slot = _hud->actorSlot(_actor);
 			result.push_back(actorSwitcherSlot(slot));
 		}
 
 		// then other selectable actors
 		for (int i = 0; i < NUMACTORS; i++) {
-			ActorSlot *slot = &_hud._actorSlots[i];
+			ActorSlot *slot = &_hud->_actorSlots[i];
 			if (slot->selectable && slot->actor && (slot->actor != _actor) && (slot->actor->_room->_name != "Void"))
 				result.push_back(actorSwitcherSlot(slot));
 		}
@@ -422,26 +448,26 @@ void TwpEngine::update(float elapsed) {
 	_time += elapsed;
 	_frameCounter++;
 
-	_audio.update(elapsed);
-	_noOverride.update(elapsed);
+	_audio->update(elapsed);
+	_noOverride->update(elapsed);
 
 	// update mouse pos
 	Math::Vector2d scrPos = winToScreen(_cursor.pos);
-	_inputState.setVisible(_inputState.getShowCursor() || _dialog.getState() == WaitingForChoice);
+	_inputState.setVisible(_inputState.getShowCursor() || _dialog->getState() == WaitingForChoice);
 	_inputState.setPos(scrPos);
 	_sentence.setPos(scrPos);
-	_dialog.setMousePos(scrPos);
+	_dialog->setMousePos(scrPos);
 
 	if (_room) {
 		// update nouns and useFlag
 		Math::Vector2d roomPos = screenToRoom(scrPos);
 		if (_room->_fullscreen == FULLSCREENROOM) {
-			if ((_hud._verb.id.id == VERB_USE) && (_useFlag != ufNone)) {
+			if ((_hud->_verb.id.id == VERB_USE) && (_useFlag != UseFlag::ufNone)) {
 				objsAt(roomPos, GetUseNoun2(_noun2));
-			} else if (_hud._verb.id.id == VERB_GIVE) {
-				if (_useFlag != ufGiveTo) {
+			} else if (_hud->_verb.id.id == VERB_GIVE) {
+				if (_useFlag != UseFlag::ufGiveTo) {
 					_noun1 = inventoryAt(roomPos);
-					_useFlag = ufNone;
+					_useFlag = UseFlag::ufNone;
 					_noun2 = nullptr;
 				} else {
 					objsAt(roomPos, GetGiveableNoun2(_noun2));
@@ -450,7 +476,7 @@ void TwpEngine::update(float elapsed) {
 				}
 			} else {
 				_noun1 = objAt(roomPos);
-				_useFlag = ufNone;
+				_useFlag = UseFlag::ufNone;
 				_noun2 = nullptr;
 			}
 
@@ -480,17 +506,17 @@ void TwpEngine::update(float elapsed) {
 			}
 
 			_inputState.setHotspot(_noun1 != nullptr);
-			bool hudVisible = _inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog.getState() == DialogState::None && !_cutscene;
-			_hud.setVisible(hudVisible);
-			_sentence.setVisible(_hud.isVisible());
+			bool hudVisible = _inputState.getInputActive() && _inputState.getInputVerbsActive() && _dialog->getState() == DialogState::None && !_cutscene;
+			_hud->setVisible(hudVisible);
+			_sentence.setVisible(_hud->isVisible());
 			_uiInv.setVisible(hudVisible);
-			_actorSwitcher.setVisible((_dialog.getState() == DialogState::None) && !_cutscene);
+			_actorSwitcher.setVisible((_dialog->getState() == DialogState::None) && !_cutscene);
 			// Common::String cursortxt = Common::String::format("%s (%d, %d) - (%d, %d)", cursorText().c_str(), (int)roomPos.getX(), (int)roomPos.getY(), (int)scrPos.getX(), (int)scrPos.getY());
 			//_sentence.setText(cursortxt.c_str());
 			_sentence.setText(cursorText());
 
 			// call clickedAt if any button down
-			if ((_inputState.getInputActive() && _dialog.getState() == DialogState::None) && !_hud.isOver()) {
+			if ((_inputState.getInputActive() && _dialog->getState() == DialogState::None) && !_hud->isOver()) {
 				if (_cursor.isLeftDown() || _cursor.isRightDown()) {
 					clickedAt(scrPos);
 				} else if (_cursor.leftDown || _cursor.rightDown) {
@@ -508,10 +534,10 @@ void TwpEngine::update(float elapsed) {
 				}
 			}
 		} else {
-			_hud.setVisible(false);
+			_hud->setVisible(false);
 			_uiInv.setVisible(false);
 			_noun1 = objAt(roomPos);
-			Common::String cText = !_noun1 ? "" : _textDb.getText(_noun1->getName());
+			Common::String cText = !_noun1 ? "" : _textDb->getText(_noun1->getName());
 			_sentence.setText(cText);
 			_inputState.setCursorShape(CursorShape::Normal);
 			if (_cursor.leftDown)
@@ -519,11 +545,11 @@ void TwpEngine::update(float elapsed) {
 		}
 	}
 
-	_dialog.update(elapsed);
+	_dialog->update(elapsed);
 	_fadeShader->_elapsed += elapsed;
 
 	// update camera
-	_camera.update(_room, _followActor, elapsed);
+	_camera->update(_room, _followActor, elapsed);
 
 	// update actorswitcher
 	_actorSwitcher.update(actorSwitcherSlots(), elapsed);
@@ -539,7 +565,7 @@ void TwpEngine::update(float elapsed) {
 	Common::Array<Common::SharedPtr<ThreadBase> > threads(_threads);
 	Common::Array<Common::SharedPtr<ThreadBase> > threadsToRemove;
 
-	bool isNotInDialog = _dialog.getState() == DialogState::None;
+	bool isNotInDialog = _dialog->getState() == DialogState::None;
 	for (auto it = threads.begin(); it != threads.end(); it++) {
 		Common::SharedPtr<ThreadBase> thread(*it);
 		if ((isNotInDialog || !thread->isGlobal()) && thread->update(elapsed)) {
@@ -595,8 +621,8 @@ void TwpEngine::update(float elapsed) {
 	if (!_actor) {
 		_uiInv.update(elapsed);
 	} else {
-		_hud.update(elapsed, scrPos, _noun1, _cursor.isLeftDown());
-		VerbUiColors *verbUI = &_hud.actorSlot(_actor)->verbUiColors;
+		_hud->update(elapsed, scrPos, _noun1, _cursor.isLeftDown());
+		VerbUiColors *verbUI = &_hud->actorSlot(_actor)->verbUiColors;
 		_uiInv.update(elapsed, _actor, verbUI->inventoryBackground, verbUI->verbNormal);
 	}
 
@@ -604,17 +630,17 @@ void TwpEngine::update(float elapsed) {
 }
 
 void TwpEngine::setShaderEffect(RoomEffect effect) {
-	_shaderParams.effect = effect;
+	_shaderParams->effect = effect;
 	switch (effect) {
 	case RoomEffect::None:
 		_gfx.use(nullptr);
 		break;
 	case RoomEffect::Sepia: {
-		_gfx.use(&_sepiaShader);
-		_sepiaShader.setUniform("sepiaFlicker", _shaderParams.sepiaFlicker);
+		_gfx.use(_sepiaShader.get());
+		_sepiaShader->setUniform("sepiaFlicker", _shaderParams->sepiaFlicker);
 	} break;
 	case RoomEffect::BlackAndWhite:
-		_gfx.use(&_bwShader);
+		_gfx.use(_bwShader.get());
 		break;
 	case RoomEffect::Ega:
 		// TODO: _gfx.use(&_egaShader);
@@ -623,7 +649,7 @@ void TwpEngine::setShaderEffect(RoomEffect effect) {
 		// TODO:_gfx.use(&_vhsShader);
 		break;
 	case RoomEffect::Ghost:
-		_gfx.use(&_ghostShader);
+		_gfx.use(_ghostShader.get());
 		break;
 	}
 }
@@ -642,7 +668,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	_gfx.setRenderTarget(&renderTexture);
 	_gfx.clear(Color(0, 0, 0));
 	_gfx.use(nullptr);
-	_scene.draw();
+	_scene->draw();
 
 	// then render this texture with room effect to another texture
 	_gfx.setRenderTarget(&renderTexture2);
@@ -650,10 +676,10 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 		setShaderEffect(_room->_effect);
 		_lighting->update(_room->_lights);
 	}
-	_shaderParams.randomValue[0] = g_twp->getRandom();
-	_shaderParams.timeLapse = fmodf(_time, 1000.f);
-	_shaderParams.iGlobalTime = _shaderParams.timeLapse;
-	_shaderParams.updateShader();
+	_shaderParams->randomValue[0] = g_twp->getRandom();
+	_shaderParams->timeLapse = fmodf(_time, 1000.f);
+	_shaderParams->iGlobalTime = _shaderParams->timeLapse;
+	_shaderParams->updateShader();
 
 	_gfx.camera(Math::Vector2d(SCREEN_WIDTH, SCREEN_HEIGHT));
 	Math::Vector2d camPos = _gfx.cameraPos();
@@ -721,7 +747,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 
 	// draw UI
 	_gfx.cameraPos(camPos);
-	_screenScene.draw();
+	_screenScene->draw();
 
 	// imgui render
 	_gfx.use(nullptr);
@@ -754,23 +780,29 @@ Common::Error TwpEngine::run() {
 	setDebugger(new Console());
 
 	_gfx.init();
-	_hud.init();
-
-	_bwShader.init("black&white", vsrc, bwShader);
-	_ghostShader.init("ghost", vsrc, ghostShader);
-	_sepiaShader.init("sepia", vsrc, sepiaShader);
+	_hud->init();
+
+	_camera.reset(new Camera());
+	_shaderParams.reset(new ShaderParams());
+	_bwShader.reset(new Shader());
+	_bwShader->init("black&white", vsrc, bwShader);
+	_ghostShader.reset(new Shader());
+	_ghostShader->init("ghost", vsrc, ghostShader);
+	_sepiaShader.reset(new Shader());
+	_sepiaShader->init("sepia", vsrc, sepiaShader);
 	_fadeShader.reset(new FadeShader());
+	_lighting.reset(new Lighting());
+	_resManager.reset(new ResManager());
+	_pack->init();
 
-	_lighting = Common::SharedPtr<Lighting>(new Lighting());
-
-	_pack.init();
-
-	Common::String lang = Common::String::format("ThimbleweedText_%s.tsv", ConfMan.get("language").c_str());
+	Common::String lang(Common::String::format("ThimbleweedText_%s.tsv", ConfMan.get("language").c_str()));
 	GGPackEntryReader entry;
-	entry.open(_pack, lang);
-	_textDb.parseTsv(entry);
+	entry.open(*_pack, lang);
+	_textDb.reset(new TextDb());
+	_textDb->parseTsv(entry);
 
-	HSQUIRRELVM v = _vm.get();
+	_vm.reset(new Vm());
+	HSQUIRRELVM v = _vm->get();
 	execNutEntry(v, "Defines.nut");
 	execBnutEntry(v, "Boot.bnut");
 
@@ -781,7 +813,7 @@ Common::Error TwpEngine::run() {
 	else {
 		// const SQChar *code = "cameraInRoom(StartScreen)";
 		const SQChar *code = "start(1)";
-		_vm.exec(code);
+		_vm->exec(code);
 	}
 
 	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
@@ -818,7 +850,7 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectActor6:
 					if (g_twp->_actorSwitcher._mode == asOn) {
 						int index = (TwpAction)e.customType - kSelectActor1;
-						ActorSlot *slot = &_hud._actorSlots[index];
+						ActorSlot *slot = &_hud->_actorSlots[index];
 						if (slot->selectable && slot->actor && (slot->actor->_room->_name != "Void")) {
 							setActor(slot->actor, true);
 						}
@@ -828,7 +860,7 @@ Common::Error TwpEngine::run() {
 					if ((g_twp->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
-							ActorSlot *slot = &_hud._actorSlots[i];
+							ActorSlot *slot = &_hud->_actorSlots[i];
 							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
 								actors.push_back(slot->actor);
 							}
@@ -843,7 +875,7 @@ Common::Error TwpEngine::run() {
 					if ((g_twp->_actorSwitcher._mode == asOn) && _actor) {
 						Common::Array<Common::SharedPtr<Object> > actors;
 						for (int i = 0; i < NUMACTORS; i++) {
-							ActorSlot *slot = &_hud._actorSlots[i];
+							ActorSlot *slot = &_hud->_actorSlots[i];
 							if (slot->selectable && (slot->actor->_room->_name != "Void")) {
 								actors.push_back(slot->actor);
 							}
@@ -863,13 +895,13 @@ Common::Error TwpEngine::run() {
 				case TwpAction::kSelectChoice4:
 				case TwpAction::kSelectChoice5:
 				case TwpAction::kSelectChoice6:
-					if (_dialog.getState() == DialogState::None) {
+					if (_dialog->getState() == DialogState::None) {
 						int index = (TwpAction)e.customType - kSelectChoice1;
-						_dialog.choose(index);
+						_dialog->choose(index);
 					}
 					break;
 				case TwpAction::kShowHotspots:
-					_hotspotMarker.setVisible(!_hotspotMarker.isVisible());
+					_hotspotMarker->setVisible(!_hotspotMarker->isVisible());
 					break;
 				}
 				break;
@@ -896,18 +928,18 @@ Common::Error TwpEngine::run() {
 					break;
 				case Common::KEYCODE_w:
 					if (control) {
-						WalkboxMode mode = (WalkboxMode)(((int)_walkboxNode.getMode() + 1) % 3);
+						WalkboxMode mode = (WalkboxMode)(((int)_walkboxNode->getMode() + 1) % 3);
 						debugC(kDebugGame, "set walkbox mode to: %s", (mode == WalkboxMode::Merged ? "merged" : mode == WalkboxMode::All ? "all"
 																																		 : "none"));
-						_walkboxNode.setMode(mode);
+						_walkboxNode->setMode(mode);
 					}
 					break;
 				case Common::KEYCODE_g:
 					if (control) {
-						PathMode mode = (PathMode)(((int)_pathNode.getMode() + 1) % 3);
+						PathMode mode = (PathMode)(((int)_pathNode->getMode() + 1) % 3);
 						debugC(kDebugGame, "set path mode to: %s", (mode == PathMode::GraphMode ? "graph" : mode == PathMode::All ? "all"
 																																  : "none"));
-						_pathNode.setMode(mode);
+						_pathNode->setMode(mode);
 					}
 					break;
 				default:
@@ -996,8 +1028,8 @@ Common::Error TwpEngine::loadGameState(int slot) {
 
 Common::Error TwpEngine::loadGameStream(Common::SeekableReadStream *stream) {
 	SaveGame savegame;
-	if (_saveGameManager.getSaveGame(stream, savegame)) {
-		_saveGameManager.loadGame(savegame);
+	if (_saveGameManager->getSaveGame(stream, savegame)) {
+		_saveGameManager->loadGame(savegame);
 	}
 	return Common::kNoError;
 }
@@ -1006,6 +1038,10 @@ Common::String TwpEngine::getSaveStateName(int slot) const {
 	return Common::String::format("twp%02d.save", slot);
 }
 
+bool TwpEngine::canSaveGameStateCurrently(Common::U32String *msg) {
+	return _saveGameManager->_allowSaveGame && !_cutscene;
+}
+
 static Common::String changeFileExt(const Common::String &s, const Common::String &ext) {
 	size_t i = s.findLastOf('.');
 	if (i != Common::String::npos) {
@@ -1035,7 +1071,7 @@ Common::Error TwpEngine::saveGameState(int slot, const Common::String &desc, boo
 }
 
 Common::Error TwpEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
-	_saveGameManager.saveGame(stream);
+	_saveGameManager->saveGame(stream);
 	return Common::kNoError;
 }
 
@@ -1110,7 +1146,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 }
 
 Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJECT table, bool pseudo) {
-	HSQUIRRELVM v = _vm.get();
+	HSQUIRRELVM v = _vm->get();
 	debugC(kDebugGame, "Load room: %s", name.c_str());
 	Common::SharedPtr<Room> result;
 	if (name == "Void") {
@@ -1126,7 +1162,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 		Common::String background;
 		sqgetf(table, "background", background);
 		GGPackEntryReader entry;
-		entry.open(_pack, background + ".wimpy");
+		entry.open(*_pack, background + ".wimpy");
 		Room::load(result, entry);
 		result->_name = name;
 		result->_pseudo = pseudo;
@@ -1229,12 +1265,12 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 		_room->_scene->remove();
 	_room = room;
 	room->_effect = RoomEffect::None;
-	_scene.addChild(_room->_scene.get());
+	_scene->addChild(_room->_scene.get());
 	_room->_lights._numLights = 0;
 	_room->setOverlay(Color(0.f, 0.f, 0.f, 0.f));
-	_camera.setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
-	if (_actor && _hud.actorSlot(_actor))
-		_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+	_camera->setBounds(Rectf::fromMinMax(Math::Vector2d(), _room->_roomSize));
+	if (_actor && _hud->actorSlot(_actor))
+		_hud->_verb = _hud->actorSlot(_actor)->verbs[0];
 
 	// move current actor to the new room
 	Math::Vector2d camPos;
@@ -1251,8 +1287,8 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 		}
 	}
 
-	_camera.setRoom(room);
-	_camera.setAt(camPos);
+	_camera->setRoom(room);
+	_camera->setAt(camPos);
 
 	stopTalking();
 
@@ -1383,7 +1419,7 @@ void TwpEngine::cancelSentence(Common::SharedPtr<Object> actor) {
 
 void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
 	GGPackEntryReader reader;
-	reader.open(_pack, entry);
+	reader.open(*_pack, entry);
 	GGBnutReader nut;
 	nut.open(&reader);
 	Common::String code = nut.readString();
@@ -1391,17 +1427,17 @@ void TwpEngine::execBnutEntry(HSQUIRRELVM v, const Common::String &entry) {
 }
 
 void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
-	if (_pack.assetExists(entry.c_str())) {
+	if (_pack->assetExists(entry.c_str())) {
 		GGPackEntryReader reader;
 		debugC(kDebugGame, "read existing '%s'", entry.c_str());
-		reader.open(_pack, entry);
+		reader.open(*_pack, entry);
 		Common::String code = reader.readString();
 		// debugC(kDebugGame, "%s", code.c_str());
 		sqexec(v, code.c_str(), entry.c_str());
 	} else {
 		Common::String newEntry = entry.substr(0, entry.size() - 4) + ".bnut";
 		debugC(kDebugGame, "read existing '%s'", newEntry.c_str());
-		if (_pack.assetExists(newEntry.c_str())) {
+		if (_pack->assetExists(newEntry.c_str())) {
 			execBnutEntry(v, newEntry);
 		} else {
 			error("'%s' and '%s' have not been found", entry.c_str(), newEntry.c_str());
@@ -1410,16 +1446,16 @@ void TwpEngine::execNutEntry(HSQUIRRELVM v, const Common::String &entry) {
 }
 
 void TwpEngine::cameraAt(Math::Vector2d at) {
-	_camera.setRoom(_room);
-	_camera.setAt(at);
+	_camera->setRoom(_room);
+	_camera->setAt(at);
 }
 
 Math::Vector2d TwpEngine::cameraPos() {
 	if (_room) {
 		Math::Vector2d screenSize = _room->getScreenSize();
-		return _camera.getAt() + screenSize / 2.0f;
+		return _camera->getAt() + screenSize / 2.0f;
 	}
-	return _camera.getAt();
+	return _camera->getAt();
 }
 
 void TwpEngine::follow(Common::SharedPtr<Object> actor) {
@@ -1473,12 +1509,12 @@ Common::SharedPtr<Object> TwpEngine::objAt(Math::Vector2d pos) {
 
 void TwpEngine::setActor(Common::SharedPtr<Object> actor, bool userSelected) {
 	_actor = actor;
-	_hud._actor = actor;
+	_hud->_actor = actor;
 	resetVerb();
-	if (!_hud.getParent() && actor) {
-		_screenScene.addChild(&_hud);
-	} else if (_hud.getParent() && !actor) {
-		_screenScene.removeChild(&_hud);
+	if (!_hud->getParent() && actor) {
+		_screenScene->addChild(_hud.get());
+	} else if (_hud->getParent() && !actor) {
+		_screenScene->removeChild(_hud.get());
 	}
 
 	// call onActorSelected callbacks
@@ -1496,7 +1532,7 @@ void TwpEngine::setActor(Common::SharedPtr<Object> actor, bool userSelected) {
 
 bool TwpEngine::selectable(Common::SharedPtr<Object> actor) {
 	for (int i = 0; i < NUMACTORS; i++) {
-		ActorSlot *slot = &_hud._actorSlots[i];
+		ActorSlot *slot = &_hud->_actorSlots[i];
 		if (slot->actor == actor)
 			return slot->selectable;
 	}
@@ -1515,8 +1551,8 @@ void TwpEngine::resetVerb() {
 	debugC(kDebugGame, "reset nouns");
 	_noun1 = nullptr;
 	_noun2 = nullptr;
-	_useFlag = ufNone;
-	_hud._verb = _hud.actorSlot(_actor)->verbs[0];
+	_useFlag = UseFlag::ufNone;
+	_hud->_verb = _hud->actorSlot(_actor)->verbs[0];
 }
 
 bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common::SharedPtr<Object> noun1, Common::SharedPtr<Object> noun2) {
@@ -1526,7 +1562,7 @@ bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common:
 	Common::String name = !actor ? "currentActor" : actor->_key;
 	Common::String noun1name = !noun1 ? "null" : noun1->_key;
 	Common::String noun2name = !noun2 ? "null" : noun2->_key;
-	ActorSlot *slot = _hud.actorSlot(actor);
+	ActorSlot *slot = _hud->actorSlot(actor);
 	Verb *verb = slot->getVerb(verbId.id);
 	Common::String verbFuncName = verb ? verb->fun : slot->verbs[0].fun;
 	debugC(kDebugGame, "callVerb(%s,%s,%s,%s)", name.c_str(), verbFuncName.c_str(), noun1name.c_str(), noun2name.c_str());
@@ -1540,7 +1576,7 @@ bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common:
 	// check if verb is use and object can be used with or in or on
 	if ((verbId.id == VERB_USE) && !noun2) {
 		_useFlag = noun1->useFlag();
-		if (_useFlag != ufNone) {
+		if (_useFlag != UseFlag::ufNone) {
 			_noun1 = noun1;
 			return false;
 		}
@@ -1549,7 +1585,7 @@ bool TwpEngine::callVerb(Common::SharedPtr<Object> actor, VerbId verbId, Common:
 	if (verbId.id == VERB_GIVE) {
 		if (!noun2) {
 			debugC(kDebugGame, "set use flag to ufGiveTo");
-			_useFlag = ufGiveTo;
+			_useFlag = UseFlag::ufGiveTo;
 			_noun1 = noun1;
 		} else {
 			bool handled = false;
@@ -1741,6 +1777,8 @@ void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
 	Image::writePNG(stream, s);
 }
 
+HSQUIRRELVM TwpEngine::getVm() { return _vm->get(); }
+
 ScalingTrigger::ScalingTrigger(Common::SharedPtr<Object> obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 4ab0011ce5e..0a92b3cbf31 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -22,50 +22,62 @@
 #ifndef TWP_H
 #define TWP_H
 
-#include "common/system.h"
-#include "common/error.h"
-#include "common/fs.h"
 #include "common/random.h"
-#include "common/serializer.h"
-#include "common/util.h"
 #include "common/ptr.h"
 #include "engines/engine.h"
-#include "engines/savestate.h"
-#include "graphics/screen.h"
-#include "twp/detection.h"
-#include "twp/vm.h"
-#include "twp/shaders.h"
-#include "twp/resmanager.h"
-#include "twp/ggpack.h"
-#include "twp/squirrel/squirrel.h"
-#include "twp/camera.h"
-#include "twp/tsv.h"
-#include "twp/scenegraph.h"
-#include "twp/dialog.h"
-#include "twp/hud.h"
-#include "twp/callback.h"
-#include "twp/walkboxnode.h"
-#include "twp/audio.h"
 #include "twp/actorswitcher.h"
-#include "twp/savegame.h"
+#include "twp/squirrel/squirrel.h"
 
 #define SCREEN_MARGIN 100.f
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 720
 #define VERBDEFAULT "verbDefault"
 
+template<typename T, class DL = Common::DefaultDeleter<T> >
+using unique_ptr = Common::ScopedPtr<T, DL>;
+
+namespace Graphics {
+class Screen;
+}
+
+struct ADGameDescription;
+
 namespace Twp {
 
-class Lighting;
-class Task;
-class ThreadBase;
+struct ActorSlot;
+class AudioSystem;
+class Callback;
+class Camera;
 class Cutscene;
-class Scene;
-class Room;
+class Dialog;
+class FadeShader;
+class GGPackSet;
+class Hud;
 class InputState;
+struct Light;
+class Lighting;
+class LightingNode;
+class NoOverrideNode;
 class Object;
-class Dialog;
+class PathNode;
+class ResManager;
+class Room;
+class SaveGameManager;
+struct Scaling;
+class Scene;
+struct ShaderParams;
+class Task;
+class TextDb;
+class ThreadBase;
 struct TwpGameDescription;
+struct Verb;
+struct VerbId;
+class Vm;
+class WalkboxNode;
+
+enum class FadeEffect;
+enum class RoomEffect;
+enum class UseFlag;
 
 class TwpEngine : public Engine {
 private:
@@ -92,9 +104,9 @@ public:
 	 */
 	Common::RandomSource &getRandomSource() { return _randomSource; }
 
-	HSQUIRRELVM getVm() { return _vm.get(); }
+	HSQUIRRELVM getVm();
 	inline Gfx &getGfx() { return _gfx; }
-	inline TextDb &getTextDb() { return _textDb; }
+	inline TextDb &getTextDb() { return *_textDb; }
 
 	bool hasFeature(EngineFeature f) const override {
 		return (f == kSupportsLoadingDuringRuntime) ||
@@ -105,9 +117,7 @@ public:
 	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
 		return !_cutscene;
 	}
-	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
-		return _saveGameManager._allowSaveGame && !_cutscene;
-	}
+	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
 
 	virtual Common::String getSaveStateName(int slot) const override;
 	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
@@ -168,12 +178,12 @@ private:
 	void skipCutscene();
 
 private:
-	Vm _vm;
+	unique_ptr<Vm> _vm;
 
 public:
 	Graphics::Screen *_screen = nullptr;
-	GGPackSet _pack;
-	ResManager _resManager;
+	unique_ptr<GGPackSet> _pack;
+	unique_ptr<ResManager> _resManager;
 	Common::Array<Common::SharedPtr<Room> > _rooms;
 	Common::Array<Common::SharedPtr<Object> > _actors;
 	Common::Array<Common::SharedPtr<ThreadBase> > _threads;
@@ -193,13 +203,13 @@ public:
 	int _frameCounter = 0;
 	Common::SharedPtr<Lighting> _lighting;
 	Common::SharedPtr<Cutscene> _cutscene;
-	Scene _scene;
-	Scene _screenScene;
-	NoOverrideNode _noOverride;
+	unique_ptr<Scene> _scene;
+	unique_ptr<Scene> _screenScene;
+	unique_ptr<NoOverrideNode> _noOverride;
 	InputState _inputState;
-	Camera _camera;
-	TextDb _textDb;
-	Dialog _dialog;
+	unique_ptr<Camera> _camera;
+	unique_ptr<TextDb> _textDb;
+	unique_ptr<Dialog> _dialog;
 	struct Cursor {
 		Math::Vector2d pos;
 		bool oldLeftDown = false;
@@ -216,24 +226,24 @@ public:
 		bool isLeftDown() { return !oldLeftDown && leftDown; }
 		bool isRightDown() { return !oldRightDown && rightDown; }
 	} _cursor;
-	Hud _hud;
+	unique_ptr<Hud> _hud;
 	Inventory _uiInv;
 	ActorSwitcher _actorSwitcher;
-	AudioSystem _audio;
-	SaveGameManager _saveGameManager;
-	ShaderParams _shaderParams;
-	HotspotMarkerNode _hotspotMarker;
+	unique_ptr<AudioSystem> _audio;
+	unique_ptr<SaveGameManager> _saveGameManager;
+	unique_ptr<ShaderParams> _shaderParams;
+	unique_ptr<HotspotMarkerNode> _hotspotMarker;
 	unique_ptr<FadeShader> _fadeShader;
-	LightingNode _lightingNode;
+	unique_ptr<LightingNode> _lightingNode;
 
 private:
 	Gfx _gfx;
 	SentenceNode _sentence;
-	WalkboxNode _walkboxNode;
-	PathNode _pathNode;
-	Shader _bwShader;
-	Shader _ghostShader;
-	Shader _sepiaShader;
+	unique_ptr<WalkboxNode> _walkboxNode;
+	unique_ptr<PathNode> _pathNode;
+	unique_ptr<Shader> _bwShader;
+	unique_ptr<Shader> _ghostShader;
+	unique_ptr<Shader> _sepiaShader;
 };
 
 extern TwpEngine *g_twp;
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index 7b5cadaf1b8..d9e0c777b7a 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -20,7 +20,10 @@
  */
 
 #include "twp/twp.h"
+#include "twp/object.h"
+#include "twp/room.h"
 #include "twp/lighting.h"
+#include "twp/walkboxnode.h"
 
 namespace Twp {
 


Commit: 09a6bc98aceab2c4975e3dba8acaf00fdccc5fd6
    https://github.com/scummvm/scummvm/commit/09a6bc98aceab2c4975e3dba8acaf00fdccc5fd6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Allow changing options during game

Changed paths:
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 80595f9a952..520fd963819 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -41,28 +41,10 @@
 
 #define MAX_SAVES 99
 
-namespace Twp {
-
-static const ADExtraGuiOptionsMap optionsList[] = {
-	{GAMEOPTION_ORIGINAL_SAVELOAD,
-	 {_s("Use original save/load screens"),
-	  _s("Use the original save/load screens instead of the ScummVM ones"),
-	  "original_menus",
-	  false,
-	  0,
-	  0}},
-	AD_EXTRA_GUI_OPTIONS_TERMINATOR};
-
-} // End of namespace Twp
-
 const char *TwpMetaEngine::getName() const {
 	return "twp";
 }
 
-const ADExtraGuiOptionsMap *TwpMetaEngine::getAdvancedExtraGuiOptions() const {
-	return Twp::optionsList;
-}
-
 Common::Error TwpMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
 	*engine = new Twp::TwpEngine(syst, desc);
 	return Common::kNoError;
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 159940b0934..0d725bb6338 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -37,8 +37,6 @@ public:
 	 */
 	bool hasFeature(MetaEngineFeature f) const override;
 
-	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
-
 	SaveStateList listSaves(const char *target) const override;
 	int getMaximumSaveSlot() const override;
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 4c8419e196d..1ebc2801b8d 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -760,6 +760,12 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	g_system->updateScreen();
 }
 
+void TwpEngine::updateSettingVars() {
+	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
+	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
+	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped"));
+}
+
 Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
@@ -816,9 +822,7 @@ Common::Error TwpEngine::run() {
 		_vm->exec(code);
 	}
 
-	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
-	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
-	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped"));
+	updateSettingVars();
 
 	static int speed = 1;
 	static bool control = false;
@@ -1779,6 +1783,12 @@ void TwpEngine::capture(Common::WriteStream &stream, Math::Vector2d size) {
 
 HSQUIRRELVM TwpEngine::getVm() { return _vm->get(); }
 
+int TwpEngine::runDialog(GUI::Dialog &dialog) {
+	int result = Engine::runDialog(dialog);
+	updateSettingVars();
+	return result;
+}
+
 ScalingTrigger::ScalingTrigger(Common::SharedPtr<Object> obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 0a92b3cbf31..828a65b09d2 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -111,9 +111,12 @@ public:
 	bool hasFeature(EngineFeature f) const override {
 		return (f == kSupportsLoadingDuringRuntime) ||
 			   (f == kSupportsSavingDuringRuntime) ||
-			   (f == kSupportsReturnToLauncher);
+			   (f == kSupportsReturnToLauncher) ||
+			   (f == kSupportsChangingOptionsDuringRuntime);
 	}
 
+	void updateSettingVars();
+
 	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
 		return !_cutscene;
 	}
@@ -157,6 +160,8 @@ public:
 	float getRandom() const;
 	float getRandom(float min, float max) const;
 
+	int runDialog(GUI::Dialog &dialog) override;
+
 private:
 	void update(float elapsedMs);
 	void draw(RenderTexture *texture = nullptr);


Commit: ebb5d9c367283b8e1b3e6b7916052dfb06f07f67
    https://github.com/scummvm/scummvm/commit/ebb5d9c367283b8e1b3e6b7916052dfb06f07f67
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove unused unicode code in squirrel

Changed paths:
    engines/twp/squirrel/sqconfig.h
    engines/twp/squirrel/sqlexer.cpp
    engines/twp/squirrel/sqlexer.h
    engines/twp/squirrel/sqstdio.cpp
    engines/twp/squirrel/sqstdstring.cpp
    engines/twp/squirrel/squirrel.h


diff --git a/engines/twp/squirrel/sqconfig.h b/engines/twp/squirrel/sqconfig.h
index 58bc9793a38..72a268fe167 100755
--- a/engines/twp/squirrel/sqconfig.h
+++ b/engines/twp/squirrel/sqconfig.h
@@ -51,57 +51,6 @@ typedef void* SQUserPointer;
 typedef SQUnsignedInteger SQBool;
 typedef SQInteger SQRESULT;
 
-#ifdef SQUNICODE
-#include <wchar.h>
-#include <wctype.h>
-
-
-typedef wchar_t SQChar;
-
-
-#define scstrcmp    wcscmp
-#ifdef _WIN32
-#define scsprintf   _snwprintf
-#else
-#define scsprintf   swprintf
-#endif
-#define scstrlen    wcslen
-#define scstrtod    wcstod
-#ifdef _SQ64
-#define scstrtol    wcstoll
-#else
-#define scstrtol    wcstol
-#endif
-#define scstrtoul   wcstoul
-#define scvsprintf  vswprintf
-#define scstrstr    wcsstr
-#define scprintf    wprintf
-
-#ifdef _WIN32
-#define WCHAR_SIZE 2
-#define WCHAR_SHIFT_MUL 1
-#define MAX_CHAR 0xFFFF
-#else
-#define WCHAR_SIZE 4
-#define WCHAR_SHIFT_MUL 2
-#define MAX_CHAR 0xFFFFFFFF
-#endif
-
-#define _SC(a) L##a
-
-
-#define scisspace   iswspace
-#define scisdigit   iswdigit
-#define scisprint   iswprint
-#define scisxdigit  iswxdigit
-#define scisalpha   iswalpha
-#define sciscntrl   iswcntrl
-#define scisalnum   iswalnum
-
-
-#define sq_rsl(l) ((l)<<WCHAR_SHIFT_MUL)
-
-#else
 typedef char SQChar;
 #define _SC(a) a
 #define scstrcmp    strcmp
@@ -136,8 +85,6 @@ typedef char SQChar;
 
 #define sq_rsl(l) (l)
 
-#endif
-
 #ifdef _SQ64
 #define _PRINT_INT_PREC _SC("ll")
 #define _PRINT_INT_FMT _SC("%lld")
diff --git a/engines/twp/squirrel/sqlexer.cpp b/engines/twp/squirrel/sqlexer.cpp
index 65937bc2748..460e9efedcd 100755
--- a/engines/twp/squirrel/sqlexer.cpp
+++ b/engines/twp/squirrel/sqlexer.cpp
@@ -293,24 +293,6 @@ SQInteger SQLexer::GetIDType(const SQChar *s,SQInteger len)
     return TK_IDENTIFIER;
 }
 
-#ifdef SQUNICODE
-#if WCHAR_SIZE == 2
-SQInteger SQLexer::AddUTF16(SQUnsignedInteger ch)
-{
-    if (ch >= 0x10000)
-    {
-        SQUnsignedInteger code = (ch - 0x10000);
-        APPEND_CHAR((SQChar)(0xD800 | (code >> 10)));
-        APPEND_CHAR((SQChar)(0xDC00 | (code & 0x3FF)));
-        return 2;
-    }
-    else {
-        APPEND_CHAR((SQChar)ch);
-        return 1;
-    }
-}
-#endif
-#else
 SQInteger SQLexer::AddUTF8(SQUnsignedInteger ch)
 {
     if (ch < 0x80) {
@@ -337,7 +319,6 @@ SQInteger SQLexer::AddUTF8(SQUnsignedInteger ch)
     }
     return 0;
 }
-#endif
 
 SQInteger SQLexer::ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits)
 {
@@ -391,15 +372,8 @@ SQInteger SQLexer::ReadString(SQInteger ndelim,bool verbatim)
                         SQChar temp[8 + 1];
                         ProcessStringHexEscape(temp, maxdigits);
                         SQChar *stemp;
-#ifdef SQUNICODE
-#if WCHAR_SIZE == 2
-                        AddUTF16(scstrtoul(temp, &stemp, 16));
-#else
-                        APPEND_CHAR((SQChar)scstrtoul(temp, &stemp, 16));
-#endif
-#else
+
                         AddUTF8(scstrtoul(temp, &stemp, 16));
-#endif
                     }
                     break;
                     case _SC('t'): APPEND_CHAR(_SC('\t')); NEXT(); break;
diff --git a/engines/twp/squirrel/sqlexer.h b/engines/twp/squirrel/sqlexer.h
index d731c20e479..55949d44f63 100755
--- a/engines/twp/squirrel/sqlexer.h
+++ b/engines/twp/squirrel/sqlexer.h
@@ -2,11 +2,7 @@
 #ifndef _SQLEXER_H_
 #define _SQLEXER_H_
 
-#ifdef SQUNICODE
-typedef SQChar LexChar;
-#else
 typedef unsigned char LexChar;
-#endif
 
 struct SQLexer
 {
@@ -24,13 +20,8 @@ private:
     void LexLineComment();
     SQInteger ReadID();
     void Next();
-#ifdef SQUNICODE
-#if WCHAR_SIZE == 2
-    SQInteger AddUTF16(SQUnsignedInteger ch);
-#endif
-#else
+
     SQInteger AddUTF8(SQUnsignedInteger ch);
-#endif
     SQInteger ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits);
     SQInteger _curtoken;
     SQTable *_keywords;
diff --git a/engines/twp/squirrel/sqstdio.cpp b/engines/twp/squirrel/sqstdio.cpp
index 87a5ddfce77..4e9df69b496 100755
--- a/engines/twp/squirrel/sqstdio.cpp
+++ b/engines/twp/squirrel/sqstdio.cpp
@@ -9,11 +9,7 @@
 //basic API
 SQFILE sqstd_fopen(const SQChar *filename ,const SQChar *mode)
 {
-#ifndef SQUNICODE
     return (SQFILE)fopen(filename,mode);
-#else
-    return (SQFILE)_wfopen(filename,mode);
-#endif
 }
 
 SQInteger sqstd_fread(void* buffer, SQInteger size, SQInteger count, SQFILE file)
@@ -265,46 +261,6 @@ static SQInteger _io_file_lexfeed_PLAIN(SQUserPointer iobuf)
 
 }
 
-#ifdef SQUNICODE
-static SQInteger _io_file_lexfeed_UTF8(SQUserPointer iobuf)
-{
-    IOBuffer *iobuffer = (IOBuffer *)iobuf;
-#define READ(iobuf) \
-    if((inchar = (unsigned char)_read_byte(iobuf)) == 0) \
-        return 0;
-
-    static const SQInteger utf8_lengths[16] =
-    {
-        1,1,1,1,1,1,1,1,        /* 0000 to 0111 : 1 byte (plain ASCII) */
-        0,0,0,0,                /* 1000 to 1011 : not valid */
-        2,2,                    /* 1100, 1101 : 2 bytes */
-        3,                      /* 1110 : 3 bytes */
-        4                       /* 1111 :4 bytes */
-    };
-    static const unsigned char byte_masks[5] = {0,0,0x1f,0x0f,0x07};
-    unsigned char inchar;
-    SQInteger c = 0;
-    READ(iobuffer);
-    c = inchar;
-    //
-    if(c >= 0x80) {
-        SQInteger tmp;
-        SQInteger codelen = utf8_lengths[c>>4];
-        if(codelen == 0)
-            return 0;
-            //"invalid UTF-8 stream";
-        tmp = c&byte_masks[codelen];
-        for(SQInteger n = 0; n < codelen-1; n++) {
-            tmp<<=6;
-            READ(iobuffer);
-            tmp |= inchar & 0x3F;
-        }
-        c = tmp;
-    }
-    return c;
-}
-#endif
-
 static SQInteger _io_file_lexfeed_UCS2_LE(SQUserPointer iobuf)
 {
     SQInteger ret;
@@ -374,11 +330,8 @@ SQRESULT sqstd_loadfile(HSQUIRRELVM v,const SQChar *filename,SQBool printerror)
                         sqstd_fclose(file);
                         return sq_throwerror(v,_SC("Unrecognized encoding"));
                     }
-#ifdef SQUNICODE
-                    func = _io_file_lexfeed_UTF8;
-#else
+
                     func = _io_file_lexfeed_PLAIN;
-#endif
                     break;//UTF-8 ;
                 default: sqstd_fseek(file,0,SQ_SEEK_SET); break; // ascii
             }
diff --git a/engines/twp/squirrel/sqstdstring.cpp b/engines/twp/squirrel/sqstdstring.cpp
index 63faa58c3fd..ab1a801127c 100755
--- a/engines/twp/squirrel/sqstdstring.cpp
+++ b/engines/twp/squirrel/sqstdstring.cpp
@@ -274,18 +274,8 @@ static SQInteger _string_escape(HSQUIRRELVM v)
         sq_push(v,2);
         return 1;
     }
-#ifdef SQUNICODE
-#if WCHAR_SIZE == 2
-    const SQChar *escpat = _SC("\\x%04x");
-    const SQInteger maxescsize = 6;
-#else //WCHAR_SIZE == 4
-    const SQChar *escpat = _SC("\\x%08x");
-    const SQInteger maxescsize = 10;
-#endif
-#else
     const SQChar *escpat = _SC("\\x%02x");
     const SQInteger maxescsize = 4;
-#endif
     SQInteger destcharsize = (size * maxescsize); //assumes every char could be escaped
     resstr = dest = (SQChar *)sq_getscratchpad(v,destcharsize * sizeof(SQChar));
     SQChar c;
diff --git a/engines/twp/squirrel/squirrel.h b/engines/twp/squirrel/squirrel.h
index 111cc48ecbc..e2259817c25 100755
--- a/engines/twp/squirrel/squirrel.h
+++ b/engines/twp/squirrel/squirrel.h
@@ -59,10 +59,6 @@ struct SQInstance;
 struct SQDelegable;
 struct SQOuter;
 
-#ifdef _UNICODE
-//#define SQUNICODE
-#endif
-
 #include "sqconfig.h"
 
 #define SQUIRREL_VERSION    _SC("Squirrel 3.1 stable")


Commit: db26ead64bcab30438a5bb38fb23e4172e2f7995
    https://github.com/scummvm/scummvm/commit/db26ead64bcab30438a5bb38fb23e4172e2f7995
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Save imgui configuration in savepath

Changed paths:
  A imgui.ini
    .gitignore
    engines/twp/twp.cpp


diff --git a/.gitignore b/.gitignore
index 9d3518bed4c..42f087cbea5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -299,6 +299,3 @@ dists/emscripten/emsdk-*
 #Ignore Atari/FreeMiNT files
 scummvm.gtp
 scummvm.ttp
-
-#Ignore Dear imgui file
-imgui.ini
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1ebc2801b8d..1ea0a009e57 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -780,6 +780,11 @@ Common::Error TwpEngine::run() {
 	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
 	ImGui_ImplOpenGL3_Init("#version 110");
 	ImGui::StyleColorsDark();
+
+	ImGuiIO &io = ImGui::GetIO();
+	Common::Path initPath(ConfMan.getPath("savepath"));
+	initPath = initPath.appendComponent("twp_imgui.ini");
+	io.IniFilename = initPath.toString().c_str();
 #endif
 
 	// Set the engine's debugger console
@@ -835,7 +840,6 @@ Common::Error TwpEngine::run() {
 		while (g_system->getEventManager()->pollEvent(e)) {
 #ifdef USE_IMGUI
 			ImGui_ImplSDL2_ProcessEvent(&e);
-			ImGuiIO &io = ImGui::GetIO();
 			if (io.WantTextInput || io.WantCaptureMouse)
 				continue;
 #endif
diff --git a/imgui.ini b/imgui.ini
new file mode 100644
index 00000000000..9930887f247
--- /dev/null
+++ b/imgui.ini
@@ -0,0 +1,4 @@
+[Window][Debug##Default]
+Pos=60,60
+Size=400,400
+


Commit: cac2f3aae0633f62635ded90dd63bdf57900f0ea
    https://github.com/scummvm/scummvm/commit/cac2f3aae0633f62635ded90dd63bdf57900f0ea
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Split engine and imgui

Changed paths:
  A engines/twp/twpimgui.cpp
  A engines/twp/twpimgui.h
    engines/twp/debugtools.cpp
    engines/twp/module.mk
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 86608f86c30..96e4f45233f 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -19,7 +19,6 @@
  *
  */
 
-#define FORBIDDEN_SYMBOL_ALLOW_ALL
 #include "graphics/imgui/imgui.h"
 
 #include "common/debug-channels.h"
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 6198c3da355..89dcf523dab 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -39,6 +39,7 @@ MODULE_OBJS = \
 	time.o \
 	tsv.o \
 	twp.o \
+	twpimgui.o \
 	util.o \
 	vm.o \
 	walkboxnode.o \
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 1ea0a009e57..df4686de6f5 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,33 +19,6 @@
  *
  */
 
-#if defined(HAVE_CONFIG_H)
-#include "config.h"
-#endif
-
-#ifdef USE_IMGUI
-#define FORBIDDEN_SYMBOL_ALLOW_ALL
-#include "graphics/imgui/imgui.h"
-#include "graphics/imgui/backends/imgui_impl_sdl2_scummvm.h"
-#include "graphics/imgui/backends/imgui_impl_opengl3_scummvm.h"
-#undef FORBIDDEN_SYMBOL_ALLOW_ALL
-#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
-// here I undefined these symbols because of the <X11/Xlib.h> defining them
-// and messing with all classes or structure using the same names
-#undef Bool
-#undef CursorShape
-#undef Expose
-#undef KeyPress
-#undef KeyRelease
-#undef FocusIn
-#undef FocusOut
-#undef FontChange
-#undef None
-#undef Status
-#undef Unsorted
-
-#endif
-
 #include "common/config-manager.h"
 #include "common/events.h"
 #include "common/savefile.h"
@@ -72,6 +45,7 @@
 #include "twp/task.h"
 #include "twp/thread.h"
 #include "twp/tsv.h"
+#include "twp/twpimgui.h"
 #include "twp/vm.h"
 #include "twp/walkboxnode.h"
 
@@ -79,10 +53,6 @@ namespace Twp {
 
 TwpEngine *g_twp;
 
-#ifdef USE_IMGUI
-SDL_Window *g_window = nullptr;
-#endif
-
 TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
 	  _gameDescription(gameDesc),
@@ -103,6 +73,7 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_hud.reset(new Hud());
 	_pack.reset(new GGPackSet());
 	_saveGameManager.reset(new SaveGameManager());
+	_imgui.reset(new TwpImGui());
 
 	_screenScene->setName("Screen");
 	_scene->addChild(_walkboxNode.get());
@@ -751,11 +722,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 
 	// imgui render
 	_gfx.use(nullptr);
-
-#ifdef USE_IMGUI
-	ImGui::Render();
-	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
-#endif
+	_imgui->render();
 
 	g_system->updateScreen();
 }
@@ -769,23 +736,7 @@ void TwpEngine::updateSettingVars() {
 Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-#ifdef USE_IMGUI
-	// Setup Dear ImGui
-	OpenGLSdlGraphics3dManager *manager = dynamic_cast<OpenGLSdlGraphics3dManager *>(g_system->getPaletteManager());
-	IMGUI_CHECKVERSION();
-	ImGui::CreateContext();
-	g_window = manager->getWindow()->getSDLWindow();
-	SDL_GLContext glContext = SDL_GL_GetCurrentContext();
-	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
-	ImGui_ImplOpenGL3_Init("#version 110");
-	ImGui::StyleColorsDark();
-
-	ImGuiIO &io = ImGui::GetIO();
-	Common::Path initPath(ConfMan.getPath("savepath"));
-	initPath = initPath.appendComponent("twp_imgui.ini");
-	io.IniFilename = initPath.toString().c_str();
-#endif
+	_imgui->init();
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -838,11 +789,8 @@ Common::Error TwpEngine::run() {
 	while (!shouldQuit()) {
 		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
-#ifdef USE_IMGUI
-			ImGui_ImplSDL2_ProcessEvent(&e);
-			if (io.WantTextInput || io.WantCaptureMouse)
+			if(_imgui->processEvent(&e))
 				continue;
-#endif
 
 			switch (e.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START: {
@@ -998,14 +946,6 @@ Common::Error TwpEngine::run() {
 		time = newTime;
 		update(speed * delta / 1000.f);
 
-#ifdef USE_IMGUI
-		ImGui_ImplOpenGL3_NewFrame();
-		ImGui_ImplSDL2_NewFrame(g_window);
-		ImGui::NewFrame();
-
-		onImGuiRender();
-#endif
-
 		draw();
 		_cursor.update();
 
@@ -1017,11 +957,7 @@ Common::Error TwpEngine::run() {
 	}
 
 	// Cleanup
-#ifdef USE_IMGUI
-	ImGui_ImplOpenGL3_Shutdown();
-	ImGui_ImplSDL2_Shutdown();
-	ImGui::DestroyContext();
-#endif
+	_imgui->cleanup();
 
 	return Common::kNoError;
 }
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 828a65b09d2..c63eac8708f 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -53,6 +53,7 @@ class Dialog;
 class FadeShader;
 class GGPackSet;
 class Hud;
+class TwpImGui;
 class InputState;
 struct Light;
 class Lighting;
@@ -249,6 +250,7 @@ private:
 	unique_ptr<Shader> _bwShader;
 	unique_ptr<Shader> _ghostShader;
 	unique_ptr<Shader> _sepiaShader;
+	unique_ptr<TwpImGui> _imgui;
 };
 
 extern TwpEngine *g_twp;
diff --git a/engines/twp/twpimgui.cpp b/engines/twp/twpimgui.cpp
new file mode 100644
index 00000000000..cffc73a0801
--- /dev/null
+++ b/engines/twp/twpimgui.cpp
@@ -0,0 +1,108 @@
+/* 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 "twp/twpimgui.h"
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#ifdef USE_IMGUI
+#include "graphics/imgui/backends/imgui_impl_sdl2_scummvm.h"
+#include "graphics/imgui/backends/imgui_impl_opengl3_scummvm.h"
+#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
+// here I undefined these symbols because of the <X11/Xlib.h> defining them
+// and messing with all classes or structure using the same names
+#undef Bool
+#undef CursorShape
+#undef Expose
+#undef KeyPress
+#undef KeyRelease
+#undef FocusIn
+#undef FocusOut
+#undef FontChange
+#undef None
+#undef Status
+#undef Unsorted
+
+#include "twp/debugtools.h"
+#endif
+
+namespace Twp {
+
+#ifdef USE_IMGUI
+SDL_Window *g_window = nullptr;
+#endif
+
+void DearImGui::init() {
+#ifdef USE_IMGUI
+	// Setup Dear ImGui
+	OpenGLSdlGraphics3dManager *manager = dynamic_cast<OpenGLSdlGraphics3dManager *>(g_system->getPaletteManager());
+	IMGUI_CHECKVERSION();
+	ImGui::CreateContext();
+	g_window = manager->getWindow()->getSDLWindow();
+	SDL_GLContext glContext = SDL_GL_GetCurrentContext();
+	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
+	ImGui_ImplOpenGL3_Init("#version 110");
+	ImGui::StyleColorsDark();
+
+	ImGuiIO &io = ImGui::GetIO();
+	Common::Path initPath(ConfMan.getPath("savepath"));
+	initPath = initPath.appendComponent("twp_imgui.ini");
+	io.IniFilename = initPath.toString().c_str();
+#endif
+}
+
+void DearImGui::cleanup() {
+#ifdef USE_IMGUI
+	ImGui_ImplOpenGL3_Shutdown();
+	ImGui_ImplSDL2_Shutdown();
+	ImGui::DestroyContext();
+#endif
+}
+
+bool DearImGui::processEvent(const Common::Event *event) {
+#ifdef USE_IMGUI
+	ImGui_ImplSDL2_ProcessEvent(event);
+	ImGuiIO &io = ImGui::GetIO();
+	if (io.WantTextInput || io.WantCaptureMouse)
+		return true;
+#endif
+	return false;
+}
+
+void DearImGui::render() {
+#ifdef USE_IMGUI
+	ImGui_ImplOpenGL3_NewFrame();
+	ImGui_ImplSDL2_NewFrame(g_window);
+	ImGui::NewFrame();
+	onRender();
+	ImGui::Render();
+	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+#endif
+}
+
+void TwpImGui::onRender() {
+#ifdef USE_IMGUI
+	onImGuiRender();
+#endif
+}
+
+} // namespace Twp
diff --git a/engines/twp/twpimgui.h b/engines/twp/twpimgui.h
new file mode 100644
index 00000000000..140db36b097
--- /dev/null
+++ b/engines/twp/twpimgui.h
@@ -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/>.
+ *
+ */
+
+#ifndef TWP_IMGUI_H
+#define TWP_IMGUI_H
+
+namespace Common {
+struct Event;
+}
+
+namespace Twp {
+
+class DearImGui {
+public:
+	virtual ~DearImGui() = default;
+
+	void init();
+	bool processEvent(const Common::Event *event);
+	void render();
+	void cleanup();
+
+protected:
+	virtual void onRender() = 0;
+};
+
+class TwpImGui : public DearImGui {
+private:
+	void onRender() override;
+};
+
+} // namespace Twp
+
+#endif


Commit: f0866ab1870db107d7c306050a4ed3eb29e20a37
    https://github.com/scummvm/scummvm/commit/f0866ab1870db107d7c306050a4ed3eb29e20a37
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove static ids

Changed paths:
  R engines/twp/ids.cpp
    engines/twp/actorlib.cpp
    engines/twp/audio.cpp
    engines/twp/ids.h
    engines/twp/module.mk
    engines/twp/motor.cpp
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/objlib.cpp
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/room.cpp
    engines/twp/savegame.cpp
    engines/twp/scenegraph.cpp
    engines/twp/syslib.cpp
    engines/twp/thread.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 647fe50b009..6c88dda7727 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -23,6 +23,7 @@
 #include "twp/detection.h"
 #include "twp/hud.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
@@ -455,10 +456,10 @@ static SQInteger actorLockFacing(HSQUIRRELVM v) {
 	} break;
 	case OT_TABLE: {
 		HSQOBJECT obj;
-		SQInteger back = FACE_BACK;
-		SQInteger front = FACE_FRONT;
-		SQInteger left = FACE_LEFT;
-		SQInteger right = FACE_RIGHT;
+		SQInteger back = static_cast<SQInteger>(Facing::FACE_BACK);
+		SQInteger front = static_cast<SQInteger>(Facing::FACE_FRONT);
+		SQInteger left = static_cast<SQInteger>(Facing::FACE_LEFT);
+		SQInteger right = static_cast<SQInteger>(Facing::FACE_RIGHT);
 		SQInteger reset = 0;
 		sq_getstackobj(v, 3, &obj);
 		sqgetf(v, obj, "back", back);
@@ -679,16 +680,16 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get dist");
 	Math::Vector2d dir;
 	switch (actor->getFacing()) {
-	case FACE_FRONT:
+	case Facing::FACE_FRONT:
 		dir = Math::Vector2d(0, -dist);
 		break;
-	case FACE_BACK:
+	case Facing::FACE_BACK:
 		dir = Math::Vector2d(0, dist);
 		break;
-	case FACE_LEFT:
+	case Facing::FACE_LEFT:
 		dir = Math::Vector2d(-dist, 0);
 		break;
-	case FACE_RIGHT:
+	case Facing::FACE_RIGHT:
 		dir = Math::Vector2d(dist, 0);
 		break;
 	}
@@ -789,7 +790,7 @@ static SQInteger createActor(HSQUIRRELVM v) {
 	sq_resetobject(&actor->_table);
 	sq_getstackobj(v, 2, &actor->_table);
 	sq_addref(vm, &actor->_table);
-	setId(actor->_table, newActorId());
+	setId(actor->_table, g_twp->_resManager->newActorId());
 
 	Common::String key;
 	sqgetf(actor->_table, "_key", key);
diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 2963462cb31..4d4013b7b3d 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -28,6 +28,7 @@
 #include "twp/twp.h"
 #include "twp/audio.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/squtil.h"
 
@@ -58,7 +59,7 @@ bool SoundStream::seek(int64 offset, int whence) {
 	return _stream.seek(offset, whence);
 }
 
-SoundDefinition::SoundDefinition(const Common::String &name) : _name(name), _id(newSoundDefId()) {
+SoundDefinition::SoundDefinition(const Common::String &name) : _name(name), _id(g_twp->_resManager->newSoundDefId()) {
 }
 
 void SoundDefinition::load() {
@@ -260,7 +261,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 	if (!audioStream)
 		error("Failed to load audio: %s", name.c_str());
 
-	int id = newSoundId();
+	int id = g_twp->_resManager->newSoundId();
 	if (fadeInTimeMs > 0.f) {
 		volume = 0;
 	}
diff --git a/engines/twp/ids.cpp b/engines/twp/ids.cpp
deleted file mode 100644
index d172de416d5..00000000000
--- a/engines/twp/ids.cpp
+++ /dev/null
@@ -1,120 +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 "twp/ids.h"
-
-namespace Twp {
-
-static int gRoomId = START_ROOMID;
-static int gActorId = START_ACTORID;
-static int gObjId = START_OBJECTID;
-static int gSoundDefId = START_SOUNDDEFID;
-static int gSoundId = START_SOUNDID;
-static int gThreadId = START_THREADID;
-static int gCallbackId = START_CALLBACKID;
-static int gLightId = START_LIGHTID;
-
-static inline bool isBetween(int id, int startId, int endId) {
-	return id >= startId && id < endId;
-}
-
-bool isThread(int id) {
-	return isBetween(id, START_THREADID, END_THREADID);
-}
-
-bool isRoom(int id) {
-	return isBetween(id, START_ROOMID, END_THREADID);
-}
-
-bool isActor(int id) {
-	return isBetween(id, START_ACTORID, END_ACTORID);
-}
-
-bool isObject(int id) {
-	return isBetween(id, START_OBJECTID, END_OBJECTID);
-}
-
-bool isSound(int id) {
-	return isBetween(id, START_SOUNDID, END_SOUNDID);
-}
-
-bool isLight(int id) {
-	return isBetween(id, START_LIGHTID, END_LIGHTID);
-}
-
-bool isCallback(int id) {
-	return isBetween(id, START_CALLBACKID, END_CALLBACKID);
-}
-
-int newRoomId() {
-	return gRoomId++;
-}
-
-int newObjId() {
-	return gObjId++;
-}
-
-int newActorId() {
-	return gActorId++;
-}
-
-int newSoundDefId() {
-	return gSoundDefId++;
-}
-
-int newSoundId() {
-	return gSoundId++;
-}
-
-int newThreadId() {
-	return gThreadId++;
-}
-
-int newCallbackId() {
-	return gCallbackId++;
-}
-
-void setCallbackId(int id) {
-	gCallbackId = id;
-}
-
-int getCallbackId() {
-	return gCallbackId;
-}
-
-int newLightId() {
-	return gLightId++;
-}
-
-Facing getOppositeFacing(Facing facing) {
-	switch (facing) {
-	default:
-	case FACE_FRONT:
-		return FACE_BACK;
-	case FACE_BACK:
-		return FACE_FRONT;
-	case FACE_LEFT:
-		return FACE_RIGHT;
-	case FACE_RIGHT:
-		return FACE_LEFT;
-	}
-}
-} // namespace Twp
diff --git a/engines/twp/ids.h b/engines/twp/ids.h
index 53e8c3c7f59..3d2b5ed1589 100644
--- a/engines/twp/ids.h
+++ b/engines/twp/ids.h
@@ -22,25 +22,6 @@
 #ifndef TWP_IDS_H
 #define TWP_IDS_H
 
-#include "common/hashmap.h"
-
-#define START_ACTORID 1000
-#define END_ACTORID 2000
-#define START_ROOMID 2000
-#define END_ROOMID 3000
-#define START_OBJECTID 3000
-#define END_OBJECTID 100000
-#define START_LIGHTID 100000
-#define END_LIGHTID 200000
-#define START_SOUNDDEFID 200000
-#define END_SOUNDDEFID 250000
-#define START_SOUNDID 250000
-#define END_SOUNDID 300000
-#define START_THREADID 300000
-#define END_THREADID 8000000
-#define START_CALLBACKID 8000000
-#define END_CALLBACKID 10000000
-
 #define ALL 1
 #define HERE 0
 #define GONE 4
@@ -229,47 +210,4 @@
 #define BUTTON_MOUSE_LEFT 0x3ED
 #define BUTTON_MOUSE_RIGHT 0x3EE
 
-namespace Twp {
-
-enum Facing {
-	FACE_RIGHT = 1,
-	FACE_LEFT = 2,
-	FACE_FRONT = 4,
-	FACE_BACK = 8
-};
-
-}
-
-namespace Common {
-template<>
-struct Hash<Twp::Facing> : public UnaryFunction<Twp::Facing, uint> {
-	uint operator()(Twp::Facing val) const { return (uint)val; }
-};
-} // namespace Common
-
-namespace Twp {
-
-bool isThread(int id);
-bool isRoom(int id);
-bool isActor(int id);
-bool isObject(int id);
-bool isSound(int id);
-bool isLight(int id);
-bool isCallback(int id);
-
-int newRoomId();
-int newObjId();
-int newActorId();
-int newSoundDefId();
-int newSoundId();
-int newThreadId();
-int newCallbackId();
-int newLightId();
-void setCallbackId(int id);
-int getCallbackId();
-
-Facing getOppositeFacing(Facing facing);
-
-} // namespace Twp
-
 #endif
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 89dcf523dab..5331234b4e5 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -16,7 +16,6 @@ MODULE_OBJS = \
 	ggpack.o \
 	graph.o \
 	hud.o \
-	ids.o \
 	lighting.o \
 	lip.o \
 	metaengine.o \
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 51aeab307a2..009d1a803b7 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -24,6 +24,7 @@
 #include "twp/twp.h"
 #include "twp/detection.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/squtil.h"
 #include "twp/tsv.h"
@@ -228,7 +229,7 @@ void WalkTo::actorArrived() {
 		Common::SharedPtr<Object> noun1 = _obj->_exec.noun1;
 		Common::SharedPtr<Object> noun2 = _obj->_exec.noun2;
 		// call `postWalk`callback
-		Common::String funcName = isActor(noun1->getId()) ? "actorPostWalk" : "objectPostWalk";
+		Common::String funcName = g_twp->_resManager->isActor(noun1->getId()) ? "actorPostWalk" : "objectPostWalk";
 		if (sqrawexists(_obj->_table, funcName)) {
 			debugC(kDebugGame, "call %s callback", funcName.c_str());
 			HSQOBJECT n2Table;
@@ -270,9 +271,9 @@ void WalkTo::update(float elapsed) {
 			Math::Vector2d dd = delta * factor;
 			_obj->_node->setPos(_obj->_node->getPos() + dd);
 			if (abs(delta.getX()) >= abs(delta.getY())) {
-				_obj->setFacing(delta.getX() >= 0 ? FACE_RIGHT : FACE_LEFT);
+				_obj->setFacing(delta.getX() >= 0 ? Facing::FACE_RIGHT : Facing::FACE_LEFT);
 			} else {
-				_obj->setFacing(delta.getY() > 0 ? FACE_BACK : FACE_FRONT);
+				_obj->setFacing(delta.getY() > 0 ? Facing::FACE_BACK : Facing::FACE_FRONT);
 			}
 		}
 	}
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 94bb75bd93b..f92b4be10ee 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -22,6 +22,7 @@
 #include "twp/twp.h"
 #include "twp/detection.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/squtil.h"
 
@@ -101,7 +102,7 @@ Object::~Object() {
 Common::SharedPtr<Object> Object::createActor() {
 	Common::SharedPtr<Object> result(new Object());
 	result->_hotspot = Common::Rect(-18, 0, 37, 71);
-	result->_facing = FACE_FRONT;
+	result->_facing = Facing::FACE_FRONT;
 	result->_useWalkboxes = true;
 	result->showLayer("blink", false);
 	result->showLayer("eyes_left", false);
@@ -167,7 +168,7 @@ bool Object::playCore(const Common::String &state, bool loop, bool instant) {
 	}
 
 	// if not found, clear the previous animation
-	if (!isActor(getId())) {
+	if (!g_twp->_resManager->isActor(getId())) {
 		_nodeAnim->clearFrames();
 		_nodeAnim->clear();
 	}
@@ -233,15 +234,15 @@ void Object::trig(const Common::String &name) {
 
 Common::String Object::suffix() const {
 	switch (getFacing()) {
-	case FACE_BACK:
+	case Facing::FACE_BACK:
 		return "_back";
 	default:
-	case FACE_FRONT:
+	case Facing::FACE_FRONT:
 		return "_front";
-	case FACE_LEFT:
+	case Facing::FACE_LEFT:
 		// there is no animation with `left` suffix but use left and flip the sprite
 		return "_right";
-	case FACE_RIGHT:
+	case Facing::FACE_RIGHT:
 		return "_right";
 	}
 }
@@ -259,14 +260,14 @@ int Object::defaultVerbId() {
 	SQInteger result = VERB_LOOKAT;
 	if (sqrawexists(_table, "defaultVerb"))
 		sqgetf(_table, "defaultVerb", result);
-	else if (isActor(getId())) {
+	else if (g_twp->_resManager->isActor(getId())) {
 		result = sqrawexists(_table, "verbTalkTo") ? VERB_TALKTO : VERB_WALKTO;
 	}
 	return result;
 }
 
 Math::Vector2d Object::getUsePos() {
-	return isActor(getId()) ? _node->getPos() + _node->getOffset() : _node->getPos() + _node->getOffset() + _usePos;
+	return g_twp->_resManager->isActor(getId()) ? _node->getPos() + _node->getOffset() : _node->getPos() + _node->getOffset() + _usePos;
 }
 
 bool Object::isTouchable() {
@@ -406,7 +407,7 @@ void Object::setRoom(Common::SharedPtr<Object> object, Common::SharedPtr<Room> r
 		}
 		object->_room = room;
 
-		if (roomChanged && isActor(object->getId())) {
+		if (roomChanged && g_twp->_resManager->isActor(object->getId())) {
 			if (room == g_twp->_room) {
 				g_twp->actorEnter(object);
 			} else if (oldRoom == g_twp->_room) {
@@ -437,14 +438,14 @@ void Object::stopObjectMotors() {
 	_node->setOffset({0.f, 0.f});
 	_node->setShakeOffset({0.f, 0.f});
 	_node->setScale({1.f, 1.f});
-	if (isActor(getId()))
+	if (g_twp->_resManager->isActor(getId()))
 		stand();
 }
 
 void Object::setFacing(Facing facing) {
 	if (_facing != facing) {
 		debugC(kDebugGame, "set facing: %d", facing);
-		bool update = !(((_facing == FACE_LEFT) && (facing == FACE_RIGHT)) || ((_facing == FACE_RIGHT) && (facing == FACE_LEFT)));
+		bool update = !(((_facing == Facing::FACE_LEFT) && (facing == Facing::FACE_RIGHT)) || ((_facing == Facing::FACE_RIGHT) && (facing == Facing::FACE_LEFT)));
 		_facing = facing;
 		if (update && _nodeAnim)
 			play(_animName, _animLoop);
@@ -454,17 +455,17 @@ void Object::setFacing(Facing facing) {
 Facing Object::getDoorFacing() {
 	int flags = getFlags();
 	if (flags & DOOR_LEFT)
-		return FACE_LEFT;
+		return Facing::FACE_LEFT;
 	else if (flags & DOOR_RIGHT)
-		return FACE_RIGHT;
+		return Facing::FACE_RIGHT;
 	else if (flags & DOOR_FRONT)
-		return FACE_FRONT;
+		return Facing::FACE_FRONT;
 	else
-		return FACE_BACK;
+		return Facing::FACE_BACK;
 }
 
 bool Object::inInventory() {
-	return isObject(getId()) && getIcon().size() > 0;
+	return g_twp->_resManager->isObject(getId()) && getIcon().size() > 0;
 }
 
 bool Object::contains(Math::Vector2d pos) {
@@ -644,10 +645,10 @@ void Object::lockFacing(int facing) {
 }
 
 void Object::lockFacing(Facing left, Facing right, Facing front, Facing back) {
-	_facingMap.push_back({FACE_LEFT, left});
-	_facingMap.push_back({FACE_RIGHT, right});
-	_facingMap.push_back({FACE_FRONT, front});
-	_facingMap.push_back({FACE_BACK, back});
+	_facingMap.push_back({Facing::FACE_LEFT, left});
+	_facingMap.push_back({Facing::FACE_RIGHT, right});
+	_facingMap.push_back({Facing::FACE_FRONT, front});
+	_facingMap.push_back({Facing::FACE_BACK, back});
 }
 
 int Object::flags() {
@@ -778,7 +779,7 @@ void Object::walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing) {
 // Walks an actor to the `obj` and then faces it.
 void Object::walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	debugC(kDebugGame, "walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
-	Facing facing = (Facing)obj->_useDir;
+	int facing = static_cast<int>(obj->_useDir);
 	walk(actor, (Vector2i)obj->getUsePos(), facing);
 }
 
diff --git a/engines/twp/object.h b/engines/twp/object.h
index 937cd1003b9..b5a4fbe9736 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -54,6 +54,13 @@ enum Direction {
 	dBack = 8
 };
 
+enum class Facing {
+	FACE_RIGHT = 1,
+	FACE_LEFT = 2,
+	FACE_FRONT = 4,
+	FACE_BACK = 8
+};
+
 enum class UseFlag {
 	ufNone,
 	ufUseWith,
@@ -243,7 +250,7 @@ public:
 	int _animFlags = 0;
 	bool _animLoop = false;
 	Common::Array<LockFacing> _facingMap;
-	Facing _facing = FACE_FRONT;
+	Facing _facing = Facing::FACE_FRONT;
 	int _facingLockValue = 0;
 	float _fps = 0.f;
 	Common::HashMap<int, Common::SharedPtr<Trigger> > _triggers;
diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 31db3c78953..98e212284d6 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -23,6 +23,7 @@
 #include "twp/detection.h"
 #include "twp/hud.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/sqgame.h"
 #include "twp/squtil.h"
@@ -205,7 +206,7 @@ static SQInteger isInventoryOnScreen(HSQUIRRELVM v) {
 // if (isObject(obj) && objectValidUsePos(obj) && objectTouchable(obj)) {
 static SQInteger isObject(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
-	sqpush(v, obj && isObject(obj->getId()));
+	sqpush(v, obj && g_twp->_resManager->isObject(obj->getId()));
 	return 1;
 }
 
@@ -1000,7 +1001,7 @@ static SQInteger removeInventory(HSQUIRRELVM v) {
 	Common::SharedPtr<Object> obj = sqobj(v, 2);
 	if (!obj)
 		return sq_throwerror(v, "failed to get object");
-	if (isActor(obj->getId())) {
+	if (g_twp->_resManager->isActor(obj->getId())) {
 		obj->_inventory.clear();
 		obj->_inventoryOffset = 0;
 	} else if (obj->_owner) {
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 8c83187267b..2ca09d6cdf4 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -106,4 +106,82 @@ Common::SharedPtr<Font> ResManager::font(const Common::String &name) {
 	return _fonts[key];
 }
 
+static inline bool isBetween(int id, int startId, int endId) {
+	return id >= startId && id < endId;
+}
+
+bool ResManager::isThread(int id) const {
+	return isBetween(id, START_THREADID, END_THREADID);
+}
+
+bool ResManager::isRoom(int id) const {
+	return isBetween(id, START_ROOMID, END_THREADID);
+}
+
+bool ResManager::isActor(int id) const {
+	return isBetween(id, START_ACTORID, END_ACTORID);
+}
+
+bool ResManager::isObject(int id) const {
+	return isBetween(id, START_OBJECTID, END_OBJECTID);
+}
+
+bool ResManager::isSound(int id) const {
+	return isBetween(id, START_SOUNDID, END_SOUNDID);
+}
+
+bool ResManager::isLight(int id) const {
+	return isBetween(id, START_LIGHTID, END_LIGHTID);
+}
+
+bool ResManager::isCallback(int id) const {
+	return isBetween(id, START_CALLBACKID, END_CALLBACKID);
+}
+
+int ResManager::newRoomId() {
+	return _roomId++;
+}
+
+int ResManager::newObjId() {
+	return _objId++;
+}
+
+int ResManager::newActorId() {
+	return _actorId++;
+}
+
+int ResManager::newSoundDefId() {
+	return _soundDefId++;
+}
+
+int ResManager::newSoundId() {
+	return _soundId++;
+}
+
+int ResManager::newThreadId() {
+	return _threadId++;
+}
+
+int ResManager::newCallbackId() {
+	return _callbackId++;
+}
+
+void ResManager::resetIds(int callbackId) {
+	// don't reset _roomId and _actorId because there are not dynamically created
+	_objId = START_OBJECTID;
+	_soundDefId = START_SOUNDDEFID;
+	_soundId = START_SOUNDID;
+	_threadId = START_THREADID;
+	_lightId = START_LIGHTID;
+	_callbackId = callbackId;
+}
+
+int ResManager::getCallbackId() const {
+	return _callbackId;
+}
+
+int ResManager::newLightId() {
+	return _lightId++;
+}
+
 } // namespace Twp
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index 96d58230e8d..daa7d7c9698 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -25,20 +25,57 @@
 #include "common/str.h"
 #include "common/hashmap.h"
 #include "twp/gfx.h"
-#include "twp/font.h"
 #include "twp/spritesheet.h"
 
 namespace Twp {
 
 class ResManager {
-public:
-	ResManager() {}
+private:
+	enum {
+		START_ACTORID = 1000,
+		END_ACTORID = 2000,
+		START_ROOMID = 2000,
+		END_ROOMID = 3000,
+		START_OBJECTID = 3000,
+		END_OBJECTID = 100000,
+		START_LIGHTID = 100000,
+		END_LIGHTID = 200000,
+		START_SOUNDDEFID = 200000,
+		END_SOUNDDEFID = 250000,
+		START_SOUNDID = 250000,
+		END_SOUNDID = 300000,
+		START_THREADID = 300000,
+		END_THREADID = 8000000,
+		START_CALLBACKID = 8000000,
+		END_CALLBACKID = 10000000,
+	};
 
+public:
 	static Common::String getKey(const Common::String &path);
 	Texture *texture(const Common::String &name);
 	SpriteSheet *spriteSheet(const Common::String &name);
 	Common::SharedPtr<Font> font(const Common::String &name);
 
+	bool isThread(int id) const;
+	bool isRoom(int id) const;
+	bool isActor(int id) const;
+	bool isObject(int id) const;
+	bool isSound(int id) const;
+	bool isLight(int id) const;
+	bool isCallback(int id) const;
+	int getCallbackId() const;
+
+	int newRoomId();
+	int newObjId();
+	int newActorId();
+	int newSoundDefId();
+	int newSoundId();
+	int newThreadId();
+	int newCallbackId();
+	int newLightId();
+
+	void resetIds(int callbackId);
+
 private:
 	void loadTexture(const Common::String &name);
 	void loadSpriteSheet(const Common::String &name);
@@ -48,6 +85,16 @@ public:
 	Common::HashMap<Common::String, Texture> _textures;
 	Common::HashMap<Common::String, SpriteSheet> _spriteSheets;
 	Common::HashMap<Common::String, Common::SharedPtr<Font> > _fonts;
+
+private:
+	int _roomId = START_ROOMID;
+	int _actorId = START_ACTORID;
+	int _objId = START_OBJECTID;
+	int _soundDefId = START_SOUNDDEFID;
+	int _soundId = START_SOUNDID;
+	int _threadId = START_THREADID;
+	int _callbackId = START_CALLBACKID;
+	int _lightId = START_LIGHTID;
 };
 } // namespace Twp
 
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 409cd867762..686a6b5ec46 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -139,7 +139,7 @@ static Common::Array<Walkbox> merge(const Common::Array<Walkbox> &walkboxes) {
 }
 
 Room::Room(const Common::String &name, HSQOBJECT &table) : _table(table) {
-	setId(_table, newRoomId());
+	setId(_table, g_twp->_resManager->newRoomId());
 	_name = name;
 	_scene = Common::SharedPtr<Scene>(new Scene());
 	_scene->addChild(&_overlayNode);
@@ -161,7 +161,7 @@ Common::SharedPtr<Object> Room::createObject(const Common::String &sheet, const
 	sq_pop(v, 1);
 
 	// assign an id
-	setId(obj->_table, newObjId());
+	setId(obj->_table, g_twp->_resManager->newObjId());
 	Common::String name = frames.size() > 0 ? frames[0] : "noname";
 	sqsetf(obj->_table, "name", name);
 	obj->_key = name;
@@ -200,7 +200,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 	sq_pop(v, 1);
 
 	// assign an id
-	setId(obj->_table, newObjId());
+	setId(obj->_table, g_twp->_resManager->newObjId());
 	debugC(kDebugGame, "Create object with new table: %s #%d", obj->_name.c_str(), obj->getId());
 	obj->_name = Common::String::format("text#%d: %s", obj->getId(), text.c_str());
 
@@ -311,7 +311,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 		for (auto it = jobjects.begin(); it != jobjects.end(); it++) {
 			const Common::JSONObject &jObject = (*it)->asObject();
 			Common::SharedPtr<Object> obj(new Object());
-			Twp::setId(obj->_table, newObjId());
+			Twp::setId(obj->_table, g_twp->_resManager->newObjId());
 			obj->_key = jObject["name"]->asString();
 			obj->_node->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			obj->_node->setZSort(jObject["zsort"]->asIntegerNumber());
diff --git a/engines/twp/savegame.cpp b/engines/twp/savegame.cpp
index f62dc98dc52..32e4bbb2493 100644
--- a/engines/twp/savegame.cpp
+++ b/engines/twp/savegame.cpp
@@ -27,6 +27,7 @@
 #include "twp/ggpack.h"
 #include "twp/hud.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/savegame.h"
 #include "twp/squtil.h"
@@ -521,7 +522,7 @@ void SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
 			g_twp->_callbacks.push_back(Common::SharedPtr<Callback>(new Callback(id, time, name, args)));
 		}
 	}
-	setCallbackId(json["nextGuid"]->asIntegerNumber());
+	g_twp->_resManager->resetIds(json["nextGuid"]->asIntegerNumber());
 }
 
 void SaveGameManager::loadGlobals(const Common::JSONObject &json) {
@@ -605,7 +606,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 static void fillMissingProperties(const Common::String &k, HSQOBJECT &oTable, void *data) {
 	JsonCallback *params = static_cast<JsonCallback *>(data);
 	if ((k.size() > 0) && (!k.hasPrefix("_"))) {
-		if (!(params->skipObj && isObject(getId(oTable)) && (params->pseudo || sqrawexists(*params->rootTable, k)))) {
+		if (!(params->skipObj && g_twp->_resManager->isObject(getId(oTable)) && (params->pseudo || sqrawexists(*params->rootTable, k)))) {
 			Common::JSONValue *json = tojson(oTable, true);
 			if (json) {
 				(*params->jObj)[k] = json;
@@ -645,11 +646,11 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 		if (checkId) {
 			SQInteger id = 0;
 			sqgetf(obj, "_id", id);
-			if (isActor(id)) {
+			if (g_twp->_resManager->isActor(id)) {
 				Common::SharedPtr<Object> a(actor(id));
 				jObj["_actorKey"] = new Common::JSONValue(a->_key);
 				return new Common::JSONValue(jObj);
-			} else if (isObject(id)) {
+			} else if (g_twp->_resManager->isObject(id)) {
 				Common::SharedPtr<Object> o(sqobj(id));
 				if (!o)
 					return new Common::JSONValue();
@@ -657,7 +658,7 @@ static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipOb
 				if (o->_room && o->_room->_pseudo)
 					jObj["_roomKey"] = new Common::JSONValue(o->_room->_name);
 				return new Common::JSONValue(jObj);
-			} else if (isRoom(id)) {
+			} else if (g_twp->_resManager->isRoom(id)) {
 				Common::SharedPtr<Room> r(getRoom(id));
 				jObj["_roomKey"] = new Common::JSONValue(r->_name);
 				return new Common::JSONValue(jObj);
@@ -763,7 +764,7 @@ static Common::JSONValue *createJCallbackArray() {
 static Common::JSONValue *createJCallbacks() {
 	Common::JSONObject json;
 	json["callbacks"] = createJCallbackArray();
-	json["nextGuid"] = new Common::JSONValue((long long int)getCallbackId());
+	json["nextGuid"] = new Common::JSONValue((long long int)g_twp->_resManager->getCallbackId());
 	return new Common::JSONValue(json);
 }
 
@@ -907,7 +908,7 @@ static Common::JSONValue *createJObject(HSQOBJECT &table, Common::SharedPtr<Obje
 
 static void fillObjects(const Common::String &k, HSQOBJECT &v, void *data) {
 	Common::JSONObject *jObj = static_cast<Common::JSONObject *>(data);
-	if (isObject(getId(v))) {
+	if (g_twp->_resManager->isObject(getId(v))) {
 		Common::SharedPtr<Object> obj(sqobj(v));
 		if (!obj || (obj->_objType == otNone)) {
 			// info fmt"obj: createJObject({k})"
@@ -925,7 +926,7 @@ static Common::JSONValue *createJObjects() {
 
 static void fillPseudoObjects(const Common::String &k, HSQOBJECT &v, void *data) {
 	Common::JSONObject *jObj = static_cast<Common::JSONObject *>(data);
-	if (isObject(getId(v))) {
+	if (g_twp->_resManager->isObject(getId(v))) {
 		Common::SharedPtr<Object> obj(sqobj(v));
 		// info fmt"pseudoObj: createJObject({k})"
 		(*jObj)[k] = createJObject(v, obj);
diff --git a/engines/twp/scenegraph.cpp b/engines/twp/scenegraph.cpp
index 0f73fde0287..172acfb20a0 100644
--- a/engines/twp/scenegraph.cpp
+++ b/engines/twp/scenegraph.cpp
@@ -368,7 +368,7 @@ void Anim::update(float elapsed) {
 		}
 		if (_anim && _anim->offsets.size() > 0) {
 			Math::Vector2d off = _frameIndex < _anim->offsets.size() ? _anim->offsets[_frameIndex] : Math::Vector2d();
-			if (_obj->getFacing() == FACE_LEFT) {
+			if (_obj->getFacing() == Facing::FACE_LEFT) {
 				off.setX(-off.getX());
 			}
 			_offset = off;
@@ -394,7 +394,7 @@ void Anim::drawCore(Math::Matrix4 trsf) {
 		if (frame == "null")
 			return;
 
-		bool flipX = _obj->getFacing() == FACE_LEFT;
+		bool flipX = _obj->getFacing() == Facing::FACE_LEFT;
 		if (_sheet.size() == 0) {
 			_sheet = _obj->_sheet;
 			if (_sheet.size() == 0 && _obj->_room) {
@@ -842,7 +842,7 @@ void HotspotMarkerNode::drawCore(Math::Matrix4 trsf) {
 		Common::SharedPtr<Layer> layer = g_twp->_room->_layers[i];
 		for (size_t j = 0; j < layer->_objects.size(); j++) {
 			Common::SharedPtr<Object> obj = layer->_objects[j];
-			if (isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
+			if (g_twp->_resManager->isObject(obj->getId()) && (obj->_objType == otNone) && obj->isTouchable()) {
 				Math::Vector2d pos = g_twp->roomToScreen(obj->_node->getAbsPos());
 				Math::Matrix4 t;
 				t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 1067b905375..574e2d28188 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -23,6 +23,7 @@
 #include "twp/callback.h"
 #include "twp/dialog.h"
 #include "twp/object.h"
+#include "twp/resmanager.h"
 #include "twp/room.h"
 #include "twp/savegame.h"
 #include "twp/sqgame.h"
@@ -145,7 +146,7 @@ static SQInteger addCallback(HSQUIRRELVM v) {
 		args.push_back(arg);
 	}
 
-	Common::SharedPtr<Callback> callback(new Callback(newCallbackId(), duration, methodName, args));
+	Common::SharedPtr<Callback> callback(new Callback(g_twp->_resManager->newCallbackId(), duration, methodName, args));
 	g_twp->_callbacks.push_back(callback);
 
 	sqpush(v, callback->getId());
@@ -336,7 +337,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 
 	Common::SharedPtr<ThreadBase> t = sqthread(id);
 	if (!t) {
-		if (!isSound(id)) {
+		if (!g_twp->_resManager->isSound(id)) {
 			warning("thread and sound not found: %lld", id);
 			return 0;
 		}
@@ -881,10 +882,10 @@ void sqgame_register_constants(HSQUIRRELVM v) {
 	regConst(v, "FADE_OUT", FADE_OUT);
 	regConst(v, "FADE_WOBBLE", FADE_WOBBLE);
 	regConst(v, "FADE_WOBBLE_TO_SEPIA", FADE_WOBBLE_TO_SEPIA);
-	regConst(v, "FACE_FRONT", FACE_FRONT);
-	regConst(v, "FACE_BACK", FACE_BACK);
-	regConst(v, "FACE_LEFT", FACE_LEFT);
-	regConst(v, "FACE_RIGHT", FACE_RIGHT);
+	regConst(v, "FACE_FRONT", static_cast<int>(Facing::FACE_FRONT));
+	regConst(v, "FACE_BACK", static_cast<int>(Facing::FACE_BACK));
+	regConst(v, "FACE_LEFT", static_cast<int>(Facing::FACE_LEFT));
+	regConst(v, "FACE_RIGHT", static_cast<int>(Facing::FACE_RIGHT));
 	regConst(v, "FACE_FLIP", FACE_FLIP);
 	regConst(v, "DIR_FRONT", DIR_FRONT);
 	regConst(v, "DIR_BACK", DIR_BACK);
diff --git a/engines/twp/thread.cpp b/engines/twp/thread.cpp
index 1b2886308ec..d9763810b46 100644
--- a/engines/twp/thread.cpp
+++ b/engines/twp/thread.cpp
@@ -22,8 +22,9 @@
 #include "twp/twp.h"
 #include "twp/detection.h"
 #include "twp/ids.h"
-#include "twp/thread.h"
+#include "twp/resmanager.h"
 #include "twp/squtil.h"
+#include "twp/thread.h"
 
 namespace Twp {
 
@@ -50,7 +51,7 @@ void ThreadBase::resume() {
 }
 
 Thread::Thread(const Common::String &name, bool global, HSQOBJECT threadObj, HSQOBJECT envObj, HSQOBJECT closureObj, const Common::Array<HSQOBJECT> args) {
-	_id = newThreadId();
+	_id = g_twp->_resManager->newThreadId();
 	_name = name;
 	_global = global;
 	_threadObj = threadObj;
@@ -124,7 +125,7 @@ Cutscene::Cutscene(const Common::String &name, int parentThreadId, HSQOBJECT thr
 	  _envObj(envObj) {
 
 	_name = name;
-	_id = newThreadId();
+	_id = g_twp->_resManager->newThreadId();
 	_inputState = g_twp->_inputState.getState();
 	_actor = g_twp->_followActor;
 	_showCursor = g_twp->_inputState.getShowCursor();
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index df4686de6f5..74042310bbf 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -146,7 +146,7 @@ bool TwpEngine::preWalk(Common::SharedPtr<Object> actor, VerbId verbId, Common::
 		sqcallfunc(result, actor->_table, "actorPreWalk", verbId.id, noun1->_table, n2Table);
 	}
 	if (!result) {
-		Common::String funcName = isActor(noun1->getId()) ? "actorPreWalk" : "objectPreWalk";
+		Common::String funcName = g_twp->_resManager->isActor(noun1->getId()) ? "actorPreWalk" : "objectPreWalk";
 		if (sqrawexists(noun1->_table, funcName)) {
 			sqcallfunc(result, noun1->_table, funcName.c_str(), verbId.id, noun1->_table, n2Table);
 			debugC(kDebugGame, "%s %d n1=%s(%s) n2=%s -> %s", funcName.c_str(), verbId.id, noun1->_name.c_str(), noun1->_key.c_str(), n2Name.c_str(), result ? "yes" : "no");
@@ -1040,7 +1040,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			if (!sqrawexists(oTable, "flags"))
 				sqsetf(oTable, "flags", 0);
 			Common::SharedPtr<Object> obj(new Object(oTable, k));
-			setId(obj->_table, newObjId());
+			setId(obj->_table, g_twp->_resManager->newObjId());
 			obj->_node = Common::SharedPtr<Node>(new Node(k));
 			obj->_nodeAnim = Common::SharedPtr<Anim>(new Anim(obj.get()));
 			obj->_node->addChild(obj->_nodeAnim.get());
@@ -1062,7 +1062,7 @@ static void onGetPairs(const Common::String &k, HSQOBJECT &oTable, void *data) {
 			}
 
 			sqgetf(params->room->_table, k, obj->_table);
-			setId(obj->_table, newObjId());
+			setId(obj->_table, g_twp->_resManager->newObjId());
 			debugC(kDebugGame, "Create object: %s #%d", k.c_str(), obj->getId());
 
 			// add it to the root table if not a pseudo room
@@ -1129,7 +1129,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 					sq_pop(v, 1);
 
 					// assign an id
-					setId(obj->_table, newObjId());
+					setId(obj->_table, g_twp->_resManager->newObjId());
 					// info fmt"Create object with new table: {obj.name} #{obj.id}"
 
 					// adds the object to the room table
@@ -1183,7 +1183,7 @@ Common::SharedPtr<Room> TwpEngine::defineRoom(const Common::String &name, HSQOBJ
 	sqgetpairs(result->_table, onGetPairs, &params);
 
 	// declare the room in the root table
-	setId(result->_table, newRoomId());
+	setId(result->_table, g_twp->_resManager->newRoomId());
 	sqsetf(sqrootTbl(v), name, result->_table);
 
 	return result;
@@ -1221,7 +1221,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 	if (_actor) {
 		cancelSentence();
 		if (door) {
-			Facing facing = getOppositeFacing(door->getDoorFacing());
+			Facing facing = flip(door->getDoorFacing());
 			Object::setRoom(_actor, room);
 			if (door) {
 				_actor->setFacing(facing);
@@ -1248,7 +1248,7 @@ void TwpEngine::enterRoom(Common::SharedPtr<Room> room, Common::SharedPtr<Object
 					_room->_scalingTriggers.push_back(ScalingTrigger(obj, scaling));
 				}
 			}
-			if (isActor(obj->getId())) {
+			if (g_twp->_resManager->isActor(obj->getId())) {
 				actorEnter(obj);
 			} else if (sqrawexists(obj->_table, "enter"))
 				sqcall(obj->_table, "enter");
@@ -1295,7 +1295,7 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 			for (size_t j = 0; j < layer->_objects.size(); j++) {
 				Common::SharedPtr<Object> obj = layer->_objects[j];
 				obj->stopObjectMotors();
-				if (isActor(obj->getId())) {
+				if (g_twp->_resManager->isActor(obj->getId())) {
 					actorExit(obj);
 				}
 			}
@@ -1317,7 +1317,7 @@ void TwpEngine::exitRoom(Common::SharedPtr<Room> nextRoom) {
 				if (obj->_temporary) {
 					it = layer->_objects.erase(it);
 					continue;
-				} else if (isActor(obj->getId()) && _actor != obj) {
+				} else if (g_twp->_resManager->isActor(obj->getId()) && _actor != obj) {
 					obj->stopObjectMotors();
 				}
 				it++;
@@ -1430,7 +1430,7 @@ struct GetByZOrder {
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (obj->_node->getZSort() <= _zOrder) {
-			if (!isActor(obj->getId()) || !obj->_key.empty()) {
+			if (!g_twp->_resManager->isActor(obj->getId()) || !obj->_key.empty()) {
 				_result = obj;
 				_zOrder = obj->_node->getZSort();
 			}
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index e25b35bd5ba..a00b39c1ff8 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -32,30 +32,30 @@ Facing getFacing(int dir, Facing facing) {
 	if (dir == 0)
 		return facing;
 	if (dir == 0x10)
-		return getOppositeFacing(facing);
+		return flip(facing);
 	return (Facing)dir;
 }
 
 Facing flip(Facing facing) {
 	switch (facing) {
-	case FACE_BACK:
-		return FACE_FRONT;
+	case Facing::FACE_BACK:
+		return Facing::FACE_FRONT;
 	default:
-	case FACE_FRONT:
-		return FACE_BACK;
-	case FACE_LEFT:
-		return FACE_RIGHT;
-	case FACE_RIGHT:
-		return FACE_LEFT;
+	case Facing::FACE_FRONT:
+		return Facing::FACE_BACK;
+	case Facing::FACE_LEFT:
+		return Facing::FACE_RIGHT;
+	case Facing::FACE_RIGHT:
+		return Facing::FACE_LEFT;
 	}
 }
 
 Facing getFacingToFaceTo(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	Math::Vector2d d = obj->_node->getPos() + obj->_node->getOffset() - (actor->_node->getPos() + actor->_node->getOffset());
 	if (abs(d.getY()) > abs(d.getX())) {
-		return d.getY() > 0 ? FACE_BACK : FACE_FRONT;
+		return d.getY() > 0 ? Facing::FACE_BACK : Facing::FACE_FRONT;
 	}
-	return d.getX() > 0 ? FACE_RIGHT : FACE_LEFT;
+	return d.getX() > 0 ? Facing::FACE_RIGHT : Facing::FACE_LEFT;
 }
 
 static float parseFps(const Common::JSONValue &jFps) {
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 0965f1c260e..f7e4ffdba31 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -23,12 +23,24 @@
 #define TWP_UTIL_H
 
 #include "common/formats/json.h"
+#include "common/hashmap.h"
 #include "common/rect.h"
 #include "math/vector2d.h"
 #include "math/matrix4.h"
 #include "twp/ids.h"
 #include "twp/objectanimation.h"
 
+namespace Twp {
+enum class Facing;
+}
+
+namespace Common {
+template<>
+struct Hash<Twp::Facing> : public UnaryFunction<Twp::Facing, uint> {
+	uint operator()(Twp::Facing val) const { return (uint)val; }
+};
+} // namespace Common
+
 namespace Twp {
 
 class Object;


Commit: 158257c0f9ef321da1d6363a2406541784f3dce5
    https://github.com/scummvm/scummvm/commit/158257c0f9ef321da1d6363a2406541784f3dce5
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Remove unsued find_if method

Changed paths:
    engines/twp/util.h


diff --git a/engines/twp/util.h b/engines/twp/util.h
index f7e4ffdba31..4868ad0f120 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -100,16 +100,6 @@ size_t find(const Common::Array<T> &array, const T &o) {
 	return (size_t)-1;
 }
 
-template<typename T, typename Pred>
-size_t find_if(const Common::Array<T> &array, Pred p) {
-	for (size_t i = 0; i < array.size(); i++) {
-		if (p(array[i])) {
-			return i;
-		}
-	}
-	return (size_t)-1;
-}
-
 template<typename T>
 size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T *o) {
 	for (size_t i = 0; i < array.size(); i++) {


Commit: 0cba66b2046badae894575d8a9cad9f84c7b137c
    https://github.com/scummvm/scummvm/commit/0cba66b2046badae894575d8a9cad9f84c7b137c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Move ImGui to backends

Changed paths:
  A backends/imgui/LICENSE.txt
  A backends/imgui/backends/imgui_impl_opengl3_loader.h
  A backends/imgui/backends/imgui_impl_opengl3_scummvm.cpp
  A backends/imgui/backends/imgui_impl_opengl3_scummvm.h
  A backends/imgui/backends/imgui_impl_sdl2_scummvm.cpp
  A backends/imgui/backends/imgui_impl_sdl2_scummvm.h
  A backends/imgui/imconfig.h
  A backends/imgui/imgui.cpp
  A backends/imgui/imgui.h
  A backends/imgui/imgui_demo.cpp
  A backends/imgui/imgui_draw.cpp
  A backends/imgui/imgui_internal.h
  A backends/imgui/imgui_tables.cpp
  A backends/imgui/imgui_widgets.cpp
  A backends/imgui/imstb_rectpack.h
  A backends/imgui/imstb_textedit.h
  A backends/imgui/imstb_truetype.h
  R engines/twp/twpimgui.cpp
  R engines/twp/twpimgui.h
  R graphics/imgui/LICENSE.txt
  R graphics/imgui/backends/imgui_impl_opengl3_loader.h
  R graphics/imgui/backends/imgui_impl_opengl3_scummvm.cpp
  R graphics/imgui/backends/imgui_impl_opengl3_scummvm.h
  R graphics/imgui/backends/imgui_impl_sdl2_scummvm.cpp
  R graphics/imgui/backends/imgui_impl_sdl2_scummvm.h
  R graphics/imgui/imconfig.h
  R graphics/imgui/imgui.cpp
  R graphics/imgui/imgui.h
  R graphics/imgui/imgui_demo.cpp
  R graphics/imgui/imgui_draw.cpp
  R graphics/imgui/imgui_internal.h
  R graphics/imgui/imgui_tables.cpp
  R graphics/imgui/imgui_widgets.cpp
  R graphics/imgui/imstb_rectpack.h
  R graphics/imgui/imstb_textedit.h
  R graphics/imgui/imstb_truetype.h
    backends/events/sdl/sdl-events.cpp
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
    backends/graphics3d/openglsdl/openglsdl-graphics3d.h
    backends/module.mk
    engines/metaengine.h
    engines/twp/debugtools.cpp
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/module.mk
    engines/twp/twp.cpp
    engines/twp/twp.h
    graphics/module.mk


diff --git a/backends/events/sdl/sdl-events.cpp b/backends/events/sdl/sdl-events.cpp
index 629cced6ef0..65db35997a5 100644
--- a/backends/events/sdl/sdl-events.cpp
+++ b/backends/events/sdl/sdl-events.cpp
@@ -32,6 +32,10 @@
 #include "engines/engine.h"
 #include "gui/gui-manager.h"
 
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+#include "backends/imgui/backends/imgui_impl_sdl2_scummvm.h"
+#endif
+
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 #define GAMECONTROLLERDB_FILE "gamecontrollerdb.txt"
 
@@ -74,9 +78,10 @@ SdlEventSource::SdlEventSource()
 	: EventSource(), _scrollLock(false), _joystick(nullptr), _lastScreenID(0), _graphicsManager(nullptr), _queuedFakeMouseMove(false),
 	  _lastHatPosition(SDL_HAT_CENTERED), _mouseX(0), _mouseY(0), _engineRunning(false)
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	  , _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr)
+	  ,
+	  _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr)
 #endif
-	  {
+{
 	int joystick_num = ConfMan.getInt("joystick_num");
 	if (joystick_num >= 0) {
 		// Initialize SDL joystick subsystem
@@ -129,7 +134,7 @@ int SdlEventSource::mapKey(SDL_Keycode sdlKey, SDL_Keymod mod, Uint16 unicode) {
 				// We allow Hebrew characters
 				if (unicode >= 0x05D0 && unicode <= 0x05EA)
 					return unicode;
-				
+
 				// Cyrillic
 				if (unicode >= 0x0400 && unicode <= 0x045F)
 					return unicode;
@@ -210,198 +215,383 @@ void SdlEventSource::SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &even
 
 Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
 	switch (key) {
-	case SDLK_BACKSPACE: return Common::KEYCODE_BACKSPACE;
-	case SDLK_TAB: return Common::KEYCODE_TAB;
-	case SDLK_CLEAR: return Common::KEYCODE_CLEAR;
-	case SDLK_RETURN: return Common::KEYCODE_RETURN;
-	case SDLK_PAUSE: return Common::KEYCODE_PAUSE;
-	case SDLK_ESCAPE: return Common::KEYCODE_ESCAPE;
-	case SDLK_SPACE: return Common::KEYCODE_SPACE;
-	case SDLK_EXCLAIM: return Common::KEYCODE_EXCLAIM;
-	case SDLK_QUOTEDBL: return Common::KEYCODE_QUOTEDBL;
-	case SDLK_HASH: return Common::KEYCODE_HASH;
-	case SDLK_DOLLAR: return Common::KEYCODE_DOLLAR;
-	case SDLK_AMPERSAND: return Common::KEYCODE_AMPERSAND;
-	case SDLK_QUOTE: return Common::KEYCODE_QUOTE;
-	case SDLK_LEFTPAREN: return Common::KEYCODE_LEFTPAREN;
-	case SDLK_RIGHTPAREN: return Common::KEYCODE_RIGHTPAREN;
-	case SDLK_ASTERISK: return Common::KEYCODE_ASTERISK;
-	case SDLK_PLUS: return Common::KEYCODE_PLUS;
-	case SDLK_COMMA: return Common::KEYCODE_COMMA;
-	case SDLK_MINUS: return Common::KEYCODE_MINUS;
-	case SDLK_PERIOD: return Common::KEYCODE_PERIOD;
-	case SDLK_SLASH: return Common::KEYCODE_SLASH;
-	case SDLK_0: return Common::KEYCODE_0;
-	case SDLK_1: return Common::KEYCODE_1;
-	case SDLK_2: return Common::KEYCODE_2;
-	case SDLK_3: return Common::KEYCODE_3;
-	case SDLK_4: return Common::KEYCODE_4;
-	case SDLK_5: return Common::KEYCODE_5;
-	case SDLK_6: return Common::KEYCODE_6;
-	case SDLK_7: return Common::KEYCODE_7;
-	case SDLK_8: return Common::KEYCODE_8;
-	case SDLK_9: return Common::KEYCODE_9;
-	case SDLK_COLON: return Common::KEYCODE_COLON;
-	case SDLK_SEMICOLON: return Common::KEYCODE_SEMICOLON;
-	case SDLK_LESS: return Common::KEYCODE_LESS;
-	case SDLK_EQUALS: return Common::KEYCODE_EQUALS;
-	case SDLK_GREATER: return Common::KEYCODE_GREATER;
-	case SDLK_QUESTION: return Common::KEYCODE_QUESTION;
-	case SDLK_AT: return Common::KEYCODE_AT;
-	case SDLK_LEFTBRACKET: return Common::KEYCODE_LEFTBRACKET;
-	case SDLK_BACKSLASH: return Common::KEYCODE_BACKSLASH;
-	case SDLK_RIGHTBRACKET: return Common::KEYCODE_RIGHTBRACKET;
-	case SDLK_CARET: return Common::KEYCODE_CARET;
-	case SDLK_UNDERSCORE: return Common::KEYCODE_UNDERSCORE;
-	case SDLK_BACKQUOTE: return Common::KEYCODE_BACKQUOTE;
-	case SDLK_a: return Common::KEYCODE_a;
-	case SDLK_b: return Common::KEYCODE_b;
-	case SDLK_c: return Common::KEYCODE_c;
-	case SDLK_d: return Common::KEYCODE_d;
-	case SDLK_e: return Common::KEYCODE_e;
-	case SDLK_f: return Common::KEYCODE_f;
-	case SDLK_g: return Common::KEYCODE_g;
-	case SDLK_h: return Common::KEYCODE_h;
-	case SDLK_i: return Common::KEYCODE_i;
-	case SDLK_j: return Common::KEYCODE_j;
-	case SDLK_k: return Common::KEYCODE_k;
-	case SDLK_l: return Common::KEYCODE_l;
-	case SDLK_m: return Common::KEYCODE_m;
-	case SDLK_n: return Common::KEYCODE_n;
-	case SDLK_o: return Common::KEYCODE_o;
-	case SDLK_p: return Common::KEYCODE_p;
-	case SDLK_q: return Common::KEYCODE_q;
-	case SDLK_r: return Common::KEYCODE_r;
-	case SDLK_s: return Common::KEYCODE_s;
-	case SDLK_t: return Common::KEYCODE_t;
-	case SDLK_u: return Common::KEYCODE_u;
-	case SDLK_v: return Common::KEYCODE_v;
-	case SDLK_w: return Common::KEYCODE_w;
-	case SDLK_x: return Common::KEYCODE_x;
-	case SDLK_y: return Common::KEYCODE_y;
-	case SDLK_z: return Common::KEYCODE_z;
-	case SDLK_DELETE: return Common::KEYCODE_DELETE;
-	case SDLK_KP_PERIOD: return Common::KEYCODE_KP_PERIOD;
-	case SDLK_KP_DIVIDE: return Common::KEYCODE_KP_DIVIDE;
-	case SDLK_KP_MULTIPLY: return Common::KEYCODE_KP_MULTIPLY;
-	case SDLK_KP_MINUS: return Common::KEYCODE_KP_MINUS;
-	case SDLK_KP_PLUS: return Common::KEYCODE_KP_PLUS;
-	case SDLK_KP_ENTER: return Common::KEYCODE_KP_ENTER;
-	case SDLK_KP_EQUALS: return Common::KEYCODE_KP_EQUALS;
-	case SDLK_UP: return Common::KEYCODE_UP;
-	case SDLK_DOWN: return Common::KEYCODE_DOWN;
-	case SDLK_RIGHT: return Common::KEYCODE_RIGHT;
-	case SDLK_LEFT: return Common::KEYCODE_LEFT;
-	case SDLK_INSERT: return Common::KEYCODE_INSERT;
-	case SDLK_HOME: return Common::KEYCODE_HOME;
-	case SDLK_END: return Common::KEYCODE_END;
-	case SDLK_PAGEUP: return Common::KEYCODE_PAGEUP;
-	case SDLK_PAGEDOWN: return Common::KEYCODE_PAGEDOWN;
-	case SDLK_F1: return Common::KEYCODE_F1;
-	case SDLK_F2: return Common::KEYCODE_F2;
-	case SDLK_F3: return Common::KEYCODE_F3;
-	case SDLK_F4: return Common::KEYCODE_F4;
-	case SDLK_F5: return Common::KEYCODE_F5;
-	case SDLK_F6: return Common::KEYCODE_F6;
-	case SDLK_F7: return Common::KEYCODE_F7;
-	case SDLK_F8: return Common::KEYCODE_F8;
-	case SDLK_F9: return Common::KEYCODE_F9;
-	case SDLK_F10: return Common::KEYCODE_F10;
-	case SDLK_F11: return Common::KEYCODE_F11;
-	case SDLK_F12: return Common::KEYCODE_F12;
-	case SDLK_F13: return Common::KEYCODE_F13;
-	case SDLK_F14: return Common::KEYCODE_F14;
-	case SDLK_F15: return Common::KEYCODE_F15;
-	case SDLK_CAPSLOCK: return Common::KEYCODE_CAPSLOCK;
-	case SDLK_RSHIFT: return Common::KEYCODE_RSHIFT;
-	case SDLK_LSHIFT: return Common::KEYCODE_LSHIFT;
-	case SDLK_RCTRL: return Common::KEYCODE_RCTRL;
-	case SDLK_LCTRL: return Common::KEYCODE_LCTRL;
-	case SDLK_RALT: return Common::KEYCODE_RALT;
-	case SDLK_LALT: return Common::KEYCODE_LALT;
-	case SDLK_MODE: return Common::KEYCODE_MODE;
-	case SDLK_HELP: return Common::KEYCODE_HELP;
-	case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ;
-	case SDLK_MENU: return Common::KEYCODE_MENU;
-	case SDLK_POWER: return Common::KEYCODE_POWER;
+	case SDLK_BACKSPACE:
+		return Common::KEYCODE_BACKSPACE;
+	case SDLK_TAB:
+		return Common::KEYCODE_TAB;
+	case SDLK_CLEAR:
+		return Common::KEYCODE_CLEAR;
+	case SDLK_RETURN:
+		return Common::KEYCODE_RETURN;
+	case SDLK_PAUSE:
+		return Common::KEYCODE_PAUSE;
+	case SDLK_ESCAPE:
+		return Common::KEYCODE_ESCAPE;
+	case SDLK_SPACE:
+		return Common::KEYCODE_SPACE;
+	case SDLK_EXCLAIM:
+		return Common::KEYCODE_EXCLAIM;
+	case SDLK_QUOTEDBL:
+		return Common::KEYCODE_QUOTEDBL;
+	case SDLK_HASH:
+		return Common::KEYCODE_HASH;
+	case SDLK_DOLLAR:
+		return Common::KEYCODE_DOLLAR;
+	case SDLK_AMPERSAND:
+		return Common::KEYCODE_AMPERSAND;
+	case SDLK_QUOTE:
+		return Common::KEYCODE_QUOTE;
+	case SDLK_LEFTPAREN:
+		return Common::KEYCODE_LEFTPAREN;
+	case SDLK_RIGHTPAREN:
+		return Common::KEYCODE_RIGHTPAREN;
+	case SDLK_ASTERISK:
+		return Common::KEYCODE_ASTERISK;
+	case SDLK_PLUS:
+		return Common::KEYCODE_PLUS;
+	case SDLK_COMMA:
+		return Common::KEYCODE_COMMA;
+	case SDLK_MINUS:
+		return Common::KEYCODE_MINUS;
+	case SDLK_PERIOD:
+		return Common::KEYCODE_PERIOD;
+	case SDLK_SLASH:
+		return Common::KEYCODE_SLASH;
+	case SDLK_0:
+		return Common::KEYCODE_0;
+	case SDLK_1:
+		return Common::KEYCODE_1;
+	case SDLK_2:
+		return Common::KEYCODE_2;
+	case SDLK_3:
+		return Common::KEYCODE_3;
+	case SDLK_4:
+		return Common::KEYCODE_4;
+	case SDLK_5:
+		return Common::KEYCODE_5;
+	case SDLK_6:
+		return Common::KEYCODE_6;
+	case SDLK_7:
+		return Common::KEYCODE_7;
+	case SDLK_8:
+		return Common::KEYCODE_8;
+	case SDLK_9:
+		return Common::KEYCODE_9;
+	case SDLK_COLON:
+		return Common::KEYCODE_COLON;
+	case SDLK_SEMICOLON:
+		return Common::KEYCODE_SEMICOLON;
+	case SDLK_LESS:
+		return Common::KEYCODE_LESS;
+	case SDLK_EQUALS:
+		return Common::KEYCODE_EQUALS;
+	case SDLK_GREATER:
+		return Common::KEYCODE_GREATER;
+	case SDLK_QUESTION:
+		return Common::KEYCODE_QUESTION;
+	case SDLK_AT:
+		return Common::KEYCODE_AT;
+	case SDLK_LEFTBRACKET:
+		return Common::KEYCODE_LEFTBRACKET;
+	case SDLK_BACKSLASH:
+		return Common::KEYCODE_BACKSLASH;
+	case SDLK_RIGHTBRACKET:
+		return Common::KEYCODE_RIGHTBRACKET;
+	case SDLK_CARET:
+		return Common::KEYCODE_CARET;
+	case SDLK_UNDERSCORE:
+		return Common::KEYCODE_UNDERSCORE;
+	case SDLK_BACKQUOTE:
+		return Common::KEYCODE_BACKQUOTE;
+	case SDLK_a:
+		return Common::KEYCODE_a;
+	case SDLK_b:
+		return Common::KEYCODE_b;
+	case SDLK_c:
+		return Common::KEYCODE_c;
+	case SDLK_d:
+		return Common::KEYCODE_d;
+	case SDLK_e:
+		return Common::KEYCODE_e;
+	case SDLK_f:
+		return Common::KEYCODE_f;
+	case SDLK_g:
+		return Common::KEYCODE_g;
+	case SDLK_h:
+		return Common::KEYCODE_h;
+	case SDLK_i:
+		return Common::KEYCODE_i;
+	case SDLK_j:
+		return Common::KEYCODE_j;
+	case SDLK_k:
+		return Common::KEYCODE_k;
+	case SDLK_l:
+		return Common::KEYCODE_l;
+	case SDLK_m:
+		return Common::KEYCODE_m;
+	case SDLK_n:
+		return Common::KEYCODE_n;
+	case SDLK_o:
+		return Common::KEYCODE_o;
+	case SDLK_p:
+		return Common::KEYCODE_p;
+	case SDLK_q:
+		return Common::KEYCODE_q;
+	case SDLK_r:
+		return Common::KEYCODE_r;
+	case SDLK_s:
+		return Common::KEYCODE_s;
+	case SDLK_t:
+		return Common::KEYCODE_t;
+	case SDLK_u:
+		return Common::KEYCODE_u;
+	case SDLK_v:
+		return Common::KEYCODE_v;
+	case SDLK_w:
+		return Common::KEYCODE_w;
+	case SDLK_x:
+		return Common::KEYCODE_x;
+	case SDLK_y:
+		return Common::KEYCODE_y;
+	case SDLK_z:
+		return Common::KEYCODE_z;
+	case SDLK_DELETE:
+		return Common::KEYCODE_DELETE;
+	case SDLK_KP_PERIOD:
+		return Common::KEYCODE_KP_PERIOD;
+	case SDLK_KP_DIVIDE:
+		return Common::KEYCODE_KP_DIVIDE;
+	case SDLK_KP_MULTIPLY:
+		return Common::KEYCODE_KP_MULTIPLY;
+	case SDLK_KP_MINUS:
+		return Common::KEYCODE_KP_MINUS;
+	case SDLK_KP_PLUS:
+		return Common::KEYCODE_KP_PLUS;
+	case SDLK_KP_ENTER:
+		return Common::KEYCODE_KP_ENTER;
+	case SDLK_KP_EQUALS:
+		return Common::KEYCODE_KP_EQUALS;
+	case SDLK_UP:
+		return Common::KEYCODE_UP;
+	case SDLK_DOWN:
+		return Common::KEYCODE_DOWN;
+	case SDLK_RIGHT:
+		return Common::KEYCODE_RIGHT;
+	case SDLK_LEFT:
+		return Common::KEYCODE_LEFT;
+	case SDLK_INSERT:
+		return Common::KEYCODE_INSERT;
+	case SDLK_HOME:
+		return Common::KEYCODE_HOME;
+	case SDLK_END:
+		return Common::KEYCODE_END;
+	case SDLK_PAGEUP:
+		return Common::KEYCODE_PAGEUP;
+	case SDLK_PAGEDOWN:
+		return Common::KEYCODE_PAGEDOWN;
+	case SDLK_F1:
+		return Common::KEYCODE_F1;
+	case SDLK_F2:
+		return Common::KEYCODE_F2;
+	case SDLK_F3:
+		return Common::KEYCODE_F3;
+	case SDLK_F4:
+		return Common::KEYCODE_F4;
+	case SDLK_F5:
+		return Common::KEYCODE_F5;
+	case SDLK_F6:
+		return Common::KEYCODE_F6;
+	case SDLK_F7:
+		return Common::KEYCODE_F7;
+	case SDLK_F8:
+		return Common::KEYCODE_F8;
+	case SDLK_F9:
+		return Common::KEYCODE_F9;
+	case SDLK_F10:
+		return Common::KEYCODE_F10;
+	case SDLK_F11:
+		return Common::KEYCODE_F11;
+	case SDLK_F12:
+		return Common::KEYCODE_F12;
+	case SDLK_F13:
+		return Common::KEYCODE_F13;
+	case SDLK_F14:
+		return Common::KEYCODE_F14;
+	case SDLK_F15:
+		return Common::KEYCODE_F15;
+	case SDLK_CAPSLOCK:
+		return Common::KEYCODE_CAPSLOCK;
+	case SDLK_RSHIFT:
+		return Common::KEYCODE_RSHIFT;
+	case SDLK_LSHIFT:
+		return Common::KEYCODE_LSHIFT;
+	case SDLK_RCTRL:
+		return Common::KEYCODE_RCTRL;
+	case SDLK_LCTRL:
+		return Common::KEYCODE_LCTRL;
+	case SDLK_RALT:
+		return Common::KEYCODE_RALT;
+	case SDLK_LALT:
+		return Common::KEYCODE_LALT;
+	case SDLK_MODE:
+		return Common::KEYCODE_MODE;
+	case SDLK_HELP:
+		return Common::KEYCODE_HELP;
+	case SDLK_SYSREQ:
+		return Common::KEYCODE_SYSREQ;
+	case SDLK_MENU:
+		return Common::KEYCODE_MENU;
+	case SDLK_POWER:
+		return Common::KEYCODE_POWER;
 #if SDL_VERSION_ATLEAST(1, 2, 3)
-	case SDLK_UNDO: return Common::KEYCODE_UNDO;
+	case SDLK_UNDO:
+		return Common::KEYCODE_UNDO;
 #endif
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	case SDLK_SCROLLLOCK: return Common::KEYCODE_SCROLLOCK;
-	case SDLK_NUMLOCKCLEAR: return Common::KEYCODE_NUMLOCK;
-	case SDLK_LGUI: return Common::KEYCODE_LSUPER;
-	case SDLK_RGUI: return Common::KEYCODE_RSUPER;
-	case SDLK_PRINTSCREEN: return Common::KEYCODE_PRINT;
-	case SDLK_APPLICATION: return Common::KEYCODE_COMPOSE;
-	case SDLK_KP_0: return Common::KEYCODE_KP0;
-	case SDLK_KP_1: return Common::KEYCODE_KP1;
-	case SDLK_KP_2: return Common::KEYCODE_KP2;
-	case SDLK_KP_3: return Common::KEYCODE_KP3;
-	case SDLK_KP_4: return Common::KEYCODE_KP4;
-	case SDLK_KP_5: return Common::KEYCODE_KP5;
-	case SDLK_KP_6: return Common::KEYCODE_KP6;
-	case SDLK_KP_7: return Common::KEYCODE_KP7;
-	case SDLK_KP_8: return Common::KEYCODE_KP8;
-	case SDLK_KP_9: return Common::KEYCODE_KP9;
-	case SDLK_PERCENT: return Common::KEYCODE_PERCENT;
-	case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE): return Common::KEYCODE_TILDE;
-	case SDLK_F16: return Common::KEYCODE_F16;
-	case SDLK_F17: return Common::KEYCODE_F17;
-	case SDLK_F18: return Common::KEYCODE_F18;
-	case SDLK_SLEEP: return Common::KEYCODE_SLEEP;
-	case SDLK_MUTE: return Common::KEYCODE_MUTE;
-	case SDLK_VOLUMEUP: return Common::KEYCODE_VOLUMEUP;
-	case SDLK_VOLUMEDOWN: return Common::KEYCODE_VOLUMEDOWN;
-	case SDLK_EJECT: return Common::KEYCODE_EJECT;
-	case SDLK_WWW: return Common::KEYCODE_WWW;
-	case SDLK_MAIL: return Common::KEYCODE_MAIL;
-	case SDLK_CALCULATOR: return Common::KEYCODE_CALCULATOR;
-	case SDLK_CUT: return Common::KEYCODE_CUT;
-	case SDLK_COPY: return Common::KEYCODE_COPY;
-	case SDLK_PASTE: return Common::KEYCODE_PASTE;
-	case SDLK_SELECT: return Common::KEYCODE_SELECT;
-	case SDLK_CANCEL: return Common::KEYCODE_CANCEL;
-	case SDLK_AC_SEARCH: return Common::KEYCODE_AC_SEARCH;
-	case SDLK_AC_HOME: return Common::KEYCODE_AC_HOME;
-	case SDLK_AC_BACK: return Common::KEYCODE_AC_BACK;
-	case SDLK_AC_FORWARD: return Common::KEYCODE_AC_FORWARD;
-	case SDLK_AC_STOP: return Common::KEYCODE_AC_STOP;
-	case SDLK_AC_REFRESH: return Common::KEYCODE_AC_REFRESH;
-	case SDLK_AC_BOOKMARKS: return Common::KEYCODE_AC_BOOKMARKS;
-	case SDLK_AUDIONEXT: return Common::KEYCODE_AUDIONEXT;
-	case SDLK_AUDIOPREV: return Common::KEYCODE_AUDIOPREV;
-	case SDLK_AUDIOSTOP: return Common::KEYCODE_AUDIOSTOP;
-	case SDLK_AUDIOPLAY: return Common::KEYCODE_AUDIOPLAYPAUSE;
-	case SDLK_AUDIOMUTE: return Common::KEYCODE_AUDIOMUTE;
+	case SDLK_SCROLLLOCK:
+		return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCKCLEAR:
+		return Common::KEYCODE_NUMLOCK;
+	case SDLK_LGUI:
+		return Common::KEYCODE_LSUPER;
+	case SDLK_RGUI:
+		return Common::KEYCODE_RSUPER;
+	case SDLK_PRINTSCREEN:
+		return Common::KEYCODE_PRINT;
+	case SDLK_APPLICATION:
+		return Common::KEYCODE_COMPOSE;
+	case SDLK_KP_0:
+		return Common::KEYCODE_KP0;
+	case SDLK_KP_1:
+		return Common::KEYCODE_KP1;
+	case SDLK_KP_2:
+		return Common::KEYCODE_KP2;
+	case SDLK_KP_3:
+		return Common::KEYCODE_KP3;
+	case SDLK_KP_4:
+		return Common::KEYCODE_KP4;
+	case SDLK_KP_5:
+		return Common::KEYCODE_KP5;
+	case SDLK_KP_6:
+		return Common::KEYCODE_KP6;
+	case SDLK_KP_7:
+		return Common::KEYCODE_KP7;
+	case SDLK_KP_8:
+		return Common::KEYCODE_KP8;
+	case SDLK_KP_9:
+		return Common::KEYCODE_KP9;
+	case SDLK_PERCENT:
+		return Common::KEYCODE_PERCENT;
+	case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE):
+		return Common::KEYCODE_TILDE;
+	case SDLK_F16:
+		return Common::KEYCODE_F16;
+	case SDLK_F17:
+		return Common::KEYCODE_F17;
+	case SDLK_F18:
+		return Common::KEYCODE_F18;
+	case SDLK_SLEEP:
+		return Common::KEYCODE_SLEEP;
+	case SDLK_MUTE:
+		return Common::KEYCODE_MUTE;
+	case SDLK_VOLUMEUP:
+		return Common::KEYCODE_VOLUMEUP;
+	case SDLK_VOLUMEDOWN:
+		return Common::KEYCODE_VOLUMEDOWN;
+	case SDLK_EJECT:
+		return Common::KEYCODE_EJECT;
+	case SDLK_WWW:
+		return Common::KEYCODE_WWW;
+	case SDLK_MAIL:
+		return Common::KEYCODE_MAIL;
+	case SDLK_CALCULATOR:
+		return Common::KEYCODE_CALCULATOR;
+	case SDLK_CUT:
+		return Common::KEYCODE_CUT;
+	case SDLK_COPY:
+		return Common::KEYCODE_COPY;
+	case SDLK_PASTE:
+		return Common::KEYCODE_PASTE;
+	case SDLK_SELECT:
+		return Common::KEYCODE_SELECT;
+	case SDLK_CANCEL:
+		return Common::KEYCODE_CANCEL;
+	case SDLK_AC_SEARCH:
+		return Common::KEYCODE_AC_SEARCH;
+	case SDLK_AC_HOME:
+		return Common::KEYCODE_AC_HOME;
+	case SDLK_AC_BACK:
+		return Common::KEYCODE_AC_BACK;
+	case SDLK_AC_FORWARD:
+		return Common::KEYCODE_AC_FORWARD;
+	case SDLK_AC_STOP:
+		return Common::KEYCODE_AC_STOP;
+	case SDLK_AC_REFRESH:
+		return Common::KEYCODE_AC_REFRESH;
+	case SDLK_AC_BOOKMARKS:
+		return Common::KEYCODE_AC_BOOKMARKS;
+	case SDLK_AUDIONEXT:
+		return Common::KEYCODE_AUDIONEXT;
+	case SDLK_AUDIOPREV:
+		return Common::KEYCODE_AUDIOPREV;
+	case SDLK_AUDIOSTOP:
+		return Common::KEYCODE_AUDIOSTOP;
+	case SDLK_AUDIOPLAY:
+		return Common::KEYCODE_AUDIOPLAYPAUSE;
+	case SDLK_AUDIOMUTE:
+		return Common::KEYCODE_AUDIOMUTE;
 #if SDL_VERSION_ATLEAST(2, 0, 6)
-	case SDLK_AUDIOREWIND: return Common::KEYCODE_AUDIOREWIND;
-	case SDLK_AUDIOFASTFORWARD: return Common::KEYCODE_AUDIOFASTFORWARD;
+	case SDLK_AUDIOREWIND:
+		return Common::KEYCODE_AUDIOREWIND;
+	case SDLK_AUDIOFASTFORWARD:
+		return Common::KEYCODE_AUDIOFASTFORWARD;
 #endif
 #else
-	case SDLK_SCROLLOCK: return Common::KEYCODE_SCROLLOCK;
-	case SDLK_NUMLOCK: return Common::KEYCODE_NUMLOCK;
-	case SDLK_LSUPER: return Common::KEYCODE_LSUPER;
-	case SDLK_RSUPER: return Common::KEYCODE_RSUPER;
-	case SDLK_PRINT: return Common::KEYCODE_PRINT;
-	case SDLK_COMPOSE: return Common::KEYCODE_COMPOSE;
-	case SDLK_KP0: return Common::KEYCODE_KP0;
-	case SDLK_KP1: return Common::KEYCODE_KP1;
-	case SDLK_KP2: return Common::KEYCODE_KP2;
-	case SDLK_KP3: return Common::KEYCODE_KP3;
-	case SDLK_KP4: return Common::KEYCODE_KP4;
-	case SDLK_KP5: return Common::KEYCODE_KP5;
-	case SDLK_KP6: return Common::KEYCODE_KP6;
-	case SDLK_KP7: return Common::KEYCODE_KP7;
-	case SDLK_KP8: return Common::KEYCODE_KP8;
-	case SDLK_KP9: return Common::KEYCODE_KP9;
-	case SDLK_WORLD_16: return Common::KEYCODE_TILDE;
-	case SDLK_BREAK: return Common::KEYCODE_BREAK;
-	case SDLK_LMETA: return Common::KEYCODE_LMETA;
-	case SDLK_RMETA: return Common::KEYCODE_RMETA;
-	case SDLK_EURO: return Common::KEYCODE_EURO;
+	case SDLK_SCROLLOCK:
+		return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCK:
+		return Common::KEYCODE_NUMLOCK;
+	case SDLK_LSUPER:
+		return Common::KEYCODE_LSUPER;
+	case SDLK_RSUPER:
+		return Common::KEYCODE_RSUPER;
+	case SDLK_PRINT:
+		return Common::KEYCODE_PRINT;
+	case SDLK_COMPOSE:
+		return Common::KEYCODE_COMPOSE;
+	case SDLK_KP0:
+		return Common::KEYCODE_KP0;
+	case SDLK_KP1:
+		return Common::KEYCODE_KP1;
+	case SDLK_KP2:
+		return Common::KEYCODE_KP2;
+	case SDLK_KP3:
+		return Common::KEYCODE_KP3;
+	case SDLK_KP4:
+		return Common::KEYCODE_KP4;
+	case SDLK_KP5:
+		return Common::KEYCODE_KP5;
+	case SDLK_KP6:
+		return Common::KEYCODE_KP6;
+	case SDLK_KP7:
+		return Common::KEYCODE_KP7;
+	case SDLK_KP8:
+		return Common::KEYCODE_KP8;
+	case SDLK_KP9:
+		return Common::KEYCODE_KP9;
+	case SDLK_WORLD_16:
+		return Common::KEYCODE_TILDE;
+	case SDLK_BREAK:
+		return Common::KEYCODE_BREAK;
+	case SDLK_LMETA:
+		return Common::KEYCODE_LMETA;
+	case SDLK_RMETA:
+		return Common::KEYCODE_RMETA;
+	case SDLK_EURO:
+		return Common::KEYCODE_EURO;
 #endif
-	default: return Common::KEYCODE_INVALID;
+	default:
+		return Common::KEYCODE_INVALID;
 	}
 }
 
@@ -434,6 +624,14 @@ bool SdlEventSource::pollEvent(Common::Event &event) {
 	SDL_Event ev;
 	while (SDL_PollEvent(&ev)) {
 		preprocessEvents(&ev);
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+		if (ImGui_ImplSDL2_Ready()) {
+			ImGui_ImplSDL2_ProcessEvent(&ev);
+			ImGuiIO &io = ImGui::GetIO();
+			if (io.WantTextInput || io.WantCaptureMouse)
+				continue;
+		}
+#endif
 		if (dispatchSDLEvent(ev, event))
 			return true;
 	}
@@ -474,7 +672,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		} else {
 			return false;
 		}
-		}
+	}
 
 	case SDL_TEXTINPUT: {
 		// When we get a TEXTINPUT event it means we got some user input for
@@ -495,7 +693,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		_fakeKeyUp.type = Common::EVENT_KEYUP;
 
 		return _queuedFakeKeyUp;
-		}
+	}
 
 	case SDL_WINDOWEVENT:
 		// We're only interested in events from the current display window
@@ -524,7 +722,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		// However if the documentation is correct we can ignore SDL_WINDOWEVENT_RESIZED since when we
 		// get one we should always get a SDL_WINDOWEVENT_SIZE_CHANGED as well.
 		case SDL_WINDOWEVENT_SIZE_CHANGED:
-		//case SDL_WINDOWEVENT_RESIZED:
+			// case SDL_WINDOWEVENT_RESIZED:
 			return handleResizeEvent(event, ev.window.data1, ev.window.data2);
 
 		case SDL_WINDOWEVENT_FOCUS_GAINED: {
@@ -617,7 +815,6 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 	return false;
 }
 
-
 bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
 
 	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
@@ -785,17 +982,16 @@ void SdlEventSource::closeJoystick() {
 
 int SdlEventSource::mapSDLJoystickButtonToOSystem(Uint8 sdlButton) {
 	Common::JoystickButton osystemButtons[] = {
-	    Common::JOYSTICK_BUTTON_A,
-	    Common::JOYSTICK_BUTTON_B,
-	    Common::JOYSTICK_BUTTON_X,
-	    Common::JOYSTICK_BUTTON_Y,
-	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_BACK,
-	    Common::JOYSTICK_BUTTON_START,
-	    Common::JOYSTICK_BUTTON_LEFT_STICK,
-	    Common::JOYSTICK_BUTTON_RIGHT_STICK
-	};
+		Common::JOYSTICK_BUTTON_A,
+		Common::JOYSTICK_BUTTON_B,
+		Common::JOYSTICK_BUTTON_X,
+		Common::JOYSTICK_BUTTON_Y,
+		Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+		Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+		Common::JOYSTICK_BUTTON_BACK,
+		Common::JOYSTICK_BUTTON_START,
+		Common::JOYSTICK_BUTTON_LEFT_STICK,
+		Common::JOYSTICK_BUTTON_RIGHT_STICK};
 
 	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
 		return -1;
@@ -836,15 +1032,15 @@ bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) {
 	return true;
 }
 
-#define HANDLE_HAT_UP(new, old, mask, joybutton) \
-	if ((old & mask) && !(new & mask)) { \
-		event.joystick.button = joybutton; \
+#define HANDLE_HAT_UP(new, old, mask, joybutton)       \
+	if ((old & mask) && !(new &mask)) {                \
+		event.joystick.button = joybutton;             \
 		g_system->getEventManager()->pushEvent(event); \
 	}
 
-#define HANDLE_HAT_DOWN(new, old, mask, joybutton) \
-	if ((new & mask) && !(old & mask)) { \
-		event.joystick.button = joybutton; \
+#define HANDLE_HAT_DOWN(new, old, mask, joybutton)     \
+	if ((new &mask) && !(old & mask)) {                \
+		event.joystick.button = joybutton;             \
 		g_system->getEventManager()->pushEvent(event); \
 	}
 
@@ -912,22 +1108,21 @@ bool SdlEventSource::handleJoystickRemoved(const SDL_JoyDeviceEvent &device, Com
 
 int SdlEventSource::mapSDLControllerButtonToOSystem(Uint8 sdlButton) {
 	Common::JoystickButton osystemButtons[] = {
-	    Common::JOYSTICK_BUTTON_A,
-	    Common::JOYSTICK_BUTTON_B,
-	    Common::JOYSTICK_BUTTON_X,
-	    Common::JOYSTICK_BUTTON_Y,
-	    Common::JOYSTICK_BUTTON_BACK,
-	    Common::JOYSTICK_BUTTON_GUIDE,
-	    Common::JOYSTICK_BUTTON_START,
-	    Common::JOYSTICK_BUTTON_LEFT_STICK,
-	    Common::JOYSTICK_BUTTON_RIGHT_STICK,
-	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
-	    Common::JOYSTICK_BUTTON_DPAD_UP,
-	    Common::JOYSTICK_BUTTON_DPAD_DOWN,
-	    Common::JOYSTICK_BUTTON_DPAD_LEFT,
-	    Common::JOYSTICK_BUTTON_DPAD_RIGHT
-	};
+		Common::JOYSTICK_BUTTON_A,
+		Common::JOYSTICK_BUTTON_B,
+		Common::JOYSTICK_BUTTON_X,
+		Common::JOYSTICK_BUTTON_Y,
+		Common::JOYSTICK_BUTTON_BACK,
+		Common::JOYSTICK_BUTTON_GUIDE,
+		Common::JOYSTICK_BUTTON_START,
+		Common::JOYSTICK_BUTTON_LEFT_STICK,
+		Common::JOYSTICK_BUTTON_RIGHT_STICK,
+		Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+		Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+		Common::JOYSTICK_BUTTON_DPAD_UP,
+		Common::JOYSTICK_BUTTON_DPAD_DOWN,
+		Common::JOYSTICK_BUTTON_DPAD_LEFT,
+		Common::JOYSTICK_BUTTON_DPAD_RIGHT};
 
 	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
 		return -1;
@@ -970,9 +1165,9 @@ void SdlEventSource::fakeWarpMouse(const int x, const int y) {
 bool SdlEventSource::isJoystickConnected() const {
 	return _joystick
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	        || _controller
+		   || _controller
 #endif
-	        ;
+		;
 }
 
 void SdlEventSource::setEngineRunning(const bool value) {
@@ -1059,8 +1254,7 @@ uint32 SdlEventSource::obtainUnicode(const SDL_Keysym keySym) {
 	int n = SDL_PeepEvents(events, 2, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_TEXTINPUT);
 	// Make sure that the TEXTINPUT event belongs to this KEYDOWN
 	// event and not another pending one.
-	if ((n > 0 && events[0].type == SDL_TEXTINPUT)
-	    || (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) {
+	if ((n > 0 && events[0].type == SDL_TEXTINPUT) || (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) {
 		// Remove the text input event we associate with the key press. This
 		// makes sure we never get any SDL_TEXTINPUT events which do "belong"
 		// to SDL_KEYDOWN events.
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index bafc0541519..e7220d551d6 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -46,6 +46,11 @@
 #include "image/bmp.h"
 #endif
 
+#ifdef USE_IMGUI
+#include "backends/imgui/backends/imgui_impl_sdl2_scummvm.h"
+#include "backends/imgui/backends/imgui_impl_opengl3_scummvm.h"
+#endif
+
 OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSource, SdlWindow *window, bool supportsFrameBuffer)
 	: SdlGraphicsManager(eventSource, window),
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -145,6 +150,12 @@ OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSour
 }
 
 OpenGLSdlGraphics3dManager::~OpenGLSdlGraphics3dManager() {
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+	ImGui_ImplOpenGL3_Shutdown();
+	ImGui_ImplSDL2_Shutdown();
+	ImGui::DestroyContext();
+#endif
+
 	closeOverlay();
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 	deinitializeRenderer();
@@ -307,7 +318,7 @@ void OpenGLSdlGraphics3dManager::setupScreen() {
 		int currentSamples = 0;
 
 		#if defined(__EMSCRIPTEN__)
-		// SDL_GL_MULTISAMPLESAMPLES isn't available on a  WebGL 1.0 context 
+		// SDL_GL_MULTISAMPLESAMPLES isn't available on a  WebGL 1.0 context
 		// (or not bridged in Emscripten?). This forces a windows reset.
 		currentSamples = -1;
 		#else
@@ -462,6 +473,21 @@ void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const {
 	if (SDL_GL_SetSwapInterval(_vsync ? 1 : 0)) {
 		warning("Unable to %s VSync: %s", _vsync ? "enable" : "disable", SDL_GetError());
 	}
+
+#ifdef USE_IMGUI
+	if(!_imguiInit) {
+		// Setup Dear ImGui
+		IMGUI_CHECKVERSION();
+		ImGui::CreateContext();
+		ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), _glContext);
+		ImGui_ImplOpenGL3_Init("#version 110");
+		ImGui::StyleColorsDark();
+		ImGuiIO &io = ImGui::GetIO();
+		Common::Path initPath(ConfMan.getPath("savepath"));
+		initPath = initPath.appendComponent("imgui.ini");
+		io.IniFilename = initPath.toString().c_str();
+	}
+#endif
 #endif
 }
 
@@ -591,6 +617,12 @@ bool OpenGLSdlGraphics3dManager::createOrUpdateGLContext(uint gameWidth, uint ga
 		return false;
 
 	initializeOpenGLContext();
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+	_imguiInit = true;
+	const Plugin *plugin = EngineMan.findPlugin(ConfMan.get("engineid"));
+	plugin = PluginMan.getEngineFromMetaEngine(plugin);
+	_metaEngine = &plugin->get<MetaEngine>();
+#endif
 
 	if (clear)
 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@@ -632,6 +664,17 @@ OpenGL::FrameBuffer *OpenGLSdlGraphics3dManager::createFramebuffer(uint width, u
 }
 
 void OpenGLSdlGraphics3dManager::updateScreen() {
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+	ImGui_ImplOpenGL3_NewFrame();
+	ImGui_ImplSDL2_NewFrame(_window->getSDLWindow());
+
+	ImGui::NewFrame();
+	_metaEngine->renderImGui();
+	ImGui::Render();
+
+	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+#endif
+
 	GLint prevStateViewport[4];
 	glGetIntegerv(GL_VIEWPORT, prevStateViewport);
 	if (_frameBuffer) {
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
index e3861177bf9..20ef5143e6a 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
@@ -120,6 +120,8 @@ protected:
 	int _glContextProfileMask, _glContextMajor, _glContextMinor;
 	SDL_GLContext _glContext;
 	void deinitializeRenderer();
+	MetaEngine* _metaEngine = nullptr;
+	bool _imguiInit = false;
 #endif
 
 	OpenGL::ContextType _glContextType;
diff --git a/graphics/imgui/LICENSE.txt b/backends/imgui/LICENSE.txt
similarity index 100%
rename from graphics/imgui/LICENSE.txt
rename to backends/imgui/LICENSE.txt
diff --git a/graphics/imgui/backends/imgui_impl_opengl3_loader.h b/backends/imgui/backends/imgui_impl_opengl3_loader.h
similarity index 100%
rename from graphics/imgui/backends/imgui_impl_opengl3_loader.h
rename to backends/imgui/backends/imgui_impl_opengl3_loader.h
diff --git a/graphics/imgui/backends/imgui_impl_opengl3_scummvm.cpp b/backends/imgui/backends/imgui_impl_opengl3_scummvm.cpp
similarity index 99%
rename from graphics/imgui/backends/imgui_impl_opengl3_scummvm.cpp
rename to backends/imgui/backends/imgui_impl_opengl3_scummvm.cpp
index c0e07133ec2..b46af5e9043 100644
--- a/graphics/imgui/backends/imgui_impl_opengl3_scummvm.cpp
+++ b/backends/imgui/backends/imgui_impl_opengl3_scummvm.cpp
@@ -109,7 +109,7 @@
 #define _CRT_SECURE_NO_WARNINGS
 #endif
 
-#include "graphics/imgui/imgui.h"
+#include "backends/imgui/imgui.h"
 #ifndef IMGUI_DISABLE
 #include "imgui_impl_opengl3_scummvm.h"
 #include <stdio.h>
@@ -162,7 +162,7 @@
 // - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases
 // Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version.
 #define IMGL3W_IMPL
-#include "graphics/imgui/backends/imgui_impl_opengl3_loader.h"
+#include "backends/imgui/backends/imgui_impl_opengl3_loader.h"
 #endif
 
 // Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension
diff --git a/graphics/imgui/backends/imgui_impl_opengl3_scummvm.h b/backends/imgui/backends/imgui_impl_opengl3_scummvm.h
similarity index 98%
rename from graphics/imgui/backends/imgui_impl_opengl3_scummvm.h
rename to backends/imgui/backends/imgui_impl_opengl3_scummvm.h
index 36864bae45e..e47b370116e 100644
--- a/graphics/imgui/backends/imgui_impl_opengl3_scummvm.h
+++ b/backends/imgui/backends/imgui_impl_opengl3_scummvm.h
@@ -26,7 +26,7 @@
 //  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
 
 #pragma once
-#include "graphics/imgui/imgui.h" // IMGUI_IMPL_API
+#include "backends/imgui/imgui.h" // IMGUI_IMPL_API
 #ifndef IMGUI_DISABLE
 
 // Backend API
diff --git a/backends/imgui/backends/imgui_impl_sdl2_scummvm.cpp b/backends/imgui/backends/imgui_impl_sdl2_scummvm.cpp
new file mode 100644
index 00000000000..7c25d6b9b92
--- /dev/null
+++ b/backends/imgui/backends/imgui_impl_sdl2_scummvm.cpp
@@ -0,0 +1,681 @@
+// dear imgui: Platform Backend for SDL2
+// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
+// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
+// (Prefer SDL 2.0.5+ for full feature support.)
+
+// Implemented features:
+//  [X] Platform: Clipboard support.
+//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
+//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
+//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+//  [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
+
+// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
+// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
+//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
+//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
+//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
+//  2023-02-07: Implement IME handler (io.SetPlatformImeDataFn will call SDL_SetTextInputRect()/SDL_StartTextInput()).
+//  2023-02-07: *BREAKING CHANGE* Renamed this backend file from imgui_impl_sdl.cpp/.h to imgui_impl_sdl2.cpp/.h in prevision for the future release of SDL3.
+//  2023-02-02: Avoid calling SDL_SetCursor() when cursor has not changed, as the function is surprisingly costly on Mac with latest SDL (may be fixed in next SDL version).
+//  2023-02-02: Added support for SDL 2.0.18+ preciseX/preciseY mouse wheel data for smooth scrolling + Scaling X value on Emscripten (bug?). (#4019, #6096)
+//  2023-02-02: Removed SDL_MOUSEWHEEL value clamping, as values seem correct in latest Emscripten. (#4019)
+//  2023-02-01: Flipping SDL_MOUSEWHEEL 'wheel.x' value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463)
+//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
+//  2022-09-26: Inputs: Disable SDL 2.0.22 new "auto capture" (SDL_HINT_MOUSE_AUTO_CAPTURE) which prevents drag and drop across windows for multi-viewport support + don't capture when drag and dropping. (#5710)
+//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
+//  2022-03-22: Inputs: Fix mouse position issues when dragging outside of boundaries. SDL_CaptureMouse() erroneously still gives out LEAVE events when hovering OS decorations.
+//  2022-03-22: Inputs: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2).
+//  2022-02-04: Added SDL_Renderer* parameter to ImGui_ImplSDL2_InitForSDLRenderer(), so we can use SDL_GetRendererOutputSize() instead of SDL_GL_GetDrawableSize() when bound to a SDL_Renderer.
+//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
+//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
+//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
+//  2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
+//  2022-01-12: Update mouse inputs using SDL_MOUSEMOTION/SDL_WINDOWEVENT_LEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API.
+//  2022-01-12: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted.
+//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
+//  2021-08-17: Calling io.AddFocusEvent() on SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST.
+//  2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+)
+//  2021-06-29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary.
+//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
+//  2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950)
+//  2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
+//  2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).
+//  2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).
+//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
+//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
+//  2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
+//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
+//  2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.
+//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
+//  2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.
+//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
+//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
+//  2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.
+//  2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.
+//  2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).
+//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
+//  2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.
+//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
+//  2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).
+//  2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.
+//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
+//  2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS.
+//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
+//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
+//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
+
+#ifndef IMGUI_DISABLE
+#include "backends/platform/sdl/sdl.h"
+#include "imgui_impl_sdl2_scummvm.h"
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+
+// Clang warnings with -Weverything
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
+#endif
+
+// SDL
+#include <SDL.h>
+#include <SDL_syswm.h>
+#if defined(__APPLE__)
+#include <TargetConditionals.h>
+#endif
+
+#if SDL_VERSION_ATLEAST(2,0,4) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
+#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    1
+#else
+#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    0
+#endif
+#define SDL_HAS_VULKAN                      SDL_VERSION_ATLEAST(2,0,6)
+
+// SDL Data
+struct ImGui_ImplSDL2_Data
+{
+    SDL_Window*     Window;
+    SDL_Renderer*   Renderer;
+    Uint64          Time;
+    Uint32          MouseWindowID;
+    int             MouseButtonsDown;
+    SDL_Cursor*     MouseCursors[ImGuiMouseCursor_COUNT];
+    SDL_Cursor*     LastMouseCursor;
+    int             PendingMouseLeaveFrame;
+    char*           ClipboardTextData;
+    bool            MouseCanUseGlobalState;
+
+    ImGui_ImplSDL2_Data()   { memset((void*)this, 0, sizeof(*this)); }
+};
+
+// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
+// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
+// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
+// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
+static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData()
+{
+    return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
+}
+
+// Functions
+static const char* ImGui_ImplSDL2_GetClipboardText(void*)
+{
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+    if (bd->ClipboardTextData)
+        SDL_free(bd->ClipboardTextData);
+    bd->ClipboardTextData = SDL_GetClipboardText();
+    return bd->ClipboardTextData;
+}
+
+static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)
+{
+    SDL_SetClipboardText(text);
+}
+
+// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow().
+static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data)
+{
+    if (data->WantVisible)
+    {
+        SDL_Rect r;
+        r.x = (int)data->InputPos.x;
+        r.y = (int)data->InputPos.y;
+        r.w = 1;
+        r.h = (int)data->InputLineHeight;
+        SDL_SetTextInputRect(&r);
+    }
+}
+
+static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode)
+{
+    switch (keycode)
+    {
+        case SDLK_TAB: return ImGuiKey_Tab;
+        case SDLK_LEFT: return ImGuiKey_LeftArrow;
+        case SDLK_RIGHT: return ImGuiKey_RightArrow;
+        case SDLK_UP: return ImGuiKey_UpArrow;
+        case SDLK_DOWN: return ImGuiKey_DownArrow;
+        case SDLK_PAGEUP: return ImGuiKey_PageUp;
+        case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
+        case SDLK_HOME: return ImGuiKey_Home;
+        case SDLK_END: return ImGuiKey_End;
+        case SDLK_INSERT: return ImGuiKey_Insert;
+        case SDLK_DELETE: return ImGuiKey_Delete;
+        case SDLK_BACKSPACE: return ImGuiKey_Backspace;
+        case SDLK_SPACE: return ImGuiKey_Space;
+        case SDLK_RETURN: return ImGuiKey_Enter;
+        case SDLK_ESCAPE: return ImGuiKey_Escape;
+        case SDLK_QUOTE: return ImGuiKey_Apostrophe;
+        case SDLK_COMMA: return ImGuiKey_Comma;
+        case SDLK_MINUS: return ImGuiKey_Minus;
+        case SDLK_PERIOD: return ImGuiKey_Period;
+        case SDLK_SLASH: return ImGuiKey_Slash;
+        case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
+        case SDLK_EQUALS: return ImGuiKey_Equal;
+        case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
+        case SDLK_BACKSLASH: return ImGuiKey_Backslash;
+        case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
+        case SDLK_BACKQUOTE: return ImGuiKey_GraveAccent;
+        case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
+        case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
+        case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
+        case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
+        case SDLK_PAUSE: return ImGuiKey_Pause;
+        case SDLK_KP_0: return ImGuiKey_Keypad0;
+        case SDLK_KP_1: return ImGuiKey_Keypad1;
+        case SDLK_KP_2: return ImGuiKey_Keypad2;
+        case SDLK_KP_3: return ImGuiKey_Keypad3;
+        case SDLK_KP_4: return ImGuiKey_Keypad4;
+        case SDLK_KP_5: return ImGuiKey_Keypad5;
+        case SDLK_KP_6: return ImGuiKey_Keypad6;
+        case SDLK_KP_7: return ImGuiKey_Keypad7;
+        case SDLK_KP_8: return ImGuiKey_Keypad8;
+        case SDLK_KP_9: return ImGuiKey_Keypad9;
+        case SDLK_KP_PERIOD: return ImGuiKey_KeypadDecimal;
+        case SDLK_KP_DIVIDE: return ImGuiKey_KeypadDivide;
+        case SDLK_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
+        case SDLK_KP_MINUS: return ImGuiKey_KeypadSubtract;
+        case SDLK_KP_PLUS: return ImGuiKey_KeypadAdd;
+        case SDLK_KP_ENTER: return ImGuiKey_KeypadEnter;
+        case SDLK_KP_EQUALS: return ImGuiKey_KeypadEqual;
+        case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
+        case SDLK_LSHIFT: return ImGuiKey_LeftShift;
+        case SDLK_LALT: return ImGuiKey_LeftAlt;
+        case SDLK_LGUI: return ImGuiKey_LeftSuper;
+        case SDLK_RCTRL: return ImGuiKey_RightCtrl;
+        case SDLK_RSHIFT: return ImGuiKey_RightShift;
+        case SDLK_RALT: return ImGuiKey_RightAlt;
+        case SDLK_RGUI: return ImGuiKey_RightSuper;
+        case SDLK_APPLICATION: return ImGuiKey_Menu;
+        case SDLK_0: return ImGuiKey_0;
+        case SDLK_1: return ImGuiKey_1;
+        case SDLK_2: return ImGuiKey_2;
+        case SDLK_3: return ImGuiKey_3;
+        case SDLK_4: return ImGuiKey_4;
+        case SDLK_5: return ImGuiKey_5;
+        case SDLK_6: return ImGuiKey_6;
+        case SDLK_7: return ImGuiKey_7;
+        case SDLK_8: return ImGuiKey_8;
+        case SDLK_9: return ImGuiKey_9;
+        case SDLK_a: return ImGuiKey_A;
+        case SDLK_b: return ImGuiKey_B;
+        case SDLK_c: return ImGuiKey_C;
+        case SDLK_d: return ImGuiKey_D;
+        case SDLK_e: return ImGuiKey_E;
+        case SDLK_f: return ImGuiKey_F;
+        case SDLK_g: return ImGuiKey_G;
+        case SDLK_h: return ImGuiKey_H;
+        case SDLK_i: return ImGuiKey_I;
+        case SDLK_j: return ImGuiKey_J;
+        case SDLK_k: return ImGuiKey_K;
+        case SDLK_l: return ImGuiKey_L;
+        case SDLK_m: return ImGuiKey_M;
+        case SDLK_n: return ImGuiKey_N;
+        case SDLK_o: return ImGuiKey_O;
+        case SDLK_p: return ImGuiKey_P;
+        case SDLK_q: return ImGuiKey_Q;
+        case SDLK_r: return ImGuiKey_R;
+        case SDLK_s: return ImGuiKey_S;
+        case SDLK_t: return ImGuiKey_T;
+        case SDLK_u: return ImGuiKey_U;
+        case SDLK_v: return ImGuiKey_V;
+        case SDLK_w: return ImGuiKey_W;
+        case SDLK_x: return ImGuiKey_X;
+        case SDLK_y: return ImGuiKey_Y;
+        case SDLK_z: return ImGuiKey_Z;
+        case SDLK_F1: return ImGuiKey_F1;
+        case SDLK_F2: return ImGuiKey_F2;
+        case SDLK_F3: return ImGuiKey_F3;
+        case SDLK_F4: return ImGuiKey_F4;
+        case SDLK_F5: return ImGuiKey_F5;
+        case SDLK_F6: return ImGuiKey_F6;
+        case SDLK_F7: return ImGuiKey_F7;
+        case SDLK_F8: return ImGuiKey_F8;
+        case SDLK_F9: return ImGuiKey_F9;
+        case SDLK_F10: return ImGuiKey_F10;
+        case SDLK_F11: return ImGuiKey_F11;
+        case SDLK_F12: return ImGuiKey_F12;
+        case SDLK_F13: return ImGuiKey_F13;
+        case SDLK_F14: return ImGuiKey_F14;
+        case SDLK_F15: return ImGuiKey_F15;
+        case SDLK_F16: return ImGuiKey_F16;
+        case SDLK_F17: return ImGuiKey_F17;
+        case SDLK_F18: return ImGuiKey_F18;
+        case SDLK_F19: return ImGuiKey_F19;
+        case SDLK_F20: return ImGuiKey_F20;
+        case SDLK_F21: return ImGuiKey_F21;
+        case SDLK_F22: return ImGuiKey_F22;
+        case SDLK_F23: return ImGuiKey_F23;
+        case SDLK_F24: return ImGuiKey_F24;
+        case SDLK_AC_BACK: return ImGuiKey_AppBack;
+        case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
+    }
+    return ImGuiKey_None;
+}
+
+static void ImGui_ImplSDL2_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & KMOD_CTRL) != 0);
+    io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & KMOD_SHIFT) != 0);
+    io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & KMOD_ALT) != 0);
+    io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & KMOD_GUI) != 0);
+}
+
+bool ImGui_ImplSDL2_Ready() 
+{
+	return ImGui_ImplSDL2_GetBackendData() != nullptr;
+}
+
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
+bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+
+    switch (event->type)
+    {
+        case SDL_MOUSEMOTION:
+        {
+            ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
+            io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
+            return true;
+        }
+        case SDL_MOUSEWHEEL:
+        {
+            //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
+#if SDL_VERSION_ATLEAST(2,0,18) // If this fails to compile on Emscripten: update to latest Emscripten!
+            float wheel_x = -event->wheel.preciseX;
+            float wheel_y = event->wheel.preciseY;
+#else
+            float wheel_x = -(float)event->wheel.x;
+            float wheel_y = (float)event->wheel.y;
+#endif
+#ifdef __EMSCRIPTEN__
+            wheel_x /= 100.0f;
+#endif
+            io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMouseWheelEvent(wheel_x, wheel_y);
+            return true;
+        }
+        case SDL_MOUSEBUTTONDOWN:
+        case SDL_MOUSEBUTTONUP:
+        {
+            int mouse_button = -1;
+            if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
+            if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
+            if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
+            if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
+            if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
+            if (mouse_button == -1)
+                break;
+            io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
+            io.AddMouseButtonEvent(mouse_button, (event->type == SDL_MOUSEBUTTONDOWN));
+            bd->MouseButtonsDown = (event->type == SDL_MOUSEBUTTONDOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
+            return true;
+        }
+        case SDL_TEXTINPUT:
+        {
+            io.AddInputCharactersUTF8(event->text.text);
+            return true;
+        }
+        case SDL_KEYDOWN:
+        case SDL_KEYUP:
+        {
+            ImGui_ImplSDL2_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod);
+            ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->key.keysym.sym);
+            io.AddKeyEvent(key, (event->type == SDL_KEYDOWN));
+            io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
+            return true;
+        }
+        case SDL_WINDOWEVENT:
+        {
+            // - When capturing mouse, SDL will send a bunch of conflicting LEAVE/ENTER event on every mouse move, but the final ENTER tends to be right.
+            // - However we won't get a correct LEAVE event for a captured window.
+            // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
+            //   causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
+            //   we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
+            Uint8 window_event = event->window.event;
+            if (window_event == SDL_WINDOWEVENT_ENTER)
+            {
+                bd->MouseWindowID = event->window.windowID;
+                bd->PendingMouseLeaveFrame = 0;
+            }
+            if (window_event == SDL_WINDOWEVENT_LEAVE)
+                bd->PendingMouseLeaveFrame = ImGui::GetFrameCount() + 1;
+            if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED)
+                io.AddFocusEvent(true);
+            else if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
+                io.AddFocusEvent(false);
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+
+    // Check and store if we are on a SDL backend that supports global mouse position
+    // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
+    bool mouse_can_use_global_state = false;
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+    const char* sdl_backend = SDL_GetCurrentVideoDriver();
+    const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
+    for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
+        if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
+            mouse_can_use_global_state = true;
+#endif
+
+    // Setup backend capabilities flags
+    ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)();
+    io.BackendPlatformUserData = (void*)bd;
+    io.BackendPlatformName = "imgui_impl_sdl2";
+    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)
+    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;        // We can honor io.WantSetMousePos requests (optional, rarely used)
+
+    bd->Window = window;
+    bd->Renderer = renderer;
+    bd->MouseCanUseGlobalState = mouse_can_use_global_state;
+
+    io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
+    io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
+    io.ClipboardUserData = nullptr;
+    io.SetPlatformImeDataFn = ImGui_ImplSDL2_SetPlatformImeData;
+
+    // Load mouse cursors
+    bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
+    bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
+    bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
+
+    // Set platform dependent data in viewport
+    // Our mouse update function expect PlatformHandle to be filled for the main viewport
+    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+    main_viewport->PlatformHandleRaw = nullptr;
+    SDL_SysWMinfo info;
+    SDL_VERSION(&info.version);
+    if (SDL_GetWindowWMInfo(window, &info))
+    {
+#if defined(SDL_VIDEO_DRIVER_WINDOWS)
+        main_viewport->PlatformHandleRaw = (void*)info.info.win.window;
+#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
+        main_viewport->PlatformHandleRaw = (void*)info.info.cocoa.window;
+#endif
+    }
+
+    // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
+    // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
+    // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
+    // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
+    // you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
+#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
+    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+#endif
+
+    // From 2.0.18: Enable native IME.
+    // IMPORTANT: This is used at the time of SDL_CreateWindow() so this will only affects secondary windows, if any.
+    // For the main window to be affected, your application needs to call this manually before calling SDL_CreateWindow().
+#ifdef SDL_HINT_IME_SHOW_UI
+    SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
+#endif
+
+    // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
+#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
+    SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
+#endif
+
+    return true;
+}
+
+bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
+{
+    IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
+    return ImGui_ImplSDL2_Init(window, nullptr);
+}
+
+bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)
+{
+#if !SDL_HAS_VULKAN
+    IM_ASSERT(0 && "Unsupported");
+#endif
+    return ImGui_ImplSDL2_Init(window, nullptr);
+}
+
+bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)
+{
+#if !defined(_WIN32)
+    IM_ASSERT(0 && "Unsupported");
+#endif
+    return ImGui_ImplSDL2_Init(window, nullptr);
+}
+
+bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window)
+{
+    return ImGui_ImplSDL2_Init(window, nullptr);
+}
+
+bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
+{
+    return ImGui_ImplSDL2_Init(window, renderer);
+}
+
+bool ImGui_ImplSDL2_InitForOther(SDL_Window* window)
+{
+    return ImGui_ImplSDL2_Init(window, nullptr);
+}
+
+void ImGui_ImplSDL2_Shutdown()
+{
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+    IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    if (bd->ClipboardTextData)
+        SDL_free(bd->ClipboardTextData);
+    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
+        SDL_FreeCursor(bd->MouseCursors[cursor_n]);
+    bd->LastMouseCursor = nullptr;
+
+    io.BackendPlatformName = nullptr;
+    io.BackendPlatformUserData = nullptr;
+    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
+    IM_DELETE(bd);
+}
+
+static void ImGui_ImplSDL2_UpdateMouseData()
+{
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+    ImGuiIO& io = ImGui::GetIO();
+
+    // We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below)
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+    // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
+    SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
+    SDL_Window* focused_window = SDL_GetKeyboardFocus();
+    const bool is_app_focused = (bd->Window == focused_window);
+#else
+    const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
+#endif
+    if (is_app_focused)
+    {
+        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
+        if (io.WantSetMousePos)
+            SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y);
+
+        // (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured)
+        if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
+        {
+            int window_x, window_y, mouse_x_global, mouse_y_global;
+            SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
+            SDL_GetWindowPosition(bd->Window, &window_x, &window_y);
+            io.AddMousePosEvent((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y));
+        }
+    }
+}
+
+static void ImGui_ImplSDL2_UpdateMouseCursor()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+        return;
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+
+    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
+    {
+        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+        SDL_ShowCursor(SDL_FALSE);
+    }
+    else
+    {
+        // Show OS mouse cursor
+        SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
+        if (bd->LastMouseCursor != expected_cursor)
+        {
+            SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
+            bd->LastMouseCursor = expected_cursor;
+        }
+        SDL_ShowCursor(SDL_TRUE);
+    }
+}
+
+static void ImGui_ImplSDL2_UpdateGamepads()
+{
+    ImGuiIO& io = ImGui::GetIO();
+    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
+        return;
+
+    // Get gamepad
+    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
+    SDL_GameController* game_controller = SDL_GameControllerOpen(0);
+    if (!game_controller)
+        return;
+    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+
+    // Update gamepad inputs
+    #define IM_SATURATE(V)                      (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
+    #define MAP_BUTTON(KEY_NO, BUTTON_NO)       { io.AddKeyEvent(KEY_NO, SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0); }
+    #define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
+    const int thumb_dead_zone = 8000;           // SDL_gamecontroller.h suggests using this value.
+    MAP_BUTTON(ImGuiKey_GamepadStart,           SDL_CONTROLLER_BUTTON_START);
+    MAP_BUTTON(ImGuiKey_GamepadBack,            SDL_CONTROLLER_BUTTON_BACK);
+    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        SDL_CONTROLLER_BUTTON_X);              // Xbox X, PS Square
+    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       SDL_CONTROLLER_BUTTON_B);              // Xbox B, PS Circle
+    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          SDL_CONTROLLER_BUTTON_Y);              // Xbox Y, PS Triangle
+    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        SDL_CONTROLLER_BUTTON_A);              // Xbox A, PS Cross
+    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        SDL_CONTROLLER_BUTTON_DPAD_LEFT);
+    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
+    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          SDL_CONTROLLER_BUTTON_DPAD_UP);
+    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        SDL_CONTROLLER_BUTTON_DPAD_DOWN);
+    MAP_BUTTON(ImGuiKey_GamepadL1,              SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+    MAP_BUTTON(ImGuiKey_GamepadR1,              SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+    MAP_ANALOG(ImGuiKey_GamepadL2,              SDL_CONTROLLER_AXIS_TRIGGERLEFT,  0.0f, 32767);
+    MAP_ANALOG(ImGuiKey_GamepadR2,              SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767);
+    MAP_BUTTON(ImGuiKey_GamepadL3,              SDL_CONTROLLER_BUTTON_LEFTSTICK);
+    MAP_BUTTON(ImGuiKey_GamepadR3,              SDL_CONTROLLER_BUTTON_RIGHTSTICK);
+    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      SDL_CONTROLLER_AXIS_LEFTX,  -thumb_dead_zone, -32768);
+    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     SDL_CONTROLLER_AXIS_LEFTX,  +thumb_dead_zone, +32767);
+    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        SDL_CONTROLLER_AXIS_LEFTY,  -thumb_dead_zone, -32768);
+    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      SDL_CONTROLLER_AXIS_LEFTY,  +thumb_dead_zone, +32767);
+    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      SDL_CONTROLLER_AXIS_RIGHTX, -thumb_dead_zone, -32768);
+    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     SDL_CONTROLLER_AXIS_RIGHTX, +thumb_dead_zone, +32767);
+    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        SDL_CONTROLLER_AXIS_RIGHTY, -thumb_dead_zone, -32768);
+    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767);
+    #undef MAP_BUTTON
+    #undef MAP_ANALOG
+}
+
+void ImGui_ImplSDL2_NewFrame()
+{
+    ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+    IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL2_Init()?");
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size (every frame to accommodate for window resizing)
+    int w, h;
+    int display_w, display_h;
+    SDL_GetWindowSize(bd->Window, &w, &h);
+    if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
+        w = h = 0;
+    if (bd->Renderer != nullptr)
+        SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h);
+    else
+        SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h);
+    io.DisplaySize = ImVec2((float)w, (float)h);
+    if (w > 0 && h > 0)
+        io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
+
+    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
+    // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
+    static Uint64 frequency = SDL_GetPerformanceFrequency();
+    Uint64 current_time = SDL_GetPerformanceCounter();
+    if (current_time <= bd->Time)
+        current_time = bd->Time + 1;
+    io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
+    bd->Time = current_time;
+
+    if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
+    {
+        bd->MouseWindowID = 0;
+        bd->PendingMouseLeaveFrame = 0;
+        io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+    }
+
+    ImGui_ImplSDL2_UpdateMouseData();
+    ImGui_ImplSDL2_UpdateMouseCursor();
+
+    // Update game controllers (if enabled and available)
+    ImGui_ImplSDL2_UpdateGamepads();
+}
+
+//-----------------------------------------------------------------------------
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif
+#endif // #ifndef IMGUI_DISABLE
diff --git a/graphics/imgui/backends/imgui_impl_sdl2_scummvm.h b/backends/imgui/backends/imgui_impl_sdl2_scummvm.h
similarity index 66%
rename from graphics/imgui/backends/imgui_impl_sdl2_scummvm.h
rename to backends/imgui/backends/imgui_impl_sdl2_scummvm.h
index 5e772da10a3..1e21f33c644 100644
--- a/graphics/imgui/backends/imgui_impl_sdl2_scummvm.h
+++ b/backends/imgui/backends/imgui_impl_sdl2_scummvm.h
@@ -19,29 +19,26 @@
 // - Introduction, links and more at the top of imgui.cpp
 
 #pragma once
-#include "graphics/imgui/imgui.h" // IMGUI_IMPL_API
+#include "backends/imgui/imgui.h" // IMGUI_IMPL_API
 #ifndef IMGUI_DISABLE
 
 struct SDL_Window;
 struct SDL_Renderer;
 typedef union SDL_Event SDL_Event;
 
-namespace Common {
-struct Event;
-}
-
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window *window, void *sdl_gl_context);
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window *window);
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window *window);
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window *window);
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window *window, SDL_Renderer *renderer);
-IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOther(SDL_Window *window);
-IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown();
-IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame();
-IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const Common::Event *event);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForVulkan(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForD3D(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForMetal(SDL_Window* window);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOther(SDL_Window* window);
+IMGUI_IMPL_API void     ImGui_ImplSDL2_Shutdown();
+IMGUI_IMPL_API void     ImGui_ImplSDL2_NewFrame();
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);
+IMGUI_IMPL_API bool     ImGui_ImplSDL2_Ready();
 
 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-static inline void ImGui_ImplSDL2_NewFrame(SDL_Window *) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter
+static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter
 #endif
 
 #endif // #ifndef IMGUI_DISABLE
diff --git a/graphics/imgui/imconfig.h b/backends/imgui/imconfig.h
similarity index 100%
rename from graphics/imgui/imconfig.h
rename to backends/imgui/imconfig.h
diff --git a/graphics/imgui/imgui.cpp b/backends/imgui/imgui.cpp
similarity index 100%
rename from graphics/imgui/imgui.cpp
rename to backends/imgui/imgui.cpp
diff --git a/graphics/imgui/imgui.h b/backends/imgui/imgui.h
similarity index 99%
rename from graphics/imgui/imgui.h
rename to backends/imgui/imgui.h
index 0ec197760e5..9ecacb0dfb3 100644
--- a/graphics/imgui/imgui.h
+++ b/backends/imgui/imgui.h
@@ -93,8 +93,10 @@ Index of this file:
 #define IM_FMTARGS(FMT)             __attribute__((format(gnu_printf, FMT, FMT+1)))
 #define IM_FMTLIST(FMT)             __attribute__((format(gnu_printf, FMT, 0)))
 #elif !defined(IMGUI_USE_STB_SPRINTF) && (defined(__clang__) || defined(__GNUC__))
-#define IM_FMTARGS(FMT)             __attribute__((format(printf, FMT, FMT+1)))
-#define IM_FMTLIST(FMT)             __attribute__((format(printf, FMT, 0)))
+//#define IM_FMTARGS(FMT)             __attribute__((format(printf, FMT, FMT+1)))
+//#define IM_FMTLIST(FMT)             __attribute__((format(printf, FMT, 0)))
+#define IM_FMTARGS(FMT)
+#define IM_FMTLIST(FMT)
 #else
 #define IM_FMTARGS(FMT)
 #define IM_FMTLIST(FMT)
diff --git a/graphics/imgui/imgui_demo.cpp b/backends/imgui/imgui_demo.cpp
similarity index 100%
rename from graphics/imgui/imgui_demo.cpp
rename to backends/imgui/imgui_demo.cpp
diff --git a/graphics/imgui/imgui_draw.cpp b/backends/imgui/imgui_draw.cpp
similarity index 100%
rename from graphics/imgui/imgui_draw.cpp
rename to backends/imgui/imgui_draw.cpp
diff --git a/graphics/imgui/imgui_internal.h b/backends/imgui/imgui_internal.h
similarity index 100%
rename from graphics/imgui/imgui_internal.h
rename to backends/imgui/imgui_internal.h
diff --git a/graphics/imgui/imgui_tables.cpp b/backends/imgui/imgui_tables.cpp
similarity index 100%
rename from graphics/imgui/imgui_tables.cpp
rename to backends/imgui/imgui_tables.cpp
diff --git a/graphics/imgui/imgui_widgets.cpp b/backends/imgui/imgui_widgets.cpp
similarity index 100%
rename from graphics/imgui/imgui_widgets.cpp
rename to backends/imgui/imgui_widgets.cpp
diff --git a/graphics/imgui/imstb_rectpack.h b/backends/imgui/imstb_rectpack.h
similarity index 100%
rename from graphics/imgui/imstb_rectpack.h
rename to backends/imgui/imstb_rectpack.h
diff --git a/graphics/imgui/imstb_textedit.h b/backends/imgui/imstb_textedit.h
similarity index 100%
rename from graphics/imgui/imstb_textedit.h
rename to backends/imgui/imstb_textedit.h
diff --git a/graphics/imgui/imstb_truetype.h b/backends/imgui/imstb_truetype.h
similarity index 100%
rename from graphics/imgui/imstb_truetype.h
rename to backends/imgui/imstb_truetype.h
diff --git a/backends/module.mk b/backends/module.mk
index bee6c1b5b2f..00d3461ceb5 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -488,5 +488,15 @@ MODULE_OBJS += \
 	saves/recorder/recorder-saves.o
 endif
 
+ifdef USE_IMGUI
+MODULE_OBJS += \
+	imgui/imgui.o \
+	imgui/imgui_draw.o \
+	imgui/imgui_widgets.o \
+	imgui/imgui_tables.o \
+	imgui/backends/imgui_impl_opengl3_scummvm.o \
+	imgui/backends/imgui_impl_sdl2_scummvm.o
+endif
+
 # Include common rules
 include $(srcdir)/rules.mk
diff --git a/engines/metaengine.h b/engines/metaengine.h
index 71bc6b3ec8d..210bc428f65 100644
--- a/engines/metaengine.h
+++ b/engines/metaengine.h
@@ -564,6 +564,8 @@ public:
 	 * Read the extended savegame header from the given savegame file.
 	 */
 	WARN_UNUSED_RESULT static bool readSavegameHeader(Common::InSaveFile *in, ExtendedSavegameHeader *header, bool skipThumbnail = true);
+
+	virtual void renderImGui() {}
 };
 
 /**
diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 96e4f45233f..367f7370587 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -19,8 +19,7 @@
  *
  */
 
-#include "graphics/imgui/imgui.h"
-
+#include "backends/imgui/imgui.h"
 #include "common/debug-channels.h"
 #include "twp/twp.h"
 #include "twp/debugtools.h"
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 520fd963819..d270e0f0689 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -37,6 +37,7 @@
 #include "twp/savegame.h"
 #include "twp/time.h"
 #include "twp/actions.h"
+#include "twp/debugtools.h"
 #include "twp/dialogs.h"
 
 #define MAX_SAVES 99
@@ -188,6 +189,12 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 	return Common::Keymap::arrayOf(engineKeyMap);
 }
 
+void TwpMetaEngine::renderImGui() {
+#ifdef USE_IMGUI
+	Twp::onImGuiRender();
+#endif
+}
+
 #if PLUGIN_ENABLED_DYNAMIC(TWP)
 REGISTER_PLUGIN_DYNAMIC(TWP, PLUGIN_TYPE_ENGINE, TwpMetaEngine);
 #else
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 0d725bb6338..835db9af58f 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -46,6 +46,8 @@ public:
 	GUI::OptionsContainerWidget *buildEngineOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const override;
 
 	virtual Common::Array<Common::Keymap *> initKeymaps(const char *target) const override;
+
+	void renderImGui() override;
 };
 
 #endif // TWP_METAENGINE_H
diff --git a/engines/twp/module.mk b/engines/twp/module.mk
index 5331234b4e5..068a5072cbe 100644
--- a/engines/twp/module.mk
+++ b/engines/twp/module.mk
@@ -38,7 +38,6 @@ MODULE_OBJS = \
 	time.o \
 	tsv.o \
 	twp.o \
-	twpimgui.o \
 	util.o \
 	vm.o \
 	walkboxnode.o \
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 74042310bbf..d98317c5233 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -31,7 +31,6 @@
 #include "twp/actions.h"
 #include "twp/callback.h"
 #include "twp/console.h"
-#include "twp/debugtools.h"
 #include "twp/detection.h"
 #include "twp/enginedialogtarget.h"
 #include "twp/hud.h"
@@ -45,7 +44,6 @@
 #include "twp/task.h"
 #include "twp/thread.h"
 #include "twp/tsv.h"
-#include "twp/twpimgui.h"
 #include "twp/vm.h"
 #include "twp/walkboxnode.h"
 
@@ -73,7 +71,6 @@ TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	_hud.reset(new Hud());
 	_pack.reset(new GGPackSet());
 	_saveGameManager.reset(new SaveGameManager());
-	_imgui.reset(new TwpImGui());
 
 	_screenScene->setName("Screen");
 	_scene->addChild(_walkboxNode.get());
@@ -722,7 +719,6 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 
 	// imgui render
 	_gfx.use(nullptr);
-	_imgui->render();
 
 	g_system->updateScreen();
 }
@@ -736,7 +732,6 @@ void TwpEngine::updateSettingVars() {
 Common::Error TwpEngine::run() {
 	initGraphics3d(SCREEN_WIDTH, SCREEN_HEIGHT);
 	_screen = new Graphics::Screen(SCREEN_WIDTH, SCREEN_HEIGHT);
-	_imgui->init();
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -789,9 +784,6 @@ Common::Error TwpEngine::run() {
 	while (!shouldQuit()) {
 		Math::Vector2d camPos = _gfx.cameraPos();
 		while (g_system->getEventManager()->pollEvent(e)) {
-			if(_imgui->processEvent(&e))
-				continue;
-
 			switch (e.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START: {
 				switch ((TwpAction)e.customType) {
@@ -956,9 +948,6 @@ Common::Error TwpEngine::run() {
 		}
 	}
 
-	// Cleanup
-	_imgui->cleanup();
-
 	return Common::kNoError;
 }
 
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index c63eac8708f..1b2fb64aab4 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -250,7 +250,6 @@ private:
 	unique_ptr<Shader> _bwShader;
 	unique_ptr<Shader> _ghostShader;
 	unique_ptr<Shader> _sepiaShader;
-	unique_ptr<TwpImGui> _imgui;
 };
 
 extern TwpEngine *g_twp;
diff --git a/engines/twp/twpimgui.cpp b/engines/twp/twpimgui.cpp
deleted file mode 100644
index cffc73a0801..00000000000
--- a/engines/twp/twpimgui.cpp
+++ /dev/null
@@ -1,108 +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 "twp/twpimgui.h"
-#if defined(HAVE_CONFIG_H)
-#include "config.h"
-#endif
-
-#ifdef USE_IMGUI
-#include "graphics/imgui/backends/imgui_impl_sdl2_scummvm.h"
-#include "graphics/imgui/backends/imgui_impl_opengl3_scummvm.h"
-#include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
-// here I undefined these symbols because of the <X11/Xlib.h> defining them
-// and messing with all classes or structure using the same names
-#undef Bool
-#undef CursorShape
-#undef Expose
-#undef KeyPress
-#undef KeyRelease
-#undef FocusIn
-#undef FocusOut
-#undef FontChange
-#undef None
-#undef Status
-#undef Unsorted
-
-#include "twp/debugtools.h"
-#endif
-
-namespace Twp {
-
-#ifdef USE_IMGUI
-SDL_Window *g_window = nullptr;
-#endif
-
-void DearImGui::init() {
-#ifdef USE_IMGUI
-	// Setup Dear ImGui
-	OpenGLSdlGraphics3dManager *manager = dynamic_cast<OpenGLSdlGraphics3dManager *>(g_system->getPaletteManager());
-	IMGUI_CHECKVERSION();
-	ImGui::CreateContext();
-	g_window = manager->getWindow()->getSDLWindow();
-	SDL_GLContext glContext = SDL_GL_GetCurrentContext();
-	ImGui_ImplSDL2_InitForOpenGL(g_window, glContext);
-	ImGui_ImplOpenGL3_Init("#version 110");
-	ImGui::StyleColorsDark();
-
-	ImGuiIO &io = ImGui::GetIO();
-	Common::Path initPath(ConfMan.getPath("savepath"));
-	initPath = initPath.appendComponent("twp_imgui.ini");
-	io.IniFilename = initPath.toString().c_str();
-#endif
-}
-
-void DearImGui::cleanup() {
-#ifdef USE_IMGUI
-	ImGui_ImplOpenGL3_Shutdown();
-	ImGui_ImplSDL2_Shutdown();
-	ImGui::DestroyContext();
-#endif
-}
-
-bool DearImGui::processEvent(const Common::Event *event) {
-#ifdef USE_IMGUI
-	ImGui_ImplSDL2_ProcessEvent(event);
-	ImGuiIO &io = ImGui::GetIO();
-	if (io.WantTextInput || io.WantCaptureMouse)
-		return true;
-#endif
-	return false;
-}
-
-void DearImGui::render() {
-#ifdef USE_IMGUI
-	ImGui_ImplOpenGL3_NewFrame();
-	ImGui_ImplSDL2_NewFrame(g_window);
-	ImGui::NewFrame();
-	onRender();
-	ImGui::Render();
-	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
-#endif
-}
-
-void TwpImGui::onRender() {
-#ifdef USE_IMGUI
-	onImGuiRender();
-#endif
-}
-
-} // namespace Twp
diff --git a/engines/twp/twpimgui.h b/engines/twp/twpimgui.h
deleted file mode 100644
index 140db36b097..00000000000
--- a/engines/twp/twpimgui.h
+++ /dev/null
@@ -1,51 +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 TWP_IMGUI_H
-#define TWP_IMGUI_H
-
-namespace Common {
-struct Event;
-}
-
-namespace Twp {
-
-class DearImGui {
-public:
-	virtual ~DearImGui() = default;
-
-	void init();
-	bool processEvent(const Common::Event *event);
-	void render();
-	void cleanup();
-
-protected:
-	virtual void onRender() = 0;
-};
-
-class TwpImGui : public DearImGui {
-private:
-	void onRender() override;
-};
-
-} // namespace Twp
-
-#endif
diff --git a/graphics/imgui/backends/imgui_impl_sdl2_scummvm.cpp b/graphics/imgui/backends/imgui_impl_sdl2_scummvm.cpp
deleted file mode 100644
index a557c1d3872..00000000000
--- a/graphics/imgui/backends/imgui_impl_sdl2_scummvm.cpp
+++ /dev/null
@@ -1,766 +0,0 @@
-// dear imgui: Platform Backend for SDL2
-// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
-// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
-// (Prefer SDL 2.0.5+ for full feature support.)
-
-// Implemented features:
-//  [X] Platform: Clipboard support.
-//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
-//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
-//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
-//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-//  [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
-
-// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
-// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
-// Learn about Dear ImGui:
-// - FAQ                  https://dearimgui.com/faq
-// - Getting Started      https://dearimgui.com/getting-started
-// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
-// - Introduction, links and more at the top of imgui.cpp
-
-// CHANGELOG
-// (minor and older changes stripped away, please see git history for details)
-//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
-//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
-//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
-//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
-//  2023-02-07: Implement IME handler (io.SetPlatformImeDataFn will call SDL_SetTextInputRect()/SDL_StartTextInput()).
-//  2023-02-07: *BREAKING CHANGE* Renamed this backend file from imgui_impl_sdl.cpp/.h to imgui_impl_sdl2.cpp/.h in prevision for the future release of SDL3.
-//  2023-02-02: Avoid calling SDL_SetCursor() when cursor has not changed, as the function is surprisingly costly on Mac with latest SDL (may be fixed in next SDL version).
-//  2023-02-02: Added support for SDL 2.0.18+ preciseX/preciseY mouse wheel data for smooth scrolling + Scaling X value on Emscripten (bug?). (#4019, #6096)
-//  2023-02-02: Removed SDL_MOUSEWHEEL value clamping, as values seem correct in latest Emscripten. (#4019)
-//  2023-02-01: Flipping SDL_MOUSEWHEEL 'wheel.x' value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463)
-//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
-//  2022-09-26: Inputs: Disable SDL 2.0.22 new "auto capture" (SDL_HINT_MOUSE_AUTO_CAPTURE) which prevents drag and drop across windows for multi-viewport support + don't capture when drag and dropping. (#5710)
-//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
-//  2022-03-22: Inputs: Fix mouse position issues when dragging outside of boundaries. SDL_CaptureMouse() erroneously still gives out LEAVE events when hovering OS decorations.
-//  2022-03-22: Inputs: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2).
-//  2022-02-04: Added SDL_Renderer* parameter to ImGui_ImplSDL2_InitForSDLRenderer(), so we can use SDL_GetRendererOutputSize() instead of SDL_GL_GetDrawableSize() when bound to a SDL_Renderer.
-//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
-//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
-//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
-//  2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
-//  2022-01-12: Update mouse inputs using SDL_MOUSEMOTION/SDL_WINDOWEVENT_LEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API.
-//  2022-01-12: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted.
-//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
-//  2021-08-17: Calling io.AddFocusEvent() on SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST.
-//  2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+)
-//  2021-06-29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary.
-//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
-//  2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950)
-//  2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
-//  2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).
-//  2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).
-//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.
-//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
-//  2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
-//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
-//  2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.
-//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
-//  2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.
-//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
-//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
-//  2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.
-//  2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.
-//  2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).
-//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
-//  2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.
-//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
-//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
-//  2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).
-//  2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.
-//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
-//  2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS.
-//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
-//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
-//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
-
-#define FORBIDDEN_SYMBOL_ALLOW_ALL
-#include "graphics/imgui/imgui.h"
-#ifndef IMGUI_DISABLE
-#include "imgui_impl_sdl2_scummvm.h"
-#include "common/events.h"
-
-// Clang warnings with -Weverything
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
-#endif
-
-// SDL
-#include <SDL.h>
-#include <SDL_syswm.h>
-#if defined(__APPLE__)
-#include <TargetConditionals.h>
-#endif
-
-#if SDL_VERSION_ATLEAST(2, 0, 4) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 1
-#else
-#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0
-#endif
-#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2, 0, 6)
-
-// SDL Data
-struct ImGui_ImplSDL2_Data {
-	SDL_Window *Window;
-	SDL_Renderer *Renderer;
-	Uint64 Time;
-	Uint32 MouseWindowID;
-	int MouseButtonsDown;
-	SDL_Cursor *MouseCursors[ImGuiMouseCursor_COUNT];
-	SDL_Cursor *LastMouseCursor;
-	int PendingMouseLeaveFrame;
-	char *ClipboardTextData;
-	bool MouseCanUseGlobalState;
-
-	ImGui_ImplSDL2_Data() { memset((void *)this, 0, sizeof(*this)); }
-};
-
-// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
-// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
-// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
-// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
-static ImGui_ImplSDL2_Data *ImGui_ImplSDL2_GetBackendData() {
-	return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data *)ImGui::GetIO().BackendPlatformUserData : nullptr;
-}
-
-// Functions
-static const char *ImGui_ImplSDL2_GetClipboardText(void *) {
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-	if (bd->ClipboardTextData)
-		SDL_free(bd->ClipboardTextData);
-	bd->ClipboardTextData = SDL_GetClipboardText();
-	return bd->ClipboardTextData;
-}
-
-static void ImGui_ImplSDL2_SetClipboardText(void *, const char *text) {
-	SDL_SetClipboardText(text);
-}
-
-// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow().
-static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport *, ImGuiPlatformImeData *data) {
-	if (data->WantVisible) {
-		SDL_Rect r;
-		r.x = (int)data->InputPos.x;
-		r.y = (int)data->InputPos.y;
-		r.w = 1;
-		r.h = (int)data->InputLineHeight;
-		SDL_SetTextInputRect(&r);
-	}
-}
-
-static ImGuiKey ImGui_ImplSDL2_KeycodeToImGuiKey(int keycode) {
-	switch (keycode) {
-	case SDLK_TAB:
-		return ImGuiKey_Tab;
-	case SDLK_LEFT:
-		return ImGuiKey_LeftArrow;
-	case SDLK_RIGHT:
-		return ImGuiKey_RightArrow;
-	case SDLK_UP:
-		return ImGuiKey_UpArrow;
-	case SDLK_DOWN:
-		return ImGuiKey_DownArrow;
-	case SDLK_PAGEUP:
-		return ImGuiKey_PageUp;
-	case SDLK_PAGEDOWN:
-		return ImGuiKey_PageDown;
-	case SDLK_HOME:
-		return ImGuiKey_Home;
-	case SDLK_END:
-		return ImGuiKey_End;
-	case SDLK_INSERT:
-		return ImGuiKey_Insert;
-	case SDLK_DELETE:
-		return ImGuiKey_Delete;
-	case SDLK_BACKSPACE:
-		return ImGuiKey_Backspace;
-	case SDLK_SPACE:
-		return ImGuiKey_Space;
-	case SDLK_RETURN:
-		return ImGuiKey_Enter;
-	case SDLK_ESCAPE:
-		return ImGuiKey_Escape;
-	case SDLK_QUOTE:
-		return ImGuiKey_Apostrophe;
-	case SDLK_COMMA:
-		return ImGuiKey_Comma;
-	case SDLK_MINUS:
-		return ImGuiKey_Minus;
-	case SDLK_PERIOD:
-		return ImGuiKey_Period;
-	case SDLK_SLASH:
-		return ImGuiKey_Slash;
-	case SDLK_SEMICOLON:
-		return ImGuiKey_Semicolon;
-	case SDLK_EQUALS:
-		return ImGuiKey_Equal;
-	case SDLK_LEFTBRACKET:
-		return ImGuiKey_LeftBracket;
-	case SDLK_BACKSLASH:
-		return ImGuiKey_Backslash;
-	case SDLK_RIGHTBRACKET:
-		return ImGuiKey_RightBracket;
-	case SDLK_BACKQUOTE:
-		return ImGuiKey_GraveAccent;
-	case SDLK_CAPSLOCK:
-		return ImGuiKey_CapsLock;
-	case SDLK_SCROLLLOCK:
-		return ImGuiKey_ScrollLock;
-	case SDLK_NUMLOCKCLEAR:
-		return ImGuiKey_NumLock;
-	case SDLK_PRINTSCREEN:
-		return ImGuiKey_PrintScreen;
-	case SDLK_PAUSE:
-		return ImGuiKey_Pause;
-	case SDLK_KP_0:
-		return ImGuiKey_Keypad0;
-	case SDLK_KP_1:
-		return ImGuiKey_Keypad1;
-	case SDLK_KP_2:
-		return ImGuiKey_Keypad2;
-	case SDLK_KP_3:
-		return ImGuiKey_Keypad3;
-	case SDLK_KP_4:
-		return ImGuiKey_Keypad4;
-	case SDLK_KP_5:
-		return ImGuiKey_Keypad5;
-	case SDLK_KP_6:
-		return ImGuiKey_Keypad6;
-	case SDLK_KP_7:
-		return ImGuiKey_Keypad7;
-	case SDLK_KP_8:
-		return ImGuiKey_Keypad8;
-	case SDLK_KP_9:
-		return ImGuiKey_Keypad9;
-	case SDLK_KP_PERIOD:
-		return ImGuiKey_KeypadDecimal;
-	case SDLK_KP_DIVIDE:
-		return ImGuiKey_KeypadDivide;
-	case SDLK_KP_MULTIPLY:
-		return ImGuiKey_KeypadMultiply;
-	case SDLK_KP_MINUS:
-		return ImGuiKey_KeypadSubtract;
-	case SDLK_KP_PLUS:
-		return ImGuiKey_KeypadAdd;
-	case SDLK_KP_ENTER:
-		return ImGuiKey_KeypadEnter;
-	case SDLK_KP_EQUALS:
-		return ImGuiKey_KeypadEqual;
-	case SDLK_LCTRL:
-		return ImGuiKey_LeftCtrl;
-	case SDLK_LSHIFT:
-		return ImGuiKey_LeftShift;
-	case SDLK_LALT:
-		return ImGuiKey_LeftAlt;
-	case SDLK_LGUI:
-		return ImGuiKey_LeftSuper;
-	case SDLK_RCTRL:
-		return ImGuiKey_RightCtrl;
-	case SDLK_RSHIFT:
-		return ImGuiKey_RightShift;
-	case SDLK_RALT:
-		return ImGuiKey_RightAlt;
-	case SDLK_RGUI:
-		return ImGuiKey_RightSuper;
-	case SDLK_APPLICATION:
-		return ImGuiKey_Menu;
-	case SDLK_0:
-		return ImGuiKey_0;
-	case SDLK_1:
-		return ImGuiKey_1;
-	case SDLK_2:
-		return ImGuiKey_2;
-	case SDLK_3:
-		return ImGuiKey_3;
-	case SDLK_4:
-		return ImGuiKey_4;
-	case SDLK_5:
-		return ImGuiKey_5;
-	case SDLK_6:
-		return ImGuiKey_6;
-	case SDLK_7:
-		return ImGuiKey_7;
-	case SDLK_8:
-		return ImGuiKey_8;
-	case SDLK_9:
-		return ImGuiKey_9;
-	case SDLK_a:
-		return ImGuiKey_A;
-	case SDLK_b:
-		return ImGuiKey_B;
-	case SDLK_c:
-		return ImGuiKey_C;
-	case SDLK_d:
-		return ImGuiKey_D;
-	case SDLK_e:
-		return ImGuiKey_E;
-	case SDLK_f:
-		return ImGuiKey_F;
-	case SDLK_g:
-		return ImGuiKey_G;
-	case SDLK_h:
-		return ImGuiKey_H;
-	case SDLK_i:
-		return ImGuiKey_I;
-	case SDLK_j:
-		return ImGuiKey_J;
-	case SDLK_k:
-		return ImGuiKey_K;
-	case SDLK_l:
-		return ImGuiKey_L;
-	case SDLK_m:
-		return ImGuiKey_M;
-	case SDLK_n:
-		return ImGuiKey_N;
-	case SDLK_o:
-		return ImGuiKey_O;
-	case SDLK_p:
-		return ImGuiKey_P;
-	case SDLK_q:
-		return ImGuiKey_Q;
-	case SDLK_r:
-		return ImGuiKey_R;
-	case SDLK_s:
-		return ImGuiKey_S;
-	case SDLK_t:
-		return ImGuiKey_T;
-	case SDLK_u:
-		return ImGuiKey_U;
-	case SDLK_v:
-		return ImGuiKey_V;
-	case SDLK_w:
-		return ImGuiKey_W;
-	case SDLK_x:
-		return ImGuiKey_X;
-	case SDLK_y:
-		return ImGuiKey_Y;
-	case SDLK_z:
-		return ImGuiKey_Z;
-	case SDLK_F1:
-		return ImGuiKey_F1;
-	case SDLK_F2:
-		return ImGuiKey_F2;
-	case SDLK_F3:
-		return ImGuiKey_F3;
-	case SDLK_F4:
-		return ImGuiKey_F4;
-	case SDLK_F5:
-		return ImGuiKey_F5;
-	case SDLK_F6:
-		return ImGuiKey_F6;
-	case SDLK_F7:
-		return ImGuiKey_F7;
-	case SDLK_F8:
-		return ImGuiKey_F8;
-	case SDLK_F9:
-		return ImGuiKey_F9;
-	case SDLK_F10:
-		return ImGuiKey_F10;
-	case SDLK_F11:
-		return ImGuiKey_F11;
-	case SDLK_F12:
-		return ImGuiKey_F12;
-	case SDLK_F13:
-		return ImGuiKey_F13;
-	case SDLK_F14:
-		return ImGuiKey_F14;
-	case SDLK_F15:
-		return ImGuiKey_F15;
-	case SDLK_F16:
-		return ImGuiKey_F16;
-	case SDLK_F17:
-		return ImGuiKey_F17;
-	case SDLK_F18:
-		return ImGuiKey_F18;
-	case SDLK_F19:
-		return ImGuiKey_F19;
-	case SDLK_F20:
-		return ImGuiKey_F20;
-	case SDLK_F21:
-		return ImGuiKey_F21;
-	case SDLK_F22:
-		return ImGuiKey_F22;
-	case SDLK_F23:
-		return ImGuiKey_F23;
-	case SDLK_F24:
-		return ImGuiKey_F24;
-	case SDLK_AC_BACK:
-		return ImGuiKey_AppBack;
-	case SDLK_AC_FORWARD:
-		return ImGuiKey_AppForward;
-	}
-	return ImGuiKey_None;
-}
-
-static void ImGui_ImplSDL2_UpdateKeyModifiers(const Common::KeyState &keyState) {
-	ImGuiIO &io = ImGui::GetIO();
-	io.AddKeyEvent(ImGuiMod_Ctrl, keyState.hasFlags(Common::KBD_CTRL));
-	io.AddKeyEvent(ImGuiMod_Shift, keyState.hasFlags(Common::KBD_SHIFT));
-	io.AddKeyEvent(ImGuiMod_Alt, keyState.hasFlags(Common::KBD_ALT));
-	io.AddKeyEvent(ImGuiMod_Super, keyState.hasFlags(Common::KBD_META));
-}
-
-// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
-// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
-// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
-// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
-// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
-bool ImGui_ImplSDL2_ProcessEvent(const Common::Event *event) {
-	ImGuiIO &io = ImGui::GetIO();
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-
-	switch (event->type) {
-	case Common::EVENT_MOUSEMOVE: {
-		ImVec2 mouse_pos((float)event->mouse.x, (float)event->mouse.y);
-		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
-		io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
-		return true;
-	}
-	case Common::EVENT_WHEELDOWN:
-	case Common::EVENT_WHEELUP: {
-		float wheel_x = 0;
-		float wheel_y = event->type == Common::EVENT_WHEELDOWN ? -1.f : 1.f;
-		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
-		io.AddMouseWheelEvent(wheel_x, wheel_y);
-		return true;
-	}
-	case Common::EVENT_LBUTTONDOWN:
-	case Common::EVENT_LBUTTONUP:
-	case Common::EVENT_MBUTTONDOWN:
-	case Common::EVENT_MBUTTONUP:
-	case Common::EVENT_RBUTTONDOWN:
-	case Common::EVENT_RBUTTONUP: {
-		int mouse_button = -1;
-		bool mouse_down = false;
-		if (event->type == Common::EVENT_LBUTTONDOWN) {
-			mouse_button = 0;
-			mouse_down = true;
-		}
-		if (event->type == Common::EVENT_LBUTTONUP) {
-			mouse_button = 0;
-		}
-		if (event->type == Common::EVENT_MBUTTONDOWN) {
-			mouse_button = 1;
-			mouse_down = true;
-		}
-		if (event->type == Common::EVENT_MBUTTONUP) {
-			mouse_button = 2;
-		}
-		if (event->type == Common::EVENT_RBUTTONDOWN) {
-			mouse_button = 3;
-			mouse_down = true;
-		}
-		if (event->type == Common::EVENT_RBUTTONUP) {
-			mouse_button = 4;
-		}
-		if (mouse_button == -1)
-			break;
-		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
-		io.AddMouseButtonEvent(mouse_button, mouse_down);
-		bd->MouseButtonsDown = mouse_down ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
-		return true;
-	}
-	case Common::EVENT_KEYDOWN:
-	case Common::EVENT_KEYUP: {
-
-		ImGui_ImplSDL2_UpdateKeyModifiers(event->kbd);
-		ImGuiKey key = ImGui_ImplSDL2_KeycodeToImGuiKey(event->kbd.keycode);
-		io.AddKeyEvent(key, (event->type == Common::EVENT_KEYDOWN));
-		io.SetKeyEventNativeData(key, event->kbd.keycode, event->kbd.keycode);
-		if (event->type == Common::EVENT_KEYUP) {
-			io.AddInputCharacter(event->kbd.ascii);
-		}
-		return true;
-	}
-	case Common::EVENT_FOCUS_GAINED:
-		io.AddFocusEvent(true);
-		return true;
-	case Common::EVENT_FOCUS_LOST:
-		io.AddFocusEvent(false);
-		return true;
-	default:
-		return false;
-	}
-	return false;
-}
-
-static bool ImGui_ImplSDL2_Init(SDL_Window *window, SDL_Renderer *renderer) {
-	ImGuiIO &io = ImGui::GetIO();
-	IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
-
-	// Check and store if we are on a SDL backend that supports global mouse position
-	// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
-	bool mouse_can_use_global_state = false;
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-	const char *sdl_backend = SDL_GetCurrentVideoDriver();
-	const char *global_mouse_whitelist[] = {"windows", "cocoa", "x11", "DIVE", "VMAN"};
-	for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
-		if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
-			mouse_can_use_global_state = true;
-#endif
-
-	// Setup backend capabilities flags
-	ImGui_ImplSDL2_Data *bd = IM_NEW(ImGui_ImplSDL2_Data)();
-	io.BackendPlatformUserData = (void *)bd;
-	io.BackendPlatformName = "imgui_impl_sdl2";
-	io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
-	io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;  // We can honor io.WantSetMousePos requests (optional, rarely used)
-
-	bd->Window = window;
-	bd->Renderer = renderer;
-	bd->MouseCanUseGlobalState = mouse_can_use_global_state;
-
-	io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
-	io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
-	io.ClipboardUserData = nullptr;
-	io.SetPlatformImeDataFn = ImGui_ImplSDL2_SetPlatformImeData;
-
-	// Load mouse cursors
-	bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
-	bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
-	bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
-	bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
-	bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
-	bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
-	bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
-	bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
-	bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
-
-	// Set platform dependent data in viewport
-	// Our mouse update function expect PlatformHandle to be filled for the main viewport
-	ImGuiViewport *main_viewport = ImGui::GetMainViewport();
-	main_viewport->PlatformHandleRaw = nullptr;
-	SDL_SysWMinfo info;
-	SDL_VERSION(&info.version);
-	if (SDL_GetWindowWMInfo(window, &info)) {
-#if defined(SDL_VIDEO_DRIVER_WINDOWS)
-		main_viewport->PlatformHandleRaw = (void *)info.info.win.window;
-#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
-		main_viewport->PlatformHandleRaw = (void *)info.info.cocoa.window;
-#endif
-	}
-
-	// From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
-	// Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
-	// (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
-	// It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
-	// you can ignore SDL_MOUSEBUTTONDOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
-#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
-	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
-#endif
-
-	// From 2.0.18: Enable native IME.
-	// IMPORTANT: This is used at the time of SDL_CreateWindow() so this will only affects secondary windows, if any.
-	// For the main window to be affected, your application needs to call this manually before calling SDL_CreateWindow().
-#ifdef SDL_HINT_IME_SHOW_UI
-	SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
-#endif
-
-	// From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
-#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
-	SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
-#endif
-
-	return true;
-}
-
-bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window *window, void *sdl_gl_context) {
-	IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
-	return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForVulkan(SDL_Window *window) {
-#if !SDL_HAS_VULKAN
-	IM_ASSERT(0 && "Unsupported");
-#endif
-	return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForD3D(SDL_Window *window) {
-#if !defined(_WIN32)
-	IM_ASSERT(0 && "Unsupported");
-#endif
-	return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForMetal(SDL_Window *window) {
-	return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window *window, SDL_Renderer *renderer) {
-	return ImGui_ImplSDL2_Init(window, renderer);
-}
-
-bool ImGui_ImplSDL2_InitForOther(SDL_Window *window) {
-	return ImGui_ImplSDL2_Init(window, nullptr);
-}
-
-void ImGui_ImplSDL2_Shutdown() {
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-	IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
-	ImGuiIO &io = ImGui::GetIO();
-
-	if (bd->ClipboardTextData)
-		SDL_free(bd->ClipboardTextData);
-	for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
-		SDL_FreeCursor(bd->MouseCursors[cursor_n]);
-	bd->LastMouseCursor = nullptr;
-
-	io.BackendPlatformName = nullptr;
-	io.BackendPlatformUserData = nullptr;
-	io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
-	IM_DELETE(bd);
-}
-
-static void ImGui_ImplSDL2_UpdateMouseData() {
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-	ImGuiIO &io = ImGui::GetIO();
-
-	// We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below)
-#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
-	// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
-	SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
-	SDL_Window *focused_window = SDL_GetKeyboardFocus();
-	const bool is_app_focused = (bd->Window == focused_window);
-#else
-	const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
-#endif
-	if (is_app_focused) {
-		// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
-		if (io.WantSetMousePos)
-			SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y);
-
-		// (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured)
-		if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0) {
-			int window_x, window_y, mouse_x_global, mouse_y_global;
-			SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
-			SDL_GetWindowPosition(bd->Window, &window_x, &window_y);
-			io.AddMousePosEvent((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y));
-		}
-	}
-}
-
-static void ImGui_ImplSDL2_UpdateMouseCursor() {
-	ImGuiIO &io = ImGui::GetIO();
-	if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
-		return;
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-
-	ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
-	if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) {
-		// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-		SDL_ShowCursor(SDL_FALSE);
-	} else {
-		// Show OS mouse cursor
-		SDL_Cursor *expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
-		if (bd->LastMouseCursor != expected_cursor) {
-			SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
-			bd->LastMouseCursor = expected_cursor;
-		}
-		SDL_ShowCursor(SDL_TRUE);
-	}
-}
-
-static void ImGui_ImplSDL2_UpdateGamepads() {
-	ImGuiIO &io = ImGui::GetIO();
-	if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
-		return;
-
-	// Get gamepad
-	io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
-	SDL_GameController *game_controller = SDL_GameControllerOpen(0);
-	if (!game_controller)
-		return;
-	io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-
-// Update gamepad inputs
-#define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f \
-												   : V)
-#define MAP_BUTTON(KEY_NO, BUTTON_NO) \
-	{ io.AddKeyEvent(KEY_NO, SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0); }
-#define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1)                                                              \
-	{                                                                                                    \
-		float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); \
-		vn = IM_SATURATE(vn);                                                                            \
-		io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn);                                                     \
-	}
-	const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
-	MAP_BUTTON(ImGuiKey_GamepadStart, SDL_CONTROLLER_BUTTON_START);
-	MAP_BUTTON(ImGuiKey_GamepadBack, SDL_CONTROLLER_BUTTON_BACK);
-	MAP_BUTTON(ImGuiKey_GamepadFaceLeft, SDL_CONTROLLER_BUTTON_X);  // Xbox X, PS Square
-	MAP_BUTTON(ImGuiKey_GamepadFaceRight, SDL_CONTROLLER_BUTTON_B); // Xbox B, PS Circle
-	MAP_BUTTON(ImGuiKey_GamepadFaceUp, SDL_CONTROLLER_BUTTON_Y);    // Xbox Y, PS Triangle
-	MAP_BUTTON(ImGuiKey_GamepadFaceDown, SDL_CONTROLLER_BUTTON_A);  // Xbox A, PS Cross
-	MAP_BUTTON(ImGuiKey_GamepadDpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
-	MAP_BUTTON(ImGuiKey_GamepadDpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
-	MAP_BUTTON(ImGuiKey_GamepadDpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP);
-	MAP_BUTTON(ImGuiKey_GamepadDpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
-	MAP_BUTTON(ImGuiKey_GamepadL1, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
-	MAP_BUTTON(ImGuiKey_GamepadR1, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
-	MAP_ANALOG(ImGuiKey_GamepadL2, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 0.0f, 32767);
-	MAP_ANALOG(ImGuiKey_GamepadR2, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767);
-	MAP_BUTTON(ImGuiKey_GamepadL3, SDL_CONTROLLER_BUTTON_LEFTSTICK);
-	MAP_BUTTON(ImGuiKey_GamepadR3, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
-	MAP_ANALOG(ImGuiKey_GamepadLStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768);
-	MAP_ANALOG(ImGuiKey_GamepadLStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767);
-	MAP_ANALOG(ImGuiKey_GamepadLStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32768);
-	MAP_ANALOG(ImGuiKey_GamepadLStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767);
-	MAP_ANALOG(ImGuiKey_GamepadRStickLeft, SDL_CONTROLLER_AXIS_RIGHTX, -thumb_dead_zone, -32768);
-	MAP_ANALOG(ImGuiKey_GamepadRStickRight, SDL_CONTROLLER_AXIS_RIGHTX, +thumb_dead_zone, +32767);
-	MAP_ANALOG(ImGuiKey_GamepadRStickUp, SDL_CONTROLLER_AXIS_RIGHTY, -thumb_dead_zone, -32768);
-	MAP_ANALOG(ImGuiKey_GamepadRStickDown, SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767);
-#undef MAP_BUTTON
-#undef MAP_ANALOG
-}
-
-void ImGui_ImplSDL2_NewFrame() {
-	ImGui_ImplSDL2_Data *bd = ImGui_ImplSDL2_GetBackendData();
-	IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplSDL2_Init()?");
-	ImGuiIO &io = ImGui::GetIO();
-
-	// Setup display size (every frame to accommodate for window resizing)
-	int w, h;
-	int display_w, display_h;
-	SDL_GetWindowSize(bd->Window, &w, &h);
-	if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
-		w = h = 0;
-	if (bd->Renderer != nullptr)
-		SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h);
-	else
-		SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h);
-	io.DisplaySize = ImVec2((float)w, (float)h);
-	if (w > 0 && h > 0)
-		io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
-
-	// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
-	// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
-	static Uint64 frequency = SDL_GetPerformanceFrequency();
-	Uint64 current_time = SDL_GetPerformanceCounter();
-	if (current_time <= bd->Time)
-		current_time = bd->Time + 1;
-	io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
-	bd->Time = current_time;
-
-	if (bd->PendingMouseLeaveFrame && bd->PendingMouseLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) {
-		bd->MouseWindowID = 0;
-		bd->PendingMouseLeaveFrame = 0;
-		io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
-	}
-
-	ImGui_ImplSDL2_UpdateMouseData();
-	ImGui_ImplSDL2_UpdateMouseCursor();
-
-	// Update game controllers (if enabled and available)
-	ImGui_ImplSDL2_UpdateGamepads();
-}
-
-//-----------------------------------------------------------------------------
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#endif // #ifndef IMGUI_DISABLE
diff --git a/graphics/module.mk b/graphics/module.mk
index bf0beffc5d8..1013dfea3e0 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -154,15 +154,5 @@ MODULE_OBJS += \
 	blit/blit-avx2.o
 endif
 
-ifdef USE_IMGUI
-MODULE_OBJS += \
-	imgui/imgui.o \
-	imgui/imgui_draw.o \
-	imgui/imgui_widgets.o \
-	imgui/imgui_tables.o \
-	imgui/backends/imgui_impl_opengl3_scummvm.o \
-	imgui/backends/imgui_impl_sdl2_scummvm.o
-endif
-
 # Include common rules
 include $(srcdir)/rules.mk


Commit: 11ca8850ac3818b6d8d86a9cea834753a8d75168
    https://github.com/scummvm/scummvm/commit/11ca8850ac3818b6d8d86a9cea834753a8d75168
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix retroFonts is not updated when config changed

Changed paths:
    engines/twp/resmanager.cpp
    engines/twp/resmanager.h
    engines/twp/twp.cpp


diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 2ca09d6cdf4..79016ded34c 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -71,6 +71,11 @@ void ResManager::loadSpriteSheet(const Common::String &name) {
 	_spriteSheets[name].parseSpriteSheet(s);
 }
 
+void ResManager::resetSaylineFont() {
+	if(_fonts.contains("sayline"))
+		_fonts.erase("sayline");
+}
+
 void ResManager::loadFont(const Common::String &name) {
 	if (name == "sayline") {
 		debugC(kDebugRes, "Load font %s", name.c_str());
diff --git a/engines/twp/resmanager.h b/engines/twp/resmanager.h
index daa7d7c9698..f26a8a1845f 100644
--- a/engines/twp/resmanager.h
+++ b/engines/twp/resmanager.h
@@ -55,6 +55,7 @@ public:
 	Texture *texture(const Common::String &name);
 	SpriteSheet *spriteSheet(const Common::String &name);
 	Common::SharedPtr<Font> font(const Common::String &name);
+	void resetSaylineFont();
 
 	bool isThread(int id) const;
 	bool isRoom(int id) const;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index d98317c5233..062ed19f969 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -724,6 +724,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 }
 
 void TwpEngine::updateSettingVars() {
+	_resManager->resetSaylineFont();
 	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
 	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
 	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped"));


Commit: f64cb4fad7c2f931209fc7af682c528c61a7d15e
    https://github.com/scummvm/scummvm/commit/f64cb4fad7c2f931209fc7af682c528c61a7d15e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix regression in dialog with \"

Changed paths:
    engines/twp/genlib.cpp
    engines/twp/tsv.cpp
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 08e2014e6cb..90cd6381e4a 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -843,14 +843,6 @@ static SQInteger strlines(HSQUIRRELVM v) {
 	return 1;
 }
 
-static void replaceAll(Common::String &text, const Common::String &search, const Common::String &replace) {
-	auto pos = text.find(search);
-	while (pos != Common::String::npos) {
-		text.replace(pos, search.size(), replace);
-		pos = text.find(search, pos + replace.size());
-	}
-}
-
 static SQInteger strreplace(HSQUIRRELVM v) {
 	const SQChar *input;
 	const SQChar *search;
@@ -867,7 +859,7 @@ static SQInteger strreplace(HSQUIRRELVM v) {
 	Common::String strInput(input);
 	Common::String strSearch(search);
 	Common::String strReplace(replace);
-	replaceAll(strInput, strSearch, strReplace);
+	strInput = replaceAll(strInput, strSearch, strReplace);
 	sq_pushstring(v, strInput.c_str(), strInput.size());
 	return 1;
 }
diff --git a/engines/twp/tsv.cpp b/engines/twp/tsv.cpp
index 679dc9c6422..d94ef7e537d 100644
--- a/engines/twp/tsv.cpp
+++ b/engines/twp/tsv.cpp
@@ -23,6 +23,7 @@
 #include "twp/detection.h"
 #include "twp/squtil.h"
 #include "twp/tsv.h"
+#include "twp/util.h"
 
 namespace Twp {
 
@@ -45,7 +46,7 @@ Common::String TextDb::getText(int id) {
 		if (result.hasSuffix("#M") || result.hasSuffix("#F"))
 			result = result.substr(0, result.size() - 2);
 		// replace \" by ";
-		replace(result, "\\\"", "\"");
+		result = Twp::replaceAll(result, "\\\"", "\"");
 	} else {
 		result = Common::String::format("Text %d not found", id);
 		error("Text %d not found", id);
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index a00b39c1ff8..38bd7dadb12 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -177,6 +177,21 @@ Common::String remove(const Common::String &txt, char startC, char endC) {
 	return txt;
 }
 
+Common::String replaceAll(const Common::String& s, const Common::String& what, const Common::String& by) {
+	Common::String result;
+	uint i = 0;
+	size_t whatSize = what.size();
+	while (true) {
+      uint j = s.find(what, i);
+      if (j == Common::String::npos) break;
+      result += s.substr(i, j - i);
+      result += by;
+      i = j + whatSize;
+	}
+    result += s.substr(i);
+	return result;
+}
+
 void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
 	m(0, 0) *= v.getX();
 	m(1, 1) *= v.getY();
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 4868ad0f120..e6bb3c3e274 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -113,6 +113,7 @@ size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T *o) {
 // string util
 Common::String join(const Common::Array<Common::String> &array, const Common::String &sep);
 Common::String remove(const Common::String &txt, char startC, char endC);
+Common::String replaceAll(const Common::String &s, const Common::String &what, const Common::String &by);
 
 // math util
 void scale(Math::Matrix4 &m, const Math::Vector2d &v);


Commit: 28295c3de85546ea4427953befec8b2aced06ba1
    https://github.com/scummvm/scummvm/commit/28295c3de85546ea4427953befec8b2aced06ba1
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Forbid ScummVM autosave

Changed paths:
    engines/twp/twp.h


diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 1b2fb64aab4..ecd89a3ff90 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -129,6 +129,7 @@ public:
 	Common::Error loadGameState(int slot) override;
 	Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
 	Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
+	bool canSaveAutosaveCurrently() override { return false; }
 	void capture(Common::WriteStream &stream, Math::Vector2d size);
 
 	Math::Vector2d winToScreen(Math::Vector2d pos);


Commit: f9f051be8e1811e63be6eebc37d4a27d8a34f17b
    https://github.com/scummvm/scummvm/commit/f9f051be8e1811e63be6eebc37d4a27d8a34f17b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix fadeOutTimeMs not reset

Changed paths:
    engines/twp/audio.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index 4d4013b7b3d..c7fa5d8e9eb 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -272,6 +272,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 	slot->busy = true;
 	slot->volume = volume;
 	slot->fadeInTimeMs = fadeInTimeMs;
+	slot->fadeOutTimeMs = 0;
 	slot->total = audioStream->getLength().msecs();
 	slot->loopTimes = loopTimes;
 	slot->soundType = cat;
@@ -279,9 +280,9 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 }
 
 int AudioSystem::getElapsed(int id) const {
-	for (const auto &_slot : _slots) {
-		if (_slot.id == id) {
-			Audio::Timestamp t = g_twp->_mixer->getElapsedTime(_slot.handle);
+	for (const auto &slot : _slots) {
+		if (slot.id == id) {
+			Audio::Timestamp t = g_twp->_mixer->getElapsedTime(slot.handle);
 			return t.msecs();
 		}
 	}


Commit: 07dd0846db2d008c198a816410049fa85840d305
    https://github.com/scummvm/scummvm/commit/07dd0846db2d008c198a816410049fa85840d305
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Simplify savegame management

Changed paths:
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index d270e0f0689..86ae01d7858 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -78,12 +78,12 @@ static Common::String getDesc(const Twp::SaveGame &savegame) {
 }
 
 SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
-	Common::String filename = Common::String::format("%s%02d.save", target, slot);
+	Common::String filename = Common::String::format("%s.%03d", target, slot);
 	Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
 
 	if (f) {
 
-		Common::InSaveFile *thumbnailFile = g_system->getSavefileManager()->openForLoading(Common::String::format("%s%02d.png", target, slot));
+		Common::InSaveFile *thumbnailFile = g_system->getSavefileManager()->openForLoading(filename + ".png");
 
 		// Create the return descriptor
 		SaveStateDescriptor desc(this, slot, "?");
@@ -100,11 +100,10 @@ SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int sl
 		}
 		Twp::SaveGame savegame;
 		Twp::SaveGameManager::getSaveGame(f, savegame);
-		Common::String time = getDesc(savegame);
-
-		desc.setDescription(time);
-		desc.setPlayTime(savegame.gameTime * 1000);
+		Common::String savegameDesc(getDesc(savegame));
 		Twp::DateTime dt = Twp::toDateTime(savegame.time);
+		desc.setDescription(savegameDesc);
+		desc.setPlayTime(savegame.gameTime * 1000);
 		desc.setSaveDate(dt.year, dt.month, dt.day);
 		desc.setSaveTime(dt.hour, dt.min);
 
@@ -114,36 +113,6 @@ SaveStateDescriptor TwpMetaEngine::querySaveMetaInfos(const char *target, int sl
 	return SaveStateDescriptor();
 }
 
-SaveStateList TwpMetaEngine::listSaves(const char *target) const {
-	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
-	Common::StringArray filenames;
-	Common::String saveDesc;
-	Common::String pattern = Common::String::format("%s##.save", target);
-
-	filenames = saveFileMan->listSavefiles(pattern);
-
-	SaveStateList saveList;
-	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
-		const char *ext = strrchr(file->c_str(), '.');
-		int slot = ext ? atoi(ext - 2) : -1;
-
-		if (slot >= 0 && slot <= MAX_SAVES) {
-			Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
-
-			if (in) {
-				Twp::SaveGame savegame;
-				Twp::SaveGameManager::getSaveGame(in, savegame);
-				Common::String time = getDesc(savegame);
-				saveList.push_back(SaveStateDescriptor(this, slot, time));
-			}
-		}
-	}
-
-	// Sort saves based on slot number.
-	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
-	return saveList;
-}
-
 GUI::OptionsContainerWidget *TwpMetaEngine::buildEngineOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
 	GUI::OptionsContainerWidget *widget = new Twp::TwpOptionsContainerWidget(boss, name, target);
 	return widget;
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 835db9af58f..4ae11a29673 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -37,7 +37,6 @@ public:
 	 */
 	bool hasFeature(MetaEngineFeature f) const override;
 
-	SaveStateList listSaves(const char *target) const override;
 	int getMaximumSaveSlot() const override;
 
 	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 062ed19f969..03937f4b569 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -968,22 +968,10 @@ Common::Error TwpEngine::loadGameStream(Common::SeekableReadStream *stream) {
 	return Common::kNoError;
 }
 
-Common::String TwpEngine::getSaveStateName(int slot) const {
-	return Common::String::format("twp%02d.save", slot);
-}
-
 bool TwpEngine::canSaveGameStateCurrently(Common::U32String *msg) {
 	return _saveGameManager->_allowSaveGame && !_cutscene;
 }
 
-static Common::String changeFileExt(const Common::String &s, const Common::String &ext) {
-	size_t i = s.findLastOf('.');
-	if (i != Common::String::npos) {
-		return s.substr(0, i) + ext;
-	}
-	return s + ext;
-}
-
 Common::Error TwpEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
 	Common::String name = getSaveStateName(slot);
 	Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(name, false);
@@ -992,7 +980,7 @@ Common::Error TwpEngine::saveGameState(int slot, const Common::String &desc, boo
 
 	Common::Error result = saveGameStream(saveFile, isAutosave);
 	if (result.getCode() == Common::kNoError) {
-		name = changeFileExt(name, ".png");
+		name = name + ".png";
 		Common::OutSaveFile *thumbnail = _saveFileMan->openForSaving(name, false);
 		g_twp->capture(*thumbnail, Math::Vector2d(320, 180));
 		thumbnail->finalize();
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index ecd89a3ff90..59526105f92 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -123,7 +123,6 @@ public:
 	}
 	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
 
-	virtual Common::String getSaveStateName(int slot) const override;
 	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
 
 	Common::Error loadGameState(int slot) override;


Commit: e6e09b2f53c27d88127c6a02956080587ce4bd8c
    https://github.com/scummvm/scummvm/commit/e6e09b2f53c27d88127c6a02956080587ce4bd8c
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Use sys types not GL types

Changed paths:
    engines/twp/gfx.h


diff --git a/engines/twp/gfx.h b/engines/twp/gfx.h
index a01ec3b6958..f9a077aac99 100644
--- a/engines/twp/gfx.h
+++ b/engines/twp/gfx.h
@@ -108,9 +108,9 @@ public:
 	void capture(Common::Array<byte> &data);
 
 public:
-	GLuint id = 0;
+	uint id = 0;
 	int width = 0, height = 0;
-	GLuint fbo = 0;
+	uint fbo = 0;
 };
 
 class RenderTexture : public Texture {
@@ -190,7 +190,7 @@ private:
 
 private:
 	Texture _emptyTexture;
-	GLuint _vbo = 0, _ebo = 0;
+	uint _vbo = 0, _ebo = 0;
 	Shader _defaultShader;
 	Shader *_shader = nullptr;
 	Math::Matrix4 _mvp;
@@ -198,7 +198,7 @@ private:
 	Math::Vector2d _cameraSize;
 	Textures _textures;
 	Texture *_texture = nullptr;
-	GLint _oldFbo = 0;
+	int _oldFbo = 0;
 };
 } // namespace Twp
 


Commit: 5318b11c3996c3191f1d20dc5082ed94f51441d7
    https://github.com/scummvm/scummvm/commit/5318b11c3996c3191f1d20dc5082ed94f51441d7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Show warning if DLC selected but not detected

Changed paths:
    engines/twp/metaengine.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 86ae01d7858..fad70ce3521 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -63,10 +63,11 @@ int TwpMetaEngine::getMaximumSaveSlot() const {
 void TwpMetaEngine::registerDefaultSettings(const Common::String &) const {
 	ConfMan.registerDefault("toiletPaperOver", false);
 	ConfMan.registerDefault("annoyingInJokes", false);
-	ConfMan.registerDefault("invertVerbHighlight", false);
+	ConfMan.registerDefault("invertVerbHighlight", true);
 	ConfMan.registerDefault("retroFonts", false);
 	ConfMan.registerDefault("retroVerbs", false);
 	ConfMan.registerDefault("hudSentence", false);
+	ConfMan.registerDefault("ransomeUnbeeped", false);
 	ConfMan.registerDefault("language", "en");
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 03937f4b569..5349a1c4ea8 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -727,7 +727,10 @@ void TwpEngine::updateSettingVars() {
 	_resManager->resetSaylineFont();
 	sqcall("setSettingVar", "toilet_paper_over", ConfMan.getBool("toiletPaperOver"));
 	sqcall("setSettingVar", "annoying_injokes", ConfMan.getBool("annoyingInJokes"));
-	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped"));
+	if (ConfMan.getBool("ransomeUnbeeped") && _pack->containsDLC()) {
+		warning("You selected Ransome *unbeeped* (DLC) but the DLC has not been detected!");
+	}
+	sqcall("setSettingVar", "ransome_unbeeped", ConfMan.getBool("ransomeUnbeeped") && _pack->containsDLC());
 }
 
 Common::Error TwpEngine::run() {


Commit: 4df291584fe80bbbc19ddf52863e1cf2c4d1a994
    https://github.com/scummvm/scummvm/commit/4df291584fe80bbbc19ddf52863e1cf2c4d1a994
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix regression in notes and phone book

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/object.cpp
    engines/twp/resmanager.cpp
    engines/twp/room.cpp


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 367f7370587..0b444b39b50 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -41,6 +41,7 @@ namespace Twp {
 static struct {
 	bool _showThreads = false;
 	bool _showObjects = false;
+	bool _showObject = false;
 	bool _showStack = false;
 	bool _showAudio = false;
 	bool _showResources = false;
@@ -54,6 +55,7 @@ static struct {
 	bool _fadeToSepia = false;
 	Common::String _textureSelected;
 	int _selectedActor = 0;
+	int _selectedObject = 0;
 } state;
 
 ImVec4 gray(0.6f, 0.6f, 0.6f, 1.f);
@@ -130,7 +132,7 @@ static void drawObjects() {
 	state._objFilter.Draw();
 
 	// show object list
-	for (auto &layer : g_twp->_room->_layers) {
+	for (const auto &layer : g_twp->_room->_layers) {
 		for (auto &obj : layer->_objects) {
 			if (state._objFilter.PassFilter(obj->_key.c_str())) {
 				ImGui::PushID(obj->getId());
@@ -142,7 +144,7 @@ static void drawObjects() {
 				Common::String name = obj->_key.empty() ? obj->getName() : Common::String::format("%s(%s) %d", obj->getName().c_str(), obj->_key.c_str(), obj->getId());
 				bool selected = false;
 				if (ImGui::Selectable(name.c_str(), &selected)) {
-					// gObject = obj;
+					state._selectedObject = obj->getId();
 				}
 				ImGui::PopID();
 			}
@@ -152,6 +154,21 @@ static void drawObjects() {
 	ImGui::End();
 }
 
+static void drawObject() {
+	if (!state._showObject)
+		return;
+
+	Common::SharedPtr<Object> obj(sqobj(state._selectedObject));
+	if (!obj)
+		return;
+
+	Common::String name = obj->_key.empty() ? obj->getName() : Common::String::format("%s(%s) %d", obj->getName().c_str(), obj->_key.c_str(), obj->getId());
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+	ImGui::Begin("Object", &state._showObject);
+	ImGui::Text("Name: %s", name.c_str());
+	ImGui::End();
+}
+
 static Common::String toString(Audio::Mixer::SoundType type) {
 	switch (type) {
 	case Audio::Mixer::kPlainSoundType:
@@ -185,7 +202,7 @@ static void drawActors() {
 		return;
 
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
-	ImGui::Begin("Actors", &state._showStack);
+	ImGui::Begin("Actors", &state._showActor);
 	state._actorFilter.Draw();
 	ImGui::BeginChild("Actor_List");
 	for (auto &actor : g_twp->_actors) {
@@ -292,7 +309,7 @@ static void drawAudio() {
 
 	// count the number of active sounds
 	int count = 0;
-	for (auto &s : g_twp->_audio->_slots) {
+	for (const auto &s : g_twp->_audio->_slots) {
 		if (s.busy)
 			count++;
 	}
@@ -494,6 +511,7 @@ static void drawGeneral() {
 	if (ImGui::CollapsingHeader("Windows")) {
 		ImGui::Checkbox("Threads", &state._showThreads);
 		ImGui::Checkbox("Objects", &state._showObjects);
+		ImGui::Checkbox("Object", &state._showObject);
 		ImGui::Checkbox("Actor", &state._showActor);
 		ImGui::Checkbox("Stack", &state._showStack);
 		ImGui::Checkbox("Audio", &state._showAudio);
@@ -503,9 +521,9 @@ static void drawGeneral() {
 	ImGui::Separator();
 
 	// Room shader
-	const char *RoomEffects = "None\0Sepia\0EGA\0VHS\0Ghost\0Black & White\0\0";
 	if (ImGui::CollapsingHeader("Room Shader")) {
 		int effect = static_cast<int>(room->_effect);
+		const char *RoomEffects = "None\0Sepia\0EGA\0VHS\0Ghost\0Black & White\0\0";
 		if (ImGui::Combo("effect", &effect, RoomEffects))
 			room->_effect = (RoomEffect)effect;
 		ImGui::DragFloat("iFade", &g_twp->_shaderParams->iFade, 0.01f, 0.f, 1.f);
@@ -516,9 +534,9 @@ static void drawGeneral() {
 	}
 
 	// Fade Effects
-	const char *FadeEffects = "None\0In\0Out\0Wobble\0\0";
 	if (ImGui::CollapsingHeader("Fade Shader")) {
 		ImGui::Separator();
+		const char *FadeEffects = "None\0In\0Out\0Wobble\0\0";
 		ImGui::Combo("Fade effect", &state._fadeEffect, FadeEffects);
 		ImGui::DragFloat("Duration", &state._fadeDuration, 0.1f, 0.f, 10.f);
 		ImGui::Checkbox("Fade to sepia", &state._fadeToSepia);
@@ -548,7 +566,7 @@ static void drawNode(Node *node) {
 			if (ImGui::Selectable(node->getName().c_str(), &selected)) {
 				state._node = node;
 			}
-			for (auto &child : children) {
+			for (const auto &child : children) {
 				drawNode(child);
 			}
 			ImGui::TreePop();
@@ -574,7 +592,6 @@ static void drawScenegraph() {
 	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
 	if (state._node != nullptr) {
 		ImGui::Begin("Node");
-		state._node->isVisible();
 		bool visible = state._node->isVisible();
 		if (ImGui::Checkbox(state._node->getName().c_str(), &visible)) {
 			state._node->setVisible(visible);
@@ -602,6 +619,7 @@ void onImGuiRender() {
 	drawGeneral();
 	drawThreads();
 	drawObjects();
+	drawObject();
 	drawStack();
 	drawAudio();
 	drawResources();
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index f92b4be10ee..71a5b7e5652 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -96,6 +96,7 @@ Object::~Object() {
 		if (i != (size_t)-1) {
 			_layer->_objects.remove_at(i);
 		}
+		_layer = nullptr;
 	}
 }
 
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 79016ded34c..6285eb2d8ac 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -172,8 +172,7 @@ int ResManager::newCallbackId() {
 }
 
 void ResManager::resetIds(int callbackId) {
-	// don't reset _roomId and _actorId because there are not dynamically created
-	_objId = START_OBJECTID;
+	// don't reset _roomId, _objId and _actorId because there are not dynamically created
 	_soundDefId = START_SOUNDDEFID;
 	_soundId = START_SOUNDID;
 	_threadId = START_THREADID;
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index 686a6b5ec46..dabb2caa0d2 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -207,7 +207,7 @@ Common::SharedPtr<Object> Room::createTextObject(const Common::String &fontName,
 	Text txt(fontName, text, hAlign, vAlign, maxWidth);
 
 	Common::SharedPtr<TextNode> node(new TextNode());
-	node->setName(obj->_key);
+	node->setName(obj->_name);
 	node->setText(txt);
 	float y = 0.5f;
 	switch (vAlign) {
@@ -313,6 +313,7 @@ void Room::load(Common::SharedPtr<Room> room, Common::SeekableReadStream &s) {
 			Common::SharedPtr<Object> obj(new Object());
 			Twp::setId(obj->_table, g_twp->_resManager->newObjId());
 			obj->_key = jObject["name"]->asString();
+			obj->_node->setName(obj->_key.c_str());
 			obj->_node->setPos(Math::Vector2d(parseVec2(jObject["pos"]->asString())));
 			obj->_node->setZSort(jObject["zsort"]->asIntegerNumber());
 			obj->_usePos = parseVec2(jObject["usepos"]->asString());


Commit: 100c996ce5181b6e5010b72d289c53c6aae6252f
    https://github.com/scummvm/scummvm/commit/100c996ce5181b6e5010b72d289c53c6aae6252f
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Move ImGui to backends

Changed paths:
    backends/events/sdl/sdl-events.cpp


diff --git a/backends/events/sdl/sdl-events.cpp b/backends/events/sdl/sdl-events.cpp
index 65db35997a5..694fe39fed0 100644
--- a/backends/events/sdl/sdl-events.cpp
+++ b/backends/events/sdl/sdl-events.cpp
@@ -78,10 +78,9 @@ SdlEventSource::SdlEventSource()
 	: EventSource(), _scrollLock(false), _joystick(nullptr), _lastScreenID(0), _graphicsManager(nullptr), _queuedFakeMouseMove(false),
 	  _lastHatPosition(SDL_HAT_CENTERED), _mouseX(0), _mouseY(0), _engineRunning(false)
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	  ,
-	  _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr)
+	  , _queuedFakeKeyUp(false), _fakeKeyUp(), _controller(nullptr)
 #endif
-{
+	  {
 	int joystick_num = ConfMan.getInt("joystick_num");
 	if (joystick_num >= 0) {
 		// Initialize SDL joystick subsystem
@@ -215,383 +214,198 @@ void SdlEventSource::SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &even
 
 Common::KeyCode SdlEventSource::SDLToOSystemKeycode(const SDL_Keycode key) {
 	switch (key) {
-	case SDLK_BACKSPACE:
-		return Common::KEYCODE_BACKSPACE;
-	case SDLK_TAB:
-		return Common::KEYCODE_TAB;
-	case SDLK_CLEAR:
-		return Common::KEYCODE_CLEAR;
-	case SDLK_RETURN:
-		return Common::KEYCODE_RETURN;
-	case SDLK_PAUSE:
-		return Common::KEYCODE_PAUSE;
-	case SDLK_ESCAPE:
-		return Common::KEYCODE_ESCAPE;
-	case SDLK_SPACE:
-		return Common::KEYCODE_SPACE;
-	case SDLK_EXCLAIM:
-		return Common::KEYCODE_EXCLAIM;
-	case SDLK_QUOTEDBL:
-		return Common::KEYCODE_QUOTEDBL;
-	case SDLK_HASH:
-		return Common::KEYCODE_HASH;
-	case SDLK_DOLLAR:
-		return Common::KEYCODE_DOLLAR;
-	case SDLK_AMPERSAND:
-		return Common::KEYCODE_AMPERSAND;
-	case SDLK_QUOTE:
-		return Common::KEYCODE_QUOTE;
-	case SDLK_LEFTPAREN:
-		return Common::KEYCODE_LEFTPAREN;
-	case SDLK_RIGHTPAREN:
-		return Common::KEYCODE_RIGHTPAREN;
-	case SDLK_ASTERISK:
-		return Common::KEYCODE_ASTERISK;
-	case SDLK_PLUS:
-		return Common::KEYCODE_PLUS;
-	case SDLK_COMMA:
-		return Common::KEYCODE_COMMA;
-	case SDLK_MINUS:
-		return Common::KEYCODE_MINUS;
-	case SDLK_PERIOD:
-		return Common::KEYCODE_PERIOD;
-	case SDLK_SLASH:
-		return Common::KEYCODE_SLASH;
-	case SDLK_0:
-		return Common::KEYCODE_0;
-	case SDLK_1:
-		return Common::KEYCODE_1;
-	case SDLK_2:
-		return Common::KEYCODE_2;
-	case SDLK_3:
-		return Common::KEYCODE_3;
-	case SDLK_4:
-		return Common::KEYCODE_4;
-	case SDLK_5:
-		return Common::KEYCODE_5;
-	case SDLK_6:
-		return Common::KEYCODE_6;
-	case SDLK_7:
-		return Common::KEYCODE_7;
-	case SDLK_8:
-		return Common::KEYCODE_8;
-	case SDLK_9:
-		return Common::KEYCODE_9;
-	case SDLK_COLON:
-		return Common::KEYCODE_COLON;
-	case SDLK_SEMICOLON:
-		return Common::KEYCODE_SEMICOLON;
-	case SDLK_LESS:
-		return Common::KEYCODE_LESS;
-	case SDLK_EQUALS:
-		return Common::KEYCODE_EQUALS;
-	case SDLK_GREATER:
-		return Common::KEYCODE_GREATER;
-	case SDLK_QUESTION:
-		return Common::KEYCODE_QUESTION;
-	case SDLK_AT:
-		return Common::KEYCODE_AT;
-	case SDLK_LEFTBRACKET:
-		return Common::KEYCODE_LEFTBRACKET;
-	case SDLK_BACKSLASH:
-		return Common::KEYCODE_BACKSLASH;
-	case SDLK_RIGHTBRACKET:
-		return Common::KEYCODE_RIGHTBRACKET;
-	case SDLK_CARET:
-		return Common::KEYCODE_CARET;
-	case SDLK_UNDERSCORE:
-		return Common::KEYCODE_UNDERSCORE;
-	case SDLK_BACKQUOTE:
-		return Common::KEYCODE_BACKQUOTE;
-	case SDLK_a:
-		return Common::KEYCODE_a;
-	case SDLK_b:
-		return Common::KEYCODE_b;
-	case SDLK_c:
-		return Common::KEYCODE_c;
-	case SDLK_d:
-		return Common::KEYCODE_d;
-	case SDLK_e:
-		return Common::KEYCODE_e;
-	case SDLK_f:
-		return Common::KEYCODE_f;
-	case SDLK_g:
-		return Common::KEYCODE_g;
-	case SDLK_h:
-		return Common::KEYCODE_h;
-	case SDLK_i:
-		return Common::KEYCODE_i;
-	case SDLK_j:
-		return Common::KEYCODE_j;
-	case SDLK_k:
-		return Common::KEYCODE_k;
-	case SDLK_l:
-		return Common::KEYCODE_l;
-	case SDLK_m:
-		return Common::KEYCODE_m;
-	case SDLK_n:
-		return Common::KEYCODE_n;
-	case SDLK_o:
-		return Common::KEYCODE_o;
-	case SDLK_p:
-		return Common::KEYCODE_p;
-	case SDLK_q:
-		return Common::KEYCODE_q;
-	case SDLK_r:
-		return Common::KEYCODE_r;
-	case SDLK_s:
-		return Common::KEYCODE_s;
-	case SDLK_t:
-		return Common::KEYCODE_t;
-	case SDLK_u:
-		return Common::KEYCODE_u;
-	case SDLK_v:
-		return Common::KEYCODE_v;
-	case SDLK_w:
-		return Common::KEYCODE_w;
-	case SDLK_x:
-		return Common::KEYCODE_x;
-	case SDLK_y:
-		return Common::KEYCODE_y;
-	case SDLK_z:
-		return Common::KEYCODE_z;
-	case SDLK_DELETE:
-		return Common::KEYCODE_DELETE;
-	case SDLK_KP_PERIOD:
-		return Common::KEYCODE_KP_PERIOD;
-	case SDLK_KP_DIVIDE:
-		return Common::KEYCODE_KP_DIVIDE;
-	case SDLK_KP_MULTIPLY:
-		return Common::KEYCODE_KP_MULTIPLY;
-	case SDLK_KP_MINUS:
-		return Common::KEYCODE_KP_MINUS;
-	case SDLK_KP_PLUS:
-		return Common::KEYCODE_KP_PLUS;
-	case SDLK_KP_ENTER:
-		return Common::KEYCODE_KP_ENTER;
-	case SDLK_KP_EQUALS:
-		return Common::KEYCODE_KP_EQUALS;
-	case SDLK_UP:
-		return Common::KEYCODE_UP;
-	case SDLK_DOWN:
-		return Common::KEYCODE_DOWN;
-	case SDLK_RIGHT:
-		return Common::KEYCODE_RIGHT;
-	case SDLK_LEFT:
-		return Common::KEYCODE_LEFT;
-	case SDLK_INSERT:
-		return Common::KEYCODE_INSERT;
-	case SDLK_HOME:
-		return Common::KEYCODE_HOME;
-	case SDLK_END:
-		return Common::KEYCODE_END;
-	case SDLK_PAGEUP:
-		return Common::KEYCODE_PAGEUP;
-	case SDLK_PAGEDOWN:
-		return Common::KEYCODE_PAGEDOWN;
-	case SDLK_F1:
-		return Common::KEYCODE_F1;
-	case SDLK_F2:
-		return Common::KEYCODE_F2;
-	case SDLK_F3:
-		return Common::KEYCODE_F3;
-	case SDLK_F4:
-		return Common::KEYCODE_F4;
-	case SDLK_F5:
-		return Common::KEYCODE_F5;
-	case SDLK_F6:
-		return Common::KEYCODE_F6;
-	case SDLK_F7:
-		return Common::KEYCODE_F7;
-	case SDLK_F8:
-		return Common::KEYCODE_F8;
-	case SDLK_F9:
-		return Common::KEYCODE_F9;
-	case SDLK_F10:
-		return Common::KEYCODE_F10;
-	case SDLK_F11:
-		return Common::KEYCODE_F11;
-	case SDLK_F12:
-		return Common::KEYCODE_F12;
-	case SDLK_F13:
-		return Common::KEYCODE_F13;
-	case SDLK_F14:
-		return Common::KEYCODE_F14;
-	case SDLK_F15:
-		return Common::KEYCODE_F15;
-	case SDLK_CAPSLOCK:
-		return Common::KEYCODE_CAPSLOCK;
-	case SDLK_RSHIFT:
-		return Common::KEYCODE_RSHIFT;
-	case SDLK_LSHIFT:
-		return Common::KEYCODE_LSHIFT;
-	case SDLK_RCTRL:
-		return Common::KEYCODE_RCTRL;
-	case SDLK_LCTRL:
-		return Common::KEYCODE_LCTRL;
-	case SDLK_RALT:
-		return Common::KEYCODE_RALT;
-	case SDLK_LALT:
-		return Common::KEYCODE_LALT;
-	case SDLK_MODE:
-		return Common::KEYCODE_MODE;
-	case SDLK_HELP:
-		return Common::KEYCODE_HELP;
-	case SDLK_SYSREQ:
-		return Common::KEYCODE_SYSREQ;
-	case SDLK_MENU:
-		return Common::KEYCODE_MENU;
-	case SDLK_POWER:
-		return Common::KEYCODE_POWER;
+	case SDLK_BACKSPACE: return Common::KEYCODE_BACKSPACE;
+	case SDLK_TAB: return Common::KEYCODE_TAB;
+	case SDLK_CLEAR: return Common::KEYCODE_CLEAR;
+	case SDLK_RETURN: return Common::KEYCODE_RETURN;
+	case SDLK_PAUSE: return Common::KEYCODE_PAUSE;
+	case SDLK_ESCAPE: return Common::KEYCODE_ESCAPE;
+	case SDLK_SPACE: return Common::KEYCODE_SPACE;
+	case SDLK_EXCLAIM: return Common::KEYCODE_EXCLAIM;
+	case SDLK_QUOTEDBL: return Common::KEYCODE_QUOTEDBL;
+	case SDLK_HASH: return Common::KEYCODE_HASH;
+	case SDLK_DOLLAR: return Common::KEYCODE_DOLLAR;
+	case SDLK_AMPERSAND: return Common::KEYCODE_AMPERSAND;
+	case SDLK_QUOTE: return Common::KEYCODE_QUOTE;
+	case SDLK_LEFTPAREN: return Common::KEYCODE_LEFTPAREN;
+	case SDLK_RIGHTPAREN: return Common::KEYCODE_RIGHTPAREN;
+	case SDLK_ASTERISK: return Common::KEYCODE_ASTERISK;
+	case SDLK_PLUS: return Common::KEYCODE_PLUS;
+	case SDLK_COMMA: return Common::KEYCODE_COMMA;
+	case SDLK_MINUS: return Common::KEYCODE_MINUS;
+	case SDLK_PERIOD: return Common::KEYCODE_PERIOD;
+	case SDLK_SLASH: return Common::KEYCODE_SLASH;
+	case SDLK_0: return Common::KEYCODE_0;
+	case SDLK_1: return Common::KEYCODE_1;
+	case SDLK_2: return Common::KEYCODE_2;
+	case SDLK_3: return Common::KEYCODE_3;
+	case SDLK_4: return Common::KEYCODE_4;
+	case SDLK_5: return Common::KEYCODE_5;
+	case SDLK_6: return Common::KEYCODE_6;
+	case SDLK_7: return Common::KEYCODE_7;
+	case SDLK_8: return Common::KEYCODE_8;
+	case SDLK_9: return Common::KEYCODE_9;
+	case SDLK_COLON: return Common::KEYCODE_COLON;
+	case SDLK_SEMICOLON: return Common::KEYCODE_SEMICOLON;
+	case SDLK_LESS: return Common::KEYCODE_LESS;
+	case SDLK_EQUALS: return Common::KEYCODE_EQUALS;
+	case SDLK_GREATER: return Common::KEYCODE_GREATER;
+	case SDLK_QUESTION: return Common::KEYCODE_QUESTION;
+	case SDLK_AT: return Common::KEYCODE_AT;
+	case SDLK_LEFTBRACKET: return Common::KEYCODE_LEFTBRACKET;
+	case SDLK_BACKSLASH: return Common::KEYCODE_BACKSLASH;
+	case SDLK_RIGHTBRACKET: return Common::KEYCODE_RIGHTBRACKET;
+	case SDLK_CARET: return Common::KEYCODE_CARET;
+	case SDLK_UNDERSCORE: return Common::KEYCODE_UNDERSCORE;
+	case SDLK_BACKQUOTE: return Common::KEYCODE_BACKQUOTE;
+	case SDLK_a: return Common::KEYCODE_a;
+	case SDLK_b: return Common::KEYCODE_b;
+	case SDLK_c: return Common::KEYCODE_c;
+	case SDLK_d: return Common::KEYCODE_d;
+	case SDLK_e: return Common::KEYCODE_e;
+	case SDLK_f: return Common::KEYCODE_f;
+	case SDLK_g: return Common::KEYCODE_g;
+	case SDLK_h: return Common::KEYCODE_h;
+	case SDLK_i: return Common::KEYCODE_i;
+	case SDLK_j: return Common::KEYCODE_j;
+	case SDLK_k: return Common::KEYCODE_k;
+	case SDLK_l: return Common::KEYCODE_l;
+	case SDLK_m: return Common::KEYCODE_m;
+	case SDLK_n: return Common::KEYCODE_n;
+	case SDLK_o: return Common::KEYCODE_o;
+	case SDLK_p: return Common::KEYCODE_p;
+	case SDLK_q: return Common::KEYCODE_q;
+	case SDLK_r: return Common::KEYCODE_r;
+	case SDLK_s: return Common::KEYCODE_s;
+	case SDLK_t: return Common::KEYCODE_t;
+	case SDLK_u: return Common::KEYCODE_u;
+	case SDLK_v: return Common::KEYCODE_v;
+	case SDLK_w: return Common::KEYCODE_w;
+	case SDLK_x: return Common::KEYCODE_x;
+	case SDLK_y: return Common::KEYCODE_y;
+	case SDLK_z: return Common::KEYCODE_z;
+	case SDLK_DELETE: return Common::KEYCODE_DELETE;
+	case SDLK_KP_PERIOD: return Common::KEYCODE_KP_PERIOD;
+	case SDLK_KP_DIVIDE: return Common::KEYCODE_KP_DIVIDE;
+	case SDLK_KP_MULTIPLY: return Common::KEYCODE_KP_MULTIPLY;
+	case SDLK_KP_MINUS: return Common::KEYCODE_KP_MINUS;
+	case SDLK_KP_PLUS: return Common::KEYCODE_KP_PLUS;
+	case SDLK_KP_ENTER: return Common::KEYCODE_KP_ENTER;
+	case SDLK_KP_EQUALS: return Common::KEYCODE_KP_EQUALS;
+	case SDLK_UP: return Common::KEYCODE_UP;
+	case SDLK_DOWN: return Common::KEYCODE_DOWN;
+	case SDLK_RIGHT: return Common::KEYCODE_RIGHT;
+	case SDLK_LEFT: return Common::KEYCODE_LEFT;
+	case SDLK_INSERT: return Common::KEYCODE_INSERT;
+	case SDLK_HOME: return Common::KEYCODE_HOME;
+	case SDLK_END: return Common::KEYCODE_END;
+	case SDLK_PAGEUP: return Common::KEYCODE_PAGEUP;
+	case SDLK_PAGEDOWN: return Common::KEYCODE_PAGEDOWN;
+	case SDLK_F1: return Common::KEYCODE_F1;
+	case SDLK_F2: return Common::KEYCODE_F2;
+	case SDLK_F3: return Common::KEYCODE_F3;
+	case SDLK_F4: return Common::KEYCODE_F4;
+	case SDLK_F5: return Common::KEYCODE_F5;
+	case SDLK_F6: return Common::KEYCODE_F6;
+	case SDLK_F7: return Common::KEYCODE_F7;
+	case SDLK_F8: return Common::KEYCODE_F8;
+	case SDLK_F9: return Common::KEYCODE_F9;
+	case SDLK_F10: return Common::KEYCODE_F10;
+	case SDLK_F11: return Common::KEYCODE_F11;
+	case SDLK_F12: return Common::KEYCODE_F12;
+	case SDLK_F13: return Common::KEYCODE_F13;
+	case SDLK_F14: return Common::KEYCODE_F14;
+	case SDLK_F15: return Common::KEYCODE_F15;
+	case SDLK_CAPSLOCK: return Common::KEYCODE_CAPSLOCK;
+	case SDLK_RSHIFT: return Common::KEYCODE_RSHIFT;
+	case SDLK_LSHIFT: return Common::KEYCODE_LSHIFT;
+	case SDLK_RCTRL: return Common::KEYCODE_RCTRL;
+	case SDLK_LCTRL: return Common::KEYCODE_LCTRL;
+	case SDLK_RALT: return Common::KEYCODE_RALT;
+	case SDLK_LALT: return Common::KEYCODE_LALT;
+	case SDLK_MODE: return Common::KEYCODE_MODE;
+	case SDLK_HELP: return Common::KEYCODE_HELP;
+	case SDLK_SYSREQ: return Common::KEYCODE_SYSREQ;
+	case SDLK_MENU: return Common::KEYCODE_MENU;
+	case SDLK_POWER: return Common::KEYCODE_POWER;
 #if SDL_VERSION_ATLEAST(1, 2, 3)
-	case SDLK_UNDO:
-		return Common::KEYCODE_UNDO;
+	case SDLK_UNDO: return Common::KEYCODE_UNDO;
 #endif
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-	case SDLK_SCROLLLOCK:
-		return Common::KEYCODE_SCROLLOCK;
-	case SDLK_NUMLOCKCLEAR:
-		return Common::KEYCODE_NUMLOCK;
-	case SDLK_LGUI:
-		return Common::KEYCODE_LSUPER;
-	case SDLK_RGUI:
-		return Common::KEYCODE_RSUPER;
-	case SDLK_PRINTSCREEN:
-		return Common::KEYCODE_PRINT;
-	case SDLK_APPLICATION:
-		return Common::KEYCODE_COMPOSE;
-	case SDLK_KP_0:
-		return Common::KEYCODE_KP0;
-	case SDLK_KP_1:
-		return Common::KEYCODE_KP1;
-	case SDLK_KP_2:
-		return Common::KEYCODE_KP2;
-	case SDLK_KP_3:
-		return Common::KEYCODE_KP3;
-	case SDLK_KP_4:
-		return Common::KEYCODE_KP4;
-	case SDLK_KP_5:
-		return Common::KEYCODE_KP5;
-	case SDLK_KP_6:
-		return Common::KEYCODE_KP6;
-	case SDLK_KP_7:
-		return Common::KEYCODE_KP7;
-	case SDLK_KP_8:
-		return Common::KEYCODE_KP8;
-	case SDLK_KP_9:
-		return Common::KEYCODE_KP9;
-	case SDLK_PERCENT:
-		return Common::KEYCODE_PERCENT;
-	case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE):
-		return Common::KEYCODE_TILDE;
-	case SDLK_F16:
-		return Common::KEYCODE_F16;
-	case SDLK_F17:
-		return Common::KEYCODE_F17;
-	case SDLK_F18:
-		return Common::KEYCODE_F18;
-	case SDLK_SLEEP:
-		return Common::KEYCODE_SLEEP;
-	case SDLK_MUTE:
-		return Common::KEYCODE_MUTE;
-	case SDLK_VOLUMEUP:
-		return Common::KEYCODE_VOLUMEUP;
-	case SDLK_VOLUMEDOWN:
-		return Common::KEYCODE_VOLUMEDOWN;
-	case SDLK_EJECT:
-		return Common::KEYCODE_EJECT;
-	case SDLK_WWW:
-		return Common::KEYCODE_WWW;
-	case SDLK_MAIL:
-		return Common::KEYCODE_MAIL;
-	case SDLK_CALCULATOR:
-		return Common::KEYCODE_CALCULATOR;
-	case SDLK_CUT:
-		return Common::KEYCODE_CUT;
-	case SDLK_COPY:
-		return Common::KEYCODE_COPY;
-	case SDLK_PASTE:
-		return Common::KEYCODE_PASTE;
-	case SDLK_SELECT:
-		return Common::KEYCODE_SELECT;
-	case SDLK_CANCEL:
-		return Common::KEYCODE_CANCEL;
-	case SDLK_AC_SEARCH:
-		return Common::KEYCODE_AC_SEARCH;
-	case SDLK_AC_HOME:
-		return Common::KEYCODE_AC_HOME;
-	case SDLK_AC_BACK:
-		return Common::KEYCODE_AC_BACK;
-	case SDLK_AC_FORWARD:
-		return Common::KEYCODE_AC_FORWARD;
-	case SDLK_AC_STOP:
-		return Common::KEYCODE_AC_STOP;
-	case SDLK_AC_REFRESH:
-		return Common::KEYCODE_AC_REFRESH;
-	case SDLK_AC_BOOKMARKS:
-		return Common::KEYCODE_AC_BOOKMARKS;
-	case SDLK_AUDIONEXT:
-		return Common::KEYCODE_AUDIONEXT;
-	case SDLK_AUDIOPREV:
-		return Common::KEYCODE_AUDIOPREV;
-	case SDLK_AUDIOSTOP:
-		return Common::KEYCODE_AUDIOSTOP;
-	case SDLK_AUDIOPLAY:
-		return Common::KEYCODE_AUDIOPLAYPAUSE;
-	case SDLK_AUDIOMUTE:
-		return Common::KEYCODE_AUDIOMUTE;
+	case SDLK_SCROLLLOCK: return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCKCLEAR: return Common::KEYCODE_NUMLOCK;
+	case SDLK_LGUI: return Common::KEYCODE_LSUPER;
+	case SDLK_RGUI: return Common::KEYCODE_RSUPER;
+	case SDLK_PRINTSCREEN: return Common::KEYCODE_PRINT;
+	case SDLK_APPLICATION: return Common::KEYCODE_COMPOSE;
+	case SDLK_KP_0: return Common::KEYCODE_KP0;
+	case SDLK_KP_1: return Common::KEYCODE_KP1;
+	case SDLK_KP_2: return Common::KEYCODE_KP2;
+	case SDLK_KP_3: return Common::KEYCODE_KP3;
+	case SDLK_KP_4: return Common::KEYCODE_KP4;
+	case SDLK_KP_5: return Common::KEYCODE_KP5;
+	case SDLK_KP_6: return Common::KEYCODE_KP6;
+	case SDLK_KP_7: return Common::KEYCODE_KP7;
+	case SDLK_KP_8: return Common::KEYCODE_KP8;
+	case SDLK_KP_9: return Common::KEYCODE_KP9;
+	case SDLK_PERCENT: return Common::KEYCODE_PERCENT;
+	case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_GRAVE): return Common::KEYCODE_TILDE;
+	case SDLK_F16: return Common::KEYCODE_F16;
+	case SDLK_F17: return Common::KEYCODE_F17;
+	case SDLK_F18: return Common::KEYCODE_F18;
+	case SDLK_SLEEP: return Common::KEYCODE_SLEEP;
+	case SDLK_MUTE: return Common::KEYCODE_MUTE;
+	case SDLK_VOLUMEUP: return Common::KEYCODE_VOLUMEUP;
+	case SDLK_VOLUMEDOWN: return Common::KEYCODE_VOLUMEDOWN;
+	case SDLK_EJECT: return Common::KEYCODE_EJECT;
+	case SDLK_WWW: return Common::KEYCODE_WWW;
+	case SDLK_MAIL: return Common::KEYCODE_MAIL;
+	case SDLK_CALCULATOR: return Common::KEYCODE_CALCULATOR;
+	case SDLK_CUT: return Common::KEYCODE_CUT;
+	case SDLK_COPY: return Common::KEYCODE_COPY;
+	case SDLK_PASTE: return Common::KEYCODE_PASTE;
+	case SDLK_SELECT: return Common::KEYCODE_SELECT;
+	case SDLK_CANCEL: return Common::KEYCODE_CANCEL;
+	case SDLK_AC_SEARCH: return Common::KEYCODE_AC_SEARCH;
+	case SDLK_AC_HOME: return Common::KEYCODE_AC_HOME;
+	case SDLK_AC_BACK: return Common::KEYCODE_AC_BACK;
+	case SDLK_AC_FORWARD: return Common::KEYCODE_AC_FORWARD;
+	case SDLK_AC_STOP: return Common::KEYCODE_AC_STOP;
+	case SDLK_AC_REFRESH: return Common::KEYCODE_AC_REFRESH;
+	case SDLK_AC_BOOKMARKS: return Common::KEYCODE_AC_BOOKMARKS;
+	case SDLK_AUDIONEXT: return Common::KEYCODE_AUDIONEXT;
+	case SDLK_AUDIOPREV: return Common::KEYCODE_AUDIOPREV;
+	case SDLK_AUDIOSTOP: return Common::KEYCODE_AUDIOSTOP;
+	case SDLK_AUDIOPLAY: return Common::KEYCODE_AUDIOPLAYPAUSE;
+	case SDLK_AUDIOMUTE: return Common::KEYCODE_AUDIOMUTE;
 #if SDL_VERSION_ATLEAST(2, 0, 6)
-	case SDLK_AUDIOREWIND:
-		return Common::KEYCODE_AUDIOREWIND;
-	case SDLK_AUDIOFASTFORWARD:
-		return Common::KEYCODE_AUDIOFASTFORWARD;
+	case SDLK_AUDIOREWIND: return Common::KEYCODE_AUDIOREWIND;
+	case SDLK_AUDIOFASTFORWARD: return Common::KEYCODE_AUDIOFASTFORWARD;
 #endif
 #else
-	case SDLK_SCROLLOCK:
-		return Common::KEYCODE_SCROLLOCK;
-	case SDLK_NUMLOCK:
-		return Common::KEYCODE_NUMLOCK;
-	case SDLK_LSUPER:
-		return Common::KEYCODE_LSUPER;
-	case SDLK_RSUPER:
-		return Common::KEYCODE_RSUPER;
-	case SDLK_PRINT:
-		return Common::KEYCODE_PRINT;
-	case SDLK_COMPOSE:
-		return Common::KEYCODE_COMPOSE;
-	case SDLK_KP0:
-		return Common::KEYCODE_KP0;
-	case SDLK_KP1:
-		return Common::KEYCODE_KP1;
-	case SDLK_KP2:
-		return Common::KEYCODE_KP2;
-	case SDLK_KP3:
-		return Common::KEYCODE_KP3;
-	case SDLK_KP4:
-		return Common::KEYCODE_KP4;
-	case SDLK_KP5:
-		return Common::KEYCODE_KP5;
-	case SDLK_KP6:
-		return Common::KEYCODE_KP6;
-	case SDLK_KP7:
-		return Common::KEYCODE_KP7;
-	case SDLK_KP8:
-		return Common::KEYCODE_KP8;
-	case SDLK_KP9:
-		return Common::KEYCODE_KP9;
-	case SDLK_WORLD_16:
-		return Common::KEYCODE_TILDE;
-	case SDLK_BREAK:
-		return Common::KEYCODE_BREAK;
-	case SDLK_LMETA:
-		return Common::KEYCODE_LMETA;
-	case SDLK_RMETA:
-		return Common::KEYCODE_RMETA;
-	case SDLK_EURO:
-		return Common::KEYCODE_EURO;
+	case SDLK_SCROLLOCK: return Common::KEYCODE_SCROLLOCK;
+	case SDLK_NUMLOCK: return Common::KEYCODE_NUMLOCK;
+	case SDLK_LSUPER: return Common::KEYCODE_LSUPER;
+	case SDLK_RSUPER: return Common::KEYCODE_RSUPER;
+	case SDLK_PRINT: return Common::KEYCODE_PRINT;
+	case SDLK_COMPOSE: return Common::KEYCODE_COMPOSE;
+	case SDLK_KP0: return Common::KEYCODE_KP0;
+	case SDLK_KP1: return Common::KEYCODE_KP1;
+	case SDLK_KP2: return Common::KEYCODE_KP2;
+	case SDLK_KP3: return Common::KEYCODE_KP3;
+	case SDLK_KP4: return Common::KEYCODE_KP4;
+	case SDLK_KP5: return Common::KEYCODE_KP5;
+	case SDLK_KP6: return Common::KEYCODE_KP6;
+	case SDLK_KP7: return Common::KEYCODE_KP7;
+	case SDLK_KP8: return Common::KEYCODE_KP8;
+	case SDLK_KP9: return Common::KEYCODE_KP9;
+	case SDLK_WORLD_16: return Common::KEYCODE_TILDE;
+	case SDLK_BREAK: return Common::KEYCODE_BREAK;
+	case SDLK_LMETA: return Common::KEYCODE_LMETA;
+	case SDLK_RMETA: return Common::KEYCODE_RMETA;
+	case SDLK_EURO: return Common::KEYCODE_EURO;
 #endif
-	default:
-		return Common::KEYCODE_INVALID;
+	default: return Common::KEYCODE_INVALID;
 	}
 }
 
@@ -672,7 +486,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		} else {
 			return false;
 		}
-	}
+		}
 
 	case SDL_TEXTINPUT: {
 		// When we get a TEXTINPUT event it means we got some user input for
@@ -693,7 +507,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		_fakeKeyUp.type = Common::EVENT_KEYUP;
 
 		return _queuedFakeKeyUp;
-	}
+		}
 
 	case SDL_WINDOWEVENT:
 		// We're only interested in events from the current display window
@@ -722,7 +536,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 		// However if the documentation is correct we can ignore SDL_WINDOWEVENT_RESIZED since when we
 		// get one we should always get a SDL_WINDOWEVENT_SIZE_CHANGED as well.
 		case SDL_WINDOWEVENT_SIZE_CHANGED:
-			// case SDL_WINDOWEVENT_RESIZED:
+		//case SDL_WINDOWEVENT_RESIZED:
 			return handleResizeEvent(event, ev.window.data1, ev.window.data2);
 
 		case SDL_WINDOWEVENT_FOCUS_GAINED: {
@@ -815,6 +629,7 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
 	return false;
 }
 
+
 bool SdlEventSource::handleKeyDown(SDL_Event &ev, Common::Event &event) {
 
 	SDLModToOSystemKeyFlags(SDL_GetModState(), event);
@@ -982,16 +797,17 @@ void SdlEventSource::closeJoystick() {
 
 int SdlEventSource::mapSDLJoystickButtonToOSystem(Uint8 sdlButton) {
 	Common::JoystickButton osystemButtons[] = {
-		Common::JOYSTICK_BUTTON_A,
-		Common::JOYSTICK_BUTTON_B,
-		Common::JOYSTICK_BUTTON_X,
-		Common::JOYSTICK_BUTTON_Y,
-		Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
-		Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
-		Common::JOYSTICK_BUTTON_BACK,
-		Common::JOYSTICK_BUTTON_START,
-		Common::JOYSTICK_BUTTON_LEFT_STICK,
-		Common::JOYSTICK_BUTTON_RIGHT_STICK};
+	    Common::JOYSTICK_BUTTON_A,
+	    Common::JOYSTICK_BUTTON_B,
+	    Common::JOYSTICK_BUTTON_X,
+	    Common::JOYSTICK_BUTTON_Y,
+	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_BACK,
+	    Common::JOYSTICK_BUTTON_START,
+	    Common::JOYSTICK_BUTTON_LEFT_STICK,
+	    Common::JOYSTICK_BUTTON_RIGHT_STICK
+	};
 
 	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
 		return -1;
@@ -1032,15 +848,15 @@ bool SdlEventSource::handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) {
 	return true;
 }
 
-#define HANDLE_HAT_UP(new, old, mask, joybutton)       \
-	if ((old & mask) && !(new &mask)) {                \
-		event.joystick.button = joybutton;             \
+#define HANDLE_HAT_UP(new, old, mask, joybutton) \
+	if ((old & mask) && !(new & mask)) { \
+		event.joystick.button = joybutton; \
 		g_system->getEventManager()->pushEvent(event); \
 	}
 
-#define HANDLE_HAT_DOWN(new, old, mask, joybutton)     \
-	if ((new &mask) && !(old & mask)) {                \
-		event.joystick.button = joybutton;             \
+#define HANDLE_HAT_DOWN(new, old, mask, joybutton) \
+	if ((new & mask) && !(old & mask)) { \
+		event.joystick.button = joybutton; \
 		g_system->getEventManager()->pushEvent(event); \
 	}
 
@@ -1108,21 +924,22 @@ bool SdlEventSource::handleJoystickRemoved(const SDL_JoyDeviceEvent &device, Com
 
 int SdlEventSource::mapSDLControllerButtonToOSystem(Uint8 sdlButton) {
 	Common::JoystickButton osystemButtons[] = {
-		Common::JOYSTICK_BUTTON_A,
-		Common::JOYSTICK_BUTTON_B,
-		Common::JOYSTICK_BUTTON_X,
-		Common::JOYSTICK_BUTTON_Y,
-		Common::JOYSTICK_BUTTON_BACK,
-		Common::JOYSTICK_BUTTON_GUIDE,
-		Common::JOYSTICK_BUTTON_START,
-		Common::JOYSTICK_BUTTON_LEFT_STICK,
-		Common::JOYSTICK_BUTTON_RIGHT_STICK,
-		Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
-		Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
-		Common::JOYSTICK_BUTTON_DPAD_UP,
-		Common::JOYSTICK_BUTTON_DPAD_DOWN,
-		Common::JOYSTICK_BUTTON_DPAD_LEFT,
-		Common::JOYSTICK_BUTTON_DPAD_RIGHT};
+	    Common::JOYSTICK_BUTTON_A,
+	    Common::JOYSTICK_BUTTON_B,
+	    Common::JOYSTICK_BUTTON_X,
+	    Common::JOYSTICK_BUTTON_Y,
+	    Common::JOYSTICK_BUTTON_BACK,
+	    Common::JOYSTICK_BUTTON_GUIDE,
+	    Common::JOYSTICK_BUTTON_START,
+	    Common::JOYSTICK_BUTTON_LEFT_STICK,
+	    Common::JOYSTICK_BUTTON_RIGHT_STICK,
+	    Common::JOYSTICK_BUTTON_LEFT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_RIGHT_SHOULDER,
+	    Common::JOYSTICK_BUTTON_DPAD_UP,
+	    Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	    Common::JOYSTICK_BUTTON_DPAD_LEFT,
+	    Common::JOYSTICK_BUTTON_DPAD_RIGHT
+	};
 
 	if (sdlButton >= ARRAYSIZE(osystemButtons)) {
 		return -1;
@@ -1165,9 +982,9 @@ void SdlEventSource::fakeWarpMouse(const int x, const int y) {
 bool SdlEventSource::isJoystickConnected() const {
 	return _joystick
 #if SDL_VERSION_ATLEAST(2, 0, 0)
-		   || _controller
+	        || _controller
 #endif
-		;
+	        ;
 }
 
 void SdlEventSource::setEngineRunning(const bool value) {
@@ -1254,7 +1071,8 @@ uint32 SdlEventSource::obtainUnicode(const SDL_Keysym keySym) {
 	int n = SDL_PeepEvents(events, 2, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_TEXTINPUT);
 	// Make sure that the TEXTINPUT event belongs to this KEYDOWN
 	// event and not another pending one.
-	if ((n > 0 && events[0].type == SDL_TEXTINPUT) || (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) {
+	if ((n > 0 && events[0].type == SDL_TEXTINPUT)
+	    || (n > 1 && events[0].type != SDL_KEYDOWN && events[1].type == SDL_TEXTINPUT)) {
 		// Remove the text input event we associate with the key press. This
 		// makes sure we never get any SDL_TEXTINPUT events which do "belong"
 		// to SDL_KEYDOWN events.


Commit: f69d2c35e06edf2abb5d42d2e09a3777e1daaf11
    https://github.com/scummvm/scummvm/commit/f69d2c35e06edf2abb5d42d2e09a3777e1daaf11
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix assertion when screen size is greater than room height

Changed paths:
    engines/twp/camera.cpp


diff --git a/engines/twp/camera.cpp b/engines/twp/camera.cpp
index 82136976a32..d2dc1aa7c32 100644
--- a/engines/twp/camera.cpp
+++ b/engines/twp/camera.cpp
@@ -29,6 +29,10 @@ void Camera::clamp(Math::Vector2d at) {
 	if (_room) {
 		Math::Vector2d roomSize = _room->_roomSize;
 		Math::Vector2d screenSize = _room->getScreenSize();
+		// fix assertion when screen size is greater than room height
+		if(screenSize.getY() > roomSize.getY()) {
+			screenSize.setY(roomSize.getY());
+		}
 
 		_pos.setX(CLIP(at.getX(), screenSize.getX() / 2.f + _bounds.left(), screenSize.getX() / 2 + _bounds.right()));
 		_pos.setY(CLIP(at.getY(), _bounds.bottom(), _bounds.top() - screenSize.getY() / 2));


Commit: efc6ee8752c778545e0a7d03f117c9ea6d623ec6
    https://github.com/scummvm/scummvm/commit/efc6ee8752c778545e0a7d03f117c9ea6d623ec6
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix audio crash when loading savegames

Changed paths:
    engines/twp/audio.cpp
    engines/twp/resmanager.cpp


diff --git a/engines/twp/audio.cpp b/engines/twp/audio.cpp
index c7fa5d8e9eb..3ced868cae3 100644
--- a/engines/twp/audio.cpp
+++ b/engines/twp/audio.cpp
@@ -248,7 +248,7 @@ int AudioSystem::play(Common::SharedPtr<SoundDefinition> sndDef, Audio::Mixer::S
 		return 0;
 
 	const Common::String &name = sndDef->getName();
-	Audio::SeekableAudioStream *audioStream;
+	Audio::SeekableAudioStream *audioStream = nullptr;
 	if (name.hasSuffixIgnoreCase(".ogg")) {
 		slot->stream.open(sndDef);
 		audioStream = Audio::makeVorbisStream(&slot->stream, DisposeAfterUse::NO);
diff --git a/engines/twp/resmanager.cpp b/engines/twp/resmanager.cpp
index 6285eb2d8ac..c88723017d4 100644
--- a/engines/twp/resmanager.cpp
+++ b/engines/twp/resmanager.cpp
@@ -172,9 +172,7 @@ int ResManager::newCallbackId() {
 }
 
 void ResManager::resetIds(int callbackId) {
-	// don't reset _roomId, _objId and _actorId because there are not dynamically created
-	_soundDefId = START_SOUNDDEFID;
-	_soundId = START_SOUNDID;
+	// don't reset _roomId, _objId, _soundDefId, _soundId and _actorId because there are not dynamically created
 	_threadId = START_THREADID;
 	_lightId = START_LIGHTID;
 	_callbackId = callbackId;


Commit: b917604200bae6fd64df60941be4b8b9ea79c605
    https://github.com/scummvm/scummvm/commit/b917604200bae6fd64df60941be4b8b9ea79c605
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
BACKENDS: Fix create random IniFilename

This was due to a pointer not kept in memory

Changed paths:
  R imgui.ini
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp


diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index e7220d551d6..262a95bf34d 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -475,17 +475,15 @@ void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const {
 	}
 
 #ifdef USE_IMGUI
-	if(!_imguiInit) {
+	if (!_imguiInit) {
 		// Setup Dear ImGui
 		IMGUI_CHECKVERSION();
 		ImGui::CreateContext();
 		ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), _glContext);
-		ImGui_ImplOpenGL3_Init("#version 110");
+		ImGui_ImplOpenGL3_Init(nullptr);
 		ImGui::StyleColorsDark();
 		ImGuiIO &io = ImGui::GetIO();
-		Common::Path initPath(ConfMan.getPath("savepath"));
-		initPath = initPath.appendComponent("imgui.ini");
-		io.IniFilename = initPath.toString().c_str();
+		io.IniFilename = nullptr;
 	}
 #endif
 #endif
diff --git a/imgui.ini b/imgui.ini
deleted file mode 100644
index 9930887f247..00000000000
--- a/imgui.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[Window][Debug##Default]
-Pos=60,60
-Size=400,400
-


Commit: cf9cbad96912d36a4f54bed4dd448d9137522e3b
    https://github.com/scummvm/scummvm/commit/cf9cbad96912d36a4f54bed4dd448d9137522e3b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix it's hard to scare the kid

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 5349a1c4ea8..cb81d12e2fe 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -376,6 +376,26 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 	return result;
 }
 
+struct GetNoun2 {
+	GetNoun2(int verbId, Common::SharedPtr<Object> &obj) : _verbId(verbId), _noun2(obj) {
+		_noun2 = nullptr;
+	}
+
+	bool operator()(Common::SharedPtr<Object> obj) {
+		if (((_verbId == VERB_TALKTO) || !g_twp->_resManager->isActor(obj->getId())) && (obj->_node->getZSort() <= _zOrder)) {
+			if (g_twp->_noun2 != obj) {
+				_noun2 = obj;
+			}
+		}
+		return false;
+	}
+
+public:
+	Common::SharedPtr<Object> &_noun2;
+	int _zOrder = INT_MAX;
+	const int _verbId;
+};
+
 struct GetUseNoun2 {
 	explicit GetUseNoun2(Common::SharedPtr<Object> &obj) : _noun2(obj) {
 		_noun2 = nullptr;
@@ -443,7 +463,7 @@ void TwpEngine::update(float elapsed) {
 						debugC(kDebugGame, "Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
 				}
 			} else {
-				_noun1 = objAt(roomPos);
+				objsAt(roomPos, GetNoun2(_hud->_verb.id.id, _noun1));
 				_useFlag = UseFlag::ufNone;
 				_noun2 = nullptr;
 			}


Commit: 9c7098fb84a616de553fc9c7c217d9431ec028dc
    https://github.com/scummvm/scummvm/commit/9c7098fb84a616de553fc9c7c217d9431ec028dc
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
BACKENDS: Fix regression imgui windows not visible

Changed paths:
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp


diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index 262a95bf34d..b2b824ef9c4 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -480,7 +480,7 @@ void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const {
 		IMGUI_CHECKVERSION();
 		ImGui::CreateContext();
 		ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), _glContext);
-		ImGui_ImplOpenGL3_Init(nullptr);
+		ImGui_ImplOpenGL3_Init("#version 110");
 		ImGui::StyleColorsDark();
 		ImGuiIO &io = ImGui::GetIO();
 		io.IniFilename = nullptr;


Commit: 2fdde4ccc25cc84c1ed2a124087b1ff121c45bcd
    https://github.com/scummvm/scummvm/commit/2fdde4ccc25cc84c1ed2a124087b1ff121c45bcd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Hide system cursor

Changed paths:
    engines/twp/debugtools.cpp
    engines/twp/twp.cpp


diff --git a/engines/twp/debugtools.cpp b/engines/twp/debugtools.cpp
index 0b444b39b50..2a035551153 100644
--- a/engines/twp/debugtools.cpp
+++ b/engines/twp/debugtools.cpp
@@ -613,9 +613,12 @@ static void drawScenegraph() {
 }
 
 void onImGuiRender() {
-	if (!debugChannelSet(-1, kDebugConsole))
+	if (!debugChannelSet(-1, kDebugConsole)) {
+		ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
 		return;
+	}
 
+	ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
 	drawGeneral();
 	drawThreads();
 	drawObjects();
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index cb81d12e2fe..99ea09cd317 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -23,6 +23,7 @@
 #include "common/events.h"
 #include "common/savefile.h"
 #include "engines/util.h"
+#include "graphics/cursorman.h"
 #include "graphics/screen.h"
 #include "graphics/opengl/system_headers.h"
 #include "image/png.h"
@@ -782,6 +783,8 @@ Common::Error TwpEngine::run() {
 	_textDb.reset(new TextDb());
 	_textDb->parseTsv(entry);
 
+	CursorMan.showMouse(false);
+
 	_vm.reset(new Vm());
 	HSQUIRRELVM v = _vm->get();
 	execNutEntry(v, "Defines.nut");


Commit: 8807ab74db560b9a89b44f706591d11ff2832eca
    https://github.com/scummvm/scummvm/commit/8807ab74db560b9a89b44f706591d11ff2832eca
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
BACKENDS: New imgui proposition

Changed paths:
    backends/graphics/graphics.h
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
    backends/graphics3d/openglsdl/openglsdl-graphics3d.h
    engines/metaengine.h
    engines/twp/metaengine.cpp
    engines/twp/metaengine.h
    engines/twp/twp.cpp


diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h
index b78e2a81a60..d73fa98f037 100644
--- a/backends/graphics/graphics.h
+++ b/backends/graphics/graphics.h
@@ -113,6 +113,8 @@ public:
 
 	virtual void saveScreenshot() {}
 	virtual bool lockMouse(bool lock) { return false; }
+
+	virtual void renderImGui(void(*render)()) {}
 };
 
 #endif
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index b2b824ef9c4..17e4744f944 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -617,9 +617,6 @@ bool OpenGLSdlGraphics3dManager::createOrUpdateGLContext(uint gameWidth, uint ga
 	initializeOpenGLContext();
 #if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
 	_imguiInit = true;
-	const Plugin *plugin = EngineMan.findPlugin(ConfMan.get("engineid"));
-	plugin = PluginMan.getEngineFromMetaEngine(plugin);
-	_metaEngine = &plugin->get<MetaEngine>();
 #endif
 
 	if (clear)
@@ -661,17 +658,20 @@ OpenGL::FrameBuffer *OpenGLSdlGraphics3dManager::createFramebuffer(uint width, u
 	}
 }
 
-void OpenGLSdlGraphics3dManager::updateScreen() {
+void OpenGLSdlGraphics3dManager::renderImGui(void(*render)()) {
 #if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
 	ImGui_ImplOpenGL3_NewFrame();
 	ImGui_ImplSDL2_NewFrame(_window->getSDLWindow());
 
 	ImGui::NewFrame();
-	_metaEngine->renderImGui();
+	render();
 	ImGui::Render();
 
 	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
 #endif
+}
+
+void OpenGLSdlGraphics3dManager::updateScreen() {
 
 	GLint prevStateViewport[4];
 	glGetIntegerv(GL_VIEWPORT, prevStateViewport);
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
index 20ef5143e6a..821cda659b1 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
@@ -115,12 +115,15 @@ public:
 
 	void showSystemMouseCursor(bool visible) override;
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	void renderImGui(void(*render)()) override;
+#endif
+
 protected:
 #if SDL_VERSION_ATLEAST(2, 0, 0)
 	int _glContextProfileMask, _glContextMajor, _glContextMinor;
 	SDL_GLContext _glContext;
 	void deinitializeRenderer();
-	MetaEngine* _metaEngine = nullptr;
 	bool _imguiInit = false;
 #endif
 
diff --git a/engines/metaengine.h b/engines/metaengine.h
index 210bc428f65..71bc6b3ec8d 100644
--- a/engines/metaengine.h
+++ b/engines/metaengine.h
@@ -564,8 +564,6 @@ public:
 	 * Read the extended savegame header from the given savegame file.
 	 */
 	WARN_UNUSED_RESULT static bool readSavegameHeader(Common::InSaveFile *in, ExtendedSavegameHeader *header, bool skipThumbnail = true);
-
-	virtual void renderImGui() {}
 };
 
 /**
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index fad70ce3521..3402f57286f 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -159,12 +159,6 @@ Common::Array<Common::Keymap *> TwpMetaEngine::initKeymaps(const char *target) c
 	return Common::Keymap::arrayOf(engineKeyMap);
 }
 
-void TwpMetaEngine::renderImGui() {
-#ifdef USE_IMGUI
-	Twp::onImGuiRender();
-#endif
-}
-
 #if PLUGIN_ENABLED_DYNAMIC(TWP)
 REGISTER_PLUGIN_DYNAMIC(TWP, PLUGIN_TYPE_ENGINE, TwpMetaEngine);
 #else
diff --git a/engines/twp/metaengine.h b/engines/twp/metaengine.h
index 4ae11a29673..28fd0d5d1c0 100644
--- a/engines/twp/metaengine.h
+++ b/engines/twp/metaengine.h
@@ -45,8 +45,6 @@ public:
 	GUI::OptionsContainerWidget *buildEngineOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const override;
 
 	virtual Common::Array<Common::Keymap *> initKeymaps(const char *target) const override;
-
-	void renderImGui() override;
 };
 
 #endif // TWP_METAENGINE_H
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 99ea09cd317..5b1db9ac553 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "backends/modular-backend.h"
+#include "backends/graphics/graphics.h"
 #include "common/config-manager.h"
 #include "common/events.h"
 #include "common/savefile.h"
@@ -32,6 +34,7 @@
 #include "twp/actions.h"
 #include "twp/callback.h"
 #include "twp/console.h"
+#include "twp/debugtools.h"
 #include "twp/detection.h"
 #include "twp/enginedialogtarget.h"
 #include "twp/hud.h"
@@ -741,6 +744,13 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	// imgui render
 	_gfx.use(nullptr);
 
+#ifdef USE_IMGUI
+	ModularGraphicsBackend *sdl_g_system = dynamic_cast<ModularGraphicsBackend*>(g_system);
+	if (sdl_g_system) {
+		sdl_g_system->getGraphicsManager()->renderImGui(onImGuiRender);
+	}
+#endif
+
 	g_system->updateScreen();
 }
 


Commit: d922f17f4a60676ae544de0ae796e4f5d378bec8
    https://github.com/scummvm/scummvm/commit/d922f17f4a60676ae544de0ae796e4f5d378bec8
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix mansion door trigger

Changed paths:
    engines/twp/roomlib.cpp


diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index c76d5e2e34f..feef126fa9d 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -43,6 +43,7 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 		if (SQ_FAILED(sqget(v, 4, obj->_leave)))
 			return sq_throwerror(v, "failed to get leave");
 	sq_addref(g_twp->getVm(), &obj->_leave);
+	obj->_triggerActive = false;
 	g_twp->_room->_triggers.push_back(obj);
 	return 0;
 }


Commit: e1208eaec311c53a9bd2ba7361ae075edde953e7
    https://github.com/scummvm/scummvm/commit/e1208eaec311c53a9bd2ba7361ae075edde953e7
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix actor meeting delores cutscene

Changed paths:
    engines/twp/motor.cpp


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 009d1a803b7..8c6b0fb4e59 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -164,7 +164,7 @@ void ReachAnim::update(float elapsed) {
 		break;
 	case 1:
 		_elapsed += elapsed;
-		if (_elapsed > 0.5)
+		if (_elapsed > 0.10)
 			_state = 2;
 		break;
 	case 2:


Commit: c99572efe7475f6b5fe0047665cbc7de78035905
    https://github.com/scummvm/scummvm/commit/c99572efe7475f6b5fe0047665cbc7de78035905
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Detect more versions

Changed paths:
    engines/twp/detection.cpp
    engines/twp/detection.h
    engines/twp/detection_tables.h
    engines/twp/ggpack.cpp
    engines/twp/ggpack.h
    engines/twp/metaengine.cpp
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/detection.cpp b/engines/twp/detection.cpp
index 759d358765d..54b24c09aed 100644
--- a/engines/twp/detection.cpp
+++ b/engines/twp/detection.cpp
@@ -42,17 +42,20 @@ const DebugChannelDef TwpMetaEngineDetection::debugFlagList[] = {
 
 TwpMetaEngineDetection::TwpMetaEngineDetection()
 	: AdvancedMetaEngineDetection(Twp::gameDescriptions,
-								  sizeof(ADGameDescription), Twp::twpGames) {
+								  sizeof(Twp::TwpGameDescription), Twp::twpGames) {
 }
 
 DetectedGame TwpMetaEngineDetection::toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const {
 	DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame, extraInfo);
+	Twp::LanguageSupported languageSupported = reinterpret_cast<const Twp::TwpGameDescription *>(adGame.desc)->languageSupported;
 	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
 	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::FR_FRA));
 	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::IT_ITA));
 	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::DE_DEU));
 	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ES_ESP));
-	game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::RU_RUS));
+	if (languageSupported == Twp::LS_WITH_RUSSIAN) {
+		game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::RU_RUS));
+	}
 	return game;
 }
 
diff --git a/engines/twp/detection.h b/engines/twp/detection.h
index 4f30d5a2ea3..d7db69c40dc 100644
--- a/engines/twp/detection.h
+++ b/engines/twp/detection.h
@@ -41,11 +41,26 @@ enum TwpDebugChannels {
 	kDebugConsole
 };
 
-extern const PlainGameDescriptor twpGames[];
+enum GameXorKey {
+	GAME_XORKEY_56AD,
+	GAME_XORKEY_566D,
+	GAME_XORKEY_5B6D,
+	GAME_XORKEY_5BAD,
+};
+
+enum LanguageSupported {
+	LS_WITH_RUSSIAN,
+	LS_WITHOUT_RUSSIAN
+};
 
-extern const ADGameDescription gameDescriptions[];
+extern const PlainGameDescriptor twpGames[];
+struct TwpGameDescription {
+	ADGameDescription desc;
+	GameXorKey xorKey;
+	LanguageSupported languageSupported;
+};
 
-#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+extern const TwpGameDescription gameDescriptions[];
 
 } // End of namespace Twp
 
diff --git a/engines/twp/detection_tables.h b/engines/twp/detection_tables.h
index a72bb6cc434..bab4be4abea 100644
--- a/engines/twp/detection_tables.h
+++ b/engines/twp/detection_tables.h
@@ -25,26 +25,91 @@ const PlainGameDescriptor twpGames[] = {
 	{"twp", "Thimbleweed Park"},
 	{0, 0}};
 
-const ADGameDescription gameDescriptions[] = {
-	// Thimbleweed Park - GOG/1.0.958
+const TwpGameDescription gameDescriptions[] = {
+	// Thimbleweed Park - STEAM/1.0.859
 	{
-		"twp",
-		"",
-		AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
-		Common::UNK_LANG,
-		Common::kPlatformUnknown,
-		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOMIDI)},
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "b1c35b7a6b5b0c2e6f466ea384f22558", 435353845),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_5B6D,
+		LS_WITHOUT_RUSSIAN
+	},
 	// Thimbleweed Park - EPIC Games/1.0.955
 	{
-		"twp",
-		"",
-		AD_ENTRY1s("ThimbleweedPark.ggpack1", "a97546ee2d9e19aab59714a267009a31", 502540584),
-		Common::UNK_LANG,
-		Common::kPlatformUnknown,
-		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOMIDI)},
-
-	AD_TABLE_END_MARKER};
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "a97546ee2d9e19aab59714a267009a31", 502540584),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_566D,
+		LS_WITH_RUSSIAN
+	},
+	// Thimbleweed Park - GOG/1.0.938
+	{
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "5532019821c077dda5cf86b619e4b173", 502495748),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_566D,
+		LS_WITH_RUSSIAN
+	},
+	// Thimbleweed Park - GOG/1.0.951
+  	{
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "f0bd29df9fcaba3d4047eac1046e0abf", 502503739),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_566D,
+		LS_WITH_RUSSIAN
+	},
+	// Thimbleweed Park - GOG/1.0.957
+  	{
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "5631cf51cb7afc4babf7f2d5a8bdfc21", 502661437),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_56AD,
+		LS_WITH_RUSSIAN
+	},
+  	// Thimbleweed Park - GOG/1.0.958
+	{
+		{
+			"twp",
+			"",
+			AD_ENTRY1s("ThimbleweedPark.ggpack1", "6180145221d18e9e9caac6459e840579", 502661439),
+			Common::UNK_LANG,
+			Common::kPlatformUnknown,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_XORKEY_56AD,
+		LS_WITH_RUSSIAN
+	},
+	{AD_TABLE_END_MARKER,(GameXorKey)0, (LanguageSupported)0}};
 
 } // End of namespace Twp
diff --git a/engines/twp/ggpack.cpp b/engines/twp/ggpack.cpp
index 1b0a7dc576f..75f31dd5c1c 100644
--- a/engines/twp/ggpack.cpp
+++ b/engines/twp/ggpack.cpp
@@ -737,44 +737,30 @@ bool GGPackSet::containsDLC() const {
 	return _packs.find(3) != _packs.end();
 }
 
-void GGPackSet::init() {
-	// try to auto-detect which XOR key to use to decrypt the resources of the game
-	const XorKey key1{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
-	const XorKey key2{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	const XorKey key3{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
-	const XorKey key4{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
-
-	const XorKey keys[]{key1, key2, key3, key4};
-	const char *key_names[]{"56ad", "566d", "5b6d", "5bad"};
-
+void GGPackSet::init(const XorKey& key) {
 	Common::ArchiveMemberList fileList;
 	SearchMan.listMatchingMembers(fileList, "*.ggpack*");
 
-	for (int i = 0; i < ARRAYSIZE(keys); i++) {
-		const XorKey *key = &keys[i];
-		for (auto it = fileList.begin(); it != fileList.end(); ++it) {
-			const Common::ArchiveMember &m = **it;
-			Common::String fileName = m.getFileName();
-			size_t pos = fileName.findLastOf("ggpack");
-			if (pos == Common::String::npos)
-				continue;
-
-			long index = atol(fileName.c_str() + pos + 1);
-
-			Common::SeekableReadStream *stream = m.createReadStream();
-			GGPackDecoder pack;
-			if (stream && pack.open(stream, *key)) {
-				_packs[index] = pack;
-			}
-		}
+	for (auto it = fileList.begin(); it != fileList.end(); ++it) {
+		const Common::ArchiveMember &m = **it;
+		Common::String fileName = m.getFileName();
+		size_t pos = fileName.findLastOf("ggpack");
+		if (pos == Common::String::npos)
+			continue;
 
-		if (!_packs.empty()) {
-			// the game has been detected because we have at least 1 ggpack file.
-			debugC(kDebugGGPack, "Thimbleweed Park detected with key %s", key_names[i]);
-			return;
+		long index = atol(fileName.c_str() + pos + 1);
+
+		Common::SeekableReadStream *stream = m.createReadStream();
+		GGPackDecoder pack;
+		if (stream && pack.open(stream, key)) {
+			_packs[index] = pack;
 		}
 	}
 
+	if (!_packs.empty()) {
+		return;
+	}
+
 	error("This version of the game is invalid or not supported (yet?)");
 }
 
diff --git a/engines/twp/ggpack.h b/engines/twp/ggpack.h
index 7f34ec3b232..b087a62fceb 100644
--- a/engines/twp/ggpack.h
+++ b/engines/twp/ggpack.h
@@ -176,7 +176,7 @@ private:
 
 class GGPackSet {
 public:
-	void init();
+	void init(const XorKey& key);
 	bool assetExists(const char *asset);
 
 	bool containsDLC() const;
diff --git a/engines/twp/metaengine.cpp b/engines/twp/metaengine.cpp
index 3402f57286f..704236407ca 100644
--- a/engines/twp/metaengine.cpp
+++ b/engines/twp/metaengine.cpp
@@ -47,7 +47,7 @@ const char *TwpMetaEngine::getName() const {
 }
 
 Common::Error TwpMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
-	*engine = new Twp::TwpEngine(syst, desc);
+	*engine = new Twp::TwpEngine(syst, (const Twp::TwpGameDescription*)(desc));
 	return Common::kNoError;
 }
 
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 5b1db9ac553..f1307c91801 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -55,7 +55,7 @@ namespace Twp {
 
 TwpEngine *g_twp;
 
-TwpEngine::TwpEngine(OSystem *syst, const ADGameDescription *gameDesc)
+TwpEngine::TwpEngine(OSystem *syst, const TwpGameDescription *gameDesc)
 	: Engine(syst),
 	  _gameDescription(gameDesc),
 	  _randomSource("Twp") {
@@ -109,11 +109,11 @@ Math::Vector2d TwpEngine::screenToRoom(Math::Vector2d pos) {
 }
 
 uint32 TwpEngine::getFeatures() const {
-	return _gameDescription->flags;
+	return _gameDescription->desc.flags;
 }
 
 Common::String TwpEngine::getGameId() const {
-	return _gameDescription->gameId;
+	return _gameDescription->desc.gameId;
 }
 
 bool TwpEngine::clickedAtHandled(Math::Vector2d roomPos) {
@@ -745,7 +745,7 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	_gfx.use(nullptr);
 
 #ifdef USE_IMGUI
-	ModularGraphicsBackend *sdl_g_system = dynamic_cast<ModularGraphicsBackend*>(g_system);
+	ModularGraphicsBackend *sdl_g_system = dynamic_cast<ModularGraphicsBackend *>(g_system);
 	if (sdl_g_system) {
 		sdl_g_system->getGraphicsManager()->renderImGui(onImGuiRender);
 	}
@@ -785,7 +785,32 @@ Common::Error TwpEngine::run() {
 	_fadeShader.reset(new FadeShader());
 	_lighting.reset(new Lighting());
 	_resManager.reset(new ResManager());
-	_pack->init();
+
+	switch (_gameDescription->xorKey) {
+	case GAME_XORKEY_56AD: {
+		const XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+		_pack->init(key);
+		break;
+	}
+	case GAME_XORKEY_566D: {
+		const XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x56, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+		_pack->init(key);
+		break;
+	}
+	case GAME_XORKEY_5B6D: {
+		const XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0x6D};
+		_pack->init(key);
+		break;
+	}
+	case GAME_XORKEY_5BAD: {
+		const XorKey key{{0x4F, 0xD0, 0xA0, 0xAC, 0x4A, 0x5B, 0xB9, 0xE5, 0x93, 0x79, 0x45, 0xA5, 0xC1, 0xCB, 0x31, 0x93}, 0xAD};
+		_pack->init(key);
+		break;
+	}
+	default:
+		error("This version of the game is invalid or not supported (yet?)");
+		break;
+	}
 
 	Common::String lang(Common::String::format("ThimbleweedText_%s.tsv", ConfMan.get("language").c_str()));
 	GGPackEntryReader entry;
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index 59526105f92..e430bd0815b 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -82,7 +82,7 @@ enum class UseFlag;
 
 class TwpEngine : public Engine {
 private:
-	const ADGameDescription *_gameDescription;
+	const TwpGameDescription *_gameDescription;
 	Common::RandomSource _randomSource;
 
 protected:
@@ -90,7 +90,7 @@ protected:
 	Common::Error run() override;
 
 public:
-	TwpEngine(OSystem *syst, const ADGameDescription *gameDesc);
+	TwpEngine(OSystem *syst, const TwpGameDescription *gameDesc);
 	~TwpEngine() override;
 
 	uint32 getFeatures() const;


Commit: 9ff903dd75ab13e3240c2b07fa0ffa329ea00da4
    https://github.com/scummvm/scummvm/commit/9ff903dd75ab13e3240c2b07fa0ffa329ea00da4
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
BACKENDS: Fix imgui width sdl1

Changed paths:
    backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
    backends/graphics3d/openglsdl/openglsdl-graphics3d.h


diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
index 17e4744f944..dba1795f40a 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.cpp
@@ -658,8 +658,8 @@ OpenGL::FrameBuffer *OpenGLSdlGraphics3dManager::createFramebuffer(uint width, u
 	}
 }
 
-void OpenGLSdlGraphics3dManager::renderImGui(void(*render)()) {
 #if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
+void OpenGLSdlGraphics3dManager::renderImGui(void(*render)()) {
 	ImGui_ImplOpenGL3_NewFrame();
 	ImGui_ImplSDL2_NewFrame(_window->getSDLWindow());
 
@@ -668,8 +668,8 @@ void OpenGLSdlGraphics3dManager::renderImGui(void(*render)()) {
 	ImGui::Render();
 
 	ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
-#endif
 }
+#endif
 
 void OpenGLSdlGraphics3dManager::updateScreen() {
 
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
index 821cda659b1..f4909c99460 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
@@ -115,7 +115,7 @@ public:
 
 	void showSystemMouseCursor(bool visible) override;
 
-#if SDL_VERSION_ATLEAST(2, 0, 0)
+#if defined(USE_IMGUI) && SDL_VERSION_ATLEAST(2, 0, 0)
 	void renderImGui(void(*render)()) override;
 #endif
 


Commit: b8a444a81505c6585372945c305e6fa3101aacca
    https://github.com/scummvm/scummvm/commit/b8a444a81505c6585372945c305e6fa3101aacca
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix skip cutscene crash

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index f1307c91801..9f9a85bd0a0 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -1727,7 +1727,7 @@ void TwpEngine::skipCutscene() {
 		_cutscene->cutsceneOverride();
 		return;
 	}
-	_noOverride.reset();
+	_noOverride->reset();
 }
 
 Scaling *TwpEngine::getScaling(const Common::String &name) {


Commit: 94c97c7c633c77442b74cdd979bd40db66c70681
    https://github.com/scummvm/scummvm/commit/94c97c7c633c77442b74cdd979bd40db66c70681
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix not possible to pickup the glass in kitchen

Changed paths:
    engines/twp/twp.cpp


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 9f9a85bd0a0..03145e862e8 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -380,22 +380,21 @@ Common::Array<ActorSwitcherSlot> TwpEngine::actorSwitcherSlots() {
 	return result;
 }
 
-struct GetNoun2 {
-	GetNoun2(int verbId, Common::SharedPtr<Object> &obj) : _verbId(verbId), _noun2(obj) {
-		_noun2 = nullptr;
+struct GetNoun {
+	GetNoun(int verbId, Common::SharedPtr<Object> &obj) : _verbId(verbId), _noun(obj) {
+		_noun = nullptr;
 	}
 
 	bool operator()(Common::SharedPtr<Object> obj) {
 		if (((_verbId == VERB_TALKTO) || !g_twp->_resManager->isActor(obj->getId())) && (obj->_node->getZSort() <= _zOrder)) {
-			if (g_twp->_noun2 != obj) {
-				_noun2 = obj;
-			}
+			_noun = obj;
+			_zOrder = obj->_node->getZSort();
 		}
 		return false;
 	}
 
 public:
-	Common::SharedPtr<Object> &_noun2;
+	Common::SharedPtr<Object> &_noun;
 	int _zOrder = INT_MAX;
 	const int _verbId;
 };
@@ -409,6 +408,7 @@ struct GetUseNoun2 {
 		if (obj->_node->getZSort() <= _zOrder) {
 			if ((obj != g_twp->_actor) && (g_twp->_noun2 != obj)) {
 				_noun2 = obj;
+				_zOrder = obj->_node->getZSort();
 			}
 		}
 		return false;
@@ -467,7 +467,7 @@ void TwpEngine::update(float elapsed) {
 						debugC(kDebugGame, "Give '%s' to '%s'", _noun1->_key.c_str(), _noun2->_key.c_str());
 				}
 			} else {
-				objsAt(roomPos, GetNoun2(_hud->_verb.id.id, _noun1));
+				objsAt(roomPos, GetNoun(_hud->_verb.id.id, _noun1));
 				_useFlag = UseFlag::ufNone;
 				_noun2 = nullptr;
 			}


Commit: 1c9e1a93336fe6b766567bfb3f8fb51b99cb9c1e
    https://github.com/scummvm/scummvm/commit/1c9e1a93336fe6b766567bfb3f8fb51b99cb9c1e
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix objectHotspot

Changed paths:
    engines/twp/objlib.cpp


diff --git a/engines/twp/objlib.cpp b/engines/twp/objlib.cpp
index 98e212284d6..edac820a14c 100644
--- a/engines/twp/objlib.cpp
+++ b/engines/twp/objlib.cpp
@@ -422,7 +422,7 @@ static SQInteger objectHotspot(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get object or actor");
 	if (sq_gettop(v) == 2) {
 		Math::Vector2d pos = obj->_node->getAbsPos();
-		sqpush(v, Rectf::fromPosAndSize(Math::Vector2d(obj->_hotspot.left + pos.getX(), obj->_hotspot.bottom + pos.getY()), Math::Vector2d(obj->_hotspot.width(), obj->_hotspot.height())));
+		sqpush(v, Rectf::fromPosAndSize(Math::Vector2d(obj->_hotspot.left + pos.getX(), obj->_hotspot.top + pos.getY()), Math::Vector2d(obj->_hotspot.width(), obj->_hotspot.height())));
 		return 1;
 	}
 	SQInteger left = 0;


Commit: e8161a32ac0822b4f6fead187d95c24beb0c6774
    https://github.com/scummvm/scummvm/commit/e8161a32ac0822b4f6fead187d95c24beb0c6774
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix stop sound when stop talk

Changed paths:
    engines/twp/motor.cpp


diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 8c6b0fb4e59..37b9f25e919 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -482,6 +482,9 @@ void Talking::say(const Common::String &text) {
 
 void Talking::disable() {
 	Motor::disable();
+	if (_obj->_sound) {
+		g_twp->_audio->stop(_obj->_sound);
+	}
 	_texts.clear();
 	_obj->setHeadIndex(1);
 	if (_node)


Commit: 0f94a8257c6f173cc62ec34d895e63b62e08c969
    https://github.com/scummvm/scummvm/commit/0f94a8257c6f173cc62ec34d895e63b62e08c969
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix strange walk path

Changed paths:
    engines/twp/actorlib.cpp
    engines/twp/graph.cpp
    engines/twp/graph.h
    engines/twp/motor.cpp
    engines/twp/motor.h
    engines/twp/object.cpp
    engines/twp/object.h
    engines/twp/room.cpp
    engines/twp/room.h
    engines/twp/roomlib.cpp
    engines/twp/twp.cpp
    engines/twp/util.cpp
    engines/twp/util.h
    engines/twp/walkboxnode.cpp
    engines/twp/walkboxnode.h


diff --git a/engines/twp/actorlib.cpp b/engines/twp/actorlib.cpp
index 6c88dda7727..1fee934f276 100644
--- a/engines/twp/actorlib.cpp
+++ b/engines/twp/actorlib.cpp
@@ -205,7 +205,7 @@ static SQInteger actorDistanceTo(HSQUIRRELVM v) {
 	} else {
 		obj = g_twp->_actor;
 	}
-	sqpush(v, distance((Vector2i)actor->_node->getPos(), (Vector2i)obj->getUsePos()));
+	sqpush(v, distance(actor->_node->getPos(), obj->getUsePos()));
 	return 1;
 }
 
@@ -222,7 +222,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 		if (actor1->_room != actor2->_room)
 			return false;
 		// not sure about this, needs to be check one day ;)
-		sqpush(v, distance((Vector2i)actor1->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < distance((Vector2i)actor2->_node->getAbsPos(), (Vector2i)obj->getUsePos()));
+		sqpush(v, distance(actor1->_node->getAbsPos(), obj->getUsePos()) < distance(actor2->_node->getAbsPos(), obj->getUsePos()));
 		return 1;
 	}
 
@@ -238,7 +238,7 @@ static SQInteger actorDistanceWithin(HSQUIRRELVM v) {
 			return sq_throwerror(v, "failed to get distance");
 		if (actor->_room != obj->_room)
 			return false;
-		sqpush(v, distance((Vector2i)actor->_node->getAbsPos(), (Vector2i)obj->getUsePos()) < dist);
+		sqpush(v, distance(actor->_node->getAbsPos(), obj->getUsePos()) < dist);
 		return 1;
 	}
 	return sq_throwerror(v, "actorDistanceWithin not implemented");
@@ -341,7 +341,7 @@ static SQInteger actorInWalkbox(HSQUIRRELVM v) {
 		return sq_throwerror(v, "failed to get name");
 	for (const auto &walkbox : g_twp->_room->_walkboxes) {
 		if (walkbox._name == name) {
-			if (walkbox.contains((Vector2i)actor->_node->getAbsPos())) {
+			if (walkbox.contains(actor->_node->getAbsPos())) {
 				sqpush(v, true);
 				return 1;
 			}
@@ -693,7 +693,7 @@ static SQInteger actorWalkForward(HSQUIRRELVM v) {
 		dir = Math::Vector2d(dist, 0);
 		break;
 	}
-	Object::walk(actor, (Vector2i)(actor->_node->getAbsPos() + dir));
+	Object::walk(actor, (actor->_node->getAbsPos() + dir));
 	return 0;
 }
 
@@ -762,7 +762,7 @@ static SQInteger actorWalkTo(HSQUIRRELVM v) {
 			if (SQ_FAILED(sqget(v, 5, facing)))
 				return sq_throwerror(v, "failed to get dir");
 		}
-		Object::walk(actor, Vector2i(static_cast<int>(x), static_cast<int>(y)), facing);
+		Object::walk(actor, Math::Vector2d(x, y), facing);
 	} else {
 		return sq_throwerror(v, "invalid number of arguments in actorWalkTo");
 	}
diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index 74c723e1807..f4bdee72264 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -23,6 +23,51 @@
 
 namespace Twp {
 
+static float dot(Math::Vector2d u, Math::Vector2d v) {
+	return (u.getX() * v.getX()) + (u.getY() * v.getY());
+}
+
+static float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
+
+static bool lineSegmentsCross(Math::Vector2d a, Math::Vector2d b, Math::Vector2d c, Math::Vector2d d) {
+	const float EPSILON = 1e-3f;
+	const float denominator = ((b.getX() - a.getX()) * (d.getY() - c.getY())) - ((b.getY() - a.getY()) * (d.getX() - c.getX()));
+	if (abs(denominator) < EPSILON) {
+		return false;
+	}
+
+	const float numerator1 = ((a.getY() - c.getY()) * (d.getX() - c.getX())) - ((a.getX() - c.getX()) * (d.getY() - c.getY()));
+	const float numerator2 = ((a.getY() - c.getY()) * (b.getX() - a.getX())) - ((a.getX() - c.getX()) * (b.getY() - a.getY()));
+	if ((abs(numerator1) < EPSILON) || (abs(numerator2) < EPSILON)) {
+		return false;
+	}
+
+	const float r = numerator1 / denominator;
+	const float s = numerator2 / denominator;
+	return ((r > 0.f) && (r < 1.f)) && ((s > 0.f) && (s < 1.f));
+}
+
+static Common::Array<int> reverse(const Common::Array<int> &arr) {
+	Common::Array<int> result(arr.size());
+	for (size_t i = 0; i < arr.size(); i++) {
+		result[arr.size() - 1 - i] = arr[i];
+	}
+	return result;
+}
+
+static uint minIndex(const Common::Array<float> &values) {
+	if(values.empty()) return (uint)-1;
+	float min = values[0];
+	uint index = 0;
+	for (uint i = 1; i < values.size(); i++) {
+		if (values[i] < min) {
+			index = i;
+			min = values[i];
+		}
+	}
+	return index;
+}
+
 IndexedPriorityQueue::IndexedPriorityQueue(Common::Array<float> &keys)
 	: _keys(keys) {
 }
@@ -76,7 +121,7 @@ GraphEdge::GraphEdge(int s, int t, float c)
 	: start(s), to(t), cost(c) {
 }
 
-void Graph::addNode(Vector2i node) {
+void Graph::addNode(Math::Vector2d node) {
 	_nodes.push_back(node);
 	_edges.push_back(Common::Array<GraphEdge>());
 }
@@ -86,12 +131,6 @@ AStar::AStar(Graph *graph)
 	_graph = graph;
 }
 
-static float dot(Vector2i u, Vector2i v) {
-	return (u.x * v.x) + (u.y * v.y);
-}
-
-static float length(Vector2i v) { return sqrt(dot(v, v)); }
-
 void AStar::search(int source, int target) {
 	IndexedPriorityQueue pq(_fCost);
 	pq.insert(source);
@@ -139,14 +178,6 @@ GraphEdge *Graph::edge(int start, int to) {
 	return nullptr;
 }
 
-Common::Array<int> reverse(const Common::Array<int> &arr) {
-	Common::Array<int> result(arr.size());
-	for (size_t i = 0; i < arr.size(); i++) {
-		result[arr.size() - 1 - i] = arr[i];
-	}
-	return result;
-}
-
 Common::Array<int> Graph::getPath(int source, int target) {
 	Common::Array<int> result;
 	AStar astar(this);
@@ -168,18 +199,14 @@ void PathFinder::setWalkboxes(const Common::Array<Walkbox> &walkboxes) {
 	_graph = nullptr;
 }
 
-static Vector2i toVector2i(float x, float y) {
-	return Vector2i(roundf(x), roundf(y));
-}
-
-Vector2i Walkbox::getClosestPointOnEdge(Vector2i p) const {
+Math::Vector2d Walkbox::getClosestPointOnEdge(Math::Vector2d p) const {
 	int vi1 = -1;
 	int vi2 = -1;
 	float minDist = 100000.0f;
 
 	const Common::Array<Vector2i> &polygon = getPoints();
 	for (size_t i = 0; i < polygon.size(); i++) {
-		float dist = distanceToSegment(p, polygon[i], polygon[(i + 1) % polygon.size()]);
+		float dist = distanceToSegment(p, (Math::Vector2d)polygon[i], (Math::Vector2d)polygon[(i + 1) % polygon.size()]);
 		if (dist < minDist) {
 			minDist = dist;
 			vi1 = i;
@@ -187,15 +214,15 @@ Vector2i Walkbox::getClosestPointOnEdge(Vector2i p) const {
 		}
 	}
 
-	Vector2i p1 = polygon[vi1];
-	Vector2i p2 = polygon[vi2];
+	Math::Vector2d p1 = (Math::Vector2d)polygon[vi1];
+	Math::Vector2d p2 = (Math::Vector2d)polygon[vi2];
 
-	float x1 = p1.x;
-	float y1 = p1.y;
-	float x2 = p2.x;
-	float y2 = p2.y;
-	float x3 = p.x;
-	float y3 = p.y;
+	float x1 = p1.getX();
+	float y1 = p1.getY();
+	float x2 = p2.getX();
+	float y2 = p2.getY();
+	float x3 = p.getX();
+	float y3 = p.getY();
 
 	float u = (((x3 - x1) * (x2 - x1)) + ((y3 - y1) * (y2 - y1))) / (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
 
@@ -203,31 +230,13 @@ Vector2i Walkbox::getClosestPointOnEdge(Vector2i p) const {
 	float yu = y1 + u * (y2 - y1);
 
 	if (u < 0)
-		return toVector2i(x1, y1);
+		return Math::Vector2d(x1, y1);
 	if (u > 1)
-		return toVector2i(x2, y2);
-	return toVector2i(xu, yu);
-}
-
-static bool lineSegmentsCross(Vector2i a, Vector2i b, Vector2i c, Vector2i d) {
-	const float EPSILON = 4.f;
-	const float denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x));
-	if (abs(denominator) < EPSILON) {
-		return false;
-	}
-
-	const float numerator1 = ((a.y - c.y) * (d.x - c.x)) - ((a.x - c.x) * (d.y - c.y));
-	const float numerator2 = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y));
-	if ((abs(numerator1) < EPSILON) || (abs(numerator2) < EPSILON)) {
-		return false;
-	}
-
-	const float r = numerator1 / denominator;
-	const float s = numerator2 / denominator;
-	return ((r > 0.f) && (r < 1.f)) && ((s > 0.f) && (s < 1.f));
+		return Math::Vector2d(x2, y2);
+	return Math::Vector2d(xu, yu);
 }
 
-bool PathFinder::inLineOfSight(Vector2i start, Vector2i to) {
+bool PathFinder::inLineOfSight(Math::Vector2d start, Math::Vector2d to) {
 	const float epsilon = 0.5f;
 
 	// Not in LOS if any of the ends is outside the polygon
@@ -244,8 +253,8 @@ bool PathFinder::inLineOfSight(Vector2i start, Vector2i to) {
 		const Common::Array<Vector2i> &polygon = walkbox.getPoints();
 		const uint size = polygon.size();
 		for (uint j = 0; j < size; j++) {
-			Vector2i v1 = polygon[j];
-			Vector2i v2 = polygon[(j + 1) % size];
+			Math::Vector2d v1 = (Math::Vector2d)polygon[j];
+			Math::Vector2d v2 = (Math::Vector2d)polygon[(j + 1) % size];
 			if (!lineSegmentsCross(start, to, v1, v2))
 				continue;
 
@@ -256,7 +265,7 @@ bool PathFinder::inLineOfSight(Vector2i start, Vector2i to) {
 	}
 
 	// Finally the middle point in the segment determines if in LOS or not
-	const Vector2i v2 = (start + to) / 2.0f;
+	const Math::Vector2d v2 = (start + to) / 2.0f;
 	if (!_walkboxes[0].contains(v2))
 		return false;
 	for (uint i = 1; i < _walkboxes.size(); i++) {
@@ -266,18 +275,6 @@ bool PathFinder::inLineOfSight(Vector2i start, Vector2i to) {
 	return true;
 }
 
-static uint minIndex(const Common::Array<float> &values) {
-	float min = values[0];
-	uint index = 0;
-	for (uint i = 1; i < values.size(); i++) {
-		if (values[i] < min) {
-			index = i;
-			min = values[i];
-		}
-	}
-	return index;
-}
-
 Common::SharedPtr<Graph> PathFinder::createGraph() {
 	Common::SharedPtr<Graph> result(new Graph());
 	for (uint i = 0; i < _walkboxes.size(); i++) {
@@ -288,7 +285,7 @@ Common::SharedPtr<Graph> PathFinder::createGraph() {
 				firstWalkbox = true;
 			for (uint j = 0; j < walkbox.getPoints().size(); j++) {
 				if (walkbox.concave(j) == firstWalkbox) {
-					const Vector2i &vertex = walkbox.getPoints()[j];
+					Math::Vector2d vertex = (Math::Vector2d)walkbox.getPoints()[j];
 					result->_concaveVertices.push_back(vertex);
 					result->addNode(vertex);
 				}
@@ -298,8 +295,8 @@ Common::SharedPtr<Graph> PathFinder::createGraph() {
 
 	for (uint i = 0; i < result->_concaveVertices.size(); i++) {
 		for (uint j = 0; j < result->_concaveVertices.size(); j++) {
-			const Vector2i c1(result->_concaveVertices[i]);
-			const Vector2i c2(result->_concaveVertices[j]);
+			const Math::Vector2d c1(result->_concaveVertices[i]);
+			const Math::Vector2d c2(result->_concaveVertices[j]);
 			if (inLineOfSight(c1, c2)) {
 				const float d = distance(c1, c2);
 				result->addEdge(GraphEdge(i, j, d));
@@ -309,8 +306,8 @@ Common::SharedPtr<Graph> PathFinder::createGraph() {
 	return result;
 }
 
-Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
-	Common::Array<Vector2i> result;
+Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Math::Vector2d to) {
+	Common::Array<Math::Vector2d> result;
 	if (!_walkboxes.empty()) {
 		// find the walkbox where the actor is and put it first
 		for (uint i = 0; i < _walkboxes.size(); i++) {
@@ -364,7 +361,7 @@ Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
 		_walkgraph.addNode(start);
 
 		for (uint i = 0; i < _walkgraph._concaveVertices.size(); i++) {
-			const Vector2i c = _walkgraph._concaveVertices[i];
+			const Math::Vector2d c = _walkgraph._concaveVertices[i];
 			if (inLineOfSight(start, c))
 				_walkgraph.addEdge(GraphEdge(startNodeIndex, i, distance(start, c)));
 		}
@@ -374,7 +371,7 @@ Common::Array<Vector2i> PathFinder::calculatePath(Vector2i start, Vector2i to) {
 		_walkgraph.addNode(to);
 
 		for (uint i = 0; i < _walkgraph._concaveVertices.size(); i++) {
-			const Vector2i c = _walkgraph._concaveVertices[i];
+			const Math::Vector2d c = _walkgraph._concaveVertices[i];
 			if (inLineOfSight(to, c))
 				_walkgraph.addEdge(GraphEdge(i, endNodeIndex, distance(to, c)));
 		}
diff --git a/engines/twp/graph.h b/engines/twp/graph.h
index a6981c7063c..a07c10c275f 100644
--- a/engines/twp/graph.h
+++ b/engines/twp/graph.h
@@ -63,15 +63,15 @@ struct GraphEdge {
 class Graph {
 public:
 	Graph();
-	void addNode(Vector2i node);
+	void addNode(Math::Vector2d node);
 	void addEdge(GraphEdge edge);
 	// Gets the edge from 'from' index to 'to' index.
 	GraphEdge *edge(int start, int to);
 	Common::Array<int> getPath(int source, int target);
 
-	Common::Array<Vector2i> _nodes;
+	Common::Array<Math::Vector2d> _nodes;
 	Common::Array<Common::Array<GraphEdge> > _edges;
-	Common::Array<Vector2i> _concaveVertices;
+	Common::Array<Math::Vector2d> _concaveVertices;
 };
 
 class AStar {
@@ -92,12 +92,12 @@ public:
 	Walkbox(const Common::Array<Vector2i> &polygon, bool visible = true);
 
 	// Indicates whether or not the specified position is inside this walkbox.
-	bool contains(Vector2i position, bool toleranceOnOutside = true) const;
+	bool contains(Math::Vector2d position, bool toleranceOnOutside = true) const;
 	bool concave(int vertex) const;
 	void setVisible(bool visible) { _visible = visible; }
 	bool isVisible() const { return _visible; }
 	const Common::Array<Vector2i> &getPoints() const { return _polygon; }
-	Vector2i getClosestPointOnEdge(Vector2i p) const;
+	Math::Vector2d getClosestPointOnEdge(Math::Vector2d p) const;
 
 public:
 	Common::String _name;
@@ -112,14 +112,14 @@ class PathFinder {
 public:
 	void setWalkboxes(const Common::Array<Walkbox> &walkboxes);
 	Common::Array<Walkbox> getWalkboxes() const { return _walkboxes; }
-	Common::Array<Vector2i> calculatePath(Vector2i start, Vector2i to);
+	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d start, Math::Vector2d to);
 	void setDirty(bool dirty) { _isDirty = dirty; }
 	bool isDirty() const { return _isDirty; }
 	const Graph &getGraph() const { return _walkgraph; }
 
 private:
 	Common::SharedPtr<Graph> createGraph();
-	bool inLineOfSight(Vector2i start, Vector2i to);
+	bool inLineOfSight(Math::Vector2d start, Math::Vector2d to);
 
 private:
 	Common::Array<Walkbox> _walkboxes;
diff --git a/engines/twp/motor.cpp b/engines/twp/motor.cpp
index 37b9f25e919..4eac93b1b60 100644
--- a/engines/twp/motor.cpp
+++ b/engines/twp/motor.cpp
@@ -178,12 +178,12 @@ void ReachAnim::update(float elapsed) {
 	}
 }
 
-WalkTo::WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing)
+WalkTo::WalkTo(Common::SharedPtr<Object> obj, Math::Vector2d dest, int facing)
 	: _obj(obj), _facing(facing) {
 	if (obj->_useWalkboxes) {
-		_path = obj->_room->calculatePath((Vector2i)obj->_node->getAbsPos(), dest);
+		_path = obj->_room->calculatePath(obj->_node->getAbsPos(), dest);
 	} else {
-		_path = {(Vector2i)obj->_node->getAbsPos(), dest};
+		_path = {obj->_node->getAbsPos(), dest};
 	}
 
 	_wsd = sqrt(obj->_walkSpeed.getX() * obj->_walkSpeed.getX() + obj->_walkSpeed.getY() * obj->_walkSpeed.getY());
@@ -251,8 +251,8 @@ void WalkTo::update(float elapsed) {
 	if (!_enabled)
 		return;
 	if (_state == kWalking && !_path.empty()) {
-		Vector2i dest = _path[0];
-		float d = distance(dest, (Vector2i)_obj->_node->getAbsPos());
+		Math::Vector2d dest = _path[0];
+		float d = distance(dest, _obj->_node->getAbsPos());
 
 		// arrived at destination ?
 		if (d < 1.0) {
diff --git a/engines/twp/motor.h b/engines/twp/motor.h
index 01c80ec56d0..12e363e9c69 100644
--- a/engines/twp/motor.h
+++ b/engines/twp/motor.h
@@ -224,10 +224,10 @@ enum WalkToState {
 
 class WalkTo : public Motor {
 public:
-	WalkTo(Common::SharedPtr<Object> obj, Vector2i dest, int facing = 0);
+	WalkTo(Common::SharedPtr<Object> obj, Math::Vector2d dest, int facing = 0);
 	void disable() override;
 
-	const Common::Array<Vector2i> &getPath() const { return _path; }
+	const Common::Array<Math::Vector2d> &getPath() const { return _path; }
 
 private:
 	void actorArrived();
@@ -235,7 +235,7 @@ private:
 
 private:
 	Common::SharedPtr<Object> _obj;
-	Common::Array<Vector2i> _path;
+	Common::Array<Math::Vector2d> _path;
 	int _facing = 0;
 	float _wsd;
 	WalkToState _state = kWalking;
diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 71a5b7e5652..5317b2baab3 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -735,7 +735,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 				return;
 			}
 			// Did we get close enough?
-			float dist = distance((Vector2i)obj->getUsePos(), (Vector2i)noun1->getUsePos());
+			float dist = distance(obj->getUsePos(), noun1->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debugC(kDebugGame, "actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
@@ -753,7 +753,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 				obj->_exec.enabled = false;
 				return;
 			}
-			float dist = distance((Vector2i)obj->getUsePos(), (Vector2i)noun2->getUsePos());
+			float dist = distance(obj->getUsePos(), noun2->getUsePos());
 			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
 			debugC(kDebugGame, "actorArrived: noun2 min_dist: %f > %f ?", dist, min_dist);
 			if (dist > min_dist) {
@@ -769,8 +769,8 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 }
 
 // Walks an actor to the `pos` or actor `obj` and then faces `dir`.
-void Object::walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing) {
-	debugC(kDebugGame, "walk to obj %s: %d,%d, %d", obj->_key.c_str(), pos.x, pos.y, facing);
+void Object::walk(Common::SharedPtr<Object> obj, Math::Vector2d pos, int facing) {
+	debugC(kDebugGame, "walk to obj %s: %f,%f, %d", obj->_key.c_str(), pos.getX(), pos.getY(), facing);
 	if (!obj->_walkTo || (!obj->_walkTo->isEnabled())) {
 		obj->play(obj->getAnimName(WALK_ANIMNAME), true);
 	}
@@ -781,7 +781,7 @@ void Object::walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing) {
 void Object::walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj) {
 	debugC(kDebugGame, "walk to obj %s: (%f,%f)", obj->_key.c_str(), obj->getUsePos().getX(), obj->getUsePos().getY());
 	int facing = static_cast<int>(obj->_useDir);
-	walk(actor, (Vector2i)obj->getUsePos(), facing);
+	walk(actor, obj->getUsePos(), facing);
 }
 
 void Object::turn(Facing facing) {
diff --git a/engines/twp/object.h b/engines/twp/object.h
index b5a4fbe9736..0e3fcb86985 100644
--- a/engines/twp/object.h
+++ b/engines/twp/object.h
@@ -199,7 +199,7 @@ public:
 	void setReach(Common::SharedPtr<Motor> reach);
 	Common::SharedPtr<Motor> getWalkTo() { return _walkTo; }
 	Common::SharedPtr<Motor> getReach() { return _reach; }
-	static void walk(Common::SharedPtr<Object> obj, Vector2i pos, int facing = 0);
+	static void walk(Common::SharedPtr<Object> obj, Math::Vector2d pos, int facing = 0);
 	static void walk(Common::SharedPtr<Object> actor, Common::SharedPtr<Object> obj);
 
 	void setTalking(Common::SharedPtr<Motor> talking);
diff --git a/engines/twp/room.cpp b/engines/twp/room.cpp
index dabb2caa0d2..14f8dadff45 100644
--- a/engines/twp/room.cpp
+++ b/engines/twp/room.cpp
@@ -462,7 +462,7 @@ void Room::walkboxHidden(const Common::String &name, bool hidden) {
 	}
 }
 
-Common::Array<Vector2i> Room::calculatePath(Vector2i frm, Vector2i to) {
+Common::Array<Math::Vector2d> Room::calculatePath(Math::Vector2d frm, Math::Vector2d to) {
 	if (_mergedPolygon.size() > 0) {
 		if (_pathFinder.isDirty()) {
 			_mergedPolygon = merge(_walkboxes);
@@ -491,19 +491,19 @@ Walkbox::Walkbox(const Common::Array<Vector2i> &polygon, bool visible)
 }
 
 bool Walkbox::concave(int vertex) const {
-	Vector2i current = _polygon[vertex];
-	Vector2i next = _polygon[(vertex + 1) % _polygon.size()];
-	Vector2i previous = _polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
+	Math::Vector2d current = (Math::Vector2d)_polygon[vertex];
+	Math::Vector2d next = (Math::Vector2d)_polygon[(vertex + 1) % _polygon.size()];
+	Math::Vector2d previous = (Math::Vector2d)_polygon[vertex == 0 ? _polygon.size() - 1 : vertex - 1];
 
-	Vector2i left{current.x - previous.x, current.y - previous.y};
-	Vector2i right{next.x - current.x, next.y - current.y};
+	Math::Vector2d left{current.getX() - previous.getX(), current.getY() - previous.getY()};
+	Math::Vector2d right{next.getX() - current.getX(), next.getY() - current.getY()};
 
-	float cross = (left.x * right.y) - (left.y * right.x);
+	float cross = (left.getX() * right.getY()) - (left.getY() * right.getX());
 	return cross < 0;
 }
 
-bool Walkbox::contains(Vector2i position, bool toleranceOnOutside) const {
-	Vector2i point = position;
+bool Walkbox::contains(Math::Vector2d position, bool toleranceOnOutside) const {
+	Math::Vector2d point = position;
 	const float epsilon = 2.0f;
 	bool result = false;
 
@@ -511,19 +511,19 @@ bool Walkbox::contains(Vector2i position, bool toleranceOnOutside) const {
 	if (_polygon.size() < 3)
 		return false;
 
-	Vector2i oldPoint(_polygon[_polygon.size() - 1]);
+	Math::Vector2d oldPoint(_polygon[_polygon.size() - 1]);
 	float oldSqDist = distanceSquared(oldPoint, point);
 
 	for (size_t i = 0; i < _polygon.size(); i++) {
-		Vector2i newPoint = _polygon[i];
+		Math::Vector2d newPoint = (Math::Vector2d)_polygon[i];
 		float newSqDist = distanceSquared(newPoint, point);
 
 		if (oldSqDist + newSqDist + 2.0f * sqrt(oldSqDist * newSqDist) - distanceSquared(newPoint, oldPoint) < epsilon)
 			return toleranceOnOutside;
 
-		Vector2i left;
-		Vector2i right;
-		if (newPoint.x > oldPoint.x) {
+		Math::Vector2d left;
+		Math::Vector2d right;
+		if (newPoint.getX() > oldPoint.getX()) {
 			left = oldPoint;
 			right = newPoint;
 		} else {
@@ -531,7 +531,7 @@ bool Walkbox::contains(Vector2i position, bool toleranceOnOutside) const {
 			right = oldPoint;
 		}
 
-		if ((left.x < point.x) && (point.x <= right.x) && ((point.y - left.y) * (right.x - left.x)) < ((right.y - left.y) * (point.x - left.x)))
+		if ((left.getX() < point.getX()) && (point.getX() <= right.getX()) && ((point.getY() - left.getY()) * (right.getX() - left.getX())) < ((right.getY() - left.getY()) * (point.getX() - left.getX())))
 			result = !result;
 
 		oldPoint = newPoint;
diff --git a/engines/twp/room.h b/engines/twp/room.h
index cfa174b4a54..a386a421806 100644
--- a/engines/twp/room.h
+++ b/engines/twp/room.h
@@ -127,7 +127,7 @@ public:
 	Color getOverlay() const;
 
 	void walkboxHidden(const Common::String &name, bool hidden);
-	Common::Array<Vector2i> calculatePath(Vector2i frm, Vector2i to);
+	Common::Array<Math::Vector2d> calculatePath(Math::Vector2d frm, Math::Vector2d to);
 
 public:
 	Common::String _name;                             // Name of the room
diff --git a/engines/twp/roomlib.cpp b/engines/twp/roomlib.cpp
index feef126fa9d..e25b6042ef7 100644
--- a/engines/twp/roomlib.cpp
+++ b/engines/twp/roomlib.cpp
@@ -50,7 +50,7 @@ static SQInteger addTrigger(HSQUIRRELVM v) {
 
 static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 	SQInteger numArgs = sq_gettop(v);
-	Vector2i pos1, pos2;
+	Math::Vector2d pos1, pos2;
 	if (numArgs == 3) {
 		SQInteger x = 0;
 		if (SQ_FAILED(sqget(v, 2, x)))
@@ -58,7 +58,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 		SQInteger y = 0;
 		if (SQ_FAILED(sqget(v, 3, y)))
 			return sq_throwerror(v, "failed to get y");
-		pos1 = Vector2i(static_cast<int>(x), static_cast<int>(y));
+		pos1 = Math::Vector2d(x, y);
 		pos2 = pos1;
 	} else if (numArgs == 5) {
 		SQInteger x1 = 0;
@@ -67,14 +67,14 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 		SQInteger y1 = 0;
 		if (SQ_FAILED(sqget(v, 3, y1)))
 			return sq_throwerror(v, "failed to get y1");
-		pos1 = Vector2i(static_cast<int>(x1), static_cast<int>(y1));
+		pos1 = Math::Vector2d(x1, y1);
 		SQInteger x2 = 0;
 		if (SQ_FAILED(sqget(v, 4, x2)))
 			return sq_throwerror(v, "failed to get x2");
 		SQInteger y2 = 0;
 		if (SQ_FAILED(sqget(v, 5, y1)))
 			return sq_throwerror(v, "failed to get y2");
-		pos2 = Vector2i(static_cast<int>(x2), static_cast<int>(y2));
+		pos2 = Math::Vector2d(x2, y2);
 	} else {
 		return sq_throwerror(v, "Invalid argument number in clampInWalkbox");
 	}
@@ -86,7 +86,7 @@ static SQInteger clampInWalkbox(HSQUIRRELVM v) {
 			return 1;
 		}
 	}
-	Vector2i pos = walkboxes[0].getClosestPointOnEdge(pos2);
+	Math::Vector2d pos = walkboxes[0].getClosestPointOnEdge(pos2);
 	sqpush(v, pos);
 	return 1;
 }
diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 03145e862e8..57a2770456c 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -244,7 +244,7 @@ void TwpEngine::clickedAt(Math::Vector2d scrPos) {
 					// Just clicking on the ground
 					cancelSentence(_actor);
 					if (_actor->_room == _room)
-						Object::walk(_actor, (Vector2i)roomPos);
+						Object::walk(_actor, roomPos);
 					_hud->_verb = _hud->actorSlot(_actor)->verbs[0];
 					_holdToMove = true;
 				}
@@ -515,8 +515,8 @@ void TwpEngine::update(float elapsed) {
 					if (_holdToMove && (_time > _nextHoldToMoveTime)) {
 						walkFast();
 						cancelSentence(_actor);
-						if (_actor->_room == _room && (distance((Vector2i)_actor->_node->getAbsPos(), (Vector2i)roomPos) > 5)) {
-							Object::walk(_actor, (Vector2i)roomPos);
+						if (_actor->_room == _room && (distance(_actor->_node->getAbsPos(), roomPos) > 5)) {
+							Object::walk(_actor, roomPos);
 						}
 						_nextHoldToMoveTime = _time + 0.250f;
 					}
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 38bd7dadb12..064ac94e390 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -130,29 +130,29 @@ void parseObjectAnimations(const Common::JSONArray &jAnims, Common::Array<Object
 	}
 }
 
-float distanceSquared(Vector2i p1, Vector2i p2) {
-	const float dx = p1.x - p2.x;
-	const float dy = p1.y - p2.y;
+float distanceSquared(Math::Vector2d p1, Math::Vector2d p2) {
+	const float dx = p1.getX() - p2.getX();
+	const float dy = p1.getY() - p2.getY();
 	return dx * dx + dy * dy;
 }
 
-float distanceToSegmentSquared(Vector2i p, Vector2i v, Vector2i w) {
+float distanceToSegmentSquared(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
 	const float l2 = distanceSquared(v, w);
 	if (l2 == 0)
 		return distanceSquared(p, v);
-	const float t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
+	const float t = ((p.getX() - v.getX()) * (w.getX() - v.getX()) + (p.getY() - v.getY()) * (w.getY() - v.getY())) / l2;
 	if (t < 0)
 		return distanceSquared(p, v);
 	if (t > 1)
 		return distanceSquared(p, w);
-	return distanceSquared(p, Vector2i(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)));
+	return distanceSquared(p, Math::Vector2d(v.getX() + t * (w.getX() - v.getX()), v.getY() + t * (w.getY() - v.getY())));
 }
 
-float distanceToSegment(Vector2i p, Vector2i v, Vector2i w) {
+float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w) {
 	return sqrt(distanceToSegmentSquared(p, v, w));
 }
 
-float distance(Vector2i p1, Vector2i p2) {
+float distance(Math::Vector2d p1, Math::Vector2d p2) {
 	return sqrt(distanceSquared(p1, p2));
 }
 
diff --git a/engines/twp/util.h b/engines/twp/util.h
index e6bb3c3e274..72213cc7f74 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -118,9 +118,9 @@ Common::String replaceAll(const Common::String &s, const Common::String &what, c
 // math util
 void scale(Math::Matrix4 &m, const Math::Vector2d &v);
 Math::Vector2d operator*(Math::Vector2d v, float f);
-float distance(Vector2i p1, Vector2i p2);
-float distanceSquared(Vector2i p1, Vector2i p2);
-float distanceToSegment(Vector2i p, Vector2i v, Vector2i w);
+float distance(Math::Vector2d p1, Math::Vector2d p2);
+float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
+float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
 
 } // namespace Twp
 
diff --git a/engines/twp/walkboxnode.cpp b/engines/twp/walkboxnode.cpp
index d9e0c777b7a..6851943e6ee 100644
--- a/engines/twp/walkboxnode.cpp
+++ b/engines/twp/walkboxnode.cpp
@@ -97,11 +97,11 @@ PathNode::PathNode() : Node("Path") {
 	_zOrder = -1000;
 }
 
-Vector2i PathNode::fixPos(Vector2i pos) {
+Math::Vector2d PathNode::fixPos(Math::Vector2d pos) {
 	for (size_t i = 0; i < g_twp->_room->_mergedPolygon.size(); i++) {
 		Walkbox &wb = g_twp->_room->_mergedPolygon[i];
 		if (!wb.isVisible() && wb.contains(pos)) {
-			return wb.getClosestPointOnEdge((Vector2i)pos);
+			return wb.getClosestPointOnEdge(pos);
 		}
 	}
 	//   for wb in gEngine.room.mergedPolygon:
@@ -123,7 +123,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 	// draw actor path
 	if (((_mode == PathMode::GraphMode) || (_mode == PathMode::All)) && actor && actor->getWalkTo()) {
 		const WalkTo *walkTo = (WalkTo *)actor->getWalkTo().get();
-		const Common::Array<Vector2i> &path = walkTo->getPath();
+		const Common::Array<Math::Vector2d> &path = walkTo->getPath();
 		if (path.size() > 0) {
 			Common::Array<Vertex> vertices;
 			vertices.push_back(Vertex(g_twp->roomToScreen(actor->_node->getPos()), yellow));
@@ -173,7 +173,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 
 		const Math::Vector2d scrPos = g_twp->winToScreen(g_twp->_cursor.pos);
 		const Math::Vector2d roomPos = g_twp->screenToRoom(scrPos);
-		Vector2i p = fixPos((Vector2i)roomPos);
+		Math::Vector2d p = fixPos(roomPos);
 		t = Math::Matrix4();
 		pos = g_twp->roomToScreen((Math::Vector2d)p) - Math::Vector2d(4.f, 4.f);
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
@@ -187,7 +187,7 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 			g_twp->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), red, t);
 		}
 
-		const Common::Array<Vector2i> path = g_twp->_room->calculatePath(fixPos((Vector2i)actor->_node->getPos()), p);
+		const Common::Array<Math::Vector2d> path = g_twp->_room->calculatePath(fixPos(actor->_node->getPos()), p);
 		Common::Array<Vertex> vertices;
 		for (uint i = 0; i < path.size(); i++) {
 			vertices.push_back(Vertex(g_twp->roomToScreen((Math::Vector2d)path[i]), yellow));
@@ -201,14 +201,14 @@ void PathNode::drawCore(Math::Matrix4 trsf) {
 		if (walkboxes.empty())
 			return;
 
-		const bool inside = (walkboxes.size() > 0) && walkboxes[0].contains((Vector2i)roomPos);
+		const bool inside = (walkboxes.size() > 0) && walkboxes[0].contains(roomPos);
 		pos = scrPos - Math::Vector2d(4.f, 4.f);
 		t = Math::Matrix4();
 		t.translate(Math::Vector3d(pos.getX(), pos.getY(), 0.f));
 		g_twp->getGfx().drawQuad(Math::Vector2d(8.f, 8.f), inside ? green : red, t);
 
 		// draw a blue square on the closest point
-		pos = g_twp->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge((Vector2i)roomPos));
+		pos = g_twp->roomToScreen((Math::Vector2d)walkboxes[0].getClosestPointOnEdge(roomPos));
 		t = Math::Matrix4();
 		t.translate(Math::Vector3d(pos.getX() - 2.f, pos.getY() - 2.f, 0.f));
 		g_twp->getGfx().drawQuad(Math::Vector2d(4.f, 4.f), blue, t);
diff --git a/engines/twp/walkboxnode.h b/engines/twp/walkboxnode.h
index d97734f4646..b38b0404193 100644
--- a/engines/twp/walkboxnode.h
+++ b/engines/twp/walkboxnode.h
@@ -60,7 +60,7 @@ public:
 	PathMode getMode() const { return _mode; }
 
 private:
-	Vector2i fixPos(Vector2i pos);
+	Math::Vector2d fixPos(Math::Vector2d pos);
 	virtual void drawCore(Math::Matrix4 trsf) override;
 
 private:


Commit: 0041949337c97fa67eef750c93a5c93a1da45752
    https://github.com/scummvm/scummvm/commit/0041949337c97fa67eef750c93a5c93a1da45752
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Move static functions to util

Changed paths:
    engines/twp/graph.cpp
    engines/twp/util.cpp
    engines/twp/util.h


diff --git a/engines/twp/graph.cpp b/engines/twp/graph.cpp
index f4bdee72264..522556471fa 100644
--- a/engines/twp/graph.cpp
+++ b/engines/twp/graph.cpp
@@ -23,51 +23,6 @@
 
 namespace Twp {
 
-static float dot(Math::Vector2d u, Math::Vector2d v) {
-	return (u.getX() * v.getX()) + (u.getY() * v.getY());
-}
-
-static float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
-
-static bool lineSegmentsCross(Math::Vector2d a, Math::Vector2d b, Math::Vector2d c, Math::Vector2d d) {
-	const float EPSILON = 1e-3f;
-	const float denominator = ((b.getX() - a.getX()) * (d.getY() - c.getY())) - ((b.getY() - a.getY()) * (d.getX() - c.getX()));
-	if (abs(denominator) < EPSILON) {
-		return false;
-	}
-
-	const float numerator1 = ((a.getY() - c.getY()) * (d.getX() - c.getX())) - ((a.getX() - c.getX()) * (d.getY() - c.getY()));
-	const float numerator2 = ((a.getY() - c.getY()) * (b.getX() - a.getX())) - ((a.getX() - c.getX()) * (b.getY() - a.getY()));
-	if ((abs(numerator1) < EPSILON) || (abs(numerator2) < EPSILON)) {
-		return false;
-	}
-
-	const float r = numerator1 / denominator;
-	const float s = numerator2 / denominator;
-	return ((r > 0.f) && (r < 1.f)) && ((s > 0.f) && (s < 1.f));
-}
-
-static Common::Array<int> reverse(const Common::Array<int> &arr) {
-	Common::Array<int> result(arr.size());
-	for (size_t i = 0; i < arr.size(); i++) {
-		result[arr.size() - 1 - i] = arr[i];
-	}
-	return result;
-}
-
-static uint minIndex(const Common::Array<float> &values) {
-	if(values.empty()) return (uint)-1;
-	float min = values[0];
-	uint index = 0;
-	for (uint i = 1; i < values.size(); i++) {
-		if (values[i] < min) {
-			index = i;
-			min = values[i];
-		}
-	}
-	return index;
-}
-
 IndexedPriorityQueue::IndexedPriorityQueue(Common::Array<float> &keys)
 	: _keys(keys) {
 }
@@ -327,7 +282,7 @@ Common::Array<Math::Vector2d> PathFinder::calculatePath(Math::Vector2d start, Ma
 				dists[i] = distance(wb.getClosestPointOnEdge(start), start);
 			}
 
-			const uint index = minIndex(dists);
+			const size_t index = minIndex(dists);
 			if (index != 0) {
 				_graph.reset();
 				SWAP(_walkboxes[0], _walkboxes[index]);
diff --git a/engines/twp/util.cpp b/engines/twp/util.cpp
index 064ac94e390..3b5d3bb7bb4 100644
--- a/engines/twp/util.cpp
+++ b/engines/twp/util.cpp
@@ -197,4 +197,28 @@ void scale(Math::Matrix4 &m, const Math::Vector2d &v) {
 	m(1, 1) *= v.getY();
 }
 
+float dot(Math::Vector2d u, Math::Vector2d v) {
+	return (u.getX() * v.getX()) + (u.getY() * v.getY());
+}
+
+float length(Math::Vector2d v) { return sqrt(dot(v, v)); }
+
+bool lineSegmentsCross(Math::Vector2d a, Math::Vector2d b, Math::Vector2d c, Math::Vector2d d) {
+	const float EPSILON = 1e-3f;
+	const float denominator = ((b.getX() - a.getX()) * (d.getY() - c.getY())) - ((b.getY() - a.getY()) * (d.getX() - c.getX()));
+	if (abs(denominator) < EPSILON) {
+		return false;
+	}
+
+	const float numerator1 = ((a.getY() - c.getY()) * (d.getX() - c.getX())) - ((a.getX() - c.getX()) * (d.getY() - c.getY()));
+	const float numerator2 = ((a.getY() - c.getY()) * (b.getX() - a.getX())) - ((a.getX() - c.getX()) * (b.getY() - a.getY()));
+	if ((abs(numerator1) < EPSILON) || (abs(numerator2) < EPSILON)) {
+		return false;
+	}
+
+	const float r = numerator1 / denominator;
+	const float s = numerator2 / denominator;
+	return ((r > 0.f) && (r < 1.f)) && ((s > 0.f) && (s < 1.f));
+}
+
 } // namespace Twp
diff --git a/engines/twp/util.h b/engines/twp/util.h
index 72213cc7f74..17a7da32481 100644
--- a/engines/twp/util.h
+++ b/engines/twp/util.h
@@ -110,6 +110,29 @@ size_t find(const Common::Array<Common::SharedPtr<T> > &array, const T *o) {
 	return (size_t)-1;
 }
 
+template<typename T>
+size_t minIndex(const Common::Array<T> &values) {
+	if(values.empty()) return (size_t)-1;
+	T min = values[0];
+	size_t index = 0;
+	for (size_t i = 1; i < values.size(); i++) {
+		if (values[i] < min) {
+			index = i;
+			min = values[i];
+		}
+	}
+	return index;
+}
+
+template<typename T>
+Common::Array<T> reverse(const Common::Array<T> &arr) {
+	Common::Array<T> result(arr.size());
+	for (size_t i = 0; i < arr.size(); i++) {
+		result[arr.size() - 1 - i] = arr[i];
+	}
+	return result;
+}
+
 // string util
 Common::String join(const Common::Array<Common::String> &array, const Common::String &sep);
 Common::String remove(const Common::String &txt, char startC, char endC);
@@ -121,6 +144,9 @@ Math::Vector2d operator*(Math::Vector2d v, float f);
 float distance(Math::Vector2d p1, Math::Vector2d p2);
 float distanceSquared(Math::Vector2d p1, Math::Vector2d p2);
 float distanceToSegment(Math::Vector2d p, Math::Vector2d v, Math::Vector2d w);
+float dot(Math::Vector2d u, Math::Vector2d v);
+float length(Math::Vector2d v);
+bool lineSegmentsCross(Math::Vector2d a, Math::Vector2d b, Math::Vector2d c, Math::Vector2d d);
 
 } // namespace Twp
 


Commit: e6a9096a99f3d660736555ba14df2d875b8deffa
    https://github.com/scummvm/scummvm/commit/e6a9096a99f3d660736555ba14df2d875b8deffa
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix randomFrom crash

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index 90cd6381e4a..df715c61e6f 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -572,7 +572,9 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		HSQOBJECT obj;
 		sq_resetobject(&obj);
 		SQInteger size = sq_getsize(v, 2);
+		assert(size > 0);
 		int index = g_twp->getRandomSource().getRandomNumber(size - 1);
+		assert(index >= 0);
 		int i = 0;
 		sq_push(v, 2);  // array
 		sq_pushnull(v); // null iterator
@@ -590,7 +592,8 @@ static SQInteger randomFrom(HSQUIRRELVM v) {
 		sq_pushobject(v, obj);
 	} else {
 		SQInteger size = sq_gettop(v);
-		int index = g_twp->getRandomSource().getRandomNumber(size - 3);
+		int index = g_twp->getRandomSource().getRandomNumber(size - 2);
+		assert(index >= 0);
 		sq_push(v, 2 + index);
 	}
 	return 1;


Commit: eb04a3a159278677706d00fc7c467c39cb6f6c65
    https://github.com/scummvm/scummvm/commit/eb04a3a159278677706d00fc7c467c39cb6f6c65
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix never ending breakwhiletalking

Changed paths:
    engines/twp/syslib.cpp


diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 574e2d28188..5cdd5a4a1ca 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -164,12 +164,12 @@ static SQInteger addFolder(HSQUIRRELVM v) {
 
 static void threadFrames(Common::SharedPtr<ThreadBase> tb, void *data) {
 	int numFrames = *(int *)data;
-	((Thread *)tb.get())->_numFrames = numFrames;
+	tb->_numFrames = numFrames;
 }
 
 static void threadTime(Common::SharedPtr<ThreadBase> tb, void *data) {
 	float time = *(float *)data;
-	((Thread *)tb.get())->_waitTime = time;
+	tb->_waitTime = time;
 }
 
 // When called in a function started with startthread, execution is suspended for count frames.
@@ -352,6 +352,7 @@ static SQInteger breakwhilerunning(HSQUIRRELVM v) {
 static bool isSomeoneTalking() {
 	for (auto it = g_twp->_actors.begin(); it != g_twp->_actors.end(); it++) {
 		Common::SharedPtr<Object> obj = *it;
+		if(obj->_room != g_twp->_room) continue;
 		if (obj->getTalking() && obj->getTalking()->isEnabled())
 			return true;
 	}
@@ -359,6 +360,7 @@ static bool isSomeoneTalking() {
 		Common::SharedPtr<Layer> layer = *it;
 		for (auto it2 = layer->_objects.begin(); it2 != layer->_objects.end(); it2++) {
 			Common::SharedPtr<Object> obj = *it2;
+			if(obj->_room != g_twp->_room) continue;
 			if (obj->getTalking() && obj->getTalking()->isEnabled())
 				return true;
 		}


Commit: 7dd6c4ba6279c63a3fbbc48be811c685053f94bd
    https://github.com/scummvm/scummvm/commit/7dd6c4ba6279c63a3fbbc48be811c685053f94bd
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix give minimum distance

Changed paths:
    engines/twp/object.cpp


diff --git a/engines/twp/object.cpp b/engines/twp/object.cpp
index 5317b2baab3..0b5d3426dac 100644
--- a/engines/twp/object.cpp
+++ b/engines/twp/object.cpp
@@ -27,10 +27,19 @@
 #include "twp/squtil.h"
 
 #define MIN_TALK_DIST 60
+#define MIN_GIVE_DIST 30
 #define MIN_USE_DIST 15
 
 namespace Twp {
 
+static float getVerbDist(VerbId verb) {
+	if(verb.id == VERB_TALKTO)
+		return MIN_TALK_DIST;
+	if(verb.id == VERB_GIVE)
+		return MIN_GIVE_DIST;
+	return MIN_USE_DIST;
+}
+
 enum class BlinkState {
 	Closed,
 	Open
@@ -736,7 +745,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 			}
 			// Did we get close enough?
 			float dist = distance(obj->getUsePos(), noun1->getUsePos());
-			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
+			float min_dist = getVerbDist(verb);
 			debugC(kDebugGame, "actorArrived: noun1 min_dist: %f > %f (actor: {self.getUsePos}, obj: {noun1.getUsePos}) ?", dist, min_dist);
 			if (!verbNotClose(verb) && (dist > min_dist)) {
 				cantReach(noun1, noun2);
@@ -754,7 +763,7 @@ void Object::execVerb(Common::SharedPtr<Object> obj) {
 				return;
 			}
 			float dist = distance(obj->getUsePos(), noun2->getUsePos());
-			float min_dist = verb.id == VERB_TALKTO ? MIN_TALK_DIST : MIN_USE_DIST;
+			float min_dist = getVerbDist(verb);
 			debugC(kDebugGame, "actorArrived: noun2 min_dist: %f > %f ?", dist, min_dist);
 			if (dist > min_dist) {
 				cantReach(noun1, noun2);


Commit: c8e7ce8f61973eec5bfc95c06f02c86e25662b3b
    https://github.com/scummvm/scummvm/commit/c8e7ce8f61973eec5bfc95c06f02c86e25662b3b
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix invalid target in cameraPanTo

Changed paths:
    engines/twp/genlib.cpp


diff --git a/engines/twp/genlib.cpp b/engines/twp/genlib.cpp
index df715c61e6f..5e4a66ac5f9 100644
--- a/engines/twp/genlib.cpp
+++ b/engines/twp/genlib.cpp
@@ -243,10 +243,9 @@ static SQInteger cameraPanTo(HSQUIRRELVM v) {
 	} else {
 		return sq_throwerror(v, Common::String::format("invalid argument number: %lld", numArgs).c_str());
 	}
-	Math::Vector2d halfScreen(g_twp->_room->getScreenSize() / 2.f);
 	debugC(kDebugGenScript, "cameraPanTo: (%f,%f), dur=%f, method=%d", pos.getX(), pos.getY(), duration, interpolation);
 	g_twp->follow(nullptr);
-	g_twp->_camera->panTo(pos - Math::Vector2d(0.f, halfScreen.getY()), duration, interpolation);
+	g_twp->_camera->panTo(pos, duration, interpolation);
 	return 0;
 }
 


Commit: fcf9a7fee6e591015c02f1ca5856ce4e2c45c251
    https://github.com/scummvm/scummvm/commit/fcf9a7fee6e591015c02f1ca5856ce4e2c45c251
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix autosave should be enabled by default

Changed paths:
    engines/twp/savegame.h
    engines/twp/syslib.cpp


diff --git a/engines/twp/savegame.h b/engines/twp/savegame.h
index f8ede3d8d30..9285f5ffa39 100644
--- a/engines/twp/savegame.h
+++ b/engines/twp/savegame.h
@@ -53,7 +53,7 @@ private:
 
 public:
 	bool _allowSaveGame = true;
-	bool _autoSave = false;
+	bool _autoSave = true;
 };
 
 } // namespace Twp
diff --git a/engines/twp/syslib.cpp b/engines/twp/syslib.cpp
index 5cdd5a4a1ca..3ef19768546 100644
--- a/engines/twp/syslib.cpp
+++ b/engines/twp/syslib.cpp
@@ -523,7 +523,7 @@ static SQInteger exCommand(HSQUIRRELVM v) {
 		g_twp->_saveGameManager->_autoSave = enabled != 0;
 	} break;
 	case EX_AUTOSAVE: {
-		if (g_twp->_saveGameManager->_autoSave) {
+		if (g_twp->_saveGameManager->_autoSave && g_twp->_saveGameManager->_allowSaveGame) {
 			g_twp->saveGameState(0, "", true);
 		}
 	} break;


Commit: ac597e184bff3b2cce839122f9536a16d743c9dc
    https://github.com/scummvm/scummvm/commit/ac597e184bff3b2cce839122f9536a16d743c9dc
Author: scemino (scemino74 at gmail.com)
Date: 2024-03-07T20:08:26+01:00

Commit Message:
TWP: Fix system mouse cursor still visible

Changed paths:
    engines/twp/twp.cpp
    engines/twp/twp.h


diff --git a/engines/twp/twp.cpp b/engines/twp/twp.cpp
index 57a2770456c..f48e1dc112a 100644
--- a/engines/twp/twp.cpp
+++ b/engines/twp/twp.cpp
@@ -745,13 +745,13 @@ void TwpEngine::draw(RenderTexture *outTexture) {
 	_gfx.use(nullptr);
 
 #ifdef USE_IMGUI
-	ModularGraphicsBackend *sdl_g_system = dynamic_cast<ModularGraphicsBackend *>(g_system);
+	ModularGraphicsBackend *sdl_g_system = dynamic_cast<ModularGraphicsBackend *>(_system);
 	if (sdl_g_system) {
 		sdl_g_system->getGraphicsManager()->renderImGui(onImGuiRender);
 	}
 #endif
 
-	g_system->updateScreen();
+	_system->updateScreen();
 }
 
 void TwpEngine::updateSettingVars() {
@@ -819,6 +819,7 @@ Common::Error TwpEngine::run() {
 	_textDb->parseTsv(entry);
 
 	CursorMan.showMouse(false);
+	g_system->lockMouse(true);
 
 	_vm.reset(new Vm());
 	HSQUIRRELVM v = _vm->get();
@@ -845,7 +846,7 @@ Common::Error TwpEngine::run() {
 	uint time = _system->getMillis();
 	while (!shouldQuit()) {
 		Math::Vector2d camPos = _gfx.cameraPos();
-		while (g_system->getEventManager()->pollEvent(e)) {
+		while (_system->getEventManager()->pollEvent(e)) {
 			switch (e.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START: {
 				switch ((TwpAction)e.customType) {
@@ -1006,7 +1007,7 @@ Common::Error TwpEngine::run() {
 		// Delay for a bit. All events loops should have a delay
 		// to prevent the system being unduly loaded
 		if (delta < 10) {
-			g_system->delayMillis(10 - delta);
+			_system->delayMillis(10 - delta);
 		}
 	}
 
@@ -1768,6 +1769,11 @@ int TwpEngine::runDialog(GUI::Dialog &dialog) {
 	return result;
 }
 
+void TwpEngine::pauseEngineIntern(bool pause) {
+	// Unlock the mouse so that the cursor is usable when the GMM opens
+	_system->lockMouse(!debugChannelSet(-1, kDebugConsole) && !pause);
+}
+
 ScalingTrigger::ScalingTrigger(Common::SharedPtr<Object> obj, Scaling *scaling) : _obj(obj), _scaling(scaling) {}
 
 } // End of namespace Twp
diff --git a/engines/twp/twp.h b/engines/twp/twp.h
index e430bd0815b..500bbf59bef 100644
--- a/engines/twp/twp.h
+++ b/engines/twp/twp.h
@@ -182,6 +182,7 @@ private:
 	ActorSwitcherSlot actorSwitcherSlot(ActorSlot *slot);
 	Scaling *getScaling(const Common::String &name);
 	void skipCutscene();
+	void pauseEngineIntern(bool pause) override;
 
 private:
 	unique_ptr<Vm> _vm;




More information about the Scummvm-git-logs mailing list