[Scummvm-git-logs] scummvm master -> 9a37bf5b3b0083fc0b7a7242ce0707f3652f7733

mduggan mgithub at guarana.org
Fri Jun 4 10:38:19 UTC 2021


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

Summary:
9a37bf5b3b ULTIMA8: Implement No Regret's Rolling Thunder process


Commit: 9a37bf5b3b0083fc0b7a7242ce0707f3652f7733
    https://github.com/scummvm/scummvm/commit/9a37bf5b3b0083fc0b7a7242ce0707f3652f7733
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-06-04T19:37:12+09:00

Commit Message:
ULTIMA8: Implement No Regret's Rolling Thunder process

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


diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 3981f3f4a7..19222edffb 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -665,7 +665,7 @@ DirectionMode Actor::animDirMode(Animation::Sequence anim) const {
 	return action->getDirCount() == 8 ? dirmode_8dirs : dirmode_16dirs;
 }
 
-uint16 Actor::turnTowardDir(Direction targetdir) {
+uint16 Actor::turnTowardDir(Direction targetdir, ProcId prevpid /* = 0 */) {
 	bool combatRun = hasActorFlags(Actor::ACT_COMBATRUN);
 	Direction curdir = getDir();
 	bool combat = isInCombat() && !combatRun;
@@ -694,7 +694,6 @@ uint16 Actor::turnTowardDir(Direction targetdir) {
 	}
 
 	ProcId animpid = 0;
-	ProcId prevpid = 0;
 
 	// Create a sequence of turn animations from
 	// our current direction to the new one
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index 73a86a79a4..dd3a75fbe8 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -270,7 +270,8 @@ public:
 
 	//! Turn one step toward the given direction. If the current direction is already the same,
 	//! do nothing. Returns an anim process or 0 if no move needed.
-	uint16 turnTowardDir(Direction dir);
+	//! If a previous pid is specified, wait for that process.
+	uint16 turnTowardDir(Direction dir, ProcId prevpid = 0);
 
 	//! create an actor, assign objid, make it ethereal and load monster stats.
 	static Actor *createActor(uint32 shape, uint32 frame);
@@ -309,6 +310,7 @@ public:
 	void tookHitCru();
 
 	//! Add the x/y/z fire offsets given the current state of the actor
+	//! Return whether or not a "fire" frame was found.
 	void addFireAnimOffsets(int32 &x, int32 &y, int32 &z);
 
 	uint32 getAttackMoveTimeoutFinish() const {
diff --git a/engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp b/engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp
index c1d7c467bb..47903c85ab 100644
--- a/engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp
+++ b/engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp
@@ -21,20 +21,33 @@
  */
 
 #include "ultima/ultima8/world/actors/rolling_thunder_process.h"
+#include "ultima/ultima8/world/world.h"
+#include "ultima/ultima8/world/get_object.h"
+#include "ultima/ultima8/world/loop_script.h"
+#include "ultima/ultima8/world/current_map.h"
 #include "ultima/ultima8/world/actors/actor.h"
+#include "ultima/ultima8/world/actors/pathfinder.h"
+#include "ultima/ultima8/world/actors/anim_action.h"
+#include "ultima/ultima8/games/game_data.h"
+#include "ultima/ultima8/graphics/main_shape_archive.h"
+#include "ultima/ultima8/graphics/anim_dat.h"
 #include "ultima/ultima8/kernel/kernel.h"
 #include "ultima/ultima8/kernel/delay_process.h"
-#include "ultima/ultima8/world/get_object.h"
+#include "ultima/ultima8/usecode/uc_list.h"
+#include "ultima/ultima8/misc/direction_util.h"
 
 namespace Ultima {
 namespace Ultima8 {
 
 DEFINE_RUNTIME_CLASSTYPE_CODE(RollingThunderProcess)
 
-RollingThunderProcess::RollingThunderProcess() : Process() {
+static const uint16 CRUSPID = 0x584;
+static const uint16 BULLET_SPLASH_SHAPE = 0x1d9;
+
+RollingThunderProcess::RollingThunderProcess() : Process(), _target(0), _timer(0) {
 }
 
-RollingThunderProcess::RollingThunderProcess(Actor *actor) {
+RollingThunderProcess::RollingThunderProcess(Actor *actor) : _target(0), _timer(0) {
 	assert(actor);
 	_itemNum = actor->getObjId();
 
@@ -51,22 +64,288 @@ void RollingThunderProcess::run() {
 	}
 
 	if (actor->isBusy()) {
-		Process *wait = new DelayProcess(60);
-		Kernel::get_instance()->addProcess(wait);
-		waitFor(wait);
+		sleepFor60Ticks();
+		return;
+	}
+
+	uint16 controllednpc = World::get_instance()->getControlledNPCNum();
+	const Item *target = getItem(_target);
+
+	// Target the controlled npc, unless our current target is a spider bomb
+	if (_target != controllednpc && (!target || target->getShape() != CRUSPID)) {
+		_target = controllednpc ? controllednpc : 1;
+		target = getItem(_target);
+	}
+
+	const Actor *targeta = dynamic_cast<const Actor *>(target);
+	if (targeta && targeta->isDead()) {
+		_target = controllednpc;
+		sleepFor60Ticks();
+		return;
+	}
+
+	if (!actor->isPartlyOnScreen()) {
+		sleepFor60Ticks();
 		return;
 	}
 
-	warning("TODO: Implement rolling thunder");
-	terminate();
+	Animation::Sequence anim = (getRandom() % 2) ? Animation::combatRollLeft : Animation::combatRollRight;
+
+	Direction actordir = actor->getDir();
+	Direction outdir = actordir;
+	bool canroll = checkDir(anim, outdir);
+
+	if (!canroll) {
+		// try the other way
+		if (anim == Animation::combatRollLeft)
+			anim = Animation::combatRollRight;
+		else
+			anim = Animation::combatRollLeft;
+
+		canroll = checkDir(anim, outdir);
+	}
+
+	if (!canroll) {
+		int32 x, y, z, tx, ty, tz;
+		actor->getLocation(x, y, z);
+		target->getLocation(tx, ty, tz);
+		Direction dirtotarget = Direction_GetWorldDir(ty - y, tx - x, dirmode_16dirs);
+
+		if (dirtotarget == actordir) {
+			uint32 now = Kernel::get_instance()->getTickNum();
+			if (now - actor->getLastTimeWasHit() >= 120) {
+				if (actor->fireDistance(target, dirtotarget, 0, 0, 0)) {
+					actor->doAnim(Animation::attack, dir_current);
+					return;
+				}
+			}
+			checkForSpiderBomb();
+		} else {
+			uint16 turnproc = actor->turnTowardDir(dirtotarget);
+			waitFor(turnproc);
+		}
+	} else {
+		uint16 animpid = actor->doAnim(anim, dir_current);
+		if (outdir != actordir) {
+			animpid = actor->turnTowardDir(outdir, animpid);
+		}
+		int attackcount = (getRandom() % 3) + 1;
+		for (int i = 0; i < attackcount; i++) {
+			animpid = actor->doAnimAfter(Animation::attack, outdir, animpid);
+		}
+		Animation::Sequence rollback;
+		if (anim == Animation::combatRollLeft) {
+			rollback = Animation::combatRollRight;
+		} else {
+			rollback = Animation::combatRollLeft;
+		}
+		animpid = actor->doAnimAfter(rollback, dir_current, animpid);
+		waitFor(animpid);
+	}
+}
+
+
+bool RollingThunderProcess::checkDir(Animation::Sequence anim, Direction &outdir) const {
+	Actor *actor = getActor(_itemNum);
+	Direction curdir = actor->getDir();
+	if (!actor->isPartlyOnScreen())
+		return false;
+
+	const Item *target = getItem(_target);
+	if (!target)
+		return false;
+
+	PathfindingState state;
+	state.load(actor);
+
+	// Check if the anim is blocked or would take the actor off-screen.
+	Animation::Result animresult = actor->tryAnim(anim, dir_current, 0, &state);
+
+	if (animresult == Animation::FAILURE || !actor->isPartlyOnScreen())
+		return false;
+
+	// check if the dir to the target is within 2 direction steps of the current dir
+	int32 tx, ty, tz;
+	target->getLocation(tx, ty, tz);
+	Direction dirtotarget = Direction_GetWorldDir(ty - state._y, tx - state._x, dirmode_16dirs);
+
+	static const int DIROFFSETS[] = {0, -1, 1, -2, 2};
+
+	outdir = dirtotarget;
+
+	// Check that the target is in a nearby direction
+	bool nearby = false;
+	for (int i = 0; i < ARRAYSIZE(DIROFFSETS); i++) {
+		Direction dir = Direction_TurnByDelta(dirtotarget, DIROFFSETS[i], dirmode_16dirs);
+		if (curdir == dir) {
+			nearby = true;
+			break;
+		}
+	}
+	if (!nearby)
+		return false;
+
+	// Check whether we can fire in that direction and hit the target
+	for (int i = 0; i < ARRAYSIZE(DIROFFSETS); i++) {
+		Direction dir = Direction_TurnByDelta(dirtotarget, DIROFFSETS[i], dirmode_16dirs);
+		if (fireDistance(dir, state._x, state._y, state._z))
+			return true;
+	}
+
+	return false;
+}
+
+
+//
+// This is practically a copy of Item::fireDistance, but with some changes
+// to measure from the hypothetical position of the actor after rolling.
+//
+// Ideally it would be refactored, but for now copy it with changes just like
+// the game does.
+//
+bool RollingThunderProcess::fireDistance(Direction dir, int32 x, int32 y, int32 z) const {
+	int32 xoff = 0;
+	int32 yoff = 0;
+	int32 zoff = 0;
+	int32 xoff2 = 0;
+	int32 yoff2 = 0;
+	int32 zoff2 = 0;
+
+	const Actor *actor = getActor(_itemNum);
+	const Item *target = getItem(_target);
+
+	if (!actor || !target)
+		return 0;
+
+	int32 tx, ty, tz;
+	target->getLocation(tx, ty, tz);
+
+	uint16 shapeno = actor->getShape();
+	uint32 actionno = AnimDat::getActionNumberForSequence(Animation::attack, actor);
+	const AnimAction *animaction = GameData::get_instance()->getMainShapes()->getAnim(shapeno, actionno);
+
+	CurrentMap *cm = World::get_instance()->getCurrentMap();
+
+	bool other_offsets = false;
+	bool first_offsets = false;
+	int nframes = animaction->getSize();
+	for (int frameno = 0; frameno < nframes; frameno++) {
+		const AnimFrame &frame = animaction->getFrame(dir, frameno);
+		if (frame.is_cruattack()) {
+			if (!first_offsets) {
+				xoff = frame.cru_attackx();
+				yoff = frame.cru_attacky();
+				zoff = frame.cru_attackz();
+				first_offsets = true;
+			} else {
+				xoff2 = frame.cru_attackx();
+				yoff2 = frame.cru_attacky();
+				zoff2 = frame.cru_attackz();
+				other_offsets = true;
+				break;
+			}
+		}
+	}
+
+	if (!first_offsets)
+		return 0;
+
+	int dist = 0;
+	for (int i = 0; i < (other_offsets ? 2 : 1) && dist == 0; i++) {
+		int32 cx = x + (i == 0 ? xoff : xoff2);
+		int32 cy = y + (i == 0 ? yoff : yoff2);
+		int32 cz = z + (i == 0 ? zoff : zoff2);
+
+		const Item *blocker = nullptr;
+		bool valid = cm->isValidPosition(cx, cy, cz, BULLET_SPLASH_SHAPE,
+									   _itemNum, nullptr, nullptr, &blocker);
+		if (!valid) {
+			if (blocker->getObjId() == target->getObjId())
+				dist = MAX(abs(x - tx), abs(y - ty));
+		} else {
+			int32 ocx, ocy, ocz;
+			target->getCentre(ocx, ocy, ocz);
+			ocz = target->getTargetZRelativeToAttackerZ(z);
+			const int32 start[3] = {cx, cy, cz};
+			const int32 end[3] = {ocx, ocy, ocz};
+			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,
+						 _itemNum, false, &collisions);
+			for (it = collisions.begin(); it != collisions.end(); it++) {
+				if (it->_item == _itemNum)
+					continue;
+				if (it->_item != target->getObjId())
+					break;
+				int32 out[3];
+				it->GetInterpolatedCoords(out, start, end);
+				dist = MAX(abs(x - out[0]), abs(y - out[1]));
+				break;
+			}
+		}
+	}
+	return dist;
+}
+
+bool RollingThunderProcess::checkForSpiderBomb() {
+	const Item *target = getItem(_target);
+	const Actor *actor = getActor(_itemNum);
+
+	if (target && target->getShape() == CRUSPID)
+		return false;
+	if (!checkTimer())
+		return false;
+
+	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
+	UCList spiderlist(2);
+	LOOPSCRIPT(script, LS_SHAPE_EQUAL(CRUSPID));
+	currentmap->areaSearch(&spiderlist, script, sizeof(script), actor, 800, false);
+
+	for (unsigned int i = 0; i < spiderlist.getSize(); ++i) {
+		int32 x, y, z, sx, sy, sz;
+		const Item *spider = getItem(spiderlist.getuint16(i));
+		if (!spider)
+			continue;
+		actor->getLocation(x, y, z);
+		spider->getLocation(sx, sy, sz);
+		Direction dirtospider = Direction_GetWorldDir(sy - y, sx - x, dirmode_16dirs);
+		uint16 dist = actor->fireDistance(spider, dirtospider, 0, 0, 0);
+		if (dist > 0) {
+			_target = spider->getObjId();
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool RollingThunderProcess::checkTimer() {
+	uint32 ticksnow = Kernel::get_instance()->getTickNum();
+	if (ticksnow > _timer + 90) {
+		_timer = ticksnow;
+		return true;
+	}
+	return false;
+}
+
+void RollingThunderProcess::sleepFor60Ticks() {
+	Process *wait = new DelayProcess(60);
+	Kernel::get_instance()->addProcess(wait);
+	waitFor(wait);
 }
 
 void RollingThunderProcess::saveData(Common::WriteStream *ws) {
 	Process::saveData(ws);
+	ws->writeUint16LE(_target);
+	ws->writeUint32LE(_timer);
 }
 
 bool RollingThunderProcess::loadData(Common::ReadStream *rs, uint32 version) {
 	if (!Process::loadData(rs, version)) return false;
+	_target = rs->readUint16LE();
+	_timer = rs->readUint32LE();
 	return true;
 }
 
diff --git a/engines/ultima/ultima8/world/actors/rolling_thunder_process.h b/engines/ultima/ultima8/world/actors/rolling_thunder_process.h
index 59f45ba071..f6f6b15c9e 100644
--- a/engines/ultima/ultima8/world/actors/rolling_thunder_process.h
+++ b/engines/ultima/ultima8/world/actors/rolling_thunder_process.h
@@ -24,6 +24,8 @@
 #define WORLD_ACTORS_ROLLINGTHUNDERPROCESS_H
 
 #include "ultima/ultima8/kernel/process.h"
+#include "ultima/ultima8/misc/direction.h"
+#include "ultima/ultima8/world/actors/animation.h"
 
 namespace Ultima {
 namespace Ultima8 {
@@ -46,6 +48,19 @@ public:
 	bool loadData(Common::ReadStream *rs, uint32 version);
 	void saveData(Common::WriteStream *ws) override;
 
+private:
+	void sleepFor60Ticks();
+
+	bool checkForSpiderBomb();
+
+	bool checkTimer();
+
+	bool fireDistance(Direction dir, int32 x, int32 y, int32 z) const;
+
+	bool checkDir(Animation::Sequence anim, Direction &outdir) const;
+
+	uint16 _target;
+	uint32 _timer;
 };
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/item.cpp b/engines/ultima/ultima8/world/item.cpp
index d390b1bbbf..92dba3e5e4 100644
--- a/engines/ultima/ultima8/world/item.cpp
+++ b/engines/ultima/ultima8/world/item.cpp
@@ -1317,7 +1317,7 @@ uint16 Item::fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype,
 	return spriteprocpid;
 }
 
-uint16 Item::fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) {
+uint16 Item::fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) const {
 	if (!other)
 		return 0;
 
@@ -1335,13 +1335,13 @@ uint16 Item::fireDistance(const Item *other, Direction dir, int16 xoff, int16 yo
 	int16 yoff2 = 0;
 	int16 zoff2 = 0;
 	bool other_offsets = false;
-	Actor *a = dynamic_cast<Actor *>(this);
+	const Actor *a = dynamic_cast<const Actor *>(this);
 	if (a) {
 		Animation::Sequence anim;
 		bool kneeling = a->isKneeling();
 		bool smallwpn = true;
-		MainActor *ma = dynamic_cast<MainActor *>(this);
-		Item *wpn = getItem(a->getActiveWeapon());
+		const MainActor *ma = dynamic_cast<const MainActor *>(this);
+		const Item *wpn = getItem(a->getActiveWeapon());
 		if (wpn && wpn->getShapeInfo()->_weaponInfo) {
 			smallwpn = wpn->getShapeInfo()->_weaponInfo->_small;
 		}
diff --git a/engines/ultima/ultima8/world/item.h b/engines/ultima/ultima8/world/item.h
index 54393dab14..2932828221 100644
--- a/engines/ultima/ultima8/world/item.h
+++ b/engines/ultima/ultima8/world/item.h
@@ -395,7 +395,7 @@ public:
 
 	//! get the distance (in map tiles) if we were to fire in this direction to "other"
 	//! and could hit, otherwise return 0.
-	uint16 fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff);
+	uint16 fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) const;
 
 	//! get damage points, used in Crusader for item damage.
 	uint8 getDamagePoints() const {
@@ -407,6 +407,10 @@ public:
 		_damagePoints = points;
 	}
 
+	//! Get the right Z which an attacker should aim for, given the attacker's z.
+	//! (Crusader only)
+	int32 getTargetZRelativeToAttackerZ(int32 attackerz) const;
+
 	//! count nearby objects of a given shape
 	unsigned int countNearby(uint32 shape, uint16 range);
 
@@ -661,10 +665,6 @@ private:
 	//! The Crusader version of receiveHit
 	void receiveHitCru(ObjId other, Direction dir, int damage, uint16 type);
 
-	//! Get the right Z which an attacker should aim for, given the attacker's z.
-	//! (Crusader only)
-	int32 getTargetZRelativeToAttackerZ(int32 attackerz) const;
-
 public:
 	enum statusflags {
 		FLG_DISPOSABLE   = 0x0002,  //!< Item is discarded on map change




More information about the Scummvm-git-logs mailing list