[Scummvm-git-logs] scummvm master -> 1267341d74c62dc2cc0d185ccf0a93aea7352ceb

mduggan mgithub at guarana.org
Sat Oct 31 23:58:42 UTC 2020


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

Summary:
628620f680 ULTIMA8: Correct a lot of things about AttackProcess
5512efa1b9 ULTIMA8: Implement fireDistance intrinsic
c54bfbe919 ULTIMA8: Correct unkegg ids for Crusader
1267341d74 ULTIMA8: Avoid slowing game down by failed attack pathfinding


Commit: 628620f680100a6fcb5f734f2bbe69af6b9a869c
    https://github.com/scummvm/scummvm/commit/628620f680100a6fcb5f734f2bbe69af6b9a869c
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-11-01T08:50:23+09:00

Commit Message:
ULTIMA8: Correct a lot of things about AttackProcess

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


diff --git a/engines/ultima/ultima8/convert/crusader/convert_usecode_regret.h b/engines/ultima/ultima8/convert/crusader/convert_usecode_regret.h
index de41665525..727162fc9f 100644
--- a/engines/ultima/ultima8/convert/crusader/convert_usecode_regret.h
+++ b/engines/ultima/ultima8/convert/crusader/convert_usecode_regret.h
@@ -239,7 +239,7 @@ const char* const ConvertUsecodeRegret::_intrinsics[] = {
 	"Item::isOn(uint16)",
 	"Item::getFootpad(sint16&,sint16&,sint16&)",
 	"Actor::isDead(void)",
-	"Actor::createNPCCru()",
+	"Actor::I_createActorCru()",
 	"Actor::I_setActivity()",
 	"KeypadGump::I_showKeypad()",
 	"Item::andStatus(void)",
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index 06e3e5954e..143b89ffe0 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -216,7 +216,7 @@ Intrinsic RemorseIntrinsics[] = {
 	Actor::I_setDead,
 	0, // I_playFlic(char *) Intrinsic0A9(void)
 	AudioProcess::I_playSFX, // void Intrinsic0AA(2 bytes)
-	Actor::I_getField0x59Bit1, // int Intrinsic0AB(4 bytes)
+	0, // int Actor::I_getFlag0x59Field1  Intrinsic0AB(4 bytes)
 	Item::I_getFamilyOfType, // void Intrinsic0AC(2 bytes)
 	Item::I_getNpcNum, // based on same coff as 102 (-> variable name in TRIGGER::ordinal21)
 	Item::I_getQLo, // based on same coff set as 02B
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 7dc2c3cedf..df7f7a8b6b 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -480,10 +480,15 @@ uint16 Actor::doAnim(Animation::Sequence anim, Direction dir, unsigned int steps
 	}
 #endif
 
-	// HACK: When switching from 16-dir combat to 8-dir walking,
-	// fix the direction to only 8 dirs
-	if (GAME_IS_CRUSADER && anim == Animation::stand) {
-		dir = static_cast<Direction>(dir - (static_cast<uint32>(dir) % 2));
+	if (GAME_IS_CRUSADER) {
+		// HACK: When switching from 16-dir combat to 8-dir walking,
+		// fix the direction to only 8 dirs
+		if (anim == Animation::stand)
+			dir = static_cast<Direction>(dir - (static_cast<uint32>(dir) % 2));
+		else if (anim == Animation::readyWeapon)
+			setActorFlag(ACT_WEAPONREADY);
+		else if (anim == Animation::unreadyWeapon)
+			clearActorFlag(ACT_WEAPONREADY);
 	}
 
 	Process *p = new ActorAnimProcess(this, anim, dir, steps);
@@ -2123,14 +2128,30 @@ uint32 Actor::I_createActorCru(const uint8 *args, unsigned int /*argsize*/) {
 
 	newactor->setUnkByte(item->getQuality() & 0xff);
 
+	bool wpnflag = (item->getMapNum() & 4);
 	uint16 wpntype = npcData->getWpnType();
-	Item *weapon = ItemFactory::createItem(wpntype, 0, 0, 0, 0, newactor->getMapNum(), 0, true);
+	uint16 wpntype2 = npcData->getWpnType2();
+
 	if (World::get_instance()->getGameDifficulty() == 4) {
 	   wpntype = NPCDat::randomlyGetStrongerWeaponTypes(shape);
 	}
 
-	// TODO: should this be addItemCru? If so need to move it from MainActor.
-	weapon->moveToContainer(newactor, false);
+	if ((!wpntype || !wpnflag) && wpntype2) {
+		wpntype = wpntype2;
+	}
+
+	if (wpntype) {
+		// TODO: Nasty hard coded list.. use the ini file for this.
+		static const int WPNSHAPES[] = {0, 0x032E, 0x032F, 0x0330, 0x038C, 0x0332, 0x0333,
+			0x0334, 0x038E, 0x0388, 0x038A, 0x038D, 0x038B, 0x0386};
+		// wpntype is an offset into wpn table
+		Item *weapon = ItemFactory::createItem(WPNSHAPES[wpntype], 0, 0, 0, 0, newactor->getMapNum(), 0, true);
+		if (weapon) {
+			weapon->moveToContainer(newactor, false);
+			newactor->_activeWeapon = weapon->getObjId();
+		}
+	}
+
 	newactor->setCombatTactic(0);
 	newactor->setHomePosition(x, y, z);
 
@@ -2304,15 +2325,5 @@ uint32 Actor::I_turnToward(const uint8 *args, unsigned int /*argsize*/) {
 	return actor->turnTowardDir(Direction_FromUsecodeDir(dir));
 }
 
-uint32 Actor::I_getField0x59Bit1(const uint8 *args, unsigned int /*argsize*/) {
-	ARG_ACTOR_FROM_PTR(actor);
-	if (!actor) return 0;
-
-	if (actor->hasActorFlags(ACT_CRU5ABIT1))
-		return 1;
-	return 0;
-}
-
-
 } // End of namespace Ultima8
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index 4897cb4499..c8b943a339 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -345,7 +345,6 @@ public:
 	INTRINSIC(I_getLastActivityNo);
 	INTRINSIC(I_getCurrentActivityNo);
 	INTRINSIC(I_turnToward);
-	INTRINSIC(I_getField0x59Bit1);
 
 	enum ActorFlags {
 		ACT_INVINCIBLE     = 0x000001, // flags from npcdata byte 0x1B
@@ -357,7 +356,7 @@ public:
 		ACT_INCOMBAT       = 0x000800,
 		ACT_DEAD           = 0x001000,
 		ACT_SURRENDERED    = 0x002000, // not the same bit used in Crusader, but use this because it's empty.
-		ACT_CRU5ABIT1	   = 0x004000, // not the same bit used in Crusader, but use this because it's empty.
+		ACT_WEAPONREADY	   = 0x004000, // not the same bit used in Crusader, but use this because it's empty.
 		ACT_COMBATRUN      = 0x008000,
 
 		ACT_AIRWALK        = 0x010000, // flags from npcdata byte 0x30
diff --git a/engines/ultima/ultima8/world/actors/attack_process.cpp b/engines/ultima/ultima8/world/actors/attack_process.cpp
index 58aa0d0c6f..b74954464d 100644
--- a/engines/ultima/ultima8/world/actors/attack_process.cpp
+++ b/engines/ultima/ultima8/world/actors/attack_process.cpp
@@ -63,7 +63,7 @@ static const int16 ATTACK_SFX_7[] = {0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
 
 // If data is referenced in the metalang with an offset of this or greater,
 // read from the data array.
-static const int MAGIC_DATA_OFFSET = 33000;
+static const int MAGIC_DATA_OFF = 33000;
 
 
 static uint16 someSleepGlobal = 0;
@@ -90,6 +90,9 @@ _npcInitialDir(dir_invalid), _field57(0), _field59(0), _field7f(false), _field96
 _isActivity9orB(false), _isActivityAorB(false), _timer3set(false), _timer2set(false),
 _doubleDelay(false), _wpnField8(1), _wpnBasedTimeout(0), _difficultyBasedTimeout(0), _timer2(0),
 _timer3(0), _timer4(0), _timer5(0), _soundTimestamp(0), _fireTimestamp(0) {
+	for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
+		_dataArray[i] = 0;
+	}
 }
 
 AttackProcess::AttackProcess(Actor *actor) : _block(0), _target(1), _tactic(0), _tacticDat(nullptr),
@@ -102,6 +105,13 @@ _soundTimestamp(0), _fireTimestamp(0) {
 	_itemNum = actor->getObjId();
 	_npcInitialDir = actor->getDir();
 
+	// Note: this isn't actually initialized in the original which
+	// suggests it can't ever get used before setting, but to make
+	// coverity etc happy clear it anyway.
+	for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
+		_dataArray[i] = 0;
+	}
+
 	const Item *wpn = getItem(actor->getActiveWeapon());
 	if (wpn) {
 		const uint32 wpnshape = wpn->getShape();
@@ -186,13 +196,13 @@ void AttackProcess::run() {
 	switch (opcode) {
 		case 0x81:
 			// Seems like field 0x53 is never used anywhere?
-			/*_field53 = */readNextWordWithGlobals();
+			/*_field53 = */readNextWordWithData();
 			return;
 		case 0x82:
 			/*_field53 = 0*/;
 			return;
 		case 0x84:
-			_target = readNextWordWithGlobals();
+			_target = readNextWordWithData();
 			// This is called in the original, but basically redundant.
 			// a->setActivity(5);
 			return;
@@ -279,7 +289,7 @@ void AttackProcess::run() {
 		case 0x93:
 		{
 			// Sleep for a random value scaled by difficult level
-			int ticks = readNextWordWithGlobals();
+			int ticks = readNextWordWithData();
 			if (ticks == someSleepGlobal) {
 				ticks = randomOf(0x32) + 0x14;
 			}
@@ -290,7 +300,7 @@ void AttackProcess::run() {
 		case 0x94:
 		{
 			// Loiter a bit..
-			uint16 data = readNextWordWithGlobals();
+			uint16 data = readNextWordWithData();
 			ProcId pid = Kernel::get_instance()->addProcess(new LoiterProcess(a, data));
 			waitFor(pid);
 			return;
@@ -303,11 +313,11 @@ void AttackProcess::run() {
 		}
 		case 0x96:
 			// do activity specified by next word
-			a->setActivity(readNextWordWithGlobals());
+			a->setActivity(readNextWordWithData());
 			return;
 		case 0x97:
 			// switch to tactic no specified by next word
-			setTacticNo(readNextWordWithGlobals());
+			setTacticNo(readNextWordWithData());
 			return;
 		case 0x98:
 		{
@@ -328,7 +338,7 @@ void AttackProcess::run() {
 			a->getLocation(apt);
 			target->getLocation(tpt);
 			int maxdiff = apt.maxDistXYZ(tpt);
-			int16 data = readNextWordWithGlobals();
+			int16 data = readNextWordWithData();
 			if (maxdiff < 481) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
 			}
@@ -341,7 +351,7 @@ void AttackProcess::run() {
 			a->getLocation(apt);
 			target->getLocation(tpt);
 			int maxdiff = apt.maxDistXYZ(tpt);
-			int16 data = readNextWordWithGlobals();
+			int16 data = readNextWordWithData();
 			if (maxdiff > 160) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
 			}
@@ -350,7 +360,7 @@ void AttackProcess::run() {
 		case 0x9c:
 		{
 			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
-			uint16 data = readNextWordWithGlobals();
+			uint16 data = readNextWordWithData();
 			if (!result) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
 			}
@@ -359,7 +369,7 @@ void AttackProcess::run() {
 		case 0x9d:
 		{
 			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
-			uint16 data = readNextWordWithGlobals();
+			uint16 data = readNextWordWithData();
 			if (result) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
 			}
@@ -367,21 +377,21 @@ void AttackProcess::run() {
 		}
 		case 0x9e:
 		{
-			uint16 maxval = readNextWordWithGlobals();
+			uint16 maxval = readNextWordWithData();
 			uint16 randval = randomOf(maxval);
-			uint16 offset = readNextWordWithGlobals();
+			uint16 offset = readNextWordWithData();
 			if (randval != 0) {
 				_tacticDatReadStream->seek(offset);
 			}
 			return;
 		}
 		case 0x9f:
-			_field57 = readNextWordWithGlobals();
+			_field57 = readNextWordWithData();
 			_field59 = _tacticDatReadStream->pos();
 			return;
 		case 0xa6:
 		{
-			const uint16 targetFrame = readNextWordWithGlobals();
+			const uint16 targetFrame = readNextWordWithData();
 			const uint16 targetQ = a->getUnkByte();
 
 			UCList uclist(2);
@@ -430,57 +440,57 @@ void AttackProcess::run() {
 			return;
 		case 0xaf:
 		{
-			uint16 next = readNextWordWithGlobals();
-			uint16 offset = readNextWord();
-			setAttackDataArray(offset, next);
+			uint16 next = readNextWordWithData();
+			uint16 offset = readNextWordRaw();
+			setAttackData(offset, next);
 			return;
 		}
 		case 0xb0:
 		{
-			uint16 offset = readNextWord();
-			uint16 val = getAttackDataArray(offset);
-			setAttackDataArray(opcode, val + readNextWordWithGlobals());
+			uint16 offset = readNextWordRaw();
+			uint16 val = getAttackData(offset);
+			setAttackData(opcode, val + readNextWordWithData());
 			return;
 		}
 		case 0xb1:
 		{
-			uint16 offset = readNextWord();
-			uint16 val = getAttackDataArray(offset);
-			setAttackDataArray(offset, val - readNextWordWithGlobals());
+			uint16 offset = readNextWordRaw();
+			uint16 val = getAttackData(offset);
+			setAttackData(offset, val - readNextWordWithData());
 			return;
 		}
 		case 0xb2:
 		{
-			uint16 offset = readNextWord();
-			uint16 val = getAttackDataArray(offset);
-			setAttackDataArray(offset, val * readNextWordWithGlobals());
+			uint16 offset = readNextWordRaw();
+			uint16 val = getAttackData(offset);
+			setAttackData(offset, val * readNextWordWithData());
 			return;
 		}
 		case 0xb3:
 		{
-			uint16 offset = readNextWord();
-			uint16 val = getAttackDataArray(offset);
-			setAttackDataArray(opcode, val / readNextWordWithGlobals());
+			uint16 offset = readNextWordRaw();
+			uint16 val = getAttackData(offset);
+			setAttackData(opcode, val / readNextWordWithData());
 			return;
 		}
 		case 0xb4:
 		{
 			uint16 dir = Direction_ToUsecodeDir(curdir);
-			uint16 offset = readNextWord();
-			setAttackDataArray(offset, dir);
+			uint16 offset = readNextWordRaw();
+			setAttackData(offset, dir);
 			return;
 		}
 		case 0xb5:
 		{
-			uint16 dir = readNextWordWithGlobals();
+			uint16 dir = readNextWordWithData();
 			a->setDir(Direction_FromUsecodeDir(dir));
 			return;
 		}
 		case 0xb6:
 		{
-			uint16 offset = readNextWord();
+			uint16 offset = readNextWordRaw();
 			uint16 dir = Direction_ToUsecodeDir(curdir);
-			setAttackDataArray(offset, dir);
+			setAttackData(offset, dir);
 			return;
 		}
 		case 0xb7:
@@ -493,7 +503,7 @@ void AttackProcess::run() {
 			a->doAnim(Animation::kneelingSlowRetreat, dir_current);
 			return;
 		case 0xc0:
-			_tacticDatReadStream->seek(readNextWordWithGlobals(), SEEK_SET);
+			_tacticDatReadStream->seek(readNextWordWithData(), SEEK_SET);
 			return;
 		case 0xc1:
 			_field57--;
@@ -669,7 +679,7 @@ void AttackProcess::genericAttack() {
 
 			checkRandomAttackSound(now, a->getShape());
 
-			if (!a->hasActorFlags(Actor::ACT_CRU5ABIT1)) {
+			if (!a->hasActorFlags(Actor::ACT_WEAPONREADY)) {
 				_timer4 = now;
 				a->doAnim(Animation::readyWeapon, dir_current); // ready small wpn
 				return;
@@ -733,7 +743,7 @@ void AttackProcess::genericAttack() {
 			}
 
 			// 5a flag 1 set?
-			if (!a->hasActorFlags(Actor::ACT_CRU5ABIT1) && local_1b) {
+			if (!a->hasActorFlags(Actor::ACT_WEAPONREADY) && local_1b) {
 				_timer4 = now;
 				a->doAnim(Animation::readyWeapon, dir_current); // ready SmallWpn
 				return;
@@ -776,7 +786,7 @@ void AttackProcess::checkRandomAttackSound(int now, uint32 shapeno) {
 			}
 		}
 	} else {
-		if (checkSoundTimeElapsed(now)) {
+		if (readyForNextSound(now)) {
 			if (shapeno == 0x2df)
 				attacksound = RANDOM_ELEM(ATTACK_SFX_6);
 			else if (shapeno == 899)
@@ -790,7 +800,7 @@ void AttackProcess::checkRandomAttackSound(int now, uint32 shapeno) {
 	}
 }
 
-bool AttackProcess::checkSoundTimeElapsed(int now) {
+bool AttackProcess::readyForNextSound(int now) {
 	if (_soundTimestamp == 0 || _soundTimestamp - now >= 480) {
 		_soundTimestamp = now;
 		return true;
@@ -805,18 +815,18 @@ bool AttackProcess::checkTimer2PlusDelayElapsed(int now) {
 	return (now > _timer2 + delay);
 }
 
-void AttackProcess::setAttackDataArray(uint16 offset, uint16 val) {
-	if (offset >= MAGIC_DATA_OFFSET && offset < MAGIC_DATA_OFFSET + 10)
-		_dataArray[offset] = val;
+void AttackProcess::setAttackData(uint16 off, uint16 val) {
+	if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray))
+		_dataArray[off] = val;
 
-	warning("Invalid offset to setAttackDataArray %d %d", offset, val);
+	warning("Invalid offset to setAttackDataArray %d %d", off, val);
 }
 
-uint16 AttackProcess::getAttackDataArray(uint16 offset) const {
-	if (offset >= MAGIC_DATA_OFFSET && offset < MAGIC_DATA_OFFSET + 10)
-		return _dataArray[offset];
+uint16 AttackProcess::getAttackData(uint16 off) const {
+	if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray))
+		return _dataArray[off];
 
-	warning("Invalid offset to getAttackDataArray: %d", offset);
+	warning("Invalid offset to getAttackDataArray: %d", off);
 	return 0;
 }
 
@@ -904,16 +914,16 @@ void AttackProcess::setBlockNo(int block) {
 	_tacticDatReadStream->seek(_tacticDatStartOffset, SEEK_SET);
 }
 
-uint16 AttackProcess::readNextWordWithGlobals() {
+uint16 AttackProcess::readNextWordWithData() {
 	uint16 data = _tacticDatReadStream->readUint16LE();
-	if (data >= MAGIC_DATA_OFFSET) {
-		data = getAttackDataArray(data);
+	if (data >= MAGIC_DATA_OFF) {
+		data = getAttackData(data);
 	}
 
 	return data;
 }
 
-uint16 AttackProcess::readNextWord() {
+uint16 AttackProcess::readNextWordRaw() {
 	assert(_tacticDatReadStream);
 	return _tacticDatReadStream->readUint16LE();
 }
@@ -949,7 +959,7 @@ void AttackProcess::saveData(Common::WriteStream *ws) {
 
 	ws->writeUint16LE(_wpnField8);
 
-	for (int i = 0; i < 10; i++) {
+	for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
 		ws->writeUint16LE(_dataArray[i]);
 	}
 
@@ -989,7 +999,7 @@ bool AttackProcess::loadData(Common::ReadStream *rs, uint32 version) {
 
 	_wpnField8 = rs->readUint16LE();
 
-	for (int i = 0; i < 10; i++) {
+	for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
 		_dataArray[i] = rs->readUint16LE();
 	}
 
diff --git a/engines/ultima/ultima8/world/actors/attack_process.h b/engines/ultima/ultima8/world/actors/attack_process.h
index 39efa7aab3..041c9aaa53 100644
--- a/engines/ultima/ultima8/world/actors/attack_process.h
+++ b/engines/ultima/ultima8/world/actors/attack_process.h
@@ -34,6 +34,11 @@ namespace Ultima8 {
 class Actor;
 class CombatDat;
 
+/**
+ * The NPC attack process used in Crusader games.  This is more advanced than the Ultima
+ * CombatProcess, and contains a small language to implement the AI, which is specified in
+ * the combat.dat file (see CombatDat class)
+ */
 class AttackProcess : public Process {
 public:
 	AttackProcess();
@@ -66,25 +71,41 @@ public:
 	void saveData(Common::WriteStream *ws) override;
 
 private:
-	void setBlockNo(int block);
+	/** Set the current tactic in use from the combat.dat file.  If 0,
+	 * will use the genericAttack function. */
 	void setTacticNo(int block);
+	/** Set the sub-tactic block - should be 0 or 1 (although 0-3 are
+	 * supported in the dat file format, only 0/1 are ever used) */
+	void setBlockNo(int block);
 
-	uint16 readNextWordWithGlobals();
-	uint16 readNextWord();
+	/// Read the next word and return the value without using array
+	uint16 readNextWordRaw();
+	/** Read the next word and pull from the data array if its value
+	 * is over the magic number*/
+	uint16 readNextWordWithData();
 
-	void setAttackDataArray(uint16 offset, uint16 val);
-	uint16 getAttackDataArray(uint16 offset) const;
+	/// set data in the array - offset includes the magic number
+	void setAttackData(uint16 offset, uint16 val);
+	/// get data from the array - offset includes the magic number
+	uint16 getAttackData(uint16 offset) const;
 
-	// This is the equivalent of run() when a tactic hasn't been selected yet.
+	/// This is the equivalent of run() when a tactic hasn't been selected yet.
 	void genericAttack();
 
+	/// Sleep the process for the given number of ticks
 	void sleep(int ticks);
-	bool checkSoundTimeElapsed(int now);
+
+	/// Check the sound timer and return if we are ready for a new sound
+	bool readyForNextSound(int now);
+
 	bool checkTimer2PlusDelayElapsed(int now);
 	void pathfindToItemInNPCData();
 	bool timer4and5Update(int now);
 	void timeNowToTimerVal2(int now);
 	bool checkReady(int now, Direction targetdir);
+
+	/** Check if it's time to make a sound and if so start one - for most NPCs
+	 * that's on startup, but some make regular sounds (see readyForNextSound) */
 	void checkRandomAttackSound(int now, uint32 shapeno);
 
 	uint16 _target; // TODO: this is stored in NPC in game, does it matter?
@@ -115,17 +136,19 @@ private:
 
 	uint16 _wpnField8;
 
+	/// an array used to hold data for the combat lang
 	uint16 _dataArray[10];
 
 	int32 _wpnBasedTimeout;
 	int32 _difficultyBasedTimeout;
 
-	int32 _timer2; // 0x73 / 0x75 in orig
-	int32 _timer3; // 0x77 / 0x79 in orig
-	int32 _timer4; // 0x6f / 0x71 in orig
-	int32 _timer5; // 0x8a / 0x8c in oric
-	int32 _soundTimestamp; // 0x84 / 0x86 in orig
-	int32 _fireTimestamp; // 0x90 / 0x92 in orig
+	int32 _timer2; // 0x73/0x75 in orig
+	int32 _timer3; // 0x77/0x79 in orig
+	int32 _timer4; // 0x6f/0x71 in orig
+	int32 _timer5; // 0x8a/0x8c in orig
+
+	int32 _soundTimestamp; /// 0x84/0x86 in orig - time a sound was last played
+	int32 _fireTimestamp; /// 0x90/0x92 in orig - time NPC last fired
 
 };
 
diff --git a/engines/ultima/ultima8/world/actors/npc_dat.cpp b/engines/ultima/ultima8/world/actors/npc_dat.cpp
index a5d0927323..0b009733fb 100644
--- a/engines/ultima/ultima8/world/actors/npc_dat.cpp
+++ b/engines/ultima/ultima8/world/actors/npc_dat.cpp
@@ -39,7 +39,7 @@ NPCDat::NPCDat(Common::SeekableReadStream &rs, Common::SeekableReadStream &namer
 	//
 	rs.skip(20);
 	// offset 0x18 (24): wpntable offset
-	/*uint16 _wpnTableOffset =*/ rs.readUint16LE();
+	_wpnType2 = rs.readUint16LE();
 	// offset 0x1a (26): wpntype
 	_wpnType = rs.readUint16LE();
 	rs.skip(2);
diff --git a/engines/ultima/ultima8/world/actors/npc_dat.h b/engines/ultima/ultima8/world/actors/npc_dat.h
index cf4003f2a6..1b89ffdc62 100644
--- a/engines/ultima/ultima8/world/actors/npc_dat.h
+++ b/engines/ultima/ultima8/world/actors/npc_dat.h
@@ -54,6 +54,10 @@ public:
 		return _wpnType;
 	};
 
+	uint16 getWpnType2() const {
+		return _wpnType2;
+	};
+
 	uint16 getDefaultActivity(int no) const {
 		assert(no >= 0 && no < 3);
 		return _defaultActivity[no];
@@ -70,6 +74,7 @@ private:
 	uint16 _maxHp;
 	uint16 _shapeNo;
 	uint16 _wpnType;
+	uint16 _wpnType2;
 	uint16 _defaultActivity[3];  // activities 0x6, 0x8, and 0xA in game.
 };
 


Commit: 5512efa1b91fd5174ef688d3f5f1a708acf0cd8d
    https://github.com/scummvm/scummvm/commit/5512efa1b91fd5174ef688d3f5f1a708acf0cd8d
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-11-01T08:50:23+09:00

Commit Message:
ULTIMA8: Implement fireDistance intrinsic

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/actors/actor.h
    engines/ultima/ultima8/world/actors/attack_process.cpp
    engines/ultima/ultima8/world/current_map.h
    engines/ultima/ultima8/world/item.cpp
    engines/ultima/ultima8/world/item.h


diff --git a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
index 94eea93e34..71bab6143b 100644
--- a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
+++ b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
@@ -97,7 +97,7 @@ const char* const ConvertUsecodeCrusader::_intrinsics[] = {
 	"void Item::I_receiveHit(Item *, other, dir, damage, damagetype)", // based on disasm
 	"byte Actor::I_isBusy(4 bytes)", // same code as U8
 	"int16 Item::I_getDirFromTo16(x1, y1, x2, y2)",
-	"byte Actor::I_getIsKneeling(Item *)",
+	"byte Actor::I_isKneeling(Item *)",
 	"int16 Actor::I_doAnim(12 bytes)", // v. similar code to U8
 	"byte MainActor::I_addItemCru(4 bytes)", // same coff as 0B8
 	"void AudioProcess::I_stopSFXCru(Item *, int16 sndno)",
@@ -336,7 +336,7 @@ const char* const ConvertUsecodeCrusader::_intrinsics[] = {
 	"int16 UCMachine::I_numToStr(int16 num)", // based on VMAIL::func0A example usage
 	"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
 	"int16 Item::I_getNPCNum(Item *)", // part of same coff set 067, 06D, 089, 08E, 0AD, 0F8, 100, 102, 105, 107, 109, 10B, 10D, 10F, 111, 115, 11C, 123, 129
-	"byte Intrinsic116(14 bytes)", // something like distance-to?
+	"byte Item::I_fireDistance(14 bytes)",
 	"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
 	"int16 Item::I_hurl(Item *,8 bytes)", // part of same coff set 028, 08D, 0BD, 0C0, 0C2, 0C8, 0F7, 0F9, 118, 11D
 	"void Item::I_setIsBroken(Item *)", // same coff as 08C, 12A
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index 143b89ffe0..15138e0218 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -330,7 +330,7 @@ Intrinsic RemorseIntrinsics[] = {
 	UCMachine::I_numToStr, // see VMAIL::func0A for example usage
 	Item::I_andStatus, // void Intrinsic114(6 bytes)
 	Item::I_getNpcNum, // based on same coff as 102 (-> variable name in TRIGGER::ordinal21)
-	0, // byte Intrinsic116(14 bytes)
+	Item::I_fireDistance, // byte Intrinsic116(14 bytes)
 	Item::I_andStatus, // void Intrinsic117(6 bytes)
 	Item::I_hurl, // int16 Intrinsic118(12 bytes)
 	Item::I_setBroken, // void Intrinsic119(4 bytes)
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index df7f7a8b6b..1caa6cc757 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -481,6 +481,7 @@ uint16 Actor::doAnim(Animation::Sequence anim, Direction dir, unsigned int steps
 #endif
 
 	if (GAME_IS_CRUSADER) {
+		// Crusader sets some flags on animation start
 		// HACK: When switching from 16-dir combat to 8-dir walking,
 		// fix the direction to only 8 dirs
 		if (anim == Animation::stand)
@@ -489,6 +490,12 @@ uint16 Actor::doAnim(Animation::Sequence anim, Direction dir, unsigned int steps
 			setActorFlag(ACT_WEAPONREADY);
 		else if (anim == Animation::unreadyWeapon)
 			clearActorFlag(ACT_WEAPONREADY);
+		else if (anim == Animation::startKneeling || anim == Animation::kneelAndFire ||
+				 anim == Animation::kneelAndFireSmallWeapon ||
+				 anim == Animation::kneelAndFireLargeWeapon)
+			setActorFlag(ACT_KNEELING);
+		else if (anim == Animation::stopKneeling)
+			clearActorFlag(ACT_KNEELING);
 	}
 
 	Process *p = new ActorAnimProcess(this, anim, dir, steps);
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index c8b943a339..2d69f44847 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -352,6 +352,7 @@ public:
 		ACT_DESCENDING     = 0x000004,
 		ACT_ANIMLOCK       = 0x000008,
 
+		ACT_KNEELING	   = 0x000100, // not the same bit used in Crusader, but use this because it's empty.
 		ACT_FIRSTSTEP      = 0x000400, // flags from npcdata byte 0x2F
 		ACT_INCOMBAT       = 0x000800,
 		ACT_DEAD           = 0x001000,
diff --git a/engines/ultima/ultima8/world/actors/attack_process.cpp b/engines/ultima/ultima8/world/actors/attack_process.cpp
index b74954464d..077cb90dcf 100644
--- a/engines/ultima/ultima8/world/actors/attack_process.cpp
+++ b/engines/ultima/ultima8/world/actors/attack_process.cpp
@@ -68,12 +68,6 @@ static const int MAGIC_DATA_OFF = 33000;
 
 static uint16 someSleepGlobal = 0;
 
-// TODO: work out what this function does - probably like "can see" or "is facing"
-// Should probably be in Actor
-static bool Intrinsic116(Actor *, Actor *, Direction, uint16, uint16, uint16) {
-	return false;
-}
-
 // TODO: Implement me. Set timer for some avatar moves.
 static bool World_FinishedAvatarMoveTimeout() {
 	return true;
@@ -231,7 +225,7 @@ void AttackProcess::run() {
 		}
 		case 0x8a:
 		{
-			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+			bool result = a->fireDistance(target, curdir, 0, 0, 0);
 			// Fire small weapon
 			if (result)
 				a->doAnim(Animation::attack, dir_current);
@@ -239,7 +233,7 @@ void AttackProcess::run() {
 		}
 		case 0x8b:
 		{
-			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+			bool result = a->fireDistance(target, curdir, 0, 0, 0);
 			// Fire large weapon
 			if (result)
 				a->doAnim(Animation::attack, dir_current);
@@ -359,7 +353,7 @@ void AttackProcess::run() {
 		}
 		case 0x9c:
 		{
-			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+			bool result = a->fireDistance(target, curdir, 0, 0, 0);
 			uint16 data = readNextWordWithData();
 			if (!result) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
@@ -368,7 +362,7 @@ void AttackProcess::run() {
 		}
 		case 0x9d:
 		{
-			bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+			bool result = a->fireDistance(target, curdir, 0, 0, 0);
 			uint16 data = readNextWordWithData();
 			if (result) {
 				_tacticDatReadStream->seek(data, SEEK_SET);
@@ -733,7 +727,7 @@ void AttackProcess::genericAttack() {
 				if (standDirMode != dirmode_16dirs) {
 					targetdir = a->getDirToItemCentre(*target);
 				}
-				local_1b = Intrinsic116(a, target, targetdir, 0, 0, 0);
+				local_1b = a->fireDistance(target, targetdir, 0, 0, 0);
 				if (local_1b)
 					timeNowToTimerVal2(now);
 			} else {
@@ -874,7 +868,7 @@ bool AttackProcess::checkReady(int now, Direction targetdir) {
 	Actor *target = getActor(_target);
 	if (!a || !target)
 		return false;
-	return Intrinsic116(a, target, targetdir, 0, 0, 0);
+	return a->fireDistance(target, targetdir, 0, 0, 0) > 0;
 }
 
 void AttackProcess::timeNowToTimerVal2(int now) {
diff --git a/engines/ultima/ultima8/world/current_map.h b/engines/ultima/ultima8/world/current_map.h
index 3542865ac5..ed5d3251ec 100644
--- a/engines/ultima/ultima8/world/current_map.h
+++ b/engines/ultima/ultima8/world/current_map.h
@@ -170,7 +170,7 @@ public:
 		// Bitmask. Bit 0 is x, 1 is y, 2 is z.
 
 		// Use this func to get the interpolated location of the hit
-		void GetInterpolatedCoords(int32 out[3], int32 start[3], int32 end[3]) const {
+		void GetInterpolatedCoords(int32 out[3], const int32 start[3], const int32 end[3]) const {
 			for (int i = 0; i < 3; i++)
 				out[i] = start[i] + ((end[i] - start[i]) * (_hitTime >= 0 ? _hitTime : 0) + (end[i] > start[i] ? 0x2000 : -0x2000)) / 0x4000;
 		}
diff --git a/engines/ultima/ultima8/world/item.cpp b/engines/ultima/ultima8/world/item.cpp
index ae1bc1b619..fa5e6164db 100644
--- a/engines/ultima/ultima8/world/item.cpp
+++ b/engines/ultima/ultima8/world/item.cpp
@@ -72,6 +72,7 @@ namespace Ultima {
 namespace Ultima8 {
 
 static const uint32 SNAP_EGG_SHAPE = 0x4fe;
+static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
 
 // p_dynamic_cast stuff
 DEFINE_RUNTIME_CLASSTYPE_CODE(Item)
@@ -1162,8 +1163,6 @@ uint16 Item::fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype,
 	if (GAME_IS_REGRET)
 		warning("Item::fireWeapon: TODO: Update for Regret (different firetypes)");
 
-	static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
-
 	ix += x;
 	iy += y;
 	iz += z;
@@ -1297,6 +1296,78 @@ uint16 Item::fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype,
 	return 0;
 }
 
+uint16 Item::fireDistance(Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) {
+	if (!other)
+		return 0;
+
+	Actor *a = dynamic_cast<Actor *>(this);
+	if (a) {
+		Animation::Sequence anim;
+		bool kneeling = a->hasActorFlags(Actor::ACT_KNEELING);
+		bool smallwpn = true;
+		MainActor *ma = dynamic_cast<MainActor *>(this);
+		Item *wpn = getItem(a->getActiveWeapon());
+		if (wpn && wpn->getShapeInfo()->_weaponInfo) {
+			smallwpn = wpn->getShapeInfo()->_weaponInfo->_small;
+		}
+
+		if (kneeling) {
+			if (smallwpn)
+				anim = Animation::kneelAndFireSmallWeapon;
+			else
+				anim = Animation::kneelAndFireLargeWeapon;
+		} else {
+			if (ma || smallwpn)
+				anim = Animation::attack;
+			else
+				anim = Animation::fire2;
+		}
+
+		// TODO: Get midpoint of frames in anim.  For now we ignore it and get the centre below.
+	}
+
+	int32 cx, cy, cz;
+	getCentre(cx, cy, cz);
+
+	int32 ox, oy, oz;
+	other->getLocation(ox, oy, oz);
+
+	int32 dist = 0;
+
+	CurrentMap *cm = World::get_instance()->getCurrentMap();
+	if (!cm)
+		return 0;
+	const Item *blocker = nullptr;
+	bool valid = cm->isValidPosition(cx, cy, cz, BULLET_SPLASH_SHAPE,
+									 getObjId(), nullptr, nullptr, &blocker);
+	if (!valid) {
+		if (blocker->getObjId() == other->getObjId())
+			dist = MAX(abs(_x - ox), abs(_y - oy));
+	} else {
+		int32 ocx, ocy, ocz;
+		other->getCentre(ocx, ocy, ocz);
+		const int32 start[3] = {cx, cy, cz};
+		const int32 end[3] {ocx, ocy, cz};
+		const int32 dims[3] = { 2, 2, 2 };
+
+		Std::list<CurrentMap::SweepItem> collisions;
+		Std::list<CurrentMap::SweepItem>::iterator it;
+		cm->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
+					   _objId, false, &collisions);
+		for (it = collisions.begin(); it != collisions.end(); it++) {
+			if (it->_item == getObjId())
+				continue;
+			if (it->_item != other->getObjId())
+				break;
+			int32 out[3];
+			it->GetInterpolatedCoords(out, start, end);
+			dist = MAX(abs(_x - out[0]), abs(_y - out[1]));
+			break;
+		}
+	}
+	return dist / 32;
+}
+
 unsigned int Item::countNearby(uint32 shape, uint16 range) {
 	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
 	UCList itemlist(2);
@@ -3645,5 +3716,20 @@ uint32 Item::I_fireWeapon(const uint8 *args, unsigned int /*argsize*/) {
 	return item->fireWeapon(x * 2, y * 2, z, Direction_FromUsecodeDir(dir), firetype, unkflag);
 }
 
+uint32 Item::I_fireDistance(const uint8 *args, unsigned int /*argsize*/) {
+	ARG_ITEM_FROM_PTR(item);
+	ARG_UINT16(other);
+	ARG_SINT16(dir);
+	ARG_SINT16(xoff);
+	ARG_SINT16(yoff);
+	ARG_SINT16(zoff);
+
+	Item *otheritem = getItem(other);
+
+	if (!item || !otheritem) return 0;
+
+	return item->fireDistance(otheritem, Direction_FromUsecodeDir(dir), xoff * 2, yoff * 2, zoff);
+}
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/item.h b/engines/ultima/ultima8/world/item.h
index 0bf7bdeaa9..eeba0b5314 100644
--- a/engines/ultima/ultima8/world/item.h
+++ b/engines/ultima/ultima8/world/item.h
@@ -390,6 +390,9 @@ public:
 	//! fire the given weapon type in the given direction from location x, y, z.
 	uint16 fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype, char someflag);
 
+	//! get the distance (in map tiles) if we were to fire in this direction
+	uint16 fireDistance(Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff);
+
 	//! get damage points, used in Crusader for item damage.
 	uint8 getDamagePoints() const {
 		return _damagePoints;
@@ -592,6 +595,7 @@ public:
 	INTRINSIC(I_avatarStoleSomething);
 	INTRINSIC(I_isOnScreen);
 	INTRINSIC(I_fireWeapon);
+	INTRINSIC(I_fireDistance);
 
 private:
 	uint32 _shape;   // DO NOT modify this directly! Always use setShape()!


Commit: c54bfbe91977b54983fbc0799d69b64154ce9c13
    https://github.com/scummvm/scummvm/commit/c54bfbe91977b54983fbc0799d69b64154ce9c13
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-11-01T08:56:52+09:00

Commit Message:
ULTIMA8: Correct unkegg ids for Crusader

Changed paths:
    engines/ultima/ultima8/graphics/shape_info.h
    engines/ultima/ultima8/world/item.cpp


diff --git a/engines/ultima/ultima8/graphics/shape_info.h b/engines/ultima/ultima8/graphics/shape_info.h
index cb1005eef9..e1f65ab17d 100644
--- a/engines/ultima/ultima8/graphics/shape_info.h
+++ b/engines/ultima/ultima8/graphics/shape_info.h
@@ -64,6 +64,8 @@ public:
 		SF_QUALITY     = 1,
 		SF_QUANTITY    = 2,
 		SF_GLOBEGG     = 3,
+		// "Unk" eggs are not "unknown", they are triggers for usecode
+		// (unk is the source language for usecode)
 		SF_UNKEGG      = 4,
 		SF_BREAKABLE   = 5,
 		SF_CONTAINER   = 6,
diff --git a/engines/ultima/ultima8/world/item.cpp b/engines/ultima/ultima8/world/item.cpp
index fa5e6164db..87bf7bad16 100644
--- a/engines/ultima/ultima8/world/item.cpp
+++ b/engines/ultima/ultima8/world/item.cpp
@@ -1395,9 +1395,9 @@ uint32 Item::callUsecodeEvent(uint32 event, const uint8 *args, int argsize) {
 	        !(_flags & FLG_FAST_ONLY))
 		return 0;
 
-	// UnkEggs have _quality+0x47F
+	// UnkEggs have class quality + 0x47F in U8, +0x900 in Crusader
 	if (getFamily() == ShapeInfo::SF_UNKEGG)
-		class_id = _quality + 0x47F;
+		class_id = _quality + (GAME_IS_U8 ? 0x47F : 0x900);
 
 	Usecode *u = GameData::get_instance()->getMainUsecode();
 	uint32 offset = u->get_class_event(class_id, event);


Commit: 1267341d74c62dc2cc0d185ccf0a93aea7352ceb
    https://github.com/scummvm/scummvm/commit/1267341d74c62dc2cc0d185ccf0a93aea7352ceb
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-11-01T08:56:55+09:00

Commit Message:
ULTIMA8: Avoid slowing game down by failed attack pathfinding

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 077cb90dcf..8117a9a3a9 100644
--- a/engines/ultima/ultima8/world/actors/attack_process.cpp
+++ b/engines/ultima/ultima8/world/actors/attack_process.cpp
@@ -833,8 +833,9 @@ void AttackProcess::pathfindToItemInNPCData() {
 	Actor *target = getActor(_target);
 
 	Process *pathproc = new PathfinderProcess(a, target->getObjId());
-	// In case pathfinding fails just delay for a bit to ensure we don't get stuck in a notification loop.
-	Process *delayproc = new DelayProcess(2);
+	// In case pathfinding fails delay for a bit to ensure we don't get
+	// stuck in a tight loop using all the cpu
+	Process *delayproc = new DelayProcess(10);
 	Kernel::get_instance()->addProcess(pathproc);
 	Kernel::get_instance()->addProcess(delayproc);
 	delayproc->waitFor(pathproc);




More information about the Scummvm-git-logs mailing list