[Scummvm-git-logs] scummvm master -> f021cfaff3c1cfd2185fe15641866dc0cd502e3f

mduggan mgithub at guarana.org
Mon Jul 27 06:28:41 UTC 2020


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

Summary:
95d0fc6364 ULTIMA8: Keep track of the crosshair process instance
18e7da15e3 ULTIMA8: Add ability to return blocker from isValidPosition
8f48861acc ULTIMA8: Document WorldPoint struct
ada0315e8f ULTIMA8: Make pathfinder proc type visible
82c651fcab ULTIMA8: JANITORIAL: Whitespace.
8e72e3c7ef ULTIMA8: Add a simple 3d point struct
f021cfaff3 ULTIMA8: Basic code for firing weapons in Crusader


Commit: 95d0fc63646669a57941305e4b5b1a0e59f83035
    https://github.com/scummvm/scummvm/commit/95d0fc63646669a57941305e4b5b1a0e59f83035
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T13:47:00+09:00

Commit Message:
ULTIMA8: Keep track of the crosshair process instance

Changed paths:
    engines/ultima/ultima8/world/crosshair_process.cpp
    engines/ultima/ultima8/world/crosshair_process.h


diff --git a/engines/ultima/ultima8/world/crosshair_process.cpp b/engines/ultima/ultima8/world/crosshair_process.cpp
index e0585c53ad..3c25519cc7 100644
--- a/engines/ultima/ultima8/world/crosshair_process.cpp
+++ b/engines/ultima/ultima8/world/crosshair_process.cpp
@@ -39,7 +39,10 @@ DEFINE_RUNTIME_CLASSTYPE_CODE(CrosshairProcess)
 static const uint32 CROSSHAIR_SHAPE = 0x4CC;
 static const float CROSSHAIR_DIST = 400.0;
 
+CrosshairProcess *CrosshairProcess::_instance = nullptr;
+
 CrosshairProcess::CrosshairProcess() : Process() {
+	_instance = this;
 }
 
 void CrosshairProcess::run() {
diff --git a/engines/ultima/ultima8/world/crosshair_process.h b/engines/ultima/ultima8/world/crosshair_process.h
index d06ef09b41..8bf706e1f7 100644
--- a/engines/ultima/ultima8/world/crosshair_process.h
+++ b/engines/ultima/ultima8/world/crosshair_process.h
@@ -45,6 +45,13 @@ public:
 
 	bool loadData(Common::ReadStream *rs, uint32 version);
 	void saveData(Common::WriteStream *ws) override;
+
+	static CrosshairProcess *get_instance() {
+		return _instance;
+	}
+
+private:
+	static CrosshairProcess *_instance;
 };
 
 } // End of namespace Ultima8


Commit: 18e7da15e3fb2f0f97359bcb45fc8e5ebeb75dad
    https://github.com/scummvm/scummvm/commit/18e7da15e3fb2f0f97359bcb45fc8e5ebeb75dad
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T13:47:00+09:00

Commit Message:
ULTIMA8: Add ability to return blocker from isValidPosition

Changed paths:
    engines/ultima/ultima8/world/current_map.cpp
    engines/ultima/ultima8/world/current_map.h


diff --git a/engines/ultima/ultima8/world/current_map.cpp b/engines/ultima/ultima8/world/current_map.cpp
index 2bb616e631..7dc266a7f8 100644
--- a/engines/ultima/ultima8/world/current_map.cpp
+++ b/engines/ultima/ultima8/world/current_map.cpp
@@ -683,7 +683,7 @@ const Std::list<Item *> *CurrentMap::getItemList(int32 gx, int32 gy) const {
 bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
                                  uint32 shape,
                                  ObjId item, const Item **support,
-                                 ObjId *roof) const {
+                                 ObjId *roof, const Item **blocker) const {
 	const ShapeInfo *si = GameData::get_instance()->
 	                getMainShapes()->getShapeInfo(shape);
 	int32 xd, yd, zd;
@@ -693,18 +693,18 @@ bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
 	return isValidPosition(x, y, z,
 	                       INT_MAX_VALUE / 2, INT_MAX_VALUE / 2, INT_MAX_VALUE / 2,
 	                       xd, yd, zd,
-	                       si->_flags, item, support, roof);
+	                       si->_flags, item, support, roof, blocker);
 }
 
 bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
                                  int xd, int yd, int zd,
                                  uint32 shapeflags,
-                                 ObjId item_, const Item **support_,
-                                 ObjId *roof_) const {
+                                 ObjId item, const Item **support,
+                                 ObjId *roof, const Item **blocker) const {
 	return isValidPosition(x, y, z,
 	                       INT_MAX_VALUE / 2, INT_MAX_VALUE / 2, INT_MAX_VALUE / 2,
 	                       xd, yd, zd,
-	                       shapeflags, item_, support_, roof_);
+	                       shapeflags, item, support, roof, blocker);
 }
 
 
@@ -713,13 +713,14 @@ bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
                                  int xd, int yd, int zd,
                                  uint32 shapeflags,
                                  ObjId item_, const Item **support_,
-                                 ObjId *roof_) const {
+                                 ObjId *roof_, const Item **blocker_) const {
 	const uint32 flagmask = (ShapeInfo::SI_SOLID | ShapeInfo::SI_DAMAGING |
 	                         ShapeInfo::SI_ROOF);
 	const uint32 blockflagmask = (ShapeInfo::SI_SOLID | ShapeInfo::SI_DAMAGING);
 
 	bool valid = true;
 	const Item *support = nullptr;
+	const Item *blocker = nullptr;
 	ObjId roof = 0;
 	int32 roofz = INT_MAX_VALUE;
 
@@ -772,6 +773,9 @@ bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
 #if 0
 					item->dumpInfo();
 #endif
+					if (blocker == nullptr) {
+						blocker = item;
+					}
 					valid = false;
 				}
 
