[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