/* 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/>.
 *
 */

#ifndef ULTIMA8_WORLD_ITEM_H
#define ULTIMA8_WORLD_ITEM_H

#include "ultima/ultima8/kernel/object.h"
#include "ultima/ultima8/gfx/shape_info.h"

#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/misc/box.h"
#include "ultima/ultima8/misc/point3.h"
#include "ultima/ultima8/misc/direction.h"

namespace Ultima {
namespace Ultima8 {

class Container;
class ShapeInfo;
class Shape;
class Gump;
class GravityProcess;

class Item : public Object {
	friend class ItemFactory;

public:
	Item();
	~Item() override;

	ENABLE_RUNTIME_CLASSTYPE()

	//! Get the Container this Item is in, if any. (0 if not in a Container)
	ObjId getParent() const {
		return _parent;
	}

	//! Set the parent container of this item.
	void setParent(ObjId p) {
		_parent = p;
	}

	//! Get the Container this Item is in, if any. (NULL if not in a Container)
	Container *getParentAsContainer() const;

	//! Get the top-most Container this Item is in, if any. (NULL if not in a Container)
	Container *getRootContainer() const;

	//! Get the top-most Container this Item is in, or the Item itself if not
	//! in a container
	const Item *getTopItem() const;

	//! Set item location. This strictly sets the location, and does not
	//! even update CurrentMap
	void setLocation(int32 x, int32 y, int32 z); // this only sets the loc.
	void setLocation(const Point3 &pt); // this only sets the loc.

	//! Move an item. This moves an item to the new location, and updates
	//! CurrentMap and fastArea if necessary.
	virtual void move(int32 x, int32 y, int32 z);

	//! Move, but with a point struct.
	void move(const Point3 &pt);

	//! Move an item. This moves an item to a container and  updates
	//! CurrentMap and fastArea if necessary.
	//! \param container The container this item should be placed in
	//! \return true if item was moved, false if failed
	bool moveToContainer(Container *container, bool checkwghtvol = false);

	//! Move an item to the Ethereal Void
	void moveToEtherealVoid();

	//! Move an item out of the Ethereal Void to where it originally was
	void returnFromEtherealVoid();

	//! Check if moving this item is stealing; call AvatarStoleSomething if so
	void movedByPlayer();

	//! Get the location of the top-most container this Item is in, or
	//! this Item's location if not in a container.
	Point3 getLocationAbsolute() const;

	//! Get this Item's location. Note that this does not return
	//! 'usable' coordinates if the Item is contained or equipped.
	inline Point3 getLocation() const {
		return Point3(_x, _y, _z);
	}

	//! Get this Item's Z coordinate.
	int32 getZ() const;

	//! Set this Item's Z coordinate
	void setZ(int32 z) {
		_z = z;
	}

	//! Get this Item's location in a ContainerGump. Undefined if the Item
	//! is not in a Container.
	void getGumpLocation(int32 &x, int32 &y) const;

	//! Set the Item's location in a ContainerGump. NOP if the Item
	//! is not in a Container.
	void setGumpLocation(int32 x, int32 y);

	//! Randomize the Item's location in a ContainerGump. Effectively
	//! this sets the coordinates to (255,255) and lets the ContainerGump
	//! randomize the position when it is next opened.
	void randomGumpLocation();

	//! Get the world coordinates of the Item's centre. Undefined if the Item
	//! is contained or equipped.
	Point3 getCentre() const;

	//! Get the size of this item's 3D bounding box, in world coordinates.
	inline void getFootpadWorld(int32 &x, int32 &y, int32 &z) const;

	//! Get the size of this item's 3D bounding box, scaled as in the datafiles
	//! (i.e., the dimensions are not in the same unit as world coordinates!)
	inline void getFootpadData(int32 &x, int32 &y, int32 &z) const;

	//! Get the Box this item occupies in the world. Undef if item is contained
	Box getWorldBox() const;

	//! Get all flags
	inline uint16 getFlags() const {
		return _flags;
	}

	//! Does this item have any of the given flags mask set
	inline bool hasFlags(uint16 flags) const {
		return (_flags & flags) != 0;
	}

	//! Set the flags set in the given mask.
	void setFlag(uint32 mask) {
		_flags |= mask;
	}

	virtual void setFlagRecursively(uint32 mask) {
		setFlag(mask);
	}

	//! Clear the flags set in the given mask.
	void clearFlag(uint32 mask) {
		_flags &= ~mask;
	}

	//! Set _extendedFlags
	void setExtFlags(uint32 f) {
		_extendedFlags = f;
	}

	//! Get _extendedFlags
	inline uint32 getExtFlags() const {
		return _extendedFlags;
	}

	//! Does item have any of the given extended flags
	inline bool hasExtFlags(uint32 flags) const {
		return (_extendedFlags & flags) != 0;
	}

	//! Set the _extendedFlags set in the given mask.
	void setExtFlag(uint32 mask) {
		_extendedFlags |= mask;
	}

	//! Clear the _extendedFlags set in the given mask.
	void clearExtFlag(uint32 mask) {
		_extendedFlags &= ~mask;
	}

	//! Get this Item's shape number
	uint32 getShape() const {
		return _shape;
	}

	//! Set this Item's shape number
	void setShape(uint32 shape);

	//! Get this Item's frame number
	uint32 getFrame() const {
		return _frame;
	}

	//! Set this Item's frame number
	void setFrame(uint32 frame) {
		_frame = frame;
	}

	//! Get this Item's quality (a.k.a. 'Q')
	uint16 getQuality() const {
		return _quality;
	}

	//! Set this Item's quality (a.k.a 'Q');
	void setQuality(uint16 quality) {
		_quality = quality;
	}

	//! Get the 'NpcNum' of this Item. Note that this can represent various
	//! things depending on the family of this Item.
	uint16 getNpcNum() const {
		return _npcNum;
	}

	//! Set the 'NpcNum' of this Item. Note that this can represent various
	//! things depending on the family of this Item.
	void setNpcNum(uint16 npcnum) {
		_npcNum = npcnum;
	}

	//! Get the 'MapNum' of this Item. Note that this can represent various
	//! things depending on the family of this Item.
	uint16 getMapNum() const {
		return _mapNum;
	}

	//! Set the 'MapNum' of this Item. Note that this can represent various
	//! things depending on the family of this Item.
	void setMapNum(uint16 mapnum) {
		_mapNum = mapnum;
	}

	//! Get the ShapeInfo object for this Item. (The pointer will be cached.)
	inline const ShapeInfo *getShapeInfo() const;

	//! Get the ShapeInfo object for this Item from the game instance.
	virtual const ShapeInfo *getShapeInfoFromGameInstance() const;

	//! Get the Shape object for this Item. (The pointer will be cached.)
	const Shape *getShapeObject() const;

	//! Get the family of the shape number of this Item. (This is a
	//! member of the ShapeInfo object.)
	uint16 getFamily() const;

	//! Check if we can merge with another item.
	bool canMergeWith(const Item *other) const;

	//! Get the open ContainerGump for this Item, if any. (NULL if not open.)
	ObjId getGump() const {
		return _gump;
	}
	//! Call this to notify the Item's open Gump has closed.
	void clearGump(); // set gump to 0 and clear the GUMP_OPEN flag
	//! Open a gump with the given shape for this Item
	ObjId openGump(uint32 gumpshape);
	//! Close this Item's gump, if any
	void closeGump();

	ProcId bark(const Std::string &msg, ObjId id = 0);
	//! Call this to notify the Item's open bark has closed.
	void clearBark(); // set bark to 0
	//! Close this Item's bark, if any
	void closeBark();

	//! Destroy self.
	virtual void destroy(bool delnow = false);

