/* 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 .
*
*/
#include "ultima/ultima.h"
#include "ultima/ultima8/gumps/container_gump.h"
#include "ultima/ultima8/gfx/shape.h"
#include "ultima/ultima8/gfx/shape_frame.h"
#include "ultima/ultima8/gfx/render_surface.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/mouse.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/gfx/main_shape_archive.h"
#include "ultima/ultima8/gumps/slider_gump.h"
#include "ultima/ultima8/gumps/gump_notify_process.h"
#include "ultima/ultima8/world/item_factory.h"
#include "ultima/ultima8/world/split_item_process.h"
#include "ultima/ultima8/gumps/game_map_gump.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(ContainerGump)
ContainerGump::ContainerGump()
: ItemRelativeGump(), _displayDragging(false), _draggingShape(0),
_draggingFrame(0), _draggingFlags(0), _draggingX(0), _draggingY(0) {
}
ContainerGump::ContainerGump(const Shape *shape, uint32 frameNum, uint16 owner,
uint32 flags, int32 layer)
: ItemRelativeGump(0, 0, 5, 5, owner, flags, layer),
_displayDragging(false), _draggingShape(0), _draggingFrame(0),
_draggingFlags(0), _draggingX(0), _draggingY(0) {
_shape = shape;
_frameNum = frameNum;
}
ContainerGump::~ContainerGump() {
}
void ContainerGump::InitGump(Gump *newparent, bool take_focus) {
UpdateDimsFromShape();
// Wait with ItemRelativeGump initialization until we calculated our size.
ItemRelativeGump::InitGump(newparent, take_focus);
// make every item enter the fast area
Container *c = getContainer(_owner);
if (!c) return; // Container gone!?
for (auto *item : c->_contents) {
item->enterFastArea();
}
// Position isn't like in the original
// U8 puts a container gump slightly to the left of an object
}
void ContainerGump::run() {
Gump::run();
Container *c = getContainer(_owner);
if (!c) {
// Container gone!?
Close();
return;
}
for (auto *item : c->_contents) {
int32 itemx, itemy;
item->getGumpLocation(itemx, itemy);
const Shape *sh = item->getShapeObject();
assert(sh);
const ShapeFrame *fr = sh->getFrame(item->getFrame());
assert(fr);
// Ensure item locations within item area.
int32 minx = fr->_xoff;
int32 miny = fr->_yoff;
int32 maxx = _itemArea.width() + fr->_xoff - fr->_width;
int32 maxy = _itemArea.height() + fr->_yoff - fr->_height;
if (itemx == 0xFF && itemy == 0xFF) {
// randomize position
// TODO: maybe try to put it somewhere where it doesn't overlap others?
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
itemx = rs.getRandomNumberRng(minx, maxx);
itemy = rs.getRandomNumberRng(miny, maxy);
item->setGumpLocation(itemx, itemy);
}
if (itemx < minx) {
itemx = minx;
item->setGumpLocation(itemx, itemy);
}
if (itemx > maxx) {
itemx = maxx;
item->setGumpLocation(itemx, itemy);
}
if (itemy < miny) {
itemy = miny;
item->setGumpLocation(itemx, itemy);
}
if (itemy > maxy) {
itemy = maxy;
item->setGumpLocation(itemx, itemy);
}
}
}
void ContainerGump::getItemCoords(Item *item, int32 &itemx, int32 &itemy) {
item->getGumpLocation(itemx, itemy);
itemx += _itemArea.left;
itemy += _itemArea.top;
}
void ContainerGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scaled) {
// paint self
ItemRelativeGump::PaintThis(surf, lerp_factor, scaled);
Container *c = getContainer(_owner);
if (!c) {
// Container gone!?
Close();
return;
}
int32 gameframeno = Kernel::get_instance()->getFrameNum();
//!! TODO: check these painting commands (flipped? translucent?)
bool showEditorItems = Ultima8Engine::get_instance()->isShowEditorItems();
for (auto *item : c->_contents) {
item->setupLerp(gameframeno);
if (!showEditorItems && item->getShapeInfo()->is_editor())
continue;
int32 itemx, itemy;
getItemCoords(item, itemx, itemy);
const Shape *s = item->getShapeObject();
assert(s);
surf->Paint(s, item->getFrame(), itemx, itemy);
}
if (_displayDragging) {
int32 itemx, itemy;
itemx = _draggingX + _itemArea.left;
itemy = _draggingY + _itemArea.top;
Shape *s = GameData::get_instance()->getMainShapes()->
getShape(_draggingShape);
assert(s);
surf->PaintInvisible(s, _draggingFrame, itemx, itemy, false, (_draggingFlags & Item::FLG_FLIPPED) != 0);
}
}
// Find object (if any) at (mx,my)
// (mx,my) are relative to parent
uint16 ContainerGump::TraceObjId(int32 mx, int32 my) {
uint16 objId_ = Gump::TraceObjId(mx, my);
if (objId_ && objId_ != 65535) return objId_;
ParentToGump(mx, my);
Container *c = getContainer(_owner);
if (!c)
return 0; // Container gone!?
bool showEditorItems = Ultima8Engine::get_instance()->isShowEditorItems();
Std::list- &contents = c->_contents;
Std::list
- ::iterator iter;
// iterate backwards, since we're painting from begin() to end()
for (iter = contents.reverse_begin(); iter != contents.end(); --iter) {
Item *item = *iter;
if (!showEditorItems && item->getShapeInfo()->is_editor())
continue;
int32 itemx, itemy;
getItemCoords(item, itemx, itemy);
const Shape *s = item->getShapeObject();
assert(s);
const ShapeFrame *frame = s->getFrame(item->getFrame());
if (frame->hasPoint(mx - itemx, my - itemy)) {
// found it
return item->getObjId();
}
}
// didn't find anything, so return self
return getObjId();
}
// get item coords relative to self
bool ContainerGump::GetLocationOfItem(uint16 itemid, int32 &gx, int32 &gy,
int32 lerp_factor) {
Item *item = getItem(itemid);
if (!item) return false;
Item *parent = item->getParentAsContainer();
if (!parent) return false;
if (parent->getObjId() != _owner) return false;
//!!! need to use lerp_factor
int32 itemx, itemy;
getItemCoords(item, itemx, itemy);
gx = itemx;
gy = itemy;
return false;
}
// we don't want our position to depend on Gump of parent container
// so change the default ItemRelativeGump behaviour
void ContainerGump::GetItemLocation(int32 lerp_factor) {
Item *it = getItem(_owner);
if (!it) {
// This shouldn't ever happen, the GumpNotifyProcess should
// close us before we get here
Close();
return;
}
int32 gx, gy;
Container *root = it->getRootContainer();
Item *topitem = root ? root : it;
Gump *gump = GetRootGump()->FindGump();
assert(gump);
gump->GetLocationOfItem(topitem->getObjId(), gx, gy, lerp_factor);
// Convert the GumpSpaceCoord relative to the world/item gump
// into screenspace coords
gy = gy - it->getShapeInfo()->_z * 8 - 16;
gump->GumpToScreenSpace(gx, gy);
// Convert the screenspace coords into the coords of us
if (_parent) _parent->ScreenSpaceToGump(gx, gy);
// Set x and y, and center us over it
_ix = gx - _dims.width() / 2;
_iy = gy - _dims.height();
}
void ContainerGump::Close(bool no_del) {
// close any gumps belonging to contents
// and make every item leave the fast area
Container *c = getContainer(_owner);
if (!c) return; // Container gone!?
Std::list
- &contents = c->_contents;
Std::list
- ::iterator iter = contents.begin();
while (iter != contents.end()) {
Item *item = *iter;
++iter;
Gump *g = getGump(item->getGump());
if (g) {
g->Close(); //!! what about no_del?
}
item->leaveFastArea(); // Can destroy the item
}
Item *o = getItem(_owner);
if (o)
o->clearGump(); //!! is this the appropriate place?
ItemRelativeGump::Close(no_del);
}
Container *ContainerGump::getTargetContainer(Item *item, int mx, int my) {
int32 px = mx, py = my;
GumpToParent(px, py);
Container *targetcontainer = getContainer(TraceObjId(px, py));
if (targetcontainer && targetcontainer->getObjId() == item->getObjId())
targetcontainer = nullptr;
if (targetcontainer) {
const ShapeInfo *targetinfo = targetcontainer->getShapeInfo();
if ((targetcontainer->getObjId() == item->getObjId()) ||
targetinfo->is_land() ||
targetcontainer->hasFlags(Item::FLG_IN_NPC_LIST)) {
targetcontainer = nullptr;
}
}
if (!targetcontainer)
targetcontainer = getContainer(_owner);
return targetcontainer;
}
Gump *ContainerGump::onMouseDown(int button, int32 mx, int32 my) {
Gump *handled = Gump::onMouseDown(button, mx, my);
if (handled) return handled;
// only interested in left clicks
if (button == Mouse::BUTTON_LEFT)
return this;
return nullptr;
}
void ContainerGump::onMouseClick(int button, int32 mx, int32 my) {
if (button == Mouse::BUTTON_LEFT) {
uint16 objID = TraceObjId(mx, my);
Item *item = getItem(objID);
if (item) {
debugC(kDebugObject, "%s", item->dumpInfo().c_str());
if (Ultima8Engine::get_instance()->isAvatarInStasis()) {
debugC(kDebugObject, "Can't look: avatarInStasis");
} else {
item->callUsecodeEvent_look();
}
}
}
}
void ContainerGump::onMouseDouble(int button, int32 mx, int32 my) {
if (button == Mouse::BUTTON_LEFT) {
uint16 objID = TraceObjId(mx, my);
if (objID == getObjId()) {
objID = _owner; // use container when double click on self
}
Item *item = getItem(objID);
if (item) {
debugC(kDebugObject, "%s", item->dumpInfo().c_str());
if (objID == _owner) {
// call the 'use' event
item->use();
return;
}
if (Ultima8Engine::get_instance()->isAvatarInStasis()) {
debugC(kDebugObject, "Can't use: avatarInStasis");
return;
}
MainActor *avatar = getMainActor();
if (avatar->canReach(item, 128)) { // CONSTANT!
// call the 'use' event
item->use();
} else {
Mouse::get_instance()->flashCrossCursor();
}
}
}
}
bool ContainerGump::StartDraggingItem(Item *item, int mx, int my) {
// probably don't need to check if item can be moved, since it shouldn't
// be in a container otherwise
Container *c = getContainer(_owner);
assert(c);
// check if the container the item is in is in range
MainActor *avatar = getMainActor();
if (!avatar->canReach(c, 128)) return false;
int32 itemx, itemy;
getItemCoords(item, itemx, itemy);
Mouse::get_instance()->setDraggingOffset(mx - itemx, my - itemy);
return true;
}
bool ContainerGump::DraggingItem(Item *item, int mx, int my) {
Container *c = getContainer(_owner);
assert(c);
// check if the container the item is in is in range
MainActor *avatar = getMainActor();
if (!avatar->canReach(c, 128)) {
_displayDragging = false;
return false;
}
int32 dox, doy;
Mouse::get_instance()->getDraggingOffset(dox, doy);
Mouse::get_instance()->setMouseCursor(Mouse::MOUSE_TARGET);
_displayDragging = true;
_draggingShape = item->getShape();
_draggingFrame = item->getFrame();
_draggingFlags = item->getFlags();
// determine target location and set dragging_x/y
_draggingX = mx - _itemArea.left - dox;
_draggingY = my - _itemArea.top - doy;
const Shape *sh = item->getShapeObject();
assert(sh);
const ShapeFrame *fr = sh->getFrame(_draggingFrame);
assert(fr);
if (_draggingX - fr->_xoff < 0 ||
_draggingX - fr->_xoff + fr->_width > _itemArea.width() ||
_draggingY - fr->_yoff < 0 ||
_draggingY - fr->_yoff + fr->_height > _itemArea.height()) {
_displayDragging = false;
return false;
}
// check if item will fit (weight/volume/adding container to itself)
Container *target = getTargetContainer(item, mx, my);
if (!target || !target->CanAddItem(item, true)) {
_displayDragging = false;
return false;
}
return true;
}
void ContainerGump::DraggingItemLeftGump(Item *item) {
_displayDragging = false;
}
void ContainerGump::StopDraggingItem(Item *item, bool moved) {
if (!moved) return; // nothing to do
}
void ContainerGump::DropItem(Item *item, int mx, int my) {
_displayDragging = false;
int32 px = mx, py = my;
GumpToParent(px, py);
// see what the item is being dropped on
Item *targetitem = getItem(TraceObjId(px, py));
Container *targetcontainer = dynamic_cast(targetitem);
if (item->getShapeInfo()->hasQuantity() &&
item->getQuality() > 1) {
// more than one, so see if we should ask if we should split it up
Item *splittarget = nullptr;
// also try to combine
if (targetitem && item->canMergeWith(targetitem)) {
splittarget = targetitem;
}
if (!splittarget) {
// create new item
splittarget = ItemFactory::createItem(
item->getShape(), item->getFrame(), 0,
item->getFlags() & (Item::FLG_DISPOSABLE | Item::FLG_OWNED | Item::FLG_INVISIBLE | Item::FLG_FLIPPED | Item::FLG_FAST_ONLY | Item::FLG_LOW_FRICTION), item->getNpcNum(), item->getMapNum(),
item->getExtFlags() & (Item::EXT_SPRITE | Item::EXT_HIGHLIGHT | Item::EXT_TRANSPARENT), true);
if (!splittarget) {
warning("ContainerGump failed to create item (%u,%u) while splitting",
item->getShape(), item->getFrame());
return;
}
if (targetcontainer) {
splittarget->moveToContainer(targetcontainer);
splittarget->randomGumpLocation();
} else {
splittarget->moveToContainer(getContainer(_owner));
splittarget->setGumpLocation(_draggingX, _draggingY);
}
}
SliderGump *slidergump = new SliderGump(100, 100,
0, item->getQuality(),
item->getQuality());
slidergump->InitGump(0);
slidergump->CreateNotifier(); // manually create notifier
Process *notifier = slidergump->GetNotifyProcess();
SplitItemProcess *splitproc = new SplitItemProcess(item, splittarget);
Kernel::get_instance()->addProcess(splitproc);
splitproc->waitFor(notifier);
return;
}
if (targetitem && item->getShapeInfo()->hasQuantity()) {
// try to combine items
if (item->canMergeWith(targetitem)) {
uint16 newquant = targetitem->getQuality() + item->getQuality();
if (newquant > Item::MAX_QUANTITY) {
item->setQuality(newquant - Item::MAX_QUANTITY);
targetitem->setQuality(Item::MAX_QUANTITY);
// maybe this isn't needed? original doesn't do it here..
targetitem->callUsecodeEvent_combine();
} else {
targetitem->setQuality(newquant);
targetitem->callUsecodeEvent_combine();
// combined, so delete other
item->destroy();
}
return;
}
}
targetcontainer = getTargetContainer(item, mx, my);
assert(targetcontainer);
if (targetcontainer->getObjId() != _owner) {
if (item->getParent() == targetcontainer->getObjId()) {
// already in this container, so move item to let it be drawn
// on top of all other items
targetcontainer->moveItemToEnd(item);
} else {
item->moveToContainer(targetcontainer);
item->randomGumpLocation();
}
} else {
// add item to self
if (item->getParent() == _owner) {
targetcontainer->moveItemToEnd(item);
} else {
item->moveToContainer(targetcontainer);
}
int32 dox, doy;
Mouse::get_instance()->getDraggingOffset(dox, doy);
_draggingX = mx - _itemArea.left - dox;
_draggingY = my - _itemArea.top - doy;
item->setGumpLocation(_draggingX, _draggingY);
}
}
void ContainerGump::saveData(Common::WriteStream *ws) {
ItemRelativeGump::saveData(ws);
ws->writeUint32LE(static_cast(_itemArea.left));
ws->writeUint32LE(static_cast(_itemArea.top));
ws->writeUint32LE(static_cast(_itemArea.width()));
ws->writeUint32LE(static_cast(_itemArea.height()));
}
bool ContainerGump::loadData(Common::ReadStream *rs, uint32 version) {
if (!ItemRelativeGump::loadData(rs, version)) return false;
int32 iax = static_cast(rs->readUint32LE());
int32 iay = static_cast(rs->readUint32LE());
int32 iaw = static_cast(rs->readUint32LE());
int32 iah = static_cast(rs->readUint32LE());
_itemArea.moveTo(iax, iay);
_itemArea.setWidth(iaw);
_itemArea.setHeight(iah);
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima