[Scummvm-git-logs] scummvm master -> 8eeb19c53d6d41710a23ef066f01186d66c719db

mduggan mgithub at guarana.org
Tue May 4 12:38:27 UTC 2021


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

Summary:
b88dbd5ebc ULTIMA8: Make Crusader death more closely match original
352f7593e8 ULTIMA8: Implement Crusader AutoFirer Process
48d12d8a46 ULTIMA8: Small cleanup for sequential animations
7eb7b301ec ULTIMA8: Remove unused forward declaration
20bb32503f ULTIMA8: Don't allow jump on Cru NPCs that can't jump
dc2c801b20 ULTIMA8: Clean up some constants
7709c9b94c ULTIMA8: Add detection entry for fan Spanish translation No Remorse
54f67f02cc ULTIMA8: Add more weapons and ammo to Crusader testing hack
8aabf2dce1 ULTIMA8: Fix Crusader deaths getting stuck sometimes
321222cf6e ULTIMA8: Save some Crusader fields which were previously missed
ec26584d04 ULTIMA8: Add Vargas shield for Crusader: No Remorse
28db99d779 ULTIMA8: Add various checks for expected ranges of save data
8eeb19c53d ULTIMA8: Small consistency fixes for Crusader gumps


Commit: b88dbd5ebc356a7d4f3e3313f2ebacab894ed04b
    https://github.com/scummvm/scummvm/commit/b88dbd5ebc356a7d4f3e3313f2ebacab894ed04b
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Make Crusader death more closely match original

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


diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 4bc643e39c..4c1c4f1f21 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -28,6 +28,7 @@
 #include "ultima/ultima8/usecode/uc_list.h"
 #include "ultima/ultima8/misc/direction_util.h"
 #include "ultima/ultima8/games/game_data.h"
+#include "ultima/ultima8/world/fire_type.h"
 #include "ultima/ultima8/graphics/anim_dat.h"
 #include "ultima/ultima8/graphics/main_shape_archive.h"
 #include "ultima/ultima8/graphics/shape.h"
@@ -556,6 +557,15 @@ uint16 Actor::doAnim(Animation::Sequence anim, Direction dir, unsigned int steps
 	return Kernel::get_instance()->addProcess(p);
 }
 