	//! Check if this item overlaps another item in 3D world-space
	bool overlaps(const Item &item2) const;

	//! Check if this item overlaps another item in the xy dims in 3D space
	bool overlapsxy(const Item &item2) const;

	//! Check if this item is on top of another item
	bool isOn(const Item &item2) const;

	//! Check if this item is on completely on top of another item
	bool isCompletelyOn(const Item &item2) const;

	//! Check if the centre of this item is on top of another item
	bool isCentreOn(const Item &item2) const;

	//! Check if the item is currently entirely visible on screen
	bool isOnScreen() const;

	//! Check if the item is currently partly visible on screen
	bool isPartlyOnScreen() const;

	//! Check if this item can exist at the given coordinates
	bool canExistAt(int32 x, int32 y, int32 z, bool needsupport = false) const;
	bool canExistAt(const Point3 &pt, bool needsupport = false) const;

	//! Get direction from centre to another item's centre.
	//! Undefined if either item is contained or equipped.
	Direction getDirToItemCentre(const Item &item2) const;

	//! Same as above, but from a fixed point.
	Direction getDirToItemCentre(const Point3 &pt) const;

	//! get 'distance' to other item. This is the maximum of the differences
	//! between the x, y (and possibly z) coordinates of the items.
	int getRange(const Item &item2, bool checkz = false) const;

	//! get 'distance' to other item if it's visible (ie, there's nothing blocking the path)
	int getRangeIfVisible(const Item &item2) const;

	//! Check if this item can reach another item. (This includes LoS.)
	//! \param other item to be reached
	//! \param range range
	//! \param x x coordinate of other to use, If zero, use real coords.
	//! \param y y coordinate of other to use
	//! \param z z coordinate of other to use.
	bool canReach(const Item *other, int range, int32 x = 0, int32 y = 0, int32 z = 0) const;

	//! Move the object to (x,y,z) colliding with objects in the way.
	//! \param teleport move without colliding with objects between source and
	//!        destination
	//! \param force force the object to get to the destination without being
	//!        blocked by solid objects
	//! \param hititem if non-NULL, this is set to (one of) the item(s)
	//!        blocking the movement, or to zero if nothing blocked it
	//! \param dirs if non-NULL, this is set to a bitmask of the x/y/z
	//         directions in which movement was blocked (bit 0=x,1=y,2=z)
	//! \returns 0-0x4000 representing how far it got.
	//!          0 = didn't move
	//!          0x4000 = reached destination
	//! \note This can destroy the object
	virtual int32 collideMove(int32 x, int32 y, int32 z, bool teleport, bool force,
	                  ObjId *hititem = 0, uint8 *dirs = 0);

	//! Make the item move up (delta>0) or down (delta<0),
	//! including any items on top of it
	//! \param delta distance in Z-direction to move
	//! \returns 0-0x4000 representing how far it got.
	//!          0 = didn't move
	//!          0x4000 = reached destination
	int32 ascend(int delta);

	//! Make the item fall down.
	//! This creates a GravityProcess to do the actual work if the Item
	//! doesn't already have one.
	void fall();

	//! Make any items on top of this Item fall down and notify any supporting
	//! items that we're gone by calling the 'release' event.
	//! Note that this Item has to be moved away right after calling grab(),
	//! since otherwise the items will immediately hit this Item again.
	void grab();

	//! Hurl the item in the given direction
	void hurl(int xs, int ys, int zs, int grav);

	//! Set the PID of the GravityProcess for this Item.  There should be only one.
	void setGravityPID(ProcId pid) {
		assert(_gravityPid == 0 || pid == 0);
		_gravityPid = pid;
	}

	//! Get the PID of the GravityProcess for this Item (or 0)
	ProcId getGravityPID() const {
		return _gravityPid;
	}

	//! Get the GravityProcess of this Item, creating it if necessary
	virtual GravityProcess *ensureGravityProcess();

