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