+uint16 Actor::doAnimAfter(Animation::Sequence anim, Direction dir, ProcId waitfor) {
+	ProcId newanim = doAnim(anim, dir);
+	if (newanim && waitfor) {
+		Process *newproc = Kernel::get_instance()->getProcess(newanim);
+		newproc->waitFor(waitfor);
+	}
+	return newanim;
+}
+
 bool Actor::isBusy() const {
 	uint32 count = Kernel::get_instance()->getNumProcesses(_objId, ActorAnimProcess::ACTOR_ANIM_PROC_TYPE);
 	return count != 0;
@@ -890,16 +900,32 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
 	Kernel *kernel = Kernel::get_instance();
 	uint32 shape = getShape();
 
-	if (shape == 0x3ac && _hitPoints > 0) {
-		// TODO: Finish special case for Vargas.  Should not do any damage
-		// if there is a particular anim process running.  Also, check if the
-		// same special case exists in REGRET.
+	if (GAME_IS_REMORSE && shape == 0x3ac && _hitPoints > 0) {
+		if (isBusy()) {
+			ActorAnimProcess *proc = dynamic_cast<ActorAnimProcess *>(Kernel::get_instance()->findProcess(_objId, ActorAnimProcess::ACTOR_ANIM_PROC_TYPE));
+			if (proc->getAction() == Animation::teleportIn || proc->getAction() == Animation::teleportOut || proc->getAction() == Animation::teleportInReplacement || proc->getAction() == Animation::teleportOutReplacement)
+				return;
+		}
+
+		// TODO: Finish special case for Vargas in No Remorse.
 		doAnim(Animation::teleportOutReplacement, dir_current);
 		doAnim(Animation::teleportInReplacement, dir_current);
 		_hitPoints -= damage;
-		//if (_hitPoints < 0)
-		//	_hitPoints = 0;
+		//if (_bossHealth < 0)
+		//	_bossHealth = 0;
 		return;
+	} else if (GAME_IS_REGRET && shape == 0x5b1) {
+		/* TODO: Finish special case for No Regret */
+		/*
+		_bossHealth = _bossHealth - damage;
+		if (_bossHealth < 1) {
+		 _bossHealth = 0;
+		  Sprite_Int_Create(0x4f1,10,0x13,3,local_8,local_6,local_4);
+		  Item_PlaySFXCru((uint *)pitemno,0x1eb);
+		} else {
+		  Sprite_Int_Create(0x4f1,0,9,3,local_8,local_6,local_4);
+		}
+		*/
 	}
 
 	if (isDead())
@@ -961,7 +987,7 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
 
 	if (_hitPoints == 0) {
 		// Die!
-		die(damage_type);
+		die(damage_type, damage, dir);
 	} else if (damage) {
 		// Not dead yet.
 		if (!isRobotCru()) {
@@ -1109,7 +1135,7 @@ void Actor::receiveHitU8(uint16 other, Direction dir, int damage, uint16 damage_
 				if (audioproc) audioproc->playSFX(59, 0x60, _objId, 0);
 				clearActorFlag(ACT_WITHSTANDDEATH);
 			} else {
-				die(damage_type);
+				die(damage_type, damage, dir);
 			}
 			return;
 		}
@@ -1188,12 +1214,19 @@ void Actor::receiveHitU8(uint16 other, Direction dir, int damage, uint16 damage_
 	}
 }
 
-ProcId Actor::die(uint16 damageType) {
+ProcId Actor::die(uint16 damageType, uint16 damagePts, Direction srcDir) {
 	setHP(0);
 	setActorFlag(ACT_DEAD);
 	setFlag(FLG_BROKEN);
 	clearActorFlag(ACT_INCOMBAT);
 
+	if (GAME_IS_U8)
+		return dieU8(damageType);
+	else
+		return dieCru(damageType, damagePts, srcDir);
+}
+
+ProcId Actor::dieU8(uint16 damageType) {
 	ProcId animprocid = 0;
 #if 1
 	animprocid = killAllButFallAnims(true);
@@ -1201,37 +1234,18 @@ ProcId Actor::die(uint16 damageType) {
 	Kernel::get_instance()->killProcesses(getObjId(), 6, true); // CONSTANT!
 #endif
 
-	// TODO: In Crusader, this should default to 0x12, but randomly choose anim 0x14
-	// if it's available.
 	if (!animprocid)
 		animprocid = doAnim(Animation::die, dir_current);
 
 	MainActor *avatar = getMainActor();
 	// if hostile to avatar
-	if (GAME_IS_U8 && (getEnemyAlignment() & avatar->getAlignment())) {
+	if (getEnemyAlignment() & avatar->getAlignment()) {
 		if (avatar->isInCombat()) {
 			// play victory fanfare
 			MusicProcess::get_instance()->playCombatMusic(109);
 			// and resume combat music afterwards
 			MusicProcess::get_instance()->queueMusic(98);
 		}
-	} else if (GAME_IS_CRUSADER) {
-		if (!isRobotCru()) {
-			uint16 sfxno;
-			static const uint16 FADING_SCREAM_SFX[] = { 0xD9, 0xDA };
-			static const uint16 MALE_DEATH_SFX[] = { 0x88, 0x8C, 0x8F };
-			static const uint16 FEMALE_DEATH_SFX[] = { 0xD8, 0x10 };
-			if (damageType == 0xf) {
-				sfxno = FADING_SCREAM_SFX[getRandom() % 2];
-			} else {
-				if (hasExtFlags(EXT_FEMALE)) {
-					sfxno = FEMALE_DEATH_SFX[getRandom() % 2];
-				} else {
-					sfxno = MALE_DEATH_SFX[getRandom() % 3];
-				}
-			}
-			AudioProcess::get_instance()->playSFX(sfxno, 0x10, _objId, 0, true);
-		}
 	}
 
 	destroyContents();
@@ -1269,32 +1283,178 @@ ProcId Actor::die(uint16 damageType) {
 
 		int count = 5;
 		Shape *explosionshape = GameData::get_instance()->getMainShapes()
-		                        ->getShape(mi->_explode);
+								->getShape(mi->_explode);
 		assert(explosionshape);
 		unsigned int framecount = explosionshape->frameCount();
 
 		for (int i = 0; i < count; ++i) {
 			Item *piece = ItemFactory::createItem(mi->_explode,
-			                                      getRandom() % framecount,
-			                                      0, // qual
-			                                      Item::FLG_FAST_ONLY, //flags,
-			                                      0, // npcnum
-			                                      0, // mapnum
-			                                      0, true // ext. flags, _objId
-			                                     );
+												  getRandom() % framecount,
+												  0, // qual
+												  Item::FLG_FAST_ONLY, //flags,
+												  0, // npcnum
+												  0, // mapnum
+												  0, true // ext. flags, _objId
+												 );
 			piece->move(_x - 128 + 32 * (getRandom() % 6),
-			            _y - 128 + 32 * (getRandom() % 6),
-			            _z + getRandom() % 8); // move to near actor's position
+						_y - 128 + 32 * (getRandom() % 6),
+						_z + getRandom() % 8); // move to near actor's position
 			piece->hurl(-25 + (getRandom() % 50),
-			            -25 + (getRandom() % 50),
-			            10 + (getRandom() % 10),
-			            4); // (wrong?) CONSTANTS!
+						-25 + (getRandom() % 50),
+						10 + (getRandom() % 10),
+						4); // (wrong?) CONSTANTS!
 		}
 	}
 
 	return animprocid;
 }
 
+ProcId Actor::dieCru(uint16 damageType, uint16 damagePts, Direction srcDir) {
+	bool is_robot = isRobotCru();
+	bool created_koresh = false;
+
+	World *world = World::get_instance();
+
+    if (world->getControlledNPCNum() == _objId) {
+		TargetReticleProcess::get_instance()->avatarMoved();
+		if (_objId != 1) {
+			world->setControlledNPCNum(0);
+		}
+	}
+
+	ProcId lastanim = 0;
+	Kernel::get_instance()->killProcesses(_objId, Kernel::PROC_TYPE_ALL, true);
+	if (damageType == 3 || damageType == 4 || damageType == 10 || damageType == 12) {
+		if (!is_robot /* && violence enabled */) {
+			const FireType *ft = GameData::get_instance()->getFireType(damageType);
+			assert(ft);
+			uint16 dmg2 = ft->getRandomDamage();
+			if (damageType == 3) {
+				dmg2 *= 3;
+			} else {
+				dmg2 *= 2;
+			}
+			dmg2 /= 5;
+			if (dmg2 <= damagePts) {
+				moveToEtherealVoid();
+				CurrentMap *cm = world->getCurrentMap();
+				/* 0x576 - flaming guy running around */
+				bool can_create_koresh = cm->isValidPosition(_x, _y, _z, 0x576, _objId);
+				returnFromEtherealVoid();
+
+				if (can_create_koresh) {
+					created_koresh = true;
+					Direction rundir = _direction;
+					setShape(0x576);
+					setToStartOfAnim(Animation::walk);
+
+					int num_random_steps = getRandom() % 9;
+					// switch to an 8-dir value
+					if (rundir % 2)
+						rundir = static_cast<Direction>((rundir + 1) % 16);
+
+					for (int i = 0; i < num_random_steps; i++) {
+						rundir = Direction_TurnByDelta(rundir, (int)(getRandom() % 3) - 1, dirmode_8dirs);
+						lastanim = doAnimAfter(Animation::walk, rundir, lastanim);
+					}
+
+					lastanim = doAnimAfter(Animation::fallBackwardsCru, dir_current, lastanim);
+
+					int num_random_falls = (getRandom() % 3) + 1;
+					for (int i = 0; i < num_random_falls; i++) {
+						lastanim = doAnimAfter(Animation::fallForwardsCru, dir_current, lastanim);
+					}
+
+					lastanim = doAnimAfter(Animation::combatRollLeft, dir_current, lastanim);
+					tookHitCru();
+				}
+			}
+		}
+	} else if (damageType == 6) {
+		if (!is_robot) {
+			/*  1423 = plasma death */
+			setShape(0x58f);
+			setToStartOfAnim(Animation::stand);
+		}
+	} else if (damageType == 14) {
+		if (!is_robot) {
+			if (true /*isViolenceEnabled()*/) {
+				/* 1430 = fire death skeleton */
+				setShape(0x596);
+				setToStartOfAnim(Animation::fallBackwardsCru);
+			}
+		}
+	} else if (damageType == 15) {
+		if (!is_robot) {
+			setShape(0x59c);
+			setToStartOfAnim(Animation::fallBackwardsCru);
+		}
+	} else if (damageType == 7 && _objId == 1) {
+		lastanim = doAnimAfter(Animation::electrocuted, dir_current, lastanim);
+	}
+
+	if (!created_koresh) {
+		bool fall_backwards = true;
+		bool fall_random_dir = false;
+		Direction dirtest[8];
+		/* 0x383 == 899 (robot), 1423 = plasma death, 1430 = fire death skeleton */
+		if (getShape() != 899 && getShape() != 0x58f && getShape() != 0x596) {
+			dirtest[0] = _direction;
+			for (int i = 1; i < 9; i = i + 1) {
+				char testdir = (i % 2) ? 1 : -1;
+				dirtest[i] = Direction_TurnByDelta(_direction, ((i + 1) / 2) * testdir, dirmode_8dirs);
+			}
+			for (int i = 0; i < 9; i++) {
+				if (dirtest[i] == srcDir) {
+					if (i == 8 || i == 9) {
+						fall_random_dir = true;
+					} else {
+						fall_backwards = false;
+					}
+					break;
+				}
+			}
+		} else {
+			fall_random_dir = true;
+		}
+
+		if (!hasAnim(Animation::fallForwardsCru)) {
+			lastanim = doAnimAfter(Animation::fallBackwardsCru, dir_current, lastanim);
+		} else {
+			if (fall_random_dir) {
+				fall_backwards = (getRandom() % 2) == 0;
+			}
+			if (fall_backwards) {
+				lastanim = doAnimAfter(Animation::fallBackwardsCru, dir_current, lastanim);
+			} else {
+				lastanim = doAnimAfter(Animation::fallForwardsCru, dir_current, lastanim);
+			}
+		}
+
+		if (!is_robot) {
+			uint16 sfxno;
+			static const uint16 FADING_SCREAM_SFX[] = { 0xD9, 0xDA };
+			static const uint16 MALE_DEATH_SFX[] = { 0x88, 0x8C, 0x8F };
+			static const uint16 FEMALE_DEATH_SFX[] = { 0xD8, 0x10 };
+			if (damageType == 0xf) {
+				sfxno = FADING_SCREAM_SFX[getRandom() % 2];
+			} else {
+				if (hasExtFlags(EXT_FEMALE)) {
+					sfxno = FEMALE_DEATH_SFX[getRandom() % 2];
+				} else {
+					sfxno = MALE_DEATH_SFX[getRandom() % 3];
+				}
+			}
+			AudioProcess::get_instance()->playSFX(sfxno, 0x10, _objId, 0, true);
+		}
+	}
+
+	destroyContents();
+	giveTreasure();
+
+	return lastanim;
+}
+
 void Actor::killAllButCombatProcesses() {
 	// loop over all processes, keeping only the relevant ones
 	ProcessIter iter = Kernel::get_instance()->getProcessBeginIterator();
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index b57d6e19d2..01ff56c14c 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -198,8 +198,10 @@ public:
 
 	//! die
 	//! \param damageType damage type that caused the death
+	//! \param damagPts damage points that caused the death
+	//! \param srcDir direction damage came from
 	//! \return the process ID of the death animation
-	virtual ProcId die(uint16 damageType);
+	virtual ProcId die(uint16 damageType, uint16 damagePts, Direction srcDir);
 
 	//! kill all processes except those related to combat
 	void killAllButCombatProcesses();
@@ -235,6 +237,11 @@ public:
 	//! \return the PID of the ActorAnimProcess
 	uint16 doAnim(Animation::Sequence anim, Direction dir, unsigned int steps = 0);
 
+	//! run the given anim after the other animation (waitfor).
+	//! Safe for either anim to be 0.
+	//! \return the new anim pid, or 0 if failed
+	uint16 doAnimAfter(Animation::Sequence anim, Direction dir, ProcId waitfor);
+
 	//! check if this actor has a specific animation
 	bool hasAnim(Animation::Sequence anim);
 
@@ -484,6 +491,9 @@ protected:
 
 	void setInCombatU8();
 	void setInCombatCru(int activity);
+
+	ProcId dieU8(uint16 damageType);
+	ProcId dieCru(uint16 damageType, uint16 damagePts, Direction srcDir);
 };
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/actors/avatar_death_process.cpp b/engines/ultima/ultima8/world/actors/avatar_death_process.cpp
index b37fbc6f36..818b5f2512 100644
--- a/engines/ultima/ultima8/world/actors/avatar_death_process.cpp
+++ b/engines/ultima/ultima8/world/actors/avatar_death_process.cpp
@@ -80,7 +80,7 @@ void AvatarDeathProcess::run() {
 		// a couple of seconds before showing menu
 		AudioProcess *ap = AudioProcess::get_instance();
 		ap->playSFX(9, 0x10, 0, 1);
-		DelayProcess *delayproc = new DelayProcess(60);
+		DelayProcess *delayproc = new DelayProcess(120);
 		Kernel::get_instance()->addProcess(delayproc);
 		menuproc->waitFor(delayproc);
 	}
diff --git a/engines/ultima/ultima8/world/actors/main_actor.cpp b/engines/ultima/ultima8/world/actors/main_actor.cpp
index 4adad57a5c..e5467b807f 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.cpp
+++ b/engines/ultima/ultima8/world/actors/main_actor.cpp
@@ -547,8 +547,8 @@ void MainActor::clearInCombat() {
 		MusicProcess::get_instance()->restoreMusic();
 }
 
-ProcId MainActor::die(uint16 damageType) {
-	ProcId animprocid = Actor::die(damageType);
+ProcId MainActor::die(uint16 damageType, uint16 damagePts, Direction srcDir) {
+	ProcId animprocid = Actor::die(damageType, damagePts, srcDir);
 
 	Ultima8Engine *app = Ultima8Engine::get_instance();
 	assert(app);
diff --git a/engines/ultima/ultima8/world/actors/main_actor.h b/engines/ultima/ultima8/world/actors/main_actor.h
index 4577047c66..fa94d1f5fe 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.h
+++ b/engines/ultima/ultima8/world/actors/main_actor.h
@@ -104,7 +104,7 @@ public:
 	void setInCombat(int activity) override;
 	void clearInCombat() override;
 
-	ProcId die(uint16 DamageType) override;
+	ProcId die(uint16 damageType, uint16 damagePts, Direction srcDir) override;
 
 	const Std::string &getName() const {
 		return _name;
diff --git a/engines/ultima/ultima8/world/target_reticle_process.cpp b/engines/ultima/ultima8/world/target_reticle_process.cpp
index 4a4c8ec2d2..976a2091b0 100644
--- a/engines/ultima/ultima8/world/target_reticle_process.cpp
+++ b/engines/ultima/ultima8/world/target_reticle_process.cpp
@@ -53,7 +53,8 @@ void TargetReticleProcess::run() {
 		spriteProc = kernel->getProcess(_reticleSpriteProcess);
 	}
 
-	if (!_reticleEnabled || (mainactor && !mainactor->isInCombat())) {
+	if (!_reticleEnabled || (mainactor && !mainactor->isInCombat()) || !mainactor) {
+		// Reticle not enabled, actor not in combat, or actor is gone.
 		if (spriteProc) {
 			spriteProc->terminate();
 		}


Commit: 352f7593e8d22c9d74832bafc64654ca5e0a90aa
    https://github.com/scummvm/scummvm/commit/352f7593e8d22c9d74832bafc64654ca5e0a90aa
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Implement Crusader AutoFirer Process

Changed paths:
  A engines/ultima/ultima8/world/actors/auto_firer_process.cpp
  A engines/ultima/ultima8/world/actors/auto_firer_process.h
    engines/ultima/module.mk
    engines/ultima/ultima8/ultima8.cpp
    engines/ultima/ultima8/world/actors/actor_anim_process.cpp


diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index c6a1c89889..4624525905 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -549,6 +549,7 @@ MODULE_OBJS := \
 	ultima8/world/actors/animation_tracker.o \
 	ultima8/world/actors/anim_action.o \
 	ultima8/world/actors/attack_process.o \
+	ultima8/world/actors/auto_firer_process.o \
 	ultima8/world/actors/avatar_death_process.o \
 	ultima8/world/actors/avatar_gravity_process.o \
 	ultima8/world/actors/avatar_mover_process.o \
diff --git a/engines/ultima/ultima8/ultima8.cpp b/engines/ultima/ultima8/ultima8.cpp
index 6ba598347b..799e3abf3e 100644
--- a/engines/ultima/ultima8/ultima8.cpp
+++ b/engines/ultima/ultima8/ultima8.cpp
@@ -81,6 +81,7 @@
 #include "ultima/ultima8/world/actors/combat_process.h"
 #include "ultima/ultima8/world/actors/guard_process.h"
 #include "ultima/ultima8/world/actors/attack_process.h"
+#include "ultima/ultima8/world/actors/auto_firer_process.h"
 #include "ultima/ultima8/world/actors/pace_process.h"
 #include "ultima/ultima8/world/super_sprite_process.h"
 #include "ultima/ultima8/world/destroy_item_process.h"
@@ -300,6 +301,8 @@ bool Ultima8Engine::startup() {
 		ProcessLoader<SuperSpriteProcess>::load);
 	_kernel->addProcessLoader("AttackProcess",
 		ProcessLoader<AttackProcess>::load);
+	_kernel->addProcessLoader("AutoFirerProcess",
+		ProcessLoader<AutoFirerProcess>::load);
 
 	_objectManager = new ObjectManager();
 	_mouse = new Mouse();
diff --git a/engines/ultima/ultima8/world/actors/actor_anim_process.cpp b/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
index 7af2516360..e4d35770e9 100644
--- a/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
+++ b/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
@@ -34,6 +34,7 @@
 #include "ultima/ultima8/world/actors/animation_tracker.h"
 #include "ultima/ultima8/audio/audio_process.h"
 #include "ultima/ultima8/world/actors/combat_process.h"
+#include "ultima/ultima8/world/actors/auto_firer_process.h"
 #include "ultima/ultima8/world/sprite_process.h"
 #include "ultima/ultima8/graphics/palette_fader_process.h"
 #include "ultima/ultima8/world/create_item_process.h"
@@ -526,7 +527,8 @@ void ActorAnimProcess::doFireWeaponCru(Actor *a, const AnimFrame *f) {
 		return;
 
 	if (a->getObjId() == 1 && wpninfo->_weaponInfo->_damageType == 6) {
-		warning("TODO: implement AutoFirerProcess for Crusader");
+		Process *auto_firer = new AutoFirerProcess();
+		Kernel::get_instance()->addProcess(auto_firer);
 	}
 
 	a->fireWeapon(f->cru_attackx(), f->cru_attacky(), f->cru_attackz(),
@@ -535,7 +537,6 @@ void ActorAnimProcess::doFireWeaponCru(Actor *a, const AnimFrame *f) {
 	AudioProcess *audioproc = AudioProcess::get_instance();
 	if (audioproc)
 		audioproc->playSFX(wpninfo->_weaponInfo->_sound, 0x80, a->getObjId(), 0, false);
-
 }
 
 
diff --git a/engines/ultima/ultima8/world/actors/auto_firer_process.cpp b/engines/ultima/ultima8/world/actors/auto_firer_process.cpp
new file mode 100644
index 0000000000..328492eb46
--- /dev/null
+++ b/engines/ultima/ultima8/world/actors/auto_firer_process.cpp
@@ -0,0 +1,94 @@
+/* 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/world/actors/auto_firer_process.h"
+#include "ultima/ultima8/world/actors/actor.h"
+#include "ultima/ultima8/audio/audio_process.h"
+#include "ultima/ultima8/kernel/kernel.h"
+#include "ultima/ultima8/ultima8.h"
+#include "ultima/ultima8/world/get_object.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+DEFINE_RUNTIME_CLASSTYPE_CODE(AutoFirerProcess)
+
+AutoFirerProcess::AutoFirerProcess() : Process() {
+	Actor *a = getControlledActor();
+	if (a)
+		_itemNum = a->getObjId();
+	_type = 0x260; // CONSTANT !
+	_startTicks = Kernel::get_instance()->getTickNum();
+}
+
+void AutoFirerProcess::run() {
+	if (Kernel::get_instance()->getTickNum() > _startTicks + 10) {
+		Actor *a = getControlledActor();
+
+		if (!a) {
+			terminate();
+			return;
+		}
+
+		uint16 weaponno = a->getActiveWeapon();
+		const Item *wpn = getItem(weaponno);
+		if (wpn && wpn->getShape() == 0x38d && wpn->getShapeInfo()->_weaponInfo) {
+			const WeaponInfo *info = wpn->getShapeInfo()->_weaponInfo;
+			int shotsleft;
+			if (info->_ammoShape) {
+				shotsleft = wpn->getQuality();
+			} else if (info->_energyUse) {
+				shotsleft = a->getMana() / info->_energyUse;
+			} else {
+				shotsleft = 1;
+			}
+			if (shotsleft > 0) {
+				int32 x = 0;
+				int32 y = 0;
+				int32 z = 0;
+				a->addFireAnimOffsets(x, y, z);
+				a->fireWeapon(x, y, z, a->getDir(), info->_damageType, true);
+
+				AudioProcess *audioproc = AudioProcess::get_instance();
+				if (audioproc && info->_sound)
+					audioproc->playSFX(info->_sound, 0x80, a->getObjId(), 0, false);
+			}
+		}
+		terminate();
+	}
+}
+
+void AutoFirerProcess::saveData(Common::WriteStream *ws) {
+	Process::saveData(ws);
+	ws->writeUint32LE(_startTicks);
+}
+
+bool AutoFirerProcess::loadData(Common::ReadStream *rs, uint32 version) {
+	if (!Process::loadData(rs, version)) return false;
+
+	_startTicks = rs->readUint32LE();
+	return true;
+}
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/actors/auto_firer_process.h b/engines/ultima/ultima8/world/actors/auto_firer_process.h
new file mode 100644
index 0000000000..a28aa8d002
--- /dev/null
+++ b/engines/ultima/ultima8/world/actors/auto_firer_process.h
@@ -0,0 +1,52 @@
+/* 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 WORLD_ACTORS_AUTOFIRERPROCESS_H
+#define WORLD_ACTORS_AUTOFIRERPROCESS_H
+
+#include "ultima/ultima8/kernel/process.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+/**
+ * A process which fires another shot after a short delay, then terminates
+ */
+class AutoFirerProcess : public Process {
+public:
+	AutoFirerProcess();
+
+	ENABLE_RUNTIME_CLASSTYPE()
+
+	void run() override;
+
+	bool loadData(Common::ReadStream *rs, uint32 version);
+	void saveData(Common::WriteStream *ws) override;
+
+private:
+	uint32 _startTicks;
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif


Commit: 48d12d8a461c5bed604a53669a1dd7a9253a4010
    https://github.com/scummvm/scummvm/commit/48d12d8a461c5bed604a53669a1dd7a9253a4010
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Small cleanup for sequential animations

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


diff --git a/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp b/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
index dbd554a3b6..4143a2d381 100644
--- a/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
+++ b/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
@@ -122,18 +122,14 @@ void AvatarMoverProcess::turnToDirection(Direction direction) {
 void AvatarMoverProcess::slowFromRun(Direction direction) {
 	Actor *avatar = getControlledActor();
 	ProcId walkpid = avatar->doAnim(Animation::walk, direction);
-	ProcId standpid = avatar->doAnim(Animation::stand, direction);
-	Process *standproc = Kernel::get_instance()->getProcess(standpid);
-	standproc->waitFor(walkpid);
+	ProcId standpid = avatar->doAnimAfter(Animation::stand, direction, walkpid);
 	waitFor(standpid);
 }
 
 void AvatarMoverProcess::putAwayWeapon(Direction direction) {
 	Actor *avatar = getControlledActor();
 	ProcId anim1 = avatar->doAnim(Animation::unreadyWeapon, direction);
-	ProcId anim2 = avatar->doAnim(Animation::stand, direction);
-	Process *anim2p = Kernel::get_instance()->getProcess(anim2);
-	anim2p->waitFor(anim1);
+	ProcId anim2 = avatar->doAnimAfter(Animation::stand, direction, anim1);
 	waitFor(anim2);
 }
 


Commit: 7eb7b301ec0dbfd57d66fbe91dbc424e493e6438
    https://github.com/scummvm/scummvm/commit/7eb7b301ec0dbfd57d66fbe91dbc424e493e6438
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Remove unused forward declaration

Changed paths:
    engines/ultima/ultima8/world/actors/avatar_death_process.h


diff --git a/engines/ultima/ultima8/world/actors/avatar_death_process.h b/engines/ultima/ultima8/world/actors/avatar_death_process.h
index df29d461c2..345e8c47b2 100644
--- a/engines/ultima/ultima8/world/actors/avatar_death_process.h
+++ b/engines/ultima/ultima8/world/actors/avatar_death_process.h
@@ -28,8 +28,6 @@
 namespace Ultima {
 namespace Ultima8 {
 
-class MainActor;
-
 class AvatarDeathProcess : public Process {
 public:
 	AvatarDeathProcess();


Commit: 20bb32503f73c040bdb69910b4e39c8f5ee59465
    https://github.com/scummvm/scummvm/commit/20bb32503f73c040bdb69910b4e39c8f5ee59465
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Don't allow jump on Cru NPCs that can't jump

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


diff --git a/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp b/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
index 58d6563810..6ea261a2bd 100644
--- a/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
+++ b/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
@@ -141,7 +141,7 @@ void CruAvatarMoverProcess::handleCombatMode() {
 				nextanim = Animation::combatRunSmallWeapon;
 			else
 				nextanim = Animation::startRunSmallWeapon;
-		} else if (hasMovementFlags(MOVE_JUMP)) {
+		} else if (hasMovementFlags(MOVE_JUMP) && avatar->hasAnim(Animation::jumpForward)) {
 			if (lastanim == Animation::walk || lastanim == Animation::run || lastanim == Animation::combatRunSmallWeapon)
 				nextanim = Animation::jumpForward;
 			else


Commit: dc2c801b20aa050e209149a2c4431dd814c3bbf9
    https://github.com/scummvm/scummvm/commit/dc2c801b20aa050e209149a2c4431dd814c3bbf9
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Clean up some constants

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


diff --git a/engines/ultima/ultima8/kernel/object.cpp b/engines/ultima/ultima8/kernel/object.cpp
index d2d995362c..48abc71bc2 100644
--- a/engines/ultima/ultima8/kernel/object.cpp
+++ b/engines/ultima/ultima8/kernel/object.cpp
@@ -44,7 +44,7 @@ ObjId Object::assignObjId() {
 
 void Object::clearObjId() {
 	// On clearObjId we kill all processes that belonged to us
-	Kernel::get_instance()->killProcesses(_objId, 6, true);
+	Kernel::get_instance()->killProcesses(_objId, Kernel::PROC_TYPE_ALL, true);
 
 	if (_objId != 0xFFFF)
 		ObjectManager::get_instance()->clearObjId(_objId);
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 4c1c4f1f21..38252bba01 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -908,8 +908,8 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
 		}
 
 		// TODO: Finish special case for Vargas in No Remorse.
-		doAnim(Animation::teleportOutReplacement, dir_current);
-		doAnim(Animation::teleportInReplacement, dir_current);
+		ProcId teleout = doAnim(Animation::teleportOutReplacement, dir_current);
+		doAnimAfter(Animation::teleportInReplacement, dir_current, teleout);
 		_hitPoints -= damage;
 		//if (_bossHealth < 0)
 		//	_bossHealth = 0;
@@ -1231,7 +1231,7 @@ ProcId Actor::dieU8(uint16 damageType) {
 #if 1
 	animprocid = killAllButFallAnims(true);
 #else
-	Kernel::get_instance()->killProcesses(getObjId(), 6, true); // CONSTANT!
+	Kernel::get_instance()->killProcesses(getObjId(), Kernel::PROC_TYPE_ALL, true);
 #endif
 
 	if (!animprocid)
@@ -1655,7 +1655,7 @@ void Actor::setInCombatU8() {
 	assert(getCombatProcess() == nullptr);
 
 	// kill any processes belonging to this actor
-	Kernel::get_instance()->killProcesses(getObjId(), 6, true);
+	Kernel::get_instance()->killProcesses(getObjId(), Kernel::PROC_TYPE_ALL, true);
 
 	// perform _special actions
 	ProcId castproc = callUsecodeEvent_cast(0);


Commit: 7709c9b94cc8997176a91b31e3ec9ef7e9cdea6b
    https://github.com/scummvm/scummvm/commit/7709c9b94cc8997176a91b31e3ec9ef7e9cdea6b
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Add detection entry for fan Spanish translation No Remorse

Changed paths:
    engines/ultima/detection_tables.h


diff --git a/engines/ultima/detection_tables.h b/engines/ultima/detection_tables.h
index 21a590dbe9..90b9fcb57d 100644
--- a/engines/ultima/detection_tables.h
+++ b/engines/ultima/detection_tables.h
@@ -407,6 +407,21 @@ static const UltimaGameDescription GAME_DESCRIPTIONS[] = {
 		0
 	},
 
+	// Crusader - No Remorse (Spanish fan patch) provided by Wesker
+	{
+		{
+			"remorse",
+			"Fan Translation",
+			AD_ENTRY1s("eusecode.flx", "a8b5c421c5d74be8c69fcd4fecadd1dd", 559015),
+			Common::ES_ESP,
+			Common::kPlatformDOS,
+			ADGF_UNSTABLE,
+			GUIO1(GUIO_NOMIDI)
+		},
+		GAME_CRUSADER_REM,
+		0
+	},
+
 	// Crusader - No Remorse (Japanese) provided by Dominus
 	{
 		{


Commit: 54f67f02cca72f078163e5aae8a284b5b3726b80
    https://github.com/scummvm/scummvm/commit/54f67f02cca72f078163e5aae8a284b5b3726b80
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Add more weapons and ammo to Crusader testing hack

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 a022f1b0f6..0c82274f33 100644
--- a/engines/ultima/ultima8/games/start_crusader_process.cpp
+++ b/engines/ultima/ultima8/games/start_crusader_process.cpp
@@ -91,12 +91,19 @@ void StartCrusaderProcess::run() {
 		avatar->setShieldType(1);
 
 #if 0
-		// Give the avatar *all the weapons*.. (handy for testing)
-		uint32 wpnshapes[] = {0x032E, 0x032F, 0x0330, 0x038C, 0x0332, 0x0333, 0x0334,
-			0x038E, 0x0388, 0x038A, 0x038D, 0x038B, 0x0386};
+		// Give the avatar *all the weapons and ammo*.. (handy for testing)
+		uint32 wpnshapes[] = {
+			// Weapons
+			0x032E, 0x032F, 0x0330, 0x038C, 0x0332, 0x0333, 0x0334,
+			0x038E, 0x0388, 0x038A, 0x038D, 0x038B, 0x0386,
+			// Ammo
+			0x033D, 0x033E, 0x033F, 0x0340, 0x0341
+		};
 		for (int i = 0; i < ARRAYSIZE(wpnshapes); i++) {
-			Item *wpn = ItemFactory::createItem(wpnshapes[i], 0, 0, 0, 0, mapnum, 0, true);
-			avatar->addItemCru(wpn, false);
+			for (int j = 0; j < 5; j++) {
+				Item *wpn = ItemFactory::createItem(wpnshapes[i], 0, 0, 0, 0, mapnum, 0, true);
+				avatar->addItemCru(wpn, false);
+			}
 		}
 #endif
 


Commit: 8aabf2dce147d648123e2de334f342710a0cdd0a
    https://github.com/scummvm/scummvm/commit/8aabf2dce147d648123e2de334f342710a0cdd0a
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Fix Crusader deaths getting stuck sometimes

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


diff --git a/engines/ultima/ultima8/world/actors/animation_tracker.cpp b/engines/ultima/ultima8/world/actors/animation_tracker.cpp
index 9286d4710d..029b9337b7 100644
--- a/engines/ultima/ultima8/world/actors/animation_tracker.cpp
+++ b/engines/ultima/ultima8/world/actors/animation_tracker.cpp
@@ -343,16 +343,17 @@ bool AnimationTracker::step() {
 
 	if (!targetok || (f.is_onground() && !support)) {
 
-		// If on ground, try to adjust properly
+		// If on ground, try to adjust properly. Never do it for dead Crusader NPCs,
+		// as they don't get gravity and the death process gets stuck.
 		// TODO: Profile the effect of disabling this for pathfinding.
 		//       It shouldn't be necessary in that case, and may provide a
 		//       worthwhile speed-up.
-		if (f.is_onground() && zd > 8) {
+		if (f.is_onground() && zd > 8 && !(is_crusader && a->isDead())) {
 			if (is_crusader && !targetok && support) {
 				// Possibly trying to step onto an elevator platform which stops at a z slightly
 				// above the floor.  Re-scan with a small adjustment.
 				// This is a bit of a temporary hack to make navigation possible.. it "hurls"
-				// the avatar sometimes, so it needs fixing properly.
+				// the avatar sometimes.
 				tz += 2;
 			}
 
@@ -365,8 +366,10 @@ bool AnimationTracker::step() {
 			} else {
 #ifdef WATCHACTOR
 				if (a->getObjId() == watchactor) {
-					pout << "AnimationTracker: adjusted step: "
-					     << tx - (_x + dx) << "," << ty - (_y + dy) << "," << tz - (_z + dz)
+					pout << "AnimationTracker: adjusted step: x: "
+					     << tx << "," << _x << "," << dx << " y: "
+						 << ty << "," << _y << "," << dy << " z: "
+						 << tz << "," << _z << "," << dz
 					     << Std::endl;
 				}
 #endif


Commit: 321222cf6e3180047cfa1a34f7141d864209648e
    https://github.com/scummvm/scummvm/commit/321222cf6e3180047cfa1a34f7141d864209648e
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Save some Crusader fields which were previously missed

Breaks save compatibility again but game is still unstable.

Changed paths:
    engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
    engines/ultima/ultima8/world/current_map.cpp
    engines/ultima/ultima8/world/target_reticle_process.cpp


diff --git a/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp b/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
index 6ea261a2bd..3b36182d00 100644
--- a/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
+++ b/engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
@@ -537,11 +537,13 @@ void CruAvatarMoverProcess::tryAttack() {
 void CruAvatarMoverProcess::saveData(Common::WriteStream *ws) {
 	AvatarMoverProcess::saveData(ws);
 	ws->writeSint32LE(_avatarAngle);
+	ws->writeByte(_SGA1Loaded ? 1 : 0);
 }
 
 bool CruAvatarMoverProcess::loadData(Common::ReadStream *rs, uint32 version) {
 	if (!AvatarMoverProcess::loadData(rs, version)) return false;
 	_avatarAngle = rs->readSint32LE();
+	_SGA1Loaded = (rs->readByte() != 0);
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/world/current_map.cpp b/engines/ultima/ultima8/world/current_map.cpp
index 9d03e6f08b..696690053b 100644
--- a/engines/ultima/ultima8/world/current_map.cpp
+++ b/engines/ultima/ultima8/world/current_map.cpp
@@ -1307,6 +1307,11 @@ void CurrentMap::save(Common::WriteStream *ws) {
 			ws->writeUint32LE(_fast[i][j]);
 		}
 	}
+
+	if (GAME_IS_CRUSADER) {
+		for (int i = 0; i < MAP_NUM_TARGET_ITEMS; i++)
+			ws->writeUint16LE(_targets[i]);
+	}
 }
 
 bool CurrentMap::load(Common::ReadStream *rs, uint32 version) {
@@ -1321,6 +1326,11 @@ bool CurrentMap::load(Common::ReadStream *rs, uint32 version) {
 	_fastXMax = -1;
 	_fastYMax = -1;
 
+	if (GAME_IS_CRUSADER) {
+		for (int i = 0; i < MAP_NUM_TARGET_ITEMS; i++)
+			_targets[i] = rs->readUint16LE();
+	}
+
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/world/target_reticle_process.cpp b/engines/ultima/ultima8/world/target_reticle_process.cpp
index 976a2091b0..e913b24014 100644
--- a/engines/ultima/ultima8/world/target_reticle_process.cpp
+++ b/engines/ultima/ultima8/world/target_reticle_process.cpp
@@ -196,16 +196,20 @@ void TargetReticleProcess::toggle() {
 void TargetReticleProcess::saveData(Common::WriteStream *ws) {
 	Process::saveData(ws);
 
+	ws->writeByte(_reticleEnabled ? 1 : 0);
 	ws->writeUint32LE(_lastUpdate);
 	ws->writeUint16LE(_reticleSpriteProcess);
+	ws->writeByte(_lastTargetDir);
 	ws->writeUint16LE(_lastTargetItem);
 }
 
 bool TargetReticleProcess::loadData(Common::ReadStream *rs, uint32 version) {
 	if (!Process::loadData(rs, version)) return false;
 
+	_reticleEnabled = (rs->readByte() != 0);
 	_lastUpdate = rs->readUint32LE();
 	_reticleSpriteProcess = rs->readUint16LE();
+	_lastTargetDir = static_cast<Direction>(rs->readByte());
 	_lastTargetItem = rs->readUint16LE();
 
 	return true;


Commit: ec26584d04efc4ba501ee835155fa60ac2c929ca
    https://github.com/scummvm/scummvm/commit/ec26584d04efc4ba501ee835155fa60ac2c929ca
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:15+09:00

Commit Message:
ULTIMA8: Add Vargas shield for Crusader: No Remorse

This is handled as a special case in the original game.
Breaks save compatibility one more time..

Changed paths:
    engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
    engines/ultima/ultima8/usecode/remorse_intrinsics.h
    engines/ultima/ultima8/world/actors/actor.cpp
    engines/ultima/ultima8/world/world.cpp
    engines/ultima/ultima8/world/world.h


diff --git a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
index 3915623f6e..ae12114662 100644
--- a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
+++ b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
@@ -202,7 +202,7 @@ const char* const ConvertUsecodeCrusader::_intrinsics[] = {
 	"byte Kernel::I_getCurrentKeyDown(void)", // get global - something about keyboard (by disasm)
 	"int16 MainActor::I_teleportToEgg(int, int)", // a bit different to the U8 one - uses main actor map by default.
 	"void PaletteFaderProcess:I_jumpToGreyScale(void)",
-	"void I_resetVargasHealthTo500(void)", // TODO: look how this is used in disasm and usecode .. seems weird.
+	"void I_resetVargasShieldTo500(void)",
 	"void Item::I_andStatus(Item *, uint16 status)", // part of same coff set 01A, 031, 069, 06E, 099, 0B2, 0BF, 0C1, 0C3, 0E9, 0FC, 101, 104, 106, 108, 10A, 10C, 10E, 110, 114, 117, 11A, 128, 132
 	"void PaletteFaderProcess::I_jumpToNormalPalette(void)",
 	"int16 PaletteFaderProcess::I_fadeFromBlack(nsteps)",
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index a84cf0343e..1b264ddccf 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -196,7 +196,7 @@ Intrinsic RemorseIntrinsics[] = {
 	Ultima8Engine::I_moveKeyDownRecently,
 	MainActor::I_teleportToEgg, // void Intrinsic096(4 bytes)
 	PaletteFaderProcess::I_jumpToGreyScale,
-	0, // void Intrinsic098(void) // TODO: reset vargas health to 500.. weird.
+	World::I_resetVargasShield, // void Intrinsic098(void)
 	Item::I_andStatus, // void Intrinsic099(6 bytes)
 	PaletteFaderProcess::I_jumpToNormalPalette, // TODO: should also stop cycle process?
 	PaletteFaderProcess::I_fadeFromBlack, // fade to game pal with number of steps
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 38252bba01..60665ff80c 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -899,20 +899,20 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
 	AudioProcess *audio = AudioProcess::get_instance();
 	Kernel *kernel = Kernel::get_instance();
 	uint32 shape = getShape();
+	World *world = World::get_instance();
 
-	if (GAME_IS_REMORSE && shape == 0x3ac && _hitPoints > 0) {
+	// Special case for Vargas, who has a shield.
+	if (GAME_IS_REMORSE && shape == 0x3ac && world->getVargasShield() > 0) {
 		if (isBusy()) {
 			ActorAnimProcess *proc = dynamic_cast<ActorAnimProcess *>(Kernel::get_instance()->findProcess(_objId, ActorAnimProcess::ACTOR_ANIM_PROC_TYPE));
 			if (proc->getAction() == Animation::teleportIn || proc->getAction() == Animation::teleportOut || proc->getAction() == Animation::teleportInReplacement || proc->getAction() == Animation::teleportOutReplacement)
 				return;
 		}
 
-		// TODO: Finish special case for Vargas in No Remorse.
 		ProcId teleout = doAnim(Animation::teleportOutReplacement, dir_current);
 		doAnimAfter(Animation::teleportInReplacement, dir_current, teleout);
-		_hitPoints -= damage;
-		//if (_bossHealth < 0)
-		//	_bossHealth = 0;
+		int newval = MAX(0, static_cast<int>(world->getVargasShield()) - damage);
+		world->setVargasShield(static_cast<uint32>(newval));
 		return;
 	} else if (GAME_IS_REGRET && shape == 0x5b1) {
 		/* TODO: Finish special case for No Regret */
diff --git a/engines/ultima/ultima8/world/world.cpp b/engines/ultima/ultima8/world/world.cpp
index 3b935d26dc..6461b906c3 100644
--- a/engines/ultima/ultima8/world/world.cpp
+++ b/engines/ultima/ultima8/world/world.cpp
@@ -48,7 +48,7 @@ namespace Ultima8 {
 World *World::_world = nullptr;
 
 World::World() : _currentMap(nullptr), _alertActive(false), _difficulty(3),
-				 _controlledNPCNum(1) {
+				 _controlledNPCNum(1), _vargasShield(5000) {
 	debugN(MM_INFO, "Creating World...\n");
 
 	_world = this;
@@ -340,6 +340,8 @@ void World::save(Common::WriteStream *ws) {
 	if (GAME_IS_CRUSADER) {
 		ws->writeByte(_alertActive ? 1 : 0);
 		ws->writeByte(_difficulty);
+		ws->writeUint16LE(_controlledNPCNum);
+		ws->writeUint32LE(_vargasShield);
 	}
 
 	uint16 es = static_cast<uint16>(_ethereal.size());
@@ -370,6 +372,8 @@ bool World::load(Common::ReadStream *rs, uint32 version) {
 	if (GAME_IS_CRUSADER) {
 		_alertActive = (rs->readByte() != 0);
 		_difficulty = rs->readByte();
+		_controlledNPCNum = rs->readUint16LE();
+		_vargasShield = rs->readUint32LE();
 	}
 
 	uint32 etherealcount = rs->readUint32LE();
@@ -497,5 +501,11 @@ uint32 World::I_setControlledNPCNum(const uint8 *args,
 	return 0;
 }
 
+uint32 World::I_resetVargasShield(const uint8 * /*args*/,
+	unsigned int /*argsize*/) {
+	get_instance()->setVargasShield(500);
+	return 0;
+}
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/world.h b/engines/ultima/ultima8/world/world.h
index 2baf2ce6a6..d462c47947 100644
--- a/engines/ultima/ultima8/world/world.h
+++ b/engines/ultima/ultima8/world/world.h
@@ -157,12 +157,20 @@ public:
 	}
 	void setControlledNPCNum(uint16 num);
 
+	uint32 getVargasShield() const {
+		return _vargasShield;
+	}
+	void setVargasShield(uint32 val) {
+		_vargasShield = val;
+	}
+
 	INTRINSIC(I_getAlertActive); // for Crusader
 	INTRINSIC(I_setAlertActive); // for Crusader
 	INTRINSIC(I_clrAlertActive); // for Crusader
 	INTRINSIC(I_gameDifficulty); // for Crusader
 	INTRINSIC(I_getControlledNPCNum); // for Crusader
 	INTRINSIC(I_setControlledNPCNum); // for Crusader
+	INTRINSIC(I_resetVargasShield); // for Crusader: No Remorse
 
 private:
 	static World *_world;
@@ -175,6 +183,12 @@ private:
 	bool _alertActive; //!< is intruder alert active (Crusader)
 	uint8 _difficulty; //!< game difficulty level (Crusader)
 	uint16 _controlledNPCNum; //!< Current controlled NPC (normally 1, the avatar)
+	/**
+	 * Special varaible for Vargas' shield in No Remorse, which is remembered separately.
+	 *
+	 * Starts at 5000 and can be reset to 500 by an intrinsic function.
+	 */
+	uint32 _vargasShield;
 
 };
 


Commit: 28db99d779868f73626a9e951c7db904cd56d3a6
    https://github.com/scummvm/scummvm/commit/28db99d779868f73626a9e951c7db904cd56d3a6
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:16+09:00

Commit Message:
ULTIMA8: Add various checks for expected ranges of save data

Changed paths:
    engines/ultima/ultima8/gumps/gump.cpp
    engines/ultima/ultima8/kernel/kernel.cpp
    engines/ultima/ultima8/kernel/object_manager.cpp
    engines/ultima/ultima8/misc/id_man.cpp
    engines/ultima/ultima8/usecode/bit_set.cpp
    engines/ultima/ultima8/usecode/byte_set.cpp
    engines/ultima/ultima8/usecode/uc_list.cpp
    engines/ultima/ultima8/usecode/uc_machine.cpp
    engines/ultima/ultima8/world/map.cpp
    engines/ultima/ultima8/world/world.cpp


diff --git a/engines/ultima/ultima8/gumps/gump.cpp b/engines/ultima/ultima8/gumps/gump.cpp
index 11d3f6cd1d..0ca1f42431 100644
--- a/engines/ultima/ultima8/gumps/gump.cpp
+++ b/engines/ultima/ultima8/gumps/gump.cpp
@@ -847,6 +847,10 @@ bool Gump::loadData(Common::ReadStream *rs, uint32 version) {
 	uint32 shapenum = rs->readUint32LE();
 	if (flex) {
 		_shape = flex->getShape(shapenum);
+		if (shapenum > 0 && !_shape) {
+			warning("Gump shape %d is not valid. Corrupt save?", shapenum);
+			return false;
+		}
 	}
 
 	_frameNum = rs->readUint32LE();
@@ -857,6 +861,11 @@ bool Gump::loadData(Common::ReadStream *rs, uint32 version) {
 
 	// read children
 	uint32 childcount = rs->readUint32LE();
+
+	if (childcount > 65535) {
+		warning("Improbable gump child count %d.  Corrupt save?", childcount);
+		return false;
+	}
 	for (unsigned int i = 0; i < childcount; ++i) {
 		Object *obj = ObjectManager::get_instance()->loadObject(rs, version);
 		Gump *child = dynamic_cast<Gump *>(obj);
diff --git a/engines/ultima/ultima8/kernel/kernel.cpp b/engines/ultima/ultima8/kernel/kernel.cpp
index 3632ca17ec..430d69d9d7 100644
--- a/engines/ultima/ultima8/kernel/kernel.cpp
+++ b/engines/ultima/ultima8/kernel/kernel.cpp
@@ -345,6 +345,24 @@ bool Kernel::load(Common::ReadStream *rs, uint32 version) {
 		_processes.push_back(p);
 	}
 
+	// Integrity check for processes
+	Std::set<ProcId> procs;
+	for (Std::list<Process *>::const_iterator iter = _processes.begin(); iter != _processes.end(); iter++) {
+		const Process *p = *iter;
+		if (procs.find(p->getPid()) != procs.end()) {
+			warning("Duplicate process id %d in processes.  Corrupt save?", p->getPid());
+			return false;
+		}
+		if (p->getTicksPerRun() > 100) {
+			warning("Improbable value for ticks per run %d in process id %d .  Corrupt save?", p->getTicksPerRun(), p->getPid());
+			return false;
+		}
+		if (p->getType() > 0x1000) {
+			warning("Improbable value for proctype %x in process id %d .  Corrupt save?", p->getType(), p->getPid());
+			return false;
+		}
+	}
+
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/kernel/object_manager.cpp b/engines/ultima/ultima8/kernel/object_manager.cpp
index 5275fa65c4..290ed8248b 100644
--- a/engines/ultima/ultima8/kernel/object_manager.cpp
+++ b/engines/ultima/ultima8/kernel/object_manager.cpp
@@ -280,6 +280,28 @@ bool ObjectManager::load(Common::ReadStream *rs, uint32 version) {
 	}
 	pout << "Reclaimed " << count << " _objIDs on load." << Std::endl;
 
+	// Integrity check items - their ids should match, and if they have
+	// parents, those should be valid.
+	for (unsigned int i = 0; i < _objects.size(); i++) {
+		if (!_objects[i])
+			continue;
+		const Object *obj = _objects[i];
+		ObjId oid = obj->getObjId();
+		if (oid != i) {
+			warning("Corrupt save? Object %d thinks its id is %d", i, oid);
+			return false;
+		}
+
+		const Item *it = dynamic_cast<const Item *>(obj);
+		if (it) {
+			ObjId parent = it->getParent();
+			if (parent && !_objects[parent]) {
+				warning("Corrupt save? Object %d has parent %d which no longer exists", i, parent);
+				return false;
+			}
+		}
+	}
+
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/misc/id_man.cpp b/engines/ultima/ultima8/misc/id_man.cpp
index 55c85765d9..f6777ed553 100644
--- a/engines/ultima/ultima8/misc/id_man.cpp
+++ b/engines/ultima/ultima8/misc/id_man.cpp
@@ -216,6 +216,12 @@ bool idMan::load(Common::ReadStream *rs, uint32 version) {
 
 	_usedCount = realusedcount;
 
+	// Integrity check
+	if (_begin > _end || _begin > _maxEnd) {
+		warning("begin > end loading ids, corrupt save?");
+		return false;
+	}
+
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/usecode/bit_set.cpp b/engines/ultima/ultima8/usecode/bit_set.cpp
index ef108958c1..b03e6b040d 100644
--- a/engines/ultima/ultima8/usecode/bit_set.cpp
+++ b/engines/ultima/ultima8/usecode/bit_set.cpp
@@ -119,6 +119,12 @@ void BitSet::save(Common::WriteStream *ws) {
 
 bool BitSet::load(Common::ReadStream *rs, uint32 version) {
 	uint32 s = rs->readUint32LE();
+
+	if (s > 1024 * 1024) {
+		warning("Improbable globals size %d, corrupt save?", s);
+		return false;
+	}
+
 	setSize(s);
 	rs->read(_data, _bytes);
 
diff --git a/engines/ultima/ultima8/usecode/byte_set.cpp b/engines/ultima/ultima8/usecode/byte_set.cpp
index 6c77b7d7b7..c89cb623d9 100644
--- a/engines/ultima/ultima8/usecode/byte_set.cpp
+++ b/engines/ultima/ultima8/usecode/byte_set.cpp
@@ -80,6 +80,12 @@ void ByteSet::save(Common::WriteStream *ws) {
 
 bool ByteSet::load(Common::ReadStream *rs, uint32 version) {
 	uint32 s = rs->readUint32LE();
+
+	if (s > 1024 * 1024) {
+		warning("Improbable globals size %d, corrupt save?", s);
+		return false;
+	}
+
 	setSize(s);
 	rs->read(_data, _size);
 
diff --git a/engines/ultima/ultima8/usecode/uc_list.cpp b/engines/ultima/ultima8/usecode/uc_list.cpp
index a199d7a6a4..3241d147fd 100644
--- a/engines/ultima/ultima8/usecode/uc_list.cpp
+++ b/engines/ultima/ultima8/usecode/uc_list.cpp
@@ -124,6 +124,10 @@ void UCList::save(Common::WriteStream *ws) const {
 bool UCList::load(Common::ReadStream *rs, uint32 version) {
 	_elementSize = rs->readUint32LE();
 	_size = rs->readUint32LE();
+	if (_elementSize * _size > 1024 * 1024) {
+		warning("Improbable UCList size %d x %d, corrupt save?", _elementSize, _size);
+		return false;
+	}
 	_elements.resize(_size * _elementSize);
 	rs->read(&(_elements[0]), _size * _elementSize);
 
diff --git a/engines/ultima/ultima8/usecode/uc_machine.cpp b/engines/ultima/ultima8/usecode/uc_machine.cpp
index 34dd51200f..709e2e6d33 100644
--- a/engines/ultima/ultima8/usecode/uc_machine.cpp
+++ b/engines/ultima/ultima8/usecode/uc_machine.cpp
@@ -2411,6 +2411,12 @@ bool UCMachine::loadLists(Common::ReadStream *rs, uint32 version) {
 	if (!_listIDs->load(rs, version)) return false;
 
 	uint32 listcount = rs->readUint32LE();
+
+	if (listcount > 65536) {
+		warning("Improbable number of UC lists %d in save, corrupt save?", listcount);
+		return false;
+	}
+
 	for (unsigned int i = 0; i < listcount; ++i) {
 		uint16 lid = rs->readUint16LE();
 		UCList *l = new UCList(2); // the "2" will be ignored by load()
diff --git a/engines/ultima/ultima8/world/map.cpp b/engines/ultima/ultima8/world/map.cpp
index d9e4bfd41b..8cacf4115e 100644
--- a/engines/ultima/ultima8/world/map.cpp
+++ b/engines/ultima/ultima8/world/map.cpp
@@ -302,6 +302,12 @@ void Map::save(Common::WriteStream *ws) {
 bool Map::load(Common::ReadStream *rs, uint32 version) {
 	uint32 itemcount = rs->readUint32LE();
 
+	// Integrity check
+	if (itemcount > 65536) {
+		warning("improbable item count in map data: %d", itemcount);
+		return false;
+	}
+
 	for (unsigned int i = 0; i < itemcount; ++i) {
 		Object *obj = ObjectManager::get_instance()->loadObject(rs, version);
 		Item *item = dynamic_cast<Item *>(obj);
diff --git a/engines/ultima/ultima8/world/world.cpp b/engines/ultima/ultima8/world/world.cpp
index 6461b906c3..28e524ecbb 100644
--- a/engines/ultima/ultima8/world/world.cpp
+++ b/engines/ultima/ultima8/world/world.cpp
@@ -394,6 +394,12 @@ void World::saveMaps(Common::WriteStream *ws) {
 bool World::loadMaps(Common::ReadStream *rs, uint32 version) {
 	uint32 mapcount = rs->readUint32LE();
 
+	// Integrity check
+	if (mapcount > _maps.size()) {
+		warning("Invalid mapcount in save: %d.  Corrupt save?", mapcount);
+		return false;
+	}
+
 	// Map objects have already been created by reset()
 	for (unsigned int i = 0; i < mapcount; ++i) {
 		bool res = _maps[i]->load(rs, version);


Commit: 8eeb19c53d6d41710a23ef066f01186d66c719db
    https://github.com/scummvm/scummvm/commit/8eeb19c53d6d41710a23ef066f01186d66c719db
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-05-04T21:37:50+09:00

Commit Message:
ULTIMA8: Small consistency fixes for Crusader gumps

Changed paths:
    engines/ultima/ultima8/gumps/cru_stat_gump.cpp
    engines/ultima/ultima8/gumps/cru_status_gump.cpp
    engines/ultima/ultima8/gumps/cru_weapon_gump.cpp


diff --git a/engines/ultima/ultima8/gumps/cru_stat_gump.cpp b/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
index d1a91eb839..c27fc93339 100644
--- a/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
+++ b/engines/ultima/ultima8/gumps/cru_stat_gump.cpp
@@ -41,17 +41,17 @@ CruStatGump::~CruStatGump() {
 }
 
 void CruStatGump::InitGump(Gump *newparent, bool take_focus) {
-	Gump::InitGump(newparent, take_focus);
+	TranslucentGump::InitGump(newparent, take_focus);
 
 	UpdateDimsFromShape();
 }
 
 void CruStatGump::saveData(Common::WriteStream *ws) {
-	Gump::saveData(ws);
+	TranslucentGump::saveData(ws);
 }
 
 bool CruStatGump::loadData(Common::ReadStream *rs, uint32 version) {
-	return Gump::loadData(rs, version);
+	return TranslucentGump::loadData(rs, version);
 }
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/gumps/cru_status_gump.cpp b/engines/ultima/ultima8/gumps/cru_status_gump.cpp
index 228b02f4a2..ca843b0769 100644
--- a/engines/ultima/ultima8/gumps/cru_status_gump.cpp
+++ b/engines/ultima/ultima8/gumps/cru_status_gump.cpp
@@ -109,12 +109,14 @@ void CruStatusGump::saveData(Common::WriteStream *ws) {
 }
 
 bool CruStatusGump::loadData(Common::ReadStream *rs, uint32 version) {
-	if (Gump::loadData(rs, version)) {
-		createStatusItems();
-		return true;
-	} else {
+	if (!Gump::loadData(rs, version))
 		return false;
-	}
+
+	if (_instance && _instance != this)
+		delete _instance;
+	createStatusItems();
+	_instance = this;
+	return true;
 }
 
 uint32 CruStatusGump::I_hideStatusGump(const uint8 * /*args*/,
@@ -133,6 +135,7 @@ uint32 CruStatusGump::I_showStatusGump(const uint8 * /*args*/,
 	if (!instance) {
 		instance = new CruStatusGump();
 		instance->InitGump(nullptr, false);
+		assert(_instance);
 	}
 	return 0;
 }
diff --git a/engines/ultima/ultima8/gumps/cru_weapon_gump.cpp b/engines/ultima/ultima8/gumps/cru_weapon_gump.cpp
index 26dc83a60a..5d60dd8e82 100644
--- a/engines/ultima/ultima8/gumps/cru_weapon_gump.cpp
+++ b/engines/ultima/ultima8/gumps/cru_weapon_gump.cpp
@@ -110,11 +110,11 @@ void CruWeaponGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scale
 }
 
 void CruWeaponGump::saveData(Common::WriteStream *ws) {
-	Gump::saveData(ws);
+	CruStatGump::saveData(ws);
 }
 
 bool CruWeaponGump::loadData(Common::ReadStream *rs, uint32 version) {
-	return Gump::loadData(rs, version);
+	return CruStatGump::loadData(rs, version);
 }
 
 } // End of namespace Ultima8




More information about the Scummvm-git-logs mailing list