	//! Get the weight of this Item
	virtual uint32 getWeight() const;

	//! Get the weight of this Item and its contents, if any
	virtual uint32 getTotalWeight() const;

	//! Get the volume this item takes up in a container
	virtual uint32 getVolume() const;

	//! explode with explosion type (0,1,2), whether to destroy the item,
	//! and whether to cause splash damage.
	void explode(int explosion_type, bool destroy_item, bool cause_damage = true);

	//! get the damage type this object does when hitting something
	virtual uint16 getDamageType() const;

	//! receive a hit
	//! \param other The item delivering the hit
	//! \param dir The direction the hit is coming from (or inverse? CHECKME!)
	//! \param damage The force of the hit. Zero for default
	//! \param type The type of damage done. Zero for default
	virtual void receiveHit(ObjId other, Direction dir, int damage, uint16 type);

	//! fire the given weapon type in the given direction from location x, y, z.
	uint16 fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype, bool findtarget);

	//! get the distance (in map tiles) if we were to fire in this direction to "other"
	//! and could hit, otherwise return 0.
	uint16 fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) const;

	//! get damage points, used in Crusader for item damage.
	uint8 getDamagePoints() const {
		return _damagePoints;
	}

	//! set damage points, used in Crusader for item damage.
	void setDamagePoints(uint8 points) {
		_damagePoints = points;
	}

	//! Get the right Z which an attacker should aim for, given the attacker's z.
	//! (Crusader only)
	int32 getTargetZRelativeToAttackerZ(int32 attackerz) const;

	//! count nearby objects of a given shape
	unsigned int countNearby(uint32 shape, uint16 range) const;

	//! can this item be dragged?
	bool canDrag() const;

	//! how far can this item be thrown?
	//! \return range, or 0 if item can't be thrown
	int getThrowRange() const;

	//! Check this Item against the given loopscript
	//! \param script The loopscript to run
	//! \param scriptsize The size (in bytes) of the loopscript
	//! \return true if the item matches, false otherwise
	bool checkLoopScript(const uint8 *script, uint32 scriptsize) const;

	uint32 callUsecodeEvent_look();                             // event 0
	uint32 callUsecodeEvent_use();                              // event 1
	uint32 callUsecodeEvent_anim();                             // event 2
	uint32 callUsecodeEvent_cachein();                          // event 4
	uint32 callUsecodeEvent_hit(ObjId hitted, int16 hitforce);  // event 5
	uint32 callUsecodeEvent_gotHit(ObjId hitter, int16 hitforce);// event 6
	uint32 callUsecodeEvent_hatch();                            // event 7
	uint32 callUsecodeEvent_schedule(uint32 time);              // event 8
	uint32 callUsecodeEvent_release();                          // event 9
	uint32 callUsecodeEvent_equip();                            // event A
	uint32 callUsecodeEvent_equipWithParam(ObjId param);        // event A
	uint32 callUsecodeEvent_unequip();                          // event B
	uint32 callUsecodeEvent_unequipWithParam(ObjId param);      // event B
	uint32 callUsecodeEvent_combine();                          // event C
	uint32 callUsecodeEvent_calledFromAnim();                   // event E
	uint32 callUsecodeEvent_enterFastArea();                    // event F
	uint32 callUsecodeEvent_leaveFastArea();                    // event 10
	uint32 callUsecodeEvent_cast(uint16 unk);                   // event 11
	uint32 callUsecodeEvent_justMoved();                        // event 12
	uint32 callUsecodeEvent_AvatarStoleSomething(uint16 unk);   // event 14
	uint32 callUsecodeEvent_guardianBark(int16 unk);            // event 15 (Ultima)
	uint32 callUsecodeEvent_unhatch();							// event 15 (Crusader)

	uint32 use();

	//! Get lerped location.
	inline Point3 getLerped() const {
		return Point3(_ix, _iy, _iz);
	}

