1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ultima/ultima8/gumps/container_gump.h"
24
25 #include "ultima/ultima8/graphics/shape.h"
26 #include "ultima/ultima8/graphics/shape_frame.h"
27 #include "ultima/ultima8/graphics/render_surface.h"
28 #include "ultima/ultima8/ultima8.h"
29 #include "ultima/ultima8/kernel/kernel.h"
30 #include "ultima/ultima8/kernel/mouse.h"
31 #include "ultima/ultima8/games/game_data.h"
32 #include "ultima/ultima8/graphics/main_shape_archive.h"
33 #include "ultima/ultima8/gumps/slider_gump.h"
34 #include "ultima/ultima8/gumps/gump_notify_process.h"
35 #include "ultima/ultima8/world/item_factory.h"
36 #include "ultima/ultima8/world/split_item_process.h"
37 #include "ultima/ultima8/gumps/game_map_gump.h"
38 #include "ultima/ultima8/world/actors/main_actor.h"
39 #include "ultima/ultima8/world/get_object.h"
40
41 namespace Ultima {
42 namespace Ultima8 {
43
DEFINE_RUNTIME_CLASSTYPE_CODE(ContainerGump)44 DEFINE_RUNTIME_CLASSTYPE_CODE(ContainerGump)
45
46 ContainerGump::ContainerGump()
47 : ItemRelativeGump(), _displayDragging(false), _draggingShape(0),
48 _draggingFrame(0), _draggingFlags(0), _draggingX(0), _draggingY(0) {
49
50 }
51
ContainerGump(const Shape * shape,uint32 frameNum,uint16 owner,uint32 flags,int32 layer)52 ContainerGump::ContainerGump(const Shape *shape, uint32 frameNum, uint16 owner,
53 uint32 flags, int32 layer)
54 : ItemRelativeGump(0, 0, 5, 5, owner, flags, layer),
55 _displayDragging(false), _draggingShape(0), _draggingFrame(0),
56 _draggingFlags(0), _draggingX(0), _draggingY(0) {
57 _shape = shape;
58 _frameNum = frameNum;
59 }
60
~ContainerGump()61 ContainerGump::~ContainerGump() {
62 }
63
InitGump(Gump * newparent,bool take_focus)64 void ContainerGump::InitGump(Gump *newparent, bool take_focus) {
65 UpdateDimsFromShape();
66
67 // Wait with ItemRelativeGump initialization until we calculated our size.
68 ItemRelativeGump::InitGump(newparent, take_focus);
69
70 // make every item enter the fast area
71 Container *c = getContainer(_owner);
72
73 if (!c) return; // Container gone!?
74
75 Std::list<Item *> &contents = c->_contents;
76 Std::list<Item *>::iterator iter;
77 for (iter = contents.begin(); iter != contents.end(); ++iter) {
78 (*iter)->enterFastArea();
79 }
80
81
82 // Position isn't like in the original
83 // U8 puts a container gump slightly to the left of an object
84 }
85
getItemCoords(Item * item,int32 & itemx,int32 & itemy)86 void ContainerGump::getItemCoords(Item *item, int32 &itemx, int32 &itemy) {
87 item->getGumpLocation(itemx, itemy);
88
89 if (itemx == 0xFF && itemy == 0xFF) {
90 // randomize position
91 // TODO: maybe try to put it somewhere where it doesn't overlap others?
92
93 itemx = getRandom() % _itemArea.width();
94 itemy = getRandom() % _itemArea.height();
95
96 item->setGumpLocation(itemx, itemy);
97 }
98
99 itemx += _itemArea.left;
100 itemy += _itemArea.top;
101 }
102
103
PaintThis(RenderSurface * surf,int32 lerp_factor,bool scaled)104 void ContainerGump::PaintThis(RenderSurface *surf, int32 lerp_factor, bool scaled) {
105 // paint self
106 ItemRelativeGump::PaintThis(surf, lerp_factor, scaled);
107
108 Container *c = getContainer(_owner);
109
110 if (!c) {
111 // Container gone!?
112 Close();
113 return;
114 }
115
116 Std::list<Item *> &contents = c->_contents;
117 int32 gameframeno = Kernel::get_instance()->getFrameNum();
118
119 //!! TODO: check these painting commands (flipped? translucent?)
120 bool paintEditorItems = Ultima8Engine::get_instance()->isPaintEditorItems();
121
122 Std::list<Item *>::iterator iter;
123 for (iter = contents.begin(); iter != contents.end(); ++iter) {
124 Item *item = *iter;
125 item->setupLerp(gameframeno);
126
127 if (!paintEditorItems && item->getShapeInfo()->is_editor())
128 continue;
129
130 int32 itemx, itemy;
131 getItemCoords(item, itemx, itemy);
132 const Shape *s = item->getShapeObject();
133 assert(s);
134 surf->Paint(s, item->getFrame(), itemx, itemy);
135 }
136
137
138 if (_displayDragging) {
139 int32 itemx, itemy;
140 itemx = _draggingX + _itemArea.left;
141 itemy = _draggingY + _itemArea.top;
142 Shape *s = GameData::get_instance()->getMainShapes()->
143 getShape(_draggingShape);
144 assert(s);
145 surf->PaintInvisible(s, _draggingFrame, itemx, itemy, false, (_draggingFlags & Item::FLG_FLIPPED) != 0);
146 }
147
148 }
149
150 // Find object (if any) at (mx,my)
151 // (mx,my) are relative to parent
TraceObjId(int32 mx,int32 my)152 uint16 ContainerGump::TraceObjId(int32 mx, int32 my) {
153 uint16 objId_ = Gump::TraceObjId(mx, my);
154 if (objId_ && objId_ != 65535) return objId_;
155
156 ParentToGump(mx, my);
157
158 Container *c = getContainer(_owner);
159
160 if (!c)
161 return 0; // Container gone!?
162
163 bool paintEditorItems = Ultima8Engine::get_instance()->isPaintEditorItems();
164
165 Std::list<Item *> &contents = c->_contents;
166 Std::list<Item *>::reverse_iterator iter;
167
168 // iterate backwards, since we're painting from begin() to end()
169 for (iter = contents.rbegin(); iter != contents.rend(); ++iter) {
170 Item *item = *iter;
171 if (!paintEditorItems && item->getShapeInfo()->is_editor())
172 continue;
173
174 int32 itemx, itemy;
175 getItemCoords(item, itemx, itemy);
176 const Shape *s = item->getShapeObject();
177 assert(s);
178 const ShapeFrame *frame = s->getFrame(item->getFrame());
179
180 if (frame->hasPoint(mx - itemx, my - itemy)) {
181 // found it
182 return item->getObjId();
183 }
184 }
185
186 // didn't find anything, so return self
187 return getObjId();
188 }
189
190 // get item coords relative to self
GetLocationOfItem(uint16 itemid,int32 & gx,int32 & gy,int32 lerp_factor)191 bool ContainerGump::GetLocationOfItem(uint16 itemid, int32 &gx, int32 &gy,
192 int32 lerp_factor) {
193 Item *item = getItem(itemid);
194 if (!item) return false;
195 Item *parent = item->getParentAsContainer();
196 if (!parent) return false;
197 if (parent->getObjId() != _owner) return false;
198
199 //!!! need to use lerp_factor
200
201 int32 itemx, itemy;
202 getItemCoords(item, itemx, itemy);
203
204 gx = itemx;
205 gy = itemy;
206
207 return false;
208 }
209
210 // we don't want our position to depend on Gump of parent container
211 // so change the default ItemRelativeGump behaviour
GetItemLocation(int32 lerp_factor)212 void ContainerGump::GetItemLocation(int32 lerp_factor) {
213 Item *it = getItem(_owner);
214
215 if (!it) {
216 // This shouldn't ever happen, the GumpNotifyProcess should
217 // close us before we get here
218 Close();
219 return;
220 }
221
222 int32 gx, gy;
223 Item *topitem = it;
224
225 Container *p = it->getParentAsContainer();
226 if (p) {
227 while (p->getParentAsContainer()) {
228 p = p->getParentAsContainer();
229 }
230
231 topitem = p;
232 }
233
234 Gump *gump = GetRootGump()->FindGump<GameMapGump>();
235 assert(gump);
236 gump->GetLocationOfItem(topitem->getObjId(), gx, gy, lerp_factor);
237
238 // Convert the GumpSpaceCoord relative to the world/item gump
239 // into screenspace coords
240 gy = gy - it->getShapeInfo()->_z * 8 - 16;
241 gump->GumpToScreenSpace(gx, gy);
242
243 // Convert the screenspace coords into the coords of us
244 if (_parent) _parent->ScreenSpaceToGump(gx, gy);
245
246 // Set x and y, and center us over it
247 _ix = gx - _dims.width() / 2;
248 _iy = gy - _dims.height();
249 }
250
Close(bool no_del)251 void ContainerGump::Close(bool no_del) {
252 // close any gumps belonging to contents
253 // and make every item leave the fast area
254 Container *c = getContainer(_owner);
255 if (!c) return; // Container gone!?
256
257 Std::list<Item *> &contents = c->_contents;
258 Std::list<Item *>::iterator iter = contents.begin();
259 while (iter != contents.end()) {
260 Item *item = *iter;
261 ++iter;
262 Gump *g = getGump(item->getGump());
263 if (g) {
264 g->Close(); //!! what about no_del?
265 }
266 item->leaveFastArea(); // Can destroy the item
267 }
268
269 Item *o = getItem(_owner);
270 if (o)
271 o->clearGump(); //!! is this the appropriate place?
272
273 ItemRelativeGump::Close(no_del);
274 }
275
getTargetContainer(Item * item,int mx,int my)276 Container *ContainerGump::getTargetContainer(Item *item, int mx, int my) {
277 int32 px = mx, py = my;
278 GumpToParent(px, py);
279 Container *targetcontainer = getContainer(TraceObjId(px, py));
280
281 if (targetcontainer && targetcontainer->getObjId() == item->getObjId())
282 targetcontainer = nullptr;
283
284 if (targetcontainer) {
285 const ShapeInfo *targetinfo = targetcontainer->getShapeInfo();
286 if ((targetcontainer->getObjId() == item->getObjId()) ||
287 targetinfo->is_land() ||
288 targetcontainer->hasFlags(Item::FLG_IN_NPC_LIST)) {
289 targetcontainer = nullptr;
290 }
291 }
292
293 if (!targetcontainer)
294 targetcontainer = getContainer(_owner);
295
296 return targetcontainer;
297 }
298
299
onMouseDown(int button,int32 mx,int32 my)300 Gump *ContainerGump::onMouseDown(int button, int32 mx, int32 my) {
301 Gump *handled = Gump::onMouseDown(button, mx, my);
302 if (handled) return handled;
303
304 // only interested in left clicks
305 if (button == Shared::BUTTON_LEFT)
306 return this;
307
308 return nullptr;
309 }
310
onMouseClick(int button,int32 mx,int32 my)311 void ContainerGump::onMouseClick(int button, int32 mx, int32 my) {
312 if (button == Shared::BUTTON_LEFT) {
313 uint16 objID = TraceObjId(mx, my);
314
315 Item *item = getItem(objID);
316 if (item) {
317 item->dumpInfo();
318
319 if (Ultima8Engine::get_instance()->isAvatarInStasis()) {
320 pout << "Can't look: avatarInStasis" << Std::endl;
321 } else {
322 item->callUsecodeEvent_look();
323 }
324 }
325 }
326 }
327
onMouseDouble(int button,int32 mx,int32 my)328 void ContainerGump::onMouseDouble(int button, int32 mx, int32 my) {
329 if (button == Shared::BUTTON_LEFT) {
330 uint16 objID = TraceObjId(mx, my);
331
332 if (objID == getObjId()) {
333 objID = _owner; // use container when double click on self
334 }
335
336 Item *item = getItem(objID);
337 if (item) {
338 item->dumpInfo();
339
340 if (Ultima8Engine::get_instance()->isAvatarInStasis()) {
341 pout << "Can't use: avatarInStasis" << Std::endl;
342 return;
343 }
344
345 MainActor *avatar = getMainActor();
346 if (objID == _owner || avatar->canReach(item, 128)) { // CONSTANT!
347 // call the 'use' event
348 item->use();
349 } else {
350 Mouse::get_instance()->flashCrossCursor();
351 }
352 }
353 }
354 }
355
356
StartDraggingItem(Item * item,int mx,int my)357 bool ContainerGump::StartDraggingItem(Item *item, int mx, int my) {
358 // probably don't need to check if item can be moved, since it shouldn't
359 // be in a container otherwise
360
361 Container *c = getContainer(_owner);
362 assert(c);
363
364 // check if the container the item is in is in range
365 MainActor *avatar = getMainActor();
366 if (!avatar->canReach(c, 128)) return false;
367
368 int32 itemx, itemy;
369 getItemCoords(item, itemx, itemy);
370
371 Mouse::get_instance()->setDraggingOffset(mx - itemx, my - itemy);
372
373 return true;
374 }
375
DraggingItem(Item * item,int mx,int my)376 bool ContainerGump::DraggingItem(Item *item, int mx, int my) {
377 Container *c = getContainer(_owner);
378 assert(c);
379
380 // check if the container the item is in is in range
381 MainActor *avatar = getMainActor();
382 if (!avatar->canReach(c, 128)) {
383 _displayDragging = false;
384 return false;
385 }
386
387 int32 dox, doy;
388 Mouse::get_instance()->getDraggingOffset(dox, doy);
389 Mouse::get_instance()->setMouseCursor(Mouse::MOUSE_TARGET);
390 _displayDragging = true;
391
392 _draggingShape = item->getShape();
393 _draggingFrame = item->getFrame();
394 _draggingFlags = item->getFlags();
395
396 // determine target location and set dragging_x/y
397
398 _draggingX = mx - _itemArea.left - dox;
399 _draggingY = my - _itemArea.top - doy;
400
401 const Shape *sh = item->getShapeObject();
402 assert(sh);
403 const ShapeFrame *fr = sh->getFrame(_draggingFrame);
404 assert(fr);
405
406 if (_draggingX - fr->_xoff < 0 ||
407 _draggingX - fr->_xoff + fr->_width > _itemArea.width() ||
408 _draggingY - fr->_yoff < 0 ||
409 _draggingY - fr->_yoff + fr->_height > _itemArea.height()) {
410 _displayDragging = false;
411 return false;
412 }
413
414 // check if item will fit (weight/volume/adding container to itself)
415 Container *target = getTargetContainer(item, mx, my);
416 if (!target || !target->CanAddItem(item, true)) {
417 _displayDragging = false;
418 return false;
419 }
420
421 return true;
422 }
423
DraggingItemLeftGump(Item * item)424 void ContainerGump::DraggingItemLeftGump(Item *item) {
425 _displayDragging = false;
426 }
427
428
StopDraggingItem(Item * item,bool moved)429 void ContainerGump::StopDraggingItem(Item *item, bool moved) {
430 if (!moved) return; // nothing to do
431 }
432
DropItem(Item * item,int mx,int my)433 void ContainerGump::DropItem(Item *item, int mx, int my) {
434 _displayDragging = false;
435
436 int32 px = mx, py = my;
437 GumpToParent(px, py);
438 // see what the item is being dropped on
439 Item *targetitem = getItem(TraceObjId(px, py));
440 Container *targetcontainer = dynamic_cast<Container *>(targetitem);
441
442
443 if (item->getShapeInfo()->hasQuantity() &&
444 item->getQuality() > 1) {
445 // more than one, so see if we should ask if we should split it up
446
447 Item *splittarget = nullptr;
448
449 // also try to combine
450 if (targetitem && item->canMergeWith(targetitem)) {
451 splittarget = targetitem;
452 }
453
454 if (!splittarget) {
455 // create new item
456 splittarget = ItemFactory::createItem(
457 item->getShape(), item->getFrame(), 0,
458 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(),
459 item->getExtFlags() & (Item::EXT_SPRITE | Item::EXT_HIGHLIGHT | Item::EXT_TRANSPARENT), true);
460 if (!splittarget) {
461 perr << "ContainerGump failed to create item ("
462 << item->getShape() << "," << item->getFrame()
463 << ") while splitting" << Std::endl;
464 return;
465 }
466
467
468 if (targetcontainer) {
469 splittarget->moveToContainer(targetcontainer);
470 splittarget->randomGumpLocation();
471 } else {
472 splittarget->moveToContainer(getContainer(_owner));
473 splittarget->setGumpLocation(_draggingX, _draggingY);
474 }
475 }
476
477 SliderGump *slidergump = new SliderGump(100, 100,
478 0, item->getQuality(),
479 item->getQuality());
480 slidergump->InitGump(0);
481 slidergump->CreateNotifier(); // manually create notifier
482 Process *notifier = slidergump->GetNotifyProcess();
483 SplitItemProcess *splitproc = new SplitItemProcess(item, splittarget);
484 Kernel::get_instance()->addProcess(splitproc);
485 splitproc->waitFor(notifier);
486
487 return;
488 }
489
490 if (targetitem && item->getShapeInfo()->hasQuantity()) {
491 // try to combine items
492 if (item->canMergeWith(targetitem)) {
493 uint16 newquant = targetitem->getQuality() + item->getQuality();
494 // easter egg as in original: items stack to max quantity of 666
495 if (newquant > 666) {
496 item->setQuality(newquant - 666);
497 targetitem->setQuality(666);
498 // maybe this isn't needed? original doesn't do it here..
499 targetitem->callUsecodeEvent_combine();
500 } else {
501 targetitem->setQuality(newquant);
502 targetitem->callUsecodeEvent_combine();
503 // combined, so delete other
504 item->destroy();
505 }
506 return;
507 }
508 }
509
510 targetcontainer = getTargetContainer(item, mx, my);
511 assert(targetcontainer);
512
513 if (targetcontainer->getObjId() != _owner) {
514 if (item->getParent() == targetcontainer->getObjId()) {
515 // already in this container, so move item to let it be drawn
516 // on top of all other items
517 targetcontainer->moveItemToEnd(item);
518 } else {
519 item->moveToContainer(targetcontainer);
520 item->randomGumpLocation();
521 }
522 } else {
523 // add item to self
524
525 if (item->getParent() == _owner) {
526 targetcontainer->moveItemToEnd(item);
527 } else {
528 item->moveToContainer(targetcontainer);
529 }
530
531 int32 dox, doy;
532 Mouse::get_instance()->getDraggingOffset(dox, doy);
533 _draggingX = mx - _itemArea.left - dox;
534 _draggingY = my - _itemArea.top - doy;
535 item->setGumpLocation(_draggingX, _draggingY);
536 }
537 }
538
saveData(Common::WriteStream * ws)539 void ContainerGump::saveData(Common::WriteStream *ws) {
540 ItemRelativeGump::saveData(ws);
541
542 ws->writeUint32LE(static_cast<uint32>(_itemArea.left));
543 ws->writeUint32LE(static_cast<uint32>(_itemArea.top));
544 ws->writeUint32LE(static_cast<uint32>(_itemArea.width()));
545 ws->writeUint32LE(static_cast<uint32>(_itemArea.height()));
546 }
547
loadData(Common::ReadStream * rs,uint32 version)548 bool ContainerGump::loadData(Common::ReadStream *rs, uint32 version) {
549 if (!ItemRelativeGump::loadData(rs, version)) return false;
550
551 int32 iax = static_cast<int32>(rs->readUint32LE());
552 int32 iay = static_cast<int32>(rs->readUint32LE());
553 int32 iaw = static_cast<int32>(rs->readUint32LE());
554 int32 iah = static_cast<int32>(rs->readUint32LE());
555 _itemArea.moveTo(iax, iay);
556 _itemArea.setWidth(iaw);
557 _itemArea.setHeight(iah);
558
559 return true;
560 }
561
562 } // End of namespace Ultima8
563 } // End of namespace Ultima
564