[Scummvm-git-logs] scummvm master -> 3fd36166ba9f64f54ff7f199b73c5c5f5f0469ae

mduggan mgithub at guarana.org
Sat Dec 26 23:46:49 UTC 2020


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

Summary:
2b3864e2ce ULTIMA8: Fix compiler warning
ba4e20b075 ULTIMA8: Fix offset to Crusader attack data
23aed36cb6 ULTIMA8: Add convenience function for gump visibility and close notification
19397a7b62 ULTIMA8: Slight refactor of MovieGump
d66745b5fd ULTIMA8: Add Weasel (shop) gump for Crusader
9e8b97159d ULTIMA8: Workaround to avoid No Regret crashing
00b0438c34 ULTIMA8: Fixes for Remorse startup
a3340fe6a6 ULTIMA8: Fix Crusader KeypadGump to actually work
b566a9b2e3 ULTIMA8: Delete some crusader hacks from Actor
d0b9f00be8 ULTIMA8: Hide Crusader status gumps in movies
cc1ad1749f ULTIMA8: Don't save Crusader stat gumps
3fd36166ba ULTIMA8: Fix Crusader anim dat flags, again


Commit: 2b3864e2cedb32828472a0717f61858969cb1206
    https://github.com/scummvm/scummvm/commit/2b3864e2cedb32828472a0717f61858969cb1206
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:43:37+09:00

Commit Message:
ULTIMA8: Fix compiler warning

Changed paths:
    engines/ultima/ultima8/world/actors/actor.cpp


diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 270640b7ae..4edfb20709 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -1464,9 +1464,9 @@ void Actor::clearInCombat() {
 	clearActorFlag(ACT_INCOMBAT);
 }
 
-int32 Actor::collideMove(int32 x, int32 y, int32 z, bool teleport, bool force,
+int32 Actor::collideMove(int32 x, int32 y, int32 z, bool teleports, bool force,
 						 ObjId *hititem, uint8 *dirs) {
-	int32 result = Item::collideMove(x, y, z, teleport, force, hititem, dirs);
+	int32 result = Item::collideMove(x, y, z, teleports, force, hititem, dirs);
 	if (_objId == 1 && GAME_IS_CRUSADER) {
 		notifyNearbyItems();
 		TargetReticleProcess::get_instance()->avatarMoved();


Commit: ba4e20b075d1498ddbbb5e729c7f8e8491dfa3db
    https://github.com/scummvm/scummvm/commit/ba4e20b075d1498ddbbb5e729c7f8e8491dfa3db
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:22+09:00

Commit Message:
ULTIMA8: Fix offset to Crusader attack data

Changed paths:
    engines/ultima/ultima8/world/actors/attack_process.cpp


diff --git a/engines/ultima/ultima8/world/actors/attack_process.cpp b/engines/ultima/ultima8/world/actors/attack_process.cpp
index 83871801af..8f6bfa7887 100644
--- a/engines/ultima/ultima8/world/actors/attack_process.cpp
+++ b/engines/ultima/ultima8/world/actors/attack_process.cpp
@@ -815,14 +815,14 @@ bool AttackProcess::checkTimer2PlusDelayElapsed(int now) {
 
 void AttackProcess::setAttackData(uint16 off, uint16 val) {
 	if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray) - 1)
-		_dataArray[off] = val;
+		_dataArray[off - MAGIC_DATA_OFF] = val;
 
 	warning("Invalid offset to setAttackDataArray %d %d", off, val);
 }
 
 uint16 AttackProcess::getAttackData(uint16 off) const {
 	if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray) - 1)
-		return _dataArray[off];
+		return _dataArray[off - MAGIC_DATA_OFF];
 
 	warning("Invalid offset to getAttackDataArray: %d", off);
 	return 0;


Commit: 23aed36cb61af208a738879b863d72fbc8befd07
    https://github.com/scummvm/scummvm/commit/23aed36cb61af208a738879b863d72fbc8befd07
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Add convenience function for gump visibility and close notification

Changed paths:
    engines/ultima/ultima8/gumps/gump.cpp
    engines/ultima/ultima8/gumps/gump.h


diff --git a/engines/ultima/ultima8/gumps/gump.cpp b/engines/ultima/ultima8/gumps/gump.cpp
index 8845fafcfc..93b08bb6e5 100644
--- a/engines/ultima/ultima8/gumps/gump.cpp
+++ b/engines/ultima/ultima8/gumps/gump.cpp
@@ -123,12 +123,14 @@ void Gump::Close(bool no_del) {
 	}
 	_notifier = 0;
 
+	_flags |= FLAG_CLOSING;
 	if (!_parent) {
-		_flags |= FLAG_CLOSING;
-		if (!no_del) delete this;
+		if (!no_del)
+			delete this;
 	} else {
-		_flags |= FLAG_CLOSING;
-		if (!no_del) _flags |= FLAG_CLOSE_AND_DEL;
+		_parent->ChildNotify(this, Gump::GUMP_CLOSING);
+		if (!no_del)
+			_flags |= FLAG_CLOSE_AND_DEL;
 	}
 }
 
diff --git a/engines/ultima/ultima8/gumps/gump.h b/engines/ultima/ultima8/gumps/gump.h
index cd4efb696b..93f8e8c88a 100644
--- a/engines/ultima/ultima8/gumps/gump.h
+++ b/engines/ultima/ultima8/gumps/gump.h
@@ -38,8 +38,8 @@ class Item;
 class GumpNotifyProcess;
 
 class Gump;