@@ -796,6 +800,8 @@ bool CurrentMap::isValidPosition(int32 x, int32 y, int32 z,
 
 	if (support_)
 		*support_ = support;
+	if (blocker_)
+		*blocker_ = blocker;
 	if (roof_)
 		*roof_ = roof;
 
@@ -1311,8 +1317,8 @@ uint32 CurrentMap::I_canExistAtPoint(const uint8 *args, unsigned int /*argsize*/
 		return 0;
 
 	if (GAME_IS_CRUSADER) {
-		pt.setX(pt.getX());
-		pt.setY(pt.getY());
+		pt.setX(pt.getX() * 2);
+		pt.setY(pt.getY() * 2);
 	}
 
 	const CurrentMap *cm = World::get_instance()->getCurrentMap();
diff --git a/engines/ultima/ultima8/world/current_map.h b/engines/ultima/ultima8/world/current_map.h
index 7f24f23e7d..9b6350cbf3 100644
--- a/engines/ultima/ultima8/world/current_map.h
+++ b/engines/ultima/ultima8/world/current_map.h
@@ -107,30 +107,33 @@ public:
 
 	// Collision detection. Returns true if the box [x,y,z]-[x-xd,y-yd,z+zd]
 	// does not collide with any solid items.
-	// Additionally, if support is not NULL, *support is set to the item
-	// supporting the given box, or 0 if it isn't supported.
-	// If under_roof is not NULL, *roof is set to the roof item with the lowest
-	// z coordinate that's over the box, or 0 if there is no roof above box.
+	// Additionally:
+	// * If support is not NULL, *support is set to the item supporting
+	//   the given box, or 0 if it isn't supported.
+	// * If roof is not NULL, *roof is set to the roof item with the lowest
+	//   z coordinate that's over the box, or 0 if there is no roof above box.
+	// * If blocker is not NULL, *blocker will be set to an item blocking
+	//   the whole box if there is one, or 0 if there is no such item.
 	// Ignores collisions which were already occurring at the start position.
 	// NB: isValidPosition doesn't consider item 'item'.
 	bool isValidPosition(int32 x, int32 y, int32 z,
 	                     int32 startx, int32 starty, int32 startz,
 	                     int xd, int yd, int zd, uint32 shapeflags,
-	                     ObjId item,
-	                     const Item **support = 0, ObjId *roof = 0) const;
+	                     ObjId item, const Item **support = 0,
+	                     ObjId *roof = 0, const Item **blocker = 0) const;
 
 	// Note that this version of isValidPosition does not look for start
 	// position collisions.
 	bool isValidPosition(int32 x, int32 y, int32 z,
 	                     int xd, int yd, int zd, uint32 shapeflags,
-	                     ObjId item,
-	                     const Item **support = 0, ObjId *roof = 0) const;
+	                     ObjId item, const Item **support = 0,
+						 ObjId *roof = 0, const Item **blocker = 0) const;
 
 	// Note that this version of isValidPosition can not take 'flipped'
 	// into account!
 	bool isValidPosition(int32 x, int32 y, int32 z, uint32 shape,
 	                     ObjId item, const Item **support = 0,
-                         ObjId *roof = 0) const;
+                         ObjId *roof = 0, const Item **blocker = 0) const;
 
 	//! Scan for a valid position for item in directions orthogonal to movedir
 	bool scanForValidPosition(int32 x, int32 y, int32 z, const Item *item,


Commit: 8f48861acc0b0ccab28cb70ffc5099ab0565210e
    https://github.com/scummvm/scummvm/commit/8f48861acc0b0ccab28cb70ffc5099ab0565210e
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T13:47:00+09:00

Commit Message:
ULTIMA8: Document WorldPoint struct

Changed paths:
    engines/ultima/ultima8/world/world_point.h


diff --git a/engines/ultima/ultima8/world/world_point.h b/engines/ultima/ultima8/world/world_point.h
index 1cf057046c..58d375b6e6 100644
--- a/engines/ultima/ultima8/world/world_point.h
+++ b/engines/ultima/ultima8/world/world_point.h
@@ -23,6 +23,10 @@
 #ifndef ULTIMA8_WORLD_WORLDPOINT_H
 #define ULTIMA8_WORLD_WORLDPOINT_H
 
+/**
+ * A 3D point in the format needed for passing back and forth to usecode.
+ * Not intended for general engine use, as it has a sort of clunky interface.
+ */
 struct WorldPoint {
 	uint8 _buf[5];
 


Commit: ada0315e8f8b8734f6670d7b92063c6e2ba85730
    https://github.com/scummvm/scummvm/commit/ada0315e8f8b8734f6670d7b92063c6e2ba85730
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T13:47:00+09:00

Commit Message:
ULTIMA8: Make pathfinder proc type visible

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


diff --git a/engines/ultima/ultima8/world/actors/pathfinder_process.cpp b/engines/ultima/ultima8/world/actors/pathfinder_process.cpp
index 6427295379..96e2876fac 100644
--- a/engines/ultima/ultima8/world/actors/pathfinder_process.cpp
+++ b/engines/ultima/ultima8/world/actors/pathfinder_process.cpp
@@ -33,6 +33,8 @@ namespace Ultima8 {
 static const unsigned int PATH_OK = 1;
 static const unsigned int PATH_FAILED = 0;
 
+const uint16 PathfinderProcess::PATHFINDER_PROC_TYPE = 0x204;
+
 // p_dynamic_cast stuff
 DEFINE_RUNTIME_CLASSTYPE_CODE(PathfinderProcess)
 
@@ -46,7 +48,7 @@ PathfinderProcess::PathfinderProcess(Actor *actor, ObjId item_, bool hit) :
 		_targetX(0), _targetY(0), _targetZ(0) {
 	assert(actor);
 	_itemNum = actor->getObjId();
-	_type = 0x0204; // CONSTANT !
+	_type = PATHFINDER_PROC_TYPE; // CONSTANT !
 
 	Item *item = getItem(item_);
 	if (!item) {
diff --git a/engines/ultima/ultima8/world/actors/pathfinder_process.h b/engines/ultima/ultima8/world/actors/pathfinder_process.h
index 729bd3560f..104c4a2d7a 100644
--- a/engines/ultima/ultima8/world/actors/pathfinder_process.h
+++ b/engines/ultima/ultima8/world/actors/pathfinder_process.h
@@ -57,6 +57,9 @@ protected:
 
 	Std::vector<PathfindingAction> _path;
 	unsigned int _currentStep;
+
+public:
+	static const uint16 PATHFINDER_PROC_TYPE;
 };
 
 } // End of namespace Ultima8


Commit: 82c651fcab2f2c96496921bf2fcef7024ac14b19
    https://github.com/scummvm/scummvm/commit/82c651fcab2f2c96496921bf2fcef7024ac14b19
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T13:47:00+09:00

Commit Message:
ULTIMA8: JANITORIAL: Whitespace.

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


diff --git a/engines/ultima/ultima8/ultima8.cpp b/engines/ultima/ultima8/ultima8.cpp
index 248642ad32..c009487807 100644
--- a/engines/ultima/ultima8/ultima8.cpp
+++ b/engines/ultima/ultima8/ultima8.cpp
@@ -1084,8 +1084,6 @@ void Ultima8Engine::setupCoreGumps() {
 		_gameMapGump->InitGump(0);
 	}
 
-
-
 	// TODO: clean this up
 	if (GAME_IS_U8) {
 		assert(_desktopGump->getObjId() == 256);


Commit: 8e72e3c7ef8df08182c24803586169638960e7a7
    https://github.com/scummvm/scummvm/commit/8e72e3c7ef8df08182c24803586169638960e7a7
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T15:25:06+09:00

Commit Message:
ULTIMA8: Add a simple 3d point struct

Changed paths:
  A engines/ultima/ultima8/misc/point3.h


diff --git a/engines/ultima/ultima8/misc/point3.h b/engines/ultima/ultima8/misc/point3.h
new file mode 100644
index 0000000000..e4560507dc
--- /dev/null
+++ b/engines/ultima/ultima8/misc/point3.h
@@ -0,0 +1,71 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA8_MISC_POINT3_H
+#define ULTIMA8_MISC_POINT3_H
+
+namespace Ultima {
+namespace Ultima8 {
+
+struct Point3 {
+	int32 x, y, z;
+
+	Point3() : x(0), y(0), z(0) {}
+	Point3(int32 nx, int32 ny, int32 nz) : x(nx), y(ny), z(nz) {}
+
+	int maxDistXYZ(const Point3 &other) const {
+		int xdiff = abs(x - other.x);
+		int ydiff = abs(y - other.y);
+		int zdiff = abs(z - other.z);
+		return MAX(xdiff, MAX(ydiff, zdiff));
+	}
+
+	void set(int32 nx, int32 ny, int32 nz) {
+		x = nx;
+		y = ny;
+		z = nz;
+	}
+
+	void move(int32 dx, int32 dy, int32 dz) {
+		x += dx;
+		y += dy;
+		z += dz;
+	}
+
+	bool loadData(Common::ReadStream *rs, uint32 version) {
+		x = rs->readSint32LE();
+		y = rs->readSint32LE();
+		z = rs->readSint32LE();
+		return true;
+	}
+
+	void saveData(Common::WriteStream *ws) {
+		ws->writeSint32LE(x);
+		ws->writeSint32LE(y);
+		ws->writeSint32LE(z);
+	}
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif


Commit: f021cfaff3c1cfd2185fe15641866dc0cd502e3f
    https://github.com/scummvm/scummvm/commit/f021cfaff3c1cfd2185fe15641866dc0cd502e3f
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-07-27T15:27:05+09:00

Commit Message:
ULTIMA8: Basic code for firing weapons in Crusader

Still needs a lot of work, but this is a first cut and works (with many bugs).

Changed paths:
  A engines/ultima/ultima8/world/super_sprite_process.cpp
  A engines/ultima/ultima8/world/super_sprite_process.h
    engines/ultima/module.mk
    engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
    engines/ultima/ultima8/misc/direction.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/main_actor.cpp
    engines/ultima/ultima8/world/actors/main_actor.h
    engines/ultima/ultima8/world/fire_type.cpp
    engines/ultima/ultima8/world/fire_type.h
    engines/ultima/ultima8/world/item.cpp
    engines/ultima/ultima8/world/item.h


diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index fa9af239a8..d61212c8d7 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -549,6 +549,7 @@ MODULE_OBJS := \
 	ultima8/world/snap_process.o \
 	ultima8/world/split_item_process.o \
 	ultima8/world/sprite_process.o \
+	ultima8/world/super_sprite_process.o \
 	ultima8/world/target_reticle_process.o \
 	ultima8/world/teleport_egg.o \
 	ultima8/world/world.o \
diff --git a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
index d7fce24aa6..c5a074d5c8 100644
--- a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
+++ b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
@@ -98,7 +98,7 @@ const char* const ConvertUsecodeCrusader::_intrinsics[] = {
 	"int16 World::I_getControlledNPCNum()",
 	"byte Actor::I_getDir(4 bytes)", // based on disasm. same coff as 112, 121
 	"int16 Actor::I_getLastAnimSet(4 bytes)", // based on disasm. part of same coff set 01D, 05A, 0B9, 0D7, 0E4, 124
-	"int16 Actor::I_maybeFire(Actor *, x, y, z, byte, int, byte)",
+	"int16 Item::I_fireWeapon(Item *, x, y, z, byte, int, byte)",
 	"byte Item::I_create(Item *, uint16 shapenum, uint16 framenum)", // probably - used in MISS1EGG referencing keycards and NPCDEATH in creating blood spills
 	// 0020
 	"void Item::I_popToCoords(Item *, uint16 x, uint16 y, uint16 z)", // set coords, used after creating blood spills in NPCDEATH
diff --git a/engines/ultima/ultima8/misc/direction.h b/engines/ultima/ultima8/misc/direction.h
index a81db522f6..39fa0af7ac 100644
--- a/engines/ultima/ultima8/misc/direction.h
+++ b/engines/ultima/ultima8/misc/direction.h
@@ -96,6 +96,44 @@ inline Direction Get_WorldDirection(int deltay, int deltax) {
 		return dydx >= -424 ? west : dydx >= -2472 ? southwest : south;
 }
 
+inline Direction Get_WorldDirectionClosestInRange(int deltay, int deltax, uint16 ndirs, uint16 mindir, uint16 maxdir) {
+	// TODO: Implement proper 16 directions here.
+	uint32 dir = static_cast<uint32>(Get_WorldDirection(deltay, deltax));
+	if (ndirs == 16) {
+		dir *= 2;
+	}
+
+	if ((dir < mindir) || (dir > maxdir)) {
+		int32 dmin1 = dir - mindir;
+		int32 dmin2 = mindir - dir;
+		if (dmin1 < 0) {
+			dmin1 = dmin1 + ndirs;
+		}
+		if (dmin2 < 0) {
+			dmin2 = dmin2 + ndirs;
+		}
+		int32 dist_to_min = MIN(dmin1, dmin2);
+
+		int dmax1 = dir - maxdir;
+		int dmax2 = maxdir - dir;
+		if (dmax1 < 0) {
+			dmax1 = dmax1 + ndirs;
+		}
+		if (dmax2 < 0) {
+			dmax2 = dmax2 + ndirs;
+		}
+		int32 dist_to_max = MIN(dmax1, dmax2);
+
+		if (dist_to_min < dist_to_max) {
+			return static_cast<Direction>(mindir);
+		} else {
+			return static_cast<Direction>(maxdir);
+		}
+	}
+
+	return static_cast<Direction>(dir);
+}
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index 8a3672032c..dd30f8ba8d 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -66,7 +66,7 @@ Intrinsic RemorseIntrinsics[] = {
 	World::I_getControlledNPCNum,  // int16 Intrinsic01B(void)
 	Actor::I_getDir, // byte Intrinsic01C(4 bytes)
 	Actor::I_getLastAnimSet, // int Intrinsic01D(4 bytes)
-	0, // int Intrinsic01E(16 bytes)
+	Item::I_fireWeapon, // int Intrinsic01E(16 bytes)
 	Item::I_create,
 	// 0x020
 	Item::I_popToCoords, // void Intrinsic020(10 bytes)
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index bd9e44f6a9..b3a6392c27 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -57,6 +57,7 @@
 #include "ultima/ultima8/world/get_object.h"
 #include "ultima/ultima8/world/item_factory.h"
 #include "ultima/ultima8/world/loop_script.h"
+#include "ultima/ultima8/world/fire_type.h"
 
 namespace Ultima {
 namespace Ultima8 {
@@ -156,16 +157,16 @@ bool Actor::loadMonsterStatsU8() {
 
 bool Actor::giveTreasure() {
 	MainShapeArchive *mainshapes = GameData::get_instance()->getMainShapes();
-	ShapeInfo *shapeinfo = getShapeInfo();
+	const ShapeInfo *shapeinfo = getShapeInfo();
 	MonsterInfo *mi = nullptr;
 	if (shapeinfo) mi = shapeinfo->_monsterInfo;
 	if (!mi)
 		return false;
 
-	Std::vector<TreasureInfo> &treasure = mi->_treasure;
+	const Std::vector<TreasureInfo> &treasure = mi->_treasure;
 
 	for (unsigned int i = 0; i < treasure.size(); ++i) {
-		TreasureInfo &ti = treasure[i];
+		const TreasureInfo &ti = treasure[i];
 		Item *item;
 
 		// check map
@@ -193,7 +194,7 @@ bool Actor::giveTreasure() {
 
 				// NB: this is rather biased towards weapons with low _shapes...
 				for (unsigned int s = 0; s < mainshapes->getCount(); ++s) {
-					ShapeInfo *si = mainshapes->getShapeInfo(s);
+					const ShapeInfo *si = mainshapes->getShapeInfo(s);
 					if (!si->_weaponInfo) continue;
 
 					int chance = si->_weaponInfo->_treasureChance;
@@ -319,7 +320,7 @@ bool Actor::giveTreasure() {
 
 		if (ti._shapes.size() == 1) {
 			uint32 shapeNum = ti._shapes[0];
-			ShapeInfo *si = mainshapes->getShapeInfo(shapeNum);
+			const ShapeInfo *si = mainshapes->getShapeInfo(shapeNum);
 			if (!si) {
 				perr << "Trying to create treasure with an invalid shapeNum ("
 				     << shapeNum << ")" << Std::endl;
@@ -727,7 +728,121 @@ void Actor::getHomePosition(int32 &x, int32 &y, int32 &z) const {
 	z = _homeZ;
 }
 
+
 void Actor::receiveHit(uint16 other, int dir, int damage, uint16 damage_type) {
+	if (GAME_IS_U8) {
+		receiveHitU8(other, dir, damage, damage_type);
+	} else {
+		receiveHitCru(other, dir, damage, damage_type);
+	}
+}
+
+void Actor::receiveHitCru(uint16 other, int dir, int damage, uint16 damage_type) {
+	//
+	// This is a big stack of constants and hard-coded things.
+	// It's like that in the original game.
+	//
+	Actor *attacker = getActor(other);
+	AudioProcess *audio = AudioProcess::get_instance();
+	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.
+		doAnim(static_cast<Animation::Sequence>(0x21), getDir());
+		doAnim(static_cast<Animation::Sequence>(0x20), getDir());
+		_hitPoints -= damage;
+		return;
+	}
+
+	if (isDead())
+		return;
+
+	if (shape != 1 && this != getControlledActor()) {
+		//Actor *controlled = getControlledActor();
+		if (!isInCombat()) {
+			setActivity(getDefaultActivity(2)); // get activity from field 0xA
+			if (!isInCombat()) {
+				setInCombat();
+				// TODO: attack the currently controlled NPC.
+			}
+		} else {
+			if (getCurrentActivityNo() == 8) {
+				setActivity(5);
+			}
+			// TODO: attack the currently controlled NPC.
+		}
+
+		// If the attacker is the controlled npc and this actor is not pathfinding
+		if (attacker && attacker == getControlledActor() &&
+			kernel->findProcess(_objId, PathfinderProcess::PATHFINDER_PROC_TYPE) != nullptr) {
+			int32 x, y, z;
+			int32 ox, oy, oz;
+			getLocation(x, y, z);
+			attacker->getLocation(ox, oy, oz);
+			int32 maxdiff = MAX(MAX(abs(x - ox), abs(y - oy)), abs(z - oz));
+			if (maxdiff < 641 && isOnScreen()) {
+				// TODO: implement the equivalent of this function.  For now, we always
+				// cancel pathfinding for the NPC.
+				// uint32 direction = static_cast<uint32>(Get_WorldDirection(y - oy, x - ox));
+				// int result = FUN_1128_1755(this, attacker, direction, 0, 0, 0);
+				// if (result) {
+					kernel->killProcesses(_objId, PathfinderProcess::PATHFINDER_PROC_TYPE, true);
+				// }
+			}
+		}
+	} else {
+		damage = receiveShieldHit(damage, damage_type);
+	}
+
+	if (hasActorFlags(ACT_IMMORTAL))
+		damage = 0;
+
+	if (damage > _hitPoints)
+		damage = _hitPoints;
+
+	setHP(static_cast<uint16>(_hitPoints - damage));
+
+	if (_hitPoints == 0) {
+		// Die!
+		die(damage_type);
+	} else if (damage) {
+		// Not dead yet.
+		if (!isRobotCru()) {
+			uint16 sfxno;
+			if (hasExtFlags(EXT_FEMALE)) {
+				sfxno = 0xd8; // female scream
+			} else {
+				sfxno = 0x8f; // male scream
+			}
+			if (audio && !audio->isSFXPlayingForObject(sfxno, other)) {
+				audio->playSFX(sfxno, 0x10, other, 1, false);
+			}
+		}
+		if (damage_type == 0xf || damage_type == 7) {
+			if (shape == 1) {
+				kernel->killProcesses(_objId, 0x204, true);
+				doAnim(static_cast<Animation::Sequence>(0x37), getDir());
+			} else if (shape == 0x4e6 || shape == 0x338 || shape == 0x385 || shape == 899) {
+				if (!(getRandom() % 3)) {
+					// Randomly stun the NPC for these damage types.
+					// CHECK ME: is this time accurate?
+					Process *attack = kernel->findProcess(_objId, 0x259);
+					uint stun = ((getRandom() % 10) + 8) * 60;
+					if (attack && stun) {
+						Process *delay = new DelayProcess(stun);
+						kernel->addProcess(delay);
+						attack->waitFor(delay);
+					}
+				}
+			}
+		}
+	}
+}
+
+void Actor::receiveHitU8(uint16 other, int dir, int damage, uint16 damage_type) {
 	if (isDead())
 		return; // already dead, so don't bother
 
@@ -885,7 +1000,7 @@ ProcId Actor::die(uint16 damageType) {
 
 	MainActor *avatar = getMainActor();
 	// if hostile to avatar
-	if (getEnemyAlignment() & avatar->getAlignment()) {
+	if (GAME_IS_U8 && (getEnemyAlignment() & avatar->getAlignment())) {
 		if (avatar->isInCombat()) {
 			// play victory fanfare
 			MusicProcess::get_instance()->playCombatMusic(109);
@@ -894,7 +1009,6 @@ ProcId Actor::die(uint16 damageType) {
 		}
 	}
 
-
 	destroyContents();
 	giveTreasure();
 
@@ -1799,8 +1913,7 @@ uint32 Actor::I_createActorCru(const uint8 *args, unsigned int /*argsize*/) {
 	if (!item || !other)
 		return 0;
 
-	// TODO: get game difficulty here.
-	static const int gameDifficulty = 1;
+	const int gameDifficulty = World::get_instance()->getGameDifficulty();
 	int npcDifficulty = (item->getMapNum() & 3) + 1;
 
 	if (gameDifficulty < npcDifficulty)
@@ -1851,10 +1964,10 @@ uint32 Actor::I_createActorCru(const uint8 *args, unsigned int /*argsize*/) {
 
 	uint16 wpntype = npcData->getWpnType();
 	Item *weapon = ItemFactory::createItem(wpntype, 0, 0, 0, 0, newactor->getMapNum(), 0, true);
-	/* TODO:
-	if (gameDifficulty == 4) {
-	   wpntype = randomlyGetHigherWeaponType(shape, wpntype);
-	}*/
+	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);
 	newactor->setCombatTactic(0);
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index f6e7ecf46c..9b366c5543 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -256,6 +256,15 @@ public:
 	bool loadData(Common::ReadStream *rs, uint32 version);
 	void saveData(Common::WriteStream *ws) override;
 
+	//! take a hit and optionally adjust it with the shields for this NPC.
+	virtual int receiveShieldHit(int damage, uint16 damage_type) {
+		return damage;
+	}
+
+	virtual uint8 getShieldType() const {
+		return 0;
+	}
+
 	ENABLE_RUNTIME_CLASSTYPE()
 
 	INTRINSIC(I_isNPC);
@@ -389,6 +398,8 @@ protected:
 	bool loadMonsterStatsU8();
 	bool loadMonsterStatsCru();
 
+	void receiveHitU8(uint16 other, int dir, int damage, uint16 type);
+	void receiveHitCru(uint16 other, int dir, int damage, uint16 type);
 };
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/actors/main_actor.cpp b/engines/ultima/ultima8/world/actors/main_actor.cpp
index 17425aa2b4..6b82a1ae4f 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.cpp
+++ b/engines/ultima/ultima8/world/actors/main_actor.cpp
@@ -45,6 +45,8 @@
 #include "ultima/ultima8/usecode/uc_list.h"
 #include "ultima/ultima8/usecode/uc_machine.h"
 #include "ultima/ultima8/world/loop_script.h"
+#include "ultima/ultima8/world/fire_type.h"
+#include "ultima/ultima8/world/sprite_process.h"
 #include "ultima/ultima8/world/actors/avatar_gravity_process.h"
 #include "ultima/ultima8/audio/music_process.h"
 
@@ -56,7 +58,7 @@ DEFINE_RUNTIME_CLASSTYPE_CODE(MainActor)
 
 MainActor::MainActor() : _justTeleported(false), _accumStr(0), _accumDex(0),
 	_accumInt(0), _cruBatteryType(ChemicalBattery), _keycards(0),
-	_activeWeapon(0), _activeInvItem(0) {
+	_activeWeapon(0), _activeInvItem(0), _shieldType(0), _shieldSpriteProc(0) {
 }
 
 MainActor::~MainActor() {
@@ -661,6 +663,8 @@ void MainActor::saveData(Common::WriteStream *ws) {
 		ws->writeUint32LE(_keycards);
 		ws->writeUint16LE(_activeWeapon);
 		ws->writeUint16LE(_activeInvItem);
+		ws->writeUint16LE(_shieldType);
+		ws->writeUint16LE(_shieldSpriteProc);
 	}
 
 	uint8 namelength = static_cast<uint8>(_name.size());
@@ -683,6 +687,8 @@ bool MainActor::loadData(Common::ReadStream *rs, uint32 version) {
 		_keycards = rs->readUint32LE();
 		_activeWeapon = rs->readUint16LE();
 		_activeInvItem = rs->readUint16LE();
+		_shieldType = rs->readUint16LE();
+		_shieldSpriteProc = rs->readUint16LE();
 	}
 
 	uint8 namelength = rs->readByte();
@@ -854,5 +860,71 @@ void MainActor::useInventoryItem(Item *item) {
 	}
 }
 
+int MainActor::receiveShieldHit(int damage, uint16 damage_type) {
+	uint8 shieldtype = getShieldType();
+	if (shieldtype == 3) {
+		shieldtype = 4;
+	}
+
+	const FireType *firetype = GameData::get_instance()->getFireType(damage_type);
+	int energy = getMana();
+	Kernel *kernel = Kernel::get_instance();
+
+	if (shieldtype && firetype && firetype->getShieldCost() && (firetype->getShieldMask() & shieldtype) && damage < energy) {
+		setMana(energy - damage);
+		damage = 0;
+		AudioProcess *audio = AudioProcess::get_instance();
+		audio->playSFX(0x48, 0x10, _objId, 1, true);
+
+		// If there's no active shield sprite, create a new one.
+		if (!_shieldSpriteProc || kernel->getProcess(_shieldSpriteProc) == nullptr) {
+			// Create the shield damage sprite
+			uint16 shieldsprite;
+			uint16 shieldstartframe;
+			uint16 shieldendframe;
+			bool remembersprite;
+			int32 x, y, z;
+
+			switch (shieldtype) {
+			case 1:
+				shieldsprite = 0x5a9;
+				shieldstartframe = 7;
+				shieldendframe = 0xd;
+				remembersprite = false;
+				// NOTE: In the game, this is put in the location of the
+				// hit.  For now just put in centre.
+				getCentre(x, y, z);
+				break;
+			case 2:
+				shieldsprite = 0x5a9;
+				shieldstartframe = 0;
+				shieldendframe = 6;
+				remembersprite = false;
+				getCentre(x, y, z);
+				break;
+			default:
+				shieldsprite = 0x52b;
+				shieldstartframe = 0;
+				shieldendframe = 8;
+				getLocation(x, y, z);
+				x += 0x10;
+				y += 0x18;
+				remembersprite = false;
+				break;
+			}
+			Process *p = new SpriteProcess(shieldsprite, shieldstartframe,
+										   shieldendframe, 1, 4, x, y, z);
+			kernel->addProcess(p);
+			if (remembersprite) {
+				_shieldSpriteProc = p->getPid();
+			} else {
+				_shieldSpriteProc = 0;
+			}
+		}
+	}
+	return damage;
+}
+
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/actors/main_actor.h b/engines/ultima/ultima8/world/actors/main_actor.h
index c4f6ac4070..e7915f05e7 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.h
+++ b/engines/ultima/ultima8/world/actors/main_actor.h
@@ -132,6 +132,9 @@ public:
 	//!< Swap to the next inventory item (in Crusader)
 	void nextInvItem();
 
+	//! Check if we can absorb a hit with the shield. Returns the modified damage value.
+	int receiveShieldHit(int damage, uint16 damage_type) override;
+
 	bool loadData(Common::ReadStream *rs, uint32 version);
 	void saveData(Common::WriteStream *ws) override;
 
@@ -170,6 +173,9 @@ protected:
 
 	Std::string _name;
 
+	uint16 _shieldSpriteProc;
+	uint16 _shieldType;
+
 };
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/fire_type.cpp b/engines/ultima/ultima8/world/fire_type.cpp
index 44622eb7cc..e18bcd74c6 100644
--- a/engines/ultima/ultima8/world/fire_type.cpp
+++ b/engines/ultima/ultima8/world/fire_type.cpp
@@ -24,7 +24,15 @@
 
 #include "ultima/ultima8/world/sprite_process.h"
 #include "ultima/ultima8/world/fire_type.h"
+#include "ultima/ultima8/world/item.h"
+#include "ultima/ultima8/world/current_map.h"
+#include "ultima/ultima8/world/loop_script.h"
+#include "ultima/ultima8/world/get_object.h"
+#include "ultima/ultima8/world/world.h"
+#include "ultima/ultima8/world/actors/actor.h"
+#include "ultima/ultima8/usecode/uc_list.h"
 #include "ultima/ultima8/kernel/kernel.h"
+#include "ultima/ultima8/misc/point3.h"
 #include "ultima/ultima8/audio/audio_process.h"
 
 namespace Ultima {
@@ -112,15 +120,18 @@ void FireType::makeBulletSplashShapeAndPlaySound(int32 x, int32 y, int32 z) cons
 	case 0x56b:
 		firstframe = (getRandom() % 3) * 6;
 		lastframe = firstframe + 5;
-			break;
+		break;
 	case 0x537:
 		lastframe = 10;
+		break;
 	case 0x578:
 		firstframe = (getRandom() % 3) * 6;
 		lastframe = firstframe + 4;
+		break;
 	case 0x59b:
 		firstframe = (getRandom() % 2) * 4;
 		lastframe = firstframe + 3;
+		break;
 	case 0x1d8: {
 		switch (getRandom() % 4) {
 			case 0:
@@ -152,5 +163,43 @@ void FireType::makeBulletSplashShapeAndPlaySound(int32 x, int32 y, int32 z) cons
 	}
 }
 
+void FireType::applySplashDamageAround(const Point3 &pt, int damage, const Item *exclude, const Item *src) const {
+	if (!getRange())
+		return;
+	static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
+
+	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
+
+	//
+	// Find items in range and apply splash damage
+	//
+	UCList uclist(2);
+	LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
+	currentmap->areaSearch(&uclist, script, sizeof(script), nullptr,
+						   getRange() * 16, true);
+	for (unsigned int i = 0; i < uclist.getSize(); ++i) {
+		Item *splashitem = getItem(uclist.getuint16(i));
+		assert(splashitem);
+		//
+		// Other items don't get splash damage from their own fire.. but the
+		// player does.  Life is not fair..
+		//
+		if (splashitem == exclude || (splashitem == src && src != getControlledActor()) ||
+			splashitem->getShape() == BULLET_SPLASH_SHAPE)
+			continue;
+		int splashitemdamage = damage;
+		if (_typeNo == 3 || _typeNo == 4 || _typeNo == 10) {
+			Point3 pt2;
+			splashitem->getLocation(pt2);
+			int splashrange = pt.maxDistXYZ(pt2);
+			splashrange = (splashrange / 16) / 3;
+			if (splashrange)
+				splashitemdamage /= splashrange;
+		}
+		int splashdir = src->getDirToItemCentre(pt);
+		splashitem->receiveHit(0, splashdir, splashitemdamage, _typeNo);
+	}
+}
+
 }
 }
diff --git a/engines/ultima/ultima8/world/fire_type.h b/engines/ultima/ultima8/world/fire_type.h
index b31d8a1d5e..f7cd30bba2 100644
--- a/engines/ultima/ultima8/world/fire_type.h
+++ b/engines/ultima/ultima8/world/fire_type.h
@@ -26,6 +26,9 @@
 namespace Ultima {
 namespace Ultima8 {
 
+class Item;
+class Point3;
+
 /**
  * A structure to hold data about the fire that comes from various weapons
  */
@@ -81,6 +84,8 @@ public:
 
 	uint16 getRandomDamage() const;
 
+	void applySplashDamageAround(const Point3 &pt, int damage, const Item *exclude, const Item *src) const;
+
 	void makeBulletSplashShapeAndPlaySound(int32 x, int32 y, int32 z) const;
 
 private:
diff --git a/engines/ultima/ultima8/world/item.cpp b/engines/ultima/ultima8/world/item.cpp
index 5e52cda7c4..8c8a29fe84 100644
--- a/engines/ultima/ultima8/world/item.cpp
+++ b/engines/ultima/ultima8/world/item.cpp
@@ -40,6 +40,7 @@
 #include "ultima/ultima8/graphics/shape_info.h"
 #include "ultima/ultima8/world/item_factory.h"
 #include "ultima/ultima8/world/current_map.h"
+#include "ultima/ultima8/world/fire_type.h"
 #include "ultima/ultima8/usecode/uc_stack.h"
 #include "ultima/ultima8/misc/direction.h"
 #include "ultima/ultima8/gumps/bark_gump.h"
@@ -59,10 +60,12 @@
 #include "ultima/ultima8/world/destroy_item_process.h"
 #include "ultima/ultima8/world/target_reticle_process.h"
 #include "ultima/ultima8/world/snap_process.h"
+#include "ultima/ultima8/world/super_sprite_process.h"
 #include "ultima/ultima8/audio/audio_process.h"
 #include "ultima/ultima8/games/game_info.h"
 #include "ultima/ultima8/world/actors/main_actor.h"
 #include "ultima/ultima8/world/missile_tracker.h"
+#include "ultima/ultima8/world/crosshair_process.h"
 
 namespace Ultima {
 namespace Ultima8 {
@@ -152,6 +155,10 @@ void Item::setLocation(int32 X, int32 Y, int32 Z) {
 	_z = Z;
 }
 
+void Item::move(const Point3 &pt) {
+	move(pt.x, pt.y, pt.z);
+}
+
 void Item::move(int32 X, int32 Y, int32 Z) {
 	bool no_lerping = false;
 	CurrentMap *map = World::get_instance()->getCurrentMap();
@@ -161,6 +168,9 @@ void Item::move(int32 X, int32 Y, int32 Z) {
 		perr.Print("Warning: moving avatar below Z=0. (%d,%d,%d)\n", X, Y, Z);
 	}
 
+	// TODO: In Crusader, if we are moving the avatar the game also checks whether
+	// there is an active "shield zap" sprite that needs moving at the same time.
+
 	// It's currently in the ethereal void, remove it from there
 	if (_flags & FLG_ETHEREAL) {
 
@@ -621,6 +631,14 @@ int Item::getDirToItemCentre(const Item &item2) const {
 	return Get_WorldDirection(i2y - yv, i2x - xv);
 }
 
+int Item::getDirToItemCentre(const Point3 &pt) const {
+	int32 xv, yv, zv;
+	getCentre(xv, yv, zv);
+
+	return Get_WorldDirection(pt.y - yv, pt.x - xv);
+}
+
+
 int Item::getRange(const Item &item2, bool checkz) const {
 	int32 thisX, thisY, thisZ;
 	int32 otherX, otherY, otherZ;
@@ -1134,6 +1152,144 @@ int32 Item::collideMove(int32 dx, int32 dy, int32 dz, bool teleport, bool force,
 	return 0;
 }
 
+uint16 Item::fireWeapon(int32 x, int32 y, int32 z, int dir, int firetype, char someflag) {
+	int32 ix, iy, iz;
+	getLocation(ix, iy, iz);
+
+	if (!GAME_IS_CRUSADER)
+		return 0;
+
+	static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
+
+	ix += x;
+	iy += y;
+	iz += z;
+
+	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
+	const FireType *firetypedat = GameData::get_instance()->getFireType(firetype);
+
+	if (!firetypedat)
+		return 0;
+
+	int damage = firetypedat->getRandomDamage();
+
+	const Item *blocker = nullptr;
+	bool isvalid = currentmap->isValidPosition(ix, iy, iz, BULLET_SPLASH_SHAPE, 0, nullptr, nullptr, &blocker);
+	if (!isvalid && blocker) {
+		Item *block = getItem(blocker->getObjId());
+		Point3 blockpt;
+		block->getLocation(blockpt);
+		int damagedir = Get_WorldDirection(blockpt.y - iy, blockpt.x - ix);
+		block->receiveHit(getObjId(), damagedir, damage, firetype);
+		int splashdamage = firetypedat->getRandomDamage();
+		firetypedat->applySplashDamageAround(blockpt, splashdamage, block, this);
+
+		firetypedat->makeBulletSplashShapeAndPlaySound(ix, iy, iz);
+		return 0;
+	} else {
+		int spriteframe = 0;
+		switch (firetype) {
+		case 3:
+		case 9:
+		case 10:
+			spriteframe = dir + 0x11;
+			break;
+		case 5:
+			spriteframe = dir + 1;
+			break;
+		case 6:
+			spriteframe = 0x46;
+			break;
+		case 0xe:
+			spriteframe = 0x47 + getRandom() % 5;
+			break;
+		case 0xf:
+			spriteframe = 0x4c;
+			break;
+		}
+
+		// HACK: this should be fixed to use inheritence so the behavior
+		// is clean for both Item and Actor.
+		const Actor *thisactor = dynamic_cast<Actor *>(this);
+		if (thisactor) {
+			// TODO: Get damage for active inventory item, and do something with
+			// animation here (lines 185~208 of disasm)
+		}
+
+		Item *target;
+		if (someflag) {
+			target = getControlledActor();
+		} else {
+			target = currentmap->findBestTargetItem(x, y, dir);
+		}
+
+		int32 tx, ty, tz;
+		if (target) {
+			int32 tsx, tsy, tsz;
+			target->getCentre(tx, ty, tz);
+			target->getFootpadData(tsx, tsy, tsz);
+
+			tz = target->getZ() + tsz * 8;
+
+			if (tsz < 3) {
+				if (tsz)
+					tz -= 8;
+			} else {
+				int32 targetz = tz;
+				int32 thisz = getZ();
+				tz -= 16;
+				if (thisz - targetz < -0x2f) {
+					tz += 8;
+				} else if (thisz - targetz > 0x2f) {
+					if (tsz == 6) {
+						tz -= 16;
+					} else if (tsz >= 7) {
+						tz -= 24;
+					}
+				}
+			}
+		} else {
+			tx = -1;
+		}
+
+		// TODO: check if we need the equivalent of FUN_1130_0299 here..
+		// maybe not? It seems to reset the target reticle etc which we
+		// handle differently.
+
+		int numshots = firetypedat->getNumShots();
+		for (int i = 0; i < numshots; i++) {
+			SuperSpriteProcess *ssp;
+			CrosshairProcess *chp = CrosshairProcess::get_instance();
+			assert(chp);
+			Item *crosshair = getItem(chp->getItemNum());
+			int32 ssx, ssy, ssz;
+			if (tx != -1) {
+				// Shoot toward the target
+				ssx = tx;
+				ssy = ty;
+				ssz = tz;
+			} else if (this == getControlledActor() && crosshair) {
+				// Shoot toward the crosshair
+				crosshair->getLocation(ssx, ssy, ssz);
+			} else {
+				// Just send the projectile off into the distance
+				// CHECKME: This is not how the game does it - it has different
+				// tables (at 1478:129b and 1478:12ac). Check the logic here.
+				ssx = ix + x_fact[dir] * 0x500;
+				ssy = iy + x_fact[dir] * 0x500;
+				ssz = iz;
+			}
+
+			ssp = new SuperSpriteProcess(BULLET_SPLASH_SHAPE, spriteframe,
+										 ix, iy, iz, ssx, ssy, ssz, firetype,
+										 damage, _objId, target->getObjId(), someflag);
+			Kernel::get_instance()->addProcess(ssp);
+		}
+	}
+
+	return 0;
+}
+
 unsigned int Item::countNearby(uint32 shape_, uint16 range) {
 	CurrentMap *currentmap = World::get_instance()->getCurrentMap();
 	UCList itemlist(2);
@@ -1169,12 +1325,6 @@ uint32 Item::callUsecodeEvent(uint32 event, const uint8 *args, int argsize) {
 	uint32 offset = u->get_class_event(class_id, event);
 	if (!offset) return 0; // event not found
 
-	/*if (GAME_IS_CRUSADER && event != 1) {
-		debug(6, "Crusader: not running event %d for item %d shape %d",
-					event, _objId, _shape);
-		return 0;
-	}*/
-
 	debug(10, "Item: %d (shape %d) calling usecode event %d @ %04X:%04X",
 			_objId, _shape, event, class_id, offset);
 
@@ -1841,7 +1991,6 @@ void Item::receiveHitCru(uint16 other, int dir, int damage, uint16 type) {
 	int yhurl = 10 + getRandom() % 15;
 
 	// nothing special, so just hurl the item
-	// TODO: hurl item in direction, with speed depending on damage
 	hurl(-xhurl * x_fact[dir], -yhurl * y_fact[dir], 16, 4); //!! constants
 }
 
@@ -2069,12 +2218,10 @@ int Item::scaleReceivedDamageCru(int damage, uint16 type) const {
 	uint8 difficulty = World::get_instance()->getGameDifficulty();
 	const Actor *actor = dynamic_cast<const Actor *>(this);
 	//
-	// TODO: this should check for current controlled NPC *or* avatar.
-	//
 	// For difficulty 1 and 2, we scale damage to others *up* and damage
 	// to avatar *down*.
 	//
-	if (!actor || getObjId() != 1) {
+	if (!actor || (this != getMainActor() && this != getControlledActor())) {
 		if (difficulty == 1) {
 			damage *= 5;
 		} else if (difficulty == 2) {
@@ -2455,7 +2602,7 @@ uint32 Item::I_getTypeFlag(const uint8 *args, unsigned int /*argsize*/) {
 	ARG_UINT16(typeflag);
 	if (!item) return 0;
 
-	ShapeInfo *info = item->getShapeInfo();
+	const ShapeInfo *info = item->getShapeInfo();
 
 	if (GAME_IS_U8 && typeflag >= 64)
 		perr << "Invalid TypeFlag greater than 63 requested (" << typeflag << ") by Usecode" << Std::endl;
@@ -3206,41 +3353,7 @@ uint32 Item::I_getClosestDirectionInRange(const uint8 *args, unsigned int /*args
 	ARG_UINT16(mindir);
 	ARG_UINT16(maxdir);
 
-	// TODO: Implement proper 16 directions here.
-	uint32 dir = static_cast<uint32>(Get_WorldDirection(y2 - y1, x2 - x1));
-	if (ndirs == 16) {
-		dir *= 2;
-	}
-
-	if ((dir < mindir) || (dir > maxdir)) {
-		int32 dmin1 = dir - mindir;
-		int32 dmin2 = mindir - dir;
-		if (dmin1 < 0) {
-			dmin1 = dmin1 + ndirs;
-		}
-		if (dmin2 < 0) {
-			dmin2 = dmin2 + ndirs;
-		}
-		int32 dist_to_min = MIN(dmin1, dmin2);
-
-		int dmax1 = dir - maxdir;
-		int dmax2 = maxdir - dir;
-		if (dmax1 < 0) {
-			dmax1 = dmax1 + ndirs;
-		}
-		if (dmax2 < 0) {
-			dmax2 = dmax2 + ndirs;
-		}
-		int32 dist_to_max = MIN(dmax1, dmax2);
-
-		if (dist_to_min < dist_to_max) {
-			return mindir;
-		} else {
-			return maxdir;
-		}
-	}
-
-	return dir;
+	return Get_WorldDirectionClosestInRange(y2 - y1, x2 - x1, ndirs, mindir, maxdir);
 }
 
 uint32 Item::I_hurl(const uint8 *args, unsigned int /*argsize*/) {
@@ -3497,6 +3610,19 @@ uint32 Item::I_isOnScreen(const uint8 *args, unsigned int /*argsize*/) {
 	return item->isOnScreen();
 }
 
+uint32 Item::I_fireWeapon(const uint8 *args, unsigned int /*argsize*/) {
+	ARG_ITEM_FROM_PTR(item);
+	ARG_SINT16(x);
+	ARG_SINT16(y);
+	ARG_SINT16(z);
+	ARG_UINT16(dir);
+	ARG_UINT16(firetype);
+	ARG_UINT16(unkflag);
+
+	if (!item) return 0;
+
+	return item->fireWeapon(x * 2, y * 2, z, dir, firetype, unkflag);
+}
 
 } // 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 c28c9d9862..58935aac91 100644
--- a/engines/ultima/ultima8/world/item.h
+++ b/engines/ultima/ultima8/world/item.h
@@ -28,6 +28,7 @@
 
 #include "ultima/ultima8/usecode/intrinsics.h"
 #include "ultima/ultima8/misc/box.h"
+#include "ultima/ultima8/misc/point3.h"
 
 namespace Ultima {
 namespace Ultima8 {
@@ -73,6 +74,9 @@ public:
 	//! CurrentMap and fastArea if necessary.
 	void move(int32 x, int32 y, int32 z);
 
+	//! Move, but with a point struct.
+	void move(const Point3 &pt);
+
 	//! Move an item. This moves an item to a container and  updates
 	//! CurrentMap and fastArea if necessary.
 	//! \param container The container this item should be placed in
@@ -96,6 +100,9 @@ public:
 	//! 'usable' coordinates if the Item is contained or equipped.
 	inline void getLocation(int32 &x, int32 &y, int32 &z) const;
 
+	//! Get the Item's location using a Point3 struct.
+	inline void getLocation(Point3 &pt) const;
+
 	//! Get this Item's Z coordinate.
 	int32 getZ() const;
 
@@ -287,6 +294,9 @@ public:
 	//! Undefined if either item is contained or equipped.
 	int getDirToItemCentre(const Item &item2) const;
 
+	//! Same as above, but from a fixed point.
+	int getDirToItemCentre(const Point3 &pt) const;
+
 	//! get 'distance' to other item. This is the maximum of the differences
 	//! between the x, y (and possibly z) coordinates of the items.
 	int getRange(const Item &item2, bool checkz = false) const;
@@ -375,6 +385,9 @@ public:
 	//! \param type The type of damage done. Zero for default
 	virtual void receiveHit(ObjId other, int dir, int damage, uint16 type);
 
+	//! fire the given weapon type in the given direction from location x, y, z.
+	uint16 fireWeapon(int32 x, int32 y, int32 z, int dir, int firetype, char someflag);
+
 	//! get damage points, used in Crusader for item damage.
 	uint8 getDamagePoints() const {
 		return _damagePoints;
@@ -573,6 +586,7 @@ public:
 	INTRINSIC(I_unequip);
 	INTRINSIC(I_avatarStoleSomething);
 	INTRINSIC(I_isOnScreen);
+	INTRINSIC(I_fireWeapon);
 
 private:
 	uint32 _shape;   // DO NOT modify this directly! Always use setShape()!
@@ -697,6 +711,12 @@ inline void Item::getLocation(int32 &X, int32 &Y, int32 &Z) const {
 	Z = _z;
 }
 
+inline void Item::getLocation(Point3 &pt) const {
+	pt.x = _x;
+	pt.y = _y;
+	pt.z = _z;
+}
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima8/world/super_sprite_process.cpp b/engines/ultima/ultima8/world/super_sprite_process.cpp
new file mode 100644
index 0000000000..7631a1ff76
--- /dev/null
+++ b/engines/ultima/ultima8/world/super_sprite_process.cpp
@@ -0,0 +1,433 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima8/misc/pent_include.h"
+#include "ultima/ultima8/world/super_sprite_process.h"
+
+#include "ultima/ultima8/games/game_data.h"
+#include "ultima/ultima8/graphics/shape.h"
+#include "ultima/ultima8/kernel/kernel.h"
+#include "ultima/ultima8/kernel/core_app.h"
+#include "ultima/ultima8/kernel/delay_process.h"
+#include "ultima/ultima8/misc/direction.h"
+#include "ultima/ultima8/usecode/uc_list.h"
+#include "ultima/ultima8/world/loop_script.h"
+#include "ultima/ultima8/world/current_map.h"
+#include "ultima/ultima8/world/fire_type.h"
+#include "ultima/ultima8/world/get_object.h"
+#include "ultima/ultima8/world/item_factory.h"
+#include "ultima/ultima8/world/item.h"
+#include "ultima/ultima8/world/world.h"
+#include "ultima/ultima8/world/actors/actor.h"
+#include "ultima/ultima8/world/sprite_process.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+// p_dynamic_class stuff
+DEFINE_RUNTIME_CLASSTYPE_CODE(SuperSpriteProcess)
+
+SuperSpriteProcess::SuperSpriteProcess() : Process(),
+	_shape(0), _frame(0), _fireType(0), _damage(0), _source(0),
+	_target(0), _counter(1), _item0x77(0), _spriteNo(0),
+	_startedAsFiretype9(false), _xstep(0), _ystep(0), _zstep(0),
+	_expired(false) {
+}
+
+SuperSpriteProcess::SuperSpriteProcess(int shape, int frame, int sx, int sy, int sz,
+									   int dx, int dy, int dz,
+									   uint16 firetype, uint16 damage, uint16 source,
+									   uint16 target, uint8 spread) :
+		_shape(shape), _frame(frame), _startpt(sx, sy, sz), _destpt(dx, dy, dz),
+		_nextpt(sx, sy, sz), _fireType(firetype), _damage(damage), _source(source),
+		_target(target), _counter(1), _item0x77(0), _spriteNo(0),
+		_startedAsFiretype9(firetype == 9), _xstep(0), _ystep(0), _zstep(0),
+		_expired(false) {
+
+	const FireType *firetypedat = GameData::get_instance()->getFireType(firetype);
+	assert(firetypedat);
+	if (firetypedat->getAccurate()) {
+		spread = 0;
+	}
+	if (spread) {
+		int rng = _startpt.maxDistXYZ(_destpt);
+		Item *srcitem = getItem(source);
+		if (srcitem == getControlledActor()) {
+			if (firetype == 2 || firetype == 13)
+				rng /= 8;
+			else if (firetype == 5)
+				rng /= 12;
+			else if (firetype == 10)
+				rng /= 5;
+			else
+				rng /= 10;
+		} else {
+			if (dynamic_cast<Actor *>(srcitem) != nullptr) {
+				rng /= 2;
+			} else {
+				// TODO: various other flags are checked in the game (around 1138:0bd1)
+				//  to make it either 5 or 8.  For now just use 5.
+				rng /= 5;
+			}
+		}
+
+		if (rng > 0x50)
+			rng = 0x50;
+		int xoff = -rng + (getRandom() % (rng * 2 + 1));
+		int yoff = -rng + (getRandom() % (rng * 2 + 1));
+		rng /= 3;
+		if (rng > 0x18)
+			rng = 0x18;
+		int zoff = -rng + (getRandom() % (rng * 2 + 1));
+
+		_destpt.move(xoff, yoff, zoff);
+		if (_destpt.z > 0xfa)
+			_destpt.z = 0xfa;
+		else if (_destpt.z < 0)
+			_destpt.z = 0;
+	}
+
+	float travel = _destpt.maxDistXYZ(_nextpt);
+	float speed = firetypedat->getCellsPerRound() * 32.0f;
+	float rounds = travel / speed;
+	if (rounds < 1)
+		rounds = 1;
+
+	_xstep = (_destpt.x - sx) / rounds;
+	_ystep = (_destpt.y - sy) / rounds;
+	_zstep = (_destpt.z - sz) / rounds;
+
+	if (_fireType == 2 || _fireType == 0xd) {
+		_destpt.x += travel / 5;
+		_destpt.y += travel / 5;
+		_destpt.z += travel / 5;
+	}
+
+}
+
+SuperSpriteProcess::~SuperSpriteProcess(void) {
+	Item *item = getItem(_itemNum);
+	if (item) item->destroy();
+}
+
+void SuperSpriteProcess::move(int x, int y, int z) {
+	_nowpt.set(x, y, z);
+
+	Item *item = getItem(_itemNum);
+	if (item)
+		item->move(_nowpt);
+}
+
+void SuperSpriteProcess::run() {
+	CurrentMap *map = World::get_instance()->getCurrentMap();
+	int mapChunkSize = map->getChunkSize();
+	const FireType *firetypedat = GameData::get_instance()->getFireType(_fireType);
+
+	if (!firetypedat || !map->isChunkFast(_nextpt.x / mapChunkSize, _nextpt.y / mapChunkSize)) {
+		destroyItemOrTerminate();
+		return;
+	}
+
+	_nowpt = _nextpt;
+
+	Point3 newpt(_startpt);
+	if (!_startedAsFiretype9) {
+		newpt.x += _counter * _xstep;
+		newpt.y += _counter * _ystep;
+		newpt.z += _counter * _zstep;
+	} else {
+		int targetz = 0;
+		if (_counter > firetypedat->getRoundDuration()) {
+			if (!_expired) {
+				if (_target == 0) {
+					targetz = _nowpt.z;
+				} else {
+					Item *target = getItem(_target);
+					if (target) {
+						int32 tx, ty, tz;
+						int32 cx, cy, cz;
+						target->getLocation(tx, ty, tz);
+						target->getCentre(cx, cy, cz);
+						targetz = cz + 8;
+					}
+				}
+
+				// TODO: Apply point adjustments for firetype 9 here
+				// (lines 134~214 of disasm)
+			}
+		} else {
+			_expired = true;
+		}
+
+		if (!_expired) {
+			if (_nowpt.z < targetz) {
+				_zstep++;
+			} else if (targetz < _nowpt.z) {
+				_zstep--;
+			}
+		} else {
+			_zstep--;
+		}
+
+		_xstep = CLIP(_xstep, -32.0f, 32.0f);
+		_ystep = CLIP(_ystep, -32.0f, 32.0f);
+		_zstep = CLIP(_zstep, -16.0f, 16.0f);
+
+		newpt.x += _counter * _xstep;
+		newpt.y += _counter * _ystep;
+		newpt.z += _counter * _zstep;
+
+		if (_fireType == 9 && !_expired) {
+			if (_nowpt.x != newpt.x || _nowpt.y != newpt.y) {
+				uint8 dir = Get_WorldDirection(_nowpt.y - newpt.y, _nowpt.x - newpt.x);
+				Item *item;
+				if (_itemNum == 0) {
+					item = getItem(_spriteNo);
+				} else {
+					item = getItem(_itemNum);
+				}
+				if (item) {
+					item->setFrame(dir + 0x11);
+				}
+			}
+		}
+	}
+	_pt3 = newpt;
+
+	_counter++;
+
+	if (!_itemNum && _counter > 1) {
+		Item *sprite = ItemFactory::createItem(_shape, _frame, 0, Item::FLG_DISPOSABLE,
+			0, 0, Item::EXT_SPRITE, true);
+		_spriteNo = sprite->getObjId();
+		sprite->move(_nowpt);
+	}
+
+	if (_pt3.z != 0 && _pt3.z != 0xfa) {
+		int32 duration = firetypedat->getRoundDuration();
+
+		if (_counter >= duration) {
+			// disasm ~line 311
+			if (!map->isChunkFast(_nowpt.x / mapChunkSize, _nowpt.y / mapChunkSize)) {
+				destroyItemOrTerminate();
+				return;
+			}
+			if (areaSearch()) {
+				advanceFrame();
+				Process *delay = new DelayProcess(_fireType == 9 ? 3 : 2);
+				ProcId delayid = Kernel::get_instance()->addProcess(delay);
+				waitFor(delayid);
+				return;
+			}
+			if (_item0x77 && _fireType == 5) {
+				Item *item = getItem(_item0x77);
+				assert(item);
+				const ShapeInfo *info = item->getShapeInfo();
+				if (info->is_roof() && _fireType == 5) {
+					makeBulletSplash(_pt3);
+					// Finish disasm lines 347~381 here.
+					warning("TODO: SuperSpriteProcess::run: Implement special case for firetype 5 hitting a roof");
+					terminate();
+					return;
+				}
+			}
+		}
+	}
+	if (_source && (_item0x77 == _source && _counter < 5)) {
+		Item *source = getItem(_source);
+		assert(source);
+		// CHECKME: This appears to be a hack in the original game
+		// to avoid hitting the source item.. for now reproduce it.
+		source->moveToEtherealVoid();
+		run();
+		source->returnFromEtherealVoid();
+	}
+	hitAndFinish();
+}
+
+void SuperSpriteProcess::makeBulletSplash(const Point3 &pt) {
+	const FireType *firetypedat = GameData::get_instance()->getFireType(_fireType);
+
+	if (firetypedat->getRange()) {
+		Item *item = getItem(_item0x77);
+		Item *src = getItem(_source);
+		firetypedat->applySplashDamageAround(pt, _damage, item, src);
+	}
+	firetypedat->makeBulletSplashShapeAndPlaySound(pt.x, pt.y, pt.z);
+}
+
+void SuperSpriteProcess::hitAndFinish() {
+	Point3 pt(_nowpt);
+	//int dist = _nowpt.maxDistXYZ(_pt3);
+
+	CurrentMap *map = World::get_instance()->getCurrentMap();
+	Std::list<CurrentMap::SweepItem> hits;
+	int32 start[3] = {_nowpt.x, _nowpt.y, _nowpt.z};
+	int32 end[3] = {_pt3.x, _pt3.y, _pt3.z};
+	int32 dims[3] = {1, 1, 1};
+	bool collision = map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
+									_source, true, &hits);
+
+	if (collision && hits.size()) {
+		_item0x77 = hits.front()._item;
+	}
+
+	Item *item = getItem(_item0x77);
+	if (item) {
+		int32 ifx, ify, ifz;
+		item->getFootpadData(ifx, ify, ifz);
+		int32 ix, iy, iz;
+		item->getLocation(ix, iy, iz);
+
+		if (ifx > 2 && ify > 2 && ifz > 2) {
+			int32 ixsize = (ifx - 2) * 16;
+			int32 iysize = (ifx - 2) * 16;
+			if (pt.x < ix - ixsize)
+				pt.x = ix - ixsize;
+			if (pt.y < iy - iysize)
+				pt.y = iy - iysize;
+		}
+
+		//Actor *actor = dynamic_cast<Actor *>(item);
+		// CHECKME: This is not exactly the same as the game, but
+		// it should work? See disasm 1138:1384, lines 142 ~ 172
+		// There is some random factor added for non-actor items
+		// which needs checking
+		int dir = Get_WorldDirection(iy - _nowpt.y, ix - _nowpt.x);
+		item->receiveHit(_itemNum, dir, _damage, _fireType);
+	}
+	makeBulletSplash(pt);
+	destroyItemOrTerminate();
+}
+
+void SuperSpriteProcess::destroyItemOrTerminate() {
+	if (_itemNum) {
+		Item *item = getItem(_itemNum);
+		if (item)
+			item->destroy();
+		_itemNum = 0;
+	} else {
+		terminate();
+	}
+}
+
+// Implementation of fn at 1138:0f9e
+void SuperSpriteProcess::advanceFrame() {
+	_nextpt = _pt3;
+
+	if (_itemNum) {
+		warning("TODO: SuperSpriteProcess::advanceFrame: Implement area search 10e0_123a");
+		// TODO: do an area search for something here.
+		// AreaSearch_10e0_123a
+	}
+
+	if (_spriteNo) {
+		Item *sprite = getItem(_spriteNo);
+		assert(sprite);
+		sprite->move(_nextpt);
+		uint32 frame = sprite->getFrame();
+		frame++;
+		if (frame > 0x4b)
+			frame = 0x47;
+		sprite->setFrame(frame);
+	}
+
+	if (_fireType == 3) {
+		if (_pt5.x != -1) {
+			// Create a little sparkly thing
+			Process *p = new SpriteProcess(0x426, 0, 9, 0, 3, _pt5.x, _pt5.y, _pt5.z);
+			Kernel::get_instance()->addProcess(p);
+		}
+		_pt5 = _nextpt;
+	}
+}
+
+bool SuperSpriteProcess::areaSearch() {
+	// int x1, int y1, int z1, int x2, int y2, int z2
+	CurrentMap *map = World::get_instance()->getCurrentMap();
+
+	warning("TODO: SuperSpriteProcess::areaSearch: Implement area search 1138_0ee8");
+	// TODO: Implement SuperSprite_AreaSearch_1138_0ee8
+	uint16 lsrange = _pt3.maxDistXYZ(_nextpt);
+	UCList uclist(2);
+	LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
+	map->areaSearch(&uclist, script, sizeof(script), nullptr,
+					lsrange, true, _nextpt.x, _nextpt.y);
+	for (unsigned int i = 0; i < uclist.getSize(); ++i) {
+		Item *searchitem = getItem(uclist.getuint16(i));
+		assert(searchitem);
+	}
+
+	return false;
+}
+
+
+void SuperSpriteProcess::saveData(Common::WriteStream *ws) {
+	Process::saveData(ws);
+
+	ws->writeUint32LE(static_cast<uint32>(_shape));
+	ws->writeUint32LE(_frame);
+	_nowpt.saveData(ws);
+	_nextpt.saveData(ws);
+	_pt3.saveData(ws);
+	_startpt.saveData(ws);
+	_pt5.saveData(ws);
+	_destpt.saveData(ws);
+	ws->writeUint16LE(_fireType);
+	ws->writeUint16LE(_damage);
+	ws->writeUint16LE(_source);
+	ws->writeUint16LE(_target);
+	ws->writeUint16LE(_item0x77);
+	ws->writeUint16LE(_spriteNo);
+	ws->writeFloatLE(_xstep);
+	ws->writeFloatLE(_ystep);
+	ws->writeFloatLE(_zstep);
+
+	// TODO: Update save and load functions once this is finished.
+}
+
+bool SuperSpriteProcess::loadData(Common::ReadStream *rs, uint32 version) {
+	if (!Process::loadData(rs, version)) return false;
+
+	_shape = static_cast<int>(rs->readUint32LE());
+	_frame = rs->readUint16LE();
+	_nowpt.loadData(rs, version);
+	_nextpt.loadData(rs, version);
+	_pt3.loadData(rs, version);
+	_startpt.loadData(rs, version);
+	_pt5.loadData(rs, version);
+	_destpt.loadData(rs, version);
+	_fireType = rs->readUint16LE();
+	_damage = rs->readUint16LE();
+	_source = rs->readUint16LE();
+	_target = rs->readUint16LE();
+	_item0x77 = rs->readUint16LE();
+	_spriteNo = rs->readUint16LE();
+	_xstep = rs->readFloatLE();
+	_ystep = rs->readFloatLE();
+	_zstep = rs->readFloatLE();
+
+	return true;
+}
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/super_sprite_process.h b/engines/ultima/ultima8/world/super_sprite_process.h
new file mode 100644
index 0000000000..a59455e2f4
--- /dev/null
+++ b/engines/ultima/ultima8/world/super_sprite_process.h
@@ -0,0 +1,104 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA8_WORLD_SUPERSPRITEPROCESS_H
+#define ULTIMA8_WORLD_SUPERSPRITEPROCESS_H
+
+#include "ultima/ultima8/kernel/process.h"
+#include "ultima/ultima8/usecode/intrinsics.h"
+#include "ultima/ultima8/misc/point3.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+//! Creates a damaging sprite like a rocket or something, for Crusader
+class SuperSpriteProcess : public Process {
+private:
+	int _shape;
+	struct Point3 _nowpt; 	//!< The current x,y,z position (1st pt in struct)
+	struct Point3 _nextpt; 	//!< The next x,y,z position (2nd pt in struct)
+	struct Point3 _pt3; 	//!< The ?? x,y,z position (3rd pt in struct)
+	struct Point3 _startpt;	//!< The start x,y,z position (4th pt in struct)
+	struct Point3 _pt5; 	//!< The ?? x,y,z position (5th pt in struct)
+	struct Point3 _destpt;	//!< The destination x,y,z position
+
+	uint16 _frame;
+	uint16 _fireType;
+	uint16 _damage;
+	uint16 _source;
+	uint16 _target;
+	int32 _counter;
+
+	uint16 _item0x77; //!< TODO: what is this?
+	uint16 _spriteNo;
+
+	float _xstep;
+	float _ystep;
+	float _zstep;
+
+	bool _startedAsFiretype9;
+	bool _expired;
+
+public:
+	// p_dynamic_class stuff
+	ENABLE_RUNTIME_CLASSTYPE()
+
+	SuperSpriteProcess();
+
+	//! SuperSpriteProcess Constructor
+	//! \param shape The shape to use
+	//! \param frame The initial/first frame of the sprite animation
+	//! \param sx Start X coord of the sprite in the world
+	//! \param sy Start Y coord of the sprite in the world
+	//! \param sz Start Z coord of the sprite in the world
+	//! \param dx Dest X coord of the sprite in the world
+	//! \param dy Dest Y coord of the sprite in the world
+	//! \param dz Dest Z coord of the sprite in the world
+	//!
+	SuperSpriteProcess(int shape, int frame, int sx, int sy, int sz,
+					   int dx, int dy, int dz, uint16 firetype,
+					   uint16 damage, uint16 source, uint16 target, uint8 flag);
+
+	//! The SuperSpriteProcess destructor
+	~SuperSpriteProcess(void) override;
+
+	//! The SpriteProcess run function
+	void run() override;
+
+	//! Move the sprite to a new location
+	void move(int x, int y, int z);
+
+	bool loadData(Common::ReadStream *rs, uint32 version);
+	void saveData(Common::WriteStream *ws) override;
+
+protected:
+	bool areaSearch();
+	void advanceFrame();
+	void hitAndFinish();
+	void makeBulletSplash(const Point3 &pt);
+	void destroyItemOrTerminate();
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif




More information about the Scummvm-git-logs mailing list