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