Files
2026-02-02 04:50:13 +01:00

4070 lines
104 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/usecode/usecode.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/usecode/uc_machine.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/kernel/object_manager.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/gfx/main_shape_archive.h"
#include "ultima/ultima8/gfx/gump_shape_archive.h"
#include "ultima/ultima8/gfx/shape.h"
#include "ultima/ultima8/gfx/shape_frame.h"
#include "ultima/ultima8/world/item_factory.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/coord_utils.h"
#include "ultima/ultima8/world/fire_type.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/gumps/bark_gump.h"
#include "ultima/ultima8/gumps/ask_gump.h"
#include "ultima/ultima8/world/actors/actor_bark_notify_process.h"
#include "ultima/ultima8/gumps/paperdoll_gump.h"
#include "ultima/ultima8/gumps/game_map_gump.h"
#include "ultima/ultima8/world/world_point.h"
#include "ultima/ultima8/world/gravity_process.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/camera_process.h"
#include "ultima/ultima8/world/sprite_process.h"
#include "ultima/ultima8/gumps/slider_gump.h"
#include "ultima/ultima8/usecode/uc_process.h"
#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/world/actors/main_actor.h"
#include "ultima/ultima8/world/actors/attack_process.h"
#include "ultima/ultima8/world/missile_tracker.h"
#include "ultima/ultima8/world/crosshair_process.h"
#include "ultima/ultima8/world/actors/anim_action.h"
namespace Ultima {
namespace Ultima8 {
// Shape for camera snap eggs. Same in Remorse and Regret.
static const uint32 SNAP_EGG_SHAPE = 0x4fe;
// Shape for small spark where a bullet hits. Same in Remorse and Regret.
static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
DEFINE_RUNTIME_CLASSTYPE_CODE(Item)
Item::Item()
: _shape(0), _frame(0), _x(0), _y(0), _z(0),
_flags(0), _quality(0), _npcNum(0), _mapNum(0),
_extendedFlags(0), _parent(0),
_cachedShape(nullptr), _cachedShapeInfo(nullptr),
_gump(0), _bark(0), _gravityPid(0), _lastSetup(0),
_ix(0), _iy(0), _iz(0), _damagePoints(1) {
}
Item::~Item() {
}
Common::String Item::dumpInfo() const {
Common::String info = Common::String::format("Item %u (class %s, shape %u)", getObjId(), GetClassType()._className, getShape());
const char *ucname = GameData::get_instance()->getMainUsecode()->get_class_name(_shape);
if (ucname != nullptr) {
info += Common::String::format(" (uc: %s)", ucname);
}
info += Common::String::format(", %u, (", getFrame());
if (_parent) {
int32 gx, gy;
getGumpLocation(gx, gy);
info += Common::String::format("%d, %d", gx, gy);
} else {
info += Common::String::format("%d, %d, %d", _x, _y, _z);
}
info += Common::String::format(") q: %u, m: %u, n: %u, f: 0x%x, ef: 0x%x",
getQuality(), getMapNum() , getNpcNum(), getFlags(), getExtFlags());
const ShapeInfo *si = getShapeInfo();
if (si) {
info += Common::String::format(" shapeinfo f: %x, fam: %x, et: %x",
si->_flags, si->_family, si->_equipType);
}
info += ")";
return info;
}
Container *Item::getParentAsContainer() const {
// No parent, no container
if (!_parent || getObject(_parent) == nullptr)
return nullptr;
Container *p = getContainer(_parent);
if (!p) {
warning("Item %u _parent (%u) is an invalid Container ObjID", getObjId(), _parent);
}
return p;
}
Container* Item::getRootContainer() const {
Container *root = nullptr;
Container *parent = getParentAsContainer();
while (parent) {
root = parent;
parent = parent->getParentAsContainer();
}
return root;
}
const Item *Item::getTopItem() const {
Container *root = getRootContainer();
return root ? root : this;
}
void Item::setLocation(int32 X, int32 Y, int32 Z) {
_x = X;
_y = Y;
_z = Z;
}
void Item::setLocation(const Point3 &pt) {
_x = pt.x;
_y = pt.y;
_z = pt.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();
int mapChunkSize = map->getChunkSize();
if (getObjId() == kMainActorId && Z < 0) {
warning("Moving main actor below Z=0. (%d,%d,%d)", 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) {
// Remove us from the ethereal void
World::get_instance()->etherealRemove(_objId);
}
// Remove from container (if contained or equipped)
if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
if (_parent) {
// If we are flagged as Ethereal, we are already removed
if (!(_flags & FLG_ETHEREAL)) {
Container *p = getParentAsContainer();
if (p) p->removeItem(this);
}
} else
warning("Item %u FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent", getObjId());
// Clear our owner.
_parent = 0;
// No lerping when going from a container to somewhere else
no_lerping = true;
}
// Item needs to be removed if in the map, and it is moving to a
// different chunk
else if ((_extendedFlags & EXT_INCURMAP) &&
((_x / mapChunkSize != X / mapChunkSize) ||
(_y / mapChunkSize != Y / mapChunkSize))) {
// Remove us from the map
map->removeItem(this);
}
// Unset all the various _flags that no longer apply
_flags &= ~(FLG_CONTAINED | FLG_EQUIPPED | FLG_ETHEREAL);
// Set the location
_x = X;
_y = Y;
_z = Z;
// Add it back to the map if needed
if (!(_extendedFlags & EXT_INCURMAP)) {
// Disposable fast only items get put at the end
// While normal items get put at start
if (_flags & (FLG_DISPOSABLE | FLG_FAST_ONLY))
map->addItemToEnd(this);
else
map->addItem(this);
}
// Call just moved
callUsecodeEvent_justMoved();
// Are we moving somewhere fast
bool dest_fast = map->isChunkFast(X / mapChunkSize, Y / mapChunkSize);
// No lerping for this move
if (no_lerping) _extendedFlags |= EXT_LERP_NOPREV;
//
// If the destination is not in the fast area, and we are in
// the fast area, we need to call leaveFastArea, as long as we aren't
// being followed by the camera. We also disable lerping in such a case.
//
// In Crusader, the camera does not directly track the avatar (instead
// it uses SnapProcess), so manually move the camera here when moving
// the controlled actor.
//
if (!dest_fast && (_flags & Item::FLG_FASTAREA)) {
_extendedFlags |= EXT_LERP_NOPREV;
if (_extendedFlags & EXT_CAMERA)
CameraProcess::GetCameraProcess()->itemMoved();
else if (GAME_IS_CRUSADER && this == getControlledActor() && (X || Y))
CameraProcess::GetCameraProcess()->moveToLocation(X, Y, Z);
else
leaveFastArea();
return; //we are done
}
// Or if the dest is fast, and we are not, we need to call enterFastArea
else if (dest_fast && !(_flags & Item::FLG_FASTAREA)) {
_extendedFlags |= EXT_LERP_NOPREV;
enterFastArea();
}
// If we are being followed, notify the camera that we moved
// Note that we don't need to
if (_extendedFlags & EXT_CAMERA)
CameraProcess::GetCameraProcess()->itemMoved();
if (_extendedFlags & EXT_TARGET)
TargetReticleProcess::get_instance()->itemMoved(this);
}
bool Item::moveToContainer(Container *container, bool checkwghtvol) {
// Null container, report an error message
if (!container) {
warning("NULL container passed to Item::moveToContainer");
return false;
}
// Already there, do nothing, but only if not ethereal
bool ethereal_same = false;
if (container->getObjId() == _parent) {
// If we are ethereal we'd like to know if we are just being moved back
if (_flags & FLG_ETHEREAL) ethereal_same = true;
else return true;
}
// Eh, can't do it
if (!container->CanAddItem(this, checkwghtvol)) return false;
// It's currently in the ethereal void
if (_flags & FLG_ETHEREAL) {
// Remove us from the ethereal void
World::get_instance()->etherealRemove(_objId);
}
// Remove from container (if contained or equipped)
if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
if (_parent) {
// If we are flagged as Ethereal, we are already removed
if (!(_flags & FLG_ETHEREAL)) {
Container *p = getParentAsContainer();
if (p) p->removeItem(this);
}
} else
warning("Item %u FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent", getObjId());
// Clear our owner.
_parent = 0;
}
// Item needs to be removed if it in the map
else if (_extendedFlags & EXT_INCURMAP) {
// Remove us from the map
World::get_instance()->getCurrentMap()->removeItem(this);
}
// Unset all the various _flags that no longer apply
_flags &= ~(FLG_CONTAINED | FLG_EQUIPPED | FLG_ETHEREAL);
// Set location to 0,0,0 if we aren't an ethereal item moving back
if (!ethereal_same) _x = _y = 0;
_z = 0;
// Add the item, don't bother with checking weight or vol since we already
// know if it will fit or not
container->addItem(this, false);
// Set our owner.
_parent = container->getObjId();
// Set us contained
_flags |= FLG_CONTAINED;
// If moving to avatar, mark as OWNED
Container *root = getRootContainer();
if (root && root->getObjId() == kMainActorId)
setFlagRecursively(FLG_OWNED);
// No lerping when moving to a container
_extendedFlags |= EXT_LERP_NOPREV;
// Call just moved
callUsecodeEvent_justMoved();
// For a container, it's fast if it's got a _gump open
bool dest_fast = (container->_flags & FLG_GUMP_OPEN) != 0;
// If the destination is not in the fast area, and we are in
// the fast area, we need to call leaveFastArea
if (!dest_fast && (_flags & Item::FLG_FASTAREA))
leaveFastArea();
// Or if the dest is fast, and we are not, we need to call enterFastArea
else if (dest_fast && !(_flags & Item::FLG_FASTAREA))
enterFastArea();
// Done
return true;
}
void Item::moveToEtherealVoid() {
// It's already Ethereal
if (_flags & FLG_ETHEREAL) return;
// Add it to the ethereal void
World::get_instance()->etherealPush(_objId);
// It's owned by something removed it from the something, but keep _flags
if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
if (_parent) {
Container *p = getParentAsContainer();
if (p) p->removeItem(this);
} else
warning("Item %u FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent", getObjId());
} else if (_extendedFlags & EXT_INCURMAP) {
World::get_instance()->getCurrentMap()->removeItem(this);
}
// Set the ETHEREAL Flag
_flags |= FLG_ETHEREAL;
}
void Item::returnFromEtherealVoid() {
// It's not Ethereal
if (!(_flags & FLG_ETHEREAL)) return;
// Ok, we can do 2 things here
// If an item has the contained or Equipped _flags set, we return it to it's owner
if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
Container *p = getParentAsContainer();
if (!p) {
warning("Item %u FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent", getObjId());
}
moveToContainer(p);
}
// or we return it to the world
else {
move(_x, _y, _z);
}
}
void Item::movedByPlayer() {
// owned-by-avatar items can't be stolen
if (_flags & FLG_OWNED) return;
// Otherwise, player is stealing.
// See if anybody is around to notice.
Item *avatar = getItem(1);
UCList itemlist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE);
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
currentmap->areaSearch(&itemlist, script, sizeof(script), avatar, 640, 0);
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
Actor *actor = getActor(itemlist.getuint16(i));
if (actor && !actor->isDead())
actor->callUsecodeEvent_AvatarStoleSomething(getObjId());
}
}
int32 Item::getZ() const {
return _z;
}
Point3 Item::getLocationAbsolute() const {
if (_parent) {
Item *p = getParentAsContainer();
if (p) {
return p->getLocationAbsolute();
}
}
return Point3(_x, _y, _z);
}
void Item::getGumpLocation(int32 &X, int32 &Y) const {
if (!_parent) return;
X = _y & 0xFF;
Y = (_y >> 8) & 0xFF;
}
void Item::setGumpLocation(int32 X, int32 Y) {
if (!_parent) return;
_y = (X & 0xFF) + ((Y & 0xFF) << 8);
}
void Item::randomGumpLocation() {
if (!_parent) return;
// This sets the coordinates to (255,255) and lets the ContainerGump
// randomize the position when it is next opened.
_y = 0xFFFF;
}
Point3 Item::getCentre() const {
// constants!
Point3 pt(_x, _y, _z);
const ShapeInfo *shapeinfo = getShapeInfo();
if (_flags & FLG_FLIPPED) {
pt.x -= shapeinfo->_y * 16;
pt.y -= shapeinfo->_x * 16;
} else {
pt.x -= shapeinfo->_x * 16;
pt.y -= shapeinfo->_y * 16;
}
pt.y += shapeinfo->_z * 4;
return pt;
}
Box Item::getWorldBox() const {
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
return Box(_x, _y, _z, xd, yd, zd);
}
void Item::setShape(uint32 shape) {
_cachedShape = nullptr;
if (GAME_IS_CRUSADER && _shape && shape != _shape) {
// In Crusader, here we need to check if the shape
// changed from targetable to not-targetable, or vice-versa
const ShapeInfo *oldinfo = getShapeInfo();
_shape = shape;
_cachedShapeInfo = nullptr;
const ShapeInfo *newinfo = getShapeInfo();
if (!hasFlags(FLG_BROKEN) && oldinfo && newinfo) {
if (oldinfo->is_targetable() && !newinfo->is_targetable()) {
World::get_instance()->getCurrentMap()->removeTargetItem(this);
}
else if (!oldinfo->is_targetable() && newinfo->is_targetable()) {
World::get_instance()->getCurrentMap()->addTargetItem(this);
}
}
} else {
_shape = shape;
_cachedShapeInfo = nullptr;
}
}
bool Item::overlaps(const Item &item2) const {
int32 x1a, y1a, z1b;
int32 x2a, y2a, z2b;
Point3 pt1 = getLocation();
Point3 pt2 = item2.getLocation();
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
x1a = pt1.x - xd;
y1a = pt1.y - yd;
z1b = pt1.z + zd;
item2.getFootpadWorld(xd, yd, zd);
x2a = pt2.x - xd;
y2a = pt2.y - yd;
z2b = pt2.z + zd;
if (pt1.x <= x2a || pt2.x <= x1a)
return false;
if (pt1.y <= y2a || pt2.y <= y1a)
return false;
if (z1b <= pt2.z || z2b <= pt1.z)
return false;
return true;
}
bool Item::overlapsxy(const Item &item2) const {
int32 x1a, y1a;
int32 x2a, y2a;
Point3 pt1 = getLocation();
Point3 pt2 = item2.getLocation();
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
x1a = pt1.x - xd;
y1a = pt1.y - yd;
item2.getFootpadWorld(xd, yd, zd);
x2a = pt2.x - xd;
y2a = pt2.y - yd;
if (pt1.x <= x2a || pt2.x <= x1a)
return false;
if (pt1.y <= y2a || pt2.y <= y1a)
return false;
return true;
}
bool Item::isOn(const Item &item2) const {
int32 x1a, y1a;
int32 x2a, y2a, z2b;
Point3 pt1 = getLocation();
Point3 pt2 = item2.getLocation();
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
x1a = pt1.x - xd;
y1a = pt1.y - yd;
item2.getFootpadWorld(xd, yd, zd);
x2a = pt2.x - xd;
y2a = pt2.y - yd;
z2b = pt2.z + zd;
if (pt1.x <= x2a || pt2.x <= x1a)
return false;
if (pt1.y <= y2a || pt2.y <= y1a)
return false;
if (z2b == pt1.z)
return true;
return false;
}
bool Item::isCompletelyOn(const Item &item2) const {
if (hasFlags(FLG_CONTAINED) || item2.hasFlags(FLG_CONTAINED))
return false;
int32 x1a, y1a;
int32 x2a, y2a, z2b;
Point3 pt1 = getLocation();
Point3 pt2 = item2.getLocation();
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
x1a = pt1.x - xd;
y1a = pt1.y - yd;
item2.getFootpadWorld(xd, yd, zd);
x2a = pt2.x - xd;
y2a = pt2.y - yd;
z2b = pt2.z + zd;
return ((pt1.x <= pt2.x && x2a <= x1a) &&
(pt1.y <= pt2.y && y2a <= y1a) &&
(z2b == pt1.z));
}
bool Item::isCentreOn(const Item &item2) const {
int32 x2a, y2a, z2b;
Point3 pt1 = getCentre();
Point3 pt2 = item2.getLocation();
int32 xd, yd, zd;
item2.getFootpadWorld(xd, yd, zd);
x2a = pt2.x - xd;
y2a = pt2.y - yd;
z2b = pt2.z + zd;
if (pt1.x <= x2a || pt2.x <= pt1.x)
return false;
if (pt1.y <= y2a || pt2.y <= pt1.y)
return false;
if (z2b == getZ())
return true;
return false;
}
bool Item::isOnScreen() const {
GameMapGump *game_map = Ultima8Engine::get_instance()->getGameMapGump();
if (!game_map)
return false;
int32 screenx = -1;
int32 screeny = -1;
game_map->GetLocationOfItem(_objId, screenx, screeny);
Common::Rect32 game_map_dims = game_map->getDims();
const Shape *shape = getShapeObject();
if (!shape)
return false;
const ShapeFrame *frame = shape->getFrame(getFrame());
if (!frame)
return false;
if (game_map_dims.contains(screenx - frame->_xoff, screeny - frame->_yoff) &&
game_map_dims.contains(screenx + frame->_width, screeny + frame->_height)) {
return true;
}
return false;
}
bool Item::isPartlyOnScreen() const {
GameMapGump *game_map = Ultima8Engine::get_instance()->getGameMapGump();
if (!game_map)
return false;
int32 screenx = -1;
int32 screeny = -1;
game_map->GetLocationOfItem(_objId, screenx, screeny);
Common::Rect32 game_map_dims = game_map->getDims();
const Shape *shape = getShapeObject();
if (!shape)
return false;
const ShapeFrame *frame = shape->getFrame(getFrame());
if (!frame)
return false;
if (game_map_dims.contains(screenx - frame->_xoff, screeny - frame->_yoff) ||
game_map_dims.contains(screenx + frame->_width, screeny + frame->_height)) {
return true;
}
return false;
}
bool Item::canExistAt(int32 x, int32 y, int32 z, bool needsupport) const {
CurrentMap *cm = World::get_instance()->getCurrentMap();
int32 xd, yd, zd;
getFootpadWorld(xd, yd, zd);
Box target(x, y, z, xd, yd, zd);
Box empty;
PositionInfo info = cm->getPositionInfo(target, empty, 0, getObjId());
return info.valid && (!needsupport || info.supported);
}
bool Item::canExistAt(const Point3 &pt, bool needsupport) const {
return canExistAt(pt.x, pt.y, pt.z, needsupport);
}
Direction Item::getDirToItemCentre(const Item &item2) const {
Point3 i1 = getCentre();
Point3 i2 = item2.getCentre();
return Direction_GetWorldDir(i2.y - i1.y, i2.x - i1.x, dirmode_8dirs);
}
Direction Item::getDirToItemCentre(const Point3 &pt) const {
Point3 i1 = getCentre();
return Direction_GetWorldDir(pt.y - i1.y, pt.x - i1.x, dirmode_8dirs);
}
int Item::getRange(const Item &item2, bool checkz) const {
int32 thisXd, thisYd, thisZd;
int32 otherXd, otherYd, otherZd;
int32 thisXmin, thisYmin, thisZmax;
int32 otherXmin, otherYmin, otherZmax;
Point3 pt1 = getLocationAbsolute();
Point3 pt2 = item2.getLocationAbsolute();
getFootpadWorld(thisXd, thisYd, thisZd);
item2.getFootpadWorld(otherXd, otherYd, otherZd);
thisXmin = pt1.x - thisXd;
thisYmin = pt1.y - thisYd;
thisZmax = pt1.z + thisZd;
otherXmin = pt2.x - otherXd;
otherYmin = pt2.y - otherYd;
otherZmax = pt2.z + otherZd;
int32 range = 0;
if (thisXmin - pt2.x > range)
range = thisYmin - pt2.y;
if (otherXmin - pt1.x > range)
range = thisXmin - pt2.x;
if (thisYmin - pt2.y > range)
range = otherXmin - pt1.x;
if (otherYmin - pt1.y > range)
range = otherYmin - pt1.y;
if (checkz && pt1.z - otherZmax > range)
range = pt1.z - otherZmax;
if (checkz && pt2.z - thisZmax > range)
range = pt2.z - thisZmax;
return range;
}
int Item::getRangeIfVisible(const Item &item2) const {
World *world = World::get_instance();
CurrentMap *map = world->getCurrentMap();
Std::list<CurrentMap::SweepItem> hitItems;
Point3 start = getCentre();
Point3 end = item2.getCentre();
int32 dims[3] = {1, 1, 1};
int xdiff = abs(start.x - end.x);
int ydiff = abs(start.y - end.y);
int zdiff = abs(start.z - end.z);
map->sweepTest(start, end, dims, getShapeInfo()->_flags, _objId, true, &hitItems);
for (const auto &hit : hitItems) {
int objId = hit._item;
if (hit._blocking && objId != _objId && objId != item2.getObjId()) {
//int out[3];
//hit.GetInterpolatedCoords(out, start, end);
//warning("found blocking item %d at %d %d %d.", objId, out[0], out[1], out[2]);
return 0;
}
}
int distance = MAX(MAX(xdiff, ydiff), zdiff);
return distance;
}
const ShapeInfo *Item::getShapeInfoFromGameInstance() const {
return GameData::get_instance()->getMainShapes()->getShapeInfo(_shape);
}
const Shape *Item::getShapeObject() const {
if (!_cachedShape) _cachedShape = GameData::get_instance()->getMainShapes()->getShape(_shape);
return _cachedShape;
}
uint16 Item::getFamily() const {
const ShapeInfo *info = getShapeInfo();
if (!info)
return 0;
return static_cast<uint16>(info->_family);
}
uint32 Item::getWeight() const {
uint32 weight = getShapeInfo()->_weight;
switch (getShapeInfo()->_family) {
case ShapeInfo::SF_QUANTITY:
return ((getQuality() * weight) + 9) / 10;
case ShapeInfo::SF_REAGENT:
return getQuality() * weight;
default:
return weight * 10;
}
}
uint32 Item::getTotalWeight() const {
return getWeight();
}
uint32 Item::getVolume() const {
// invisible items (trap markers and such) don't take up volume
if (hasFlags(FLG_INVISIBLE))
return 0;
uint32 volume = getShapeInfo()->_volume;
switch (getShapeInfo()->_family) {
case ShapeInfo::SF_QUANTITY:
return ((getQuality() * volume) + 99) / 100;
case ShapeInfo::SF_REAGENT:
return ((getQuality() * volume) + 9) / 10;
case ShapeInfo::SF_CONTAINER:
return (volume == 0) ? 1 : volume;
default:
return volume;
}
}
bool Item::checkLoopScript(const uint8 *script, uint32 scriptsize) const {
// if really necessary this could be made static to prevent news/deletes
DynamicUCStack stack(0x40); // 64bytes should be plenty of room
unsigned int i = 0;
uint16 ui16a, ui16b;
stack.push2(1); // default to true if script is empty
while (i < scriptsize) {
switch (script[i]) {
case LS_TOKEN_FALSE: // false
stack.push2(0);
break;
case LS_TOKEN_TRUE: // true
stack.push2(1);
break;
case LS_TOKEN_END: // end
ui16a = stack.pop2();
return (ui16a != 0);
case LS_TOKEN_INT: // int
//! check for i out of bounds
ui16a = script[i + 1] + (script[i + 2] << 8);
stack.push2(ui16a);
i += 2;
break;
case LS_TOKEN_AND: // and
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16a != 0 && ui16b != 0)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_OR: // or
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16a != 0 || ui16b != 0)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_NOT: // not
ui16a = stack.pop2();
if (ui16a != 0)
stack.push2(0);
else
stack.push2(1);
break;
case LS_TOKEN_STATUS: // item status
stack.push2(getFlags());
break;
case LS_TOKEN_Q: // item _quality
stack.push2(getQuality());
break;
case LS_TOKEN_NPCNUM: // npc num
stack.push2(getNpcNum());
break;
case LS_TOKEN_EQUAL: // equal
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16a == ui16b)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_GREATER: // greater than
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16b > ui16a)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_LESS: // less than
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16b < ui16a)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_GEQUAL: // greater or equal
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16b >= ui16a)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_LEQUAL: // smaller or equal
ui16a = stack.pop2();
ui16b = stack.pop2();
if (ui16b <= ui16a)
stack.push2(1);
else
stack.push2(0);
break;
case LS_TOKEN_FAMILY: // item family
stack.push2(getFamily());
break;
case LS_TOKEN_SHAPE: // item _shape
stack.push2(static_cast<uint16>(getShape()));
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z': {
bool match = false;
int count = script[i] - '@';
for (int j = 0; j < count; j++) {
//! check for i out of bounds
if (getShape() == static_cast<uint32>(script[i + 1] + (script[i + 2] << 8)))
match = true;
i += 2;
}
if (match)
stack.push2(1);
else
stack.push2(0);
}
break;
case LS_TOKEN_FRAME: // item _frame
stack.push2(static_cast<uint16>(getFrame()));
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z': {
bool match = false;
int count = script[i] - '`';
for (int j = 0; j < count; j++) {
//! check for i out of bounds
if (getFrame() == static_cast<uint32>(script[i + 1] + (script[i + 2] << 8)))
match = true;
i += 2;
}
if (match)
stack.push2(1);
else
stack.push2(0);
}
break;
default:
warning("Unknown loopscript opcode %02X", script[i]);
}
i++;
}
warning("Didn't encounter $ in loopscript");
return false;
}
int32 Item::collideMove(int32 dx, int32 dy, int32 dz, bool teleport, bool force, ObjId *hititem, uint8 *dirs) {
Ultima8Engine *guiapp = Ultima8Engine::get_instance();
World *world = World::get_instance();
CurrentMap *map = world->getCurrentMap();
if (hititem) *hititem = 0;
if (dirs) *dirs = 0;
Point3 start(dx, dy, dz);
Point3 end(dx, dy, dz);
// If we are moving from a container, only check the destination
// Otherwise check from where we are to where we want to go
if (!_parent) {
start = getLocation();
}
int32 dims[3];
getFootpadWorld(dims[0], dims[1], dims[2]);
// Do the sweep test
Std::list<CurrentMap::SweepItem> collisions;
map->sweepTest(start, end, dims, getShapeInfo()->_flags, _objId, false, &collisions);
// Ok, now to work out what to do
// the force of the hit, used for the gotHit/hit usecode calls
int deltax = ABS(start.x - end.x) / 4;
int deltay = ABS(start.y - end.y) / 4;
int deltaz = ABS(start.z - end.z);
int maxdirdelta = deltay;
if (deltay > maxdirdelta) maxdirdelta = deltay;
if (deltaz > maxdirdelta) maxdirdelta = deltaz;
int hitforce = (deltax + deltay + deltaz + maxdirdelta) / 2;
// if we are contained, we always teleport
if (teleport || _parent) {
// If teleporting and not force, work out if we can reach the end
if (!force) {
for (const auto &collision : collisions) {
// Uh oh, we hit something, can't move
if (collision._endTime == 0x4000 && !collision._touching && collision._blocking) {
if (hititem)
*hititem = collision._item;
if (dirs)
*dirs = collision._dirs;
return 0;
}
}
}
// Trigger all the required events
bool we_were_released = false;
for (const auto &collision : collisions) {
Item *item = getItem(collision._item);
if (!item)
continue; // shouldn't happen..
// Hitting us at the start and end, don't do anything
if (!_parent && collision._hitTime == 0x0000 &&
collision._endTime == 0x4000) {
continue;
}
// Hitting us at the end (call hit on us, got hit on them)
else if (collision._endTime == 0x4000) {
if (_objId == kMainActorId && guiapp->isShowTouchingItems())
item->setExtFlag(Item::EXT_HIGHLIGHT);
item->callUsecodeEvent_gotHit(_objId, hitforce);
callUsecodeEvent_hit(item->getObjId(), hitforce);
}
// Hitting us at the start (call release on us and them)
else if (!_parent && collision._hitTime == 0x0000) {
if (_objId == kMainActorId)
item->clearExtFlag(Item::EXT_HIGHLIGHT);
we_were_released = true;
item->callUsecodeEvent_release();
}
}
// Call release on us
if (we_were_released) callUsecodeEvent_release();
// Move US!
move(end);
// We reached the end
return 0x4000;
} else {
int32 hit = 0x4000;
// If not force, work out if we can reach the end
// if not, need to do 'stuff'
// We don't care about items hitting us at the start
if (!force) {
Std::list<CurrentMap::SweepItem>::iterator it;
for (it = collisions.begin(); it != collisions.end(); it++) {
if (it->_blocking && !it->_touching) {
if (hititem)
*hititem = it->_item;
if (dirs)
*dirs = it->_dirs;
hit = it->_hitTime;
break;
}
}
if (hit < 0)
hit = 0;
if (hit != 0x4000) {
debugC(kDebugCollision, "Hit time: %d; Start: %d, %d, %d; End: %d, %d, %d",
hit, start.x, start.y, start.z, end.x, end.y, end.z);
end = it->GetInterpolatedCoords(start, end);
debugC(kDebugCollision, "Collision: %d, %d, %d", end.x, end.y, end.z);
}
}
// Trigger all the required events
bool we_were_released = false;
for (const auto &collision : collisions) {
Item *item = getItem(collision._item);
if (!item) continue;
// Did we go past the endpoint of the move?
if (collision._hitTime > hit) break;
uint16 proc_gothit = 0, proc_rel = 0;
// If hitting at start, we should have already
// called gotHit and hit. In Crusader we call
// hit for every hitting frame to make sickbays
// and teleporters work.
bool call_hit = collision._hitTime >= 0 || GAME_IS_CRUSADER;
if ((!collision._touching || collision._touchingFloor) && call_hit) {
if (_objId == kMainActorId && guiapp->isShowTouchingItems())
item->setExtFlag(Item::EXT_HIGHLIGHT);
proc_gothit = item->callUsecodeEvent_gotHit(_objId, hitforce);
callUsecodeEvent_hit(item->getObjId(), hitforce);
}
// If not hitting at end, we will need to call release
if (collision._endTime < hit) {
if (_objId == kMainActorId)
item->clearExtFlag(Item::EXT_HIGHLIGHT);
we_were_released = true;
proc_rel = item->callUsecodeEvent_release();
}
// We want to make sure that release is called AFTER gotHit.
if (proc_rel && proc_gothit) {
Process *p = Kernel::get_instance()->getProcess(proc_rel);
p->waitFor(proc_gothit);
}
}
// Call release on us
if (we_were_released) callUsecodeEvent_release();
// Move US!
move(end);
return hit;
}
return 0;
}
uint16 Item::fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype, bool findtarget) {
Point3 pt = getLocation();
if (!GAME_IS_CRUSADER)
return 0;
pt.x += x;
pt.y += y;
pt.z += z;
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
const FireType *firetypedat = GameData::get_instance()->getFireType(firetype);
if (!firetypedat)
return 0;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
int damage = firetypedat->getRandomDamage();
// CHECKME: the original doesn't exclude the source like this,
// but it seems obvious we have to or NPCs shoot themselves?
PositionInfo info = currentmap->getPositionInfo(pt.x, pt.y, pt.z, BULLET_SPLASH_SHAPE, _objId);
if (!info.valid && info.blocker) {
Item *block = getItem(info.blocker->getObjId());
Point3 blockpt = block->getLocation();
Direction damagedir = Direction_GetWorldDir(blockpt.y - pt.y, blockpt.x - pt.x, dirmode_8dirs);
block->receiveHit(getObjId(), damagedir, damage, firetype);
if (firetypedat->getRange() != 0) {
int splashdamage = firetypedat->getRandomDamage();
firetypedat->applySplashDamageAround(blockpt, splashdamage, 1, block, this);
}
if (firetypedat->getNearSprite())
firetypedat->makeBulletSplashShapeAndPlaySound(pt.x, pt.y, pt.z);
return 0;
}
int spriteframe = 0; // fire types 1, 2, 7, 8, 0xb, 0xd
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 + rs.getRandomNumber(4);
break;
case 0xf:
case 0x12: // No Regret only
case 0x13: // No Regret only
spriteframe = 0x4c;
break;
case 0x10: // No Regret only
spriteframe = dir + 0x50;
break;
case 0x11: // No Regret only
spriteframe = dir * 6 + 0x78;
break;
case 0x14: // No Regret only
spriteframe = dir * 3 + 0xdc;
break;
case 0x15: // No Regret only
spriteframe = dir + 100;
break;
case 0x16: // No Regret only
spriteframe = dir + 0x11;
break;
default:
break;
}
// HACK: this should be fixed to use inheritance so the behavior
// is clean for both Item and Actor.
DirectionMode dirmode = dirmode_8dirs;
const Actor *thisactor = dynamic_cast<Actor *>(this);
Item *target = nullptr;
if (thisactor) {
dirmode = thisactor->animDirMode(thisactor->getLastAnim());
// In the original code there is logic here deciding whether to get
// damage for the active weapon or some other item, but in the end
// it always gets random damage for the same firetype, so as far as
// I can tell it makes no difference.
// In No Regret, we get another chance to increase damage. We also
// set target based on the npc attack rather than whatever controlled
// actor is.
if (GAME_IS_REGRET) {
if (damage < 2)
damage = firetypedat->getRandomDamage();
AttackProcess *attackproc = thisactor->getAttackProcess();
if (attackproc) {
target = getActor(attackproc->getTarget());
}
}
}
if (findtarget) {
if (this != getControlledActor()) {
if (GAME_IS_REMORSE || !thisactor)
target = getControlledActor();
// else already set above to attackproc target
} else {
target = currentmap->findBestTargetItem(pt.x, pt.y, pt.z - z, dir, dirmode);
}
}
Point3 t(-1, 0, 0);
if (target) {
t = target->getCentre();
t.z = target->getTargetZRelativeToAttackerZ(getZ());
}
// 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();
uint16 spriteprocpid = 0;
for (int i = 0; i < numshots; i++) {
SuperSpriteProcess *ssp;
CrosshairProcess *chp = CrosshairProcess::get_instance();
assert(chp);
const Item *crosshair = getItem(chp->getItemNum());
Point3 ss;
if (t.x != -1) {
// Shoot toward the target
ss = t;
findtarget = true;
} else if (this == getControlledActor() && crosshair) {
// Shoot toward the crosshair
ss = crosshair->getLocation();
ss.z = pt.z;
} else {
// Just send the projectile off into the distance
ss.x = pt.x + Direction_XFactor(dir) * 0x500;
ss.y = pt.y + Direction_YFactor(dir) * 0x500;
ss.z = pt.z;
}
uint16 targetid = (target ? target->getObjId() : 0);
ssp = new SuperSpriteProcess(BULLET_SPLASH_SHAPE, spriteframe,
pt.x, pt.y, pt.z, ss.x, ss.y, ss.z, firetype,
damage, _objId, targetid, findtarget);
Kernel::get_instance()->addProcess(ssp);
spriteprocpid = ssp->getPid();
}
return spriteprocpid;
}
uint16 Item::fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) const {
if (!other)
return 0;
//
// We pick what animation the actor would do to fire, then
// pick the frame(s) where they fire in that anim.
//
// Then, check if the target can be hit using the attackx/attacky/attackz offsets.
// The offsets are checked in priority order:
// * First fire frame in anim
// * Second fire frame
// * if there are no fire frames, use the parameter offsets
//
int16 xoff2 = 0;
int16 yoff2 = 0;
int16 zoff2 = 0;
bool other_offsets = false;
const Actor *a = dynamic_cast<const Actor *>(this);
if (a) {
Animation::Sequence anim;
bool kneeling = a->isKneeling();
bool smallwpn = true;
const MainActor *ma = dynamic_cast<const MainActor *>(this);
const Item *wpn = getItem(a->getActiveWeapon());
if (wpn && wpn->getShapeInfo()->_weaponInfo) {
smallwpn = wpn->getShapeInfo()->_weaponInfo->_small;
}
if (kneeling) {
if (smallwpn)
anim = Animation::kneelAndFireSmallWeapon;
else
anim = Animation::kneelAndFireLargeWeapon;
} else {
if (smallwpn || !ma)
anim = Animation::fireSmallWeapon;
else
anim = Animation::fireLargeWeapon;
}
bool first_offsets = false;
const AnimAction *action = GameData::get_instance()->getMainShapes()->getAnim(_shape, static_cast<int32>(anim));
unsigned int nframes = action ? action->getSize() : 0;
for (unsigned int i = 0; i < nframes; i++) {
const AnimFrame &frame = action->getFrame(dir, i);
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;
}
}
}
}
Point3 pt = getLocation();
Point3 pto = other->getLocation();
int32 dist = 0;
const CurrentMap *cm = World::get_instance()->getCurrentMap();
if (!cm)
return 0;
for (int i = 0; i < (other_offsets ? 2 : 1) && dist == 0; i++) {
int32 cx = pt.x + (i == 0 ? xoff : xoff2);
int32 cy = pt.y + (i == 0 ? yoff : yoff2);
int32 cz = pt.z + (i == 0 ? zoff : zoff2);
PositionInfo info = cm->getPositionInfo(cx, cy, cz, BULLET_SPLASH_SHAPE, getObjId());
if (!info.valid && info.blocker) {
if (info.blocker->getObjId() == other->getObjId())
dist = MAX(abs(_x - pto.x), abs(_y - pto.y));
} else {
Point3 oc = other->getCentre();
oc.z = other->getTargetZRelativeToAttackerZ(getZ());
const Point3 start(cx, cy, cz);
const Point3 end = oc;
const int32 dims[3] = {2, 2, 2};
Std::list<CurrentMap::SweepItem> collisions;
cm->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
_objId, true, &collisions);
for (const auto &collision : collisions) {
if (collision._item == getObjId())
continue;
if (collision._touching)
continue;
if (collision._item != other->getObjId())
break;
Point3 out = collision.GetInterpolatedCoords(start, end);
dist = MAX(abs(_x - out.x), abs(_y - out.y));
break;
}
}
}
if (!dist)
return 0;
return dist < 32 ? 1 : dist / 32;
}
int32 Item::getTargetZRelativeToAttackerZ(int32 otherz) const {
int32 tsx, tsy, tsz;
getFootpadData(tsx, tsy, tsz);
int32 tz = getZ() + tsz * 8;
if (tsz < 3) {
if (tsz)
tz -= 8;
} else {
int32 targetz = tz;
tz -= 16;
if (otherz - targetz < -0x2f) {
tz += 8;
} else if (otherz - targetz > 0x2f) {
if (tsz == 6) {
tz -= 16;
} else if (tsz >= 7) {
tz -= 24;
}
}
}
return tz;
}
unsigned int Item::countNearby(uint32 shape, uint16 range) const {
const CurrentMap *currentmap = World::get_instance()->getCurrentMap();
UCList itemlist(2);
LOOPSCRIPT(script, LS_SHAPE_EQUAL(shape));
currentmap->areaSearch(&itemlist, script, sizeof(script),
this, range, false);
return itemlist.getSize();
}
uint32 Item::callUsecodeEvent(uint32 event, const uint8 *args, int argsize) {
uint32 class_id = _shape;
// Non-monster NPCs use _objId/_npcNum + 1024 (2048 in crusader)
// Note: in the original, a non-monster NPC is specified with
// the FAST_ONLY flag. However, this causes some summoned monster which
// do not receive the FAST_ONLY flag to behave strangely. (Confirmed that
// happens in the original as well.) -wjp 20050128
if (_objId < 256 && (_extendedFlags & EXT_PERMANENT_NPC)) {
if (GAME_IS_U8)
class_id = _objId + 1024;
else
class_id = _objId + 2048;
}
// CHECKME: to make Pentagram behave as much like the original as possible,
// don't call any usecode if the original would call the wrong class
if (GAME_IS_U8 && _objId < 256 && !(_extendedFlags & EXT_PERMANENT_NPC) &&
!(_flags & FLG_FAST_ONLY))
return 0;
// UnkEggs have class quality + 0x47F in U8, +0x900 in Crusader
if (getFamily() == ShapeInfo::SF_UNKEGG)
class_id = _quality + (GAME_IS_U8 ? 0x47F : 0x900);
Usecode *u = GameData::get_instance()->getMainUsecode();
uint32 offset = u->get_class_event(class_id, event);
if (!offset) return 0; // event not found
debugC(kDebugObject, "Item: %d (shape %d) calling usecode event %d @ %04X:%04X",
_objId, _shape, event, class_id, offset);
return callUsecode(static_cast<uint16>(class_id),
static_cast<uint16>(offset),
args, argsize);
}
uint32 Item::callUsecodeEvent_look() { // event 0
return callUsecodeEvent(0); // CONSTANT
}
uint32 Item::callUsecodeEvent_use() { // event 1
return callUsecodeEvent(1); // CONSTANT
}
uint32 Item::callUsecodeEvent_anim() { // event 2
return callUsecodeEvent(2); // CONSTANT
}
uint32 Item::callUsecodeEvent_cachein() { // event 4
return callUsecodeEvent(4); // CONSTANT
}
uint32 Item::callUsecodeEvent_hit(uint16 hitter, int16 hitforce) { // event 5
DynamicUCStack arg_stack(4);
arg_stack.push2(hitforce);
arg_stack.push2(hitter);
return callUsecodeEvent(5, arg_stack.access(), 4); // CONSTANT 5
}
uint32 Item::callUsecodeEvent_gotHit(uint16 hitter, int16 hitforce) { // event 6
DynamicUCStack arg_stack(4);
arg_stack.push2(hitforce);
arg_stack.push2(hitter);
return callUsecodeEvent(6, arg_stack.access(), 4); // CONSTANT 6
}
uint32 Item::callUsecodeEvent_hatch() { // event 7
return callUsecodeEvent(7); // CONSTANT
}
uint32 Item::callUsecodeEvent_schedule(uint32 time) { // event 8
DynamicUCStack arg_stack(4);
arg_stack.push4(time);
return callUsecodeEvent(8, arg_stack.access(), 4); // CONSTANT 8
}
uint32 Item::callUsecodeEvent_release() { // event 9
return callUsecodeEvent(9); // CONSTANT
}
uint32 Item::callUsecodeEvent_equip() { // event A
return callUsecodeEvent(0xA); // CONSTANT
}
uint32 Item::callUsecodeEvent_equipWithParam(ObjId param) { // event A
DynamicUCStack arg_stack(2);
arg_stack.push2(param);
return callUsecodeEvent(0xA, arg_stack.access(), 2);
}
uint32 Item::callUsecodeEvent_unequip() { // event B
return callUsecodeEvent(0xB); // CONSTANT
}
uint32 Item::callUsecodeEvent_unequipWithParam(ObjId param) { // event B
DynamicUCStack arg_stack(2);
arg_stack.push2(param);
return callUsecodeEvent(0xB, arg_stack.access(), 2);
}
uint32 Item::callUsecodeEvent_combine() { // event C
return callUsecodeEvent(0xC); // CONSTANT
}
uint32 Item::callUsecodeEvent_calledFromAnim() { // event E
return callUsecodeEvent(0xE); // CONSTANT
}
uint32 Item::callUsecodeEvent_enterFastArea() { // event F
return callUsecodeEvent(0xF); // CONSTANT
}
uint32 Item::callUsecodeEvent_leaveFastArea() { // event 10
return callUsecodeEvent(0x10); // CONSTANT
}
uint32 Item::callUsecodeEvent_cast(uint16 unk) { // event 11
DynamicUCStack arg_stack(2);
arg_stack.push2(unk);
return callUsecodeEvent(0x11, arg_stack.access(), 2); // CONSTANT 0x11
}
uint32 Item::callUsecodeEvent_justMoved() { // event 12
return callUsecodeEvent(0x12); // CONSTANT
}
uint32 Item::callUsecodeEvent_AvatarStoleSomething(uint16 unk) { // event 13
DynamicUCStack arg_stack(2);
arg_stack.push2(unk);
return callUsecodeEvent(0x13, arg_stack.access(), 2); // CONSTANT 0x13
}
uint32 Item::callUsecodeEvent_guardianBark(int16 unk) { // event 15
DynamicUCStack arg_stack(2);
arg_stack.push2(unk);
return callUsecodeEvent(0x15, arg_stack.access(), 2); // CONSTANT 0x15
}
uint32 Item::callUsecodeEvent_unhatch() { // event 15
return callUsecodeEvent(0x15); // CONSTANT
}
uint32 Item::use() {
Actor *actor = dynamic_cast<Actor *>(this);
if (actor) {
if (actor->isDead()) {
if (GAME_IS_U8) {
// dead actor, so open/close the dead-body-gump
if (hasFlags(FLG_GUMP_OPEN)) {
closeGump();
} else {
openGump(12); // CONSTANT!!
}
}
return 0;
}
}
return callUsecodeEvent_use();
}
void Item::destroy(bool delnow) {
if (_flags & FLG_ETHEREAL) {
// Remove us from the ether
World::get_instance()->etherealRemove(_objId);
} else if (_parent) {
// we're in a container, so remove self from _parent
//!! need to make sure this works for equipped items too...
Container *p = getParentAsContainer();
if (p) p->removeItem(this);
} else if (_extendedFlags & EXT_INCURMAP) {
// remove self from CurrentMap
World::get_instance()->getCurrentMap()->removeItemFromList(this, _x, _y);
}
if (GAME_IS_CRUSADER) {
// Ensure sounds for this object are stopped
AudioProcess *audio = AudioProcess::get_instance();
if (audio)
audio->stopSFX(-1, _objId);
if (_shape == SNAP_EGG_SHAPE) {
SnapProcess *snap = SnapProcess::get_instance();
if (snap)
snap->removeEgg(this);
}
}
if (_extendedFlags & Item::EXT_CAMERA)
CameraProcess::SetCameraProcess(0);
// Do we want to delete now or not?
if (!delnow) {
Process *dap = new DestroyItemProcess(this);
Kernel::get_instance()->addProcess(dap);
return;
}
clearObjId();
delete this; // delete self.
}
//
// Item::setupLerp()
//
// Desc: Setup the lerped info for this frame
//
void Item::setupLerp(int32 gametick) {
// Don't need to set us up
if (_lastSetup && gametick == _lastSetup)
return;
// Are we lerping or are we not? Default we lerp.
bool no_lerp = false;
// No lerping this _frame if Shape Changed, or No lerp is set,
// or no last setup, or last setup more than 1 tick ago
if ((_lastSetup == 0) || (_lNext._shape != _shape) ||
(_extendedFlags & EXT_LERP_NOPREV) ||
(gametick - _lastSetup) > 1 || _flags & FLG_CONTAINED)
no_lerp = true;
// Update the time we were just setup
_lastSetup = gametick;
// Clear the flag
_extendedFlags &= ~EXT_LERP_NOPREV;
// Animate it, if needed.
//
// We use (tick % speed == 0) here. To be completely faithful to the original
// game it should be (tick % speed == tick % _objId). That is how the game
// does it, but it also causes animation frame mismatches on multi-shape
// objects. This is easily noticeable on the waterfall West of Tenebrae,
// which appears to tear slightly even on the original.
//
// In the original it was likely done to spread CPU time over different frames,
// but it's a little bit nicer to do it correctly now that we can afford to..
//
const ShapeInfo *info = getShapeInfo();
if (info->_animType &&
((gametick % info->_animSpeed) == 0))
animateItem();
// Setup the prev values for lerping
if (!no_lerp) _lPrev = _lNext;
// Setup next
if (_flags & FLG_CONTAINED) {
_lNext._x = _ix = _y & 0xFF;
_lNext._y = _iy = (_y >> 8) & 0xFF;
_lNext._z = _iz = 0;
} else {
_lNext._x = _ix = _x;
_lNext._y = _iy = _y;
_lNext._z = _iz = _z;
}
_lNext._shape = _shape;
_lNext._frame = _frame;
// Setup prev values for not lerping
if (no_lerp) _lPrev = _lNext;
}
// Animate the item
void Item::animateItem() {
const ShapeInfo *info = getShapeInfo();
if (!info->_animType)
return;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
uint32 anim_data = info->_animData;
const Shape *shp = getShapeObject();
switch (info->_animType) {
case 2:
// Randomly change frame
if (rs.getRandomBit() && shp)
_frame = rs.getRandomNumber(shp->frameCount() - 1);
break;
case 1:
case 3:
// animdata 0 = always increment
// animdata 1 = 50 % chance of changing
// animdata 2+ = loop in frame blocks of size animdata
if (anim_data == 0 || (anim_data == 1 && rs.getRandomBit())) {
_frame++;
if (shp && _frame >= shp->frameCount())
_frame = 0;
} else if (anim_data > 1) {
_frame++;
uint32 num = (_frame - 1) / anim_data;
if (_frame == ((num + 1) * anim_data))
_frame = num * anim_data;
}
break;
case 4:
// Randomly start animating, with chance of 1/(animdata + 2)
// once animating, go through all frames.
if (_frame || rs.getRandomNumber(anim_data + 1) == 0) {
_frame++;
if (shp && _frame >= shp->frameCount())
_frame = 0;
}
break;
case 5:
// Just call the usecode
callUsecodeEvent_anim();
break;
case 6:
// animdata 0 = stick on frame 0, else loop from 1 to count
// animdata 1 = same as 0, but with 50% chance of change
// animdata 2+ = same, but loop in frame blocks of size animdata
if (anim_data == 0 || (anim_data == 1 && rs.getRandomBit())) {
if (!_frame)
break;
_frame++;
if (shp && _frame >= shp->frameCount())
_frame = 1;
} else if (anim_data > 1) {
if (!(_frame % anim_data))
break;
_frame++;
uint32 num = (_frame - 1) / anim_data;
if (_frame == ((num + 1) * anim_data))
_frame = num * anim_data + 1;
}
break;
default:
debugC(kDebugObject, "type %u data %u", info->_animType, anim_data);
break;
}
}
// Called when an item has entered the fast area
uint32 Item::enterFastArea() {
uint16 retval = 0;
//!! HACK to get rid of endless SFX loops
if (_shape == 0x2c8 && GAME_IS_U8)
return 0;
const ShapeInfo *si = getShapeInfo();
// Call usecode
if (!(_flags & FLG_FASTAREA)) {
Actor *actor = dynamic_cast<Actor *>(this);
// Crusader special-cases a few shapes even when they are dead.
bool call_even_if_dead = (_shape == 0x576 || _shape == 0x596 ||
_shape == 0x59c || _shape == 0x58f) && GAME_IS_CRUSADER;
if (actor && actor->isDead() && !call_even_if_dead) {
// dead actor, don't call the usecode
} else {
if (actor && _objId != kMainActorId) {
if (GAME_IS_CRUSADER) {
uint16 lastactivity = actor->getLastActivityNo();
actor->clearLastActivityNo();
actor->clearInCombat();
actor->setToStartOfAnim(Animation::stand);
actor->clearActorFlag(Actor::ACT_WEAPONREADY);
actor->setActivity(lastactivity);
} else {
actor->clearInCombat();
actor->setToStartOfAnim(Animation::stand);
actor->clearActorFlag(Actor::ACT_WEAPONREADY);
}
}
// TODO: For eggs, Crusader also resets the NPC info if a
// certain global is set. For now just skip that.
//
// NOTE: Original games only call usecode for actors or NOISY
// types. We call for all types to fix an usecode bug in
// Crusader: No Remorse. See note about it below.
//
// if (actor || si->_flags & ShapeInfo::SI_NOISY)
retval = callUsecodeEvent_enterFastArea();
}
}
if (!hasFlags(FLG_BROKEN) && GAME_IS_CRUSADER) {
if (si->is_targetable()) {
World::get_instance()->getCurrentMap()->addTargetItem(this);
}
if (_shape == SNAP_EGG_SHAPE) {
SnapProcess *snap = SnapProcess::get_instance();
if (snap)
snap->addEgg(this);
}
}
// We're fast!
_flags |= FLG_FASTAREA;
//
// WORKAROUND: Crusader: No Remorse usecode has one place (REB_EGG, after
// randomly creating 0x34D REB_COUP (rebel couple) at the bar) where this
// is called with an "implies" but the enterFastArea event code never
// returns. Every other instance this is "spawn"ed.
//
// In the original this bug is never triggered as enterFastArea intrinsic
// does not call usecode event unless the shape is SI_NOISY (REB_COUP is
// not). The result is the rebel couple do not start moving until you move
// near them and trigger their event, then enterFastArea function is
// spawned directly.
//
// Work around both problems by always calling event above and return 0.
//
if (_shape == 0x34D && GAME_IS_REMORSE)
return 0;
return retval;
}
// Called when an item is leaving the fast area
void Item::leaveFastArea() {
if (_objId == kMainActorId) {
debugC(kDebugActor, "Main actor leaving fast area");
}
// Call usecode
if ((!(_flags & FLG_FAST_ONLY) || getShapeInfo()->is_noisy()) &&
(_flags & FLG_FASTAREA))
callUsecodeEvent_leaveFastArea();
// If we have a gump open, close it (unless we're in a container)
if (!_parent) {
closeGump();
closeBark();
}
// Unset the flag
_flags &= ~FLG_FASTAREA;
if (!hasFlags(FLG_BROKEN) && GAME_IS_CRUSADER) {
World::get_instance()->getCurrentMap()->removeTargetItem(this);
if (_shape == SNAP_EGG_SHAPE) {
SnapProcess *snap = SnapProcess::get_instance();
if (snap)
snap->removeEgg(this);
}
}
// CHECKME: what do we need to do exactly?
// currently, destroy object
// Kill us if we are fast only, unless we're in a container
if ((_flags & FLG_FAST_ONLY) && !getParent()) {
// destroy contents if container
Container *c = dynamic_cast<Container *>(this);
if (c) c->destroyContents();
destroy();
// NB: destroy() creates a DestroyItemProcess to actually
// delete the item in this case.
}
// If we have a gravity process, move us to the ground
else if (_gravityPid) {
Process *p = Kernel::get_instance()->getProcess(_gravityPid);
if (p) {
p->terminateDeferred();
_gravityPid = 0;
collideMove(_x, _y, 0, true, false);
}
}
}
uint16 Item::openGump(uint32 gumpshape) {
if (_flags & FLG_GUMP_OPEN) return 0;
assert(_gump == 0);
const Shape *shapeP = GameData::get_instance()->getGumps()->getShape(gumpshape);
ContainerGump *cgump;
if (getObjId() != kMainActorId) {
cgump = new ContainerGump(shapeP, 0, _objId, Gump::FLAG_ITEM_DEPENDENT |
Gump::FLAG_DRAGGABLE);
} else {
cgump = new PaperdollGump(shapeP, 0, _objId, Gump::FLAG_ITEM_DEPENDENT |
Gump::FLAG_DRAGGABLE);
}
//!!TODO: clean up the way this is set
//!! having the itemarea associated with the shapeP through the
//!! GumpShapeFlex maybe
cgump->setItemArea(GameData::get_instance()->
getGumps()->getGumpItemArea(gumpshape));
cgump->InitGump(0);
_flags |= FLG_GUMP_OPEN;
_gump = cgump->getObjId();
return _gump;
}
void Item::closeGump() {
if (!(_flags & FLG_GUMP_OPEN)) return;
Gump *g = Ultima8Engine::get_instance()->getGump(_gump);
if (g)
g->Close();
// can we already clear gump here, or do we need to wait for the gump
// to really close??
clearGump();
}
void Item::clearGump() {
_gump = 0;
_flags &= ~FLG_GUMP_OPEN;
}
ProcId Item::bark(const Std::string &msg) {
closeBark();
uint32 shapenum = getShape();
Gump *gump = new BarkGump(getObjId(), msg, shapenum);
_bark = gump->getObjId();
// Adds talk animations when bark is active.
// FIXME: This also affects bark after look unlike original game
if (getObjId() < 256) { // CONSTANT!
GumpNotifyProcess *notifyproc;
notifyproc = new ActorBarkNotifyProcess(getObjId());
Kernel::get_instance()->addProcess(notifyproc);
gump->SetNotifyProcess(notifyproc);
}
gump->InitGump(0);
return gump->GetNotifyProcess()->getPid();
}
void Item::closeBark() {
Gump *gump = Ultima8Engine::get_instance()->getGump(_bark);
if (gump)
gump->Close();
clearBark();
}
void Item::clearBark() {
_bark = 0;
}
int32 Item::ascend(int delta) {
debugC(kDebugObject, "Ascend: _objId=%u, delta=%d", getObjId(), delta);
if (delta == 0) return 0x4000;
// * gather all items on top of this item (recursively?)
// * etherealize all those items to get them out of the way
// * move self up/down
// * attempt to rematerialize the items up/down
// (going through etherealness to avoid having to sort the supported items)
UCList uclist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
World *world = World::get_instance();
world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
this, true, false, false);
for (uint32 i = 0; i < uclist.getSize(); i++) {
Item *item = getItem(uclist.getuint16(i));
if (!item) continue;
if (item->getShapeInfo()->is_fixed()) continue;
item->moveToEtherealVoid();
}
// move self
Point3 ptv = getLocation();
int dist = collideMove(ptv.x, ptv.y, ptv.z + delta, false, false);
delta = (delta * dist) / 0x4000;
debugC(kDebugObject, "Ascend: dist=%d", dist);
// move other items
for (uint32 i = 0; i < uclist.getSize(); i++) {
Item *item = getItem(uclist.getuint16(i));
if (!item) continue;
if (item->getShapeInfo()->is_fixed()) continue;
Point3 pti = item->getLocation();
_ix = pti.x;
_iy = pti.y;
_iz = pti.z;
pti.z += delta;
if (item->canExistAt(pti)) {
item->move(pti); // automatically un-etherealizes item
} else {
// uh oh...
// CHECKME: what do we do here?
item->move(_ix, _iy, _iz);
if (delta < 0) item->fall();
}
}
return dist;
}
GravityProcess *Item::ensureGravityProcess() {
GravityProcess *p;
if (_gravityPid) {
p = dynamic_cast<GravityProcess *>(
Kernel::get_instance()->getProcess(_gravityPid));
} else {
p = new GravityProcess(this, 0);
Kernel::get_instance()->addProcess(p);
p->init();
}
assert(p);
return p;
}
void Item::fall() {
const ShapeInfo *info = getShapeInfo();
bool hanging = GAME_IS_U8 && (_flags & FLG_HANGING);
if (hanging || info->is_fixed() || info->_weight == 0) {
// can't fall
return;
}
int gravity = GAME_IS_CRUSADER ? 2 : 4; //!! constants
hurl(0, 0, 0, gravity);
}
void Item::grab() {
// CHECKME: is the fall/release timing correct?
UCList uclist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
World *world = World::get_instance();
world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
this, true, false, true);
for (uint32 i = 0; i < uclist.getSize(); i++) {
Item *item = getItem(uclist.getuint16(i));
if (!item) continue;
item->fall();
}
uclist.free();
world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
this, false, true, false);
for (uint32 i = 0; i < uclist.getSize(); i++) {
Item *item = getItem(uclist.getuint16(i));
if (!item) continue;
item->callUsecodeEvent_release();
}
}
void Item::hurl(int xs, int ys, int zs, int grav) {
if (_parent) {
// Should be removed from the container first??
// This will break otherwise as location is 0,0,0
warning("Ignoring hurl for contained item %d.", _objId);
return;
}
// crusader sleeps existing gravity at first
bool do_sleep = GAME_IS_CRUSADER && (_gravityPid == 0);
GravityProcess *p = ensureGravityProcess();
p->setGravity(grav);
p->move(xs, ys, zs);
if (do_sleep) {
Process *delayProc = new DelayProcess(0x14);
ProcId pid = Kernel::get_instance()->addProcess(delayProc);
p->waitFor(pid);
}
}
void Item::explode(int explosion_type, bool destroy_item, bool cause_damage) {
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
Process *p;
int damage_divisor = 1;
if (GAME_IS_CRUSADER) {
damage_divisor = explosion_type + 1;
if (damage_divisor == 1)
damage_divisor = 3;
else if (damage_divisor == 3)
damage_divisor = 1;
setFlag(FLG_BROKEN);
// TODO: original game puts them at cx/cy/cz, but that looks wrong..
Point3 c = getCentre();
static const int expshapes[] = {0x31C, 0x31F, 0x326, 0x320, 0x321, 0x324, 0x323, 0x325};
uint rnd = rs.getRandomNumber(UINT_MAX);
int spriteno;
// NOTE: The game does some weird 32-bit stuff to decide what
// shapenum to use. Just simplified to a random.
switch (explosion_type) {
case 0:
spriteno = expshapes[rnd % 2];
break;
case 1:
spriteno = expshapes[2 + rnd % 3];
break;
case 2:
default:
spriteno = expshapes[5 + rnd % 3];
break;
}
p = new SpriteProcess(spriteno, 0, 39, 1, 1, //!! constants
_x, _y, c.z);
} else {
p = new SpriteProcess(578, 20, 34, 1, 1, //!! constants
_x, _y, _z);
}
Kernel::get_instance()->addProcess(p);
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc) {
int sfx;
if (GAME_IS_CRUSADER) {
sfx = rs.getRandomBit() ? 28 : 108;
audioproc->stopSFX(-1, _objId);
} else {
sfx = rs.getRandomBit() ? 31 : 158;
}
audioproc->playSFX(sfx, 0x60, 0, 0);
}
Point3 ptv = getLocation();
if (destroy_item) {
destroy(); // delete self
// WARNING: we are deleted at this point
}
if (!cause_damage)
return;
if (GAME_IS_U8) {
UCList itemlist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
currentmap->areaSearch(&itemlist, script, sizeof(script), 0,
160, false, ptv.x, ptv.y); //! CHECKME: 128?
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
Item *item = getItem(itemlist.getuint16(i));
if (!item) continue;
if (getRange(*item, true) > 160) continue; // check vertical distance
Point3 pti = item->getLocation();
Direction dir = Direction_GetWorldDir(pti.x - ptv.x, pti.y - ptv.y, dirmode_8dirs); //!! CHECKME
item->receiveHit(0, dir, rs.getRandomNumberRng(6, 11),
WeaponInfo::DMG_BLUNT | WeaponInfo::DMG_FIRE);
}
} else {
Point3 pt = getLocation();
// Note: same FireType number used in both Remorse and Regret
const FireType *firetypedat = GameData::get_instance()->getFireType(4);
if (firetypedat) {
int damage = firetypedat->getRandomDamage() / damage_divisor;
firetypedat->applySplashDamageAround(pt, damage, damage_divisor, this, this);
} else {
warning("couldn't explode properly - no firetype 4 data");
}
}
}
uint16 Item::getDamageType() const {
const ShapeInfo *si = getShapeInfo();
if (si->_weaponInfo) {
return si->_weaponInfo->_damageType;
}
return 0;
}
void Item::receiveHit(uint16 other, Direction dir, int damage, uint16 type) {
if (GAME_IS_U8)
receiveHitU8(other, dir, damage, type);
else
receiveHitCru(other, dir, damage, type);
}
void Item::receiveHitU8(uint16 other, Direction dir, int damage, uint16 type) {
// first, check if the item has a 'gotHit' usecode event
if (callUsecodeEvent_gotHit(other, 0)) //!! TODO: what should the 0 be??
return;
// explosive?
if (getShapeInfo()->is_u8_explode()) {
explode(0, true); // warning: deletes this
return;
}
// breakable?
if (getFamily() == ShapeInfo::SF_BREAKABLE) {
// CHECKME: anything else?
destroy();
return;
}
if (getShapeInfo()->is_fixed() || getShapeInfo()->_weight == 0) {
// can't move
return;
}
// nothing special, so just hurl the item
// TODO: hurl item in direction, with speed depending on damage
hurl(-16 * Direction_XFactor(dir), -16 * Direction_YFactor(dir), 16, 4); //!! constants
}
void Item::receiveHitCru(uint16 other, Direction dir, int damage, uint16 type) {
damage = scaleReceivedDamageCru(damage, type);
const ShapeInfo *shapeInfo = getShapeInfo();
if (!shapeInfo)
return;
const DamageInfo *damageInfo = shapeInfo->_damageInfo;
//
// The original games seem to only call the usecode if the hit will destroy
// the item and the item has usecode for gotHit -
// but we can safely call the function even if no usecode is present.
//
// TODO: Only calling for destruction seems to break a lot of things, so
// need to look more carefully at the disasm.
//
bool callUsecode = true;// damageInfo && damageInfo->wouldDestroyItem(this, damage);
if (callUsecode)
callUsecodeEvent_gotHit(0x4000, (type << 8) | (damage & 0xff));
if (damageInfo) {
bool wasbroken = damageInfo->applyToItem(this, damage);
if (wasbroken) {
Kernel::get_instance()->killProcesses(_objId, Kernel::PROC_TYPE_ALL, true);
}
}
// Fixed items or 0 weight items can't move.
// Only damage types 3 and 4 move items in Crusader.
if (shapeInfo->is_fixed() || shapeInfo->_weight == 0 || (type != 3 && type != 4)) {
return;
}
assert((int)dir >= 0 && (int)dir < 16);
static const int hurl_x_factor[] = { 0, +1, +2, +2, +2, +2, +2, +1, 0, -1, -2, -2, -2, -2, -2, -1 };
static const int hurl_y_factor[] = { -2, -2, -2, -1, 0, +1, +2, +2, +2, +2, +2, +1, 0, -1, -2, -2 };
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
int xhurl = rs.getRandomNumberRng(10, 24);
int yhurl = rs.getRandomNumberRng(10, 24);
hurl(-xhurl * hurl_x_factor[(int)dir], -yhurl * hurl_y_factor[(int)dir], 0, 2); //!! constants
}
bool Item::canDrag() const {
const ShapeInfo *si = getShapeInfo();
if (si->is_fixed()) return false;
if (si->_weight == 0) return false;
const Actor *actor = dynamic_cast<const Actor *>(this);
if (actor) {
// living actors can't be moved
if (!actor->isDead()) return false;
}
// CHECKME: might need more checks here (weight?)
return true;
}
int Item::getThrowRange() const {
if (!canDrag()) return 0;
const Actor *avatar = getMainActor();
int range = 64 - getTotalWeight() + avatar->getStr();
if (range < 1) range = 1;
range = (range * range) / 2;
return range;
}
static bool checkLineOfSightCollisions(
const Std::list<CurrentMap::SweepItem> &collisions,
bool usingAlternatePos, ObjId item, ObjId other) {
int32 other_hit_time = 0x4000;
int32 blocked_time = 0x4000;
for (const auto &collision : collisions) {
// ignore self and other
if (collision._item == item)
continue;
if (collision._item == other && !usingAlternatePos) {
other_hit_time = collision._hitTime;
continue;
}
// only touching?
if (collision._touching)
continue;
// hit something
if (collision._blocking && collision._hitTime < blocked_time) {
blocked_time = collision._hitTime;
}
}
// 'other' must be the first item that is hit.
return (blocked_time >= other_hit_time);
}
bool Item::canReach(const Item *other, int range,
int32 otherX, int32 otherY, int32 otherZ) const {
// get location and dimensions of self and other (or their root containers)
int32 thisXd, thisYd, thisZd;
int32 otherXd, otherYd, otherZd;
int32 thisXmin, thisYmin;
int32 otherXmin, otherYmin;
bool usingAlternatePos = (otherX != 0);
Point3 pt1 = getLocationAbsolute();
other = other->getTopItem();
if (otherX == 0) {
Point3 pt2 = other->getLocationAbsolute();
otherX = pt2.x;
otherY = pt2.y;
otherZ = pt2.z;
}
getFootpadWorld(thisXd, thisYd, thisZd);
other->getFootpadWorld(otherXd, otherYd, otherZd);
thisXmin = pt1.x - thisXd;
thisYmin = pt1.y - thisYd;
otherXmin = otherX - otherXd;
otherYmin = otherY - otherYd;
// if items are further away than range in any direction, return false
if (thisXmin - otherX > range) return false;
if (otherXmin - pt1.x > range)
return false;
if (thisYmin - otherY > range) return false;
if (otherYmin - pt1.y > range)
return false;
// if not, do line of sight between origins of items
Point3 start = pt1;
Point3 end(otherX, otherY, otherZ);
int32 dims[3] = { 2, 2, 2 };
if (otherZ > pt1.z && otherZ < pt1.z + thisZd)
start.z = end.z; // bottom of other between bottom and top of this
Std::list<CurrentMap::SweepItem> collisions;
Std::list<CurrentMap::SweepItem>::iterator it;
World *world = World::get_instance();
CurrentMap *map = world->getCurrentMap();
map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
_objId, false, &collisions);
if (checkLineOfSightCollisions(collisions, usingAlternatePos,
getObjId(), other->getObjId()))
return true;
// if that fails, try line of sight between centers
start.x = pt1.x - thisXd / 2; // xy center of this
start.y = pt1.y - thisYd / 2;
start.z = pt1.z;
if (thisZd > 16)
start.z += thisZd - 8; // eye height
end.x = otherX - otherXd / 2; // xyz center of other
end.y = otherY - otherYd / 2;
end.z = otherZ + otherZd / 2;
collisions.clear();
map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
_objId, false, &collisions);
if (checkLineOfSightCollisions(collisions, usingAlternatePos,
getObjId(), other->getObjId()))
return true;
// if that fails, try line of sight between eye level and top of 2nd
end.z = otherZ + otherZd;
collisions.clear();
map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
_objId, false, &collisions);
return checkLineOfSightCollisions(collisions, usingAlternatePos,
getObjId(), other->getObjId());
}
/**
* A helper function to check if both frames are in the
* same range (inclusive) for the merge check below */
static inline bool bothInRange(uint32 x, uint32 y, uint32 lo, uint32 hi) {
return (x >= lo && x <= hi && y >= lo && y <= hi);
}
bool Item::canMergeWith(const Item *other) const {
// can't merge with self
if (other->getObjId() == getObjId()) return false;
if (other->getShape() != getShape()) return false;
int family = getFamily();
if (family == ShapeInfo::SF_QUANTITY) return true;
if (family != ShapeInfo::SF_REAGENT) return false;
uint32 frame1 = getFrame();
uint32 frame2 = other->getFrame();
if (frame1 == frame2) return true;
// special cases:
// necromancy reagents (shape 395)
// blood: frame 0-5
// bone: frame 6-7
// wood: frame 8
// dirt: frame 9
// ex.hood: frame 10-12
// blackmoor: frame 14-15
// dead man's elbow: frame 16-20
// sorcery reagents (shape 398)
// volcanic ash: frame 0-1
// pumice: frame 2-5
// obsidian: 6-9
// iron: 10-13
// brimstone: 14-17
// daemon bones: 18-20
// ether reagents (shape 399)
// only one frame per type, no special case needed
// Note: necromancy reagents are special-cased to be plural in their look()
// function, but the sorcery ones aren't, but the original game is the same.
//
if (GAME_IS_U8) {
if (getShape() == 395) {
if (bothInRange(frame1, frame2, 0, 5))
return true;
if (bothInRange(frame1, frame2, 6, 7))
return true;
if (bothInRange(frame1, frame2, 10, 12))
return true;
if (bothInRange(frame1, frame2, 14, 15))
return true;
if (bothInRange(frame1, frame2, 16, 20))
return true;
}
if (getShape() == 398) {
if (bothInRange(frame1, frame2, 0, 1))
return true;
if (bothInRange(frame1, frame2, 2, 5))
return true;
if (bothInRange(frame1, frame2, 6, 9))
return true;
if (bothInRange(frame1, frame2, 10, 13))
return true;
if (bothInRange(frame1, frame2, 14, 17))
return true;
if (bothInRange(frame1, frame2, 18, 20))
return true;
}
}
return false;
}
bool Item::isRobotCru() const {
uint32 shape = getShape();
return (shape == 0x4c8 || shape == 0x338 || shape == 0x45d ||
shape == 0x2cb || shape == 0x4e6 || shape == 899 ||
shape == 0x385);
}
int Item::scaleReceivedDamageCru(int damage, uint16 type) const {
uint8 difficulty = World::get_instance()->getGameDifficulty();
const Actor *actor = dynamic_cast<const Actor *>(this);
//
// For difficulty 1 and 2, we scale damage to others *up* and damage
// to avatar *down*.
//
if (!actor || (this != getMainActor() && this != getControlledActor())) {
if (difficulty == 1) {
damage *= 5;
} else if (difficulty == 2) {
damage *= 3;
}
} else {
if (difficulty == 1) {
damage /= 5;
} else if (difficulty == 2) {
damage /= 3;
}
}
if (isRobotCru() && (type == 1 || type == 2 || type == 0xb || type == 0xd)) {
damage /= 3;
}
damage = CLIP(damage, 1, 0xfa);
return damage;
}
void Item::saveData(Common::WriteStream *ws) {
Object::saveData(ws);
ws->writeUint16LE(static_cast<uint16>(_extendedFlags));
ws->writeUint16LE(_flags);
ws->writeUint16LE(static_cast<uint16>(_shape));
ws->writeUint16LE(static_cast<uint16>(_frame));
ws->writeUint16LE(static_cast<uint16>(_x));
ws->writeUint16LE(static_cast<uint16>(_y));
ws->writeUint16LE(static_cast<uint16>(_z));
ws->writeUint16LE(_quality);
ws->writeUint16LE(_npcNum);
ws->writeUint16LE(_mapNum);
if (getObjId() != 0xFFFF) {
// these only make sense in currently loaded items
ws->writeUint16LE(_gump);
ws->writeUint16LE(_gravityPid);
}
if ((_flags & FLG_ETHEREAL) && (_flags & (FLG_CONTAINED | FLG_EQUIPPED)))
ws->writeUint16LE(_parent);
// TODO: Consider saving this
// ws->writeUint16LE(_bark);
}
bool Item::loadData(Common::ReadStream *rs, uint32 version) {
if (!Object::loadData(rs, version)) return false;
_extendedFlags = rs->readUint16LE();
_flags = rs->readUint16LE();
_shape = rs->readUint16LE();
_frame = rs->readUint16LE();
_x = rs->readUint16LE();
_y = rs->readUint16LE();
_z = rs->readUint16LE();
_quality = rs->readUint16LE();
_npcNum = rs->readUint16LE();
_mapNum = rs->readUint16LE();
if (getObjId() != 0xFFFF) {
_gump = rs->readUint16LE();
_gravityPid = rs->readUint16LE();
} else {
_gump = _gravityPid = 0;
}
if ((_flags & FLG_ETHEREAL) && (_flags & (FLG_CONTAINED | FLG_EQUIPPED)))
_parent = rs->readUint16LE();
else
_parent = 0;
_bark = 0;
//!! hackish...
if (_extendedFlags & EXT_INCURMAP) {
World::get_instance()->getCurrentMap()->addItem(this);
}
return true;
}
uint32 Item::I_touch(const uint8 */*args*/, unsigned int /*argsize*/) {
//ARG_NULL32(); // ARG_ITEM_FROM_PTR(item);
// Guess: this is used to make sure an item is painted in the original.
// Our renderer is different, making this intrinsic unnecessary.
return 0;
}
uint32 Item::I_getX(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
return World_ToUsecodeCoord(pt.x);
}
uint32 Item::I_getY(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
return World_ToUsecodeCoord(pt.y);
}
uint32 Item::I_getZ(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
return pt.z;
}
uint32 Item::I_getCX(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
if (item->_flags & FLG_FLIPPED)
return World_ToUsecodeCoord(pt.x - item->getShapeInfo()->_y * 16);
else
return World_ToUsecodeCoord(pt.x - item->getShapeInfo()->_x * 16);
}
uint32 Item::I_getCY(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
if (item->_flags & FLG_FLIPPED)
return World_ToUsecodeCoord(pt.y - item->getShapeInfo()->_x * 16);
else
return World_ToUsecodeCoord(pt.y - item->getShapeInfo()->_y * 16);
}
uint32 Item::I_getCZ(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
return pt.z + item->getShapeInfo()->_z * 4;
}
uint32 Item::I_getPoint(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UC_PTR(ptr);
if (!item) return 0;
Point3 pt = item->getLocationAbsolute();
World_ToUsecodeXY(pt.x, pt.y);
WorldPoint point;
point.setX(pt.x);
point.setY(pt.y);
point.setZ(pt.z);
UCMachine::get_instance()->assignPointer(ptr, point._buf, 5);
return 0;
}
uint32 Item::I_getShape(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getShape();
}
uint32 Item::I_setShape(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(shape);
if (!item) return 0;
#if 0
debugC(kDebugObject, "Item::setShape: objid %04X shape (%d -> %d)",
item->getObjId(), item->getShape(), shape);
#endif
item->setShape(shape);
return 0;
}
uint32 Item::I_getFrame(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getFrame();
}
uint32 Item::I_setFrame(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(frame);
if (!item) return 0;
item->setFrame(frame);
return 0;
}
uint32 Item::I_getQuality(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
if (item->getFamily() == ShapeInfo::SF_QUALITY)
return item->getQuality();
else
return 0;
}
uint32 Item::I_getUnkEggType(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
if (item->getFamily() == ShapeInfo::SF_UNKEGG) {
if (GAME_IS_U8) {
return item->getQuality();
} else {
return item->getQuality() & 0xFF;
}
} else {
return 0;
}
}
uint32 Item::I_setUnkEggType(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(val);
if (!item) return 0;
if (item->getFamily() == ShapeInfo::SF_UNKEGG) {
item->setQuality(val);
}
return 0;
}
uint32 Item::I_getQuantity(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
if (item->getFamily() == ShapeInfo::SF_QUANTITY ||
item->getFamily() == ShapeInfo::SF_REAGENT)
return item->getQuality();
else
return 0;
}
uint32 Item::I_getContainer(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
//! What do we do if item has no _parent?
//! What do we do with equipped items?
if (item->getParent())
return item->getParent();
else
return 0;
}
uint32 Item::I_getRootContainer(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item)
return 0;
Container *root = item->getRootContainer();
//! What do we do if item has no _parent?
//! What do we do with equipped items?
if (!root)
return 0;
return root->getObjId();
}
uint32 Item::I_getQ(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getQuality();
}
uint32 Item::I_getQLo(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getQuality() & 0xFF;
}
uint32 Item::I_getQHi(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return (item->getQuality() >> 8) & 0xFF;
}
uint32 Item::I_setQ(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
item->setQuality(q);
return 0;
}
uint32 Item::I_setQLo(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
uint16 iq = item->getQuality() & 0xFF00;
item->setQuality(iq | (q & 0xFF));
return 0;
}
uint32 Item::I_setQHi(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
uint16 iq = item->getQuality() & 0x00FF;
item->setQuality(iq | ((q << 8) & 0xFF00));
return 0;
}
uint32 Item::I_setQuality(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
if (item->getFamily() != ShapeInfo::SF_GENERIC)
item->setQuality(q);
return 0;
}
uint32 Item::I_setQuantity(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
if (item->getFamily() == ShapeInfo::SF_QUANTITY ||
item->getFamily() == ShapeInfo::SF_REAGENT)
item->setQuality(q);
return 0;
}
uint32 Item::I_setQAndCombine(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(q);
if (!item) return 0;
item->setQuality(q);
item->callUsecodeEvent_combine();
return 0;
}
uint32 Item::I_getFamily(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getFamily();
}
uint32 Item::I_getTypeFlag(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(typeflag);
if (!item) return 0;
const ShapeInfo *info = item->getShapeInfo();
if (GAME_IS_U8 && typeflag >= 64)
warning("Invalid TypeFlag greater than 63 requested (%u) by Usecode", typeflag);
if (GAME_IS_CRUSADER && typeflag >= 72)
warning("Invalid TypeFlag greater than 72 requested (%u) by Usecode", typeflag);
if (info->getTypeFlag(typeflag))
return 1;
else
return 0;
}
uint32 Item::I_getStatus(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getFlags();
}
uint32 Item::I_orStatus(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(mask);
if (!item) return 0;
item->setFlag(mask);
return 0;
}
uint32 Item::I_andStatus(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(mask);
if (!item) return 0;
item->_flags &= mask;
return 0;
}
uint32 Item::I_ascend(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_SINT16(delta);
if (!item) return 0;
int dist = item->ascend(delta);
if (dist == 0x4000)
return 1;
else
return 0;
}
uint32 Item::I_getWeight(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getWeight();
}
uint32 Item::I_getWeightIncludingContents(const uint8 *args,
unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getTotalWeight();
}
uint32 Item::I_bark(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_STRING(str);
if (!item && id_item == kGuardianId) {
Actor *actor = ItemFactory::createActor(kGuardianId, 0, 0, Item::FLG_ETHEREAL | Item::FLG_IN_NPC_LIST, kGuardianId, 0, Item::EXT_PERMANENT_NPC, false);
if (!actor) {
warning("Couldn't create actor");
return 0;
}
ObjectManager::get_instance()->assignActorObjId(actor, kGuardianId);
item = actor;
}
if (!item) {
// Hack! Items should always be valid?
warning("skipping bark of '%s' because item invalid.", str.c_str());
return 0;
}
return item->bark(str);
}
uint32 Item::I_look(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->callUsecodeEvent_look();
}
uint32 Item::I_use(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->callUsecodeEvent_use();
}
uint32 Item::I_gotHit(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(hitter);
ARG_SINT16(force);
if (!item) return 0;
return item->callUsecodeEvent_gotHit(hitter, force);
}
uint32 Item::I_equip(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
// Note: The U8 version (no param) is never actually called in the usecode.
assert(argsize > 4);
ARG_UINT16(val);
return item->callUsecodeEvent_equipWithParam(val);
}
uint32 Item::I_unequip(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
// Note: The U8 version (no param) is never actually called in the usecode.
assert(argsize > 4);
ARG_UINT16(val)
return item->callUsecodeEvent_unequipWithParam(val);
}
uint32 Item::I_enterFastArea(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->enterFastArea();
}
uint32 Item::I_cast(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
ARG_UINT16(arg);
return item->callUsecodeEvent_cast(arg);
}
uint32 Item::I_avatarStoleSomething(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
// Abort if npc && dead to match original game behavior
Actor *actor = dynamic_cast<Actor *>(item);
if (actor && actor->isDead())
return 0;
ARG_UINT16(arg);
return item->callUsecodeEvent_AvatarStoleSomething(arg);
}
uint32 Item::I_ask(const uint8 *args, unsigned int /*argsize*/) {
ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // currently unused.
ARG_LIST(answers);
if (!answers) return 0;
Actor *actor = getMainActor();
if (actor)
actor->closeBark();
// Use AskGump
Gump *_gump = new AskGump(1, answers);
_gump->InitGump(0);
return _gump->GetNotifyProcess()->getPid();
}
uint32 Item::I_legalCreateAtPoint(const uint8 *args, unsigned int /*argsize*/) {
ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
ARG_UINT16(shape);
ARG_UINT16(frame);
ARG_WORLDPOINT(point);
int32 x = point.getX();
int32 y = point.getY();
int32 z = point.getZ();
World_FromUsecodeXY(x, y);
Point3 pt(x, y, z);
CurrentMap *cm = World::get_instance()->getCurrentMap();
cm->setFastAtPoint(pt);
// check if item can exist
PositionInfo info = cm->getPositionInfo(x, y, z, shape, 0);
if (!info.valid)
return 0;
Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
if (!newitem) {
warning("I_legalCreateAtPoint failed to create item (%u, %u).", shape, frame);
return 0;
}
uint16 objID = newitem->getObjId();
newitem->move(x, y, z);
uint8 buf[2];
buf[0] = static_cast<uint8>(objID);
buf[1] = static_cast<uint8>(objID >> 8);
UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
return 1;
}
uint32 Item::I_legalCreateAtCoords(const uint8 *args, unsigned int /*argsize*/) {
ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
ARG_UINT16(shape);
ARG_UINT16(frame);
ARG_UINT16(x);
ARG_UINT16(y);
ARG_UINT8(z);
World_FromUsecodeXY(x, y);
Point3 pt(x, y, z);
CurrentMap *cm = World::get_instance()->getCurrentMap();
cm->setFastAtPoint(pt);
// check if item can exist
PositionInfo info = cm->getPositionInfo(x, y, z, shape, 0);
if (!info.valid)
return 0;
// if yes, create it
Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
if (!newitem) {
warning("I_legalCreateAtCoords failed to create item (%u, %u).", shape, frame);
return 0;
}
uint16 objID = newitem->getObjId();
newitem->move(x, y, z);
uint8 buf[2];
buf[0] = static_cast<uint8>(objID);
buf[1] = static_cast<uint8>(objID >> 8);
UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
return 1;
}
uint32 Item::I_legalCreateInCont(const uint8 *args, unsigned int /*argsize*/) {
ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
ARG_UINT16(shape);
ARG_UINT16(frame);
ARG_CONTAINER_FROM_ID(container);
ARG_NULL16(); // unknown
uint8 buf[2];
buf[0] = 0;
buf[1] = 0;
UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
// Create an item and try to add it to the given container.
// If it fits, return id; otherwise return 0.
Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
if (!newitem) {
warning("I_legalCreateInCont failed to create item (%u, %u).", shape, frame);
return 0;
}
// also need to check weight, volume maybe??
if (newitem->moveToContainer(container)) {
uint16 objID = newitem->getObjId();
buf[0] = static_cast<uint8>(objID);
buf[1] = static_cast<uint8>(objID >> 8);
UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
return 1;
} else {
warning("I_legalCreateInCont failed to add item to container (%u)", container->getObjId());
// failed to add; clean up
newitem->destroy();
return 0;
}
}
uint32 Item::I_destroy(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item || item->getObjId() == kMainActorId)
return 0;
item->destroy();
return 0;
}
uint32 Item::I_getFootpadData(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UC_PTR(xptr);
ARG_UC_PTR(yptr);
ARG_UC_PTR(zptr);
if (!item) return 0;
uint8 buf[2];
int32 x, y, z;
item->getFootpadData(x, y, z);
buf[0] = static_cast<uint8>(x);
buf[1] = static_cast<uint8>(x >> 8);
UCMachine::get_instance()->assignPointer(xptr, buf, 2);
buf[0] = static_cast<uint8>(y);
buf[1] = static_cast<uint8>(y >> 8);
UCMachine::get_instance()->assignPointer(yptr, buf, 2);
buf[0] = static_cast<uint8>(z);
buf[1] = static_cast<uint8>(z >> 8);
UCMachine::get_instance()->assignPointer(zptr, buf, 2);
return 0;
}
uint32 Item::I_overlaps(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
if (item->overlaps(*item2))
return 1;
else
return 0;
}
uint32 Item::I_overlapsXY(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
if (item->overlapsxy(*item2))
return 1;
else
return 0;
}
uint32 Item::I_isOn(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
if (item->isOn(*item2))
return 1;
else
return 0;
}
uint32 Item::I_isCompletelyOn(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
if (item->isCompletelyOn(*item2))
return 1;
else
return 0;
}
uint32 Item::I_isCentreOn(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
if (item->isCentreOn(*item2))
return 1;
else
return 0;
}
uint32 Item::I_isInNpc(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item)
return 0;
const Container *container = item->getParentAsContainer();
while (container) {
const Actor *actor = dynamic_cast<const Actor *>(container);
if (actor)
return 1;
container = container->getParentAsContainer();
}
return 0;
}
uint32 Item::I_getFamilyOfType(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(shape);
return GameData::get_instance()->getMainShapes()->
getShapeInfo(shape)->_family;
}
uint32 Item::I_push(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item)
return 0;
#if 0
debugC(kDebugObject, "Pushing item to ethereal void: id: %u shp: %u, %u",
item->getObjId(), item->getShape(), item->getFrame());
#endif
item->moveToEtherealVoid();
return 0;
}
uint32 Item::I_create(const uint8 *args, unsigned int /*argsize*/) {
ARG_UC_PTR(itemptr); // need to store the item id at *itemptr (????)
ARG_UINT16(shape);
ARG_UINT16(frame);
Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
if (!newitem) {
warning("I_create failed to create item (%u, %u).", shape, frame);
return 0;
}
uint16 objID = newitem->getObjId();
#if 0
debugC(kDebugObject, "Item::create: objid %04X shape (%d, %d)",
objID, shape, frame);
#endif
newitem->moveToEtherealVoid();
uint8 buf[2];
buf[0] = static_cast<uint8>(objID);
buf[1] = static_cast<uint8>(objID >> 8);
UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
return 1;
}
uint32 Item::I_pop(const uint8 */*args*/, unsigned int /*argsize*/) {
//ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
World *w = World::get_instance();
if (w->etherealEmpty())
return 0; // no items left on stack
uint16 _objId = w->etherealPeek();
Item *item = getItem(_objId);
if (!item) {
w->etherealRemove(_objId);
return 0; // top item was invalid
}
item->returnFromEtherealVoid();
#if 0
debugC(kDebugObject, "Popping item to original location: %u, %u", item->getShape(), item->getFrame());
#endif
//! Anything else?
return _objId;
}
uint32 Item::I_popToCoords(const uint8 *args, unsigned int /*argsize*/) {
ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
ARG_UINT16(x);
ARG_UINT16(y);
ARG_UINT8(z);
World *w = World::get_instance();
if (w->etherealEmpty())
return 0; // no items left on stack
uint16 objId = w->etherealPeek();
Item *item = getItem(objId);
if (!item) {
w->etherealRemove(objId);
return 0; // top item was invalid
}
World_FromUsecodeXY(x, y);
if (GAME_IS_CRUSADER) {
// HACK!! DEATHFL::ordinal20 has a hack to add 1 to z for the death
// animation (falling into acid), but then our animation tracker
// detects a fall and stops animating. Fight hacks with hacks..
if (item->_shape == 1408 && z > 0)
z -= 1;
}
item->move(x, y, z);
#if 0
debugC(kDebugObject, "Popping item into map: %u, %u at (%d, %d, %d)",
item->getShape(), item->getFrame(), x, y, z);
#endif
//! Anything else?
return objId;
}
uint32 Item::I_popToContainer(const uint8 *args, unsigned int /*argsize*/) {
ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
ARG_ITEM_FROM_ID(citem);
World *w = World::get_instance();
if (w->etherealEmpty())
return 0; // no items left on stack
uint16 objId = w->etherealPeek();
Item *item = getItem(objId);
if (!item) {
w->etherealRemove(objId);
return 0; // top item was invalid
}
Container *container = dynamic_cast<Container *>(citem);
if (container) {
item->moveToContainer(container);
} else if (citem) {
Point3 pt = citem->getLocation();
item->move(pt);
} else {
warning("Trying to popToContainer to invalid container (%u)", id_citem);
warning("%s", item->dumpInfo().c_str());
// This object now has no home, destroy it - unless it doesn't think it's
// ethereal, in that case it is somehow there by mistake?
if (item->getFlags() & FLG_ETHEREAL) {
warning("Destroying orphaned ethereal object (%u)", objId);
item->destroy();
} else {
warning("Leaving orphaned ethereal object (%u)", objId);
w->etherealRemove(objId);
}
}
//! Anything else?
return objId;
}
uint32 Item::I_popToEnd(const uint8 *args, unsigned int /*argsize*/) {
ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
ARG_ITEM_FROM_ID(citem);
World *w = World::get_instance();
if (w->etherealEmpty())
return 0; // no items left on stack
uint16 objId = w->etherealPeek();
Item *item = getItem(objId);
if (!item) {
w->etherealRemove(objId);
return 0; // top item was invalid
}
// TODO: This should also pop to container if citem is type 7 in Crusader
Container *container = dynamic_cast<Container *>(citem);
if (container) {
item->moveToContainer(container);
} else if (citem) {
Point3 pt = citem->getLocation();
item->move(pt);
} else {
warning("Trying to popToEnd to invalid container (%u)", id_citem);
warning("%s", item->dumpInfo().c_str());
// This object now has no home, destroy it - unless it doesn't think it's
// ethereal, in that case it is somehow there by mistake?
if (item->getFlags() & FLG_ETHEREAL) {
warning("Destroying orphaned ethereal object (%u)", objId);
item->destroy();
} else {
warning("Leaving orphaned ethereal object (%u)", objId);
w->etherealRemove(objId);
}
}
//! Anything else?
//! This should probably be different from I_popToContainer, but
//! how exactly?
return objId;
}
uint32 Item::I_move(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(x);
ARG_UINT16(y);
ARG_UINT8(z);
if (!item)
return 0;
//! What should this do to ethereal items?
World_FromUsecodeXY(x, y);
#if 0
debugC(kDebugObject, "Moving item: %u, %u to (%d, %d, %d)",
item->getShape(), item->getFrame(), x, y, z);
#endif
item->move(x, y, z);
return 0;
}
uint32 Item::I_legalMoveToPoint(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
ARG_WORLDPOINT(point);
ARG_UINT16(abort_if_blocked); // 0/1
ARG_NULL16(); // always 0 in usecode. Used as sweep flags in disasm.
int32 x = point.getX();
int32 y = point.getY();
int32 z = point.getZ();
World_FromUsecodeXY(x, y);
if (!item)
return 0;
//
// Return value is 1 when there are no blockers.
// If there are blockers, do partial move unless abort_if_blocked is set.
//
int retval = 1;
Std::list<CurrentMap::SweepItem> collisions;
Point3 start = item->getLocation();
Point3 end(x, y, z);
int32 dims[3];
item->getFootpadWorld(dims[0], dims[1], dims[2]);
CurrentMap *map = World::get_instance()->getCurrentMap();
map->sweepTest(start, end, dims, item->getShapeInfo()->_flags,
item->getObjId(), true, &collisions);
for (const auto &collision : collisions) {
if (collision._blocking && !collision._touching && collision._endTime > 0) {
if (abort_if_blocked)
return 0;
retval = 0;
break;
}
}
item->collideMove(x, y, z, false, false);
return retval;
}
uint32 Item::I_legalMoveToContainer(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_CONTAINER_FROM_PTR(container);
ARG_NULL16(); // always 0
if (!item || !container) return 0; // shouldn't happen?
// try to move item to container checking weight and volume
return item->moveToContainer(container, true);
}
uint32 Item::I_getEtherealTop(const uint8 * /*args*/, unsigned int /*argsize*/) {
World *w = World::get_instance();
if (w->etherealEmpty()) return 0; // no items left on stack
return w->etherealPeek();
}
//!!! is this correct?
uint32 Item::I_getMapArray(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getMapNum();
}
//!!! is this correct?
uint32 Item::I_setMapArray(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(mapNum);
if (!item) return 0;
item->setMapNum(mapNum);
return 0;
}
uint32 Item::I_getNpcNum(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->getNpcNum();
}
uint32 Item::I_setNpcNum(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(npcNum);
if (!item) return 0;
item->setNpcNum(npcNum);
return 0;
}
uint32 Item::I_getDirToCoords(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(x);
ARG_UINT16(y);
if (!item) return 0;
World_FromUsecodeXY(x, y);
Point3 pt = item->getLocationAbsolute();
return Direction_ToUsecodeDir(Direction_GetWorldDir(y - pt.y, x - pt.x, dirmode_8dirs));
}
uint32 Item::I_getDirFromCoords(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(x);
ARG_UINT16(y);
if (!item) return 0;
World_FromUsecodeXY(x, y);
Point3 pt = item->getLocationAbsolute();
return Direction_ToUsecodeDir(Direction_GetWorldDir(pt.y - y, pt.x - x, dirmode_8dirs));
}
uint32 Item::I_getDirToItem(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
Point3 pt1 = item->getLocationAbsolute();
Point3 pt2 = item2->getLocationAbsolute();
return Direction_ToUsecodeDir(Direction_GetWorldDir(pt2.y - pt1.y, pt2.x - pt1.x, dirmode_8dirs));
}
uint32 Item::I_getDirFromItem(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(item2);
if (!item) return 0;
if (!item2) return 0;
Point3 pt1 = item->getLocationAbsolute();
Point3 pt2 = item2->getLocationAbsolute();
return Direction_ToUsecodeDir(Direction_Invert(Direction_GetWorldDir(pt2.y - pt1.y, pt2.x - pt1.x, dirmode_8dirs)));
}
uint32 Item::I_getDirFromTo16(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(x1);
ARG_UINT16(y1);
ARG_UINT16(x2);
ARG_UINT16(y2);
if (x1 == x2 && y1 == y2)
return 16;
return Direction_ToUsecodeDir(Direction_GetWorldDir(y2 - y1, x2 - x1, dirmode_16dirs));
}
uint32 Item::I_getClosestDirectionInRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(x1);
ARG_UINT16(y1);
ARG_UINT16(x2);
ARG_UINT16(y2);
ARG_UINT16(ndirs);
ARG_UINT16(mind);
ARG_UINT16(maxd);
Direction mindir = Direction_FromUsecodeDir(mind);
Direction maxdir = Direction_FromUsecodeDir(maxd);
DirectionMode mode = (ndirs == 16 ? dirmode_16dirs : dirmode_8dirs);
Direction result = Direction_GetWorldDirInRange(y2 - y1, x2 - x1, mode, mindir, maxdir);
return Direction_ToUsecodeDir(result);
}
uint32 Item::I_hurl(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_SINT16(xs);
ARG_SINT16(ys);
ARG_SINT16(zs);
ARG_SINT16(grav);
if (!item) return 0;
item->hurl(xs, ys, zs, grav);
return item->_gravityPid;
}
uint32 Item::I_shoot(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_WORLDPOINT(point);
ARG_UINT16(speed); // either 0x20 (fish) or 0x40 (death disk, dart)
ARG_UINT16(gravity); // either 2 (fish) or 1 (death disk, dart)
if (!item) return 0;
MissileTracker tracker(item, 0, point.getX(), point.getY(), point.getZ(),
speed, gravity);
tracker.launchItem();
return 0;
}
uint32 Item::I_fall(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
item->fall();
return 0;
}
uint32 Item::I_grab(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
item->grab();
return 0;
}
uint32 Item::I_getSliderInput(const uint8 *args, unsigned int /*argsize*/) {
ARG_NULL32(); // ARG_ITEM_FROM_PTR(item);
ARG_SINT16(minval);
ARG_SINT16(maxval);
ARG_SINT16(step);
UCProcess *current = dynamic_cast<UCProcess *>(Kernel::get_instance()->getRunningProcess());
assert(current);
// debugC(kDebugObject, "SliderGump: min=%d, max=%d, step=%d", minval, maxval, step);
SliderGump *_gump = new SliderGump(100, 100, minval, maxval, minval, step);
_gump->InitGump(0); // modal _gump
_gump->setUsecodeNotify(current);
current->suspend();
return 0;
}
uint32 Item::I_openGump(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(gumpshape);
if (!item) return 0;
item->openGump(gumpshape);
return 0;
}
uint32 Item::I_closeGump(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
item->closeGump();
return 0;
}
uint32 Item::I_guardianBark(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(num);
if (!item) return 0;
assert(GAME_IS_U8);
return item->callUsecodeEvent_guardianBark(num);
}
uint32 Item::I_getSurfaceWeight(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
UCList uclist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE);
World *world = World::get_instance();
world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
item, true, false, true);
uint32 weight = 0;
for (uint32 i = 0; i < uclist.getSize(); i++) {
Item *other = getItem(uclist.getuint16(i));
if (!other) continue;
weight += other->getTotalWeight();
}
return weight;
}
uint32 Item::I_isExplosive(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
assert(GAME_IS_U8); // explode bit has different meaning in Cru.
if (!item) return 0;
return item->getShapeInfo()->is_u8_explode() ? 1 : 0;
}
uint32 Item::I_receiveHit(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(other);
ARG_SINT16(dir);
ARG_SINT16(damage); // force of the hit
ARG_UINT16(type); // hit type
if (!item) return 0;
item->receiveHit(other, Direction_FromUsecodeDir(dir), damage, type);
return 0;
}
uint32 Item::I_explode(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
int exptype = 0;
bool destroy_item = true;
if (argsize > 4) {
ARG_UINT16(etype)
ARG_UINT16(destroy)
exptype = etype;
destroy_item = (destroy != 0);
}
item->explode(exptype, destroy_item);
return 0;
}
uint32 Item::I_igniteChaos(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(x);
ARG_UINT16(y);
ARG_NULL8(); // z, unused
assert(GAME_IS_U8);
UCList itemlist(2);
LOOPSCRIPT(script, LS_SHAPE_EQUAL(592)); // all oilflasks (CONSTANT!)
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
currentmap->areaSearch(&itemlist, script, sizeof(script), 0,
160, false, x, y); //! CHECKME: 160?
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
Item *item = getItem(itemlist.getuint16(i));
if (!item) continue;
item->use();
}
return 0;
}
uint32 Item::I_canReach(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(other);
ARG_SINT16(range);
// TODO: add cheat to make this always return 1
if (item && other && item->canReach(other, range))
return 1;
else
return 0;
}
uint32 Item::I_getRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(other);
if (!item) return 0;
if (!other) return 0;
assert(GAME_IS_U8);
return item->getRange(*other);
}
uint32 Item::I_getRangeIfVisible(const uint8 *args, unsigned int /*argsize*/) {
// TODO: This is not exactly the same as the implementation in Cruasder,
// but it should work?
ARG_ITEM_FROM_PTR(item);
ARG_ITEM_FROM_ID(other);
if (!item || !other)
return 0;
// Somewhat arbitrary maths in here to replicate Crusader behavior.
int range = item->getRangeIfVisible(*other) / 32;
if ((range & 0xf) != 0)
range++;
if (range <= 48) {
return range;
}
return 0;
}
uint32 Item::I_isCrusTypeNPC(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(sh);
if (sh == 0x7FE) return 1;
const ShapeInfo *info;
info = GameData::get_instance()->getMainShapes()->getShapeInfo(sh);
if (!info) return 0;
if (info->_flags & ShapeInfo::SI_CRU_NPC)
return 1;
else
return 0;
}
uint32 Item::I_setBroken(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item)
return 0;
World::get_instance()->getCurrentMap()->removeTargetItem(item);
item->setFlag(FLG_BROKEN);
return 0;
}
uint32 Item::I_inFastArea(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item)
return 0;
return item->hasFlags(FLG_FASTAREA);
}
uint32 Item::I_isPartlyOnScreen(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
if (!item) return 0;
return item->isPartlyOnScreen();
}
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(findtarget);
if (!item) return 0;
World_FromUsecodeXY(x, y);
return item->fireWeapon(x, y, z, Direction_FromUsecodeDir(dir), firetype, findtarget != 0);
}
uint32 Item::I_fireDistance(const uint8 *args, unsigned int /*argsize*/) {
ARG_ITEM_FROM_PTR(item);
ARG_UINT16(other);
ARG_SINT16(dir);
ARG_SINT16(xoff);
ARG_SINT16(yoff);
ARG_SINT16(zoff);
Item *otheritem = getItem(other);
if (!item || !otheritem) return 0;
World_FromUsecodeXY(xoff, yoff);
return item->fireDistance(otheritem, Direction_FromUsecodeDir(dir), xoff, yoff, zoff);
}
} // End of namespace Ultima8
} // End of namespace Ultima