-typedef bool (*FindGumpPredicate)(Gump *g);
-template<class T> inline bool IsOfType(Gump *g) { return dynamic_cast<T*>(g) != nullptr; }
+typedef bool (*FindGumpPredicate)(const Gump *g);
+template<class T> inline bool IsOfType(const Gump *g) { return dynamic_cast<const T*>(g) != nullptr; }
 
 /**
  * A Gump is a single GUI element within the game, like the backpack window, menu,
@@ -445,6 +445,12 @@ public:
 	virtual void UnhideGump() {
 		_flags &= ~FLAG_HIDDEN;
 	}
+	void SetVisibility(bool visible) {
+		if (visible)
+			UnhideGump();
+		else
+			HideGump();
+	}
 
 	bool mustSave(bool toplevel) const;
 
@@ -460,6 +466,10 @@ public:
 		LAYER_CONSOLE       = 16        // Layer for the console
 	};
 
+	enum Message {
+		GUMP_CLOSING = 0x100
+	};
+
 	bool loadData(Common::ReadStream *rs, uint32 version);
 	void saveData(Common::WriteStream *ws) override;
 };


Commit: 19397a7b622235204a575cdafe0c08601e77caaa
    https://github.com/scummvm/scummvm/commit/19397a7b622235204a575cdafe0c08601e77caaa
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Slight refactor of MovieGump

Changed paths:
    engines/ultima/ultima8/gumps/movie_gump.cpp


diff --git a/engines/ultima/ultima8/gumps/movie_gump.cpp b/engines/ultima/ultima8/gumps/movie_gump.cpp
index 01c1745dd1..cba32bb5e4 100644
--- a/engines/ultima/ultima8/gumps/movie_gump.cpp
+++ b/engines/ultima/ultima8/gumps/movie_gump.cpp
@@ -161,6 +161,22 @@ static Std::string _fixCrusaderMovieName(const Std::string &s) {
 	return s;
 }
 
+static Common::SeekableReadStream *_tryLoadCruMovie(const Std::string &filename) {
+	const Std::string path = Std::string::format("@game/flics/%s.avi", filename.c_str());
+	FileSystem *filesys = FileSystem::get_instance();
+	Common::SeekableReadStream *rs = filesys->ReadFile(path);
+	if (!rs) {
+		// Try with a "0" in the name
+		const Std::string adjustedfn = Std::string::format("@game/flics/0%s.avi", filename.c_str());
+		rs = filesys->ReadFile(adjustedfn);
+		if (!rs) {
+			warning("movie %s not found", filename.c_str());
+			return 0;
+		}
+	}
+	return rs;
+}
+
 uint32 MovieGump::I_playMovieOverlay(const uint8 *args,
         unsigned int /*argsize*/) {
 	ARG_ITEM_FROM_PTR(item);
@@ -178,16 +194,12 @@ uint32 MovieGump::I_playMovieOverlay(const uint8 *args,
 		const Palette *pal = palman->getPalette(PaletteManager::Pal_Game);
 		assert(pal);
 
-		const Std::string filename = Std::string::format("@game/flics/%s.avi", name.c_str());
-		FileSystem *filesys = FileSystem::get_instance();
-		Common::SeekableReadStream *rs = filesys->ReadFile(filename);
-		if (!rs) {
-			warning("couldn't create gump for unknown movie %s", name.c_str());
-			return 0;
+		Common::SeekableReadStream *rs = _tryLoadCruMovie(name);
+		if (rs) {
+			Gump *gump = new MovieGump(x, y, rs, false, pal->_palette);
+			gump->InitGump(nullptr, true);
+			gump->setRelativePosition(CENTER);
 		}
-		Gump *gump = new MovieGump(x, y, rs, false, pal->_palette);
-		gump->InitGump(nullptr, true);
-		gump->setRelativePosition(CENTER);
 	}
 
 	return 0;
@@ -199,24 +211,14 @@ uint32 MovieGump::I_playMovieCutscene(const uint8 *args, unsigned int /*argsize*
 	ARG_UINT16(x);
 	ARG_UINT16(y);
 
-	FileSystem *filesys = FileSystem::get_instance();
 	if (item) {
-		const Std::string filename = Std::string::format("@game/flics/%s.avi", name.c_str());
-		Common::SeekableReadStream *rs = filesys->ReadFile(filename);
-		if (!rs) {
-			// Try with a "0" in the name
-			const Std::string adjustedfn = Std::string::format("@game/flics/0%s.avi", name.c_str());
-			rs = filesys->ReadFile(adjustedfn);
-			if (!rs) {
-				warning("I_playMovieCutscene: movie %s not found", name.c_str());
-				return 0;
-			}
+		Common::SeekableReadStream *rs = _tryLoadCruMovie(name);
+		if (rs) {
+			// TODO: Support playback with gap lines for the CRT effect
+			Gump *gump = new MovieGump(x * 3, y * 3, rs, false);
+			gump->InitGump(nullptr, true);
+			gump->setRelativePosition(CENTER);
 		}
-
-		// TODO: Support playback with gap lines for the CRT effect
-		Gump *gump = new MovieGump(x * 3, y * 3, rs, false);
-		gump->InitGump(nullptr, true);
-		gump->setRelativePosition(CENTER);
 	}
 
 	return 0;


Commit: d66745b5fde4482cd8f1ba4819fe5dcd28b3c4a3
    https://github.com/scummvm/scummvm/commit/d66745b5fde4482cd8f1ba4819fe5dcd28b3c4a3
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Add Weasel (shop) gump for Crusader

Changed paths:
  A engines/ultima/ultima8/gumps/weasel_dat.cpp
  A engines/ultima/ultima8/gumps/weasel_dat.h
  A engines/ultima/ultima8/gumps/weasel_gump.cpp
  A engines/ultima/ultima8/gumps/weasel_gump.h
    engines/ultima/module.mk
    engines/ultima/ultima8/games/game_data.cpp
    engines/ultima/ultima8/games/game_data.h
    engines/ultima/ultima8/ultima8.cpp
    engines/ultima/ultima8/usecode/remorse_intrinsics.h


diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index ac343490e0..8d3b0c7737 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -498,6 +498,8 @@ MODULE_OBJS := \
 	ultima8/gumps/target_gump.o \
 	ultima8/gumps/translucent_gump.o \
 	ultima8/gumps/u8_save_gump.o \
+	ultima8/gumps/weasel_dat.o \
+	ultima8/gumps/weasel_gump.o \
 	ultima8/gumps/widgets/button_widget.o \
 	ultima8/gumps/widgets/edit_widget.o \
 	ultima8/gumps/widgets/sliding_widget.o \
diff --git a/engines/ultima/ultima8/games/game_data.cpp b/engines/ultima/ultima8/games/game_data.cpp
index 1a4987fdf2..152ea29af2 100644
--- a/engines/ultima/ultima8/games/game_data.cpp
+++ b/engines/ultima/ultima8/games/game_data.cpp
@@ -41,6 +41,7 @@
 #include "ultima/ultima8/conf/config_file_manager.h"
 #include "ultima/ultima8/graphics/fonts/font_manager.h"
 #include "ultima/ultima8/games/game_info.h"
+#include "ultima/ultima8/gumps/weasel_dat.h"
 #include "ultima/ultima8/conf/setting_manager.h"
 #include "ultima/ultima8/convert/crusader/convert_shape_crusader.h"
 #include "ultima/ultima8/audio/music_flex.h"
@@ -504,6 +505,13 @@ const CombatDat *GameData::getCombatDat(uint16 entry) const {
 	return nullptr;
 }
 
+const WeaselDat *GameData::getWeaselDat(uint16 entry) const {
+	if (entry < _weaselData.size()) {
+		return _weaselData[entry];
+	}
+	return nullptr;
+}
+
 const FireType *GameData::getFireType(uint16 type) const {
 	return FireTypeTable::get(type);
 }
@@ -671,6 +679,10 @@ void GameData::loadRemorseData() {
 	// 14 blocks of 323 bytes, references like W01 and I07
 	// (presumably weapon and inventory)
 	// shop data?
+	while (!stuffds->eos()) {
+		WeaselDat *data = new WeaselDat(stuffds);
+		_weaselData.push_back(data);
+	}
 
 	delete stuffds;
 
diff --git a/engines/ultima/ultima8/games/game_data.h b/engines/ultima/ultima8/games/game_data.h
index d0a54f9814..f53866bdc5 100644
--- a/engines/ultima/ultima8/games/game_data.h
+++ b/engines/ultima/ultima8/games/game_data.h
@@ -43,6 +43,7 @@ class NPCDat;
 class CombatDat;
 class FireType;
 class ShapeFrame;
+class WeaselDat;
 class SoundFlex;
 class SpeechFlex;
 struct GameInfo;
@@ -101,6 +102,8 @@ public:
 
 	const FireType *getFireType(uint16 type) const;
 
+	const WeaselDat *getWeaselDat(uint16 level) const;
+
 	Std::string translate(const Std::string &text);
 	FrameID translate(FrameID frame);
 
@@ -125,6 +128,7 @@ private:
 	WpnOvlayDat *_weaponOverlay;
 	Std::vector<NPCDat *> _npcTable;
 	Std::vector<CombatDat *> _combatData;
+	Std::vector<WeaselDat *> _weaselData;
 
 	SoundFlex *_soundFlex;
 	Std::vector<SpeechFlex **> _speech;
diff --git a/engines/ultima/ultima8/gumps/weasel_dat.cpp b/engines/ultima/ultima8/gumps/weasel_dat.cpp
new file mode 100644
index 0000000000..2accff62b7
--- /dev/null
+++ b/engines/ultima/ultima8/gumps/weasel_dat.cpp
@@ -0,0 +1,80 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima8/gumps/weasel_dat.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+static const int BLOCKS = 20;
+
+WeaselDat::WeaselDat(Common::ReadStream *rs) {
+	uint16 numentries = rs->readUint16LE();
+	if (numentries > BLOCKS)
+		numentries = BLOCKS;
+
+	// each block is 16 bytes
+	for (uint i = 0; i < numentries; i++) {
+		WeaselEntry entry;
+		// 4 byte string ID
+		for (int j = 0; j < 4; j++)
+			entry._id[j] = rs->readByte();
+
+		// Unknown 4 bytes
+		rs->readUint16LE();
+		rs->readUint16LE();
+
+		// Shapeno (2 bytes)
+		entry._shapeNo = rs->readUint16LE();
+		// Cost (2 bytes)
+		entry._cost = rs->readUint16LE();
+		entry._entryNo = rs->readUint16LE();
+		entry._unk = rs->readUint16LE();
+		if (entry._id[0] == 'W')
+			entry._type = kWeapon;
+		else if (entry._id[0] == 'I')
+			entry._type = kItem;
+		else
+			entry._type = kUnknown;
+
+		_items.push_back(entry);
+	}
+
+	const uint skip = (BLOCKS - numentries) * 16;
+	for (uint i = 0; i < skip; i++)
+		rs->readByte();
+}
+
+uint16 WeaselDat::getNumOfType(WeaselType type) const {
+	int count = 0;
+	for (Std::vector<WeaselEntry>::const_iterator iter = _items.begin(); iter != _items.end(); iter++) {
+		if (iter->_type == type)
+			count++;
+	}
+	return count;
+}
+
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima8/gumps/weasel_dat.h b/engines/ultima/ultima8/gumps/weasel_dat.h
new file mode 100644
index 0000000000..67a8c8e9cb
--- /dev/null
+++ b/engines/ultima/ultima8/gumps/weasel_dat.h
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA8_GUMPS_WEASELDAT_H
+#define ULTIMA8_GUMPS_WEASELDAT_H
+
+#include "common/stream.h"
+#include "ultima/shared/std/containers.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+/**
+ * Data for the Weasel (shop) gump on a single level. Contains a list of things you can buy.
+ */
+class WeaselDat {
+public:
+
+	enum WeaselType {
+		kUnknown,
+		kWeapon,
+		kItem,
+	};
+
+	/** A single item in the shop */
+	struct WeaselEntry {
+		char _id[4];		// eg, "W01", "I02", etc
+		uint16 _shapeNo;
+		uint32 _cost;
+		uint16 _entryNo;
+		uint16 _unk;
+		enum WeaselType _type;
+	};
+
+	WeaselDat(Common::ReadStream *rs);
+
+	uint16 getNumItems() const {
+		return _items.size();
+	}
+
+	uint16 getNumOfType(WeaselType type) const;
+
+	const Std::vector<WeaselEntry> &getItems() const {
+		return _items;
+	}
+
+private:
+	Std::vector<WeaselEntry> _items;
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima8/gumps/weasel_gump.cpp b/engines/ultima/ultima8/gumps/weasel_gump.cpp
new file mode 100644
index 0000000000..31b59b3031
--- /dev/null
+++ b/engines/ultima/ultima8/gumps/weasel_gump.cpp
@@ -0,0 +1,580 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima8/misc/pent_include.h"
+#include "ultima/ultima8/gumps/weasel_gump.h"
+#include "ultima/ultima8/gumps/weasel_dat.h"
+#include "ultima/ultima8/games/game_data.h"
+#include "ultima/ultima8/graphics/gump_shape_archive.h"
+#include "ultima/ultima8/graphics/main_shape_archive.h"
+#include "ultima/ultima8/graphics/shape.h"
+#include "ultima/ultima8/graphics/shape_frame.h"
+#include "ultima/ultima8/ultima8.h"
+#include "ultima/ultima8/gumps/widgets/button_widget.h"
+#include "ultima/ultima8/gumps/widgets/text_widget.h"
+#include "ultima/ultima8/gumps/gump_notify_process.h"
+#include "ultima/ultima8/gumps/movie_gump.h"
+#include "ultima/ultima8/games/game.h"
+#include "ultima/ultima8/world/actors/main_actor.h"
+#include "ultima/ultima8/graphics/fonts/rendered_text.h"
+#include "ultima/ultima8/graphics/palette_manager.h"
+#include "ultima/ultima8/audio/audio_process.h"
+#include "ultima/ultima8/world/get_object.h"
+#include "ultima/ultima8/world/item_factory.h"
+#include "ultima/ultima8/meta_engine.h"
+#include "ultima/ultima8/filesys/file_system.h"
+#include "engines/dialogs.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+DEFINE_RUNTIME_CLASSTYPE_CODE(WeaselGump)
+
+static const uint16 WEASEL_CANT_BUY_SFXNO = 0xb0;
+static const int WEASEL_FONT = 6;
+static const int WEASEL_SHAPE_TOP = 22;
+
+enum WeaselUiElements {
+	kBtnLeft = 0,
+	kBtnBlank = 1,
+	kBtnRight = 2,
+	kBtnYes = 3,
+	kBtnNo = 4,
+	kBtnBuy = 5,
+	kBtnAmmo = 6,
+	kBtnWeapons = 7,
+	kBtnExit = 8,
+	kTxtCredits = 9,
+	kIconItem = 10,
+	kTxtItemName = 11,
+	kTxtItemCost = 12,
+	kTxtItemPurch = 13,
+	kTxtItemOwned = 14,
+	kTxtQuestion = 15
+};
+// Coords and shapes for above list of buttons
+static const int WEASEL_BTN_X[] = { 14,  76, 138,  18, 113,  20,  19,  19,  44};
+static const int WEASEL_BTN_Y[] = {213, 213, 213, 237, 237, 280, 319, 319, 368};
+static const int WEASEL_BTN_SHAPES[] = {13, 26, 14, 16, 15, 28, 27, 83, 29};
+
+static const char *FIRST_INTRO_MOVIE = "17A";
+static const char *INTRO_MOVIES[] = {"18A", "18B", "18C"};
+static const char *BUYMORE_MOVIES[] = {"21A", "21B"};
+static const char *CONFIRM_BUY_MOVIES[] = {"21A", "21B"};
+static const char *CANCELLED_PURCHASE_MOVIES[] = {"19C", "19D"};
+static const char *COMPLETED_PURCHASE_MOVIES[] = {"21C", "21D"};
+static const char *INSUFFICIENT_FUND_MOVIES[] = {"20C", "20D"};
+
+template<int T> bool FindUiElem(const Gump *g) { return g->GetIndex() == T; }
+
+namespace {
+// A small container gump that doesn't do anything except pass notifications to the parent
+class WeaselUIContainerGump : public Gump {
+	void ChildNotify(Gump *child, uint32 message) override {
+		_parent->ChildNotify(child, message);
+	}
+};
+
+static void _closeIfExists(Gump *gump) {
+	if (gump)
+		gump->Close();
+}
+
+static const char *_getRandomMovie(const char **movies, int nmovies) {
+	int offset = Ultima8Engine::get_instance()->getRandomNumber(nmovies - 1);
+	return movies[offset];
+}
+}
+
+bool WeaselGump::_playedIntroMovie = false;
+
+WeaselGump::WeaselGump(uint16 level)
+	: ModalGump(0, 0, 640, 480, 0, FLAG_DONT_SAVE), _credits(0),
+	  _level(level), _state(kWeaselStart), _curItem(0), _ammoMode(false),
+	  _curItemCost(1), _curItemShape(0), _ui(nullptr), _movie(nullptr) {
+	Mouse *mouse = Mouse::get_instance();
+	mouse->pushMouseCursor();
+	mouse->setMouseCursor(Mouse::MOUSE_HAND);
+}
+
+WeaselGump::~WeaselGump() {
+}
+
+
+void WeaselGump::Close(bool no_del) {
+	Mouse *mouse = Mouse::get_instance();
+	mouse->popMouseCursor();
+	ModalGump::Close(no_del);
+}
+
+void WeaselGump::InitGump(Gump *newparent, bool take_focus) {
+	ModalGump::InitGump(newparent, take_focus);
+
+	GumpShapeArchive *shapeArchive = GameData::get_instance()->getGumps();
+
+	Shape *top = shapeArchive->getShape(WEASEL_SHAPE_TOP);
+	Shape *midhi = shapeArchive->getShape(WEASEL_SHAPE_TOP + 1);
+	Shape *midlo = shapeArchive->getShape(WEASEL_SHAPE_TOP + 2);
+	Shape *bot = shapeArchive->getShape(WEASEL_SHAPE_TOP + 3);
+
+	if (!top || !midhi || !midlo || !bot) {
+		error("Couldn't load shapes for weasel");
+		return;
+	}
+
+	const ShapeFrame *tFrame = top->getFrame(0);
+	const ShapeFrame *mhFrame = midhi->getFrame(0);
+	const ShapeFrame *mlFrame = midlo->getFrame(0);
+	const ShapeFrame *bFrame = bot->getFrame(0);
+	if (!tFrame || !mhFrame || !mlFrame || !bFrame) {
+		error("Couldn't load shape frames for weasel");
+		return;
+	}
+
+	_ui = new WeaselUIContainerGump();
+	_ui->SetDims(Rect(0, 0, mhFrame->_width,
+					  tFrame->_height + mhFrame->_height + mlFrame->_height + bFrame->_height));
+	_ui->InitGump(this, false);
+	_ui->setRelativePosition(CENTER);
+
+	Gump *tGump = new Gump(3, 0, tFrame->_width, tFrame->_height);
+	tGump->SetShape(top, 0);
+	tGump->InitGump(_ui, false);
+	Gump *mhGump = new Gump(0, tFrame->_height, mhFrame->_width, mhFrame->_height);
+	mhGump->SetShape(midhi, 0);
+	mhGump->InitGump(_ui, false);
+	Gump *mlGump = new Gump(5, tFrame->_height + mhFrame->_height, mlFrame->_width, mlFrame->_height);
+	mlGump->SetShape(midlo, 0);
+	mlGump->InitGump(_ui, false);
+	Gump *bGump = new Gump(9, tFrame->_height + mhFrame->_height + mlFrame->_height, bFrame->_width, bFrame->_height);
+	bGump->SetShape(bot, 0);
+	bGump->InitGump(_ui, false);
+
+	for (int i = 0; i < ARRAYSIZE(WEASEL_BTN_X); i++) {
+		uint32 buttonShapeNum = WEASEL_BTN_SHAPES[i];
+		Shape *buttonShape = shapeArchive->getShape(buttonShapeNum);
+		if (!buttonShape) {
+			error("Couldn't load shape for weasel button %d", i);
+			return;
+		}
+
+		const ShapeFrame *buttonFrame = buttonShape->getFrame(0);
+		if (!buttonFrame || buttonShape->frameCount() != 2) {
+			error("Couldn't load shape frame for weasel button %d", i);
+			return;
+		}
+
+		FrameID frame_up(GameData::GUMPS, buttonShapeNum, 0);
+		FrameID frame_down(GameData::GUMPS, buttonShapeNum, 1);
+		Gump *widget = new ButtonWidget(WEASEL_BTN_X[i], WEASEL_BTN_Y[i], frame_up, frame_down, false);
+		widget->InitGump(_ui, false);
+		widget->SetIndex(i);
+		// some buttons start hidden, the browsingMode() call below does that.
+	}
+
+	MainActor *av = getMainActor();
+	assert(av);
+	Item *item = av->getFirstItemWithShape(0x4ed, true);
+	if (item)
+		_credits = item->getQuality();
+
+	// TODO: remove me (for testing)
+	_credits += 10000;
+
+	_weaselDat = GameData::get_instance()->getWeaselDat(_level);
+	if (!_weaselDat || _weaselDat->getNumItems() == 0)
+		Close();
+}
+
+Gump *WeaselGump::playMovie(const Std::string &filename) {
+	const Std::string path = Std::string::format("@game/flics/%s.avi", filename.c_str());
+	FileSystem *filesys = FileSystem::get_instance();
+	Common::SeekableReadStream *rs = filesys->ReadFile(path);
+	Gump *gump = new MovieGump(600, 450, rs, false);
+	gump->InitGump(this, true);
+	gump->setRelativePosition(CENTER);
+	gump->CreateNotifier();
+	return gump;
+}
+
+void WeaselGump::run() {
+	ModalGump::run();
+	// Don't do much while a movie is playing.
+	if (_movie)
+		return;
+	_ui->UnhideGump();
+	switch (_state) {
+		case kWeaselStart:
+			_state = kWeaselShowIntro;
+			break;
+		case kWeaselShowIntro: {
+			if (_level == 1 && !_playedIntroMovie) {
+				_movie = playMovie(FIRST_INTRO_MOVIE);
+				_playedIntroMovie = true;
+			} else {
+				_movie = playMovie(_getRandomMovie(INTRO_MOVIES, ARRAYSIZE(INTRO_MOVIES)));
+			}
+			_state = kWeaselBrowsing;
+			browsingMode(true);
+			break;
+		}
+		case kWeaselCheckBuyMoreMovie:
+			_movie = playMovie(_getRandomMovie(BUYMORE_MOVIES, ARRAYSIZE(BUYMORE_MOVIES)));
+			_state = kWeaselCheckBuyMoreText;
+			break;
+		case kWeaselCheckBuyMoreText:
+			checkBuyMore();
+			break;
+		case kWeaselClosing:
+			Close();
+			break;
+		case kWeaselConfirmPurchaseMovie:
+			_movie = playMovie(_getRandomMovie(CONFIRM_BUY_MOVIES, ARRAYSIZE(CONFIRM_BUY_MOVIES)));
+			_state = kWeaselConfirmPurchaseText;
+			break;
+		case kWeaselConfirmPurchaseText:
+			confirmPurchase();
+			break;
+		case kWeaselCancelledPurchaseMovie:
+			browsingMode(true);
+			_movie = playMovie(_getRandomMovie(CANCELLED_PURCHASE_MOVIES, ARRAYSIZE(CANCELLED_PURCHASE_MOVIES)));
+			_state = kWeaselBrowsing;
+			break;
+		case kWeaselCompletedPurchase:
+			_movie = playMovie(_getRandomMovie(COMPLETED_PURCHASE_MOVIES, ARRAYSIZE(COMPLETED_PURCHASE_MOVIES)));
+			_state = kWeaselCheckBuyMoreText;
+			break;
+		case kWeaselInsufficientFunds:
+			// TODO: how does it get to this situation?
+			_movie = playMovie(_getRandomMovie(INSUFFICIENT_FUND_MOVIES, ARRAYSIZE(INSUFFICIENT_FUND_MOVIES)));
+			break;
+		case kWeaselBrowsing:
+			_ui->UnhideGump();
+		default:
+			break;
+	}
+	if (_movie) {
+		_ui->HideGump();
+	}
+}
+
+void WeaselGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scaled) {
+	Gump::PaintThis(surf, lerp_factor, scaled);
+}
+
+bool WeaselGump::OnKeyDown(int key, int mod) {
+	if (Gump::OnKeyDown(key, mod)) return true;
+
+	// TODO: support keyboard input
+
+	return true;
+}
+
+void WeaselGump::ChildNotify(Gump *child, uint32 message) {
+	ButtonWidget *buttonWidget = dynamic_cast<ButtonWidget *>(child);
+	MovieGump *movieGump = dynamic_cast<MovieGump *>(child);
+	if (buttonWidget && message == ButtonWidget::BUTTON_CLICK) {
+		onButtonClick(child->GetIndex());
+	} else if (movieGump && message == Gump::GUMP_CLOSING) {
+		// Movie has finished.
+		_movie = nullptr;
+	}
+}
+
+void WeaselGump::onButtonClick(int entry) {
+	switch (entry) {
+	case kBtnWeapons:
+		_ammoMode = false;
+		updateAmmoButtons();
+		break;
+	case kBtnAmmo:
+		_ammoMode = true;
+		updateAmmoButtons();
+		break;
+	case kBtnLeft:
+		prevItem();
+		break;
+	case kBtnRight:
+		nextItem();
+		break;
+	case kBtnBuy:
+		buyItem();
+		break;
+	case kBtnExit:
+		checkClose();
+		break;
+	case kBtnYes:
+		if (_state == kWeaselConfirmPurchaseText)
+			completePurchase();
+		else if (_state == kWeaselCheckBuyMoreText)
+			browsingMode(true);
+		break;
+	case kBtnNo:
+		if (_state == kWeaselConfirmPurchaseText)
+			abortPurchase();
+		else if (_state == kWeaselCheckBuyMoreText)
+			Close();
+		break;
+	case kBtnBlank:
+	default:
+		break;
+	}
+}
+
+void WeaselGump::updateAmmoButtons() {
+	_ammoMode = !_ammoMode;
+	Gump *ammobtn = _ui->FindGump(&FindUiElem<kBtnAmmo>);
+	Gump *wpnbtn = _ui->FindGump(&FindUiElem<kBtnWeapons>);
+	assert(ammobtn && wpnbtn);
+	ammobtn->SetVisibility(_ammoMode);
+	wpnbtn->SetVisibility(!_ammoMode);
+	_curItem = 0;
+	updateItemDisplay();
+}
+
+void WeaselGump::prevItem() {
+	WeaselDat::WeaselType curtype = _ammoMode ? WeaselDat::kItem : WeaselDat::kWeapon;
+	int itemcount = _weaselDat->getNumOfType(curtype);
+	_curItem--;
+	if (_curItem < 0)
+		_curItem = itemcount - 1;
+	updateItemDisplay();
+}
+
+void WeaselGump::nextItem() {
+	WeaselDat::WeaselType curtype = _ammoMode ? WeaselDat::kItem : WeaselDat::kWeapon;
+	int itemcount = _weaselDat->getNumOfType(curtype);
+	_curItem++;
+	if (_curItem >= itemcount)
+		_curItem = 0;
+	updateItemDisplay();
+}
+
+void WeaselGump::buyItem() {
+	if (_curItemCost < _credits) {
+		_purchases.push_back(_curItemShape);
+		_credits -= _curItemCost;
+	} else {
+		AudioProcess::get_instance()->playSFX(WEASEL_CANT_BUY_SFXNO, 0x80, 0, 0);
+	}
+	updateItemDisplay();
+}
+
+void WeaselGump::confirmPurchase() {
+	static const char *confirm = "Are you sure you want to buy this?";
+	setYesNoQuestion(confirm);
+}
+
+void WeaselGump::checkClose() {
+	if (_purchases.size()) {
+		_state = kWeaselConfirmPurchaseMovie;
+	} else {
+		Close();
+	}
+}
+
+void WeaselGump::completePurchase() {
+	assert(_state == kWeaselConfirmPurchaseText);
+	MainActor *av = getMainActor();
+	uint16 mapno = av->getMapNum();
+	assert(av);
+	Item *item = av->getFirstItemWithShape(0x4ed, true);
+	if (item)
+		item->setQuality(_credits);
+	for (Std::vector<uint16>::const_iterator iter = _purchases.begin();
+		 iter != _purchases.end(); iter++) {
+		Item *newitem = ItemFactory::createItem(*iter, 0, 0, 0, 0, mapno, 0, true);
+		av->addItemCru(newitem, false);
+	}
+	_state = kWeaselCompletedPurchase;
+}
+
+void WeaselGump::checkBuyMore() {
+	static const char *buymore = "Do you want anything else?";
+	setYesNoQuestion(buymore);
+}
+
+void WeaselGump::setYesNoQuestion(const Std::string &msg) {
+	browsingMode(false);
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtQuestion>));
+	TextWidget *textWidget = new TextWidget(30, 100, msg, true, WEASEL_FONT, 150);
+	textWidget->InitGump(_ui);
+	textWidget->SetIndex(kTxtQuestion);
+}
+
+void WeaselGump::browsingMode(bool browsing) {
+	_ui->UnhideGump();
+
+	updateAmmoButtons();
+	updateItemDisplay();
+
+	// Note: all these searches are not super effieient but it's
+	// not a time-sensitive function and the search is relatively short
+	Gump *yesbtn = _ui->FindGump(&FindUiElem<kBtnYes>);
+	Gump *nobtn = _ui->FindGump(&FindUiElem<kBtnNo>);
+	Gump *qtxt = _ui->FindGump(&FindUiElem<kTxtQuestion>);
+
+	Gump *buybtn = _ui->FindGump(&FindUiElem<kBtnBuy>);
+	Gump *wpnbtn = _ui->FindGump(&FindUiElem<kBtnWeapons>);
+	Gump *ammobtn = _ui->FindGump(&FindUiElem<kBtnAmmo>);
+	Gump *exitbtn = _ui->FindGump(&FindUiElem<kBtnExit>);
+	Gump *blankbtn = _ui->FindGump(&FindUiElem<kBtnBlank>);
+	Gump *leftbtn = _ui->FindGump(&FindUiElem<kBtnLeft>);
+	Gump *rightbtn = _ui->FindGump(&FindUiElem<kBtnRight>);
+	Gump *credtxt = _ui->FindGump(&FindUiElem<kTxtCredits>);
+	Gump *nametxt = _ui->FindGump(&FindUiElem<kTxtItemName>);
+	Gump *costtxt = _ui->FindGump(&FindUiElem<kTxtItemCost>);
+	Gump *purchtxt = _ui->FindGump(&FindUiElem<kTxtItemPurch>);
+	Gump *ownedtxt = _ui->FindGump(&FindUiElem<kTxtItemOwned>);
+	Gump *icon = _ui->FindGump(&FindUiElem<kIconItem>);
+
+	yesbtn->SetVisibility(!browsing);
+	nobtn->SetVisibility(!browsing);
+	if (qtxt)
+		qtxt->SetVisibility(!browsing);
+
+	buybtn->SetVisibility(browsing);
+	wpnbtn->SetVisibility(browsing && !_ammoMode);
+	ammobtn->SetVisibility(browsing && _ammoMode);
+	exitbtn->SetVisibility(browsing);
+	blankbtn->SetVisibility(browsing);
+	leftbtn->SetVisibility(browsing);
+	rightbtn->SetVisibility(browsing);
+	credtxt->SetVisibility(browsing);
+	nametxt->SetVisibility(browsing);
+	costtxt->SetVisibility(browsing);
+	purchtxt->SetVisibility(browsing);
+	ownedtxt->SetVisibility(browsing);
+	icon->SetVisibility(browsing);
+}
+
+void WeaselGump::abortPurchase() {
+	assert(_state == kWeaselConfirmPurchaseText);
+	_state = kWeaselCancelledPurchaseMovie;
+	_purchases.clear();
+}
+
+int WeaselGump::purchasedCount(uint16 shape) const {
+	int count = 0;
+	for (Std::vector<uint16>::const_iterator iter = _purchases.begin();
+		 iter != _purchases.end(); iter++) {
+		 if (*iter == shape)
+			 count++;
+	}
+	return count;
+}
+
+void WeaselGump::updateItemDisplay() {
+	WeaselDat::WeaselType curtype = _ammoMode ? WeaselDat::kItem : WeaselDat::kWeapon;
+	const Std::vector<WeaselDat::WeaselEntry> &items = _weaselDat->getItems();
+	Std::vector<WeaselDat::WeaselEntry>::const_iterator iter = items.begin();
+
+	int i = 0;
+	for (; iter != items.end(); iter++) {
+		if (iter->_type == curtype) {
+			if (i == _curItem)
+				break;
+			else
+				i++;
+		}
+	}
+
+	// should always find the item..
+	assert(iter != items.end());
+
+	_curItemCost = iter->_cost;
+	_curItemShape = iter->_shapeNo;
+
+	const ShapeInfo *shapeinfo = GameData::get_instance()->getMainShapes()->getShapeInfo(_curItemShape);
+	if (!shapeinfo || !shapeinfo->_weaponInfo) {
+		warning("Weasel: no info for shape %d", _curItemShape);
+	}
+	Shape *shape = GameData::get_instance()->getGumps()->getShape(shapeinfo->_weaponInfo->_displayGumpShape);
+
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtCredits>));
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtItemName>));
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtItemCost>));
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtItemPurch>));
+	_closeIfExists(_ui->FindGump(&FindUiElem<kTxtItemOwned>));
+	_closeIfExists(_ui->FindGump(&FindUiElem<kIconItem>));
+
+	Std::string credstr = Std::string::format("Credits:%d", _credits);
+	TextWidget *textWidget = new TextWidget(30, 57, credstr, true, WEASEL_FONT);
+	textWidget->InitGump(_ui);
+	textWidget->SetIndex(kTxtCredits);
+
+	const ShapeFrame *frame = shape->getFrame(shapeinfo->_weaponInfo->_displayGumpFrame);
+	Gump *icon = new Gump(105 - frame->_xoff, 120 - frame->_yoff, 200, 200);
+	icon->SetShape(shape, shapeinfo->_weaponInfo->_displayGumpFrame);
+	icon->UpdateDimsFromShape();
+	icon->setRelativePosition(Position::CENTER);
+	icon->InitGump(_ui, false);
+	icon->SetIndex(kIconItem);
+
+	Std::string coststr = Std::string::format("Cost:%d", _curItemCost);
+	Std::string purchstr = Std::string::format("Purchased:%02d", purchasedCount(_curItemShape));
+
+	MainActor *av = getMainActor();
+	const Item *item = av->getFirstItemWithShape(_curItemShape, true);
+	int count = 0;
+	if (item) {
+		if (shapeinfo->_family == ShapeInfo::SF_CRUWEAPON) {
+			count = 1;
+		} else {
+			count = item->getQuality();
+		}
+	}
+	Std::string ownedstr = Std::string::format("Owned:%02d", count);
+
+	TextWidget *nametxt = new TextWidget(27, 161, shapeinfo->_weaponInfo->_name, true, WEASEL_FONT);
+	nametxt->InitGump(_ui, false);
+	nametxt->SetIndex(kTxtItemName);
+	TextWidget *costtxt = new TextWidget(27, 171, coststr, true, WEASEL_FONT);
+	costtxt->InitGump(_ui, false);
+	costtxt->SetIndex(kTxtItemCost);
+	TextWidget *purchtxt = new TextWidget(27, 181, purchstr, true, WEASEL_FONT);
+	purchtxt->InitGump(_ui, false);
+	purchtxt->SetIndex(kTxtItemPurch);
+	TextWidget *ownedtxt = new TextWidget(27, 191, ownedstr, true, WEASEL_FONT);
+	ownedtxt->InitGump(_ui, false);
+	ownedtxt->SetIndex(kTxtItemOwned);
+}
+
+bool WeaselGump::OnTextInput(int unicode) {
+	if (Gump::OnTextInput(unicode)) return true;
+
+	return true;
+}
+
+//static
+uint32 WeaselGump::I_showWeaselGump(const uint8 *args, unsigned int /*argsize*/) {
+	ARG_UINT16(level);
+
+	WeaselGump *gump = new WeaselGump(level);
+	gump->InitGump(0);
+	gump->setRelativePosition(CENTER);
+
+	return 0;
+}
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima8/gumps/weasel_gump.h b/engines/ultima/ultima8/gumps/weasel_gump.h
new file mode 100644
index 0000000000..7e6a3e23a8
--- /dev/null
+++ b/engines/ultima/ultima8/gumps/weasel_gump.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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA8_GUMPS_REMORSEMENUGUMP_H
+#define ULTIMA8_GUMPS_REMORSEMENUGUMP_H
+
+#include "ultima/ultima8/gumps/modal_gump.h"
+#include "ultima/ultima8/misc/p_dynamic_cast.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+class WeaselDat;
+
+/**
+ * Weasel weapon seller Crusader.
+ */
+class WeaselGump : public ModalGump {
+public:
+	ENABLE_RUNTIME_CLASSTYPE()
+
+	enum WeaselGumpState {
+		kWeaselStart,
+		kWeaselConfirmPurchaseMovie,
+		kWeaselConfirmPurchaseText,
+		kWeaselCancelledPurchaseMovie,
+		kWeaselCancelledPurchaseText,
+		kWeaselCompletedPurchase,
+		kWeaselInsufficientFunds,
+		kWeaselBrowsing,
+		kWeaselClosing,
+		kWeaselCheckBuyMoreMovie,
+		kWeaselCheckBuyMoreText,
+		kWeaselShowIntro
+	};
+
+	WeaselGump(uint16 level);
+	~WeaselGump() override;
+
+	// Init the gump, call after construction
+	void InitGump(Gump *newparent, bool take_focus = true) override;
+	void Close(bool no_del = false) override;
+
+	void run() override;
+
+	// Paint the Gump
+	void PaintThis(RenderSurface *, int32 lerp_factor, bool scaled) override;
+
+	bool OnKeyDown(int key, int mod) override;
+	bool OnTextInput(int unicode) override;
+	void ChildNotify(Gump *child, uint32 message) override;
+
+	static uint32 I_showWeaselGump(const uint8 *args, unsigned int /*argsize*/);
+
+private:
+
+	void onButtonClick(int entry);
+
+	void prevItem();
+	void nextItem();
+	void buyItem();
+	void updateAmmoButtons();
+	void checkClose();
+	void completePurchase();
+	void abortPurchase();
+	void checkBuyMore();
+	void confirmPurchase();
+	void setYesNoQuestion(const Std::string &msg);
+	void browsingMode(bool browsing);
+	int purchasedCount(uint16 shape) const;
+
+	void updateItemDisplay();
+	Gump *playMovie(const Std::string &filename);
+
+	/// Gump to hold all the UI items (not the movies)
+	Gump *_ui;
+
+	/// Gump for playing movies
+	Gump *_movie;
+
+	/// The menu of items on offer
+	uint16 _level;
+
+	/// Current gump state
+	WeaselGumpState _state;
+
+	const WeaselDat *_weaselDat;
+
+	/// Remaining balance including pending purchases
+	int32 _credits;
+
+	/// The list of pending purchases (shape nums)
+	Std::vector<uint16> _purchases;
+
+	/// The current item num being browsed
+	int _curItem;
+
+	/// Whether we're browsing ammo or weapons
+	bool _ammoMode;
+
+	/// Cost of current item
+	int32 _curItemCost;
+
+	/// Shape of current item
+	uint16 _curItemShape;
+
+	static bool _playedIntroMovie;
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima8/ultima8.cpp b/engines/ultima/ultima8/ultima8.cpp
index 913c748d3c..0805181c96 100644
--- a/engines/ultima/ultima8/ultima8.cpp
+++ b/engines/ultima/ultima8/ultima8.cpp
@@ -63,6 +63,7 @@
 #include "ultima/ultima8/gumps/menu_gump.h"
 #include "ultima/ultima8/gumps/cru_status_gump.h"
 #include "ultima/ultima8/gumps/movie_gump.h"
+#include "ultima/ultima8/gumps/weasel_gump.h"
 
 // For gump positioning... perhaps shouldn't do it this way....
 #include "ultima/ultima8/gumps/bark_gump.h"
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index 08994a618d..dd1faf6aa1 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -362,7 +362,7 @@ Intrinsic RemorseIntrinsics[] = {
 	Actor::I_setActivity, // void Intrinsic131(6 bytes)
 	Item::I_andStatus, // void Intrinsic132(6 bytes)
 	Item::I_getQHi,  // based on same coff set as 026
-    0, // void Intrinsic134(2 bytes)
+    WeaselGump::I_showWeaselGump, // void Intrinsic134(2 bytes)
     Actor::I_setDead,
     0, // void UNUSEDInt136()
     0  // void UNUSEDInt137()


Commit: 9e8b97159dd780858eaeeca46de9ebf09b9f70cd
    https://github.com/scummvm/scummvm/commit/9e8b97159dd780858eaeeca46de9ebf09b9f70cd
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Workaround to avoid No Regret crashing

Changed paths:
    engines/ultima/ultima8/world/item.cpp


diff --git a/engines/ultima/ultima8/world/item.cpp b/engines/ultima/ultima8/world/item.cpp
index 37cdad8185..99e4610b7c 100644
--- a/engines/ultima/ultima8/world/item.cpp
+++ b/engines/ultima/ultima8/world/item.cpp
@@ -1316,6 +1316,7 @@ uint16 Item::fireDistance(Item *other, Direction dir, int16 xoff, int16 yoff, in
 			else
 				anim = Animation::kneelAndFireLargeWeapon;
 		} else {
+			// TODO: fire2 seems to be different in Regret, check me.
 			if (ma || smallwpn)
 				anim = Animation::attack;
 			else
@@ -1324,7 +1325,8 @@ uint16 Item::fireDistance(Item *other, Direction dir, int16 xoff, int16 yoff, in
 
 		bool first_offsets = false;
 		const AnimAction *action = GameData::get_instance()->getMainShapes()->getAnim(_shape, static_cast<int32>(anim));
-		for (unsigned int i = 0; i < action->getSize(); i++) {
+		unsigned int nframes = action ? action->getSize() : 0;
+		for (unsigned int i = 0; i < nframes; i++) {
 			const AnimFrame &frame = action->getFrame(dir, i);
 			if (frame.is_cruattack()) {
 				if (!first_offsets) {


Commit: 00b0438c34e78c504224c41080537f11aaf5068d
    https://github.com/scummvm/scummvm/commit/00b0438c34e78c504224c41080537f11aaf5068d
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Fixes for Remorse startup

Changed paths:
    engines/ultima/ultima8/games/start_crusader_process.cpp


diff --git a/engines/ultima/ultima8/games/start_crusader_process.cpp b/engines/ultima/ultima8/games/start_crusader_process.cpp
index b89ef76375..1225babc01 100644
--- a/engines/ultima/ultima8/games/start_crusader_process.cpp
+++ b/engines/ultima/ultima8/games/start_crusader_process.cpp
@@ -89,26 +89,23 @@ void StartCrusaderProcess::run() {
 	Ultima8Engine::get_instance()->setCheatMode(true);
 
 	if (!_skipStart) {
+		MainActor *avatar = getMainActor();
+		int mapnum = avatar->getMapNum();
+
+		// These items are the same in Regret and Remorse
+		Item *datalink = ItemFactory::createItem(0x4d4, 0, 0, 0, 0, mapnum, 0, true);
+		avatar->addItemCru(datalink, false);
+		Item *smiley = ItemFactory::createItem(0x598, 0, 0, 0, 0, mapnum, 0, true);
+		smiley->moveToContainer(avatar);
+
 		if (GAME_IS_REMORSE) {
-			MainActor *avatar = getMainActor();
-			int mapnum = avatar->getMapNum();
-			// The game doesn't do the weapon this way, but it's the same for our purposes..
-			Item *weapon = ItemFactory::createItem(0x32E, 0, 0, 0, 0, mapnum, 0, true);
-			avatar->addItemCru(weapon, false);
-			Item *datalink = ItemFactory::createItem(0x4d4, 0, 0, 0, 0, mapnum, 0, true);
-			avatar->addItemCru(datalink, false);
-			Item *smiley = ItemFactory::createItem(0x598, 0, 0, 0, 0, mapnum, 0, true);
-			smiley->moveToContainer(avatar);
-
-			avatar->setDir(dir_east);
+			// TODO: The game actually teleports to egg 0x1e (30) which has another
+			// egg to teleport to egg 99.  Is there any purpose to that?
+			Kernel::get_instance()->addProcess(new TeleportToEggProcess(1, 99));
 		} else if (GAME_IS_REGRET) {
-			// TODO: Give the appropriate startup objects to the avatar
+			Kernel::get_instance()->addProcess(new TeleportToEggProcess(1, 0x1e));
 		}
 
-		// TODO: The game actually teleports to egg 0x1f (31) which has another
-		// egg to teleport to egg 99.  Is there any purpose to that?
-		Kernel::get_instance()->addProcess(new TeleportToEggProcess(1, 99));
-
 		Process *fader = new PaletteFaderProcess(0x003F3F3F, true, 0x7FFF, 60, false);
 		Kernel::get_instance()->addProcess(fader);
 	}


Commit: a3340fe6a6a68ddc0053c2bbb90da64ec4d3c2e4
    https://github.com/scummvm/scummvm/commit/a3340fe6a6a68ddc0053c2bbb90da64ec4d3c2e4
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:36+09:00

Commit Message:
ULTIMA8: Fix Crusader KeypadGump to actually work

Changed paths:
    engines/ultima/ultima8/gumps/keypad_gump.cpp
    engines/ultima/ultima8/gumps/keypad_gump.h


diff --git a/engines/ultima/ultima8/gumps/keypad_gump.cpp b/engines/ultima/ultima8/gumps/keypad_gump.cpp
index 5c8381fd0d..3ca0483b60 100644
--- a/engines/ultima/ultima8/gumps/keypad_gump.cpp
+++ b/engines/ultima/ultima8/gumps/keypad_gump.cpp
@@ -28,18 +28,27 @@
 #include "ultima/ultima8/graphics/gump_shape_archive.h"
 #include "ultima/ultima8/graphics/shape.h"
 #include "ultima/ultima8/graphics/shape_frame.h"
+#include "ultima/ultima8/kernel/kernel.h"
 #include "ultima/ultima8/ultima8.h"
 #include "ultima/ultima8/gumps/desktop_gump.h"
 #include "ultima/ultima8/gumps/widgets/button_widget.h"
 #include "ultima/ultima8/gumps/widgets/text_widget.h"
+#include "ultima/ultima8/usecode/uc_process.h"
 
 namespace Ultima {
 namespace Ultima8 {
 
 DEFINE_RUNTIME_CLASSTYPE_CODE(KeypadGump)
 
-KeypadGump::KeypadGump(int targetValue): ModalGump(0, 0, 5, 5),
-		_value(0), _targetValue(targetValue) {
+template<int T> bool FindUiElem(const Gump *g) { return g->GetIndex() == T; }
+
+static const int TXT_CONTAINER_IDX = 0x100;
+// Actually the max val where we will allow another digit to be entered
+static const int MAX_CODE_VAL = 9999999;
+static const int CHEAT_CODE_VAL = 74697689;
+
+KeypadGump::KeypadGump(int targetValue, uint16 ucnotifypid): ModalGump(0, 0, 5, 5),
+		_value(0), _targetValue(targetValue), _ucNotifyPid(ucnotifypid) {
 	Mouse *mouse = Mouse::get_instance();
 	mouse->pushMouseCursor();
 	mouse->setMouseCursor(Mouse::MOUSE_HAND);
@@ -73,6 +82,8 @@ void KeypadGump::InitGump(Gump *newparent, bool take_focus) {
 			_buttons[bnum] = widget->getObjId();
 		}
 	}
+	// Default result is 0xff
+	SetResult(0xff);
 }
 
 void KeypadGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scaled) {
@@ -83,7 +94,26 @@ void KeypadGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scaled)
 bool KeypadGump::OnKeyDown(int key, int mod) {
 	switch (key) {
 	case Common::KEYCODE_ESCAPE: {
+		_value = 0xff;
 		Close();
+		break;
+	}
+	case Common::KEYCODE_0:
+	case Common::KEYCODE_1:
+	case Common::KEYCODE_2:
+	case Common::KEYCODE_3:
+	case Common::KEYCODE_4:
+	case Common::KEYCODE_5:
+	case Common::KEYCODE_6:
+	case Common::KEYCODE_7:
+	case Common::KEYCODE_8:
+	case Common::KEYCODE_9: {
+		onDigit(key - (int)Common::KEYCODE_0);
+		updateDigitDisplay();
+		AudioProcess *audio = AudioProcess::get_instance();
+		if (audio)
+			audio->playSFX(0x3b, 0x10, _objId, 1);
+		break;
 	}
 	break;
 	default:
@@ -93,40 +123,94 @@ bool KeypadGump::OnKeyDown(int key, int mod) {
 	return true;
 }
 
+void KeypadGump::onDigit(int digit) {
+	assert(digit >= 0 && digit <= 9);
+	if (_value < MAX_CODE_VAL) {
+		_value *= 10;
+		_value += digit;
+	}
+}
+
 void KeypadGump::ChildNotify(Gump *child, uint32 message) {
 	//ObjId cid = child->getObjId();
+	bool update = true;
 	if (message == ButtonWidget::BUTTON_CLICK) {
-		uint16 sfxno = 0;
+		uint16 sfxno = 0x3b;
 		int buttonNo = child->GetIndex();
 		if (buttonNo < 9) {
-			_value *= 10;
-			_value += buttonNo + 1;
-			sfxno = 0x3b;
+			onDigit(buttonNo + 1);
 		} else if (buttonNo == 10) {
-			_value *= 10;
-			sfxno = 0x3b;
+			onDigit(0);
 		} else if (buttonNo == 9) {
 			_value /= 10;
 			sfxno = 0x3a;
 		} else if (buttonNo == 11) {
-			SetResult(_value);
-			// TODO: Do something as a result of this other than just play a sound.
-			if (_value == _targetValue) {
+			update = false;
+			if (_value == _targetValue || _value == CHEAT_CODE_VAL) {
 				sfxno = 0x32;
-				Close();
-				// Note: careful, this is now deleted.
+				SetResult(_value);
 			} else {
 				// wrong.
 				sfxno = 0x31;
-				_value = 0;
+				SetResult(0);
 			}
+			Close();
+			// Note: careful, this is now deleted.
 		}
 		AudioProcess *audio = AudioProcess::get_instance();
 		if (audio && sfxno)
 			audio->playSFX(sfxno, 0x10, _objId, 1);
 	}
+	if (update) {
+		updateDigitDisplay();
+	}
+}
+
+void KeypadGump::updateDigitDisplay() {
+	Gump *txt = Gump::FindGump(&FindUiElem<TXT_CONTAINER_IDX>);
+	if (txt)
+		txt->Close();
+	txt = new Gump(25, 12, 200, 12);
+	txt->InitGump(this);
+	txt->SetIndex(TXT_CONTAINER_IDX);
+
+	Std::vector<Gump *> digits;
+	Shape *digitshape = GameData::get_instance()->getGumps()->getShape(12);
+	int val = _value;
+	while (val) {
+		int digitval = val % 10;
+		if (digitval == 0)
+			digitval = 10;
+		Gump *digit = new Gump(0, 0, 6, 12);
+		digit->SetShape(digitshape, digitval - 1);
+		digit->InitGump(txt);
+		digits.push_back(digit);
+		val /= 10;
+	}
+
+	int xoff = 0;
+	while (digits.size()) {
+		Gump *digit = digits.back();
+		digits.pop_back();
+		digit->setRelativePosition(Position::TOP_LEFT, xoff);
+		xoff += 6;
+	}
+}
+
+void KeypadGump::Close(bool no_del) {
+	_processResult = _value;
+
+	if (_ucNotifyPid) {
+		UCProcess *ucp = dynamic_cast<UCProcess *>(Kernel::get_instance()->getProcess(_ucNotifyPid));
+		assert(ucp);
+		ucp->setReturnValue(_value);
+		ucp->wakeUp(_value);
+	}
+
+	ModalGump::Close(no_del);
 }
 
+
 bool KeypadGump::OnTextInput(int unicode) {
 	if (!(unicode & 0xFF80)) {
 		//char c = unicode & 0x7F;
@@ -137,12 +221,17 @@ bool KeypadGump::OnTextInput(int unicode) {
 
 uint32 KeypadGump::I_showKeypad(const uint8 *args, unsigned int /*argsize*/) {
 	ARG_UINT16(target)
-	ModalGump *gump = new KeypadGump(target);
+
+	UCProcess *current = dynamic_cast<UCProcess *>(Kernel::get_instance()->getRunningProcess());
+	assert(current);
+
+	ModalGump *gump = new KeypadGump(target, current->getPid());
 	gump->InitGump(0);
 	gump->setRelativePosition(CENTER);
-	gump->CreateNotifier();
 
-	return gump->GetNotifyProcess()->getPid();
+	current->suspend();
+
+	return 0;
 }
 
 bool KeypadGump::loadData(Common::ReadStream *rs) {
diff --git a/engines/ultima/ultima8/gumps/keypad_gump.h b/engines/ultima/ultima8/gumps/keypad_gump.h
index bb8b75e3c9..2ce472ab22 100644
--- a/engines/ultima/ultima8/gumps/keypad_gump.h
+++ b/engines/ultima/ultima8/gumps/keypad_gump.h
@@ -37,9 +37,11 @@ class KeypadGump : public ModalGump {
 public:
 	ENABLE_RUNTIME_CLASSTYPE()
 
-	KeypadGump(int targetValue);
+	KeypadGump(int targetValue, uint16 ucnotifypid);
 	~KeypadGump() override;
 
+	void Close(bool no_del=false) override;
+
 	void InitGump(Gump *newparent, bool take_focus = true) override;
 
 	void PaintThis(RenderSurface *, int32 lerp_factor, bool scaled) override;
@@ -54,10 +56,15 @@ public:
 	void saveData(Common::WriteStream *ws) override;
 
 protected:
+	void updateDigitDisplay();
+
+	void onDigit(int digit);
+
 	ObjId _buttons[12];
 
 	int _value;
 	int _targetValue;
+	uint16 _ucNotifyPid;
 };
 
 } // End of namespace Ultima8


Commit: b566a9b2e3c3aa78386a5e6abfc4e424c42a1b54
    https://github.com/scummvm/scummvm/commit/b566a9b2e3c3aa78386a5e6abfc4e424c42a1b54
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:53+09:00

Commit Message:
ULTIMA8: Delete some crusader hacks from Actor

Changed paths:
    engines/ultima/ultima8/world/actors/actor.cpp
    engines/ultima/ultima8/world/actors/actor.h


diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 4edfb20709..9628ab4fc7 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -462,8 +462,6 @@ void Actor::teleport(int newmap, int32 newx, int32 newy, int32 newz) {
 		_y = newy;
 		_z = newz;
 	}
-	if (GAME_IS_CRUSADER)
-		notifyNearbyItems();
 }
 
 uint16 Actor::doAnim(Animation::Sequence anim, Direction dir, unsigned int steps) {
@@ -1467,39 +1465,13 @@ void Actor::clearInCombat() {
 int32 Actor::collideMove(int32 x, int32 y, int32 z, bool teleports, bool force,
 						 ObjId *hititem, uint8 *dirs) {
 	int32 result = Item::collideMove(x, y, z, teleports, force, hititem, dirs);
-	if (_objId == 1 && GAME_IS_CRUSADER) {
-		notifyNearbyItems();
+	if (this == getControlledActor() && GAME_IS_CRUSADER) {
 		TargetReticleProcess::get_instance()->avatarMoved();
 		ItemSelectionProcess::get_instance()->avatarMoved();
 	}
 	return result;
 }
 
-static Std::set<uint16> _notifiedItems;
-
-void Actor::notifyNearbyItems() {
-	/*
-	TODO: This is not right - maybe we want to trigger each item only when it gets close,
-	then reset the status after it moves away?  Need to dig into the assembly more.
-
-	For now this is a temporary hack to trigger some usecode events so we can
-	debug more of the game.
-	 */
-	/*
-	UCList uclist(2);
-	LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
-	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
-	currentmap->areaSearch(&uclist, script, sizeof(script), this, 0x80, false);
-
-	for (unsigned int i = 0; i < uclist.getSize(); ++i) {
-		Item *item = getItem(uclist.getuint16(i));
-		if (_notifiedItems.find(item->getObjId()) != _notifiedItems.end())
-			continue;
-		item->callUsecodeEvent_equipWithParam(_objId);
-		_notifiedItems.insert(item->getObjId());
-	}*/
-}
-
 bool Actor::activeWeaponIsSmall() const {
 	const Item *wpn = getItem(_activeWeapon);
 	if (wpn) {
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index 5a97a25bc9..7358e69c34 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -201,9 +201,6 @@ public:
 	//! check if NPCs are near which are in combat mode and hostile
 	bool areEnemiesNear();
 
-	//! check if NPCs are near which are in combat mode and hostile
-	void notifyNearbyItems();
-
 	//! starts an activity
 	//! \return processID of process handling the activity or zero
 	uint16 setActivity(int activity);


Commit: d0b9f00be86199b53b87f96d12ce283d833080b4
    https://github.com/scummvm/scummvm/commit/d0b9f00be86199b53b87f96d12ce283d833080b4
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:45:53+09:00

Commit Message:
ULTIMA8: Hide Crusader status gumps in movies

Changed paths:
    engines/ultima/ultima8/gumps/movie_gump.cpp


diff --git a/engines/ultima/ultima8/gumps/movie_gump.cpp b/engines/ultima/ultima8/gumps/movie_gump.cpp
index cba32bb5e4..57dd4234df 100644
--- a/engines/ultima/ultima8/gumps/movie_gump.cpp
+++ b/engines/ultima/ultima8/gumps/movie_gump.cpp
@@ -36,6 +36,7 @@
 #include "ultima/ultima8/world/item.h"
 #include "ultima/ultima8/gumps/desktop_gump.h"
 #include "ultima/ultima8/gumps/gump_notify_process.h"
+#include "ultima/ultima8/gumps/cru_status_gump.h"
 
 #include "ultima/ultima8/filesys/file_system.h"
 
@@ -72,11 +73,21 @@ void MovieGump::InitGump(Gump *newparent, bool take_focus) {
 
 	Mouse::get_instance()->pushMouseCursor();
 	Mouse::get_instance()->setMouseCursor(Mouse::MOUSE_NONE);
+
+	CruStatusGump *statusgump = CruStatusGump::get_instance();
+	if (statusgump) {
+		statusgump->HideGump();
+	}
 }
 
 void MovieGump::Close(bool no_del) {
 	Mouse::get_instance()->popMouseCursor();
 
+	CruStatusGump *statusgump = CruStatusGump::get_instance();
+	if (statusgump) {
+		statusgump->UnhideGump();
+	}
+
 	_player->stop();
 
 	ModalGump::Close(no_del);


Commit: cc1ad1749fdd4d21f317f5bed52abfdb6202cb94
    https://github.com/scummvm/scummvm/commit/cc1ad1749fdd4d21f317f5bed52abfdb6202cb94
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:46:02+09:00

Commit Message:
ULTIMA8: Don't save Crusader stat gumps

Changed paths:
    engines/ultima/ultima8/gumps/cru_stat_gump.cpp
    engines/ultima/ultima8/gumps/translucent_gump.cpp


diff --git a/engines/ultima/ultima8/gumps/cru_stat_gump.cpp b/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
index 01557fdb1f..1c041353d4 100644
--- a/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
+++ b/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
@@ -40,7 +40,7 @@ CruStatGump::CruStatGump() : TranslucentGump() {
 }
 
 CruStatGump::CruStatGump(Shape *shape, int x)
-	: TranslucentGump(x, 0, 5, 5, 0) {
+	: TranslucentGump(x, 0, 5, 5, 0, FLAG_DONT_SAVE) {
 	_shape = shape;
 }
 
diff --git a/engines/ultima/ultima8/gumps/translucent_gump.cpp b/engines/ultima/ultima8/gumps/translucent_gump.cpp
index ea7d25fbc9..c916ca328f 100644
--- a/engines/ultima/ultima8/gumps/translucent_gump.cpp
+++ b/engines/ultima/ultima8/gumps/translucent_gump.cpp
@@ -33,7 +33,7 @@ TranslucentGump::TranslucentGump() : Gump() {
 
 TranslucentGump::TranslucentGump(int x, int y, int width, int height,
 								 uint16 owner, uint32 flags, int32 layer) :
-	Gump(x, y, width, height, owner, flags ,layer) {
+	Gump(x, y, width, height, owner, flags, layer) {
 }
 
 TranslucentGump::~TranslucentGump() {


Commit: 3fd36166ba9f64f54ff7f199b73c5c5f5f0469ae
    https://github.com/scummvm/scummvm/commit/3fd36166ba9f64f54ff7f199b73c5c5f5f0469ae
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-12-27T08:46:02+09:00

Commit Message:
ULTIMA8: Fix Crusader anim dat flags, again

Changed paths:
    engines/ultima/ultima8/graphics/anim_dat.cpp
    engines/ultima/ultima8/world/actors/anim_action.h


diff --git a/engines/ultima/ultima8/graphics/anim_dat.cpp b/engines/ultima/ultima8/graphics/anim_dat.cpp
index 4ced82e13b..d05e5be74b 100644
--- a/engines/ultima/ultima8/graphics/anim_dat.cpp
+++ b/engines/ultima/ultima8/graphics/anim_dat.cpp
@@ -189,11 +189,19 @@ void AnimDat::load(Common::SeekableReadStream *rs) {
 			a->_actions[action]->_size = actionsize;
 			// byte 1: flags low byte
 			uint32 rawflags = rs->readByte();
-			// byte 2: frame repeat
-			a->_actions[action]->_frameRepeat = rs->readByte();
+			// byte 2: frame repeat and rotated flag
+			byte repeatAndRotateFlag = rs->readByte();
+			a->_actions[action]->_frameRepeat = repeatAndRotateFlag & 0xf;
+			if (GAME_IS_U8 && (repeatAndRotateFlag & 0xf0)) {
+				// This should never happen..
+				error("Anim data: frame repeat byte should never be > 0xf");
+			}
 			// byte 3: flags high byte
 			rawflags |= rs->readByte() << 8;
 
+			// Only one flag in this byte in crusader.. the "rotate" flag.
+			rawflags |= (repeatAndRotateFlag & 0xf0) << 12;
+
 			a->_actions[action]->_flags = AnimAction::loadAnimActionFlags(rawflags);
 
 			unsigned int dirCount = 8;
diff --git a/engines/ultima/ultima8/world/actors/anim_action.h b/engines/ultima/ultima8/world/actors/anim_action.h
index 5420eadfc6..490163030d 100644
--- a/engines/ultima/ultima8/world/actors/anim_action.h
+++ b/engines/ultima/ultima8/world/actors/anim_action.h
@@ -158,9 +158,9 @@ public:
 		AAF_ENDLOOP_U8   = 0x0020, // TODO: This starts a new anim at the end if pathfinding
 		AAF_ENDLOOP_CRU  = 0x0040, // TODO: This starts a new anim at the end if pathfinding
 		AAF_HANGING      = 0x0080,
-		AAF_ROTATED      = 0x1000, // Cru only
 		AAF_16DIRS       = 0x4000, // Cru only
 		AAF_DESTROYACTOR = 0x8000, // destroy actor after animation finishes
+		AAF_ROTATED    = 0x100000, // Cru only
 		AAF_COMMONFLAGS  = (AAF_TWOSTEP | AAF_LOOPING | AAF_UNSTOPPABLE | AAF_HANGING | AAF_DESTROYACTOR)
 	};
 




More information about the Scummvm-git-logs mailing list