	//! Do lerping for an in between frame (0-256)
	//! The result can be retrieved with getLerped(x,y,z)
	//! \param factor The lerp factor: 0 is start of move, 256 is end of move
	inline void doLerp(int32 factor) {
		// Should be noted that this does indeed limit us to 'only' 24bit coords
		// not that it matters because on disk they are unsigned 16 bit

		if (factor == 256) {
			_ix = _lNext._x;
			_iy = _lNext._y;
			_iz = _lNext._z;
		} else if (factor == 0) {
			_ix = _lPrev._x;
			_iy = _lPrev._y;
			_iz = _lPrev._z;
		} else {
#if 1
			// This way while possibly slower is more accurate
			_ix = ((_lPrev._x * (256 - factor) + _lNext._x * factor) >> 8);
			_iy = ((_lPrev._y * (256 - factor) + _lNext._y * factor) >> 8);
			_iz = ((_lPrev._z * (256 - factor) + _lNext._z * factor) >> 8);
#else
			_ix = _lPrev.x + (((_lNext.x - _lPrev.x) * factor) >> 8);
			_iy = _lPrev.y + (((_lNext.y - _lPrev.y) * factor) >> 8);
			_iz = _lPrev.z + (((_lNext.z - _lPrev.z) * factor) >> 8);
#endif
		}
	}

	//! Setup the lerped info for this gametick and animate the item
	void setupLerp(int32 gametick);

	//! The item has entered the fast area
	virtual uint32 enterFastArea();

	//! The item has left the fast area
	//! \note This can destroy the object
	virtual void leaveFastArea();

	//! dump some info about this item to a string
	Common::String dumpInfo() const override;

	bool loadData(Common::ReadStream *rs, uint32 version);
	void saveData(Common::WriteStream *ws) override;

	// Intrinsics
	INTRINSIC(I_touch);
	INTRINSIC(I_getX);
	INTRINSIC(I_getY);
	INTRINSIC(I_getZ);
	INTRINSIC(I_getCX);
	INTRINSIC(I_getCY);
	INTRINSIC(I_getCZ);
	INTRINSIC(I_getPoint);
	INTRINSIC(I_getShape);
	INTRINSIC(I_setShape);
	INTRINSIC(I_getFrame);
	INTRINSIC(I_setFrame);
	INTRINSIC(I_getQuality);
	INTRINSIC(I_getUnkEggType);
	INTRINSIC(I_setUnkEggType);
	INTRINSIC(I_getQuantity);
	INTRINSIC(I_getContainer);
	INTRINSIC(I_getRootContainer);
	INTRINSIC(I_getQ);
	INTRINSIC(I_getQHi);
	INTRINSIC(I_getQLo);
	INTRINSIC(I_setQ);
	INTRINSIC(I_setQHi);
	INTRINSIC(I_setQLo);
	INTRINSIC(I_setQuality);
	INTRINSIC(I_setQuantity);
	INTRINSIC(I_setQAndCombine);
	INTRINSIC(I_getFamily);
	INTRINSIC(I_getTypeFlag);
	INTRINSIC(I_getStatus);
	INTRINSIC(I_orStatus);
	INTRINSIC(I_andStatus);
	INTRINSIC(I_getFootpadData);
	INTRINSIC(I_overlaps);
	INTRINSIC(I_overlapsXY);
	INTRINSIC(I_isOn);
	INTRINSIC(I_isCompletelyOn);
	INTRINSIC(I_isCentreOn);
	INTRINSIC(I_isInNpc);
	INTRINSIC(I_ascend);
	INTRINSIC(I_getWeight);
	INTRINSIC(I_getWeightIncludingContents);
	INTRINSIC(I_getVolume);
	INTRINSIC(I_bark);
	INTRINSIC(I_getMapArray);
	INTRINSIC(I_setMapArray);
	INTRINSIC(I_getNpcNum);
	INTRINSIC(I_setNpcNum);
	INTRINSIC(I_getDirToCoords);
	INTRINSIC(I_getDirFromCoords);
	INTRINSIC(I_getDirToItem);
	INTRINSIC(I_getDirFromItem);
	INTRINSIC(I_getDirFromTo16);
	INTRINSIC(I_getClosestDirectionInRange);
	INTRINSIC(I_look);
	INTRINSIC(I_use);
	INTRINSIC(I_gotHit);
	INTRINSIC(I_enterFastArea);
	INTRINSIC(I_cast);
	INTRINSIC(I_ask);
	INTRINSIC(I_getSliderInput);
	INTRINSIC(I_openGump);
	INTRINSIC(I_closeGump);
	INTRINSIC(I_create);
	INTRINSIC(I_legalCreateAtPoint);
	INTRINSIC(I_legalCreateAtCoords);
	INTRINSIC(I_legalCreateInCont);
	INTRINSIC(I_push);
	INTRINSIC(I_pop);
	INTRINSIC(I_popToCoords);
	INTRINSIC(I_popToContainer);
	INTRINSIC(I_popToEnd);
	INTRINSIC(I_destroy);
	INTRINSIC(I_move);
	INTRINSIC(I_legalMoveToPoint);
	INTRINSIC(I_legalMoveToContainer);
	INTRINSIC(I_hurl);
	INTRINSIC(I_shoot);
	INTRINSIC(I_fall);
	INTRINSIC(I_grab);
	INTRINSIC(I_igniteChaos);
	INTRINSIC(I_getFamilyOfType);
	INTRINSIC(I_getEtherealTop);
	INTRINSIC(I_guardianBark);
	INTRINSIC(I_getSurfaceWeight);
	INTRINSIC(I_isExplosive);
	INTRINSIC(I_receiveHit);
	INTRINSIC(I_explode);
	INTRINSIC(I_canReach);
	INTRINSIC(I_getRange);
	INTRINSIC(I_getRangeIfVisible);
	INTRINSIC(I_isCrusTypeNPC);
	INTRINSIC(I_setBroken);
	INTRINSIC(I_inFastArea);
	INTRINSIC(I_equip);
	INTRINSIC(I_unequip);
	INTRINSIC(I_avatarStoleSomething);
	INTRINSIC(I_isPartlyOnScreen);
	INTRINSIC(I_fireWeapon);
	INTRINSIC(I_fireDistance);

private:
	uint32 _shape;   // DO NOT modify this directly! Always use setShape()!

protected:
	uint32 _frame;

	int32 _x, _y, _z; // world coordinates
	uint16 _flags;
	uint16 _quality;
	uint16 _npcNum;
	uint16 _mapNum;

	uint32 _extendedFlags; // pentagram's own flags

	ObjId _parent; // objid container this item is in (or 0 for top-level items)

	mutable const Shape *_cachedShape;
	mutable const ShapeInfo *_cachedShapeInfo;

	// This is stuff that is used for displaying and interpolation
	struct Lerped {
		Lerped() : _x(0), _y(0), _z(0), _shape(0), _frame(0) {};
		int32 _x, _y, _z;
		uint32 _shape, _frame;
	};

	Lerped  _lPrev;         // Previous state (relative to camera)
	Lerped  _lNext;         // Next (current) state (relative to camera)
	int32   _ix, _iy, _iz;  // Interpolated position in camera space

	ObjId _gump;             // Item's container gump
	ObjId _bark;             // Item's bark gump
	ProcId _gravityPid;      // Item's GravityTracker (or 0)

	uint8 _damagePoints;	// Damage points, used for item damage in Crusader

	//! True if this is a Robot shape (in a fixed list)
	bool isRobotCru() const;

	//! Scale a received damage value based on the current difficulty level
	//! and the type of object this is.
	int scaleReceivedDamageCru(int damage, uint16 type) const;

private:

	//! Call a Usecode Event. Use the separate functions instead!
	uint32 callUsecodeEvent(uint32 event, const uint8 *args = 0, int argsize = 0);

	//! The gametick setupLerp was last called on
	int32 _lastSetup;

	//! Animate the item (called by setupLerp)
	void animateItem();

	//! The U8 version of receiveHit
	void receiveHitU8(ObjId other, Direction dir, int damage, uint16 type);

	//! The Crusader version of receiveHit
	void receiveHitCru(ObjId other, Direction dir, int damage, uint16 type);

public:
	enum statusflags {
		FLG_DISPOSABLE   = 0x0002,  //!< Item is discarded on map change
		FLG_OWNED        = 0x0004,  //!< Item is owned by avatar
		FLG_CONTAINED    = 0x0008,  //!< Item is in a container
		FLG_INVISIBLE    = 0x0010,  //!< Item is invisible
		FLG_FLIPPED      = 0x0020,  //!< Item is flipped horizontally
		FLG_IN_NPC_LIST  = 0x0040,  //!< Item is a NPC
		FLG_FAST_ONLY    = 0x0080,  //!< Item is discarded when leaving fast area
		FLG_GUMP_OPEN    = 0x0100,  //!< Item has a gump open
		FLG_EQUIPPED     = 0x0200,  //!< Item is equipped
		FLG_BOUNCING     = 0x0400,  //!< Item has bounced
		FLG_ETHEREAL     = 0x0800,  //!< Item is in the ethereal list - confirmed same meaning in crusader
		FLG_HANGING      = 0x1000,  //!< Item is suspended in the air
		FLG_FASTAREA     = 0x2000,  //!< Item is in the fast area
		FLG_LOW_FRICTION = 0x4000,  //!< Item has low friction
		FLG_BROKEN       = 0x8000   //!< Item is broken - Crusader only - broken items are not targetable.
	};

	enum extflags {
		EXT_FIXED        = 0x0001,  //!< Item came from FIXED
		EXT_INCURMAP     = 0x0002,  //!< Item is in a CurrentMap display list
		EXT_LERP_NOPREV  = 0x0008,  //!< Item can't be lerped this frame
		EXT_HIGHLIGHT    = 0x0010,  //!< Item should be Painted highlighted
		EXT_CAMERA       = 0x0020,  //!< Item is being followed by the camera
		EXT_SPRITE       = 0x0040,  //!< Item is a sprite
		EXT_TRANSPARENT  = 0x0080,  //!< Item should be painted transparent
		EXT_PERMANENT_NPC = 0x0100, //!< Item is a permanent NPC
		EXT_TARGET 		 = 0x0200,  //!< Item is the current reticle target in Crusader
		EXT_FEMALE       = 0x8000	//!< Item is Crusader Female NPC (controls sfx)
	};

	// easter egg as in original: items stack to max quantity of 666
	static const int MAX_QUANTITY = 666;
};

inline const ShapeInfo *Item::getShapeInfo() const {
	if (!_cachedShapeInfo)
		_cachedShapeInfo = getShapeInfoFromGameInstance();
	return _cachedShapeInfo;
}

inline void Item::getFootpadData(int32 &X, int32 &Y, int32 &Z) const {
	const ShapeInfo *si = getShapeInfo();
	Z = si->_z;

	if (_flags & Item::FLG_FLIPPED) {
		X = si->_y;
		Y = si->_x;
	} else {
		X = si->_x;
		Y = si->_y;
	}
}

// like getFootpadData, but scaled to world coordinates
inline void Item::getFootpadWorld(int32 &X, int32 &Y, int32 &Z) const {
	const ShapeInfo *si = getShapeInfo();
	si->getFootpadWorld(X, Y, Z, _flags & Item::FLG_FLIPPED);
}

} // End of namespace Ultima8
} // End of namespace Ultima

#endif
